/* ══════════════════════════════════════════════════════════════════════════
   archiflow-components.css — bibliothèque de composants du thème unique
   Phase 1b de la refonte UI. Cf. docs/diagnostic-refonte-ui.md

   ─── CONTRAT ─────────────────────────────────────────────────────────────
   1. PRÉFIXE `af-` OBLIGATOIRE. Mesuré sur le repo : .btn existe 1 233 fois,
      .kpi 1 029, .modal 650, .card 625. Sans préfixe, chaque composant neuf
      entrerait en collision avec l'existant et la cohabitation pendant la
      migration serait impossible. Le préfixe n'est pas une coquetterie :
      c'est ce qui permet de refondre page par page sans tout casser.
   2. AUCUNE COULEUR, AUCUN ESPACEMENT, AUCUN RAYON EN DUR. Ces trois-là
      passent obligatoirement par un token --af-*. Si une valeur manque,
      l'ajouter dans archiflow-tokens.css — pas ici.
      Les DIMENSIONS INTRINSÈQUES d'un contrôle (une case à cocher de 18 px,
      un bouton de 38 px de haut) restent littérales : ce ne sont pas des
      décisions de design system, ce sont les proportions du composant. Elles
      doivent rester des multiples de 2.
   3. AUCUN !important. Ce fichier ne se bat avec personne : il se charge
      après les tokens et avant les feuilles de page. Un !important ici
      signifierait qu'on rejoue la guerre de cascade qu'on est en train de
      solder (5 076 !important dans l'existant).
   4. Les deux modes sont gérés par les tokens, pas par des sélecteurs
      [data-theme] ici. Si un composant a besoin d'une règle spécifique au
      mode sombre, c'est presque toujours qu'il lui manque un token.
   ══════════════════════════════════════════════════════════════════════════ */


/* ═════════════════════════════════════════════════════════════════════════
   1. BOUTONS
   ═════════════════════════════════════════════════════════════════════════ */

.af-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--af-sp-2);
  padding: var(--af-sp-2) var(--af-sp-5);
  min-height: 38px;
  border: 1px solid transparent;
  border-radius: var(--af-r-md);
  font-family: var(--af-font);
  font-size: var(--af-fs-md);
  font-weight: var(--af-fw-medium);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--af-dur-fast) var(--af-ease),
              border-color var(--af-dur-fast) var(--af-ease),
              color var(--af-dur-fast) var(--af-ease);
}
.af-btn:focus-visible { outline: none; box-shadow: var(--af-focus-ring); }
.af-btn[disabled], .af-btn.is-disabled {
  cursor: not-allowed;
  opacity: .55;
  /* On n'éteint PAS pointer-events : un bouton désactivé doit rester
     survolable pour porter son infobulle « pourquoi c'est désactivé ». */
}

.af-btn--primary { background: var(--af-accent); color: var(--af-on-accent); }
.af-btn--primary:hover:not([disabled]) { background: var(--af-accent-hover); }

.af-btn--secondary {
  background: var(--af-surface);
  color: var(--af-text-1);
  border-color: var(--af-border-strong);
}
.af-btn--secondary:hover:not([disabled]) { background: var(--af-surface-hover); }

.af-btn--ghost { background: transparent; color: var(--af-accent-text); }
.af-btn--ghost:hover:not([disabled]) { background: var(--af-accent-soft); }

.af-btn--quiet { background: transparent; color: var(--af-text-2); }
.af-btn--quiet:hover:not([disabled]) {
  background: var(--af-surface-hover);
  color: var(--af-text-1);
}

/* Danger — une seule variante, réservée aux actions destructrices.
   Volontairement PAS un bouton plein : un rouge plein attire l'œil autant
   qu'une action principale, alors que c'est l'action qu'on veut cliquer le
   moins souvent par erreur. */
.af-btn--danger {
  background: transparent;
  color: var(--af-err-ink);
  border-color: var(--af-err-soft);
}
.af-btn--danger:hover:not([disabled]) { background: var(--af-err-soft); }

.af-btn--sm { min-height: 30px; padding: var(--af-sp-1) var(--af-sp-3); font-size: var(--af-fs-sm); }
.af-btn--lg { min-height: 46px; padding: var(--af-sp-3) var(--af-sp-6); font-size: var(--af-fs-lg); }

.af-btn--icon { padding: 0; width: 38px; min-height: 38px; }
.af-btn--icon.af-btn--sm { width: 30px; min-height: 30px; }
.af-btn--block { display: flex; width: 100%; }

/* Bouton fendu (action + chevron de variantes) — relevé sur les planches
   02 et 08. Les deux moitiés sont deux boutons distincts, pas un menu
   déguisé : la moitié gauche DOIT rester cliquable sans ouvrir le menu. */
.af-btn-split { display: inline-flex; }
.af-btn-split > .af-btn:first-child {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.af-btn-split > .af-btn:last-child {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  padding-left: var(--af-sp-3);
  padding-right: var(--af-sp-3);
  box-shadow: inset 1px 0 0 var(--af-on-accent-veil);
}

/* État de chargement. La largeur du bouton NE DOIT PAS bouger — sinon la
   mise en page saute au clic. Le libellé reste en place, masqué en opacité,
   et le témoin se superpose. */
.af-btn.is-loading { position: relative; color: transparent; pointer-events: none; }
.af-btn.is-loading::after {
  content: '';
  position: absolute;
  width: 16px; height: 16px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--af-r-pill);
  animation: af-spin .7s linear infinite;
  color: var(--af-on-accent);
}
.af-btn--secondary.is-loading::after,
.af-btn--ghost.is-loading::after,
.af-btn--quiet.is-loading::after { color: var(--af-text-2); }
@keyframes af-spin { to { transform: rotate(360deg); } }


