/**
 * Design system — charte TIBO Médical (§7.6 du cahier des charges).
 * Architecture d'écran calquée sur Odoo : header à deux barres
 * (navigation applicative + barre de contrôle), menu horizontal,
 * lanceur d'applications plein écran en page d'accueil.
 */

:root {
  /* Marque TIBO (invariante) */
  --brand-blue: #0B21C9;
  --brand-blue-mid: #4A7DDE;
  --brand-blue-light: #5FA6E0;
  --brand-teal: #3FC3BE;
  --brand-green: #44BFA3;

  /* Surfaces et texte — charte « encre et bleu froid » (design du 13/08/2026).
     Les NOMS des jetons ne changent pas : les quarante composants déjà écrits
     s'y réfèrent, et repeindre l'application revient donc à changer ces
     valeurs — pas à retoucher chaque écran. */
  --bg: #F2F4F7;
  --surface: #FFFFFF;
  --surface-2: #EEF1F6;
  --ink: #232833;
  /* Le design donne #79818F au texte secondaire ; il ne tient que 3,56:1 sur
     le fond de page, sous le seuil AA. On l'assombrit du minimum nécessaire
     pour atteindre 4,7:1 — l'écart est imperceptible, la lisibilité non. */
  --muted: #656D7E;
  --line: #DFE4EC;

  /* Encre : la barre latérale et la barre d'application. Assez sombre pour
     que le contenu paraisse posé dessus, jamais noir — le noir pur durcit
     les icônes au trait. */
  --ink-surface: #141A24;
  --ink-surface-2: #1C2431;
  --ink-on: #FFFFFF;
  --ink-on-muted: #AEB7C6;
  --ink-on-dim: #7C8697;
  --ink-line: rgba(255, 255, 255, .07);
  --ink-hover: rgba(255, 255, 255, .06);
  --ink-active: rgba(255, 255, 255, .10);
  /* L'accent doit rester lisible SUR l'encre : #2F6FEB y tombe à 3,4:1. */
  --ink-accent: #8AB4FF;

  /* Action */
  --accent: #2F6FEB;
  --accent-ink: #FFFFFF;
  --accent-soft: #ECF2FE;
  --accent-strong: #1C4FAE;

  /* IA — turquoise, jamais confondu avec l'accent bleu */
  --ai-surface: #E8F7F6;
  --ai-border: #3FC3BE;

  /* Vert de marque assombri pour le TEXTE : #44BFA3 ne donne que 2,3:1 sur
     blanc, insuffisant pour être lu. Réservé aux libellés ; les aplats et
     liserés continuent d'utiliser --brand-green. */
  --brand-green-text: #16775F;

  /* Sémantique (non personnalisable) */
  --success: #1B7A4B;
  --success-soft: #EAF2ED;
  --warning: #8A5A12;
  --warning-soft: #FAF0E1;
  --danger: #C22C2C;
  --danger-soft: #FBEAEA;
  --favorite: #B98700;
  --favorite-soft: #FDF3D7;

  /* Échelle typographique 12/13/15/18/24 */
  --fs-label: 12px;
  --fs-dense: 13px;
  --fs-body: 15px;
  --fs-section: 18px;
  --fs-page: 24px;

  /* Grille 4 px. `--sp-5` manquait à l'échelle alors que huit règles
     l'employaient : une variable non définie ne rend RIEN, si bien que les
     marges de l'assistant de campagne étaient nulles — d'où son rendu collé
     aux bords. Le pas suit la grille, il n'y avait pas de raison de le
     sauter. */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 24px; --sp-8: 32px;
  /* Rayons de la nouvelle charte : 8 px pour les contrôles, 10 px pour les
     cartes — les deux valeurs dominantes du design importé. */
  --radius-control: 8px;
  --radius-card: 10px;

  --shadow-card: 0 1px 2px rgba(15, 23, 42, .05), 0 1px 3px rgba(15, 23, 42, .04);
  --shadow-overlay: 0 10px 30px -12px rgba(15, 23, 42, .3);

  --header-h: 50px;
  --control-h: 46px;

  /* Barre latérale : dépliée, et repliée en rail d'icônes. */
  --sidebar-w: 216px;
  --sidebar-rail-w: 60px;

  --font: "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "Cascadia Mono", Consolas, ui-monospace, monospace;
}

/* ── Thème sombre ──────────────────────────────────────────────────────
   Trois états possibles :
     data-theme="light"  → clair imposé, même si le système est en sombre
     data-theme="dark"   → sombre imposé, même si le système est en clair
     (aucun attribut)    → on suit le réglage du système
   Le réglage se change dans Paramètres › Général › Apparence. */

/* La palette sombre est déclinée de la charte claire : même bleu froid
   éclairci pour rester lisible, mêmes neutres refroidis. Elle est écrite
   DEUX fois — media query et attribut — car une variable CSS ne peut pas
   porter un bloc de déclarations. Les deux listes doivent rester identiques :
   toute couleur ajoutée ici doit l'être aux deux endroits. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0F131A; --surface: #171C26; --surface-2: #1F2632;
    --ink: #E7EAF0; --muted: #98A1B2; --line: #2A323F;
    --accent: #6E9BFF; --accent-ink: #0F131A; --accent-soft: #1B2640; --accent-strong: #9CBBFF;
    /* En sombre, l'encre des barres s'éclaircit à peine : elle doit rester
       distincte du fond de page, sans quoi la barre latérale disparaît. */
    --ink-surface: #10151D; --ink-surface-2: #1A212C;
    --ink-on: #FFFFFF; --ink-on-muted: #AEB7C6; --ink-on-dim: #7C8697;
    --ink-line: rgba(255,255,255,.08);
    --ink-hover: rgba(255,255,255,.06); --ink-active: rgba(255,255,255,.12);
    --ink-accent: #8AB4FF;
    --ai-surface: #12332F; --ai-border: #5CCFC5;
    --brand-green-text: #5FD4B6;   /* sur fond sombre, c'est l'inverse : on éclaircit */
    --success: #54C892; --success-soft: #123328;
    --warning: #E0A458; --warning-soft: #33290F;
    --danger: #F08A8A; --danger-soft: #3A2024;
    --favorite: #E8C158; --favorite-soft: #33290F;
    --shadow-card: 0 1px 2px rgba(0,0,0,.4);
    --shadow-overlay: 0 10px 30px -12px rgba(0,0,0,.6);
  }
}

/* Sombre choisi explicitement : mêmes valeurs, hors media query. */
:root[data-theme="dark"] {
  --bg: #0F131A; --surface: #171C26; --surface-2: #1F2632;
  --ink: #E7EAF0; --muted: #98A1B2; --line: #2A323F;
  --accent: #6E9BFF; --accent-ink: #0F131A; --accent-soft: #1B2640; --accent-strong: #9CBBFF;
  --ink-surface: #10151D; --ink-surface-2: #1A212C;
  --ink-on: #FFFFFF; --ink-on-muted: #AEB7C6; --ink-on-dim: #7C8697;
  --ink-line: rgba(255,255,255,.08);
  --ink-hover: rgba(255,255,255,.06); --ink-active: rgba(255,255,255,.12);
  --ink-accent: #8AB4FF;
  --ai-surface: #12332F; --ai-border: #5CCFC5;
  --brand-green-text: #5FD4B6;
  --success: #54C892; --success-soft: #123328;
  --warning: #E0A458; --warning-soft: #33290F;
  --danger: #F08A8A; --danger-soft: #3A2024;
  --favorite: #E8C158; --favorite-soft: #33290F;
  --shadow-card: 0 1px 2px rgba(0,0,0,.4);
  --shadow-overlay: 0 10px 30px -12px rgba(0,0,0,.6);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.55;
}

.num, td.num, .metric-value { font-variant-numeric: tabular-nums; }
button, input, select { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-control);
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ══ ENVELOPPE — rail applicatif + colonne d'écran ═════════════════════
   Les applications quittent la barre horizontale pour une barre latérale.
   Deux raisons : la liste s'allonge (huit applications) sans disputer sa
   place au menu des sections, et le nom de chacune reste lisible — l'ancien
   bandeau tronquait les libellés dès qu'on descendait sous 1000 px. */

.shell { display: flex; align-items: stretch; min-height: 100vh; }
.shell-main {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
}

/* ── Barre latérale ──────────────────────────────────────────────────── */
.sidebar {
  width: var(--sidebar-w); flex: none;
  background: var(--ink-surface);
  border-right: 1px solid var(--ink-line);
  padding: 14px 8px 12px;
  display: flex; flex-direction: column; gap: var(--sp-4);
  position: sticky; top: 0; height: 100vh;
  z-index: 45;
}
/* Repliée : seules les icônes restent. La largeur est la seule chose qui
   change — les règles ci-dessous masquent les textes en s'y accrochant. */
.sidebar[data-rail="1"] { width: var(--sidebar-rail-w); padding-inline: 6px; }
.sidebar[data-rail="1"] .sidebar-brand-name,
.sidebar[data-rail="1"] .sidebar-label,
.sidebar[data-rail="1"] .sidebar-user-text,
.sidebar[data-rail="1"] .sidebar-user-caret { display: none; }
.sidebar[data-rail="1"] .sidebar-item,
.sidebar[data-rail="1"] .sidebar-user { justify-content: center; padding-inline: 0; }
.sidebar[data-rail="1"] .sidebar-head { flex-direction: column; gap: var(--sp-2); }

.sidebar-head { display: flex; align-items: center; gap: var(--sp-2); padding: 0 6px; }
.sidebar-brand {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  border: 0; background: none; cursor: pointer; padding: 0;
  color: var(--ink-on);
}
.sidebar-mark {
  width: 26px; height: 26px; flex: none; border-radius: 7px;
  background: var(--accent); display: grid; place-items: center; overflow: hidden;
}
.sidebar-mark img { width: 18px; height: 18px; object-fit: contain; display: block; }
.sidebar-brand-name {
  font-size: 13.5px; font-weight: 620; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar-collapse {
  margin-left: auto; flex: none;
  display: grid; place-items: center; width: 24px; height: 24px;
  border: 0; border-radius: 7px; cursor: pointer;
  background: var(--ink-hover); color: var(--ink-on-muted);
  font-size: 15px; line-height: 1;
}
.sidebar-collapse:hover { background: var(--ink-active); color: var(--ink-on); }

.sidebar-nav { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.sidebar-item {
  display: flex; align-items: center; gap: 11px; width: 100%;
  height: 36px; padding: 0 11px; border: 0; border-radius: 8px;
  background: transparent; cursor: pointer; text-align: left;
  color: var(--ink-on-muted); font-size: 13px; font-weight: 450; letter-spacing: -.01em;
}
.sidebar-item:hover { background: var(--ink-hover); color: var(--ink-on); }
.sidebar-item:focus-visible { outline: 2px solid var(--ink-accent); outline-offset: -2px; }
/* L'application ouverte : fond tenu et libellé en blanc. L'icône passe au
   bleu clair — sur l'encre, l'accent du thème clair tomberait à 3,4:1. */
.sidebar-item[aria-current="page"] {
  background: var(--ink-active); color: var(--ink-on); font-weight: 590;
}
.sidebar-item[aria-current="page"] .sidebar-icon { color: var(--ink-accent); }
.sidebar-icon { flex: none; display: grid; place-items: center; color: var(--ink-on-dim); }
.sidebar-icon svg { display: block; }
.sidebar-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Ce qui attend dans l'application : un nombre, pas un point — « 31 pistes »
   se décide autrement que « quelque chose à voir ». */
.sidebar-count {
  flex: none; min-width: 18px; height: 17px; padding: 0 5px;
  border-radius: 99px; background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 700; display: grid; place-items: center;
  font-variant-numeric: tabular-nums;
}
.sidebar[data-rail="1"] .sidebar-count {
  position: absolute; top: -4px; right: -6px; box-shadow: 0 0 0 2px var(--ink-surface);
}
.sidebar[data-rail="1"] .sidebar-item { position: relative; }

/* Pied : le compte. La maquette ne le montrait pas — le retirer aurait
   supprimé le réglage du thème, l'accès aux Paramètres et l'aide. */
.sidebar-foot { margin-top: auto; padding-top: var(--sp-2); border-top: 1px solid var(--ink-line); }
.sidebar-user {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  padding: var(--sp-2) var(--sp-2); border: 0; border-radius: 8px;
  background: transparent; cursor: pointer; text-align: left; color: var(--ink-on);
}
.sidebar-user:hover { background: var(--ink-hover); }
.sidebar-user:focus-visible { outline: 2px solid var(--ink-accent); outline-offset: -2px; }
.sidebar-user-text { min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.sidebar-user-name {
  font-size: 12.5px; font-weight: 590;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-user-role { font-size: 10.5px; color: var(--ink-on-dim); }
.sidebar-user-caret { margin-left: auto; color: var(--ink-on-dim); font-size: 12px; }

/* Sous 900 px, la barre se réduit d'office au rail : 216 px pris sur une
   fenêtre étroite laissent trop peu au contenu. */
@media (max-width: 900px) {
  .sidebar { width: var(--sidebar-rail-w); padding-inline: 6px; }
  .sidebar .sidebar-brand-name,
  .sidebar .sidebar-label,
  .sidebar .sidebar-user-text,
  .sidebar .sidebar-user-caret { display: none; }
  .sidebar .sidebar-item, .sidebar .sidebar-user { justify-content: center; padding-inline: 0; }
  .sidebar .sidebar-head { flex-direction: column; gap: var(--sp-2); }
  .sidebar .sidebar-item { position: relative; }
  .sidebar .sidebar-count {
    position: absolute; top: -4px; right: -6px; box-shadow: 0 0 0 2px var(--ink-surface);
  }
}

/* ══ HEADER — barre 1 : application courante et ses sections ═══════════ */

.appbar {
  height: var(--header-h);
  background: var(--ink-surface);
  color: var(--ink-on);
  border-bottom: 1px solid var(--ink-line);
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: 0 var(--sp-4);
  position: sticky;
  top: 0;
  z-index: 40;
}

/* Logo : retour à l'accueil */
.logo-home {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  border: 0;
  background: none;
  cursor: pointer;
  padding: var(--sp-1) var(--sp-2) var(--sp-1) 0;
  border-radius: var(--radius-control);
  flex-shrink: 0;
}
.logo-home:hover { background: var(--ink-hover); }
/* La marque vit désormais en tête de la barre latérale ; ici le bouton porte
   l'icône et le nom de l'APPLICATION ouverte. Le logo complet ne reparaît
   qu'à l'accueil, où il n'y a pas d'application courante à nommer. */
.logo-home img { height: 26px; width: auto; display: block; }
.logo-home .app-badge {
  width: 24px; height: 24px; border-radius: 7px;
  display: grid; place-items: center; flex: none;
  background: var(--ink-active); color: var(--ink-accent);
}
.logo-home .app-name {
  font-size: 13.5px; font-weight: 620; letter-spacing: -.01em; color: var(--ink-on);
}

/* Nom du module courant, à côté du logo */
.appbar-module {
  font-weight: 650;
  font-size: var(--fs-body);
  letter-spacing: -.01em;
  white-space: nowrap;
  color: var(--ink-on);
}

/* Menu horizontal des sous-sections du module */
.appbar-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  overflow-x: auto;
  scrollbar-width: none;
  flex: 1;
  min-width: 0;
}
.appbar-nav::-webkit-scrollbar { display: none; }
.appbar-nav button {
  border: 0;
  background: none;
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-control);
  cursor: pointer;
  font-size: 12.5px;
  color: var(--ink-on-muted);
  white-space: nowrap;
}
.appbar-nav button:hover { background: var(--ink-hover); color: var(--ink-on); }
/* La section ouverte : blanc et appuyé. Sur l'encre, l'accent bleu du thème
   clair ne passerait pas le contraste — c'est la graisse qui distingue. */
.appbar-nav button[aria-current="page"] { color: var(--ink-on); font-weight: 620; }
/* Une section annoncée mais pas livrée : visible, inerte, et qui le dit. */
.appbar-nav button.nav-soon,
.appbar-nav button[disabled] {
  color: var(--ink-on-dim); cursor: not-allowed; text-decoration: line-through;
}
.appbar-nav button[disabled]:hover { background: none; color: var(--ink-on-dim); }

.appbar-right { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }

/* Compteurs de notifications */
.appbar-badge {
  position: relative;
  border: 0; background: none; cursor: pointer;
  color: var(--ink-on-muted); padding: var(--sp-1);
  border-radius: var(--radius-control);
  font-size: 15px; line-height: 1;
}
.appbar-badge:hover { background: var(--ink-hover); color: var(--ink-on); }
.appbar-badge .count {
  position: absolute; top: -2px; right: -4px;
  background: var(--danger); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1;
  min-width: 16px; height: 16px; border-radius: 99px;
  display: grid; place-items: center; padding: 0 4px;
  font-variant-numeric: tabular-nums;
}

/* L'identité n'est plus dans la barre haute : elle vit au pied de la barre
   latérale, avec son menu. L'avatar reste défini ici — il sert aussi aux
   fiches (auteur d'un message, membre d'une équipe). */
.avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent);
  color: #fff; font-weight: 590; font-size: 10px;
  display: grid; place-items: center; flex-shrink: 0;
}

/* Ligne d'un menu flottant portant un contrôle plutôt qu'une action :
   « Apparence » et le sélecteur d'utilisateur de la démonstration. */
.menu-pop-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-1) var(--sp-2);
}
.menu-pop-row-label { flex: 1; font-size: var(--fs-label); color: var(--muted); }
.menu-pop-row select { width: 100%; }

/* Entrée de menu : l'icône occupe une colonne de largeur fixe, donc tous
   les libellés s'alignent quelle que soit la largeur du glyphe. */
.menu-item-icon { color: var(--muted); text-align: center; font-size: 13px; line-height: 1; }

/* ══ HEADER — barre 2 : contrôle (2 variantes) ═════════════════════════ */

.controlbar {
  min-height: var(--control-h);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  /* Grille stricte 25 % — 50 % — 25 % : la zone centrale est centrée sur
     l'écran, quelle que soit la largeur du contenu de gauche. En flex, la
     colonne centrale se décalait dès que le titre s'allongeait. */
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  position: sticky;
  top: var(--header-h);
  z-index: 30;
}
.controlbar[hidden] { display: none; }
.controlbar-left { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }
.controlbar-center { display: flex; justify-content: center; min-width: 0; }
.controlbar-right { display: flex; align-items: center; gap: var(--sp-2); justify-content: flex-end; min-width: 0; }

/* Smart buttons — objets LIÉS à la fiche ouverte (contacts, devis, factures…),
   jamais des indicateurs de l'objet lui-même. Colonnes de largeur égale, dans
   une section centrée : la grille impose la même taille à tous. */
.stat-buttons {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(96px, 1fr);
  align-items: stretch;
  justify-content: center;
  gap: 0;
  max-width: 100%;
}
.stat-button {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  border: 1px solid var(--line); border-right-width: 0;
  background: var(--surface); cursor: pointer;
  padding: var(--sp-1) var(--sp-3); text-align: left;
  min-width: 0;
}
.stat-button:first-child { border-radius: var(--radius-control) 0 0 var(--radius-control); }
.stat-button:last-child { border-right-width: 1px; border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.stat-button:hover { background: var(--surface-2); }
.stat-button .icon { color: var(--muted); font-size: 14px; flex: none; display: grid; place-items: center; }
.stat-button .icon svg { display: block; }
.stat-button .stat-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.stat-button .stat-label {
  font-size: var(--fs-label); color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stat-button .stat-value { font-size: var(--fs-dense); font-weight: 650; font-variant-numeric: tabular-nums; }

.pager { display: flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-dense); color: var(--muted); }
.pager .position { font-variant-numeric: tabular-nums; padding: 0 var(--sp-1); }
.pager button {
  border: 1px solid var(--line); background: var(--surface); cursor: pointer;
  width: 26px; height: 26px; border-radius: var(--radius-control);
  display: grid; place-items: center; color: var(--muted);
}
.pager button:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
.pager button:disabled { opacity: .4; cursor: not-allowed; }

/* ══ Barre de recherche compacte (UX-20) ═══════════════════════════════ */

.searchbar { position: relative; width: min(560px, 100%); }
.searchbar-field {
  display: flex; align-items: center; gap: var(--sp-2);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-control); padding: 3px var(--sp-2) 3px var(--sp-3);
  min-height: 32px;
}
.searchbar-field:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.searchbar-icon { color: var(--muted); flex-shrink: 0; font-size: 14px; }
.searchbar-input { border: 0; background: none; flex: 1; min-width: 90px; outline: none; font-size: var(--fs-dense); }
.searchbar-toggle {
  border: 0; border-left: 1px solid var(--line); background: none;
  padding: 0 var(--sp-1) 0 var(--sp-2); cursor: pointer; color: var(--muted);
  font-size: 10px; align-self: stretch;
}

/* Puces de critères actifs */
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  background: var(--accent-soft); color: var(--accent);
  border-radius: var(--radius-control); padding: 2px var(--sp-2);
  font-size: var(--fs-label); font-weight: 600; white-space: nowrap;
}
.chip.favorite { background: var(--favorite-soft); color: var(--favorite); }
.chip button {
  border: 0; background: none; cursor: pointer; color: inherit;
  padding: 0 0 0 2px; line-height: 1; font-size: 13px;
}

/* Panneau 3 colonnes */
.searchpanel {
  /* Le panneau est plus large que la barre : on le centre sur elle au lieu
     de l'étirer, sinon il déborde à droite et les libellés sont coupés. */
  position: absolute; top: calc(100% + 4px);
  left: 50%; transform: translateX(-50%);
  width: max-content; max-width: min(92vw, 760px);
  z-index: 50;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-overlay);
  display: grid; grid-template-columns: repeat(3, minmax(180px, 1fr));
  padding: var(--sp-4); gap: var(--sp-4);
}
.searchpanel-col { min-width: 0; }
.searchpanel[hidden] { display: none; }
.searchpanel-col h2, .searchpanel-col h3 {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-dense); margin: 0 0 var(--sp-2); font-weight: 650;
}
.searchpanel-col h2 .dot,
.searchpanel-col h3 .dot { width: 8px; height: 8px; border-radius: 2px; }
.dot-filter { background: var(--brand-blue); }
.dot-group { background: var(--brand-blue-mid); }
.dot-fav { background: var(--favorite); }
.searchpanel-option {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%; text-align: left;
  border: 0; background: none; padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-control); cursor: pointer;
  font-size: var(--fs-dense); color: var(--ink);
}
.searchpanel-option:hover { background: var(--surface-2); }
.searchpanel-option[aria-pressed="true"] { color: var(--accent); font-weight: 600; }
.searchpanel-option .check { width: 12px; flex: none; color: var(--accent); font-size: 11px; }
/* Le favori et sa corbeille sont deux boutons voisins : ils se présentent
   comme une seule ligne, mais restent deux cibles distinctes au clavier. */
.searchpanel-fav { display: flex; align-items: center; }
.searchpanel-fav:hover { background: var(--surface-2); border-radius: var(--radius-control); }
.searchpanel-fav:hover .searchpanel-option { background: none; }
.searchpanel-fav .trash {
  flex: none; border: 0; background: none; cursor: pointer;
  color: var(--muted); opacity: .6; padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-control); font-size: var(--fs-dense);
}
.searchpanel-fav .trash:hover { opacity: 1; color: var(--danger); }
/* Un libellé long revient à la ligne au lieu de sortir de la colonne. */
.searchpanel-option > span:not(.check) { min-width: 0; overflow-wrap: anywhere; }
.searchpanel-divider { height: 1px; background: var(--line); margin: var(--sp-2) 0; }
.searchpanel-add {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  width: 100%; border: 0; background: none; cursor: pointer;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-control);
  font-size: var(--fs-dense); color: var(--muted);
  text-align: left; white-space: nowrap;
}
.searchpanel-add:hover { background: var(--surface-2); color: var(--ink); }

/* Sous-menu (dates, champs personnalisés) */
.submenu {
  margin: var(--sp-1) 0 var(--sp-2) var(--sp-4);
  padding-left: var(--sp-2); border-left: 2px solid var(--line);
  display: flex; flex-direction: column; gap: 2px;
}
.submenu[hidden] { display: none; }
.submenu button {
  border: 0; background: none; text-align: left; cursor: pointer;
  padding: 3px var(--sp-2); border-radius: var(--radius-control);
  font-size: var(--fs-label); color: var(--muted);
}
.submenu button:hover { background: var(--surface-2); color: var(--ink); }
.submenu button[aria-pressed="true"] { color: var(--accent); font-weight: 600; }

/* ══ Commutateur de vues (UX-19) ═══════════════════════════════════════ */

.viewswitch { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-control); overflow: hidden; }
.viewswitch button {
  border: 0; background: var(--surface); cursor: pointer;
  width: 30px; height: 28px; display: grid; place-items: center;
  color: var(--muted); border-right: 1px solid var(--line); font-size: 13px;
}
.viewswitch button:last-child { border-right: 0; }
.viewswitch button:hover { background: var(--surface-2); color: var(--ink); }
.viewswitch button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }

/* ══ Boutons ═══════════════════════════════════════════════════════════ */

.btn {
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  border-radius: var(--radius-control); padding: var(--sp-1) var(--sp-3);
  cursor: pointer; font-size: var(--fs-dense); white-space: nowrap;
}
.btn:hover { background: var(--surface-2); }
/* CTA principal : 1 à 2 par écran maximum */
.btn-primary {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-ink); font-weight: 600;
}
.btn-primary:hover { filter: brightness(1.1); background: var(--accent); }
.btn-ghost { border-color: transparent; background: none; color: var(--muted); }
.btn-ghost:hover { background: var(--surface-2); color: var(--ink); }
.btn:disabled { opacity: .5; cursor: not-allowed; }

/* ══ Contenu ═══════════════════════════════════════════════════════════ */

/* Pleine largeur : sur un grand écran, l'espace disponible est utilisé.
   Le plafond ne sert qu'aux très grandes dalles, pour éviter des lignes
   de tableau démesurément larges. */
.content { padding: var(--sp-6); max-width: min(100%, 2200px); margin: 0 auto; }
.content:focus { outline: none; }

/* Lu par les lecteurs d'écran, invisible à l'œil. Sert aux titres de page
   qu'un écran n'a pas besoin d'afficher mais qu'il doit nommer. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Cibles d'au moins 24 px : plusieurs petits boutons (roue de configuration,
   « + » d'un widget, filtres du fil, badges de la barre) tombaient sous ce
   seuil et devenaient difficiles à viser, en particulier au doigt.
   La zone cliquable grandit ; l'apparence, elle, ne bouge pas. */
.gear, .widget-link, .feed-filter, .appbar-badge, .btn-sm, .pager button {
  min-height: 24px; min-width: 24px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* Le doigt ne déclenche pas de double-tap zoom sur ces cibles. */
button, .trail-step, .app-strip-tile, .kanban-card { touch-action: manipulation; }

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  padding: var(--sp-4);
}

.grid { display: grid; gap: var(--sp-4); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

.metric-label { font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.metric-value { font-size: var(--fs-page); font-weight: 700; letter-spacing: -.02em; margin: var(--sp-1) 0 0; }
.metric-unit { font-size: var(--fs-dense); color: var(--muted); font-weight: 500; margin-left: 4px; }

.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-card); background: var(--surface); }

/* ── Sélection multiple et actions groupées ────────────────────────────
   Sobriété : la case cochée porte déjà la couleur d'accent. Répéter ce bleu
   sur la ligne entière ET sur la barre sature l'écran et fait perdre au bleu
   sa valeur de signal. Une ligne retenue se marque donc par un liseré à
   gauche et un fond a peine appuyé — assez pour la suivre du regard, pas
   assez pour crier. */
.select-cell {
  width: 40px; padding-left: var(--sp-3) !important; padding-right: 0 !important;
  cursor: default;
}
.select-cell input[type="checkbox"] {
  width: 16px; height: 16px; margin: 0; cursor: pointer;
  accent-color: var(--accent); vertical-align: middle;
}
.data-table tr[data-selected] > td { background: var(--surface-2); }
.data-table tr[data-selected] > td:first-child {
  box-shadow: inset 2px 0 0 var(--accent);
}

/* Actions sur la sélection — elles PRENNENT LA PLACE de la recherche dans la
   barre de contrôle, au lieu de s'ajouter au-dessus du tableau. Une ligne qui
   apparaît décale tout le contenu et fait perdre la ligne qu'on visait. */