/* ═════════════════════════════════════════════════════════════════════════
   2. CHAMPS
   ═════════════════════════════════════════════════════════════════════════ */

.af-field { display: flex; flex-direction: column; gap: var(--af-sp-2); }
.af-label {
  font-size: var(--af-fs-xs);
  font-weight: var(--af-fw-medium);
  color: var(--af-text-3);
  letter-spacing: .04em;
  text-transform: uppercase;
}
.af-label__req { color: var(--af-err); margin-left: 2px; }

.af-input, .af-select, .af-textarea {
  width: 100%;
  padding: var(--af-sp-2) var(--af-sp-3);
  min-height: 38px;
  background: var(--af-surface-sunk);
  color: var(--af-text-1);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-md);
  font-family: var(--af-font);
  font-size: var(--af-fs-md);
  transition: border-color var(--af-dur-fast) var(--af-ease),
              background-color var(--af-dur-fast) var(--af-ease);
}
.af-input::placeholder, .af-textarea::placeholder { color: var(--af-text-4); }
.af-input:hover, .af-select:hover, .af-textarea:hover { border-color: var(--af-border-strong); }
.af-input:focus, .af-select:focus, .af-textarea:focus {
  outline: none;
  background: var(--af-surface);
  border-color: var(--af-accent);
  box-shadow: var(--af-focus-ring);
}
.af-textarea { min-height: 96px; resize: vertical; line-height: var(--af-lh-normal); }

.af-select {
  appearance: none;
  padding-right: var(--af-sp-8);
  /* Le chevron est un masque, pas une image de fond : il suit currentColor
     et donc le mode, sans qu'on ait à en fournir deux versions. */
  background-image: none;
}
.af-select-wrap { position: relative; display: block; }
.af-select-wrap::after {
  content: '';
  position: absolute;
  right: var(--af-sp-3);
  top: 50%;
  width: 10px; height: 6px;
  transform: translateY(-50%);
  background: var(--af-text-3);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  pointer-events: none;
}

/* Champ numérique — les montants s'alignent par la droite et en chasse fixe,
   sinon les bords zigzaguent d'une ligne à l'autre. */
.af-input--num { text-align: right; font-variant-numeric: tabular-nums; }

/* Erreur. Le message est TOUJOURS rendu dans le DOM quand il existe : il ne
   dépend d'aucun script pour apparaître (règle de visibilité des données). */
.af-field.is-error .af-input,
.af-field.is-error .af-select,
.af-field.is-error .af-textarea { border-color: var(--af-err); }
.af-field.is-error .af-input:focus { box-shadow: 0 0 0 3px var(--af-err-soft); }
.af-help { font-size: var(--af-fs-xs); color: var(--af-text-3); }
.af-error-msg {
  display: flex; align-items: center; gap: var(--af-sp-1);
  font-size: var(--af-fs-xs); color: var(--af-err-ink); font-weight: var(--af-fw-medium);
}

.af-input[disabled], .af-select[disabled], .af-textarea[disabled] {
  opacity: .6; cursor: not-allowed; background: var(--af-surface-hover);
}

/* Champ avec icône en tête (recherche) */
.af-input-icon { position: relative; display: block; }
.af-input-icon > .af-input { padding-left: var(--af-sp-8); }
.af-input-icon > .material-symbols-outlined {
  position: absolute; left: var(--af-sp-3); top: 50%;
  transform: translateY(-50%);
  color: var(--af-text-3); font-size: 18px; pointer-events: none;
}


/* ═════════════════════════════════════════════════════════════════════════
   3. CASES, RADIOS, INTERRUPTEURS
   ═════════════════════════════════════════════════════════════════════════ */

.af-check, .af-radio {
  display: inline-flex; align-items: center; gap: var(--af-sp-2);
  cursor: pointer; font-size: var(--af-fs-md); color: var(--af-text-2);
}
.af-check input, .af-radio input { position: absolute; opacity: 0; width: 0; height: 0; }
.af-check__box, .af-radio__box {
  flex: none; width: 18px; height: 18px;
  border: 1.5px solid var(--af-border-strong);
  background: var(--af-surface);
  display: grid; place-items: center;
  transition: background-color var(--af-dur-fast) var(--af-ease),
              border-color var(--af-dur-fast) var(--af-ease);
}
.af-check__box { border-radius: var(--af-r-xs); }
.af-radio__box { border-radius: var(--af-r-pill); }

.af-check input:checked + .af-check__box,
.af-radio input:checked + .af-radio__box {
  background: var(--af-accent); border-color: var(--af-accent);
}
.af-check__box::after {
  content: ''; width: 10px; height: 6px;
  border-left: 2px solid var(--af-on-accent); border-bottom: 2px solid var(--af-on-accent);
  transform: rotate(-45deg) translate(1px, -1px) scale(0);
  transition: transform var(--af-dur-fast) var(--af-ease-out);
}
.af-check input:checked + .af-check__box::after { transform: rotate(-45deg) translate(1px, -1px) scale(1); }
.af-radio__box::after {
  content: ''; width: 8px; height: 8px; border-radius: var(--af-r-pill);
  background: var(--af-on-accent); transform: scale(0);
  transition: transform var(--af-dur-fast) var(--af-ease-out);
}
.af-radio input:checked + .af-radio__box::after { transform: scale(1); }
.af-check input:focus-visible + .af-check__box,
.af-radio input:focus-visible + .af-radio__box { box-shadow: var(--af-focus-ring); }

/* Interrupteur */
.af-switch { display: inline-flex; align-items: center; gap: var(--af-sp-3); cursor: pointer; }
.af-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.af-switch__track {
  flex: none; width: 40px; height: 22px;
  background: var(--af-border-strong);
  border-radius: var(--af-r-pill);
  padding: 3px;
  transition: background-color var(--af-dur-fast) var(--af-ease);
}
.af-switch__thumb {
  display: block; width: 16px; height: 16px;
  background: var(--af-on-accent); border-radius: var(--af-r-pill);
  box-shadow: var(--af-sh-1);
  transition: transform var(--af-dur-fast) var(--af-ease-out);
}
.af-switch input:checked + .af-switch__track { background: var(--af-accent); }
.af-switch input:checked + .af-switch__track .af-switch__thumb { transform: translateX(18px); }
.af-switch input:focus-visible + .af-switch__track { box-shadow: var(--af-focus-ring); }


/* ═════════════════════════════════════════════════════════════════════════
   4. CARTES ET KPI
   ═════════════════════════════════════════════════════════════════════════ */

.af-card {
  background: var(--af-surface);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-xl);
  box-shadow: var(--af-sh-2);
}
.af-card__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--af-sp-4);
  padding: var(--af-sp-5) var(--af-sp-5) 0;
}
.af-card__title { font-size: var(--af-fs-lg); font-weight: var(--af-fw-bold); color: var(--af-text-1); margin: 0; }
.af-card__body { padding: var(--af-sp-5); }
.af-card__foot {
  padding: var(--af-sp-4) var(--af-sp-5);
  border-top: 1px solid var(--af-border);
}
.af-card--flush > .af-card__body { padding: 0; }

/* Carte KPI — relevée sur les planches 01 et 06 : pastille d'icône teintée,
   libellé discret, valeur en gros chiffre, delta, sparkline. */
.af-kpi {
  display: flex; gap: var(--af-sp-3); padding: var(--af-sp-5);
  align-items: flex-start;
  /* Requête de conteneur : ce qui décide de montrer la courbe, c'est la
     largeur de LA CARTE, pas celle de la fenêtre. Selon les droits du compte,
     la rangée porte deux cartes larges ou cinq cartes étroites dans la même
     fenêtre — un point de rupture d'écran se tromperait une fois sur deux. */
  container-type: inline-size;
}
.af-kpi__icon {
  /* 40 px — relevé sur la planche 01. */
  flex: none; width: 40px; height: 40px;
  display: grid; place-items: center;
  border-radius: var(--af-r-md);
  background: var(--af-accent-soft); color: var(--af-accent-text);
}
.af-kpi__icon--ok   { background: var(--af-ok-soft);   color: var(--af-ok-ink); }
.af-kpi__icon--warn { background: var(--af-warn-soft); color: var(--af-warn-ink); }
.af-kpi__icon--err  { background: var(--af-err-soft);  color: var(--af-err-ink); }
.af-kpi__icon--info { background: var(--af-info-soft); color: var(--af-info-ink); }
.af-kpi__label {
  display: flex; align-items: center; gap: var(--af-sp-1);
  /* ⚠️ NI capitales NI interlettrage — corrigé le 2026-08-17.
     La Phase 1 avait choisi des capitales espacées ; la planche 01 écrit ces
     libellés en casse normale (« Projets actifs », « Budget global »), et
     MESURÉ dans une rangée de cinq cartes, « CHIFFRE D'AFFAIRES » en
     capitales tenait sur trois lignes là où « Chiffre d'affaires » tient sur
     une. La maquette avait raison pour une raison mécanique. */
  font-size: var(--af-fs-sm); color: var(--af-text-3);
  font-weight: var(--af-fw-medium);
  margin: 0 0 var(--af-sp-1);
}
.af-kpi__value {
  /* Un montant ne se coupe pas en deux lignes : « 0,00 » puis « € » se lit
     comme deux informations. Il rétrécit plutôt que de passer à la ligne. */
  white-space: nowrap;
  font-size: var(--af-fs-2xl); font-weight: var(--af-fw-bold);
  color: var(--af-text-1); line-height: var(--af-lh-tight);
  font-variant-numeric: tabular-nums;
}
/* ⚠️ Le delta suit le SENS MÉTIER, pas le signe : une baisse des dépenses
   s'affiche en vert. Relevé sur la planche 06. C'est au gabarit de choisir
   la classe, pas au signe du nombre. */
.af-kpi__delta { font-size: var(--af-fs-xs); font-weight: var(--af-fw-medium); margin: var(--af-sp-1) 0 0; }
.af-kpi__delta--good { color: var(--af-ok); }
.af-kpi__delta--bad  { color: var(--af-err); }
.af-kpi__delta--flat { color: var(--af-text-3); }


/* ═════════════════════════════════════════════════════════════════════════
   5. BADGES, CHIPS, TAGS
   ═════════════════════════════════════════════════════════════════════════ */