.selection-bar {
  display: flex; align-items: center; gap: var(--sp-2);
  /* Centrée, comme la barre de recherche qu'elle remplace : prendre toute la
     largeur collerait le compte et les actions à gauche, loin de la zone que
     l'œil vient de quitter — les cases cochées sont au centre du tableau.
     `justify-content` ne suffirait pas : sans largeur contrainte, la barre
     resterait pleine largeur et ses bords sauteraient d'un écran à l'autre
     selon le nombre d'actions. */
  min-width: 0; max-width: 100%;
  /* Même hauteur que la barre de recherche : sans cela, le tableau se
     décale de deux pixels à chaque sélection. */
  min-height: 32px;
}
.selection-count {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2) var(--sp-1) var(--sp-3);
  background: var(--accent-soft); border: 1px solid var(--accent);
  border-radius: var(--radius-control);
  font-size: var(--fs-dense); color: var(--ink); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.selection-clear {
  border: 0; background: none; cursor: pointer; color: var(--accent);
  padding: 0 2px; border-radius: var(--radius-control);
  min-height: 20px; min-width: 20px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.selection-clear:hover { background: var(--surface); }

/* Aperçu de ce qu'une suppression groupée va emporter : « supprimer 12
   éléments » ne dit pas assez, on doit reconnaître ce qu'on a coché par
   erreur. */
.bulk-preview {
  margin: var(--sp-2) 0; padding-left: var(--sp-6);
  font-size: var(--fs-dense); color: var(--ink);
}
.bulk-preview li { margin: 2px 0; }
.bulk-preview-more { color: var(--muted); font-style: italic; }

/* ── Menu déroulant générique ──────────────────────────────────────────
   Sert à la roue dentée de la barre et au sélecteur de colonnes : même
   apparence, même comportement de fermeture. */
.menu-host { position: relative; display: inline-flex; }
/* Posé sur le <body> en position fixe : le conteneur du tableau a
   `overflow-x: auto`, qui rognait le menu au lieu de le laisser déborder.
   Les coordonnées sont calculées en JavaScript depuis le bouton. */
.menu-pop {
  position: fixed; z-index: 200;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-overlay);
  min-width: 220px; padding: var(--sp-1); text-align: left;
  /* Un menu plus long que l'écran défile en lui-même plutôt que de sortir
     du cadre — la hauteur maximale est posée au placement. */
  overflow-y: auto; overscroll-behavior: contain;
}
.menu-pop-title {
  margin: 0; padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: var(--fs-label); color: var(--muted); font-weight: 650;
  text-transform: none; letter-spacing: 0;
}
.menu-pop-heading {
  margin: 0; padding: var(--sp-2) var(--sp-3) var(--sp-1);
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
.menu-pop-sep { border: 0; border-top: 1px solid var(--line); margin: var(--sp-1) 0; }
.menu-pop-item {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-control);
  font-size: var(--fs-dense); color: var(--ink);
}
.menu-pop-item:hover:not(:disabled) { background: var(--surface-2); }
.menu-pop-item:disabled { color: var(--muted); cursor: not-allowed; }
.menu-pop-item.is-danger { color: var(--danger); }
.menu-pop-item.is-danger:hover:not(:disabled) { background: var(--danger-soft); }
.menu-pop-label { flex: 1; }
.menu-pop-hint { font-size: var(--fs-label); color: var(--muted); }

/* Une entrée à cocher : la ligne entière est cliquable, pas seulement la case. */
.menu-pop-check {
  display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 0;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-control);
  font-size: var(--fs-dense); color: var(--ink); cursor: pointer;
}
.menu-pop-check:hover { background: var(--surface-2); }
.menu-pop-check input { accent-color: var(--accent); cursor: pointer; flex: none; }
.menu-pop-check span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-pop-check:has(input:disabled) { color: var(--muted); cursor: not-allowed; }

/* Une ligne du menu qui porte une case ET des commandes d'ordre. Les flèches
   restent discrètes tant qu'on ne survole pas la ligne : elles servent moins
   souvent que les cases, et ne doivent pas encombrer la lecture. */
.menu-pop-row {
  display: flex; align-items: center; gap: var(--sp-1);
  border-radius: var(--radius-control);
}
.menu-pop-row:hover { background: var(--surface-2); }
.menu-pop-row:hover .menu-pop-check { background: none; }
.col-move-group { display: flex; gap: 2px; padding-right: var(--sp-2); flex: none; }
.col-move {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  padding: 0 var(--sp-1); border-radius: var(--radius-control);
  font-size: var(--fs-dense); line-height: 1;
  min-height: 24px; min-width: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity .12s ease;
}
.menu-pop-row:hover .col-move,
.col-move:focus-visible { opacity: 1; }
.col-move:hover:not(:disabled) { background: var(--accent-soft); color: var(--accent); }
.col-move:disabled { opacity: 0 !important; cursor: default; }

/* Sélecteur de colonnes : discret dans l'en-tête, il ne concurrence pas les
   intitulés. La colonne reste étroite et ne se trie pas. */
.col-picker-cell { width: 36px; text-align: right; cursor: default; }
.col-picker {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  padding: var(--sp-1); border-radius: var(--radius-control);
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 24px; min-width: 24px;
}
.col-picker:hover { color: var(--ink); background: var(--surface); }
.col-picker svg { display: block; }
.btn-danger { border-color: var(--danger); color: var(--danger); }
.btn-danger:hover { background: var(--danger-soft); }

@media (max-width: 720px) {
  /* Sur petit écran, les actions passent à la ligne plutôt que de déborder. */
  .selection-bar { flex-wrap: wrap; }
}

/* Regroupement : une ligne d'intitulé par groupe, dans le même tableau — les
   colonnes restent alignées d'un groupe à l'autre, ce que des tableaux
   séparés ne permettraient pas. */
.table-group-head td {
  background: var(--surface-2); padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--line);
}
.table-group:first-of-type .table-group-head td { border-top: 0; }
.table-group-label {
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em;
}
.table-group-count {
  margin-left: var(--sp-2); font-size: var(--fs-label); font-weight: 650;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
table { border-collapse: collapse; width: 100%; font-size: var(--fs-dense); }
th {
  text-align: left; font-size: var(--fs-label); text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted); background: var(--surface-2);
  /* Sans cette ligne, le navigateur applique son gras par défaut : les
     intitulés de colonnes pesaient alors aussi lourd que le Total TTC. Ils
     nomment les données, ils ne sont pas la donnée. */
  font-weight: 550;
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--line); white-space: nowrap;
}
td { padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--line); }
tr:last-child td { border-bottom: 0; }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--surface-2); }
td.right, th.right { text-align: right; }
/* Hiérarchie : l'entité nommée porte le poids, les nombres s'alignent. */
tbody td:first-child { font-weight: 600; color: var(--ink); }
td.right { font-variant-numeric: tabular-nums; }

/* ── Tri : composant tableau unique ─────────────────────────────────── */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--ink); background: var(--accent-soft); }
th .th-label { vertical-align: middle; }
.sort-arrow {
  display: inline-block; margin-left: var(--sp-1);
  font-size: 10px; line-height: 1; vertical-align: middle;
}
/* Indicateur en attente : présent mais effacé, il se révèle au survol. */
.sort-arrow[data-dir="none"] { opacity: .28; }
th.sortable:hover .sort-arrow[data-dir="none"] { opacity: .7; }
.sort-arrow:not([data-dir="none"]) { color: var(--accent); opacity: 1; }
th[aria-sort]:not([aria-sort="none"]) { color: var(--accent); }

/* ══ Kanban — structure Odoo, exécution modernisée ═════════════════════ */

.kanban { display: flex; gap: var(--sp-3); overflow-x: auto; padding-bottom: var(--sp-3); align-items: flex-start; }
/* Les colonnes se partagent la largeur disponible : elles s'élargissent sur
   un grand écran et se resserrent sur un petit, sans jamais passer sous
   260 px (au-delà, la vignette devient illisible). */
.kanban-col {
  flex: 1 1 260px; min-width: 260px; max-width: 420px;
  display: flex; flex-direction: column; gap: var(--sp-2);
}

.kanban-head {
  display: grid;
  grid-template-areas: "title count amount" "bar bar bar";
  grid-template-columns: 1fr auto auto;
  align-items: baseline; gap: var(--sp-1) var(--sp-2);
  padding: var(--sp-2) var(--sp-3); background: var(--surface-2);
  border-radius: var(--radius-control); border: 1px solid var(--line);
}
.kanban-head h2, .kanban-head h3 { grid-area: title; font-size: var(--fs-dense); margin: 0; font-weight: 650; }
.kanban-head .count { grid-area: count; font-size: var(--fs-label); color: var(--muted); }
.kanban-head .amount { grid-area: amount; font-size: var(--fs-label); font-variant-numeric: tabular-nums; color: var(--muted); }

/* Barre de progression de l'étape, comme chez Odoo : poids relatif de la colonne. */
.kanban-progress { grid-area: bar; height: 3px; background: var(--line); border-radius: 2px; overflow: hidden; }
.kanban-progress i { display: block; height: 100%; background: var(--accent); border-radius: 2px; }

/* Colonne repliée : bandeau vertical étroit, cliquable sur toute sa hauteur
   pour se rouvrir (les étapes closes le sont par défaut). */
.kanban-col.collapsed { flex: 0 0 40px; min-width: 40px; }
.kanban-col.collapsed .kanban-body { display: none; }
.kanban-col.collapsed .kanban-head {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  writing-mode: vertical-rl;
  min-height: 200px; justify-content: flex-start;
  padding: var(--sp-3) var(--sp-2);
  cursor: pointer;
}
.kanban-col.collapsed .kanban-head:hover { background: var(--accent-soft); }
.kanban-col.collapsed .kanban-head h2,
.kanban-col.collapsed .kanban-head h3 { font-size: var(--fs-label); letter-spacing: .03em; }
/* Montant et barre n'ont pas de sens à la verticale ; le compteur non plus
   quand il vaut 0 — c'étaient les « 0 » qui traînaient. */
.kanban-col.collapsed .kanban-head .amount,
.kanban-col.collapsed .kanban-progress { display: none; }
.kanban-col.collapsed .kanban-head .count[data-zero="true"] { display: none; }
.kanban-col-toggle {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  font-size: var(--fs-label); padding: 0 2px; line-height: 1;
}
.kanban-col-toggle:hover { color: var(--ink); }

.kanban-body {
  display: flex; flex-direction: column; gap: var(--sp-2);
  min-height: 60px;                     /* une colonne vide reste une cible */
  border-radius: var(--radius-control);
  transition: background .12s, box-shadow .12s;
}

/* ── Glisser-déposer ────────────────────────────────────────────────────

   La colonne survolée s'ouvre à l'endroit visé : les cartes situées avant la
   couture remontent d'un demi-espace, celles d'après descendent d'autant.
   On voit la place que la carte prendra avant de relâcher.

   Tout se joue en `transform`. Écarter par la hauteur changerait la
   géométrie que le calcul d'insertion mesure, et le repère oscillerait dès
   qu'on approche d'une couture. `--drop-gap` doit rester égal à la constante
   GAP de ui/dnd.js : les deux décrivent le même espace. */
:root { --drop-gap: 10px; }

.kanban-card[draggable="true"] { cursor: grab; }
.kanban-card.dragging {
  opacity: .4; cursor: grabbing;
  /* La carte saisie ne participe plus au repère : elle ne doit ni s'écarter
     d'elle-même, ni intercepter le survol de ses voisines. */
  pointer-events: none;
}

/* L'écartement. La transformation est posée par le script — le CSS ne porte
   que la façon dont elle s'installe. */
.kanban-card.shifting { transition: transform .16s cubic-bezier(.2, .8, .3, 1); }
@media (prefers-reduced-motion: reduce) {
  .kanban-card.shifting { transition: none; }
}

/* La couture : un trait fin dans l'espace ouvert, pour lever l'ambiguïté
   entre « au-dessus » et « au-dessous » quand les deux cartes s'écartent
   symétriquement. Dessiné par la colonne, donc jamais sous le curseur. */
.kanban-body.drop-target { position: relative; }
.kanban-body.drop-target::after {
  content: ''; position: absolute; left: 0; right: 0;
  /* La couture est posée par le script via --drop-y : la distance entre le
     haut de la colonne et l'interstice visé. */
  top: var(--drop-y, 0); height: 2px; margin-top: -1px;
  border-radius: 999px; background: var(--accent);
  box-shadow: 0 0 0 1px var(--accent-soft);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .kanban-body.drop-target::after { transition: top .16s cubic-bezier(.2, .8, .3, 1); }
}

/* Colonne vide : rien à écarter, c'est le fond qui accueille. */
.kanban-body.is-empty-target {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.kanban-body.is-empty-target::after { content: none; }

/* Reste de la colonne, au-delà des douze premières cartes. Un bouton, pas une
   mention : « + 38 autres » nommait ce qu'on ne pouvait pas atteindre. */
.kanban-more {
  border: 1px dashed var(--line); background: none; cursor: pointer;
  padding: var(--sp-2); border-radius: var(--radius-control);
  font-size: var(--fs-label); color: var(--muted); text-align: center;
}
.kanban-more:hover { border-color: var(--accent); color: var(--accent); }
.kanban-more:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Vignette : plus complète, à la manière d'Odoo ─────────────────── */
.kanban-card {
  background: var(--surface); border: 1px solid var(--line);
  border-left: 3px solid var(--line);       /* incolore par défaut : voir « Température » */
  border-radius: var(--radius-card); padding: var(--sp-3); cursor: pointer;
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column; gap: var(--sp-2);
  transition: box-shadow .12s, border-color .12s;
}
.kanban-card:hover { border-color: var(--accent); box-shadow: var(--shadow-overlay); }
.kanban-card h3, .kanban-card h4 {
  margin: 0; font-size: var(--fs-dense); font-weight: 650; line-height: 1.3;
  overflow-wrap: anywhere;
}
/* Pied : client à gauche, signal et propriétaire à droite — sur une ligne. */
.kanban-foot { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.kanban-foot .spacer { margin-left: auto; }
.kanban-card .kanban-account {
  font-size: var(--fs-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* Priorité à gauche, montant à droite : les deux critères de classement de la
   colonne, sur la même ligne. */
.kanban-rank { display: flex; align-items: center; gap: var(--sp-2); }
.kanban-rank .kanban-amount { margin-left: auto; }

.kanban-stars { display: flex; gap: 1px; flex: none; }
.kanban-star {
  background: none; border: 0; padding: 0 1px; cursor: pointer;
  font-size: var(--fs-dense); line-height: 1;
  /* Éteinte, l'étoile reste discrète : c'est un réglage disponible, pas une
     information. Allumée, elle devient un signal. */
  color: var(--muted); opacity: .5;
  transition: color .12s, opacity .12s, transform .12s;
}
.kanban-star.is-on { color: var(--warning); opacity: 1; }
.kanban-star:hover { opacity: 1; transform: scale(1.15); }
.kanban-star:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: 2px; }

.kanban-amount {
  font-variant-numeric: tabular-nums; font-weight: 680;
  font-size: var(--fs-body); color: var(--ink);   /* le montant est l'information forte */
  letter-spacing: -.015em;
}
.kanban-activity { font-size: 10px; line-height: 1; flex: none; }
.kanban-activity[data-state="late"] { color: var(--danger); }
.kanban-owner {
  width: 20px; height: 20px; border-radius: 50%; flex: none;
  background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; font-size: 9px; font-weight: 700;
}

/* ══ Grille du catalogue ═══════════════════════════════════════════════
   Un catalogue se feuillette par l'image. Les vignettes s'ajustent à la
   largeur disponible sans jamais descendre sous 210 px : en deçà, la photo
   d'un échographe ne se distingue plus de celle d'un moniteur. */

.product-grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
}

.product-card {
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3); text-align: left; cursor: pointer;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  transition: box-shadow .12s, border-color .12s;
  font: inherit; color: inherit;
}
.product-card:hover { border-color: var(--accent); box-shadow: var(--shadow-overlay); }

/* La zone d'image garde une hauteur fixe : sans elle, des photos de formats
   différents décaleraient les titres d'une vignette à l'autre et la grille
   perdrait ses lignes de lecture. */
.product-card-media {
  height: 132px; display: grid; place-items: center;
  background: var(--surface-2); border-radius: var(--radius-control);
  overflow: hidden;
}
/* `contain` et non `cover` : un appareil médical se juge à sa silhouette
   entière — le rogner pour remplir le cadre couperait la colonne ou l'écran. */
.product-card-media img { width: 100%; height: 100%; object-fit: contain; }
.product-card-noimage { color: var(--muted); opacity: .55; }

.product-card-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.product-card-ref {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em; font-weight: 550;
}
.product-card-name {
  margin: 0; font-size: var(--fs-dense); font-weight: 650; line-height: 1.3;
  overflow-wrap: anywhere;
}
.product-card-family { font-size: var(--fs-label); color: var(--muted); }

/* Pied : le prix porte l'information forte, le cycle de vie ne s'affiche que
   lorsqu'il alerte (fin de commercialisation, fin de support). */
.product-card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); margin-top: auto; padding-top: var(--sp-1);
}
.product-card-price {
  font-variant-numeric: tabular-nums; font-weight: 680;
  font-size: var(--fs-body); color: var(--ink); letter-spacing: -.015em;
}

/* ══ Température (direction « Clinique ») ══════════════════════════════
   Deux états colorés seulement, tirés de la charte : --danger et
   --brand-green. Le cas ordinaire ne porte AUCUNE couleur, pour que la
   couleur reste rare et donc lisible. */

.kanban-card[data-temp="AT_RISK"] { border-left-color: var(--danger); }
.kanban-card[data-temp="AHEAD"]   { border-left-color: var(--brand-green); }

.temp-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px; font-weight: 650; white-space: nowrap;
}
.temp-flag::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor;
}
.temp-flag[data-temp="AT_RISK"] { color: var(--danger); }
.temp-flag[data-temp="AHEAD"]   { color: var(--brand-green-text); }
.temp-flag[data-temp="NEUTRAL"] { display: none; }

/* Pastille de température dans les tableaux */
td .temp-flag { font-size: var(--fs-label); }

/* ══ Badges ════════════════════════════════════════════════════════════ */

.badge {
  display: inline-block; font-size: 11px; font-weight: 650;
  padding: 1px var(--sp-2); border-radius: 99px; white-space: nowrap;
}
.badge-neutral { background: var(--surface-2); color: var(--muted); }
.badge-accent { background: var(--accent-soft); color: var(--accent); }
.badge-success { background: var(--success-soft); color: var(--success); }
.badge-warning { background: var(--warning-soft); color: var(--warning); }
.badge-danger { background: var(--danger-soft); color: var(--danger); }

/* Étape du pipeline : la teinte suit l'avancement, du plus pâle au plus
   soutenu. Toutes grises, les pastilles ne disaient rien que le texte ne
   disait déjà — or un pipeline se parcourt du regard. Les issues (gagné,
   perdu) prennent leurs propres couleurs, plus haut. */
.badge-stage { background: var(--surface-2); color: var(--muted); }
.badge-stage[data-progress="1"] { background: var(--accent-soft); color: var(--accent); }
.badge-stage[data-progress="2"] {
  background: var(--accent); color: var(--accent-ink);
}
/* Deux tons du cycle événementiel. Ils ne servent qu'ici : `badge-success`
   dit « abouti » dans dix écrans, lui donner la teinte du vert franc des
   événements changerait le sens ailleurs.

   « Confirmé » — acquis, pas encore tenu.
   Deux verts pâles ne se séparent pas par la clarté : #E6F7F3 et le
   #E3F4EC de `badge-success` ne différaient que de 1,03:1, indiscernables
   côte à côte dans une liste. La distinction vient donc de la TEINTE —
   turquoise (couleur de marque) contre vert franc — et non de la
   luminosité. Texte à 5,8:1, au-delà du minimum de 4,5. */
.badge-mint { background: #C7EEEB; color: #0D625B; }
/* « Terminé » — abouti. Vert franc, pour tenir la distance avec le
   turquoise ci-dessus. */
.badge-forest { background: #CDECD4; color: #186229; }
.badge-ai { background: var(--ai-surface); color: var(--brand-teal); }

.score { display: flex; align-items: center; gap: var(--sp-2); min-width: 120px; }
.score-track { flex: 1; height: 6px; background: var(--surface-2); border-radius: 99px; overflow: hidden; }
.score-fill { height: 100%; background: var(--brand-blue-mid); border-radius: 99px; }
.score-value { font-size: var(--fs-label); font-variant-numeric: tabular-nums; color: var(--muted); min-width: 26px; text-align: right; }

/* ══ Suggestion IA (UX-09) ═════════════════════════════════════════════ */

.ai-suggestion {
  background: var(--ai-surface); border: 1px solid var(--ai-border);
  border-radius: var(--radius-card); padding: var(--sp-3); margin-bottom: var(--sp-4);
}
.ai-suggestion header { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.ai-suggestion h2, .ai-suggestion h3 { font-size: var(--fs-dense); margin: 0; font-weight: 650; }
.ai-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); flex-wrap: wrap; }
.ai-actions button {
  border: 1px solid var(--ai-border); background: var(--surface);
  border-radius: var(--radius-control); padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-label); cursor: pointer;
}
.ai-actions button.primary { background: var(--brand-teal); color: #06231F; border-color: var(--brand-teal); font-weight: 650; }

/* ══ Page d'accueil — bandeau d'applications + tableau de bord (UX-21) ══ */

/* L'accueil est un tableau de bord : il prend TOUTE la largeur disponible.
   Les écrans de travail se bornent à 2200 px pour garder des lignes de texte
   lisibles ; ici il n'y a pas de texte long, seulement des graphiques, qui
   gagnent à respirer. Les marges se resserrent pour la même raison. */
.home-page {
  min-height: calc(100vh - var(--header-h));
  background: var(--bg);           /* épuré : pas de dégradé décoratif */
  padding: var(--sp-4) var(--sp-4) var(--sp-8);
  max-width: none;
}

/* ── Bandeau des applications ────────────────────────────────────────────
   Une rangée compacte en haut de page, à la manière du bandeau d'un tableau
   de bord Salesforce : on accède à ses applications sans que le lanceur
   occupe l'écran entier. Une seule ligne, toujours — la hauteur du bandeau
   doit rester prévisible. */
/* Le bandeau et son titre forment un bloc, au même titre que le tableau de
   bord et le sien. La marge basse vit sur le bloc, pas sur le bandeau. */
.app-launcher { margin-bottom: var(--sp-6); }

.app-bar-strip {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--sp-2) var(--sp-3);
}
/* Les applications se partagent la largeur à parts égales : le bandeau est
   occupé d'un bord à l'autre, et chaque tuile offre la même surface de clic
   quelle que soit la longueur de son libellé — « CRM » se vise aussi
   facilement qu'« Approbations ».
   `1fr` plutôt que `auto-fit` : le nombre d'applications dépend du rôle, et
   des colonnes de largeur égale répartissent l'espace sans qu'on ait à le
   connaître d'avance. `minmax(0, 1fr)` — sans le `0`, une grille refuse de
   descendre sous la largeur intrinsèque de son contenu et déborderait. */
.app-strip {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--sp-1);
  list-style: none; margin: 0; padding: 0;
}
/* La tuile remplit sa colonne, son contenu reste centré dedans. */
.app-strip-tile {
  background: none; border: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radius-control);
  color: var(--ink); font-size: var(--fs-dense);
  transition: background .12s;
}
/* Un libellé trop long pour sa colonne se tronque plutôt que d'élargir la
   grille : c'est la répartition qui prime, et le titre complet reste dans
   l'infobulle de la tuile. */
.app-strip-name {
  font-weight: 550;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-strip-tile:hover { background: var(--surface-2); }
.app-strip-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.app-strip-icon {
  width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center; flex: none;
  background: var(--accent-soft); color: var(--accent);
  /* Repère de la pastille : elle se cale sur le CARRÉ, pas sur la tuile —
     celle-ci inclut le libellé, dont la largeur varie d'un module à l'autre. */
  position: relative;
}
/* Les icônes sont des SVG au trait : la couleur vient du conteneur. */
.app-strip-icon svg { display: block; }
/* Posée à cheval sur le coin supérieur droit du carré : c'est la convention
   des pastilles de notification. Le liseré la détache du carré. */
.app-strip-badge {
  position: absolute; top: -6px; right: -6px;
  background: var(--danger); color: #fff; font-size: 10px; font-weight: 700;
  min-width: 17px; height: 17px; border-radius: 99px;
  display: grid; place-items: center; padding: 0 4px; font-variant-numeric: tabular-nums;
  box-shadow: 0 0 0 2px var(--surface);
}

/* Sous 1000 px, le libellé disparaît au profit de la seule icône.
   Le seuil est celui où « Approbations » et « Événements » commencent à se
   tronquer dans leur colonne : trois libellés coupés en « Événeme… » et
   « Approbat… » informent moins que six icônes nettes, que l'infobulle et le
   lecteur d'écran nomment de toute façon. */
@media (max-width: 1000px) {
  .app-strip-name { display: none; }
  .app-strip-tile { padding: var(--sp-2); }
}

/* ── En-tête du tableau de bord ──────────────────────────────────────── */
.board-header {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap;
  margin-bottom: var(--sp-4);
}
.board-title { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.board-title h2 {
  margin: 0; font-size: var(--fs-section); font-weight: 650; color: var(--ink);
}
/* D'où vient ce qu'on regarde : vue par défaut, ou l'une de ses versions. */
.board-source {
  font-size: var(--fs-label); color: var(--muted);
  background: var(--surface-2); border-radius: 99px;
  padding: 2px var(--sp-2);
}
/* En édition, l'étiquette se colore : on ne quitte pas le mode sans le voir. */
.board-source.is-editing {
  background: var(--warning-soft); color: var(--warning); font-weight: 600;
}
.board-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.board-actions .btn { display: inline-flex; align-items: center; gap: var(--sp-1); }

/* ── Grille de widgets ───────────────────────────────────────────────────
   Trois colonnes, et une pose occupe 1, 2 ou 3 d'entre elles. L'ordre du
   tableau est l'ordre d'affichage : pas de coordonnées, donc rien à
   recalculer quand la fenêtre rétrécit. */
.hw-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
  align-items: start;
}
.hw[data-width="2"] { grid-column: span 2; }
.hw[data-width="3"] { grid-column: span 3; }

@media (max-width: 1100px) {
  .hw-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hw[data-width="3"] { grid-column: span 2; }
}
@media (max-width: 700px) {
  .hw-grid { grid-template-columns: minmax(0, 1fr); }
  .hw[data-width="2"], .hw[data-width="3"] { grid-column: span 1; }
}

/* Un widget cliquable est un <button> : sans cette remise à plat, il hérite
   du fond et de la police du bouton natif, et l'en-tête se met à ressembler
   à une barre grise posée sur la carte. */
.hw {
  display: flex; flex-direction: column; gap: var(--sp-2);
  text-align: left; width: 100%;
  min-height: 0;
  font: inherit; color: inherit;
}
/* Un widget cliquable est un bouton : il doit le montrer sans crier. */
.hw.is-clickable { cursor: pointer; transition: border-color .12s, box-shadow .12s; }
.hw.is-clickable:hover { border-color: var(--accent); box-shadow: var(--shadow-overlay); }
.hw.is-clickable:hover .hw-go { opacity: 1; transform: translateX(0); }
.hw:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.hw-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.hw-title {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
}
/* La flèche n'apparaît qu'au survol : au repos elle ferait du bruit sur
   chaque carte de la page. */
.hw-go {
  color: var(--accent); opacity: 0; transform: translateX(-3px);
  transition: opacity .12s, transform .12s;
}

/* Forme 1 — un chiffre. */
.hw-metric { display: flex; flex-direction: column; gap: 2px; }
.hw-value {
  margin: 0; font-size: 30px; font-weight: 680; letter-spacing: -.025em;
  line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums;
}
/* La couleur n'apparaît que s'il y a effectivement quelque chose à traiter. */
.hw-value[data-alert] { color: var(--danger); }
.hw-hint { margin: 0; font-size: var(--fs-label); color: var(--muted); }

/* Forme 2 — une liste de ce qui reste à faire. */
.hw-list { display: flex; flex-direction: column; }
.hw-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--line);
  font-size: var(--fs-dense);
}
.hw-row:last-child { border-bottom: 0; }
.hw-row-label { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hw-row-meta { color: var(--muted); font-size: var(--fs-label); flex: none; }
/* Seule la ligne en retard se colore — une liste toute rouge ne dit plus rien. */
.hw-row[data-alert] .hw-row-meta { color: var(--danger); font-weight: 600; }
.hw-more { margin: var(--sp-2) 0 0; font-size: var(--fs-label); color: var(--muted); }
.hw-empty { margin: 0; font-size: var(--fs-dense); color: var(--muted); }

/* Forme 3 — une répartition. Reprend la barre des rapports. */
.hw-bars { display: flex; flex-direction: column; gap: var(--sp-2); }
.hw-bar-row { display: grid; grid-template-columns: 90px 1fr auto; align-items: center; gap: var(--sp-2); }
.hw-bar-label {
  font-size: var(--fs-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hw-bar { background: var(--surface-2); border-radius: 99px; height: 8px; overflow: hidden; }
.hw-bar-fill { background: var(--accent); height: 100%; border-radius: 99px; }
.hw-bar-value { font-size: var(--fs-label); color: var(--ink); font-variant-numeric: tabular-nums; }

/* ── Mode édition ────────────────────────────────────────────────────────
   Les cartes montrent leur poignée et leur largeur, pas leurs chiffres :
   composer une page, c'est arranger des blocs. */
.hw.is-editable {
  cursor: grab; border-style: dashed; gap: var(--sp-3);
}
.hw.is-editable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hw.dragging { opacity: .4; cursor: grabbing; }
.hw.drop-target { border-color: var(--accent); border-style: solid; background: var(--accent-soft); }

.hw-edit-head { display: flex; align-items: center; gap: var(--sp-2); }
.hw-grip { color: var(--muted); cursor: grab; font-size: 15px; line-height: 1; }
.hw-edit-head .hw-title { flex: 1; text-transform: none; letter-spacing: 0; font-size: var(--fs-dense); color: var(--ink); }
.hw-remove {
  background: none; border: 0; cursor: pointer; color: var(--muted);
  font-size: 17px; line-height: 1; padding: 0 var(--sp-1); border-radius: var(--radius-control);
}
.hw-remove:hover { color: var(--danger); background: var(--danger-soft); }
.hw-edit-desc { margin: 0; font-size: var(--fs-label); color: var(--muted); }

.hw-widths { display: flex; gap: var(--sp-1); }
.hw-width {
  border: 1px solid var(--line); background: var(--surface); color: var(--muted);
  border-radius: var(--radius-control); cursor: pointer;
  width: 26px; height: 24px; font-size: var(--fs-label); font-variant-numeric: tabular-nums;
}
.hw-width:hover { border-color: var(--accent); color: var(--accent); }
.hw-width.is-active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 650; }

/* ── Bibliothèque d'ajout ────────────────────────────────────────────── */
.hw-library { display: flex; flex-direction: column; gap: var(--sp-4); max-height: 60vh; overflow-y: auto; }
.hw-library-group {
  margin: 0 0 var(--sp-2); font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 600;
}
.hw-library-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--sp-2); }
.hw-library-item {
  display: flex; flex-direction: column; gap: 2px; text-align: left;
  border: 1px solid var(--line); background: var(--surface);
  border-radius: var(--radius-card); padding: var(--sp-2) var(--sp-3); cursor: pointer;
}
.hw-library-item:hover { border-color: var(--accent); background: var(--accent-soft); }
.hw-library-item strong { font-size: var(--fs-dense); color: var(--ink); }
.hw-library-item .hint { margin: 0; font-size: var(--fs-label); }

/* ══ Lignes d'analyse — jauge · évolution · décomposition ══════════════════
   Une ligne occupe les trois colonnes et rend trois blocs solidaires. C'est
   la grammaire d'un outil décisionnel : où en est-on, d'où vient-on, de quoi
   est-ce fait. Elle ne se règle pas en largeur — l'ordre des trois questions
   est précisément ce qu'on ne veut pas laisser défaire. */
.hw-band { grid-column: 1 / -1; display: flex; flex-direction: column; gap: var(--sp-2); }
.hw-band-head { display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap; }
.hw-band-title {
  margin: 0; font-size: var(--fs-dense); font-weight: 650; color: var(--ink);
  text-transform: uppercase; letter-spacing: .05em;
}
/* L'objectif se règle au plus près de la jauge qu'il pilote. */
.hw-band-target {
  margin-left: auto; display: inline-flex; align-items: baseline; gap: var(--sp-2);
  border: 1px solid var(--line); background: var(--surface); cursor: pointer;
  border-radius: 99px; padding: 3px var(--sp-3); font-size: var(--fs-label);
  color: var(--muted);
}
.hw-band-target:hover { border-color: var(--accent); color: var(--accent); }
.hw-band-target-label { text-transform: uppercase; letter-spacing: .05em; }
.hw-band-target-value { color: var(--ink); font-weight: 650; }

.hw-band-blocks {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4);
  align-items: stretch;
}
.hw-block { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
/* Titre à gauche, étiquette d'état puis flèche à droite. L'étiquette est
   poussée par la marge automatique plutôt que par `space-between` : à trois
   enfants, ce dernier l'aurait laissée flotter au milieu de la ligne. */
.hw-block-head { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.hw-block-head .hw-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hw-block-head > :nth-child(2) { margin-left: auto; }
.hw-block-go {
  border: 0; background: none; cursor: pointer; color: var(--accent);
  font-size: 14px; line-height: 1; padding: 0 var(--sp-1); border-radius: var(--radius-control);
  opacity: 0; transition: opacity .12s;
}
.hw-block:hover .hw-block-go, .hw-block-go:focus-visible { opacity: 1; }

/* Les trois blocs gardent la MÊME largeur à tous les paliers. Une jauge
   passée pleine largeur pendant que ses deux voisins restaient à moitié
   donnait trois cartes de trois tailles : la ligne cessait de se lire comme
   une suite de trois réponses de même rang. On préfère descendre à deux
   colonnes égales, puis à une seule. */
@media (max-width: 1100px) {
  .hw-band-blocks { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
  .hw-band-blocks { grid-template-columns: minmax(0, 1fr); }
}

/* ── Palette de données ─────────────────────────────────────────────────
   Validée par le script du guide : rampe ordinale d'un seul bleu (monotone,
   écarts suffisants, extrémité claire au-dessus de 2:1 sur les deux fonds),
   et couleurs d'ÉTAT distinctes des séries — un état n'est jamais une série,
   et réciproquement. */
.viz {
  --viz-series-1: #2a78d6;
  /* Rampe ordinale : du clair au foncé. L'ordre EST l'information. */
  --viz-ramp-1: #86b6ef;
  --viz-ramp-2: #5598e7;
  --viz-ramp-3: #2a78d6;
  --viz-ramp-4: #1c5cab;
  --viz-good: #0ca30c;
  --viz-warning: #fab219;
  --viz-critical: #d03b3b;
  --viz-grid: var(--line);
  --viz-axis: var(--muted);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .viz {
    --viz-series-1: #3987e5;
    --viz-ramp-1: #184f95;
    --viz-ramp-2: #256abf;
    --viz-ramp-3: #3987e5;
    --viz-ramp-4: #6da7ec;
    --viz-good: #3fc23f;
    --viz-warning: #fab219;
    --viz-critical: #ef6d6d;
  }
}
:root[data-theme="dark"] .viz {
  --viz-series-1: #3987e5;
  --viz-ramp-1: #184f95;
  --viz-ramp-2: #256abf;
  --viz-ramp-3: #3987e5;
  --viz-ramp-4: #6da7ec;
  --viz-good: #3fc23f;
  --viz-warning: #fab219;
  --viz-critical: #ef6d6d;
}

.viz-empty {
  margin: 0; padding: var(--sp-6) 0; text-align: center;
  font-size: var(--fs-dense); color: var(--muted);
}

/* ── Jauge ──────────────────────────────────────────────────────────────── */
.viz-gauge { display: flex; flex-direction: column; align-items: center; gap: var(--sp-1); }
.viz-gauge-svg { width: 100%; max-width: 240px; height: auto; display: block; }
.viz-zone { stroke-linecap: butt; }
.viz-zone-critical { stroke: var(--viz-critical); }
.viz-zone-warning { stroke: var(--viz-warning); }
.viz-zone-good { stroke: var(--viz-good); }
.viz-needle { stroke: var(--ink); }
.viz-needle-hub { fill: var(--ink); }

.viz-gauge-read { text-align: center; }
.viz-gauge-value {
  margin: 0; font-size: 26px; font-weight: 680; letter-spacing: -.025em;
  line-height: 1.1; color: var(--ink); font-variant-numeric: tabular-nums;
}
.viz-gauge-target { margin: 2px 0 0; font-size: var(--fs-label); color: var(--muted); }
/* L'état, en étiquette posée en tête de carte.
   Il s'écrit toujours : la couleur seule ne porte jamais l'information —
   l'orange ne tient pas 3:1 sur fond clair, et un daltonien ne lit pas
   rouge/vert. Le texte reste en encre neutre ; c'est la pastille posée à côté
   qui porte la couleur, jamais les lettres. */
.viz-tag {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 2px var(--sp-2); border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line);
  font-size: 11px; font-weight: 600; line-height: 1.5;
  color: var(--muted); white-space: nowrap;
}
.viz-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.viz-tag[data-status="good"] .viz-dot { background: var(--viz-good); }
.viz-tag[data-status="warning"] .viz-dot { background: var(--viz-warning); }
.viz-tag[data-status="critical"] .viz-dot { background: var(--viz-critical); }

/* ── Courbe ─────────────────────────────────────────────────────────────── */
.viz-trend-svg { width: 100%; height: auto; display: block; }
.viz-grid { stroke: var(--viz-grid); stroke-width: 1; }
.viz-axis { fill: var(--viz-axis); font-size: 10px; }
.viz-line { stroke: var(--viz-series-1); }
/* Le remplissage est un voile, jamais un aplat : il situe la courbe sans
   prétendre porter une valeur. */
.viz-area { fill: var(--viz-series-1); opacity: .1; }
/* La SEULE ligne en tirets du graphique : c'est ce qui la désigne comme
   référence plutôt que comme donnée. */
.viz-target { stroke: var(--muted); stroke-width: 1.5; stroke-dasharray: 5 4; }
.viz-target-label { fill: var(--muted); font-size: 10px; font-weight: 600; }
.viz-end-dot { fill: var(--viz-series-1); stroke: var(--surface); stroke-width: 2; }
/* Cible de survol : on vise une semaine, pas un trait de 2 px. */
.viz-hit { fill: transparent; cursor: crosshair; }
.viz-hit:hover, .viz-hit:focus-visible { fill: var(--surface-2); opacity: .5; outline: none; }

/* ── Barres ─────────────────────────────────────────────────────────────── */
/* Chaque part occupe deux lignes (libellé, puis barre) : l'écart entre parts
   doit rester plus grand que l'écart interne, sinon on ne sait plus quel
   libellé va avec quelle barre. */
.viz-bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
/* Libellé au-dessus, barre et valeur en dessous.
   À 104 px fixes, « Démonstration sur site » se tronquait quelle que soit la
   largeur de la page ; en colonne élastique, la barre se réduisait à un moignon
   dès que la carte passait à deux colonnes. Empiler règle les deux : le libellé
   dispose de toute la largeur, la barre aussi, et la ligne garde la même hauteur
   d'une carte à l'autre — ce qui compte quand trois cartes se lisent côte à
   côte. */
.viz-bar-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center; gap: 2px var(--sp-2);
}
/* Le nom de la part, et ce que sa valeur recouvre (« 3 affaires »).
   La troncature porte sur le NOM seul : la mention est courte et se lit
   entière, alors qu'un nom d'étape peut être long. */
.viz-bar-label {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: var(--sp-2);
  font-size: var(--fs-label); color: var(--muted); min-width: 0;
}
.viz-bar-label > span:first-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
/* La mention est une glose du montant : un cran plus discrète que le nom, et
   collée à lui. Poussée au bord droit, elle s'alignait sous l'en-tête de la
   carte et se lisait comme un titre de colonne plutôt que comme le détail de
   la barre posée juste dessous. Le point la rattache à ce qui précède. */
.viz-bar-detail {
  flex: none; font-size: 11px;
  color: var(--muted); opacity: .75; font-variant-numeric: tabular-nums;
}
.viz-bar-detail::before { content: "· "; }
.viz-bar-track {
  background: var(--surface-2); border-radius: 4px; height: 18px;
  cursor: default; border: 0; padding: 0; width: 100%;
}
.viz-bar-track:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Extrémité arrondie côté valeur, carrée à la ligne de base. */
.viz-bar-fill { height: 100%; border-radius: 0 4px 4px 0; transition: filter .12s; }
.viz-bar-track:hover .viz-bar-fill { filter: brightness(1.08); }
.viz-bar-value { font-size: var(--fs-label); color: var(--ink); font-variant-numeric: tabular-nums; }

/* ── Vue tableau ────────────────────────────────────────────────────────── */
.viz-table { margin-top: auto; padding-top: var(--sp-2); }
.viz-table summary {
  font-size: var(--fs-label); color: var(--muted); cursor: pointer;
  list-style: none; padding: 2px 0;
}
.viz-table summary::-webkit-details-marker { display: none; }
.viz-table summary::before { content: '▸ '; }
.viz-table[open] summary::before { content: '▾ '; }
.viz-table summary:hover { color: var(--accent); }
.viz-table table { width: 100%; border-collapse: collapse; margin-top: var(--sp-1); }
.viz-table th, .viz-table td {
  text-align: left; font-size: var(--fs-label); font-weight: 400;
  padding: 3px 0; border-bottom: 1px solid var(--line); color: var(--muted);
}
.viz-table td { text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }

/* ── Infobulle partagée ─────────────────────────────────────────────────── */
.viz-tip {
  position: fixed; z-index: 90; pointer-events: none;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  padding: var(--sp-2) var(--sp-3); min-width: 120px;
}
.viz-tip[hidden] { display: none; }
.viz-tip-title {
  margin: 0 0 4px; font-size: var(--fs-label); color: var(--muted);
}
.viz-tip-row { margin: 0; display: flex; align-items: baseline; gap: 6px; }
/* Un trait court, pas un carré : à cette densité l'aplat pèse plus que
   l'information qu'il porte. */
.viz-tip-key { width: 10px; height: 2px; border-radius: 1px; flex: none; }
/* La valeur mène, le libellé suit : on a la série sous les yeux, on veut le
   chiffre — l'inverse de la hiérarchie d'une légende. */
.viz-tip-value { font-size: var(--fs-dense); font-weight: 650; color: var(--ink); }
.viz-tip-label { font-size: var(--fs-label); color: var(--muted); }

/* Note du mode édition, à la place du sélecteur de largeur. */
.hw-edit-note { margin: 0; font-size: var(--fs-label); color: var(--muted); font-style: italic; }

/* ══ Actions prioritaires ═════════════════════════════════════════════════
   Le point d'entrée de la journée : ce qu'il faut traiter, toutes
   applications confondues. Hors disposition — on ne le déplace ni ne le
   retire. */
.home-actions { margin-bottom: var(--sp-6); }
.actions-card { padding: 0; overflow: hidden; }
.actions-table { width: 100%; border-collapse: collapse; }
.actions-table th {
  text-align: left; font-size: var(--fs-label); font-weight: 600; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
  padding: var(--sp-2) var(--sp-4); border-bottom: 1px solid var(--line);
  background: var(--surface-2);
}
.actions-table th.right, .actions-table td.right { text-align: right; }
.actions-table td {
  padding: var(--sp-2) var(--sp-4); border-bottom: 1px solid var(--line);
  font-size: var(--fs-dense); vertical-align: middle;
}
.actions-table tbody tr:last-child td { border-bottom: 0; }
.actions-table tbody tr:hover { background: var(--surface-2); }

/* Le TYPE se lit comme une étiquette : c'est une catégorie, pas une phrase. */
.actions-type {
  display: inline-block; font-size: var(--fs-label); font-weight: 600;
  color: var(--muted); white-space: nowrap;
}
/* Seul le retard se colore. Si chaque ligne portait une couleur, aucune ne
   ressortirait — et c'est justement ce qu'on veut voir en premier. */
.actions-table tr[data-late] .actions-type { color: var(--danger); }
.actions-table tr[data-late] .actions-due { color: var(--danger); font-weight: 600; }

.actions-desc { display: block; color: var(--ink); font-weight: 550; }
.actions-detail { display: block; font-size: var(--fs-label); color: var(--muted); }
.actions-due { font-size: var(--fs-label); color: var(--muted); white-space: nowrap; }
.actions-buttons { display: inline-flex; gap: var(--sp-2); justify-content: flex-end; }
.actions-more { margin: var(--sp-2) 0 0; }

/* Le bandeau qui replie les actions suivantes. Il occupe la largeur du
   tableau, en pied : hors de lui, il flotterait sous une carte fermée et
   l'on ne verrait plus qu'il en dépend. */
.actions-fold-row td { padding: 0; border-bottom: 0; }
.actions-fold {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; padding: var(--sp-2) var(--sp-4);
  border: 0; border-top: 1px solid var(--line);
  background: var(--surface-2); cursor: pointer; text-align: left;
  font-size: var(--fs-dense); color: var(--ink);
}
.actions-fold:hover { background: var(--accent-soft); color: var(--accent); }
.actions-fold:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.actions-fold-caret { color: var(--muted); font-size: 11px; }
/* Ce que le bandeau cache, nommé : « 5 de plus » ne dit pas si l'on rate une
   urgence ou de la routine. */
.actions-fold-hint { margin-left: auto; font-size: var(--fs-label); color: var(--muted); }

/* Sous 900 px, la colonne d'échéance passe sous la description : trois
   colonnes de texte plus deux boutons ne tiennent pas. */
@media (max-width: 900px) {
  .actions-table thead { display: none; }
  /* Les lignes du CORPS seulement : le bandeau de pied n'a qu'une cellule
     pleine largeur, la mettre en grille à deux colonnes la couperait en deux. */
  .actions-table tbody tr {
    display: grid; grid-template-columns: 1fr auto; gap: 0 var(--sp-2);
    padding: var(--sp-2) 0; border-bottom: 1px solid var(--line);
  }
  .actions-table tbody td { border: 0; padding: 2px var(--sp-4); grid-column: 1; }
  /* Les boutons à droite, sur toute la hauteur des trois lignes de texte.
     Les placer explicitement en colonne 2 évite qu'ils se posent avant la
     description : à cette largeur, la grille les remontait en tête de ligne
     et l'on lisait « Ouvrir · Ignorer » avant de savoir de quoi il s'agit. */
  .actions-table tbody td:nth-child(4) {
    grid-column: 2; grid-row: 1 / span 3; align-self: center;
  }
}

/* ── Vue par défaut d'un rôle (Paramètres › Personnalisation) ────────────
   L'administrateur règle l'ORDRE et la largeur : une liste ordonnée suffit,
   la mise en page réelle ne lui apprendrait rien de plus. */
.role-home { padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-3); }
.role-home .settings-block-head { flex-wrap: wrap; margin-bottom: 0; }
.role-home-picker { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
/* `.setting-label` est prévu pour une ligne de réglage pleine largeur : ici
   il sert d'étiquette à un menu déroulant, et doit se serrer contre lui sans
   pour autant le toucher. */
.role-home-picker .setting-label {
  margin: 0; flex: none; font-size: var(--fs-dense); color: var(--muted);
}
/* Le groupe d'actions est poussé à droite d'un bloc, pas bouton par bouton :
   `.settings-block-head .btn { margin-left: auto }` ne décalerait que le
   premier et disperserait les autres. */
.role-home .board-actions { margin-left: auto; }
.role-home .board-actions .btn { margin-left: 0; }

.role-home-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column;
  counter-reset: rang;
}
.role-home-item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--line);
  counter-increment: rang;
}
.role-home-item:last-child { border-bottom: 0; }
/* Le rang se lit à gauche : c'est l'ordre d'affichage sur la page d'accueil. */
.role-home-item::before {
  content: counter(rang); flex: none;
  width: 20px; font-size: var(--fs-label); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.role-home-name { flex: 1; font-size: var(--fs-dense); color: var(--ink); }
.role-home-item .btn[disabled] { opacity: .4; cursor: not-allowed; }

/* ══ États (UX-08) ═════════════════════════════════════════════════════ */

.empty {
  text-align: center; padding: var(--sp-8) var(--sp-4); color: var(--muted);
  background: var(--surface); border: 1px dashed var(--line); border-radius: var(--radius-card);
}
.empty h2, .empty h3 { color: var(--ink); font-size: var(--fs-body); margin: 0 0 var(--sp-2); }
.empty p { margin: 0 0 var(--sp-4); font-size: var(--fs-dense); max-width: 42ch; margin-inline: auto; }

.skeleton {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--line) 50%, var(--surface-2) 75%);
  background-size: 200% 100%; animation: shimmer 1.4s infinite;
  border-radius: var(--radius-control); height: 14px;
}
@keyframes shimmer { to { background-position: -200% 0; } }

.error-panel {
  background: var(--danger-soft); border: 1px solid var(--danger);
  border-radius: var(--radius-card); padding: var(--sp-4); color: var(--danger);
}
.error-panel h2, .error-panel h3 { margin: 0 0 var(--sp-2); font-size: var(--fs-dense); }
.error-panel p { margin: 0; font-size: var(--fs-dense); color: var(--ink); }

.toast {
  position: fixed; bottom: var(--sp-6); left: 50%; transform: translateX(-50%);
  background: var(--ink); color: var(--bg); padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-card); box-shadow: var(--shadow-overlay);
  display: flex; align-items: center; gap: var(--sp-4); z-index: 100; font-size: var(--fs-dense);
}
.toast button { background: none; border: 0; color: var(--brand-teal); font-weight: 650; cursor: pointer; }

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(22, 32, 61, .45);
  display: grid; place-items: center; z-index: 90; padding: var(--sp-4);
}
.modal {
  background: var(--surface); border-radius: var(--radius-card);
  box-shadow: var(--shadow-overlay); padding: var(--sp-6);
  max-width: 460px; width: 100%;
  /* Bornée à la fenêtre : une liste longue débordait par le haut ET par le
     bas, emportant les boutons hors de l'écran. Le corps défile, le titre et
     les actions restent atteignables. */
  display: flex; flex-direction: column;
  max-height: calc(100vh - var(--sp-8));
}
.modal-body { overflow-y: auto; min-height: 0; margin: 0 calc(var(--sp-2) * -1); padding: 0 var(--sp-2); }
.modal h2 { margin: 0 0 var(--sp-2); font-size: var(--fs-section); }
.modal p { margin: 0 0 var(--sp-4); font-size: var(--fs-dense); color: var(--muted); }
.modal label { display: block; font-size: var(--fs-label); font-weight: 600; margin-bottom: var(--sp-1); }
/* Sauf dans une liste à cocher : l'étiquette y suit sa case sur la même
   ligne, et porte le corps de texte — pas la graisse d'un intitulé. */
.modal .checklist-label {
  display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap;
  font-size: var(--fs-dense); font-weight: 400; margin-bottom: 0; min-width: 0;
}
/* Les champs de saisie occupent la largeur — mais pas les cases à cocher :
   étirées sur 100 %, elles poussaient leur libellé sur une colonne d'un
   caractère de large, et chaque ligne de la liste faisait 124 px de haut. */
/* `range` rejoint `checkbox` et `radio` parmi les exceptions : ce n'est pas
   un champ de saisie qu'on encadre, c'est un rail. La règle lui donnait un
   fond blanc et une bordure, qui masquaient le rail dessiné dessous et
   décalaient la poignée d'une marge basse. */
.modal select,
.modal input:not([type="checkbox"]):not([type="radio"]):not([type="range"]) {
  width: 100%; padding: var(--sp-2); border: 1px solid var(--line);
  border-radius: var(--radius-control); background: var(--surface); margin-bottom: var(--sp-3);
}
.modal-actions {
  display: flex; gap: var(--sp-2); justify-content: flex-end;
  flex: none; padding-top: var(--sp-4);
}

/* ══ Fiche (vue micro) ═════════════════════════════════════════════════ */

/* ══ Fiche — bandeau de résumé, tunnel d'étapes, trois panneaux ═══════ */

.record-content { display: flex; flex-direction: column; gap: var(--sp-3); }

/* Bandeau de résumé */
/* Deux rangées, comme chez Salesforce : l'identité et les actions en haut,
   les faits en dessous sur toute la largeur, séparés par un filet. Le titre
   n'a plus à disputer la place aux faits — chacun a sa ligne. */
.record-summary {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  padding: 0;
  display: flex; flex-direction: column;
}
.summary-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); padding: var(--sp-3) var(--sp-4);
}
/* Le titre prend la place que les actions laissent : sans `flex: 1`, il se
   repliait sur deux lignes alors que le bandeau était à moitié vide. */
.summary-identity { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; flex: 1 1 auto; }

/* Pastille d'objet : le repère visuel qu'on attrape avant même de lire le
   titre. Carré arrondi et aplat coloré, à la manière des icônes d'objet
   Salesforce. */
.summary-badge {
  flex: none; width: 40px; height: 40px; border-radius: var(--radius-card);
  display: grid; place-items: center;
  background: var(--accent); color: #fff;
}
.summary-badge svg { width: 22px; height: 22px; }

/* Photo d'article, à la place de la pastille. Même encombrement que celle-ci
   pour que l'alignement du titre ne bouge pas d'une fiche à l'autre. */