.af-badge {
  display: inline-flex; align-items: center; gap: var(--af-sp-1);
  padding: 3px var(--af-sp-3);
  border-radius: var(--af-r-pill);
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-bold);
  letter-spacing: .04em; text-transform: uppercase; white-space: nowrap;
}
/* ⚠️ Le libellé ET la couleur d'un statut viennent de utils/statuts.py
   (source unique). Ces classes sont des CONTENANTS : ne jamais écrire un hex
   de statut ici ni dans un gabarit. */
.af-badge--ok   { background: var(--af-ok-soft);   color: var(--af-ok-ink); }
.af-badge--warn { background: var(--af-warn-soft); color: var(--af-warn-ink); }
.af-badge--err  { background: var(--af-err-soft);  color: var(--af-err-ink); }
.af-badge--info { background: var(--af-info-soft); color: var(--af-info-ink); }
.af-badge--mute { background: var(--af-mute-soft); color: var(--af-mute-ink); }
.af-badge--dot::before {
  content: ''; width: 6px; height: 6px; border-radius: var(--af-r-pill);
  background: currentColor;
}

/* Tag de phase (ESQ, APS, PRO…) — carré, pas pilule, pour se distinguer
   d'un statut au premier coup d'œil. */
.af-tag {
  display: inline-flex; align-items: center;
  padding: 2px var(--af-sp-2);
  border-radius: var(--af-r-xs);
  background: var(--af-mute-soft); color: var(--af-mute-ink);
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-bold); letter-spacing: .06em;
}

/* Chip de filtre à compteur — relevée sur les planches 05 et 08. */
.af-chip {
  display: inline-flex; align-items: center; gap: var(--af-sp-2);
  padding: var(--af-sp-1) var(--af-sp-3);
  min-height: 30px;
  background: var(--af-surface);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-pill);
  font-size: var(--af-fs-sm); font-weight: var(--af-fw-medium);
  color: var(--af-text-2); cursor: pointer;
  transition: background-color var(--af-dur-fast) var(--af-ease),
              border-color var(--af-dur-fast) var(--af-ease),
              color var(--af-dur-fast) var(--af-ease);
}
.af-chip:hover { border-color: var(--af-border-strong); color: var(--af-text-1); }
.af-chip__count {
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-bold);
  padding: 1px var(--af-sp-2);
  border-radius: var(--af-r-pill);
  background: var(--af-surface-sunk); color: var(--af-text-3);
}
.af-chip.is-active {
  background: var(--af-accent); border-color: var(--af-accent); color: var(--af-on-accent);
}
.af-chip.is-active .af-chip__count { background: var(--af-on-accent-veil); color: var(--af-on-accent); }
.af-chip:focus-visible { outline: none; box-shadow: var(--af-focus-ring); }

/* Pastille de couleur de projet. ⚠️ DashboardProject n'a pas de champ
   couleur : la teinte vient du STATUT (ProjectStatus.color), posée en ligne
   par le gabarit. Ne pas inventer de palette ici. */
.af-dot { display: inline-block; width: 8px; height: 8px; border-radius: var(--af-r-pill); flex: none; }


/* ═════════════════════════════════════════════════════════════════════════
   6. TABLEAUX
   ═════════════════════════════════════════════════════════════════════════ */

.af-table { width: 100%; border-collapse: collapse; font-size: var(--af-fs-sm); }
.af-table th {
  text-align: left;
  padding: var(--af-sp-3) var(--af-sp-4);
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-medium);
  color: var(--af-text-3); letter-spacing: .04em; text-transform: uppercase;
  border-bottom: 1px solid var(--af-border);
  white-space: nowrap;
}
.af-table td {
  padding: var(--af-sp-4);
  color: var(--af-text-2);
  border-bottom: 1px solid var(--af-border);
  vertical-align: middle;
}
.af-table tbody tr:last-child td { border-bottom: 0; }
.af-table tbody tr { transition: background-color var(--af-dur-fast) var(--af-ease); }
.af-table--hover tbody tr:hover { background: var(--af-surface-hover); }

/* ⚠️ Montants : alignés à droite ET en chasse fixe. Sans tabular-nums les
   bords zigzaguent de plusieurs pixels d'une ligne à l'autre — dette
   identifiée de longue date, soldée ici. */
.af-table .af-num, .af-table th.af-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--af-text-1);
  font-weight: var(--af-fw-medium);
}
.af-table__foot td {
  border-top: 1px solid var(--af-border-strong);
  border-bottom: 0;
  font-weight: var(--af-fw-bold);
  color: var(--af-text-1);
  background: var(--af-surface-sunk);
}
.af-table thead.af-sticky th {
  position: sticky; top: 0; z-index: var(--af-z-sticky);
  background: var(--af-surface);
}


/* ═════════════════════════════════════════════════════════════════════════
   7. ONGLETS ET SOUS-NAVIGATION
   ═════════════════════════════════════════════════════════════════════════ */

.af-tabs { display: flex; gap: var(--af-sp-1); border-bottom: 1px solid var(--af-border); }
.af-tab {
  display: inline-flex; align-items: center; gap: var(--af-sp-2);
  padding: var(--af-sp-3) var(--af-sp-4);
  background: transparent; border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: var(--af-font); font-size: var(--af-fs-md);
  font-weight: var(--af-fw-medium); color: var(--af-text-3);
  cursor: pointer;
  transition: color var(--af-dur-fast) var(--af-ease),
              border-color var(--af-dur-fast) var(--af-ease);
}
.af-tab:hover { color: var(--af-text-1); }
.af-tab.is-active { color: var(--af-accent-text); border-bottom-color: var(--af-accent); }
.af-tab:focus-visible { outline: none; box-shadow: var(--af-focus-ring); border-radius: var(--af-r-sm); }
.af-tab__count {
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-bold);
  padding: 1px var(--af-sp-2); border-radius: var(--af-r-pill);
  background: var(--af-surface-sunk); color: var(--af-text-3);
}
.af-tab.is-active .af-tab__count { background: var(--af-accent-soft); color: var(--af-accent-text); }

/* Sous-navigation verticale (Réglages) — relevée sur la planche 03. */
.af-subnav { display: flex; flex-direction: column; gap: 2px; }
.af-subnav__item {
  padding: var(--af-sp-2) var(--af-sp-3);
  border-radius: var(--af-r-md);
  font-size: var(--af-fs-md); color: var(--af-text-2);
  text-decoration: none;
  transition: background-color var(--af-dur-fast) var(--af-ease);
}
.af-subnav__item:hover { background: var(--af-surface-hover); color: var(--af-text-1); }
.af-subnav__item.is-active {
  background: var(--af-accent-soft); color: var(--af-accent-text);
  font-weight: var(--af-fw-medium);
}

/* Bascule segmentée (Grille / Liste / Planning) — planche 08. */
.af-segmented {
  display: inline-flex; gap: 2px; padding: 3px;
  background: var(--af-surface-sunk);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-md);
}
.af-segmented__item {
  display: inline-flex; align-items: center; gap: var(--af-sp-2);
  padding: var(--af-sp-1) var(--af-sp-3);
  border: 0; background: transparent;
  border-radius: var(--af-r-sm);
  font-family: var(--af-font); font-size: var(--af-fs-sm);
  font-weight: var(--af-fw-medium); color: var(--af-text-3);
  cursor: pointer;
  transition: background-color var(--af-dur-fast) var(--af-ease);
}
.af-segmented__item.is-active { background: var(--af-surface); color: var(--af-text-1); box-shadow: var(--af-sh-1); }


/* ═════════════════════════════════════════════════════════════════════════
   8. MODALES ET VOILES
   ═════════════════════════════════════════════════════════════════════════ */

.af-overlay {
  position: fixed; inset: 0;
  background: var(--modal-backdrop);
  z-index: var(--af-z-overlay);
  display: grid; place-items: center;
  padding: var(--af-sp-6);
}
.af-modal {
  position: relative;
  z-index: var(--af-z-modal);
  width: 100%; max-width: 560px;
  max-height: calc(100vh - var(--af-sp-12));
  display: flex; flex-direction: column;
  background: var(--af-surface);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-2xl);
  box-shadow: var(--af-sh-4);
}
.af-modal--lg { max-width: 840px; }
.af-modal__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--af-sp-4);
  padding: var(--af-sp-5) var(--af-sp-5) var(--af-sp-4);
  border-bottom: 1px solid var(--af-border);
}
.af-modal__title { margin: 0; font-size: var(--af-fs-lg); font-weight: var(--af-fw-bold); color: var(--af-text-1); }
.af-modal__sub { margin: var(--af-sp-1) 0 0; font-size: var(--af-fs-sm); color: var(--af-text-3); }
.af-modal__body { padding: var(--af-sp-5); overflow-y: auto; }
.af-modal__foot {
  display: flex; justify-content: flex-end; gap: var(--af-sp-3);
  padding: var(--af-sp-4) var(--af-sp-5);
  border-top: 1px solid var(--af-border);
}


/* ═════════════════════════════════════════════════════════════════════════
   9. TOASTS, INFOBULLES, MENUS
   ═════════════════════════════════════════════════════════════════════════ */

.af-toasts {
  position: fixed; right: var(--af-sp-5);
  /* ⚠️ La bottombar occupe déjà 104 px réservés par le conteneur de contenu
     (DESIGN.md §1.6). On s'appuie sur le token, on ne redouble pas la garde. */
  bottom: var(--af-bottombar-clearance);
  z-index: var(--af-z-toast);
  display: flex; flex-direction: column; gap: var(--af-sp-3);
  pointer-events: none;
}
.af-toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: var(--af-sp-3);
  min-width: 280px; max-width: 400px;
  padding: var(--af-sp-4);
  background: var(--af-surface);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-lg);
  box-shadow: var(--af-sh-3);
  font-size: var(--af-fs-sm); color: var(--af-text-2);
}
/* La sévérité est portée par l'ICÔNE, pas par un filet d'accent latéral.
   Une barre colorée de 3 px contredirait la règle du système — « jamais de
   bordure lourde, les séparations sont des filets de 1 px à faible opacité »
   — et elle ferait doublon avec l'icône, qui dit déjà la même chose.
   La pastille teintée reprend exactement le traitement des cartes KPI :
   un seul geste pour « voici de quelle nature est cette information ». */