.summary-photo { position: relative; flex: none; }
.summary-photo-btn {
  width: 56px; height: 56px; padding: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius-card);
  background: var(--surface-2); cursor: pointer;
  display: grid; place-items: center;
}
.summary-photo-btn:hover { border-color: var(--accent); }
.summary-photo-btn img { width: 100%; height: 100%; object-fit: contain; }
.summary-photo-empty {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
/* Le retrait ne s'offre qu'au survol : un « ✕ » permanent sur chaque photo
   attire l'œil vers la destruction plutôt que vers l'article. */
.summary-photo-clear {
  position: absolute; top: -6px; right: -6px;
  width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--muted); font-size: 11px; line-height: 1;
  cursor: pointer; opacity: 0; transition: opacity .12s ease;
}
.summary-photo:hover .summary-photo-clear,
.summary-photo-clear:focus-visible { opacity: 1; }
.summary-photo-clear:hover { color: var(--danger, #b42318); border-color: currentColor; }

/* Sélecteur de valeurs multiples : des pastilles qu'on coche, et un champ
   pour ce que la liste n'a pas prévu. */
.tag-picker {
  display: flex; flex-wrap: wrap; gap: var(--sp-1);
  max-height: 320px; overflow-y: auto; padding: 2px;
}
.tag-option {
  border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 12px; font-size: var(--fs-body);
  background: var(--surface); color: var(--ink); cursor: pointer;
}
.tag-option:hover { border-color: var(--accent); }
.tag-option[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.tag-add { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }
.tag-add .field-input { flex: 1; }

/* Éditeur de caractéristiques : libellé, valeur, retrait. Le libellé est plus
   étroit que la valeur — « connectivité » tient en un mot, sa valeur énumère
   six protocoles. */
.specs-editor {
  display: flex; flex-direction: column; gap: var(--sp-1);
  max-height: 340px; overflow-y: auto; padding: 2px;
}
.specs-row { display: grid; grid-template-columns: 1fr 1.6fr auto; gap: var(--sp-2); }
.specs-remove {
  border: 1px solid var(--line); border-radius: var(--radius-input, 6px);
  background: var(--surface); color: var(--muted);
  width: 32px; cursor: pointer; line-height: 1;
}
.specs-remove:hover { color: var(--danger, #b42318); border-color: currentColor; }

.summary-main { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* Le type d'objet situe, il ne s'annonce pas : petit, gris, sans gras — le
   fil d'Ariane le dit déjà juste au-dessus. */
.summary-kind {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .07em; font-weight: 550;
}
/* Le titre tient sur UNE ligne : un nom d'événement replié coupe la lecture
   du bandeau en deux et décale tout ce qui suit. Au-delà de la place
   disponible, il s'abrège — l'infobulle et la grille de détail portent le nom
   complet. */
.summary-title {
  margin: 0; font-size: var(--fs-page); font-weight: 700; letter-spacing: -.02em;
  line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0; max-width: 100%;
}
.summary-title-edit { max-width: 100%; overflow: hidden; }
.summary-title-edit > :first-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Rangée des faits : sous le titre, séparée par un filet, comme la barre de
   champs compacte d'une fiche Salesforce. Elle disparaît quand la fiche n'a
   aucun fait à montrer, plutôt que de laisser un liseré orphelin. */
/* Les faits occupent la largeur au lieu de s'entasser à gauche.
   `auto-fit` + `minmax` donne une grille qui respire : chaque fait reçoit au
   moins 140 px et se partage le reste à parts égales. Un `space-between` sur
   flex aurait poussé le premier et le dernier aux extrémités et laissé un
   trou au milieu dès qu'il n'y a que deux faits ; ici les colonnes restent
   régulières quel que soit leur nombre. */
/* Le titre modifiable garde l'allure d'un titre : le crayon n'apparaît qu'au
   survol, et le bouton n'a ni bordure ni fond propre. */
.summary-title-edit {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
  display: inline-flex; align-items: baseline; gap: var(--sp-2);
}
.summary-title-edit .field-pencil { opacity: 0; transition: opacity .12s ease; }
.summary-title-edit:hover .field-pencil,
.summary-title-edit:focus-visible .field-pencil { opacity: 1; }

.summary-facts {
  /* Les faits se répartissent sur la largeur au lieu de s'entasser à gauche.
     Flex plutôt que grille : `auto-fit` réservait des pistes vides quand les
     faits étaient peu nombreux, et `space-between` laissait un trou au milieu
     dès qu'il n'y en avait que deux. Ici chaque fait grandit à parts égales
     entre deux bornes — jamais tassé, jamais étiré au point de séparer son
     libellé de sa valeur.

     `space-between` répartit le RESTE : les faits gardent une largeur bornée
     (un libellé loin de sa valeur se lit mal) et c'est l'écart qui absorbe la
     place disponible. `gap` fixe l'espacement minimum, pour qu'à l'étroit deux
     faits ne se touchent jamais. */
  display: flex; flex-wrap: wrap; justify-content: space-between;
  /* Écart borné des deux côtés : `gap` fixe le minimum, et les faits
     absorbent le reste en grandissant (`flex-grow`) plutôt que de laisser
     `space-between` creuser des vides de 300 px sur un bandeau large. */
  gap: var(--sp-3) var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-top: 1px solid var(--line);
}
.summary-facts:empty { display: none; }
/* Un fait ne se coupe pas en deux lignes : « 4.500.000 DH » sur deux lignes
   se relit deux fois. Les faits gardent leur largeur, c'est le titre qui
   cède — il a déjà `overflow-wrap` pour se replier proprement. */
.summary-fact {
  display: flex; flex-direction: column; gap: 2px; white-space: nowrap;
  flex: 1 1 auto; min-width: 120px; max-width: 320px;
}
/* Un libellé de fait est une étiquette : il se lit une fois, puis l'œil va
   droit à la valeur. Sans gras, la valeur ressort d'elle-même. */
.summary-fact-label {
  font-size: var(--fs-label); color: var(--muted);
  letter-spacing: .01em; font-weight: 500;
}
/* La valeur n'a plus besoin de graisse : son libellé est désormais petit et
   gris, l'écart de taille et de couleur suffit à la faire ressortir. */
.summary-fact-value {
  font-size: var(--fs-body); font-weight: 450; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1.3;
}
.summary-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }

/* Afficher ou masquer une colonne est un réglage d'affichage, pas une action
   métier : ces boutons ne doivent jamais concurrencer « Nouveau devis ». Ni
   aplat, ni couleur d'accent — l'état actif se marque par le contraste du
   glyphe et un fond neutre, comme un onglet discret. */
.panel-toggles { display: flex; gap: 2px; margin-left: var(--sp-2); }
.panel-toggles button {
  padding: var(--sp-1) var(--sp-2); font-size: var(--fs-body);
  color: var(--muted); border-color: transparent; background: none;
  line-height: 1;
}
.panel-toggles button:hover { background: var(--surface-2); color: var(--ink); }
.panel-toggles button[aria-pressed="true"] { color: var(--ink); background: var(--surface-2); }

@media (max-width: 1000px) {
  /* En étroit, les actions passent sous l'identité plutôt que de comprimer
     le titre à quelques caractères. */
  .summary-head { flex-direction: column; align-items: stretch; }
  .summary-actions { justify-content: flex-start; }
}

/* Tunnel d'étapes en chevrons, cliquable */
.stage-path { display: flex; gap: 2px; overflow-x: auto; padding-bottom: 2px; }
.stage-step {
  flex: 1 1 0; min-width: 108px;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--muted); cursor: pointer;
  font-size: var(--fs-label); font-weight: 600;
  padding: var(--sp-2) var(--sp-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%, 10px 50%);
}
.stage-step:first-child { clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%); border-radius: var(--radius-control) 0 0 var(--radius-control); }
.stage-step:last-child { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 10px 50%); border-radius: 0 var(--radius-control) var(--radius-control) 0; }
.stage-step:hover:not([data-state="current"]) { background: var(--surface-2); color: var(--ink); }
.stage-step[data-state="done"] { background: var(--accent-soft); color: var(--accent); }
.stage-step[data-state="current"] {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  cursor: default; font-weight: 700;
}

/* Trois panneaux : liés · fiche · fil. Les latéraux se replient. */
/* Un groupe du modèle de checklist, dans Configuration : une étape du cycle
   et ses actions. */
.settings-block { margin-top: var(--sp-4); }
.settings-block-head {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-2);
}
.settings-block-head h3 {
  margin: 0; font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
.settings-block-head .btn { margin-left: auto; }

/* ── Retrait au survol, dans les widgets ─────────────────────────────────
   Montrée en permanence, la croix attirerait l'œil sur l'action plutôt que
   sur la personne ou l'appareil qu'elle qualifie. `focus-within` la révèle
   aussi au clavier. */
.widget-removable { display: flex; align-items: flex-start; gap: var(--sp-2); }
.widget-removable > :first-child { flex: 1; min-width: 0; }
.widget-remove {
  flex: none; border: 0; background: none; cursor: pointer;
  color: var(--muted); font-size: var(--fs-label);
  padding: 2px var(--sp-1); border-radius: 3px;
  opacity: 0; transition: opacity .12s ease;
}
.widget-removable:hover .widget-remove,
.widget-removable:focus-within .widget-remove { opacity: 1; }
.widget-remove:hover { background: var(--danger-soft); color: var(--danger); }
/* Le « + » d'ajout : même gabarit que la croix, pour que les deux gestes
   s'alignent d'un widget à l'autre. */
.widget-add { font-size: var(--fs-body); line-height: 1; padding: 0 var(--sp-1); }
.asset-line { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; min-width: 0; }

/* Les deux gestes du partage : copier, ou aller voir. */
.share-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }
.share-actions .btn { text-decoration: none; }

/* Une communication coupée recule sans disparaître : on doit voir ce qu'on
   a désactivé, et pouvoir le réactiver. */
.data-table tr.is-muted td { color: var(--muted); }
.data-table tr.is-muted .badge { opacity: .55; }

/* ── Matériel réservé ──────────────────────────────────────────────────── */
.asset-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.asset-item { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.asset-serial { font-size: var(--fs-dense); font-weight: 600; }
/* La référence catalogue situe l'appareil sans concurrencer son numéro de
   série, qui est ce qu'on lit sur l'étiquette. */
.asset-ref { font-size: var(--fs-label); color: var(--muted); }

/* ── Jauge de remplissage, dans l'en-tête Participants ───────────────────
   Elle qualifie le titre qu'elle accompagne : un bloc « Capacité » séparé
   disait la même chose sur trois lignes, au-dessus de la liste qu'il
   décrivait. Trois tons sobres, aucun clignotement. */
.participants-gauge {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-left: var(--sp-3); min-width: 0;
}
.participants-count {
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.participants-bar {
  display: block; width: 96px; height: 5px; border-radius: 3px;
  background: var(--line); overflow: hidden; flex: none;
}
.participants-bar-fill { display: block; height: 100%; border-radius: 3px; background: var(--brand-green); }
/* Sous le seuil minimum : l'événement n'est pas encore tenable. */
.participants-gauge[data-tone="under"] .participants-bar-fill { background: var(--warning); }
.participants-gauge[data-tone="under"] .participants-count { color: var(--warning); }
/* Complet : on ne prend plus personne — un fait, pas une alerte. */
.participants-gauge[data-tone="full"] .participants-bar-fill { background: var(--accent); }
.participants-gauge[data-tone="full"] .participants-count { color: var(--accent); }
.participants-note {
  font-size: var(--fs-label); color: var(--muted); white-space: nowrap;
}

/* L'avertissement d'une page en ligne : ambre, pas rouge — modifier reste
   permis, c'est la portée du geste qu'on signale. */
.public-live-warning {
  padding: var(--sp-3) var(--sp-4);
  background: var(--warning-soft); color: var(--warning);
  border-radius: var(--radius-control);
  font-size: var(--fs-dense); line-height: 1.5;
}

/* ── Onglet « Page publique » ────────────────────────────────────────── */
.public-tab { padding: var(--sp-4) 0; display: grid; gap: var(--sp-8); }
.public-block h3 {
  margin: 0 0 var(--sp-2); font-size: var(--fs-label); font-weight: 650;
  color: var(--muted); text-transform: uppercase; letter-spacing: .05em;
}

.visuals { display: grid; gap: var(--sp-4); grid-template-columns: 2fr 1fr; }
.visual-slot {
  border: 1px solid var(--line); border-radius: var(--radius-card);
  padding: var(--sp-3); background: var(--surface);
}
.visual-head { display: flex; align-items: baseline; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.visual-head h4 { margin: 0; font-size: var(--fs-dense); font-weight: 600; }
.visual-hint { font-size: var(--fs-label); color: var(--muted); }
.visual-preview { display: block; width: 100%; border-radius: var(--radius-control); }
/* La bannière se juge dans ses proportions réelles : une vignette carrée
   masquerait le recadrage que subira la page. */
.visual-banner { aspect-ratio: 8 / 3; object-fit: cover; }
.visual-logo { max-height: 72px; width: auto; object-fit: contain; }
.visual-empty {
  display: grid; place-items: center; min-height: 88px;
  background: var(--surface-2); border-radius: var(--radius-control);
  color: var(--muted); font-size: var(--fs-dense);
}
.visual-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }

.speaker-rows, .sessions-edit { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.speaker-row, .session-edit {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2); border-radius: var(--radius-control);
}
.speaker-row:hover, .session-edit:hover { background: var(--surface-2); }
.speaker-avatar {
  flex: none; width: 32px; height: 32px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-soft); color: var(--accent);
  font-size: var(--fs-label); font-weight: 650;
}
.speaker-text, .session-text { flex: 1; min-width: 0; display: grid; }
.speaker-text span, .session-text span { font-size: var(--fs-label); color: var(--muted); }
/* L'heure en colonne fixe : c'est ce qu'on parcourt pour situer la session. */
.session-hours {
  flex: none; width: 96px; font-size: var(--fs-dense); font-weight: 650;
  color: var(--accent); font-variant-numeric: tabular-nums;
}
/* Sur petit écran, 96 px de colonne fixe ne laissent pas de place au libellé :
   l'heure passe au-dessus, comme sur la page publique (.session-time). */
@media (max-width: 560px) {
  .session-edit { flex-direction: column; align-items: start; gap: var(--sp-1); }
  .session-hours { width: auto; }
}
.schedule-day { margin-top: var(--sp-4); }
.schedule-day h4 {
  margin: 0; font-size: var(--fs-dense); font-weight: 600;
  text-transform: capitalize;
}

@media (max-width: 720px) {
  .visuals { grid-template-columns: 1fr; }
}

/* ── Onglet Budget ───────────────────────────────────────────────────── */
.budget-tab { padding: var(--sp-4) 0; display: grid; gap: var(--sp-6); }
.budget-summary {
  max-width: 420px; display: grid; gap: var(--sp-2);
  padding: var(--sp-4); border: 1px solid var(--line);
  border-radius: var(--radius-card); background: var(--surface-2);
}
.budget-line { display: flex; justify-content: space-between; gap: var(--sp-4); font-size: var(--fs-dense); }
.budget-line strong { font-variant-numeric: tabular-nums; }
.budget-spent { color: var(--muted); }
/* Le solde se détache : c'est la ligne qu'on vient lire. */
.budget-total {
  border-top: 1px solid var(--line); padding-top: var(--sp-2); margin-top: var(--sp-1);
  font-size: var(--fs-body);
}
.budget-total strong { color: var(--success); }
/* Un dépassement doit se voir, pas se deviner. */
.budget-total.is-over strong { color: var(--danger); }
.budget-gauge { margin-top: var(--sp-2); }
.budget-bar {
  height: 6px; border-radius: 3px; background: var(--line); overflow: hidden;
}
.budget-bar-fill { height: 100%; background: var(--success); border-radius: 3px; }
.budget-bar-fill.is-over { background: var(--danger); }
.budget-ratio { display: block; margin-top: var(--sp-1); font-size: var(--fs-label); color: var(--muted); }
.budget-block h3 {
  margin: 0 0 var(--sp-2); font-size: var(--fs-label); font-weight: 650;
  color: var(--muted); text-transform: uppercase; letter-spacing: .05em;
}
.budget-categories { max-width: 420px; }
.budget-categories dd { text-align: right; font-variant-numeric: tabular-nums; }
.budget-table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── Checklist de l'étape ────────────────────────────────────────────── */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.checklist-item {
  display: flex; align-items: baseline; gap: var(--sp-2);
  padding: var(--sp-1) 0;
}
/* `display: flex` l'emporte sur l'attribut `hidden` : sans cette règle, une
   ligne filtrée resterait visible. Même piège que `.setting-row`. */
.checklist-item[hidden] { display: none; }
.checklist-box { margin: 0; flex: none; cursor: pointer; accent-color: var(--accent); }
.checklist-label { font-size: var(--fs-dense); cursor: pointer; flex: 1; min-width: 0; }
/* Une action faite recule sans disparaître : on doit pouvoir vérifier ce
   qu'on a coché, et décocher si l'on s'est trompé de ligne. */
.checklist-item.is-done .checklist-label { color: var(--muted); text-decoration: line-through; }
.checklist-remove {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  font-size: var(--fs-label); padding: 0 var(--sp-1); border-radius: 3px;
  opacity: 0; transition: opacity .12s ease;
}
.checklist-item:hover .checklist-remove,
.checklist-item:focus-within .checklist-remove { opacity: 1; }
.checklist-remove:hover { background: var(--danger-soft); color: var(--danger); }

/* Colonne droite : le fil et ce qui l'accompagne. Le `sticky` vit sur cette
   pile plutôt que sur le fil seul — sinon un widget posé dessous défilerait
   pendant que le fil resterait figé. */
.record-aside {
  display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0;
  position: sticky; top: calc(var(--header-h) + var(--control-h) + var(--sp-4));
  max-height: calc(100vh - var(--header-h) - var(--control-h) - var(--sp-8));
  overflow-y: auto;
}
/* Le fil ne se colle plus lui-même : c'est la colonne qui s'en charge. */
.record-aside .feed { position: static; max-height: none; }

.record-layout {
  display: grid;
  grid-template-columns: minmax(240px, 280px) minmax(0, 1fr) minmax(320px, 380px);
  gap: var(--sp-3);
  align-items: start;
}
.record-layout[data-side="false"] { grid-template-columns: minmax(0, 1fr) minmax(320px, 380px); }
.record-layout[data-feed="false"] { grid-template-columns: minmax(240px, 280px) minmax(0, 1fr); }
.record-layout[data-side="false"][data-feed="false"] { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 1280px) {
  .record-layout,
  .record-layout[data-side="false"],
  .record-layout[data-feed="false"] { grid-template-columns: minmax(0, 1fr); }
}

/* Panneau latéral : widgets Société et Contacts */
.record-side { min-width: 0; }
.record-side[hidden] { display: none; }
.panel-body { display: flex; flex-direction: column; gap: var(--sp-3); }

.widget {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  overflow: hidden;
}
.widget-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--line);
  background: var(--surface-2);
}
.widget-head h2, .widget-head h3 {
  margin: 0; font-size: var(--fs-label); font-weight: 650;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
}
.widget-count {
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.widget-link {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  color: var(--accent); font-size: var(--fs-label); font-weight: 650;
  padding: 0 var(--sp-1); border-radius: var(--radius-control);
}
.widget-link:hover { background: var(--accent-soft); }
.widget-body { padding: var(--sp-3); display: flex; flex-direction: column; gap: var(--sp-2); }
.widget-title { font-size: var(--fs-body); font-weight: 650; letter-spacing: -.01em; }
.widget .badge { align-self: flex-start; }
.widget-facts { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 2px var(--sp-3); }
.widget-facts dt { font-size: var(--fs-label); color: var(--muted); }
.widget-facts dd { margin: 0; font-size: var(--fs-dense); }

/* Facteurs de score d'une piste : le poids se lit en colonne, l'explication
   dessous en petit — on scanne les poids, on lit le détail si besoin. */
.score-factors { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--sp-2); }
.score-factor { display: flex; flex-direction: column; gap: 1px; }
.score-factor-head { display: flex; align-items: baseline; gap: var(--sp-2); }
.score-factor-name { font-size: var(--fs-dense); font-weight: 600; }
.score-factor-weight {
  margin-left: auto; font-size: var(--fs-label); font-weight: 650;
  color: var(--brand-green-text); font-variant-numeric: tabular-nums;
}
.score-factor-why { font-size: var(--fs-label); color: var(--muted); line-height: 1.4; }

.contact-card {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-control);
  margin: 0 calc(var(--sp-2) * -1);
}
.contact-card:hover { background: var(--surface-2); }
.contact-avatar {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; font-size: 10px; font-weight: 700;
}
.contact-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.contact-name {
  font-size: var(--fs-dense); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.contact-role {
  font-size: var(--fs-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.contact-channel { margin-left: auto; color: var(--brand-green-text); font-size: 13px; }

.feed[hidden] { display: none; }

/* Le cadenas remplace la phrase qui se répétait sous quinze champs :
   l'explication vit dans l'infobulle, pour qui la cherche. */
.field-lock { margin-left: var(--sp-1); font-size: 10px; cursor: help; opacity: .6; }
.field-locked .field-value { color: var(--muted); }

/* Ce qui manque pour planifier se voit AVANT de cliquer « Planifier ».
   Deux niveaux :
     · SIGNALÉ — la puce rouge dit ce qu'il faudra remplir. Le champ garde son
       fond blanc : sur un brouillon en cours d'écriture, rien n'est en faute ;
     · BLOQUANT — après une tentative de planification refusée, le champ prend
       un fond rouge clair. L'utilisateur vient de lire la liste dans le
       dialogue, il doit la retrouver d'un coup d'œil dans la fiche. */
.field-required {
  margin-left: var(--sp-2); padding: 0 var(--sp-1);
  font-size: 10px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em;
  color: var(--danger); background: var(--danger-soft); border-radius: 3px;
}
.field-missing .field-value { color: var(--danger); }

.field-blocking {
  background: var(--danger-soft);
  border-radius: var(--radius-control);
  /* Le fond déborde du champ pour l'entourer sans décaler la grille : un
     padding pousserait les colonnes voisines. */
  box-shadow: 0 0 0 var(--sp-2) var(--danger-soft);
}
/* La puce s'inverse sur ce fond : rouge sur rouge clair ne se détacherait
   plus. */
.field-blocking .field-required { background: var(--danger); color: #fff; }

/* ══ Formulaire de création ═══════════════════════════════════════════ */

.form-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3) var(--sp-4);
  /* Chaque ligne démarre en haut : deux champs voisins de hauteurs
     différentes — un menu déroulant et une liste de pastilles — gardent
     leurs libellés alignés. */
  align-items: start;
  max-height: 60vh; overflow-y: auto; padding-right: var(--sp-1);
}
.form-row { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.form-row[data-width="full"] { grid-column: 1 / -1; }
.modal label.form-label {
  display: flex; align-items: center; gap: 3px; margin: 0;
  font-size: var(--fs-label); color: var(--muted);
  letter-spacing: .01em; font-weight: 500;
}
.form-required { color: var(--danger); font-weight: 700; }
.form-help { font-size: var(--fs-label); color: var(--muted); line-height: 1.35; }

/* Repère d'explication : l'infobulle porte le texte, le formulaire reste
   épuré. Assez discret pour ne pas concurrencer le libellé, assez net pour
   qu'on sache qu'il y a quelque chose à survoler. */
.form-hint {
  display: inline-flex; align-items: center; justify-content: center;
  width: 14px; height: 14px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--muted); cursor: help;
  font-size: 10px; font-weight: 700; line-height: 1;
  text-transform: none; letter-spacing: 0;
}
.form-hint:hover, .form-hint:focus-visible { border-color: var(--accent); color: var(--accent); }
.form-row .field-input,
.form-row .feed-composer-input { margin: 0; width: 100%; }
.form-error {
  margin: var(--sp-3) 0 0; padding: var(--sp-2) var(--sp-3);
  background: var(--danger-soft); color: var(--danger);
  border-radius: var(--radius-control); font-size: var(--fs-dense); font-weight: 600;
}
.form-error[hidden] { display: none; }

/* Séparateur de groupe : au-delà de cinq champs, une liste plate oblige à
   tout relire pour retrouver un champ. Un filet et un mot suffisent à
   découper — plus d'espace au-dessus qu'en dessous, le titre appartient à
   ce qui suit. */
.form-section {
  grid-column: 1 / -1;
  display: flex; align-items: center; gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.form-section:first-child { margin-top: 0; }
.form-section::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.form-section-label {
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .07em;
}

/* Unité dans le champ : la zone de saisie et son unité forment un seul
   bloc, l'unité restant en retrait pour ne pas concurrencer la valeur. */
.form-input-suffixed { position: relative; display: flex; align-items: center; }
.form-input-suffixed .field-input { width: 100%; padding-right: 52px; }
.form-suffix {
  position: absolute; right: var(--sp-3); pointer-events: none;
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
}

/* Sélection multiple par pastilles. Un `<select multiple>` natif exige de
   maintenir Ctrl — règle que personne ne devine. Ici chaque valeur est un
   bouton bascule : l'état sélectionné se lit sans cliquer. */
/* Sélection multiple : au repos, un menu déroulant ordinaire, de la même
   hauteur que les champs voisins. Les valeurs retenues s'ajoutent dessous en
   puces, le champ ne grandissant qu'à mesure des choix réellement faits. */
.form-multi { display: flex; flex-direction: column; gap: 6px; }
.form-multi .field-input { width: 100%; }
/* Saisie libre : n'apparaît qu'après avoir choisi « Autre… », et disparaît
   une fois la valeur retenue — le champ ne reste jamais vide à l'écran. */
.form-other[hidden] { display: none; }
.form-chips { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.form-chips[hidden] { display: none; }
.form-chip {
  display: inline-flex; align-items: center; gap: 1px; max-width: 100%;
  padding: 2px 3px 2px 8px; border-radius: 999px;
  /* Le couple accent / accent-ink est le seul lisible dans les deux thèmes :
     en sombre, le bleu de marque sous un texte clair tomberait à 1,9:1. */
  background: var(--accent); color: var(--accent-ink);
  font-size: var(--fs-label); font-weight: 600; line-height: 1.3;
}
/* Une origine saisie librement peut être longue : elle se tronque plutôt que
   de faire déborder la colonne. */
.form-chip-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.form-chip-remove {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: inherit; cursor: pointer;
  font-size: 14px; line-height: 1; opacity: .75;
}
.form-chip-remove:hover { opacity: 1; background: rgb(255 255 255 / .22); }
.form-chip-remove:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }

@media (max-width: 620px) {
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .form-row[data-width="half"] { grid-column: 1 / -1; }
}

/* ══ Configurateur guidé (CPQ) ════════════════════════════════════════ */

.cpq-modal {
  max-width: 720px; width: min(720px, 94vw);
  max-height: 88vh; display: flex; flex-direction: column; padding: 0;
}
.cpq-head {
  padding: var(--sp-6) var(--sp-6) var(--sp-4);
  border-bottom: 1px solid var(--line);
}
.cpq-head h2 { margin: 0 0 var(--sp-1); }
.cpq-head p { margin: 0 0 var(--sp-4); color: var(--muted); font-size: var(--fs-dense); }
.modal label.cpq-main-label {
  display: block; margin-bottom: var(--sp-1);
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 650;
}
.cpq-main-select {
  width: 100%; font: inherit; font-size: var(--fs-body);
  border: 1px solid var(--line); border-radius: var(--radius-control);
  background: var(--surface); color: var(--ink); padding: var(--sp-2) var(--sp-3);
}
.cpq-main-select:focus-visible { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }

/* Le corps défile ; l'en-tête et le pied restent visibles. */
.cpq-body { overflow-y: auto; padding: var(--sp-4) var(--sp-6); flex: 1; }
.cpq-step + .cpq-step { margin-top: var(--sp-6); }
.cpq-step-title {
  margin: 0 0 var(--sp-2); font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 650;
}
.cpq-choices { display: flex; flex-direction: column; gap: 2px; }

/* Une ligne de choix : case · libellé extensible · prix aligné à droite.
   `min-width: 0` sur le libellé est indispensable — sans lui, le texte se
   comprime au lieu d'occuper l'espace, et les mots se coupent. */
/* Sélecteur qualifié : `.modal label { display:block }` l'emporterait
   sinon, et les trois éléments s'empileraient au lieu de s'aligner. */
.modal label.cpq-choice {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-control);
  font-size: var(--fs-dense); font-weight: 400; cursor: pointer;
  border: 1px solid transparent; margin-bottom: 0;
}
.cpq-choice:hover { background: var(--surface-2); }
.cpq-choice:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); }
.cpq-choice[data-disabled="true"] { opacity: .5; cursor: not-allowed; }
.cpq-choice input { margin: 0; accent-color: var(--accent); width: 16px; height: 16px; flex: none; }
.cpq-choice-name {
  min-width: 0; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
}
.cpq-choice-price {
  font-variant-numeric: tabular-nums; color: var(--muted);
  white-space: nowrap; text-align: right;
}

.cpq-foot {
  border-top: 1px solid var(--line); padding: var(--sp-3) var(--sp-6) var(--sp-4);
  display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap;
  background: var(--surface);
}
.cpq-totals { font-size: var(--fs-dense); color: var(--muted); flex: 1; min-width: 0; }
.cpq-totals strong { color: var(--ink); font-size: var(--fs-body); }
.cpq-foot .modal-actions { margin: 0; }

@media (max-width: 620px) {
  .cpq-head, .cpq-body, .cpq-foot { padding-left: var(--sp-4); padding-right: var(--sp-4); }
  .cpq-foot { flex-direction: column; align-items: stretch; }
}

/* ══ Devis — présenté comme un document ═══════════════════════════════ */

.quote-doc { display: flex; flex-direction: column; gap: var(--sp-4); }
.quote-head {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--sp-4) var(--sp-8);
  padding-bottom: var(--sp-4); border-bottom: 1px solid var(--line);
}
.quote-parties { display: flex; flex-direction: column; gap: var(--sp-3); }
.quote-field { display: flex; flex-direction: column; gap: 2px; }
/* Libellé et valeur se distinguent par la TAILLE et la COULEUR, pas par la
   graisse. Le libellé était en 650 et sa valeur en 550 : le mot « CLIENT »
   pesait plus lourd que le nom du client, et la page entière semblait en
   gras. Un libellé discret laisse la valeur porter l'information — c'est
   elle qu'on vient lire. */
.quote-field-label {
  font-size: var(--fs-label); color: var(--muted);
  letter-spacing: .01em; font-weight: 500;
}
.quote-field-value { font-size: var(--fs-body); font-weight: 400; color: var(--ink); }
.quote-field .badge { align-self: flex-start; }

.quote-lines td { vertical-align: top; }
/* Le nom du produit se distingue de sa description par la COULEUR, pas par le
   gras : sur dix lignes, dix noms en gras redonnaient à la page l'aspect
   chargé qu'on vient d'en retirer. */
.quote-product { font-weight: 500; color: var(--ink); }
.quote-product-note { font-size: var(--fs-label); color: var(--muted); font-style: italic; margin-top: 2px; }
/* Le montant d'une ligne compte, mais moins que le total du devis : à graisse
   égale, on ne savait plus lequel des deux lire en premier. */
.quote-line-total { font-weight: 550; }
.margin-low { color: var(--warning); font-weight: 650; }
/* Cellule chiffrée modifiable : discrète au repos, comme les champs de fiche. */
button.field-trigger.quote-cell {
  justify-content: flex-end; width: auto; margin-left: auto;
  font-variant-numeric: tabular-nums; padding: 0 var(--sp-2);
}
.icon-btn {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  font-size: 13px; padding: 2px var(--sp-1); border-radius: var(--radius-control);
}
.icon-btn:hover { background: var(--danger-soft); color: var(--danger); }

.quote-line-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Conditions à gauche, totaux à droite — tant que les deux tiennent. En
   dessous, ils s'empilent : côte à côte dans une feuille étroite, les
   conditions tombaient à 190 px et leurs valeurs se coupaient en deux.
   `auto-fit` mesure la place réelle, là où une requête de média mesurait la
   fenêtre — qui ne dit rien de la largeur laissée par les panneaux. */
.quote-footer {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 30ch), 1fr));
  gap: var(--sp-6) var(--sp-8); align-items: start;
  padding-top: var(--sp-4); border-top: 1px solid var(--line);
}
/* Conditions contractuelles : ce qui engage TIBO au-delà du prix. Deux
   colonnes de faits courts, puis les mentions libres en pleine largeur. */
.quote-terms { max-width: 52ch; display: flex; flex-direction: column; gap: var(--sp-3); }
.quote-terms-title {
  margin: 0; font-size: var(--fs-label); font-weight: 650;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
}
/* Deux colonnes seulement si elles tiennent. `minmax(14ch, 1fr)` mesure la
   place réellement disponible, là où une requête de média mesurait la
   FENÊTRE : avec les trois panneaux ouverts, la feuille tombe à 550 px et
   les conditions se retrouvaient sur 87 px par colonne — « À définir »
   passait à la ligne au milieu. */
.quote-terms-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(14ch, 1fr));
  gap: var(--sp-3) var(--sp-4);
}
.quote-term { min-width: 0; }
.quote-term-wide { grid-column: 1 / -1; }
/* La note interne se distingue : elle ne part pas au client. */
.quote-term-internal { padding-top: var(--sp-2); border-top: 1px dashed var(--line); }
.quote-term-internal .field-value { color: var(--muted); font-style: italic; }
/* Plus de requête de média ici : `auto-fit` retombe seul sur une colonne. */
.quote-totals {
  margin: 0; display: grid; grid-template-columns: auto auto;
  gap: var(--sp-2) var(--sp-6); align-items: baseline;
  min-width: 280px;
}
.quote-totals dt { font-size: var(--fs-dense); color: var(--muted); }
.quote-totals dd {
  margin: 0; text-align: right; font-size: var(--fs-dense);
  font-weight: 650; font-variant-numeric: tabular-nums;
}
.quote-totals dt.grand, .quote-totals dd.grand {
  font-size: var(--fs-section); font-weight: 700; color: var(--ink);
  padding-top: var(--sp-2); border-top: 1px solid var(--line);
}
.quote-totals dt.discount, .quote-totals dd.discount { color: var(--warning); }
.quote-totals dt.margin, .quote-totals dd.margin { color: var(--muted); font-size: var(--fs-label); }

.quote-alert {
  background: var(--warning-soft); border: 1px solid var(--warning);
  border-radius: var(--radius-card); padding: var(--sp-3) var(--sp-4);
  display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start;
}
.quote-alert strong { color: var(--warning); font-size: var(--fs-dense); }
.quote-alert p { margin: 0; font-size: var(--fs-dense); color: var(--ink); }

/* Étape terminale du tunnel : gagné ou perdu, une seule case. */
.stage-step.stage-close[data-state="won"] {
  background: var(--success-soft); border-color: var(--success); color: var(--success);
}
.stage-step.stage-close[data-state="lost"] {
  background: var(--danger-soft); border-color: var(--danger); color: var(--danger);
}

/* ── Vue Analyse (pivot) ───────────────────────────────────────────── */
.pivot-bar {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-3); flex-wrap: wrap;
}
.pivot-label {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 650;
}
.pivot-axes { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-control); overflow: hidden; }
.pivot-axis {
  border: 0; border-right: 1px solid var(--line); background: var(--surface);
  cursor: pointer; padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-dense); color: var(--muted);
}
.pivot-axis:last-child { border-right: 0; }
.pivot-axis:hover { background: var(--surface-2); color: var(--ink); }
.pivot-axis[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); font-weight: 650; }
.pivot-total { margin-left: auto; font-size: var(--fs-dense); color: var(--muted); font-variant-numeric: tabular-nums; }
.pivot-share-head { width: 30%; }
.pivot-share { height: 8px; background: var(--surface-2); border-radius: 99px; overflow: hidden; }
.pivot-share i { display: block; height: 100%; background: var(--accent); border-radius: 99px; }

/* Badge de l'application dans la barre de navigation (remplace le logo) */
.app-badge {
  width: 26px; height: 26px; border-radius: 7px; flex: none;
  background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center;
}
.app-badge svg { display: block; }
.app-name { font-size: var(--fs-body); font-weight: 650; letter-spacing: -.01em; color: var(--ink); }

.record-sheet {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  padding: var(--sp-6); min-width: 0;
}

/* ── Feed / chatter ────────────────────────────────────────────────── */
.feed {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); box-shadow: var(--shadow-card);
  display: flex; flex-direction: column; min-width: 0;
  position: sticky; top: calc(var(--header-h) + var(--control-h) + var(--sp-4));
  max-height: calc(100vh - var(--header-h) - var(--control-h) - var(--sp-8));
}
/* Même en-tête que les widgets du panneau gauche : fond gris, même hauteur,
   même graisse de titre. Deux blocs côte à côte qui se ressemblent se lisent
   comme un ensemble ; deux traitements différents donnaient l'impression de
   deux composants sans rapport. */
.feed-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--line);
  background: var(--surface-2);
}
.feed-head h2, .feed-head h3 {
  margin: 0; font-size: var(--fs-label); font-weight: 650;
  text-transform: uppercase; letter-spacing: .07em; color: var(--muted);
}
.feed-head .feed-count { font-size: var(--fs-label); color: var(--muted); }
.feed-actions { margin-left: auto; display: flex; gap: var(--sp-1); }
.feed-actions button {
  border: 1px solid var(--line); background: var(--surface); cursor: pointer;
  border-radius: var(--radius-control); padding: 2px var(--sp-2);
  font-size: var(--fs-label); color: var(--muted);
}
.feed-actions button:hover { background: var(--surface-2); color: var(--ink); }
/* Voir « Chronologie » plus bas pour le filet et le retrait à gauche. */
.feed-list {
  overflow-y: auto; display: flex; flex-direction: column; gap: var(--sp-3);
}
/* Deux pistes : une gouttière fixe qui réserve la place du marqueur, puis le
   contenu. Le marqueur est posé en absolu, centré sur l'axe — la gouttière
   garantit seulement que le texte ne passe pas dessous. */
.feed-entry {
  display: grid;
  grid-template-columns: calc(var(--rail-r) + var(--sp-1)) minmax(0, 1fr);
}
.feed-entry > :not(.feed-avatar):not(.feed-mark) { grid-column: 2; }
.feed-avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; font-size: 10px; font-weight: 700;
}
.feed-avatar[data-kind="SYSTEM"] { background: var(--surface-2); color: var(--muted); }
.feed-avatar[data-kind="AGENT"] { background: var(--ai-surface); color: var(--brand-green-text); }
.feed-body { min-width: 0; }
.feed-meta { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.feed-author { font-size: var(--fs-dense); font-weight: 600; }
.feed-date { font-size: var(--fs-label); color: var(--muted); }
.feed-text { font-size: var(--fs-dense); margin: 2px 0 0; overflow-wrap: anywhere; }
.feed-channel {
  font-size: var(--fs-label); font-weight: 550; padding: 0 5px; border-radius: 3px;
  background: var(--surface-2); color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
}
.feed-channel[data-direction="IN"] { background: var(--ai-surface); color: var(--brand-green-text); }
.feed-empty { padding: var(--sp-6) var(--sp-4); color: var(--muted); font-size: var(--fs-dense); text-align: center; }

/* ══ Chronologie ══════════════════════════════════════════════════════
   Un filet vertical relie les entrées : le fil devient une suite d'événements
   datés, et non cinq blocs flottants. Deux poids de lecture — les échanges
   se lisent, les événements système se survolent. */

/* Un seul axe pour toute la chronologie : le filet, les avatars et les
   pastilles s'y accrochent. Chacun avait sa propre marge négative, calculée
   à la main — d'où des pastilles qui flottaient à côté du filet. */
.feed-list {
  --rail-x: calc(var(--sp-4) + 13px);   /* centre de l'axe */
  --rail-r: 13px;                        /* rayon du plus gros marqueur */
  position: relative;
  padding: var(--sp-3) var(--sp-4) var(--sp-4) var(--rail-x);
}
/* Le filet court derrière les marqueurs, du premier au dernier point. */
.feed-list::before {
  content: ''; position: absolute;
  left: var(--rail-x); top: var(--sp-4); bottom: var(--sp-4);
  width: 1px; margin-left: -.5px; background: var(--line);
}
.feed-list > * { position: relative; }

/* ── Séparateur de jour ────────────────────────────────────────────── */
.feed-day {
  display: flex; align-items: center;
  /* Remonte jusqu'au bord du panneau : le libellé masque le filet derrière
     lui, ce qui sépare visuellement les journées. */
  margin: var(--sp-4) 0 0 calc(-1 * var(--rail-x));
  padding-left: var(--sp-1);
}
.feed-day:first-child { margin-top: 0; }
.feed-day span {
  font-size: var(--fs-label); font-weight: 550; color: var(--muted);
  text-transform: uppercase; letter-spacing: .07em;
  background: var(--surface); padding-right: var(--sp-2);
}

/* ── Échange ou note : du contenu, avec son auteur ─────────────────── */
/* Marqueurs centrés sur l'axe : `left: 0` les place au bord du contenu, qui
   commence à --rail-x ; la translation de −50 % les recentre sur le filet,
   quelle que soit leur taille. Plus aucune marge négative à recalculer. */
.feed-avatar {
  position: absolute; left: 0; top: 0;
  transform: translateX(-50%);
  border: 2px solid var(--surface);        /* interrompt proprement le filet */
  box-sizing: content-box;
}
.feed-entry[data-kind="NOTE"] .feed-avatar { background: var(--favorite-soft); color: var(--favorite); }

.feed-text {
  font-size: var(--fs-dense); margin: 2px 0 0; line-height: 1.5;
  overflow-wrap: anywhere; color: var(--ink);
}
/* Une note interne ne doit jamais être confondue avec un message parti chez
   le client : un liseré suffit, sans aplat qui crierait plus fort que le texte. */
.feed-entry[data-kind="NOTE"] .feed-text {
  border-left: 2px solid var(--favorite);
  padding-left: var(--sp-3); color: var(--ink);
}
.feed-note-tag {
  font-size: var(--fs-label); font-weight: 550; color: var(--favorite);
  text-transform: uppercase; letter-spacing: .05em;
}

/* ── Reprendre ou retirer ce qu'on a écrit ───────────────────────────
   Les boutons n'apparaissent qu'au survol : montrés en permanence sur un fil
   de vingt messages, ils feraient un mur de croix et tireraient l'œil vers
   l'action plutôt que vers le texte. Ils restent atteignables au clavier —
   `:focus-within` les révèle. */
.feed-entry-actions {
  margin-left: auto; display: flex; gap: 2px;
  opacity: 0; transition: opacity .12s ease;
}
.feed-entry:hover .feed-entry-actions,
.feed-entry:focus-within .feed-entry-actions { opacity: 1; }
.feed-entry-action {
  border: 0; background: none; cursor: pointer; padding: 0 var(--sp-1);
  font-size: var(--fs-dense); line-height: 1.4; color: var(--muted);
  border-radius: 3px;
}
.feed-entry-action:hover { background: var(--surface-2); color: var(--ink); }
.feed-entry-danger:hover { background: var(--danger-soft); color: var(--danger); }
/* La mention de correction reste discrète : elle informe, elle n'accuse pas. */
.feed-edited { font-size: var(--fs-label); color: var(--muted); font-style: italic; }
/* Le texte qu'on s'apprête à retirer, rappelé dans la confirmation. */
.feed-quote {
  margin: var(--sp-3) 0 0; padding: var(--sp-2) var(--sp-3);
  border-left: 3px solid var(--line); background: var(--surface-2);
  font-size: var(--fs-dense); color: var(--muted); overflow-wrap: anywhere;
}

/* ── Événement : un fait, en une ligne, sans avatar ────────────────── */
.feed-event { align-items: start; }
/* Même axe que l'avatar : centrée sur le filet, et verticalement calée sur
   la première ligne de texte plutôt que sur un `margin-top` approximatif. */
.feed-mark {
  position: absolute; left: 0; top: 0;
  transform: translateX(-50%);
  width: 18px; height: 18px; border-radius: 50%;
  display: grid; place-items: center; font-size: 10px;
  background: var(--surface-2); color: var(--muted);
  border: 2px solid var(--surface); box-sizing: content-box;
}
.feed-event[data-kind="CREATION"] .feed-mark { background: var(--success-soft); color: var(--success); }
.feed-event[data-state="late"] .feed-mark { background: var(--danger-soft); color: var(--danger); }
.feed-event[data-state="done"] .feed-mark { background: var(--success-soft); color: var(--success); }

.feed-event-body { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.feed-event-line {
  font-size: var(--fs-label); line-height: 1.5;
  display: flex; align-items: baseline; gap: 4px; flex-wrap: wrap;
}
/* Une activité : le sujet occupe sa ligne, le complément passe dessous. */
.feed-event[data-kind="ACTIVITY"] .feed-event-line { display: block; }
.feed-event[data-kind="ACTIVITY"] .feed-event-strong { display: block; }
.feed-event[data-kind="ACTIVITY"] .feed-event-text { display: block; }
.feed-event-text { color: var(--muted); }
.feed-event-strong { color: var(--ink); font-weight: 600; font-size: var(--fs-dense); }
.feed-event[data-state="done"] .feed-event-strong { text-decoration: line-through; color: var(--muted); }
.feed-event-meta {
  display: flex; align-items: baseline; gap: var(--sp-3);
  font-size: var(--fs-label); color: var(--muted);
  flex-wrap: wrap; row-gap: 2px;
}
/* L'action reste sur la ligne des métadonnées tant que la place le permet :
   séparée, elle perdrait le lien avec l'activité qu'elle conclut. */
.feed-done-link { margin-left: auto; }

/* ── Changements de champs, sur la ligne ───────────────────────────── */
.change-field { color: var(--muted); }
.change-from { color: var(--muted); text-decoration: line-through; }
.change-arrow { color: var(--muted); opacity: .6; }
.change-to { font-weight: 650; color: var(--ink); }

/* ── Activité : le retard se signale sans crier ────────────────────── */
.feed-due { color: var(--muted); }
.feed-late { color: var(--danger); font-weight: 550; }
.feed-done-link {
  border: 0; background: none; cursor: pointer; padding: 0;
  font: inherit; font-size: var(--fs-label); font-weight: 550;
  color: var(--accent); text-decoration: underline; text-underline-offset: 2px;
}
.feed-done-link:hover { color: var(--ink); }

/* ── Abonnés ───────────────────────────────────────────────────────── */
.feed-followers {
  display: flex; align-items: center; gap: var(--sp-1);
  padding: 0 var(--sp-4) var(--sp-2); flex-wrap: wrap;
}
.feed-followers:empty { display: none; }
.feed-follower {
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface-2); color: var(--muted);
  display: grid; place-items: center; font-size: 9px; font-weight: 700;
  border: 1px solid var(--line);
}

/* ── Filtres du fil ────────────────────────────────────────────────── */
.feed-filters {
  display: flex; gap: 2px; padding: 0 var(--sp-3) var(--sp-2);
  overflow-x: auto; flex-wrap: wrap;
}
.feed-filter {
  border: 0; background: none; cursor: pointer;
  font-size: var(--fs-label); color: var(--muted);
  padding: 2px var(--sp-2); border-radius: var(--radius-control); white-space: nowrap;
}
.feed-filter:hover { background: var(--surface-2); color: var(--ink); }
/* Filtre actif : un état de vue, pas une action. Le fond neutre et le texte
   contrasté suffisent — l'accent reste réservé aux boutons qui agissent. */
.feed-filter[aria-pressed="true"] { background: var(--surface-2); color: var(--ink); font-weight: 600; }

/* ── Composition ───────────────────────────────────────────────────── */
.feed-composer-input {
  width: 100%; font: inherit; font-size: var(--fs-dense);
  border: 1px solid var(--line); border-radius: var(--radius-control);
  padding: var(--sp-2) var(--sp-3); background: var(--surface); color: var(--ink);
  resize: vertical; margin-top: var(--sp-2);
}
.feed-composer-input:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ── Fil d'Ariane du parcours (barre macro) ───────────────────────── */
.trail { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; font-size: var(--fs-dense); }
.trail-step {
  border: 0; background: none; cursor: pointer; padding: 2px var(--sp-2);
  border-radius: var(--radius-control); color: var(--muted);
  font-size: var(--fs-dense); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 220px;
}
.trail-step:hover { background: var(--surface-2); color: var(--ink); }
.trail-step[aria-current="page"] { color: var(--ink); font-weight: 650; cursor: default; }
.trail-step[aria-current="page"]:hover { background: none; }
/* Le dernier maillon est un <h1> — il nomme la page pour les lecteurs
   d'écran. Ses styles natifs sont neutralisés : l'apparence ne change pas. */
.trail-current {
  margin: 0; font-size: var(--fs-dense); line-height: inherit; display: inline-block;
  font-weight: 650; color: var(--ink);
}
.trail-sep { color: var(--muted); opacity: .6; flex: none; }
.trail .gear {
  border: 0; background: none; cursor: pointer; color: var(--muted);
  padding: var(--sp-1); flex: none;
  border-radius: var(--radius-control);
}
.trail .gear:hover { color: var(--ink); background: var(--surface-2); }
.trail .gear svg { display: block; }

/* ── Vue Carte : fond cartographique + marqueurs ──────────────────── */
.geomap {
  position: relative; width: 100%; height: min(70vh, 620px);
  border: 1px solid var(--line); border-radius: var(--radius-card);
  overflow: hidden; background: var(--surface-2);
}
.geomap { cursor: grab; user-select: none; touch-action: none; }
.geomap.dragging { cursor: grabbing; }
.geomap-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.geomap-canvas[hidden] { display: none; }
.geomap-tiles { position: absolute; inset: 0; overflow: hidden; }
.geomap-tiles[hidden] { display: none; }
.geomap-tile { position: absolute; width: 256px; height: 256px; pointer-events: none; }
.geomap-pins { position: absolute; inset: 0; pointer-events: none; }
.geomap-pins .geomap-pin { pointer-events: auto; }
.geomap-zoom {
  position: absolute; right: var(--sp-3); top: var(--sp-3);
  display: flex; flex-direction: column; gap: 1px; z-index: 4;
  border-radius: var(--radius-control); overflow: hidden;
  box-shadow: var(--shadow-card);
}
.geomap-zoom button {
  width: 30px; height: 30px; border: 0; cursor: pointer;
  background: var(--surface); color: var(--ink); font-size: 16px; line-height: 1;
}
.geomap-zoom button:hover { background: var(--surface-2); }
.geomap-pin {
  position: absolute; transform: translate(-50%, -100%);
  border: 0; background: none; cursor: pointer; padding: 0;
  display: flex; flex-direction: column; align-items: center; line-height: 1;
}
.geomap-pin .dot {
  width: 15px; height: 15px; border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg); border: 2px solid var(--surface);
  background: var(--accent); box-shadow: var(--shadow-card);
}
.geomap-pin[data-temp="AT_RISK"] .dot { background: var(--danger); }
.geomap-pin[data-temp="AHEAD"] .dot { background: var(--brand-green); }
.geomap-pin .bubble {
  margin-top: 3px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-control); padding: 1px var(--sp-2);
  font-size: 10.5px; font-weight: 650; white-space: nowrap;
  box-shadow: var(--shadow-card); font-variant-numeric: tabular-nums;
}
.geomap-pin:hover { z-index: 5; }
.geomap-pin:hover .bubble { border-color: var(--accent); color: var(--accent); }
.geomap-legend {
  position: absolute; left: var(--sp-3); bottom: var(--sp-3);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-control); padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-label); color: var(--muted);
  display: flex; gap: var(--sp-3); box-shadow: var(--shadow-card);
}
.geomap-legend span { display: inline-flex; align-items: center; gap: 5px; }
.geomap-legend i { width: 9px; height: 9px; border-radius: 50%; display: block; }
.geomap-attribution {
  position: absolute; right: var(--sp-2); bottom: var(--sp-1);
  font-size: 10px; color: var(--muted); opacity: .8;
}
/* ── Projets ────────────────────────────────────────────────────────────
   Le tableau réutilise le kanban du CRM (.kanban-col, .kanban-card…) : seul
   ce qui lui est propre est défini ici. */


.project-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--sp-3);
}
/* Vignette de projet. L'état colore le liseré gauche : sur une colonne de
   dix cartes, ce qui ne va pas se repère avant d'avoir lu un seul mot. */
.project-card { gap: var(--sp-2); border-left-width: 2px; }
.project-card[data-health="ON_TRACK"] { border-left-color: var(--brand-green); }
.project-card[data-health="AT_RISK"] { border-left-color: var(--favorite); }
.project-card[data-health="LATE"] { border-left-color: var(--warning); }
.project-card[data-health="BLOCKED"] { border-left-color: var(--danger); }
.project-card[data-health="ON_HOLD"] { border-left-color: var(--line); }
.project-card.is-archived { opacity: .55; }

.pcard-top { display: flex; align-items: baseline; gap: var(--sp-2); }
.pcard-top h3 { flex: 1; min-width: 0; }
/* Les actions du projet ne s'imposent qu'au survol : sur vingt vignettes,
   vingt boutons ⋯ permanents feraient un damier de points. */
.pcard-menu { opacity: 0; transition: opacity .12s; flex: none; }
.kanban-card:hover .pcard-menu,
.project-card:hover .pcard-menu,
.pcard-menu:focus-visible { opacity: 1; }
.health-dot {
  flex: none; width: 8px; height: 8px; border-radius: 50%;
  align-self: center;
}

/* Pour qui. « Interne » en italique discret : c'est une nature, pas un nom. */
.pcard-client {
  margin: 0; font-size: var(--fs-label); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pcard-client.is-internal { font-style: italic; opacity: .8; }

.pcard-foot {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-label); color: var(--muted); min-width: 0;
}
.pcard-foot .spacer { margin-left: auto; }
/* L'étape : l'icône porte, le mot confirme. */
.pcard-phase {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--ink); font-weight: 550; white-space: nowrap;
}
.pcard-phase svg { flex: none; color: var(--muted); }

/* Pastille d'état pour la vue liste, où il n'y a pas de liseré. */
.health-pill {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-label); white-space: nowrap;
}
.phase-cell { display: inline-flex; align-items: center; gap: 4px; }
.phase-cell svg { color: var(--muted); }
.project-progress {
  height: 5px; background: var(--line); border-radius: 3px; overflow: hidden;
}
/* `scaleX` plutôt que `width` : animer une propriété de mise en page fait
   recalculer la disposition à chaque image. */
.project-progress-fill {
  display: block; height: 100%; width: 100%; border-radius: inherit;
  background: hsl(var(--project-hue) 55% 50%);
  transform-origin: left; transform: scaleX(var(--progress, 0));
  transition: transform .25s cubic-bezier(.22, 1, .36, 1);
}
.project-meta {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-label); color: var(--muted);
}
.project-avatars { display: flex; gap: 2px; }
.project-archived-toggle {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-4); font-size: var(--fs-label); color: var(--muted);
}

/* Vignette de tâche. Le statut se lit au liseré gauche — 2 px, la largeur
   d'un signal ; au-delà, la colonne se met à clignoter de quatre couleurs. */
.task-card { border-left-width: 2px; gap: var(--sp-1); }
.task-card[data-status="TODO"] { border-left-color: var(--line); }
.task-card[data-status="IN_PROGRESS"] { border-left-color: var(--brand-green); }
.task-card[data-status="LATE"] { border-left-color: var(--warning); }
.task-card[data-status="BLOCKED"] { border-left-color: var(--danger); }
.task-card[data-status="DONE"] { border-left-color: var(--accent); }
.task-card.is-done { opacity: .66; }
.task-card.is-done h3 { color: var(--muted); text-decoration: line-through; }

/* Le statut est un bouton, mais il ne parle que lorsqu'il a quelque chose à
   dire : en retard, bloqué, en cours. Au repos il s'efface jusqu'au survol —
   une pastille de couleur sur chaque carte redit ce que le liseré porte déjà. */
.kstatus {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex: none;
}
.kstatus-btn {
  align-self: flex-start;
  border: 0; background: none; cursor: pointer; padding: 1px var(--sp-2);
  margin-left: -8px; border-radius: 4px;
  font-size: 11px; font-weight: 650; font-family: inherit; color: var(--muted);
}
.kstatus-btn:hover { background: var(--surface-2); color: var(--ink); }
.kstatus-btn.is-late { background: var(--warning-soft); color: var(--warning); }
.kstatus-btn.is-blocked { background: var(--danger-soft); color: var(--danger); }
.kstatus-btn.is-in_progress { background: var(--success-soft); color: var(--brand-green-text); }
/* Au repos : invisible jusqu'à ce qu'on approche de la carte. */
.kstatus-btn.is-quiet { opacity: 0; transition: opacity .12s; }
.kanban-card:hover .kstatus-btn.is-quiet,
.kstatus-btn.is-quiet:focus-visible { opacity: 1; }

.ktags { display: flex; flex-wrap: wrap; gap: 3px; }
.ktag {
  display: inline-block; padding: 0 6px; border-radius: 4px;
  font-size: 11px; font-weight: 600; line-height: 17px; white-space: nowrap;
}

.kmeta-ic { font-size: var(--fs-label); color: var(--muted); white-space: nowrap; }
.kmeta-ic.is-complete { color: var(--brand-green-text); }
.kmeta-ic.is-blocked { color: var(--danger); font-size: var(--fs-dense); }
.task-due {
  font-size: var(--fs-label); color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.task-due.late { color: var(--danger); font-weight: 650; }
.kdue { font-size: var(--fs-label); color: var(--muted); white-space: nowrap; }
.kdue.late { color: var(--danger); font-weight: 600; }

.kanban-add {
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-2); border-radius: var(--radius-control);
  font-size: var(--fs-label); color: var(--muted);
}
.kanban-add:hover { background: var(--surface-2); color: var(--accent); }

/* Barre de filtres : une seule rangée. Les trois familles empilées ouvraient
   une bande de 90 px entre le titre et le tableau. */
.board-bar {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-bottom: var(--sp-3); flex-wrap: wrap;
}
.board-search { flex: 0 1 320px; min-width: 200px; }
.board-bar-spacer { flex: 1 1 auto; }

/* Les colonnes descendent jusqu'au bas de la fenêtre. Sans cela un tableau de
   trois cartes flotte dans le tiers haut, et la zone de dépôt se limite à la
   hauteur des cartes présentes — on vise un ruban de 40 px. Réservé à ce
   tableau : le pipeline, lui, est toujours plein.
   `--board-top` : hauteur de l'appbar + barre de contrôle + filtres + marges. */
.board-kanban {
  /* appbar 46 + barre de contrôle 44 + filtres 28 + marges du contenu ≈ 210. */
  --board-top: 210px;
  align-items: stretch;
  min-height: calc(100vh - var(--board-top));
}
/* Sous 720 px la barre de filtres passe sur deux lignes : sans cette reprise,
   le tableau dépasse le bas de la fenêtre et impose un défilement vertical
   en plus de l'horizontal. */
@media (max-width: 720px) {
  .board-kanban { --board-top: 260px; }
}
.board-kanban .kanban-col { align-self: stretch; }
.board-kanban .kanban-col.collapsed { align-self: flex-start; }
/* La colonne prend une assise : sans fond, seules les cartes existaient et le
   tableau semblait flotter dans le vide dès qu'une pile était courte. Le fond
   dit où l'on peut déposer, avant même de commencer à glisser. */
.board-kanban .kanban-body {
  flex: 1 1 auto;
  background: var(--surface-2);
  padding: var(--sp-2);
  border-radius: var(--radius-card);
}
.board-kanban .kanban-head { background: none; border: 0; padding: 0 var(--sp-2) var(--sp-1); }
.board-kanban .kanban-col.collapsed .kanban-head {
  background: var(--surface-2); border: 1px solid var(--line); padding: var(--sp-2);
}

.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  height: 28px; padding: 0 var(--sp-3);
  border: 1px solid var(--line); border-radius: var(--radius-control);
  background: var(--surface); color: var(--muted); cursor: pointer;
  font-size: var(--fs-label); font-weight: 550; font-family: inherit;
  white-space: nowrap;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.sel { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
.chip-danger { color: var(--danger); border-color: var(--danger-soft); background: var(--danger-soft); }
.chip-danger:hover, .chip-danger.sel {
  background: var(--danger); border-color: var(--danger); color: #fff;
}
.chip-clear { padding: 0 var(--sp-2); }

/* ── Fiche d'une tâche ──────────────────────────────────────────────────
   Deux colonnes : ce qu'on lit et écrit à gauche, les propriétés à droite.
   En une seule colonne, la description se retrouvait poussée sous cinq
   sélecteurs — on ouvrait la fiche pour lire, et on tombait sur un
   formulaire. */
.task-modal {
  max-width: min(940px, 96vw); width: 100%; padding: 0;
  display: flex; flex-direction: column; max-height: 88vh;
}
/* Plein écran : la même fiche, sans les marges. Utile sur une tâche à longue
   description ou à fil fourni — le reste de l'écran n'apporte rien à ce
   moment-là. */
.task-modal.is-fullscreen {
  max-width: 100%; width: 100%; max-height: 100%; height: 100%;
  border-radius: 0;
}
.modal-backdrop:has(.task-modal.is-fullscreen) { padding: 0; }

.task-modal-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line);
  flex: none;
}
.task-modal-head .crumb {
  font-size: var(--fs-label); color: var(--muted);
  background: var(--surface-2); padding: 2px var(--sp-2); border-radius: 4px;
}
.task-modal-head .spacer { margin-left: auto; }