.af-toast__icon {
  flex: none; width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: var(--af-r-sm);
  background: var(--af-mute-soft); color: var(--af-mute-ink);
}
.af-toast--ok   .af-toast__icon { background: var(--af-ok-soft);   color: var(--af-ok-ink); }
.af-toast--warn .af-toast__icon { background: var(--af-warn-soft); color: var(--af-warn-ink); }
.af-toast--err  .af-toast__icon { background: var(--af-err-soft);  color: var(--af-err-ink); }
.af-toast--info .af-toast__icon { background: var(--af-info-soft); color: var(--af-info-ink); }
.af-toast__title { font-weight: var(--af-fw-bold); color: var(--af-text-1); margin: 0 0 2px; }

/* Infobulle. ⚠️ À portaliser sur <body> en position fixed : un parent avec
   backdrop-filter ou overflow crée un contexte d'empilement qui la rogne.
   Piège déjà rencontré (cf. DESIGN.md §2.1). */
.af-tooltip {
  position: fixed; z-index: var(--af-z-tooltip);
  padding: var(--af-sp-2) var(--af-sp-3);
  background: var(--af-n-900); color: var(--af-on-dark-1);
  border-radius: var(--af-r-sm);
  font-size: var(--af-fs-xs);
  box-shadow: var(--af-sh-3);
  pointer-events: none; max-width: 260px;
}

.af-menu {
  min-width: 200px; padding: var(--af-sp-2);
  background: var(--af-surface);
  border: 1px solid var(--af-border);
  border-radius: var(--af-r-lg);
  box-shadow: var(--af-sh-3);
  z-index: var(--af-z-dropdown);
}
.af-menu__item {
  display: flex; align-items: center; gap: var(--af-sp-3);
  width: 100%; padding: var(--af-sp-2) var(--af-sp-3);
  border: 0; background: transparent;
  border-radius: var(--af-r-sm);
  font-family: var(--af-font); font-size: var(--af-fs-md);
  color: var(--af-text-2); text-align: left; text-decoration: none; cursor: pointer;
  transition: background-color var(--af-dur-fast) var(--af-ease);
}
.af-menu__item:hover { background: var(--af-surface-hover); color: var(--af-text-1); }
.af-menu__item--danger { color: var(--af-err-ink); }
.af-menu__item--danger:hover { background: var(--af-err-soft); }
.af-menu__sep { height: 1px; margin: var(--af-sp-2) 0; background: var(--af-border); }


/* ═════════════════════════════════════════════════════════════════════════
   10. STEPPER ET TIMELINE
   ═════════════════════════════════════════════════════════════════════════ */

.af-stepper { display: flex; align-items: center; gap: var(--af-sp-2); }
.af-step { display: flex; flex-direction: column; align-items: center; gap: var(--af-sp-2); }
.af-step__dot {
  width: 28px; height: 28px; border-radius: var(--af-r-pill);
  display: grid; place-items: center;
  background: var(--af-surface-sunk); color: var(--af-text-3);
  border: 1.5px solid var(--af-border-strong);
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-bold);
}
.af-step__label { font-size: var(--af-fs-xs); color: var(--af-text-3); }
.af-step.is-active .af-step__dot { background: var(--af-accent); border-color: var(--af-accent); color: var(--af-on-accent); }
.af-step.is-active .af-step__label { color: var(--af-text-1); font-weight: var(--af-fw-medium); }
.af-step.is-done .af-step__dot { background: var(--af-ok); border-color: var(--af-ok); color: var(--af-on-accent); }
.af-stepper__bar { flex: 1; height: 2px; background: var(--af-border-strong); }
.af-stepper__bar.is-done { background: var(--af-ok); }

/* Timeline verticale à coches — relevée sur la planche 05 (paiement). */
.af-timeline { display: flex; flex-direction: column; }
.af-tl-item { display: flex; gap: var(--af-sp-3); position: relative; padding-bottom: var(--af-sp-5); }
.af-tl-item:last-child { padding-bottom: 0; }
.af-tl-item::before {
  content: ''; position: absolute;
  left: 10px; top: 22px; bottom: 0;
  width: 2px; background: var(--af-border-strong);
}
.af-tl-item:last-child::before { display: none; }
.af-tl__dot {
  flex: none; width: 22px; height: 22px; border-radius: var(--af-r-pill);
  display: grid; place-items: center;
  background: var(--af-surface-sunk); color: var(--af-text-3);
  border: 1.5px solid var(--af-border-strong);
  font-size: 13px; z-index: 1;
}
.af-tl-item.is-done .af-tl__dot { background: var(--af-ok); border-color: var(--af-ok); color: var(--af-on-accent); }
.af-tl-item.is-current .af-tl__dot { background: var(--af-accent); border-color: var(--af-accent); color: var(--af-on-accent); }
.af-tl__label { font-size: var(--af-fs-md); color: var(--af-text-1); font-weight: var(--af-fw-medium); }
.af-tl__meta { font-size: var(--af-fs-xs); color: var(--af-text-3); margin-top: 2px; }


/* ═════════════════════════════════════════════════════════════════════════
   11. PROGRESSION — barres et jauges
   ═════════════════════════════════════════════════════════════════════════ */

.af-progress {
  height: 6px; border-radius: var(--af-r-pill);
  background: var(--af-surface-sunk); overflow: hidden;
}
/* ⚠️ AUCUNE TRANSITION sur la largeur, délibérément.
   La valeur est rendue par le serveur : elle est juste dès la première
   peinture, elle n'a rien à « rejoindre ». Animer une largeur déclenche un
   recalcul de mise en page à chaque image, pour un gain nul — et surtout
   cela reviendrait à faire dépendre l'affichage d'une donnée d'une
   animation, ce que le système interdit (DESIGN.md §1.8).
   Si un jour une barre doit suivre une valeur qui change en direct, lui
   donner un modificateur dédié — ne pas rétablir la transition ici. */