/* L'indicateur remplace le bouton « Enregistrer » : il ne dit rien tant qu'il
   n'y a rien à dire, et l'échec reste affiché jusqu'à correction. */
.save-hint {
  font-size: var(--fs-label); color: var(--brand-green-text);
  opacity: 0; transition: opacity .15s;
}
.save-hint.show { opacity: 1; }
.save-hint.error { color: var(--danger); font-weight: 600; }

.task-modal-body {
  display: grid; grid-template-columns: minmax(0, 1fr) 260px;
  gap: var(--sp-6); padding: var(--sp-4) var(--sp-6) var(--sp-6);
  overflow-y: auto; align-items: start;
}
.task-main { display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0; }

/* Le titre est un champ, pas un libellé : il se corrige là où on le lit. */
.task-title-input {
  border: 0; background: none; padding: 0; width: 100%;
  font-family: inherit; font-size: var(--fs-section); font-weight: 700;
  letter-spacing: -.02em; color: var(--ink);
}
.task-title-input:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px;
}
.task-desc-input {
  border: 0; background: none; padding: 0; width: 100%; resize: none;
  font-family: inherit; font-size: var(--fs-dense); line-height: 1.55;
  color: var(--muted); min-height: 2.5em; max-height: 40vh;
}
.task-desc-input:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px;
}

.task-props {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding-left: var(--sp-6); border-left: 1px solid var(--line);
}
.task-prop-label {
  font-size: var(--fs-label); color: var(--muted); margin-top: var(--sp-3);
}
.task-props > *:first-child { margin-top: 0; }
.task-prop-note { margin: var(--sp-2) 0 0; font-size: 11px; color: var(--muted); }
.task-tags { display: flex; flex-wrap: wrap; gap: 3px; margin-bottom: var(--sp-1); }
.ktag.removable { display: inline-flex; align-items: center; gap: 3px; }
.ktag-x {
  border: 0; background: none; cursor: pointer; padding: 0; line-height: 1;
  font-size: 9px; color: inherit; opacity: .65;
}
.ktag-x:hover { opacity: 1; }

.task-section { display: flex; flex-direction: column; gap: var(--sp-1); }
.task-section h3 {
  margin: var(--sp-3) 0 0; font-size: var(--fs-label); font-weight: 650;
  color: var(--muted);
}
.subtask-row { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-dense); }
.subtask-row.done span { text-decoration: line-through; color: var(--muted); }
.subtask-add {
  border: 0; border-bottom: 1px solid var(--line); background: none;
  padding: var(--sp-1) 0; font-size: var(--fs-dense); color: var(--ink);
}
.dep-row { display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-dense); }

/* La cible de dépôt ne prend de la place que tant qu'elle sert. */
.attach-zone {
  border: 1px dashed var(--line); border-radius: var(--radius-control);
  padding: var(--sp-3); text-align: center; cursor: pointer;
  font-size: var(--fs-label); color: var(--muted);
  transition: border-color .12s, background .12s;
}
.attach-zone:hover, .attach-zone.over { border-color: var(--accent); color: var(--accent); }
.attach-zone.over { background: var(--accent-soft); }

.attach-list { display: flex; flex-direction: column; gap: var(--sp-1); }
.attach-chip {
  display: flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-control);
  background: var(--surface-2); font-size: var(--fs-label);
}
/* Le nom EST le bouton d'ouverture : consulter est le geste courant, pas
   télécharger. Il occupe donc toute la largeur restante. */
.attach-open {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--sp-2);
  border: 0; background: none; cursor: pointer; text-align: left;
  font-family: inherit; font-size: inherit; color: var(--ink); padding: 0;
}
.attach-open:hover .attach-name { color: var(--accent); text-decoration: underline; }
.attach-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-size { flex: none; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Aperçu : au-dessus de la fiche, qui reste ouverte dessous. */
.preview-backdrop {
  position: fixed; inset: 0; background: rgba(22, 32, 61, .72);
  display: grid; place-items: center; z-index: 120; padding: var(--sp-4);
}
.preview-box {
  background: var(--surface); border-radius: var(--radius-card);
  box-shadow: var(--shadow-overlay);
  display: flex; flex-direction: column;
  max-width: min(1100px, 94vw); max-height: 92vh; width: 100%;
}
.preview-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--line);
  font-size: var(--fs-dense); flex: none;
}
.preview-head .spacer { margin-left: auto; }
.preview-image { max-width: 100%; max-height: 84vh; object-fit: contain; display: block; }
.preview-frame { width: 100%; height: 84vh; border: 0; }

/* Choix dans une longue liste : le champ de saisie filtre, la liste flotte
   dessous. Un <select> de trois cents clients oblige à faire défiler pour
   trouver un nom qu'on connaît déjà. */
.search-select { position: relative; }
.search-select-list {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 30;
  display: flex; flex-direction: column; max-height: 260px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  margin-top: 2px;
}
.search-select-list[hidden] { display: none; }
.search-select-item {
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-2) var(--sp-3); font-family: inherit;
  font-size: var(--fs-dense); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.search-select-item:hover { background: var(--surface-2); }
.search-select-item.sel { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.search-select-create {
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-2) var(--sp-3); font-family: inherit;
  font-size: var(--fs-dense); color: var(--accent); font-weight: 600;
  border-top: 1px solid var(--line);
}
.search-select-create:hover { background: var(--accent-soft); }
.search-select-empty {
  padding: var(--sp-2) var(--sp-3); font-size: var(--fs-label); color: var(--muted);
}

/* Suggestions d'étiquette : la liste flotte sous le champ pour ne pas
   déplacer les propriétés à chaque frappe. */
.tag-field { position: relative; }
.tag-suggest {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 20;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-control); box-shadow: var(--shadow-overlay);
  overflow: hidden; margin-top: 2px;
}
.tag-suggest[hidden] { display: none; }
.tag-suggest-item {
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-1) var(--sp-2); font-family: inherit;
  font-size: var(--fs-label); color: var(--ink);
}
.tag-suggest-item:hover { background: var(--accent-soft); color: var(--accent); }
.tag-suggest-new {
  padding: var(--sp-1) var(--sp-2); font-size: 11px; color: var(--muted);
}

.status-choice {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  border: 0; background: none; cursor: pointer; text-align: left;
  padding: var(--sp-2); border-radius: var(--radius-control); font-size: var(--fs-dense);
}
.status-choice:hover { background: var(--surface-2); }
.status-choice.sel { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

/* Sous 780 px, les propriétés passent sous le contenu : une colonne de 260 px
   ne laisserait pas de place à la description. */
@media (max-width: 780px) {
  .task-modal-body { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
  .task-props { padding-left: 0; border-left: 0; border-top: 1px solid var(--line); padding-top: var(--sp-3); }
}

.empty-state {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  padding: var(--sp-8); color: var(--muted);
}

/* ── Champs de fiche ────────────────────────────────────────────────────
   Tous les champs se ressemblent, qu'ils soient modifiables ou non : rien
   ne doit attirer l'œil plus qu'un autre. L'affordance d'édition apparaît
   au survol (fond léger + crayon), pas en permanence. */

/* Intitulé de groupe dans la grille de détail : prend la ligne entière et
   ouvre une respiration au-dessus, sauf en tête de fiche. */
.field-section { grid-column: 1 / -1; margin-top: var(--sp-4); }
.field-section:first-child { margin-top: 0; }
.field-section h3 {
  margin: 0; padding-bottom: var(--sp-1);
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}

.field-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-1) var(--sp-6);
}
.field { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: var(--sp-2) 0; }
/* Un champ de texte long prend la ligne entière : trois phrases dans une
   colonne d'un tiers se lisent sur six lignes étroites. */
.field-wide { grid-column: 1 / -1; }
/* Une grille de champs se scanne par les valeurs, pas par les étiquettes.
   Le libellé reste en capitales — c'est ce qui le distingue — mais sans
   gras : douze libellés en semi-gras faisaient concurrence aux données. */
/* Même règle partout : le libellé se lit petit et gris, la valeur normale et
   sombre. La majuscule et l'interlettrage large donnaient à chaque intitulé
   le poids d'un titre — multipliés par vingt champs, ils écrasaient la
   grille qu'ils étaient censés servir. */
.field-label {
  font-size: var(--fs-label); color: var(--muted);
  letter-spacing: .01em; font-weight: 500;
}
.field-value {
  font-size: var(--fs-body); color: var(--ink); font-weight: 400;
  line-height: 1.45; overflow-wrap: anywhere;
}

/* Champ modifiable : c'est un bouton, mais il doit ressembler au texte
   voisin tant qu'on ne le survole pas. */
button.field-trigger {
  display: flex; align-items: center; gap: var(--sp-2);
  width: 100%; text-align: left; font: inherit;
  /* Même graisse qu'une valeur figée : à information égale, un champ
     modifiable ne doit pas peser plus lourd que son voisin verrouillé. Le
     survol et le crayon disent déjà qu'il s'édite. */
  font-size: var(--fs-body); font-weight: 400; color: var(--ink);
  border: 1px solid transparent; background: none; cursor: text;
  padding: 2px var(--sp-2) 2px 0;
  margin-left: calc(var(--sp-2) * -1); padding-left: var(--sp-2);
  border-radius: var(--radius-control);
  transition: background .1s, border-color .1s;
}
button.field-trigger:hover { background: var(--surface-2); border-color: var(--line); }
button.field-trigger:focus-visible { border-color: var(--accent); }
.field-pencil {
  margin-left: auto; color: var(--muted); font-size: var(--fs-label);
  opacity: 0; transition: opacity .1s;
}
button.field-trigger:hover .field-pencil { opacity: 1; }

/* Saisie en cours : même géométrie que l'affichage, pour éviter le saut. */
.field-input {
  width: 100%; font: inherit; font-size: var(--fs-body);
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--accent); border-radius: var(--radius-control);
  padding: 2px var(--sp-2);
  margin-left: calc(var(--sp-2) * -1);
  outline: none; box-shadow: 0 0 0 3px var(--accent-soft);
}

.tabs { display: flex; gap: var(--sp-1); border-bottom: 1px solid var(--line); margin: var(--sp-6) 0 var(--sp-4); }
.tabs button {
  border: 0; background: none; cursor: pointer; padding: var(--sp-2) var(--sp-3);
  font-size: var(--fs-dense); color: var(--muted); border-bottom: 2px solid transparent;
}
.tabs button[aria-selected="true"] { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }

/* ══ Paramètres ════════════════════════════════════════════════════════ */

/* Vignette cliquable : c'est un bouton, il doit en avoir l'air. */
.card-link {
  display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-1);
  width: 100%; text-align: left; cursor: pointer; font: inherit; color: inherit;
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
.card-link:hover { border-color: var(--accent); box-shadow: var(--shadow-overlay); }
.card-link:hover .card-go { opacity: 1; transform: translateX(2px); }
.card-go {
  margin-top: auto; padding-top: var(--sp-2);
  font-size: var(--fs-label); font-weight: 650; color: var(--accent);
  opacity: .55; transition: opacity .12s, transform .12s;
}

/* Bandeau « objets liés à … », affiché après un clic sur un smart button. */
.related-banner {
  display: flex; align-items: center; gap: var(--sp-3);
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent); border-radius: var(--radius-control);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3);
  font-size: var(--fs-dense); font-weight: 600;
}
.related-banner button { margin-left: auto; color: inherit; }

.settings-group {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); overflow: hidden;
}
.setting-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-4); align-items: center;
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line);
}
.setting-row:last-child { border-bottom: 0; }
/* `display: grid` l'emporte sur l'attribut `hidden`, qui ne pose qu'un
   `display: none` de feuille par défaut. Sans cette règle, une ligne masquée
   restait visible — le dialogue d'intervenant montrait les champs internes ET
   externes en même temps. */
.setting-row[hidden] { display: none; }
.setting-label { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.setting-label > span:first-child { font-size: var(--fs-dense); font-weight: 600; }
.setting-help { font-size: var(--fs-label); color: var(--muted); }
.setting-control { display: flex; justify-content: flex-end; min-width: 0; }
.setting-value { font-size: var(--fs-dense); color: var(--muted); text-align: right; }
.setting-control select {
  border: 1px solid var(--line); border-radius: var(--radius-control);
  background: var(--surface); padding: var(--sp-1) var(--sp-2); font-size: var(--fs-dense);
  max-width: 260px;
}

/* Interrupteur */
.switch {
  width: 40px; height: 22px; border-radius: 99px; flex: none;
  border: 1px solid var(--line); background: var(--surface-2);
  cursor: pointer; padding: 2px; display: flex; align-items: center;
  transition: background .15s, border-color .15s;
}
.switch-knob {
  width: 16px; height: 16px; border-radius: 50%; display: block;
  background: var(--surface); box-shadow: var(--shadow-card);
  transition: transform .15s;
}
.switch[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); }
.switch[aria-pressed="true"] .switch-knob { transform: translateX(18px); }

/* ── Grille applications × rôles ───────────────────────────────────── */
.matrix th.matrix-role {
  text-align: center; font-size: 10.5px; letter-spacing: .02em;
  max-width: 92px; white-space: normal; line-height: 1.25;
  text-transform: none;
}
.matrix th.matrix-corner, .matrix th.matrix-toggle-head { white-space: nowrap; }
.matrix th.matrix-toggle-head { text-align: center; }
.matrix td.matrix-module { font-weight: 600; white-space: nowrap; }
.matrix td.matrix-toggle, .matrix td.matrix-cell { text-align: center; }
.matrix td.matrix-toggle .switch { margin: 0 auto; }
.matrix-check {
  width: 22px; height: 22px; border-radius: var(--radius-control);
  border: 1px solid var(--line); background: var(--surface);
  cursor: pointer; color: var(--accent); font-size: 12px; font-weight: 700;
  line-height: 1; padding: 0;
}
.matrix-check:hover { border-color: var(--accent); background: var(--surface-2); }
.matrix-check[aria-pressed="true"] {
  background: var(--accent-soft); border-color: var(--accent);
}
/* Application désactivée : les droits par rôle n'ont plus d'effet. */
.matrix tr[data-enabled="false"] td.matrix-module,
.matrix tr[data-enabled="false"] td.matrix-cell { opacity: .4; }
.matrix tr[data-enabled="false"] .matrix-check { pointer-events: none; }

/* Sélecteur de thème */
.theme-picker { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-control); overflow: hidden; }

/* Variante compacte (menu profil) : icônes seules, libellé en infobulle.
   Les sélecteurs sont qualifiés pour l'emporter sur `.user-dropdown button`,
   qui impose sinon display:block et width:100%. */
.user-dropdown .theme-picker.compact { flex: none; }
.user-dropdown .theme-picker.compact button,
.theme-picker.compact button {
  display: block; width: 30px; padding: var(--sp-1) 0;
  text-align: center; font-size: 13px; border-radius: 0;
}
.theme-picker button {
  border: 0; background: var(--surface); cursor: pointer;
  padding: var(--sp-1) var(--sp-3); font-size: var(--fs-dense); color: var(--muted);
  border-right: 1px solid var(--line);
}
.theme-picker button:last-child { border-right: 0; }
.theme-picker button:hover { background: var(--surface-2); color: var(--ink); }
.theme-picker button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); font-weight: 650; }

/* ══ Divers ════════════════════════════════════════════════════════════ */

.region-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.region-row { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-dense); }
.region-name { width: 190px; flex-shrink: 0; }
.region-bar { flex: 1; height: 18px; background: var(--surface-2); border-radius: var(--radius-control); overflow: hidden; }
.region-fill { height: 100%; background: linear-gradient(90deg, var(--brand-blue), var(--brand-blue-mid)); }
.region-value { width: 130px; text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }

.section-title { font-size: var(--fs-section); font-weight: 650; margin: var(--sp-8) 0 var(--sp-3); letter-spacing: -.01em; }
.section-title:first-child { margin-top: 0; }
.hint { font-size: var(--fs-dense); color: var(--muted); margin: 0 0 var(--sp-4); }
/* `nowrap` : « 3 résultats » se coupait en deux lignes dès que la zone gauche
   se resserrait, et poussait la barre à 90 px de haut. */
.result-count {
  font-size: var(--fs-dense); color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap; flex: none;
}

/* Le seuil vise la largeur DISPONIBLE, pas celle de la fenêtre : la barre
   latérale prélève 216 px avant que la barre de contrôle ne commence. Un
   seuil à 980 px laissait donc le centre se serrer jusqu'à 764 px utiles.
   1180 = 980 + 216, arrondi au palier supérieur. */
@media (max-width: 1180px) {
  .appbar-module { display: none; }
  .searchpanel { min-width: min(92vw, 620px); }
  /* La barre est une GRILLE, pas un flex : `flex-wrap` et `order` n'y
     faisaient rien. Sous le seuil, la colonne centrale passe à la ligne — en
     deux colonnes (gauche · droite) puis une bande pleine largeur dessous.
     À trois colonnes, le centre devenait trop étroit pour ses actions et
     poussait la barre au-delà de la fenêtre. */
  .controlbar { grid-template-columns: 1fr auto; }
  .controlbar-center { grid-column: 1 / -1; grid-row: 2; }
}

@media (max-width: 640px) {
  /* Barre haute resserrée : le nom de l'application et les sections doivent
     tenir sans repousser les compteurs hors de l'écran. */
  .appbar { gap: var(--sp-2); }
}

/* ══ Impression du devis ══════════════════════════════════════════════════
   Le document client est monté dans #print-host, hors flux à l'écran. À
   l'impression, lui seul reste visible : ni barre d'application, ni panneaux,
   ni modale. « Imprimer → Enregistrer en PDF » produit le PDF du devis. */

#print-host { display: none; }

.quote-print { color: #111; background: #fff; }
.print-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: var(--sp-6); padding-bottom: var(--sp-4);
  border-bottom: 2px solid var(--brand-blue);
}
.print-brand { font-size: 20px; font-weight: 700; color: var(--brand-blue); letter-spacing: -.01em; }
.print-address { margin: 2px 0 0; font-size: 11px; color: #555; }
.print-meta { text-align: right; }
.print-title {
  margin: 0; font-size: 13px; font-weight: 650;
  text-transform: uppercase; letter-spacing: .12em; color: #555;
}
.print-reference { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }

.print-parties {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4); margin: var(--sp-4) 0;
}
.print-fact { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.print-fact-label {
  font-size: 10px; font-weight: 650; text-transform: uppercase;
  letter-spacing: .07em; color: #666;
}
.print-fact-value { font-size: 13px; font-weight: 550; }

.print-lines { width: 100%; border-collapse: collapse; margin-top: var(--sp-3); }
.print-lines th {
  text-align: left; font-size: 10px; font-weight: 650; color: #444;
  text-transform: uppercase; letter-spacing: .06em;
  padding: 6px 8px; border-bottom: 1px solid #999;
}
.print-lines th.right, .print-lines td.right { text-align: right; }
.print-lines td { padding: 8px; border-bottom: 1px solid #ddd; font-size: 12px; vertical-align: top; }
.print-lines .num { font-variant-numeric: tabular-nums; }
.print-line-name { font-weight: 550; }
.print-line-ref { font-size: 10px; color: #666; margin-top: 1px; }

.print-footer {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-8); align-items: start; margin-top: var(--sp-4);
}
.print-terms-title {
  margin: 0 0 4px; font-size: 10px; font-weight: 650;
  text-transform: uppercase; letter-spacing: .07em; color: #666;
}
.print-terms-list { margin: 0; padding-left: 16px; font-size: 11px; line-height: 1.6; color: #333; }
.print-totals {
  margin: 0; display: grid; grid-template-columns: auto auto;
  gap: 4px var(--sp-6); align-items: baseline; min-width: 240px;
}
.print-totals dt { font-size: 12px; color: #444; }
.print-totals dd { margin: 0; font-size: 12px; text-align: right; font-variant-numeric: tabular-nums; }
.print-totals .grand { font-size: 15px; font-weight: 700; padding-top: 4px; border-top: 1px solid #999; }

.print-sign { margin-top: var(--sp-8); }
.print-sign-hint { margin: 0 0 4px; font-size: 11px; color: #444; }
.print-sign-box { height: 70px; border: 1px solid #bbb; border-radius: 3px; max-width: 280px; }


/* ── Fiche événement : document d'opération ────────────────────────────
   Un événement se lit comme une opération — capacité, participants, bilan —
   et non comme une grille d'attributs. Mêmes tokens que le reste : aucune
   couleur ni mesure en dur.                                              */

/* Le document de l'événement suit la grille de champs sans transition : le
   dernier champ (« Budget engagé ») touchait le bloc Participants, si bien
   qu'on ne savait plus si le tableau appartenait encore à la grille. Un filet
   et une vraie respiration séparent les deux natures — des champs d'un côté,
   des listes de l'autre. */
#event-doc-host {
  margin-top: var(--sp-6); padding-top: var(--sp-6);
  border-top: 1px solid var(--line);
}
.event-doc { display: flex; flex-direction: column; gap: var(--sp-6); }

.event-block {
  border: 1px solid var(--line); border-radius: var(--radius-card);
  background: var(--surface); padding: var(--sp-4);
}
.event-block-head {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.event-block-head h2, .event-block-head h3 {
  margin: 0; font-size: var(--fs-section); font-weight: 600; color: var(--ink);
}
.event-block-actions { margin-left: auto; display: flex; gap: var(--sp-2); }

/* Remplissage d'un événement : le compte suffit, la jauge graphique qui
   l'accompagnait n'a jamais été branchée. */
.event-capacity-count {
  margin-left: auto; font-size: var(--fs-dense); color: var(--muted);
}

/* Aperçu d'un message : on montre le texte tel qu'il partira, dans une
   typographie de courriel — pas dans celle de l'application. */
.event-preview { display: flex; flex-direction: column; gap: var(--sp-3); }
.event-preview-head { display: flex; flex-direction: column; gap: var(--sp-1); }
.event-preview-subject {
  margin: 0; font-size: var(--fs-body); font-weight: 650; color: var(--ink);
}
.event-preview-body {
  white-space: pre-wrap; padding: var(--sp-4);
  border: 1px solid var(--line); border-radius: var(--radius-control);
  background: var(--surface-2); color: var(--ink);
  font-size: var(--fs-dense); line-height: 1.6; max-height: 340px; overflow-y: auto;
}

.event-merge-fields { display: flex; flex-direction: column; gap: var(--sp-2); }
.event-merge-list { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.event-merge-list .chip { cursor: pointer; border: 1px solid var(--line); }
.event-merge-list .chip:hover { background: var(--accent-soft); border-color: var(--accent); }

.event-missing {
  margin: var(--sp-2) 0; padding-left: var(--sp-6);
  color: var(--danger); font-size: var(--fs-dense);
}
.event-missing li { margin: 2px 0; }

.event-hint {
  margin: var(--sp-3) 0 0; font-size: var(--fs-dense); color: var(--muted);
}
/* Avertissement d'un dialogue : le seuil n'est pas atteint, mais on peut
   passer outre — le ton alerte sans interdire. */
.event-warn { margin: var(--sp-3) 0 0; font-size: var(--fs-dense); color: var(--warning); }

.event-row-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; }
.btn-sm { padding: 2px var(--sp-2); font-size: var(--fs-label); }

/* Bilan : quatre mesures, lisibles d'un coup d'œil. */
.event-roi {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--sp-4);
}
.event-metric {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: var(--sp-3); border-radius: var(--radius-control);
  background: var(--surface-2);
}
.event-metric-label {
  font-size: var(--fs-label); color: var(--muted); text-transform: none;
}
.event-metric-value {
  margin: 0; font-size: var(--fs-page); font-weight: 600; color: var(--ink);
  line-height: 1.1;
}
.event-metric-hint { font-size: var(--fs-label); color: var(--muted); }
.event-metric-hint[data-tone="success"] { color: var(--success); font-weight: 600; }
.event-metric-hint[data-tone="danger"] { color: var(--danger); font-weight: 600; }

/* Réponses : la barre porte la comparaison, le chiffre porte la précision. */
.event-answers { display: flex; flex-direction: column; gap: var(--sp-4); }
.event-answer { display: flex; flex-direction: column; gap: var(--sp-2); }
.event-answer-bars { display: flex; flex-direction: column; gap: var(--sp-2); }
.event-answer-row {
  display: grid; grid-template-columns: minmax(80px, 160px) 1fr auto;
  align-items: center; gap: var(--sp-3);
}
.event-answer-choice { font-size: var(--fs-dense); color: var(--ink); }
.event-answer-bar {
  height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden;
}
.event-answer-bar span {
  display: block; height: 100%; background: var(--accent); border-radius: inherit;
}
.event-answer-count { font-size: var(--fs-dense); color: var(--muted); }

/* Émargement : la recherche par nom, quand le badge est resté au bureau. */
.event-search-results {
  display: flex; flex-direction: column; gap: var(--sp-1);
  margin-top: var(--sp-3); max-height: 240px; overflow-y: auto;
}
.event-search-hit {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--line);
  border-radius: var(--radius-control); background: var(--surface);
  color: var(--ink); font-size: var(--fs-body); text-align: left; cursor: pointer;
}
.event-search-hit:hover { background: var(--accent-soft); border-color: var(--accent); }
.event-search-hit .badge { margin-left: auto; }

@media (max-width: 720px) {
  .event-block-actions { width: 100%; margin-left: 0; }
  .event-answer-row { grid-template-columns: 1fr; gap: var(--sp-1); }
}

@media print {
  /* Rien d'autre que le devis : l'application entière s'efface. */
  body[data-printing] > *:not(#print-host) { display: none !important; }
  body[data-printing] #print-host { display: block; }
  body[data-printing] {
    background: #fff; color: #111;
    margin: 0; padding: 0;
  }
  @page { size: A4; margin: 16mm; }
  .print-lines thead { display: table-header-group; }
  .print-lines tr { break-inside: avoid; }
  .print-sign, .print-footer { break-inside: avoid; }
}

/* ---- Vue calendrier (module Événements) --------------------------------
   Six semaines pleines, toujours : une grille dont la hauteur change d'un
   mois à l'autre fait sauter la page à chaque navigation. */
.calendar { padding: var(--sp-3) var(--sp-4) var(--sp-6); }
.cal-head { display: flex; align-items: center; gap: var(--sp-1); margin-bottom: var(--sp-3); }
.cal-title { font-size: var(--fs-body); min-width: 160px; text-align: center; }
.cal-today-btn { margin-left: var(--sp-2); font-size: var(--fs-dense); }
/* Ce que le mois affiché ne montre pas — repère discret, poussé à droite pour
   ne pas se confondre avec la navigation. */
.cal-elsewhere {
  margin-left: auto; font-size: var(--fs-label); color: var(--muted);
}
.cal-elsewhere:hover { color: var(--ink); }
.cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: var(--radius-control); overflow: hidden;
}
.cal-weekday {
  background: var(--surface-2); padding: var(--sp-2);
  font-size: var(--fs-label); font-weight: 600; color: var(--muted);
}
.cal-cell {
  background: var(--surface); min-height: 96px; padding: var(--sp-1);
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0;      /* sans ce plancher, un nom long élargit la colonne */
}
/* Les jours des mois voisins restent lisibles mais reculent : ils donnent le
   contexte de la semaine sans concurrencer le mois affiché. */
.cal-out { background: var(--surface-2); }
.cal-out .cal-daynum { color: var(--muted); }
.cal-daynum { font-size: var(--fs-label); color: var(--muted); padding: 1px 2px; }
.cal-today .cal-daynum {
  background: var(--brand-blue); color: var(--accent-ink); border-radius: 999px;
  width: 20px; height: 20px; display: grid; place-items: center; padding: 0;
}
.cal-event {
  display: block; width: 100%; min-width: 0; text-align: left;
  font-size: var(--fs-label); line-height: 1.4;
  padding: 2px var(--sp-1); border-radius: 4px;
  border: 0; border-left: 3px solid var(--muted);
  background: var(--surface-2); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer;
}
.cal-event:hover { background: var(--accent-soft); }
.cal-planned { border-left-color: var(--brand-blue); }
.cal-confirmed, .cal-done { border-left-color: var(--success); }
.cal-running { border-left-color: var(--warning); }
.cal-cancelled { border-left-color: var(--danger); text-decoration: line-through; }
.cal-more { font-size: var(--fs-label); color: var(--muted); padding-left: var(--sp-1); }

/* ══ Devis : lignes structurantes et désignation adaptée ═════════════════ */

/* La désignation reprise du catalogue s'adapte au client. Elle occupe la
   largeur de sa cellule — un bouton étroit forçait le texte à se replier
   mot par mot — et reste en retrait du nom du produit, qui fait référence. */
.quote-desc {
  display: block; width: 100%; text-align: left;
  margin-top: 2px; padding: 2px 4px;
  font-size: var(--fs-label); color: var(--muted); line-height: 1.4;
  background: none; border: 1px solid transparent; border-radius: var(--radius-control);
  cursor: pointer;
}
.quote-desc:hover { border-color: var(--line); background: var(--surface-2); }
.quote-text-input { width: 100%; }

/* Une section est un intertitre : elle sépare et annonce. Un filet supérieur
   marque la rupture, la graisse la distingue des lignes chiffrées. */
.quote-line-section td {
  padding-top: var(--sp-4); border-top: 1px solid var(--line);
  font-weight: 600; color: var(--ink);
}
.quote-line-section:first-child td { padding-top: var(--sp-2); border-top: 0; }

/* Une note explique : elle se lit comme du texte courant, en retrait. */
.quote-line-note td {
  color: var(--muted); font-style: italic;
  padding-left: var(--sp-4);
}

/* Le texte libre d'une section ou d'une note occupe toute la ligne. */
.quote-freeline {
  display: block; width: 100%; text-align: left;
  background: none; border: 1px solid transparent; border-radius: var(--radius-control);
  padding: 2px 4px; font: inherit; color: inherit; cursor: pointer;
}
.quote-freeline:hover { border-color: var(--line); background: var(--surface-2); }
.quote-line-actions-cell { width: 1%; white-space: nowrap; }

/* Barre de titre du tableau de lignes : le réglage des colonnes se prend en
   haut à droite du tableau qu'il gouverne, comme partout ailleurs. */
.quote-lines-bar {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-4); margin-bottom: var(--sp-2);
}
.quote-lines-title {
  margin: 0; font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
.quote-lines-bar .icon-btn { margin-left: auto; }

/* La remise globale se règle au-dessus des totaux qu'elle modifie. */
.quote-discount {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  cursor: pointer; font-variant-numeric: tabular-nums;
}
.quote-discount:hover { text-decoration: underline; }

/* Sans remise : un lien discret, pas une ligne de total. Une remise à 0 %
   affichée en permanence se lit comme un geste consenti et invite à la
   remplir — alors que le devis n'en accorde aucune. */
.quote-discount-add {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-size: var(--fs-label); color: var(--muted);
}
.quote-discount-add:hover { color: var(--accent); text-decoration: underline; }
.quote-discount-add:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* La ligne d'appel ne s'aligne pas sur les nombres : ce n'en est pas un. */
dd.discount-off { text-align: left; }

.quote-discount-set { display: inline-flex; align-items: center; gap: var(--sp-2); }
.quote-discount-clear {
  background: none; border: 0; padding: 0 2px; cursor: pointer;
  color: var(--muted); font-size: var(--fs-label); line-height: 1;
}
.quote-discount-clear:hover { color: var(--danger); }
.quote-discount-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── Onglets Historique et Documents ──────────────────────────────────── */
.history-table { margin-top: var(--sp-3); }
.history-date { white-space: nowrap; color: var(--muted); font-size: var(--fs-label); }
.history-changes { margin: 0; padding-left: var(--sp-4); }
.history-field { color: var(--muted); }
.history-from { color: var(--muted); text-decoration: line-through; }
.history-arrow { color: var(--muted); }
.history-to { font-weight: 550; }

.record-documents { display: flex; flex-direction: column; gap: var(--sp-4); margin-top: var(--sp-3); }
.doc-name { font-weight: 550; }
.doc-note { font-size: var(--fs-label); color: var(--muted); margin-top: 1px; }
.doc-upload { display: flex; flex-direction: column; gap: var(--sp-2); align-items: flex-start; }

/* ══ Devis : colonnes, contacts, suivi d'envoi ═══════════════════════════ */

/* Réglage des colonnes en deux axes : ce que je vois, ce que le client voit. */
.column-head, .column-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 64px 64px;
  align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--line);
}
.column-head {
  font-size: var(--fs-label); font-weight: 550; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em;
}
.column-head span:not(:first-child), .column-toggle { justify-self: center; }
.column-name { font-size: var(--fs-dense); display: flex; align-items: center; gap: var(--sp-2); }
.column-tag {
  font-size: var(--fs-label); color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px; padding: 0 6px;
}
.column-toggle { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.column-toggle input:disabled { cursor: not-allowed; }

/* Bascule « visible sur le devis client », dans l'entête de colonne. */
.col-client-toggle {
  margin-left: 4px; padding: 0 2px; border: 0; background: none;
  cursor: pointer; font-size: 11px; line-height: 1; opacity: .45;
}
.col-client-toggle:hover { opacity: 1; }
.col-client-toggle[aria-pressed="true"] { opacity: .9; }

/* Signataire : la coche marque celui qui engage le client. */
.contact-card { position: relative; }
/* L'étoile désigne le contact principal — celui qui signe et à qui l'on
   s'adresse. Pleine pour lui, en contour pour les autres : le rôle se lit
   d'un coup d'œil sur la liste, sans avoir à comparer des coches. Elle prend
   la marge automatique : c'est elle qui pousse le groupe à droite, la croix
   la suivant immédiatement. */
.contact-star {
  flex: none; margin-left: auto; padding: 0 2px;
  background: none; border: 0; cursor: pointer;
  font-size: var(--fs-dense); line-height: 1;
  color: var(--muted); opacity: .45;
  transition: color .12s, opacity .12s, transform .12s;
}
.contact-star:hover:not(:disabled) { opacity: 1; color: var(--warning); transform: scale(1.15); }
.contact-star[aria-pressed="true"] { color: var(--warning); opacity: 1; }
/* Le principal ne se déchoit pas d'un clic : on en désigne un autre. */
.contact-star[aria-pressed="true"] { cursor: default; }
.contact-star:disabled { cursor: default; opacity: .35; }
.contact-star:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: 2px; }
.contact-card[data-signatory="true"] .contact-role { color: var(--accent); font-weight: 550; }
/* Le retrait n'apparaît qu'au survol : il ne doit pas inviter au clic. */
.contact-remove { opacity: 0; }
/* Hors devis il n'y a pas d'étoile : la croix reprend la marge, sans quoi
   elle se collerait au nom. */
.contact-remove:first-of-type { margin-left: auto; }
.contact-card:hover .contact-remove, .contact-remove:focus-visible { opacity: 1; }

/* Suivi de l'envoi : les faits d'abord, l'alerte ensuite. */
.tracking-facts dd[data-state="ok"] { color: var(--brand-green-text); font-weight: 550; }
.tracking-facts dd[data-state="wait"] { color: var(--muted); }
.tracking-hint {
  margin: var(--sp-2) 0 0; font-size: var(--fs-label);
  color: var(--danger); line-height: 1.4;
}

/* Sections et notes sur le document client : elles se lisent comme du texte. */
.print-line-section td { padding-top: 12px; font-weight: 700; border-bottom: 1px solid #999; }
.print-line-note td { color: #444; font-style: italic; }

/* ══ Matériel événementiel (Ressources) ═══════════════════════════════════
   Stands, roll-ups, mallettes de démonstration : ce qu'on emmène sur un
   salon. À ne pas confondre avec le parc installé, qui est le matériel vendu
   et posé chez un client. */

/* L'alerte de chevauchement ouvre l'écran : c'est la seule chose qui appelle
   une décision. Un bandeau discret, pas une modale — on informe, on
   n'interrompt pas. */
.resource-alert {
  display: flex; align-items: center; gap: var(--sp-3);
  margin-bottom: var(--sp-4); padding: var(--sp-3) var(--sp-4);
  background: var(--danger-soft); border-radius: var(--radius-control);
  color: var(--danger);
}
.resource-alert p { margin: 0; flex: 1; font-size: var(--fs-dense); }
.resource-alert-icon { font-size: var(--fs-section); line-height: 1; }

.resource-name { display: flex; align-items: center; gap: var(--sp-2); }

/* Faits d'un article, dans le panneau de détail : deux colonnes serrées, le
   libellé au plus court. */
.facts-inline {
  margin: 0 0 var(--sp-4); display: grid;
  grid-template-columns: max-content 1fr; gap: var(--sp-1) var(--sp-4);
}
.facts-inline dt { font-size: var(--fs-dense); color: var(--muted); }
.facts-inline dd { margin: 0; font-size: var(--fs-dense); }

.modal-section {
  margin: var(--sp-4) 0 var(--sp-2);
  font-size: var(--fs-dense); font-weight: 650;
  text-transform: uppercase; letter-spacing: .04em; color: var(--muted);
}

/* Réservations : le nom mène à l'événement, la date reste à droite. */
.booking-list { list-style: none; margin: 0; padding: 0; }
.booking {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--line);
}
.booking:last-child { border-bottom: 0; }
/* Les lignes en conflit sont teintées ET marquées d'un liseré : la couleur
   seule ne se voit pas d'un daltonien. */
.booking.is-conflict {
  background: var(--danger-soft);
  box-shadow: inset 3px 0 0 var(--danger);
  border-radius: var(--radius-control);
}
.booking-dates {
  flex: none; font-size: var(--fs-dense); color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.resource-conflict-note {
  margin: var(--sp-3) 0 0; font-size: var(--fs-dense); color: var(--muted);
}

/* Un lien qui est en réalité un bouton : il navigue dans l'application, il
   n'a donc pas d'URL propre, mais il doit se lire comme un lien. */
.link-button {
  border: 0; background: none; padding: 0;
  color: var(--accent); font: inherit; text-align: left;
  cursor: pointer;
}
.link-button:hover { text-decoration: underline; }
.link-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Le jeu de démonstration est affiché : la barre haute le dit en permanence.
   Ambre plutôt que rouge — ce n'est pas une erreur, c'est un état qu'on a
   choisi et qu'on doit pouvoir quitter d'un clic. */
.appbar-demo {
  border: 1px solid rgba(255, 255, 255, .28);
  background: rgba(255, 255, 255, .12);
  color: var(--ink-on);
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  padding: 4px 10px; border-radius: 999px;
  cursor: pointer;
}
.appbar-demo:hover { background: rgba(255, 255, 255, .2); }
.appbar-demo:focus-visible { outline: 2px solid var(--ink-on); outline-offset: 2px; }

/* Colonne « Source » de la liste des participants : l'origine sur une ligne,
   la date en dessous — celle-ci se consulte, elle ne se lit pas. */
.participant-source { display: flex; flex-direction: column; gap: 2px; }
.participant-source-date { color: var(--muted); font-size: 12px; }

/* ══ Configuration des événements : défauts par type ══════════════════════
   Un tableau, une ligne par type. La grille rend la comparaison immédiate —
   c'est en voyant « webinaire : 200 places » à côté de « formation : 12 »
   qu'on comprend ce qu'on règle. */
.settings-lead {
  margin: 0 0 var(--sp-4); max-width: 72ch;
  color: var(--muted); font-size: var(--fs-dense);
}
.settings-note { margin-top: var(--sp-3); max-width: 72ch; }

/* Le tableau déborde sur un écran étroit : il défile SEUL, sans emporter
   la page entière. */
.table-wrap { overflow-x: auto; }
.defaults-table { min-width: 860px; }
.defaults-type { font-weight: 600; }

/* ══ Intervenants et sessions : la ligne s'ouvre pour être corrigée ═══════
   Ajoutés le 13/08/2026 : une session ne pouvait ni se modifier ni se
   supprimer une fois créée, il fallait la refaire. La ligne entière est
   désormais un bouton — au clavier comme au lecteur d'écran, on doit pouvoir
   l'atteindre et savoir qu'elle agit. Le ✕ reste à côté, distinct. */
.speaker-open, .session-open {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: var(--sp-3);
  border: 0; background: none; padding: 0;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer;
}
.speaker-open:disabled, .session-open:disabled { cursor: default; }
.speaker-open:focus-visible, .session-open:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: var(--radius-control);
}

/* ══ Compléments du 13/08/2026 ════════════════════════════════════════════ */

/* Une section annoncée mais pas livrée : visible, inerte, et qui le dit. La
   retirer masquerait ce qui est prévu ; la laisser cliquable faisait croire
   à un clic sans effet. */
.nav-soon {
  border: 0; background: none; padding: var(--sp-1) var(--sp-2);
  font: inherit; color: var(--muted); opacity: .55;
  cursor: not-allowed;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

/* Le nom d'une personne ou d'une société ouvre sa fiche. Il se lit comme le
   reste du panneau — pas comme un bouton — mais se souligne au survol pour
   annoncer qu'il mène quelque part. */
.contact-card-link, .widget-title-link {
  border: 0; background: none; padding: 0;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer; width: 100%;
}
.contact-card-link:hover .contact-name,
.widget-title-link:hover { text-decoration: underline; }
.contact-card-link:focus-visible, .widget-title-link:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: var(--radius-control);
}
.widget-title-link { color: var(--accent); font-weight: 650; }

/* Un article déjà retenu par un autre événement : on le signale dans le
   sélecteur, on ne l'interdit pas. */
.asset-conflict {
  display: block; margin-top: 2px;
  font-size: var(--fs-label); color: var(--danger);
}

/* Checklist type de la Configuration : les actions groupées par étape. */
.settings-checklist { margin-top: var(--sp-8); }
.checklist-group { margin-top: var(--sp-4); }
.checklist-group-title {
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-label); font-weight: 650;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.checklist-template { list-style: none; margin: 0; padding: 0; max-width: 72ch; }
.checklist-template-item {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2); border-radius: var(--radius-control);
}
.checklist-template-item:hover { background: var(--surface-2); }
.checklist-template-open {
  flex: 1; min-width: 0;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  border: 0; background: none; padding: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.checklist-template-open:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: var(--radius-control);
}

/* ── Onglet « Agent IA » ────────────────────────────────────────────────
   Le bandeau d'introduction reprend la surface réservée à l'IA dans la
   charte : ce qui est produit par une machine se distingue au premier
   coup d'œil de ce qu'une personne a écrit. */
.agent-intro {
  margin: 0 0 var(--sp-4);
  padding: var(--sp-3);
  background: var(--ai-surface);
  border: 1px solid var(--ai-border, var(--brand-teal));
  border-radius: var(--radius-card);
  font-size: var(--fs-body);
  line-height: 1.5;
  max-width: 78ch;
}

/* Champs dynamiques du message d'invitation : cliquer insère le jeton au
   curseur. Taper « {{specialite}} » sans faute est plus difficile qu'il n'y
   paraît, et une variable mal orthographiée s'affiche telle quelle dans le
   message que le médecin reçoit. */
.merge-help {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2);
  margin: var(--sp-2) 0 var(--sp-3);
  max-width: 78ch;
}
.merge-help-label {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-weight: 650;
}
.merge-chip {
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--brand-blue);
  border-radius: 999px;
  padding: 2px 10px;
  font: inherit; font-size: var(--fs-label);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  cursor: pointer;
}
.merge-chip:hover { background: var(--ai-surface); border-color: var(--brand-teal); }
.merge-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Types d'événement — écran de configuration.
   Un type archivé reste visible, en retrait : c'est le seul endroit d'où on
   peut le remettre en service, le masquer rendrait l'archivage définitif. */
.type-rows { list-style: none; margin: 0 0 var(--sp-5); padding: 0; max-width: 72ch; }
.type-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-3);
  border-radius: var(--radius-control);
}
.type-row:hover { background: var(--surface-2); }
.type-row-archived { opacity: .6; }
.type-open {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: var(--sp-2);
  border: 0; background: none; padding: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.type-open:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
  border-radius: var(--radius-control);
}
.type-name { font-weight: 600; }
.type-badge {
  font-size: var(--fs-label); font-weight: 650;
  text-transform: uppercase; letter-spacing: .06em;
  padding: 1px 7px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
  border: 1px solid var(--line);
}
.type-badge-off { background: var(--danger-soft, #FDECEA); }
.type-usage { font-size: var(--fs-label); color: var(--muted); }
.type-usage-list { margin: var(--sp-2) 0; padding-left: var(--sp-4); color: var(--muted); }

/* Widget Intervenants — une liste, pas une valeur unique. Même patron que le
   widget Matériel : `removable()` par ligne plutôt qu'autour du bloc. */
.speaker-widget-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.speaker-widget-item { min-width: 0; }

/* ── Assistant de campagne ──────────────────────────────────────────────
   Coque reprise du configurateur de devis : en-tête fixe, corps défilant,
   pied fixe. S'y ajoute le fil d'avancement, qui manquait — un assistant
   sans repère de progression est une suite de formulaires. */
.wizard-modal {
  max-width: 840px; width: min(840px, 96vw);
  /* `dvh` suit la barre d'adresse mobile : en `vh` seul, le pied de la boîte
     — donc « Suivant » — passait sous le chrome du navigateur. */
  max-height: min(90vh, 90dvh);
  display: flex; flex-direction: column; padding: 0;
}

/* La coque pose TOUTES les marges, et les règles génériques de `.modal` ne
   doivent plus s'appliquer au travers. C'est ce qui produisait le rendu
   collé aux bords : `.modal { padding: var(--sp-6) }` était bien annulé,
   mais `.modal h2` et `.modal p` continuaient d'imposer leurs propres marges
   à l'intérieur des trois zones — ajoutées au `gap` du corps par endroits,
   absentes ailleurs. */
.wizard-modal h2, .wizard-modal p { margin: 0; }

.wizard-head {
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  border-bottom: 1px solid var(--line);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.wizard-head h2 {
  font-size: var(--fs-section); text-wrap: balance;
}
.wizard-head > p { color: var(--muted); font-size: var(--fs-body); }
.wizard-body {
  flex: 1; overflow-y: auto;
  /* Le défilement ne se propage pas à la page derrière la boîte. */
  overscroll-behavior: contain;
  padding: var(--sp-5);
  display: flex; flex-direction: column; gap: var(--sp-5);
}
.wizard-foot {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--line); background: var(--surface-2);
}
/* Le motif d'empêchement peut être long : il rétrécit plutôt que d'écraser
   les boutons, qui gardent leur largeur. */
.wizard-foot > .wizard-blocker { flex: 1 1 12rem; min-width: 0; }
.wizard-foot > .modal-actions { margin-left: auto; }

/* Le fil d'avancement */
/* Le fil d'avancement occupe TOUTE la largeur, chaque étape recevant une part
   égale. En flex à contenu libre, les sept étapes se tassaient à gauche et
   laissaient un vide à droite : le fil ne se lisait plus comme une barre de
   progression mais comme une phrase interrompue. Des colonnes égales donnent
   au repère sa forme de règle graduée, où la position dit l'avancement. */
.wizard-steps {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  list-style: none; margin: 0; padding: 0;
}
/* Pastille au-dessus, libellé dessous : à sept colonnes égales, un libellé
   posé à côté de son numéro obligerait à tronquer « Vérification ». Empilé,
   il dispose de toute la part de l'étape. */
.wizard-step {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  padding-top: 2px;
  font-size: var(--fs-label); color: var(--muted); text-align: center;
}
/* Le trait de liaison relie les pastilles, à leur hauteur. Il part du centre
   de la case précédente et s'arrête à celui de la sienne — sans dépasser aux
   extrémités du fil, où il ne relierait rien. */
.wizard-step + .wizard-step::before {
  content: ''; position: absolute;
  top: calc(2px + 11px);            /* mi-hauteur de la pastille de 22 px */
  right: 50%; left: -50%;
  height: 2px; background: var(--line);
  z-index: 0;
}
/* Une étape franchie colore le trait qui y mène : la progression se lit sur
   la ligne autant que sur les pastilles. */
.wizard-step-done::before,
.wizard-step-current::before { background: var(--accent) !important; }
.wizard-step-num {
  position: relative; z-index: 1;
  display: grid; place-items: center;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--surface); border: 1.5px solid var(--line);
  font-size: 11px; font-weight: 700; color: var(--muted);
}
.wizard-step-label {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  max-width: 100%;
}
.wizard-step-current { color: var(--accent); font-weight: 650; }
.wizard-step-current .wizard-step-num {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
  /* Un halo discret désigne l'étape courante sans l'agrandir : à cette
     échelle, changer la taille des pastilles ferait sautiller le fil. */
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/* Une étape franchie : coche pleine dans la teinte d'action. Le turquoise de
   marque en faisait une troisième couleur sur le fil, là où il n'y a que deux
   états — fait, et pas encore fait. */
.wizard-step-done { color: var(--ink); }
.wizard-step-done .wizard-step-num {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

/* Sous 720 px, sept libellés ne tiennent plus : restent les pastilles et le
   trait, qui suffisent à situer l'avancement. L'étape courante garde son nom —
   c'est la seule qu'on ait besoin de lire. */
@media (max-width: 720px) {
  .wizard-step-label { display: none; }
  .wizard-step-current .wizard-step-label {
    display: block; position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%); margin-top: var(--sp-1);
    white-space: nowrap; overflow: visible;
  }
  .wizard-steps { margin-bottom: var(--sp-4); }
}

.wizard-lead { margin: 0; color: var(--muted); max-width: 72ch; line-height: 1.5; }
.wizard-hint { margin: 0; font-size: var(--fs-label); color: var(--muted); max-width: 72ch; }
.wizard-subtitle {
  margin: var(--sp-3) 0 var(--sp-2); font-size: var(--fs-label);
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.wizard-warning {
  margin: 0; padding: var(--sp-3); border-radius: var(--radius-control);
  background: var(--danger-soft, #FDECEA); font-size: var(--fs-body); max-width: 72ch;
}
.wizard-blocker { margin: 0; font-size: var(--fs-label); color: var(--danger, #B3261E); }
.wizard-preview {
  margin: 0; padding: var(--sp-3); border-radius: var(--radius-control);
  background: var(--surface-2); border: 1px solid var(--line);
  white-space: pre-wrap; font-family: inherit; font-size: var(--fs-body);
  max-width: 72ch;
}
.wizard-done {
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: var(--sp-4); border-radius: var(--radius-card);
  background: var(--ai-surface); border: 1px solid var(--brand-teal);
}

/* Vérification de l'événement */
.check-list { list-style: none; margin: 0; padding: 0; max-width: 72ch; }
.check-row {
  display: grid; grid-template-columns: 20px minmax(0, 180px) minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-2); padding: var(--sp-2) 0; border-bottom: 1px solid var(--line);
}
/* Sous 520 px, la colonne d'étiquettes de 180 px ne laisse plus de place à la
   valeur — encore moins à un champ de réparation. Tout passe en pile. */
@media (max-width: 520px) {
  .check-row { grid-template-columns: 20px minmax(0, 1fr); }
  .check-value, .check-fix { grid-column: 2; }
}

/* ── Réparer un manque sans quitter l'assistant ─────────────────────────
   La réparation prend la place de la valeur absente, sur la MÊME ligne :
   l'œil n'a pas à relier un « ! » en haut et un champ en bas. Fermer
   l'assistant pour aller cocher une case ailleurs, puis tout reprendre,
   n'était pas un parcours. */
.check-fix { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.check-fix-field { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
/* Le champ prend ce qu'il lui faut, pas toute la ligne : un nombre de places
   tient en trois caractères, et un champ pleine largeur repoussait son propre
   bouton d'enregistrement à la ligne suivante. */
.check-fix-field input { flex: 0 1 16rem; min-width: 0; }
.check-fix-field input[type="number"] { flex: 0 0 7rem; }
.check-fix-field button { flex: none; }
/* Le cas non réparable sur place — la date, qui passe par une
   reprogrammation. On dit pourquoi avant de renvoyer. */
.check-fix-note {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2);
  font-size: var(--fs-label); color: var(--muted);
}
.check-mark { color: var(--brand-teal); font-weight: 700; }
.check-row-ko .check-mark { color: var(--danger, #B3261E); }
.check-label { color: var(--muted); font-size: var(--fs-label); }
.check-value { font-weight: 600; }
.check-row-ko .check-value { color: var(--danger, #B3261E); font-weight: 500; }

/* Dimensionnement — champs partagés */
.wizard-field { display: flex; flex-direction: column; gap: var(--sp-1); }
.wizard-field > span:first-child { font-size: var(--fs-label); color: var(--muted); }
.wizard-input-wrap { display: flex; align-items: center; gap: var(--sp-2); }
.wizard-suffix { color: var(--muted); font-size: var(--fs-label); }
.wizard-inline-field { display: flex; align-items: center; gap: var(--sp-2); }
.wizard-inline-field span { font-size: var(--fs-label); color: var(--muted); }
.wizard-inline-field input { width: 84px; }

/* ── Étape 2 : l'entonnoir de conversion ────────────────────────────────
   Il se lit dans le SENS DE LECTURE : l'objectif en haut, le travail à faire
   en bas. Entre deux paliers, le taux qui explique le passage de l'un à
   l'autre.

   La version précédente montrait les invitations EN PREMIER, avec la plus
   large barre : on lisait le résultat avant la question, et l'on ne
   distinguait plus ce qu'on règle de ce qui se calcule. Deux natures, deux
   traitements — les champs sur fond clair, les paliers en chiffres nus.

   Le rapport des largeurs est le sujet : cent-dix-sept invitations pour dix
   places, ce sont deux barres d'un rapport de douze, et cela se lit d'un
   coup d'œil là où quatre champs et un total demandaient un calcul mental. */

/* Ce qu'on SAISIT : l'objectif, seule donnée qui ne se déduit de rien. */
.funnel-goal {
  padding: var(--sp-4); border-radius: var(--radius-card);
  background: var(--surface-2); border: 1px solid var(--line);
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.funnel-goal-fields { display: flex; align-items: flex-end; gap: var(--sp-3); flex-wrap: wrap; }
.funnel-goal-field { display: flex; flex-direction: column; gap: var(--sp-1); }
.funnel-goal-label { font-size: var(--fs-label); color: var(--muted); }
.funnel-goal-field input {
  width: 108px; font-variant-numeric: tabular-nums;
  font-size: var(--fs-section); font-weight: 650; text-align: center;
  /* Même raison qu'au-dessus : le décalage de `.field-input` n'a de sens que
     dans une fiche, où le champ prend la place d'une valeur affichée. */
  margin-left: 0;
}
/* Le « + » entre les deux champs dit que la marge s'AJOUTE à l'objectif :
   sans lui, on lit deux réglages indépendants. */
.funnel-goal-plus {
  align-self: center; padding-bottom: var(--sp-2);
  font-size: var(--fs-section); color: var(--muted); font-weight: 400;
}
.funnel-goal-hint { font-size: var(--fs-label); color: var(--muted); }

/* Ce que l'historique conseille pour la marge. Un conseil, pas une valeur
   imposée : il s'affiche à côté du champ, et c'est un clic qui l'applique.
   L'écrire d'office rétablirait le réglage en double qu'on vient de retirer —
   les absences compensées une fois par la marge, une fois par un taux. */
.funnel-advice {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2);
  margin-top: var(--sp-2); padding-top: var(--sp-2);
  border-top: 1px dashed var(--line);
  font-size: var(--fs-label); color: var(--muted);
}

/* Ce qui se CALCULE : du haut vers le bas, dans le sens de la lecture. */
.funnel { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.funnel-tier { display: flex; flex-direction: column; gap: var(--sp-2); }
.funnel-tier-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-3);
}
.funnel-tier-label { font-size: var(--fs-dense); color: var(--muted); }
.funnel-tier-value {
  font-size: 26px; line-height: 1; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
/* Les deux bouts de la chaîne se distinguent : ce qu'on a fixé, et ce qu'on
   va déclencher. Les paliers intermédiaires restent en retrait — ce sont des
   étapes de calcul, pas des décisions. */
.funnel-tier-start .funnel-tier-label,
.funnel-tier-end .funnel-tier-label { color: var(--ink); font-weight: 600; }
.funnel-tier-end .funnel-tier-value { font-size: 32px; color: var(--accent); }

.funnel-tier-bar {
  height: 10px; border-radius: 99px;
  background: var(--surface-2); overflow: hidden;
}
/* La barre occupe toute la piste et se met à l'échelle horizontalement :
   `transform` n'entraîne aucun recalcul de mise en page, là où animer
   `width` en provoquait un à chaque frappe dans les champs de taux. */
.funnel-tier-fill {
  display: block; height: 100%; width: 100%; border-radius: inherit;
  transform-origin: left center;
  transition: transform .2s cubic-bezier(.2, .7, .3, 1);
}
/* Un seul parcours, une seule teinte, du clair au foncé : la progression est
   l'information. Trois couleurs distinctes auraient suggéré trois natures. */
.funnel-tier-fill-places { background: #86b6ef; }
.funnel-tier-fill-inscrits { background: #4a8ce0; }
.funnel-tier-fill-invitations { background: var(--accent); }
.funnel-tier-hint { font-size: var(--fs-label); color: var(--muted); }

/* Le taux entre deux paliers : en retrait, avec la flèche qui dit le sens de
   la déperdition. C'est une marche, pas un palier de plus. */
.funnel-gap {
  display: grid;
  /* `auto` et non `minmax(0, auto)` sur la colonne du réglage : avec un
     minimum de zéro, elle se laissait comprimer et « Taux de présence » se
     tronquait en « Taux de présen… ». C'est l'explication, à droite, qui doit
     céder de la place — pas le libellé du champ qu'on règle. */
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center; gap: var(--sp-2) var(--sp-3);
  padding: var(--sp-3) 0 var(--sp-3) var(--sp-2);
  margin-left: var(--sp-2);
  border-left: 1px solid var(--line);
}
.funnel-gap-arrow { color: var(--muted); font-size: 15px; line-height: 1; }
.funnel-gap-field { display: flex; align-items: center; gap: var(--sp-2); }
/* `flex: none` sur les deux enfants : `.field-input` porte une largeur
   minimale globale qui, en flex, l'emportait sur sa largeur déclarée et le
   champ venait recouvrir la fin du libellé — « Taux de présence » se lisait
   « Taux de présen[75] ». Aucun des deux ne doit céder : c'est la colonne
   d'explication, à droite, qui absorbe le manque de place. */
.funnel-gap-label {
  flex: none; font-size: var(--fs-dense); color: var(--ink); white-space: nowrap;
}
.funnel-gap-control { flex: none; display: inline-flex; align-items: center; gap: 4px; }
.funnel-gap-input {
  width: 68px; min-width: 68px; font-variant-numeric: tabular-nums;
  text-align: center; font-weight: 600;
  /* `.field-input` porte `margin-left: -8px`, qui aligne le champ d'édition
     sur la valeur qu'il remplace DANS UNE FICHE. Ici le champ suit un
     libellé : ce décalage le faisait chevaucher la fin du texte, et
     « Taux de présence » se lisait « Taux de présen ». */
  margin-left: 0;
}
.funnel-gap-unit { font-size: var(--fs-label); color: var(--muted); }
.funnel-gap-hint { font-size: var(--fs-label); color: var(--muted); min-width: 0; }

/* D'où viennent les taux. La pastille dit en un mot s'ils sont mesurés sur
   l'historique ou empruntés au marché — c'est ce qui décide si l'on peut s'y
   fier ou s'il faut les corriger. */
/* `align-items: center` et non `baseline` : la pastille est une capsule, sa
   ligne de base ne coïncide pas avec celle du texte et elle se décalait vers
   le bas. `flex-wrap` retiré — la phrase se replie seule, la pastille doit
   rester collée à son début. */
.funnel-source { display: flex; align-items: center; gap: var(--sp-2); }
.funnel-source-badge {
  flex: none; padding: 1px var(--sp-2); border-radius: 99px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--line);
}
.funnel-source-badge.is-measured {
  background: var(--success-soft); color: var(--success); border-color: transparent;
}

@media (prefers-reduced-motion: reduce) {
  .funnel-tier-fill { transition: none; }
}

/* Sous 560 px, le taux passe sur deux lignes : à cette largeur, libellé,
   champ et explication ne tiennent plus côte à côte. */
@media (max-width: 560px) {
  .funnel-gap { grid-template-columns: auto minmax(0, 1fr); }
  .funnel-gap-hint { grid-column: 2; }
}

/* Ciblage */
.wizard-channel {
  display: flex; align-items: center; gap: var(--sp-3);
  padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line);
}
.wizard-channel > span:first-child {
  font-size: var(--fs-label); text-transform: uppercase;
  letter-spacing: .06em; color: var(--muted); font-weight: 650;
}
/* L'étiquette ENVELOPPE sa case : la zone cliquable est d'un seul tenant, il
   n'y a pas d'espace mort entre le texte et le contrôle. Le contour de focus
   se pose sur l'étiquette via `:focus-within`, sinon la case cochée au clavier
   ne se distingue pas — surtout sur le fond teinté d'un critère actif. */
.wizard-radio {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: 2px 6px; border-radius: var(--radius-control);
  cursor: pointer;
}
/* Les cases ne collent pas à leur texte : `checklist-box` ne porte pas de
   marge propre, et sans elle la coche touchait le premier caractère. */
.wizard-radio input { margin: 0; flex: none; }
.wizard-radio:hover { background: var(--surface-2); }
.wizard-radio:focus-within {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
/* ── Étape 3 : le ciblage ───────────────────────────────────────────────
   Les critères sont groupés par SUJET — le médecin, son établissement, son
   équipement. Une liste plate de huit cases obligeait à lire chaque libellé
   pour savoir de quoi il parlait : « Âge du médecin » et « Âge de l'appareil »
   se suivaient sans qu'on distingue lequel portait sur la personne.

   Chaque critère tient sur UNE ligne : sa case à gauche, sa saisie à droite.
   La version précédente dépliait un bloc sous le libellé, si bien que cocher
   un critère repoussait les suivants et qu'on perdait de vue ce qu'on venait
   de faire. La colonne de droite était vide, et c'est elle qu'on occupe. */
/* Le compte est collé au bas du corps défilant : sans cette réserve, il
   recouvrait les derniers critères, qu'on ne pouvait plus ni lire ni cocher.
   La hauteur du bandeau, plus l'air qu'il lui faut. */
.criteres { display: grid; gap: var(--sp-4); padding-bottom: var(--sp-6); }

.critere-famille {
  border: 1px solid var(--line); border-radius: var(--radius-card);
  overflow: hidden;
}
.critere-famille-head {
  display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.critere-famille-titre {
  margin: 0; font-size: var(--fs-label); font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--ink);
}
.critere-famille-aide { font-size: var(--fs-label); color: var(--muted); }

.critere-lignes { display: flex; flex-direction: column; }
/* Libellé à gauche sur une colonne fixe, saisie à droite sur le reste : les
   contrôles s'alignent d'une ligne à l'autre, et l'œil descend une colonne
   plutôt que de chercher où commence chaque réglage. */
/* `align-items: start` et non `center` : dès qu'un critère déplie ses puces
   ou son réglage d'information manquante, sa colonne de droite grandit, et un
   alignement centré faisait descendre la case à cocher au milieu du bloc —
   loin du libellé qu'elle commande. */
/* Trois colonnes : ce qu'on filtre, avec quoi, et ce que ça coûte.
   La case à cocher a disparu (16/08/2026) — cocher pour révéler un champ,
   puis régler ce champ, faisait deux gestes là où un suffit, et la hauteur
   de l'écran changeait à chaque coche. Le contrôle est là d'emblée, sur
   « Tous » : cet état ne filtre rien. */
.critere {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr) minmax(0, 132px);
  align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--line);
  min-height: 44px;
}
.critere:first-child { border-top: 0; }

/* Ce que le critère coûte : combien de médecins n'ont pas l'information.
   Discret tant qu'on ne filtre pas — c'est une éventualité ; net dès qu'on
   filtre — c'est devenu une perte réelle. */
.critere-cost {
  font-size: var(--fs-label); color: var(--muted);
  text-align: right; font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.critere-cost.is-applied { color: var(--danger, #B3261E); font-weight: 600; }

/* Sous 640 px, la ligne se plie : le coût passe sous le contrôle plutôt que
   de lui disputer une largeur qui n'existe plus. */
@media (max-width: 640px) {
  .critere { grid-template-columns: minmax(0, 1fr); gap: var(--sp-1); }
  .critere-cost { text-align: left; }
}
.critere-actif { background: var(--accent-soft); }
.critere-actif .critere-label { font-weight: 600; }
.critere-controls {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3);
  min-width: 0;
}

.critere-select {
  width: auto; min-width: 180px; max-width: 100%;
  margin-left: 0;   /* `.field-input` décale de -8px : sans objet ici */
  font-size: var(--fs-dense);
}

/* Sélection multiple : un bouton qui résume, un panneau qui déplie.

   Les puces retirables qui précédaient ajoutaient une ligne au-dessus du menu
   à chaque valeur cochée : le critère grandissait, ceux du dessous
   descendaient, et l'on perdait de vue ce qu'on venait de faire. Le bouton
   garde une hauteur FIXE quoi qu'on coche — c'est tout l'objet du changement.

   Le panneau vient de `openMenuUnder` : Échap, clic extérieur et
   repositionnement au défilement sont à lui, on n'en réécrit rien. */
.critere-multi { display: flex; align-items: center; min-width: 0; }
.critere-multibtn {
  display: inline-flex; align-items: center; justify-content: space-between;
  gap: var(--sp-2); width: auto; min-width: 180px; max-width: 100%;
  margin-left: 0;   /* `.field-input` décale de -8px : sans objet ici */
  font-size: var(--fs-dense); text-align: left; cursor: pointer;
}
.critere-multibtn-label {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Une valeur retenue se voit sans lire : le libellé passe en gras, comme
   pour les autres critères actifs. */
.critere-multibtn.is-set .critere-multibtn-label { font-weight: 600; }
.critere-multibtn-caret { flex: none; color: var(--muted); font-size: 10px; }
.critere-multibtn:hover { border-color: var(--accent); }
.critere-multibtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Une case du panneau : la ligne entière est cliquable, sans zone morte
   entre le texte et la case. */
.critere-check {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-control);
  font-size: var(--fs-dense); cursor: pointer;
}
.critere-check:hover { background: var(--surface-2); }
.critere-check:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
.critere-check input { margin: 0; flex: none; }


/* Le réglage de l'information manquante : c'est le point délicat du ciblage,
   il doit se voir sans être criard. */
.critere-missing {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2) var(--sp-3);
  font-size: var(--fs-label);
  flex: 1 1 100%;
  padding-top: var(--sp-1);
}
.critere-missing-count { color: var(--muted); }
.critere-missing-choice { display: flex; gap: var(--sp-3); }

/* Sous 720 px, le libellé passe au-dessus de sa saisie : deux colonnes ne
   tiennent plus, et tronquer un libellé de critère le rend indevinable. */
@media (max-width: 720px) {
  .critere { grid-template-columns: minmax(0, 1fr); gap: var(--sp-1); }
}

/* Le compte, toujours visible : c'est lui qui guide le réglage des critères.
   Avec son dénominateur et sa barre — « 43 » ne dit pas si le ciblage est
   étroit ou large, « 43 sur 128 médecins » le dit, et la barre le montre. */
.wizard-count {
  position: sticky; bottom: calc(var(--sp-5) * -1);
  display: grid; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--radius-card);
  background: var(--brand-blue); color: #fff;
  /* Détaché du fond qui défile dessous, sinon les critères transparaissent
     au ras de la bordure. */
  box-shadow: var(--shadow-overlay);
}
.wizard-count-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2);
}
.wizard-count-value {
  font-size: 26px; line-height: 1; font-variant-numeric: tabular-nums;
}
.wizard-count-label { font-weight: 600; }
.wizard-count-detail { font-size: var(--fs-label); opacity: .85; }
/* Le verdict : ai-je assez de monde pour l'objectif ? C'est la question de
   l'étape, elle se lit sans calcul. */
.wizard-count-verdict {
  margin-left: auto; font-size: var(--fs-label); font-weight: 650;
  padding: 2px var(--sp-2); border-radius: 999px;
  background: rgba(255, 255, 255, .18);
}
/* Ce qui est indicatif — la part du carnet, les injoignables déduits — passe
   en retrait. Ces chiffres renseignent, ils ne décident pas. */
.wizard-count-aside { font-size: var(--fs-label); opacity: .75; }
/* Objectif couvert : le bandeau vire au vert de marque. */
.wizard-count.is-enough { background: var(--brand-teal); color: #06302D; }
.wizard-count.is-enough .wizard-count-bar { background: rgba(6, 48, 45, .2); }
.wizard-count.is-enough .wizard-count-fill { background: #06302D; }
.wizard-count.is-enough .wizard-count-verdict { background: rgba(6, 48, 45, .15); }
.wizard-count-warn { margin-left: auto; }
.wizard-count-bar {
  height: 6px; border-radius: 3px;
  background: rgba(255, 255, 255, .25); overflow: hidden;
}
.wizard-count-fill {
  display: block; height: 100%; background: #fff;
  transition: width .18s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .wizard-count-fill { transition: none; }
}
/* Le motif d'injoignabilité passe à la ligne plutôt que de s'écraser contre
   le compte quand la boîte se resserre. */
@media (max-width: 560px) {
  .wizard-count-warn { margin-left: 0; flex-basis: 100%; }
}

.wizard-metrics {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-3);
}
/* Le chiffre et sa part sur la même ligne : c'est le pourcentage qui rend le
   nombre décidable — « 68 injoignables » n'alarme pas de la même façon selon
   qu'ils pèsent 5 % ou 53 % de la cible. */
.metric-line { display: flex; align-items: baseline; gap: var(--sp-2); }
.event-metric-share {
  font-size: var(--fs-label); font-weight: 650; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* La part se teinte comme sa carte : la couleur ne porte jamais seule
   l'information — le libellé de la carte la nomme déjà. */
.event-metric[data-tone="ok"] .event-metric-share { color: var(--success); }
.event-metric[data-tone="warn"] .event-metric-share { color: var(--warning); }
.event-metric[data-tone="danger"] .event-metric-share { color: var(--danger); }

/* Liste simple à deux colonnes : elle sert encore au dialogue d'import, où
   l'on montre la correspondance des colonnes du fichier. */
.wizard-sample { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.wizard-sample li {
  display: flex; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-1) 0; border-bottom: 1px solid var(--line);
  font-size: var(--fs-body);
}
.wizard-sample span { color: var(--muted); font-size: var(--fs-label); text-align: right; }

/* Le tableau des destinataires : le composant commun du CRM, dans la boîte de
   l'assistant. Il défile avec le corps, sa hauteur est bornée pour que le
   pied — donc « Suivant » — reste atteignable sans traverser deux cents
   lignes. */
.wizard-table { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 0; }
.wizard-table .table-wrap { max-height: 340px; overflow: auto; }
.wizard-table table { font-size: var(--fs-dense); }
.wizard-muted { color: var(--muted); }

/* L'état de l'information, ligne à ligne. Pastille + mot : la couleur seule
   ne dirait rien à qui ne la distingue pas. */
.recipient-tag {
  display: inline-block; padding: 1px var(--sp-2); border-radius: 99px;
  font-size: var(--fs-label); font-weight: 600;
  background: var(--surface-2); color: var(--muted);
}
.recipient-tag[data-tone="ok"] { background: var(--success-soft); color: var(--success); }
.recipient-tag[data-tone="warn"] { background: var(--warning-soft); color: var(--warning); }
.recipient-tag[data-tone="danger"] { background: var(--danger-soft); color: var(--danger); }

/* ── Étape 5 : le message ───────────────────────────────────────────────
   Il s'écrit ICI. Renvoyer vers un onglet de la fiche obligeait à sortir de
   l'assistant, à retrouver son chemin, puis à revenir — pour un texte qui est
   le cœur de la campagne. */
.wizard-editor {
  width: 100%; font: inherit; font-size: var(--fs-body); line-height: 1.5;
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-control);
  padding: var(--sp-3); resize: vertical; min-height: 180px;
}
.wizard-editor:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.wizard-editor-foot {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
}
.wizard-editor-foot > .btn { margin-left: auto; }
.wizard-vars { display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }
.wizard-vars-label { font-size: var(--fs-label); color: var(--muted); }
/* Les variables s'insèrent d'un clic : les retaper de mémoire est la première
   source de message cassé — une accolade manquante et « {{nom}} » part tel
   quel chez le destinataire. */
.wizard-var {
  border: 1px solid var(--line); background: var(--surface); cursor: pointer;
  border-radius: var(--radius-control); padding: 1px var(--sp-2);
  font-family: var(--font-mono); font-size: var(--fs-label); color: var(--accent);
}
.wizard-var:hover { border-color: var(--accent); background: var(--accent-soft); }

/* ── Suivi des campagnes ────────────────────────────────────────────────
   L'entonnoir : chaque étape porte son nombre ET sa part. Les deux, parce
   qu'ils ne disent pas la même chose — 31 inscrits est un résultat, 15,5 %
   de conversion est une performance. La barre donne la proportion d'un coup
   d'œil ; les chiffres restent lisibles sans elle. */
.funnel {
  margin: var(--sp-4) 0;
  padding: var(--sp-3); border-radius: var(--radius-card);
  background: var(--surface-2);
}
.funnel-title { margin: 0 0 var(--sp-2); font-size: var(--fs-body); }
.funnel-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.funnel-step {
  display: grid; align-items: center; gap: var(--sp-3);
  grid-template-columns: 110px 1fr 48px 64px;
}
.funnel-label { font-size: var(--fs-label); color: var(--muted); }
/* Le rail se pose sur `--surface-2`, fond de la section : sans contraste
   propre, une barre vide ne se lit plus comme une barre pleine d'une autre
   couleur. Le trait suffit à marquer la piste. */
.funnel-bar {
  height: 10px; border-radius: 5px; box-sizing: border-box;
  background: var(--surface-2); border: 1px solid var(--line);
  overflow: hidden;
}
.funnel-fill { display: block; height: 100%; background: var(--brand-blue); }
.funnel-value { text-align: right; font-variant-numeric: tabular-nums; }
.funnel-rate {
  text-align: right; font-size: var(--fs-label); color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.campaign-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.campaign-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-3);
  padding: var(--sp-2) 0; border-bottom: 1px solid var(--line);
}
.campaign-main { display: grid; gap: 2px; }
.campaign-meta { font-size: var(--fs-label); color: var(--muted); }
.campaign-kpi { margin-left: auto; display: flex; align-items: baseline; gap: var(--sp-1); }
.campaign-kpi span { font-size: var(--fs-label); color: var(--muted); }

/* Sous 640 px, la barre disparaît : les nombres suffisent, et une barre de
   40 px de large ne renseigne plus sur rien. */
@media (max-width: 640px) {
  .funnel-step { grid-template-columns: 1fr 48px 64px; }
  .funnel-bar { display: none; }
}

/* Une étape que le système ne mesure PAS encore : ni chiffre, ni barre pleine
   — le tiret dit « inconnu », le zéro aurait dit « échec ». */
.funnel-step-pending .funnel-value,
.funnel-step-pending .funnel-label { color: var(--muted); }
.funnel-step-pending .funnel-bar { opacity: .4; }
.funnel-step-pending .funnel-rate { font-style: italic; }
.funnel-note {
  margin: var(--sp-2) 0 0; font-size: var(--fs-label); color: var(--muted);
}

/* Ce qu'un franchissement d'étape déclenche vraiment. Une liste, parce qu'il
   y a plusieurs effets et qu'ils ne s'annulent pas : un paragraphe les
   fondrait, alors qu'on veut pouvoir les compter. */
.stage-consequences {
  margin: var(--sp-3) 0; padding-left: var(--sp-5);
  display: grid; gap: var(--sp-1);
  font-size: var(--fs-dense); color: var(--ink);
}
.stage-consequences li { line-height: 1.45; }

/* ── Complétude d'un champ, ligne à ligne ───────────────────────────────
   Sert deux écrans : l'étape 4 de l'assistant (que le message consomme) et
   l'analyse d'un fichier importé (ce qu'il contient). Même question dans les
   deux cas — « ai-je cette information, et pour combien de monde ? » — donc
   même forme.

   La barre porte le taux de remplissage, le décompte porte ce qui manque :
   l'un se lit d'un coup d'œil, l'autre se cite dans une décision. */
.field-rows { display: grid; gap: 0; }
.field-row {
  display: grid;
  grid-template-columns: auto minmax(0, 180px) minmax(0, 200px) minmax(0, 1fr);
  align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  border-top: 1px solid var(--line);
  border-radius: var(--radius-control);
  cursor: pointer;
}
.field-row:first-child { border-top: 0; }
.field-row:hover { background: var(--surface-2); }
/* La case ne colle pas à son libellé : `checklist-box` ne porte pas de marge
   propre, et la coche touchait le premier caractère. */
.field-row input { margin: 0 var(--sp-1) 0 0; flex: none; }
.field-row:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
/* En lecture seule (analyse d'import) : pas de case, pas de curseur. */
.field-row.is-readonly {
  grid-template-columns: minmax(0, 180px) minmax(0, 200px);
  cursor: default;
}
.field-row.is-readonly:hover { background: none; }
.field-row-label { font-size: var(--fs-dense); }
.field-row.is-required .field-row-label { font-weight: 600; }
.field-row-fill { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.field-row-bar {
  flex: 1; height: 8px; border-radius: 4px; box-sizing: border-box;
  background: var(--surface-2); border: 1px solid var(--line); overflow: hidden;
}
.field-row-bar-fill {
  display: block; height: 100%; background: var(--brand-teal);
  transition: width .18s ease-out;
}
/* Un champ exigé rend son manque coûteux : la barre change de sens. */
.field-row.is-required .field-row-bar-fill { background: var(--brand-blue); }
.field-row-count {
  font-size: var(--fs-label); color: var(--muted);
  min-width: 68px; text-align: right; font-variant-numeric: tabular-nums;
}
/* Ce que l'absence produit dans le message — plus parlant qu'un décompte. */
.field-row-effect {
  font-size: var(--fs-label); color: var(--muted); font-style: italic;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Un champ que tout le monde porte ne se coche pas : il n'y a rien à exiger. */
.field-row.is-complete { opacity: .65; cursor: default; }

/* Une action de rattrapage, en bas de page : présente, mais pas le sujet. */
.wizard-footnote {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2);
  padding-top: var(--sp-3); border-top: 1px solid var(--line);
}

@media (prefers-reduced-motion: reduce) {
  .field-row-bar-fill { transition: none; }
}
@media (max-width: 640px) {
  .field-row { grid-template-columns: auto minmax(0, 1fr); }
  .field-row-fill, .field-row-effect { grid-column: 2; }
  .field-row.is-readonly { grid-template-columns: minmax(0, 1fr); }
}

/* ── Ce qui n'empêche pas d'inviter ─────────────────────────────────────
   Volontairement en retrait, sous un trait : ces champs enrichissent la page
   publique et l'agent, mais l'invitation part très bien sans eux. Les mettre
   au même niveau que les vérifications bloquantes apprendrait à ignorer les
   deux — c'est ainsi qu'un avertissement perd son sens. */
.soft-block { margin-top: var(--sp-4); padding-top: var(--sp-3); border-top: 1px solid var(--line); }
.soft-block-title {
  font-size: var(--fs-label); color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em;
  margin-bottom: var(--sp-2);
}
.soft-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-1); }
.soft-row {
  display: grid; grid-template-columns: minmax(0, 150px) minmax(0, 1fr) auto;
  align-items: center; gap: var(--sp-3);
  font-size: var(--fs-label); color: var(--muted);
}
.soft-row-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soft-row-bar {
  height: 4px; border-radius: 2px;
  background: var(--surface-2); overflow: hidden;
}
.soft-row-fill {
  display: block; height: 100%; background: var(--muted);
  transition: width .18s ease-out;
}
/* Complet : la barre prend la teinte de marque, sans pour autant crier. */
.soft-row.is-full .soft-row-fill { background: var(--brand-teal); }
.soft-row-count {
  font-variant-numeric: tabular-nums; min-width: 34px; text-align: right;
  /* Le détail est au survol : le curseur le signale. */
  cursor: help;
}

@media (prefers-reduced-motion: reduce) {
  .soft-row-fill { transition: none; }
}
@media (max-width: 560px) {
  .soft-row { grid-template-columns: minmax(0, 1fr) auto; }
  .soft-row-bar { grid-column: 1 / -1; grid-row: 2; }
}

/* Un manque NON BLOQUANT : la ville du lieu, par exemple. Sans elle on perd
   le filtre géographique, mais l'invitation part. Le gris dit « à savoir »,
   là où le rouge dirait « à corriger d'abord ». */
.check-row-soft .check-mark { color: var(--muted); font-weight: 600; }
.check-row-soft .check-value { color: var(--muted); }

/* La précision entre parenthèses d'une ligne de récapitulatif : elle éclaire
   le chiffre sans lui disputer la vedette. En orange quand la couverture est
   incomplète — c'est le seul cas où elle appelle une décision. */
.check-aside { color: var(--muted); font-weight: 400; }
.check-aside.is-warn { color: var(--warning); font-weight: 600; }

/* La part de l'objectif, dans le compteur du ciblage. Détachée du libellé
   qu'elle suit : celui-ci est en gras, et la parenthèse n'a pas à l'être. */
.wizard-count-part { font-weight: 400; opacity: .85; }

/* ── Plage min–max : un rail, deux poignées ─────────────────────────────
   Deux `<input type="range">` NATIFS superposés. Un composant dessiné à la
   main donnerait un rendu identique mais exigerait de réimplémenter le
   clavier (flèches, Origine, Fin), le tactile et la restitution vocale —
   trois choses que le natif fait déjà bien, et qu'on fait mal.

   Le prix : chaque navigateur peint son propre rail, qu'il faut neutraliser.
   C'est la difficulté qui avait fait abandonner une première tentative. Elle
   se règle en trois points : `appearance: none` sur le champ, un fond
   transparent, et `pointer-events` rendus aux SEULES poignées — sans quoi le
   champ du dessus intercepte les clics destinés à celui du dessous. */
.critere-slider-wrap {
  display: grid; grid-template-columns: auto minmax(120px, 1fr) auto;
  align-items: center; gap: var(--sp-3); width: 100%;
}
.critere-slider-value {
  font-size: var(--fs-dense); font-variant-numeric: tabular-nums;
  white-space: nowrap; min-width: 92px;
}
.critere-slider-bounds {
  font-size: var(--fs-label); color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.critere-slider-track {
  position: relative; height: 22px;
  /* Le rail, dessiné une seule fois ici plutôt que par chaque champ.
     `--line` et non `--surface-2` : sur le fond teinté d'un critère actif,
     le second se confondait avec la ligne et la partie non retenue du rail
     disparaissait — on ne voyait plus jusqu'où la plage pouvait aller. */
  background: linear-gradient(var(--line), var(--line)) center / 100% 4px no-repeat;
  border-radius: 2px;
}
/* Le segment retenu : c'est lui qui montre la plage d'un coup d'œil. */
.critere-slider-fill {
  position: absolute; top: 50%; height: 4px; margin-top: -2px;
  background: var(--brand-blue); border-radius: 2px;
  pointer-events: none;
}

.critere-slider {
  position: absolute; inset: 0; width: 100%; height: 22px; margin: 0;
  /* `appearance` D'ABORD, le fond ENSUITE : sur un `input[type=range]`,
     changer l'apparence réinitialise le fond, et une transparence déclarée
     avant se perdait — le champ restait blanc et masquait le rail et le
     segment retenu, dessinés en dessous. */
  appearance: none; -webkit-appearance: none;
  background-color: transparent; background-image: none;
  border: 0; box-shadow: none; padding: 0;
  /* Le champ entier laisse passer les clics ; seules les poignées les
     prennent. Sans cela, celui du dessus capterait tout le rail et l'autre
     poignée deviendrait inatteignable. */
  pointer-events: none;
  touch-action: manipulation;
}
.critere-slider::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--brand-blue);
  box-shadow: 0 1px 3px rgba(22, 32, 61, .25);
  cursor: grab;
}
.critere-slider::-moz-range-thumb {
  pointer-events: auto;
  width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--brand-blue);
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(22, 32, 61, .25);
  cursor: grab;
}
/* Firefox peint son propre rail sous la poignée : on l'efface. */
.critere-slider::-moz-range-track { background: transparent; border: 0; }
.critere-slider:active::-webkit-slider-thumb { cursor: grabbing; }
.critere-slider:active::-moz-range-thumb { cursor: grabbing; }

/* Le focus se pose sur la POIGNÉE, pas sur le champ invisible qui l'entoure :
   un contour autour du rail entier ne dirait pas laquelle des deux est
   active. */
.critere-slider:focus { outline: none; }
.critere-slider:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.critere-slider:focus-visible::-moz-range-thumb {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

@media (max-width: 560px) {
  .critere-slider-wrap { grid-template-columns: minmax(0, 1fr); }
  .critere-slider-bounds { display: none; }
}

/* L'avertissement de l'étape 7 emprunte le fond ambre de `.public-live-warning`
   et lui ajoute la structure titre + corps : le titre dit le fait, le corps
   dit quoi en faire. Ambre et non rouge — envoyer sans avoir relu reste
   permis, c'est la portée du geste qu'on signale. */
.wizard-check-warning {
  display: flex; flex-direction: column; gap: var(--sp-1);
  max-width: 72ch;
}
.wizard-check-warning strong { font-size: var(--fs-body); }
.wizard-check-warning p { font-size: var(--fs-dense); }