.af-progress__fill {
  height: 100%; border-radius: var(--af-r-pill);
  background: var(--af-accent);
}
.af-progress__fill--ok   { background: var(--af-ok); }
.af-progress__fill--warn { background: var(--af-warn); }
.af-progress__fill--err  { background: var(--af-err); }

/* Jauge circulaire — relevée sur les planches 01, 05, 08.
   ⚠️ Le pourcentage est écrit en TEXTE au centre : la jauge l'illustre, elle
   ne le porte pas. Une donnée ne doit jamais dépendre d'un rendu graphique
   (ni d'un script) pour être lisible. */
.af-gauge { position: relative; display: inline-grid; place-items: center; }
.af-gauge__ring {
  border-radius: var(--af-r-pill);
  /* --af-gauge-pct et --af-gauge-color sont posés en ligne par le gabarit. */
  background: conic-gradient(
    var(--af-gauge-color, var(--af-accent)) calc(var(--af-gauge-pct, 0) * 1%),
    var(--af-surface-sunk) 0);
}
.af-gauge__hole {
  position: absolute; border-radius: var(--af-r-pill);
  background: var(--af-surface);
  display: grid; place-items: center;
}
.af-gauge__value {
  font-size: var(--af-fs-md); font-weight: var(--af-fw-bold);
  color: var(--af-text-1); font-variant-numeric: tabular-nums;
}
.af-gauge--sm .af-gauge__ring { width: 40px; height: 40px; }
.af-gauge--sm .af-gauge__hole { inset: 5px; }
.af-gauge--sm .af-gauge__value { font-size: var(--af-fs-xs); }
.af-gauge--md .af-gauge__ring { width: 72px; height: 72px; }
.af-gauge--md .af-gauge__hole { inset: 8px; }
.af-gauge--lg .af-gauge__ring { width: 120px; height: 120px; }
.af-gauge--lg .af-gauge__hole { inset: 12px; }
.af-gauge--lg .af-gauge__value { font-size: var(--af-fs-xl); }


/* ═════════════════════════════════════════════════════════════════════════
   12. AVATARS, MONOGRAMMES, VIGNETTES
   ═════════════════════════════════════════════════════════════════════════ */

.af-avatar {
  display: inline-grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: var(--af-r-pill);
  background: var(--af-accent-soft); color: var(--af-accent-text);
  font-size: var(--af-fs-xs); font-weight: var(--af-fw-bold);
  overflow: hidden; text-transform: uppercase;
}
.af-avatar img { width: 100%; height: 100%; object-fit: cover; }
.af-avatar--sm { width: 24px; height: 24px; font-size: var(--af-fs-xs); }
.af-avatar--lg { width: 48px; height: 48px; font-size: var(--af-fs-md); }

/* Pile d'avatars — relevée sur les planches 02 et 08. */
.af-avatars { display: inline-flex; }
.af-avatars > .af-avatar { margin-left: -8px; box-shadow: 0 0 0 2px var(--af-surface); }
.af-avatars > .af-avatar:first-child { margin-left: 0; }
.af-avatars__more {
  background: var(--af-surface-sunk); color: var(--af-text-3);
}

/* Vignette de projet. ⚠️ DashboardProject N'A PAS de champ image : l'état par
   défaut n'est pas un repli d'erreur, c'est l'état NORMAL. Un monogramme sur
   aplat, jamais une icône « image cassée ». */
.af-thumb {
  display: grid; place-items: center;
  border-radius: var(--af-r-lg);
  background: var(--af-surface-sunk);
  color: var(--af-text-3);
  overflow: hidden;
  font-size: var(--af-fs-lg); font-weight: var(--af-fw-bold);
}
.af-thumb img { width: 100%; height: 100%; object-fit: cover; }


/* ═════════════════════════════════════════════════════════════════════════
   13. ÉTATS VIDES, SQUELETTES, PAGINATION
   ═════════════════════════════════════════════════════════════════════════ */

.af-empty {
  display: flex; flex-direction: column; align-items: center; gap: var(--af-sp-3);
  padding: var(--af-sp-12) var(--af-sp-6);
  text-align: center;
}
.af-empty__icon {
  width: 48px; height: 48px; display: grid; place-items: center;
  border-radius: var(--af-r-lg);
  background: var(--af-surface-sunk); color: var(--af-text-4);
}
.af-empty__title { margin: 0; font-size: var(--af-fs-lg); font-weight: var(--af-fw-bold); color: var(--af-text-1); }
.af-empty__text { margin: 0; font-size: var(--af-fs-sm); color: var(--af-text-3); max-width: 44ch; }

/* ⚠️ RÈGLE ABSOLUE (DESIGN.md §1.8) : un squelette n'est JAMAIS l'état
   initial d'une donnée déjà rendue par le serveur. Il ne s'emploie que pour
   du contenu réellement chargé après coup (fragment AJAX). Poser .af-skeleton
   sur un bloc rendu côté serveur revient à masquer une donnée disponible —
   et à la perdre définitivement si le script échoue. */
.af-skeleton {
  background: linear-gradient(90deg,
    var(--af-surface-sunk) 25%,
    var(--af-surface-hover) 37%,
    var(--af-surface-sunk) 63%);
  background-size: 400% 100%;
  border-radius: var(--af-r-sm);
  animation: af-shimmer 1.4s ease-in-out infinite;
}
@keyframes af-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

.af-pagination { display: flex; align-items: center; gap: var(--af-sp-1); }
.af-page {
  min-width: 32px; height: 32px; padding: 0 var(--af-sp-2);
  display: inline-grid; place-items: center;
  border: 1px solid transparent; border-radius: var(--af-r-sm);
  background: transparent; color: var(--af-text-2);
  font-family: var(--af-font); font-size: var(--af-fs-sm);
  font-variant-numeric: tabular-nums; cursor: pointer;
  transition: background-color var(--af-dur-fast) var(--af-ease);
}
.af-page:hover { background: var(--af-surface-hover); color: var(--af-text-1); }
.af-page.is-active { background: var(--af-accent); color: var(--af-on-accent); }
.af-page[disabled] { opacity: .4; cursor: not-allowed; }


/* ═════════════════════════════════════════════════════════════════════════
   14. NAVIGATION LATÉRALE — DÉPLACÉE

   ⚠️ Cette section esquissait la barre latérale pour la page /design-system.
   La VRAIE implémentation vit dans static/css/archiflow-layout.css depuis la
   Phase 3a, et elle porte les mêmes noms de classes.

   Les deux ont coexisté le temps d'une session : deux définitions de
   `.af-nav__item` dans deux fichiers, celle de layout gagnant par l'ordre de
   chargement, sans que rien ne le signale. Une esquisse et une
   implémentation ne doivent pas partager un nom de classe — l'esquisse est
   donc retirée, pas commentée.
   ═════════════════════════════════════════════════════════════════════════ */



/* ═════════════════════════════════════════════════════════════════════════
   15. ZONE DE DÉPÔT DE FICHIER
   ═════════════════════════════════════════════════════════════════════════ */

.af-drop {
  display: flex; flex-direction: column; align-items: center; gap: var(--af-sp-2);
  padding: var(--af-sp-6);
  border: 1.5px dashed var(--af-border-strong);
  border-radius: var(--af-r-lg);
  background: var(--af-surface-sunk);
  color: var(--af-text-3); text-align: center; cursor: pointer;
  transition: border-color var(--af-dur-fast) var(--af-ease),
              background-color var(--af-dur-fast) var(--af-ease);
}
.af-drop:hover, .af-drop.is-over {
  border-color: var(--af-accent);
  background: var(--af-accent-soft);
  color: var(--af-accent-text);
}
.af-drop__hint { font-size: var(--af-fs-xs); color: var(--af-text-4); }


/* ═════════════════════════════════════════════════════════════════════════
   16. MOUVEMENT RÉDUIT
      Les durées sont déjà mises à 0 par les tokens. Restent les animations
      en boucle, qui ne passent pas par une durée tokenisée.
   ═════════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  .af-skeleton { animation: none; }
  .af-btn.is-loading::after { animation-duration: 2.4s; }
}


/* ═════════════════════════════════════════════════════════════════════════
   14. CARTE KPI — corps et courbe

   Ajouts de la Phase 3a. La planche 01 pose une courbe à droite de chaque
   carte KPI. Elle est DÉCORATIVE au sens strict : la valeur et son évolution
   sont écrites en toutes lettres à côté. Une donnée ne doit jamais dépendre
   d'un tracé pour être lue.
   ═════════════════════════════════════════════════════════════════════════ */

.af-kpi__corps { flex: 1 1 auto; min-width: 0; }
.af-kpi__spark {
  display: none;   /* état par défaut : la donnée se lit sans elle */
  flex: none;
  /* 100 × 40 sur la planche. */
  width: 100px; height: 40px;
  align-self: center;
  overflow: visible;
}
/* 300 px MESURÉ : en dessous, le libellé n'a plus que 47 px et « Chiffre
   d'affaires » tient sur trois lignes. La courbe est décorative, le libellé
   ne l'est pas — c'est elle qui cède. */
/* ⚠️ La requête mesure la BOÎTE DE CONTENU du conteneur, pas sa largeur
   totale : une carte de 355 px avec 20 px de marge intérieure de chaque côté
   « mesure » 315. Un seuil à 320 masquait donc la courbe sur une carte
   pourtant assez large — constaté en mesurant, pas en lisant la spec. */
@container (min-width: 300px) {
  .af-kpi__spark { display: block; }
}
/* Sans cela, `preserveAspectRatio="none"` étire le trait avec le tracé : la
   courbe devient un ruban épais à gauche et un cheveu à droite. */
.af-kpi__spark polyline { vector-effect: non-scaling-stroke; }



/* Rangée d'indicateurs. `auto-fit` plutôt qu'un nombre fixe de colonnes :
   selon les droits du compte, la rangée compte 2, 3, 4 ou 5 cartes — une
   grille à 5 colonnes laisserait des trous à un comptable. */
.af-kpi-rangee {
  display: grid;
  /* 200 px, mesuré : à 232 la cinquième carte passait à la ligne sur
     1 440 px et « 0,00 € » se coupait en deux. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--af-sp-4);
  margin-bottom: var(--af-sp-5);
}
