/* =============================================================================
   FullStage Design System v2  ·  CSS pur, portable (Rails / Hotwire / ViewComponent)
   -----------------------------------------------------------------------------
   Valeurs TRANSPOSÉES du rendu réel Astryx (Meta) sous le thème FullStage,
   capturées au pixel (Playwright getComputedStyle). Voir astryx-lab/.
   Brand-only : navy + teal, pas d'orange. Icônes = Lucide (16px en ligne).

   PORTER : reprendre le bloc Tokens dans le @theme/:root de application.css,
   puis aligner les ViewComponents sur ces classes. Aucun runtime, aucun React.
   ============================================================================= */

:root {
  /* ---- Radius (Astryx : inner 4 → element 8 → container 12 → page 28) ---- */
  --r-inner: 4px; --r-element: 8px; --r-container: 12px; --r-page: 28px; --r-full: 9999px;

  /* ---- Spacing (base 4px) ---- */
  --s-0: 0; --s-0-5: 2px; --s-1: 4px; --s-1-5: 6px; --s-2: 8px; --s-3: 12px;
  --s-4: 16px; --s-5: 20px; --s-6: 24px; --s-7: 28px; --s-8: 32px; --s-9: 36px;
  --s-10: 40px; --s-11: 44px; --s-12: 48px;

  /* ---- Tailles d'élément (28 / 32 / 36) ---- */
  --size-sm: 28px; --size-md: 32px; --size-lg: 36px;

  /* ---- Typographie (Manrope titres, Open Sans corps ; échelle 14×1.2) ---- */
  /* Police = swappable par agence, comme la couleur. Le cœur du DS est AGNOSTIQUE :
     fallback système. Une agence définit --brand-font-heading / --brand-font-body
     (+ ses @font-face) via son pack de polices. FullStage a le sien (Manrope +
     Open Sans), voir fullstage_fonts.css. Rien de spécifique à une agence ici. */
  --font-heading: var(--brand-font-heading, ui-sans-serif, system-ui, sans-serif);
  --font-body: var(--brand-font-body, ui-sans-serif, system-ui, sans-serif);
  /* Graisse des titres = pendant de --font-heading pour la famille (#77). Défaut semibold
     (Astryx : text-heading-*-weight = font-weight-semibold). Une agence dont la police
     d'affichage n'a qu'une coupe la ramène à 400 sans classe maison, plutôt que de laisser
     le navigateur synthétiser un faux gras. */
  --font-heading-weight: var(--brand-font-heading-weight, var(--fw-semibold));
  --fs-2xs: 10px; --fs-xs: 12px; --fs-sm: 13px; --fs-base: 14px; --fs-body: 16px;
  /* Astryx mesuré : chrome (boutons, badges, onglets, labels) en 14px = --fs-base ;
     CONTENU (listes, chat, prose, champs) en 16px = --fs-body. */
  --fs-lg: 17px; --fs-xl: 20px; --fs-2xl: 24px;
  --fw-normal: 400; --fw-medium: 500; --fw-semibold: 600; --fw-bold: 700;

  /* ---- Couleur de marque (contrat gem agency_branding : swappable par agence) ---- */
  --brand: #011c25;          /* FullStage = navy ; l'agence injecte SON hex ici en runtime */
  --brand-hover: #01141b;    /* gem : -10% (survol) */
  --brand-active: #000d12;   /* gem : -20% (pressé) */
  --brand-dark: #4a6b74;     /* repli statique : voir la dérivation @supports plus bas */

  /* ---- Sémantique (light) — neutres = famille navy ---- */
  --bg: #ffffff;
  --surface: #ffffff;
  --fill: #f7f9fa;
  --neutral: rgba(5, 54, 89, .1);   /* fond secondary / segmented / badge neutre / token */
  --text-strong: #011c25;
  --text-weak: #4a5f68;
  --stroke-strong: #d4d4d4;         /* Astryx --color-border-emphasized exact (repos doux ; focus = navy + ring) */
  --stroke-weak: #e8ecee;           /* séparateurs */

  --accent: var(--brand);           /* accent = couleur de marque (swap par agence via --brand) */
  --on-accent: #ffffff;
  --accent-text: var(--brand);      /* liens = marque (soulignés) */
  --focus: var(--text-strong);      /* ring de focus = marque (light) / blanc (dark) */
  --ring: rgba(1, 28, 37, .15);     /* halo interne de focus des champs */
  /* Action primaire = marque. */
  --primary-bg: var(--brand);
  --primary-fg: #ffffff;

  /* ---- Statut système (badges pleins, façon Astryx) ---- */
  --success: #166534; --on-success: #ffffff;
  --warning: #854d0e; --on-warning: #0a1317;
  --error:   #dc2626; --on-error:   #ffffff;
  --info:    #1e40af; --on-info:    #ffffff;

  --shadow-raised: 0 1px 2px rgba(1,28,37,.10), 0 1px 3px rgba(1,28,37,.06);
  --shadow-overlay: 0 16px 48px rgba(1,28,37,.16), 0 4px 12px rgba(1,28,37,.08);

  /* ---- Scrim d'overlay (agence-workspace#59) : sorti du dur de .drawer-overlay pour que
     <dialog>::backdrop ET le drawer le partagent. Opacités = source Astryx --color-overlay
     (light-dark : navy 40% / near-black 60%) ; teinte = navy de la gem, pas le #011228 littéral. ---- */
  --scrim: rgba(1, 28, 37, .4);

  /* ---- Mouvement (agence-workspace#58) — valeurs de la SOURCE Astryx (durationVars/easeVars),
     pas les valeurs ad-hoc du portail. Bandes Astryx : fast 130/175/230, medium 410. ---- */
  --dur-fast: 130ms;   /* micro-état (hover/couleur/bordure) = Astryx --duration-fast-min */
  --dur-base: 230ms;   /* composant (drawer/toast/sidenav/tabs) = Astryx --duration-fast-max */
  --dur-slow: 410ms;   /* entrée de contenu (.animate-in/.stagger) = Astryx --duration-medium */
  --ease-out: cubic-bezier(0.24, 1, 0.4, 1);   /* Astryx --ease-standard */

  /* ---- Empilement (échelle publiée ; les composants gem s'y branchent) ---- */
  --z-sticky: 30; --z-dropdown: 40; --z-drawer: 50; --z-modal: 60; --z-toast: 80; --z-tooltip: 100;

  /* ---- Teintes douces : DÉRIVÉES des tokens de statut (qui basculent déjà en dark,
     donc une seule définition couvre les deux thèmes) ---- */
  --success-soft: color-mix(in srgb, var(--success) 12%, var(--surface));
  --warning-soft: color-mix(in srgb, var(--warning) 12%, var(--surface));
  --error-soft:   color-mix(in srgb, var(--error)   12%, var(--surface));
  --info-soft:    color-mix(in srgb, var(--info)    12%, var(--surface));
  --on-success-soft: var(--success); --on-warning-soft: var(--warning);
  --on-error-soft: var(--error); --on-info-soft: var(--info);
  /* Dérivé de --accent, pas de --brand : en sombre --accent est la marque
     éclaircie (--brand-dark, 0.40.0) — mixer le --brand brut y donnerait une
     teinte invisible pour les marques foncées. En clair, --accent = --brand. */
  --brand-soft: color-mix(in srgb, var(--accent) 12%, var(--surface));

  /* ---- Tracking (em : suit la police d'agence ; échelle optique négative + caps positif) ---- */
  --tracking-ui: -0.011em; --tracking-snug: -0.02em; --tracking-tight: -0.025em;
  --tracking-display: -0.032em; --tracking-caps: .06em;

  /* ---- Page (agence-workspace#57) : --topnav-h promu du fallback dur au contrat ---- */
  --topnav-h: 56px;        /* était seulement un fallback var(--topnav-h, 56px) dans .topnav/.appshell */
  --content-max: 1280px;   /* largeur de lecture (max-w-7xl, mesuré sur 2 apps/3) */
  --page-pad: var(--s-4);  /* responsive plus bas (16 → 24 → 32, échelle Astryx) */
}
@media (min-width: 640px)  { :root { --page-pad: var(--s-6); } }
@media (min-width: 1024px) { :root { --page-pad: var(--s-8); } }

/* ===== La marque s'adapte au thème sombre =====================================
   Une couleur de marque foncée est invisible sur une surface sombre : le navy
   #011c25 de FullStage donne 1,01:1 sur --bg. La doctrine précédente contournait
   ça en posant --accent: #ffffff en sombre, ce qui effaçait purement la marque
   et gelait le white-label sur la moitié des thèmes.

   La règle appliquée est celle de Practical UI et d'Astryx : en sombre, clarté
   en hausse, saturation en baisse, TEINTE CONSTANTE. La syntaxe de couleur
   relative la calcule dans le navigateur, donc à partir de l'hex que l'agence
   injecte, sans que la gem ait à connaître les marques de ses adoptants.

   `max(c, 0.09)` relève le chroma des marques ternes : sans lui, un navy très
   désaturé donnerait un gris, et la marque serait perdue au lieu d'être
   éclaircie. Mesuré sur --bg #0f1a18 :

     navy   #011c25 -> #7fd1ef  10,39:1
     violet #7c3aed -> #ce9bff   8,26:1
     orange #c2410c -> #ff976b   8,37:1
     teal   #26c8b9 -> #49decf  10,69:1

   Toutes gardent leur teinte et passent l'AA large. Les navigateurs sans
   syntaxe relative gardent le repli statique --brand-dark du bloc :root.

   ⚠ CONTRAT D'ADOPTION : l'agence doit injecter --brand SANS le restreindre au
   thème clair. Un `:root:not([data-theme="dark"]) { --brand: … }` fait dériver
   la valeur par défaut de la gem au lieu de la marque de l'agence.        */
@supports (color: oklch(from red l c h)) {
  :root { --brand-dark: oklch(from var(--brand) 0.82 max(c, 0.09) h); }
}

[data-theme="dark"] {
  --bg: #0f1a18; --surface: #1a2826; --fill: #1a2826;
  --neutral: rgba(223, 226, 229, .12);
  --text-strong: #ffffff; --text-weak: #b8d4d0;
  --stroke-strong: #525252; --stroke-weak: #253432;
  --accent-text: var(--brand-dark); --focus: var(--text-strong); --ring: rgba(255,255,255,.18);
  /* L'emphase porte la marque éclaircie, plus le blanc : voir la dérivation
     ci-dessus. --primary-fg et --on-accent restent le navy profond, qui donne
     8,2:1 ou mieux sur toutes les marques dérivées mesurées. */
  --primary-bg: var(--brand-dark); --primary-fg: #011c25;
  --accent: var(--brand-dark); --on-accent: #011c25;
  --success: #4ade80; --on-success: #06210f;
  --warning: #facc15; --on-warning: #241a00;
  --error: #f87171; --on-error: #2a0606;
  --shadow-raised: 0 1px 2px rgba(0,0,0,.4), 0 1px 3px rgba(0,0,0,.3);
  --shadow-overlay: 0 16px 48px rgba(0,0,0,.5), 0 4px 12px rgba(0,0,0,.3);
  --scrim: rgba(0, 0, 0, .6);
}

/* ===== Base ===== */
.ds { font-family: var(--font-body); color: var(--text-strong); background: var(--bg);
  font-size: var(--fs-base); line-height: 1.5; -webkit-font-smoothing: antialiased; }
/* Titres : SOURCE UNIQUE (agence-workspace#57), utilisable hors du scope démo .ds.
   Astryx : headings TOUS semibold (vérifié tokens.stylex : --text-heading-*-weight
   = semibold) → h3/h4 passent de bold à semibold (CHANGELOG). Astryx ne définit
   pas de letter-spacing sur ses headings ; on garde le -.01em subtil de la gem
   (pas --tracking-display -.032, sur-correction non-Astryx). */
.title-page, .title-section, .title-sub, .ds h1, .ds h2, .ds h3, .ds h4 {
  font-family: var(--font-heading); margin: 0; color: var(--text-strong);
  letter-spacing: -.01em; font-weight: var(--font-heading-weight); text-wrap: balance; }
.title-page,    .ds h1 { font-size: var(--fs-2xl); line-height: 1.33; }
.title-section, .ds h2 { font-size: var(--fs-xl);  line-height: 1.4; }
.title-sub,     .ds h3 { font-size: var(--fs-lg);  line-height: 1.41; }
.ds h4 { font-size: var(--fs-base); line-height: 1.43; }

/* ===== Buttons ===== (Open Sans 500, padding-inline 12, radius 8) */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  font-family: var(--font-body); font-weight: var(--fw-medium); font-size: var(--fs-base);
  line-height: 1; border: none; cursor: pointer; border-radius: var(--r-element);
  white-space: nowrap; padding: 0 var(--s-3); height: var(--size-md);
  transition: background-color .15s, opacity .15s, transform .08s; }
.btn--sm { height: var(--size-sm); } .btn--md { height: var(--size-md); } .btn--lg { height: var(--size-lg); }
/* retour tactile au clic (remonté d'app-biz : bonne idée, donc mutualisée) */
.btn:active:not(:disabled) { scale: .96; }
/* ToggleButton (on/off) + ButtonGroup (boutons joints) */
.btn--toggle { background: var(--surface); border: 1px solid var(--stroke-strong); color: var(--text-strong); }
.btn--toggle:hover { background: var(--neutral); }
.btn--toggle[aria-pressed="true"], .btn--toggle.is-active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-group { display: inline-flex; }
.btn-group > .btn { border-radius: 0; }
.btn-group > .btn:first-child { border-radius: var(--r-element) 0 0 var(--r-element); }
.btn-group > .btn:last-child { border-radius: 0 var(--r-element) var(--r-element) 0; }
.btn-group > .btn + .btn { margin-left: 1px; }
.btn--primary     { background: var(--primary-bg); color: var(--primary-fg); }
.btn--accent      { background: var(--accent); color: var(--on-accent); }
.btn--secondary   { background: var(--neutral); color: var(--text-strong); }
.btn--ghost       { background: transparent; color: var(--text-strong); }
.btn--destructive { background: var(--error); color: #fff; }
/* Boutons sémantiques (#82) : tokens --success/--warning déjà dans la gem. Le bouton
   « valider » des flux d'approbation (17 usages Roalink) n'est pas un cas isolé. */
.btn--success     { background: var(--success); color: var(--on-success); }
.btn--warning     { background: var(--warning); color: var(--on-warning); }
.btn:active { transform: scale(.98); }
.btn:disabled { opacity: .45; cursor: not-allowed; }
.btn__icon { width: 16px; height: 16px; flex: none; }

/* ===== Promo CTA =====
   Point d'entrée compact pour une action secondaire à forte valeur (parrainage,
   offre ponctuelle). La double couche de background crée un filet multicolore
   sans sacrifier la surface, le contraste ni le thème sombre. */
.promo-cta { display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  min-height: 40px; padding: 0 var(--s-3); border: 1px solid transparent;
  border-radius: var(--r-full); color: var(--text-strong); font-family: var(--font-body);
  font-size: var(--fs-base); font-weight: var(--fw-medium); line-height: 1; white-space: nowrap;
  background: linear-gradient(var(--surface), var(--surface)) padding-box,
    linear-gradient(105deg, #8b5cf6, #ec4899 28%, #f59e0b 52%, #22c55e 76%, #3b82f6) border-box;
  box-shadow: var(--shadow-raised); cursor: pointer;
  transition: box-shadow .15s, transform .08s; }
.promo-cta__icon { width: 16px; height: 16px; flex: none; color: var(--accent); }
.promo-cta__value { font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; }
.promo-cta:active { transform: scale(.96); }

/* ===== Field ===== (bordure 1px stroke-strong, radius 8, focus accent + ring) */
.field { display: flex; flex-direction: column; gap: var(--s-1-5); }
/* Label : source unique (agence-workspace#64). `.label` est utilisable SEUL (bloc + marge) ;
   dans un `.field`, le gap prend le relais et la marge retombe à 0. */
.label, .field__label { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-sm); color: var(--text-strong); }
.label { display: block; margin-bottom: var(--s-1-5); }
.field .label { margin-bottom: 0; }
.field__hint  { font-size: var(--fs-xs); color: var(--text-weak); }
.field__error { font-size: var(--fs-xs); color: var(--error); }
/* Récapitulatif d'erreurs en tête de formulaire (agence-workspace#64). Bordure 4 côtés,
   teintée par color-mix sur --error (même formule que .confirm-pill : 35% bordure / 12% fond)
   → correct en sombre SANS règle [data-theme="dark"] dédiée. */
.form-errors { display: flex; flex-direction: column; gap: var(--s-2);
  border: 1px solid color-mix(in srgb, var(--error) 35%, transparent); border-radius: var(--r-element);
  background: color-mix(in srgb, var(--error) 12%, transparent); color: var(--error); padding: var(--s-3) var(--s-4); }
.form-errors__title { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-sm); }
.form-errors__list { margin: 0; padding-left: var(--s-4); list-style: disc; font-size: var(--fs-sm);
  display: flex; flex-direction: column; gap: var(--s-0-5); }
.input, .select, .textarea {
  /* border-box : la hauteur est celle du bouton de même taille, avec ou sans le reset
     de l'app (en content-box, la bordure ajoutait 2 px). Texte à --s-3 du bord, comme
     .btn et .filter-pill : une barre d'outils aligne ses textes sur une même grille. */
  box-sizing: border-box; width: 100%; height: var(--size-md); padding: 0 var(--s-3); font-family: var(--font-body);
  /* Astryx mesuré : champs en 16px = --fs-body (evite aussi le zoom iOS). */
  font-size: var(--fs-body); color: var(--text-strong); background: var(--bg);
  border: 1px solid var(--stroke-strong); border-radius: var(--r-element); outline: none;
  transition: border-color .15s, box-shadow .15s; }
.textarea { height: auto; min-height: 70px; padding: var(--s-2) var(--s-3); resize: vertical; line-height: 1.5; }
/* Tailles : celles des boutons (sm 28, md 32, lg 36). sm passe le texte en --fs-base
   (densité des barres de filtres et des tableaux) ; en tactile, il remonte à 16 px. */
.input--sm, .select--sm { height: var(--size-sm); font-size: var(--fs-base); }
.textarea--sm { font-size: var(--fs-base); }
.input--lg, .select--lg { height: var(--size-lg); }
/* Largeur à son contenu (filtre en ligne, select dans une barre d'outils). */
.input--auto, .select--auto { width: auto; }
.input::placeholder, .textarea::placeholder { color: var(--text-weak); opacity: 1; }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--ring); }
/* Invalide : la bordure reste rouge au focus, le halo prend la teinte de l'erreur. */
.input[aria-invalid="true"], .select[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--error); }
.input[aria-invalid="true"]:focus, .select[aria-invalid="true"]:focus, .textarea[aria-invalid="true"]:focus {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--error) 20%, transparent); }
.input:disabled, .select:disabled, .textarea:disabled { opacity: .5; cursor: not-allowed; background: var(--fill); }
/* Chevron de 16 px à --s-3 du bord ; le texte s'arrête --s-2 avant lui (12 + 16 + 8 = --s-9). */
.select { appearance: none; padding-right: var(--s-9);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%234a5f68' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right var(--s-3) center; }

/* ===== Checkbox / Radio / Switch ===== (coché = accent) */
.checkbox, .radio { appearance: none; width: 18px; height: 18px; flex: none; margin: 0; cursor: pointer;
  border: 1.5px solid var(--stroke-strong); background: var(--bg); display: inline-grid; place-content: center;
  transition: background-color .12s, border-color .12s; }
.checkbox { border-radius: var(--r-inner); } .radio { border-radius: var(--r-full); }
.checkbox:checked, .radio:checked { background: var(--accent); border-color: var(--accent); }
.checkbox:checked::after { content: ""; width: 10px; height: 10px; background: var(--on-accent);
  clip-path: polygon(14% 44%, 0 60%, 40% 100%, 100% 22%, 84% 8%, 40% 68%); }
.radio:checked::after { content: ""; width: 8px; height: 8px; border-radius: var(--r-full); background: var(--on-accent); }
.switch { appearance: none; position: relative; width: 36px; height: 20px; flex: none; margin: 0; cursor: pointer;
  border-radius: var(--r-full); background: var(--stroke-strong); transition: background-color .18s; }
.switch:checked { background: var(--accent); }
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px;
  border-radius: var(--r-full); background: #fff; transition: transform .18s; box-shadow: 0 1px 2px rgba(0,0,0,.25); }
.switch:checked::after { transform: translateX(16px); }

/* ===== Segmented control ===== (track navy 10%, item sélectionné = surface) */
.segmented { display: inline-flex; padding: var(--s-0-5); gap: var(--s-0-5);
  background: var(--neutral); border-radius: var(--r-element); }
.segmented__item { display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; cursor: pointer; font-family: var(--font-body);
  font-weight: var(--fw-medium); font-size: var(--fs-base); color: var(--text-weak);
  height: calc(var(--size-md) - var(--s-1)); padding: 0 var(--s-3);
  border-radius: calc(var(--r-element) - var(--s-0-5)); transition: color .12s, background-color .12s; }
/* Segmenté compact, à la hauteur d'un bouton --sm. */
.segmented--sm .segmented__item { height: calc(var(--size-sm) - var(--s-1)); padding: 0 var(--s-2); font-size: var(--fs-xs); }
.segmented__item[aria-selected="true"], .segmented__item[aria-current="true"], .segmented__item[aria-current="page"] {
  background: var(--surface); color: var(--text-strong); box-shadow: var(--shadow-raised); }
.segmented__item:not([aria-disabled="true"]):hover { color: var(--text-strong); }
/* Option indisponible (lien remplacé par un <span aria-disabled>) : lisible, non cliquable. */
.segmented__item[aria-disabled="true"] { cursor: default; opacity: .55; }

/* ===== Badge ===== (pill ; sémantique = plein, façon Astryx) */
.badge { display: inline-flex; align-items: center; gap: var(--s-1); height: 20px; padding: 0 var(--s-2);
  border-radius: var(--r-full); font-family: var(--font-body); font-weight: var(--fw-medium); font-size: var(--fs-xs); }
/* Petit badge (méta dense : carte de board, ligne de liste). */
.badge--sm { height: 16px; padding: 0 var(--s-1-5); font-size: var(--fs-2xs); }
.badge--neutral { background: var(--neutral); color: var(--text-strong); }
.badge--success { background: #dcfce7; color: #166534; }
.badge--warning { background: #fef9c3; color: #854d0e; }
.badge--error   { background: #fee2e2; color: #991b1b; }
.badge--info    { background: #dbeafe; color: #1e40af; }
.badge--brand   { background: color-mix(in srgb, #011c25 12%, transparent); color: #011c25; }
[data-theme="dark"] .badge--success { background: rgba(22,101,52,.35); color: #4ade80; }
[data-theme="dark"] .badge--warning { background: rgba(133,77,14,.35); color: #facc15; }
[data-theme="dark"] .badge--error   { background: rgba(153,27,27,.35); color: #f87171; }
[data-theme="dark"] .badge--info    { background: rgba(30,64,175,.35); color: #93c5fd; }
[data-theme="dark"] .badge--brand   { background: color-mix(in srgb, #ffffff 16%, transparent); color: #ffffff; }
/* Badge assistant/IA + slot icône (agence-workspace#42). NB : l'issue le décrivait
   « teal », or le teal est interdit en UI (mono-navy). L'IA prend donc l'accent PLEIN
   (comme la variante `info` d'Astryx = accent + on-accent), pas de teal. */
.badge__icon { width: 14px; height: 14px; flex: none; }
.badge--ai { background: var(--accent); color: var(--on-accent); }

/* ===== ConfirmPill ===== (agence-workspace#42 : confirmation « succès » bordée + check,
   entre un .badge et un .banner ; vert sémantique autorisé, bordure pleine 4 côtés) */
.confirm-pill { display: inline-flex; align-items: center; gap: var(--s-1-5); height: 24px; padding: 0 var(--s-3);
  border: 1px solid color-mix(in srgb, var(--success) 35%, transparent); border-radius: var(--r-full);
  background: color-mix(in srgb, var(--success) 12%, transparent); color: var(--success);
  font-family: var(--font-body); font-weight: var(--fw-medium); font-size: var(--fs-xs); }
.confirm-pill svg { width: 14px; height: 14px; flex: none; }

/* ===== StatusDot ===== (préféré aux badges pour un statut en table/liste) */
.status { display: inline-flex; align-items: center; gap: var(--s-1-5); font-size: var(--fs-base); color: var(--text-strong); }
.status::before { content: ""; }
/* Point de statut autonome (agence-workspace#44) : base + variantes PARTAGÉES avec
   .status::before (une seule source pour le point). Pour ~15 points SANS libellé
   (étapes pipeline, indicateurs, pastilles pulse) aujourd'hui en hex hors tokens. */
.dot, .status::before { width: 8px; height: 8px; flex: none; border-radius: var(--r-full); background: var(--text-weak); }
.dot { display: inline-block; }
.dot--sm { width: 6px; height: 6px; }
.dot--success, .status--success::before { background: #16a34a; }
.dot--warning, .status--warning::before { background: #d99400; }
.dot--error,   .status--error::before   { background: var(--error); }
.dot--accent,  .status--accent::before  { background: var(--accent); }
.dot--muted, .status--muted::before { background: var(--stroke-strong); }
.dot--info,  .status--info::before  { background: #60a5fa; }
.dot--violet { background: #a78bfa; }
/* Statut éditable (propriété façon Notion) : un <button class="status"> ouvre un .menu
   de valeurs (la valeur retenue en .menu__item.is-active). Reset du bouton natif,
   surface au survol et menu ouvert : la pastille se lit comme une valeur, pas comme un bouton. */
button.status { margin: -2px calc(-1 * var(--s-1-5)); padding: 2px var(--s-1-5); border: 0; border-radius: var(--r-element);
  background: none; font-family: var(--font-body); line-height: inherit; text-align: left; cursor: pointer; }
button.status[aria-expanded="true"] { background: var(--neutral); }
@media (hover: hover) { button.status:hover { background: var(--neutral); } }

/* ===== Swatch ===== (pastilles de couleur sélectionnables : 7 valeurs nommées,
   pas un color picker). <div class="swatch" role="group" aria-label="Couleur">
   puis des <button type="button" class="swatch__item swatch__item--info"
   aria-pressed="true|false" aria-label="Bleu">. Mêmes couleurs que .dot. */
.swatch { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.swatch__item { --swatch-color: var(--stroke-strong); width: 24px; height: 24px; flex: none; padding: 0;
  border: 0; border-radius: var(--r-full); background: var(--swatch-color); cursor: pointer; }
.swatch__item[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--swatch-color); }
.swatch__item:focus-visible { outline: 2px solid var(--focus); outline-offset: 5px; }
.swatch__item--success { --swatch-color: #16a34a; }
.swatch__item--warning { --swatch-color: #d99400; }
.swatch__item--error   { --swatch-color: var(--error); }
.swatch__item--info    { --swatch-color: #60a5fa; }
.swatch__item--violet  { --swatch-color: #a78bfa; }
.swatch__item--accent  { --swatch-color: var(--accent); }

/* ===== Token ===== (chip, radius 4) */
.token { display: inline-flex; align-items: center; gap: var(--s-1); height: 24px; padding: 0 var(--s-2);
  border-radius: var(--r-inner); background: var(--neutral); color: var(--text-strong); font-size: var(--fs-xs); font-weight: var(--fw-medium); }

/* ===== Card ===== (plate : radius 12, bordure 1px stroke-weak, pas d'ombre) */
.card { background: var(--surface); border: 1px solid var(--stroke-weak);
  border-radius: var(--r-container); padding: var(--s-4); }
/* Densités de carte : --sm pour une carte serrée, --flush quand le contenu (tableau,
   onglets, liste) porte lui-même ses marges. Remplacent les !p-* posés dans les apps. */
.card--sm { padding: var(--s-3); }
.card--flush { padding: 0; }
.card--elevated { box-shadow: var(--shadow-raised); }
.card--muted { background: var(--fill); }            /* carte en retrait (panneau, section repliée) */
.card--lg { padding: var(--s-6); }                   /* carte spacieuse (panneau riche en contenu) */
.card--xl { padding: var(--s-8); }                   /* carte de page (login) : padding 32 du template Astryx */

/* ===== Banner ===== (statut : filet de couleur + contenu) */
.banner { display: flex; gap: var(--s-3); padding: var(--s-3) var(--s-4); border-radius: var(--r-container); align-items: flex-start;
  transition: opacity .3s ease, transform .3s ease; }
/* Les banners avec une action directe (ex. fermeture) doivent centrer le texte et
   le contrôle sur la même ligne. Sans cette variante, un bouton tactile de 44px
   reste en haut tandis qu'une ligne de texte de 20px est plaquée au padding haut.
   Les banners multi-lignes sans action conservent l'alignement haut de la source. */
.banner:has(> button) { align-items: center; }
/* Sortie (#82) : miroir de .toast.is-leaving, décalage vers le HAUT (banner ancré en haut). */
.banner.is-leaving { opacity: 0; transform: translateY(-4px); }
.banner__icon { flex: none; margin-top: 1px; }
.banner__title { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-base); }
.banner__desc { font-size: var(--fs-sm); margin-top: 2px; opacity: .8; }
/* variantes tintées (fond + icône) — pattern de l'image de réf, pas de barre latérale */
.banner--info    { background: #dbeafe; color: #1e3a8a; }
.banner--info    .banner__icon { color: #2563eb; }
.banner--success { background: #dcfce7; color: #166534; }
.banner--success .banner__icon { color: #16a34a; }
.banner--warning { background: #fef9c3; color: #854d0e; }
.banner--warning .banner__icon { color: #b45309; }
.banner--error   { background: #fee2e2; color: #991b1b; }
.banner--error   .banner__icon { color: #dc2626; }
[data-theme="dark"] .banner--info    { background: rgba(30,58,138,.35); color: #bfdbfe; }
[data-theme="dark"] .banner--info    .banner__icon { color: #60a5fa; }
[data-theme="dark"] .banner--success { background: rgba(22,101,52,.35); color: #86efac; }
[data-theme="dark"] .banner--success .banner__icon { color: #4ade80; }
[data-theme="dark"] .banner--warning { background: rgba(133,77,14,.4); color: #fcd34d; }
[data-theme="dark"] .banner--warning .banner__icon { color: #fbbf24; }
[data-theme="dark"] .banner--error   { background: rgba(153,27,27,.35); color: #fca5a5; }
[data-theme="dark"] .banner--error   .banner__icon { color: #f87171; }

/* ===== Empty state ===== */
.empty { display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--s-2); padding: var(--s-8) var(--s-6); }
.empty__title { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-lg); }
.empty__desc { font-size: var(--fs-sm); color: var(--text-weak); max-width: 32ch; }
.empty__icon { color: var(--text-weak); opacity: .5; margin-bottom: var(--s-1); }

/* ===== Divider ===== */
.divider { display: flex; align-items: center; gap: var(--s-3); color: var(--text-weak); font-size: var(--fs-xs); }
.divider::before, .divider::after { content: ""; height: 1px; background: var(--stroke-weak); flex: 1; }

/* ===== Avatar ===== (rond, initiales) */
.avatar { position: relative; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-full);
  background: var(--neutral); color: var(--text-strong); font-family: var(--font-heading);
  font-weight: var(--fw-semibold); flex: none; }
.avatar--sm { width: 24px; height: 24px; font-size: 10px; }
.avatar--md { width: 36px; height: 36px; font-size: 13px; }
.avatar--lg { width: 48px; height: 48px; font-size: 16px; }
/* pastille de présence (en ligne / occupé / hors-ligne) */
.avatar__status { position: absolute; bottom: 0; right: 0; width: 10px; height: 10px; border-radius: var(--r-full); border: 2px solid var(--bg); }
.avatar--lg .avatar__status { width: 12px; height: 12px; }
.avatar__status--online { background: #22c55e; }
.avatar__status--busy { background: var(--error); }
.avatar__status--offline { background: var(--stroke-strong); }
/* pastille de notification (activité non lue), en haut à droite */
.avatar__dot { position: absolute; top: 0; right: 0; width: 9px; height: 9px; border-radius: var(--r-full); background: var(--error); border: 2px solid var(--bg); }

/* ===== Progress ===== */
.progress { height: 8px; border-radius: var(--r-full); background: var(--neutral); overflow: hidden; }
.progress__fill { height: 100%; border-radius: var(--r-full); background: var(--accent); }

/* ===== Spinner ===== */
.spinner { width: 18px; height: 18px; border-radius: var(--r-full);
  border: 2px solid var(--neutral); border-top-color: var(--accent); animation: spin .7s linear infinite; }
.spinner--sm { width: 12px; height: 12px; } .spinner--lg { width: 24px; height: 24px; border-width: 3px; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ===== SaveStatus ===== (pastille d'état de sauvegarde, pilotée par data-state — agence-workspace#56).
   Bâtie sur les tokens et la pastille .dot livrée en #44 : zéro classe Tailwind, zéro bordure d'accent. */
.save-status { display: inline-flex; align-items: center; gap: var(--s-1-5);
  min-height: var(--s-5); font-size: var(--fs-xs); color: var(--text-weak); }
.save-status::before { content: ""; width: 8px; height: 8px; flex: none; border-radius: var(--r-full); background: var(--stroke-strong); }
.save-status[data-state="saving"]::before { width: 12px; height: 12px; background: none;
  border: 2px solid var(--neutral); border-top-color: var(--accent); animation: spin .7s linear infinite; }
.save-status[data-state="saved"]::before { background: var(--success); }
.save-status[data-state="error"] { color: var(--error); }
.save-status[data-state="error"]::before { background: var(--error); }

/* ===== Table ===== (th 600 text-weak, td 400, padding 8/12, ligne 1px) */
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
.table th { text-align: left; padding: var(--s-2) var(--s-3); font-family: var(--font-heading);
  font-weight: var(--fw-semibold); color: var(--text-weak); white-space: nowrap; }
/* Colonne numérique : en-tête et cellules alignés à droite (chiffres tabulaires via .num). */
.table th.num, .table td.num { text-align: right; }
.table td { padding: var(--s-2) var(--s-3); color: var(--text-strong); border-top: 1px solid var(--stroke-weak); }
/* Cellule entièrement cliquable : le lien porte le padding de la cellule et couvre
   toute sa surface (clic, focus, survol). Remplace les « !p-0 » + lien en px/py. */
.table td.table__cell-link { padding: 0; }
.table__cell-link > a { display: flex; align-items: center; width: 100%; height: 100%; padding: var(--s-2) var(--s-3); color: inherit; }
/* Fond de ligne porté par --row-fill : une cellule figée (table-grid) le reprend sur une surface opaque. */
.table tbody tr:hover { --row-fill: var(--fill); background: var(--row-fill); }
/* Extras (agence-workspace#41) — remontés du portail, non spécifiques. */
/* 1. wrapper scrollable : sans lui, une table large fait scroller la page entière */
/* position: relative : bloc conteneur des descendants en absolute (sr-only des cellules,
   poignées). Sans lui, ils échappent au défilement du tableau et allongent la page. */
.table-wrap { position: relative; overflow-x: auto; border: 1px solid var(--stroke-weak); border-radius: var(--r-container); }
/* 2. zébrage OPT-IN (ne change pas les tables existantes), theme-aware via color-mix */
.table--zebra tbody tr:nth-child(even) { --row-fill: color-mix(in srgb, var(--text-strong) 2%, transparent); background: var(--row-fill); }
/* table-layout fixe (agence-workspace#62) : colonnes contraintes + ellipsis (prérequis du resize). */
.table--fixed { table-layout: fixed; }
.table--fixed th, .table--fixed td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 3. états de ligne sémantiques — le :hover explicite bat le tbody tr:hover de la base */
.table tbody tr[aria-current="true"] { --row-fill: var(--fill); background: var(--row-fill); font-weight: var(--fw-semibold); }
.table tbody tr.row--warning       { --row-fill: color-mix(in srgb, var(--warning) 9%, transparent); background: var(--row-fill); }
.table tbody tr.row--warning:hover { --row-fill: color-mix(in srgb, var(--warning) 13%, transparent); background: var(--row-fill); }
.table tbody tr.row--danger        { --row-fill: color-mix(in srgb, var(--error) 9%, transparent); background: var(--row-fill); }
.table tbody tr.row--danger:hover  { --row-fill: color-mix(in srgb, var(--error) 13%, transparent); background: var(--row-fill); }
/* 4. table éditable (0.45.0) : une cellule = un champ sans cadre, lisible comme
   du texte au repos, champ au focus. `.table__cell--missing` teinte la cellule
   à remplir et montre le repli en sourdine (placeholder) ; `.table__input--draft`
   marque une proposition automatique, en italique jusqu'à validation. */
.table--editable td { padding: 0; vertical-align: top; }
.table--editable td.table__cell--static { padding: var(--s-2) var(--s-3); }
.table__input { display: block; width: 100%; min-height: var(--size-md); margin: 0; padding: var(--s-2) var(--s-3);
  border: 1px solid transparent; border-radius: 0; background: transparent; color: var(--text-strong);
  font: inherit; line-height: 1.45; resize: none; }
.table__input:hover { background: var(--fill); }
.table__input:focus { outline: none; border-color: var(--focus); background: var(--surface); box-shadow: 0 0 0 3px var(--ring); position: relative; z-index: 1; }
.table__input::placeholder { color: var(--text-weak); }
.table__input--draft { font-style: italic; color: var(--text-weak); }
.table tbody td.table__cell--missing { background: var(--warning-soft); }
.table--editable tbody tr:hover td.table__cell--missing { background: color-mix(in srgb, var(--warning) 16%, var(--surface)); }

/* 5. table-grid (0.46.0) : colonnes redimensionnables, navigation clavier, colonnes
   figées. Contrôleur `table-grid` sur le .table-wrap ; il pose .table--grid sur la table.
   .table-wrap--sticky borne la hauteur au viewport : l'en-tête reste figé et la barre de
   défilement horizontale reste à l'écran, même sur des centaines de lignes. */
.table-wrap--sticky { overflow: auto; max-height: var(--table-max-height, calc(100dvh - 7rem)); background: var(--table-bg, var(--surface)); }
.table-wrap--sticky > .table thead th { position: sticky; top: 0; z-index: 2; background: var(--table-bg, var(--surface));
  box-shadow: inset 0 -1px var(--stroke-weak); }
.table--grid { background: var(--table-bg, var(--surface)); }
.table--grid th { position: relative; }
.table--grid th, .table--grid td { box-sizing: border-box; }
.table--grid td:focus, .table--grid th:focus { outline: none; }
.table--grid td:focus-visible, .table--grid th:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.table--grid .is-frozen { position: sticky; z-index: 1;
  background: linear-gradient(var(--row-fill, transparent), var(--row-fill, transparent)), var(--table-bg, var(--surface)); }
.table--grid thead th.is-frozen, .table-wrap--sticky > .table thead th.is-frozen { z-index: 3; }
.table--grid .is-frozen-edge { box-shadow: inset -1px 0 var(--stroke-weak); }
/* Une cellule figée crée un contexte d'empilement : son menu ouvert (statut, « … »)
   passerait sous les cellules figées des lignes suivantes. Elle monte tant qu'il est ouvert. */
.table--grid .is-frozen:has([aria-expanded="true"]) { z-index: 4; }
.table-wrap[data-scrolled-x] .table--grid .is-frozen-edge { box-shadow: inset -1px 0 var(--stroke-weak), 6px 0 8px -6px color-mix(in srgb, var(--text-strong) 22%, transparent); }
.table-wrap--sticky[data-scrolled-x] > .table thead .is-frozen-edge { box-shadow: inset -1px 0 var(--stroke-weak), inset 0 -1px var(--stroke-weak), 6px 0 8px -6px color-mix(in srgb, var(--text-strong) 22%, transparent); }
.table__resizer { position: absolute; top: 0; bottom: 0; right: 0; width: var(--s-2); cursor: col-resize; touch-action: none; z-index: 1; }
.table__resizer::after { content: ""; position: absolute; top: 20%; bottom: 20%; right: 0; width: 2px; border-radius: var(--r-full);
  background: var(--stroke-weak); opacity: 0; transition: opacity .12s, background-color .12s; }
@media (hover: hover) { .table--grid th:hover .table__resizer::after { opacity: 1; } .table__resizer:hover::after, .table__resizer[data-hover]::after { opacity: 1; background: var(--accent); top: 0; bottom: 0; } }
.table__resizer[data-resizing]::after, .table__resizer:focus-visible::after { opacity: 1; background: var(--accent); top: 0; bottom: 0; }
.table__resizer:focus-visible { outline: none; }
/* Mesure « ajuster au contenu » : un instant en disposition auto, sans largeur imposée. */
.table--grid.is-measuring { table-layout: auto !important; width: max-content !important; }
.table--grid.is-measuring th, .table--grid.is-measuring col { width: auto !important; }
/* En-tête groupé sur deux étages (0.47.0) : la 2e ligne colle sous la 1re (hauteur posée
   par le contrôleur), un filet sépare les groupes sur toute la hauteur, et le libellé
   d'un groupe (.table__group-label) reste lisible après les colonnes figées. */
.table-wrap--sticky > .table thead tr + tr th { top: var(--grid-head-row-1, 0px); }
.table.table--grid .is-group-start { background-image: linear-gradient(var(--stroke-weak), var(--stroke-weak));
  background-size: 1px 100%; background-repeat: no-repeat; }
.table--grid th[scope="colgroup"] { overflow: visible; }
.table__group-label { position: sticky; left: calc(var(--grid-frozen-width, 0px) + var(--s-3)); display: inline-block; }
@media (pointer: coarse) { .table__resizer { width: var(--s-4); } }
/* Sur un écran étroit, une colonne figée mangerait la vue : elle redevient normale. */
@media (max-width: 640px) { .table--grid .is-frozen { position: static; } .table-wrap--sticky > .table thead .is-frozen { position: sticky; left: auto !important; } }

/* ===== Tabs ===== (boutons 14/600, sélection = soulignement accent) */
.tabs { display: inline-flex; border-bottom: 1px solid var(--stroke-weak); }
/* Source Astryx (Tab.tsx) : bouton transparent, radius element, texte normal par
   defaut et semibold au selectionne, indicateur 2px accent (ici en box-shadow
   interne pour cohabiter avec le radius). */
.tab { position: relative; border: none; background: none; cursor: pointer; font-family: var(--font-body);
  font-weight: var(--fw-normal); font-size: var(--fs-base); color: var(--text-weak);
  height: var(--size-md); padding: 0 var(--s-3); border-radius: var(--r-element);
  transition: color .12s, background-color .12s; }
.tab:hover { color: var(--text-strong); background: var(--neutral); }
/* Indicateur d'onglet = barre droite de 2px, insérée de spacing-3 de chaque côté
   (sous le texte, pas bord-à-bord), posée sur le rail à bottom:-1px, en fondu.
   Aucun radius : une capsule partiellement rognée par une variante scrollable
   produit une arche et rend deux usages de la même primitive visuellement différents. */
.tab::after { content: ""; position: absolute; bottom: -1px; left: var(--s-3); right: var(--s-3);
  height: 2px; border-radius: 0; background: transparent; transition: background-color .12s; }
.tab[aria-selected="true"] { color: var(--text-strong); font-weight: var(--fw-semibold); }
.tab[aria-selected="true"]::after { background: var(--accent); }
/* onglets pleine largeur scrollables (agence-workspace#63) : le trait reste sur .tabs */
.tabs--fill { display: flex; width: 100%; overflow-x: auto; }
.tabs--fill .tab { flex: none; white-space: nowrap; }

/* ===== Breadcrumbs ===== */
.breadcrumbs { display: flex; align-items: center; gap: var(--s-1-5); font-size: var(--fs-base); color: var(--text-weak); }
.breadcrumbs a { color: var(--text-weak); text-decoration: none; }
.breadcrumbs .sep { color: var(--stroke-strong); }
.breadcrumbs [aria-current="page"] { color: var(--text-strong); font-weight: var(--fw-medium); }

/* ===== List ===== (lignes edge-to-edge, jamais une card par ligne) */
.list { display: flex; flex-direction: column; }
.list__item { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2) var(--s-3); font-size: var(--fs-body); }
/* Ligne sur plusieurs lignes de texte (liste mobile d'un tableau) : 4 px entre les lignes. */
.list__item--stack { flex-direction: column; align-items: stretch; gap: var(--s-1); }
.list--divided .list__item + .list__item { border-top: 1px solid var(--stroke-weak); }
.list__item:hover { background: var(--fill); }
/* variante robuste aux wrappers (agence-workspace#42) : borde les enfants DIRECTS,
   donc traverse un wrapper par item (ex. data-*-target Stimulus), là où le sélecteur
   de sœurs adjacentes .list__item + .list__item casse. */
.list--divided-rows > * + * { border-top: 1px solid var(--stroke-weak); }
/* Ligne d'ajout en fin de liste (« Ajouter une étape ») : pointillés sur les
   quatre côtés, focusable. Sélecteur à 3 classes : bat les bordures des
   variantes --divided et --divided-rows. Un <button> ou un <a>. */
.list .list__item.list__item--ghost { justify-content: center; gap: var(--s-2); margin: var(--s-2);
  border: 1px dashed var(--stroke-strong); border-radius: var(--r-element); background: transparent;
  color: var(--text-weak); font: inherit; font-size: var(--fs-body); text-decoration: none; cursor: pointer; }
.list .list__item.list__item--ghost:hover { background: var(--fill); color: var(--text-strong); border-color: var(--text-weak); }
.list .list__item.list__item--ghost:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ===== Sortable ===== (contrôleur `sortable` : liste verticale réordonnable,
   souris, tactile et clavier par la poignée) */
.sortable__handle { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: var(--size-sm); height: var(--size-sm); padding: 0; border: 0; border-radius: var(--r-element);
  background: transparent; color: var(--text-weak); cursor: grab; touch-action: none; }
.sortable__handle:hover { background: var(--neutral); color: var(--text-strong); }
.sortable__handle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.sortable__handle[aria-pressed="true"] { cursor: grabbing; background: var(--brand-soft); color: var(--text-strong); }
.sortable .is-dragging { position: relative; z-index: 1; background: var(--surface); box-shadow: var(--shadow-raised); }
.sortable.is-sorting, .sortable.is-sorting * { cursor: grabbing; user-select: none; }
.sortable__status { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ===== Collapsible ===== */
.collapsible { border: 1px solid var(--stroke-weak); border-radius: var(--r-container); overflow: hidden; }
.collapsible__trigger { display: flex; align-items: center; justify-content: space-between; width: 100%;
  padding: var(--s-3) var(--s-4); border: none; background: var(--surface); cursor: pointer;
  font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-lg); color: var(--text-strong); }
.collapsible__content { padding: 0 var(--s-4) var(--s-4); font-size: var(--fs-base); color: var(--text-weak); }
.collapsible__chevron { display: inline-flex; color: var(--text-weak); transition: transform .15s; transform: rotate(90deg); }
.collapsible__trigger[aria-expanded="false"] .collapsible__chevron { transform: rotate(0deg); }
/* Section secondaire dans un panneau : déclencheur au corps du texte. */
.collapsible--sm .collapsible__trigger { padding: var(--s-2) var(--s-3); font-size: var(--fs-base); }

/* ===== Skeleton ===== */
.skeleton { background: linear-gradient(90deg, var(--neutral), color-mix(in srgb, var(--neutral) 40%, var(--stroke-weak)), var(--neutral));
  background-size: 200% 100%; border-radius: var(--r-inner); animation: shimmer 1.4s ease-in-out infinite; }
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
/* wrapper de contenu (agence-workspace#42) : enrobe le vrai contenu, le masque et
   shimmer par-dessus → garde le layout EXACT pendant le chargement (vs une forme
   autonome qui approxime les dimensions). */
.skeleton--content { color: transparent !important; }
.skeleton--content * { visibility: hidden; }
/* presets de forme */
.skeleton--line   { height: .9em; border-radius: var(--r-full); }
.skeleton--circle { border-radius: var(--r-full); aspect-ratio: 1; }
.skeleton--card   { border-radius: var(--r-container); }

/* ===== Pagination ===== (boutons 32px, radius 8 ; page courante = navy) */
.pagination { display: inline-flex; align-items: center; gap: var(--s-1); }
.pagination__btn { min-width: var(--size-md); height: var(--size-md); padding: 0 var(--s-2); border: none;
  background: transparent; border-radius: var(--r-element); font-family: var(--font-body); font-weight: var(--fw-medium);
  font-size: var(--fs-base); color: var(--text-strong); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; }
.pagination__btn:hover { background: var(--neutral); }
.pagination__btn[aria-current="page"] { background: var(--primary-bg); color: var(--primary-fg); }

/* ============================================================
   BATCH 13 — data-ops (widgets cross-app restants : app-biz + Roalink)
   ============================================================ */

/* ===== Data-grid éditable inline ===== (tableur : cellule éditable, pulse de save) */
.data-grid { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-sm); }
.data-grid th { text-align: left; font-weight: var(--fw-semibold); color: var(--text-weak); padding: var(--s-2) var(--s-3);
  background: var(--fill); border-bottom: 1px solid var(--stroke-strong); }
.data-grid td { padding: 0; border-bottom: 1px solid var(--stroke-weak); border-right: 1px solid var(--stroke-weak); }
.data-grid tr td:last-child { border-right: none; }
.data-grid__cell { display: block; min-height: var(--size-md); padding: var(--s-2) var(--s-3); cursor: text; color: var(--text-strong); }
.data-grid__cell:focus, .data-grid__cell.is-editing { outline: 2px solid var(--accent); outline-offset: -2px; background: var(--bg); }
.data-grid__cell.is-saved { animation: data-grid-saved .9s ease; }
@keyframes data-grid-saved { 0% { background: color-mix(in srgb, var(--success) 22%, transparent); } 100% { background: transparent; } }

/* ===== Notification center ===== (cloche + badge + liste de rows) */
.notif-bell { position: relative; display: inline-flex; }
.notif-bell__badge { position: absolute; top: -3px; right: -3px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: var(--r-full); background: var(--error); color: var(--on-error); font-size: 10px; font-weight: var(--fw-bold);
  display: inline-flex; align-items: center; justify-content: center; }
.notif-list { display: flex; flex-direction: column; }
.notif-row { display: flex; gap: var(--s-3); padding: var(--s-3); cursor: pointer; border-bottom: 1px solid var(--stroke-weak); }
.notif-row:last-child { border-bottom: none; }
.notif-row:hover { background: var(--fill); }
.notif-row.is-unread { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.notif-row__dot { width: 8px; height: 8px; flex: none; margin-top: 6px; border-radius: var(--r-full); background: var(--accent); }
.notif-row.is-read .notif-row__dot { background: transparent; border: 1px solid var(--stroke-strong); }
.notif-row__content { display: flex; flex-direction: column; gap: 2px; }
.notif-row__title { font-size: var(--fs-sm); color: var(--text-strong); }
.notif-row__time { font-size: var(--fs-xs); color: var(--text-weak); }

/* ===== Bulk-action bar ===== (barre flottante sur sélection multiple ; inversée façon toast) */
.bulkbar { display: inline-flex; align-items: center; gap: var(--s-2); padding: var(--s-1) var(--s-2) var(--s-1) var(--s-4);
  background: var(--text-strong); color: var(--bg); border-radius: var(--r-full); box-shadow: var(--shadow-overlay); }
.bulkbar__count { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
.bulkbar__sep { width: 1px; height: 20px; background: rgba(255,255,255,.22); }
.bulkbar__action { background: none; border: none; color: inherit; font-family: var(--font-body); font-size: var(--fs-sm);
  cursor: pointer; padding: var(--s-1) var(--s-2); border-radius: var(--r-element); }
.bulkbar__action:hover { background: rgba(255,255,255,.14); }
.bulkbar__action--danger { color: #fca5a5; }
/* Désactivé (`disabled` ou aria-disabled="true", que pose la porte data-bulk-max-count
   de `bulk-bar`) : visible, atténué, sans survol. aria-disabled garde le focus, donc
   l'explication en tooltip reste lisible au clavier (contrôleur `tooltip`). */
.bulkbar__action:disabled, .bulkbar__action[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.bulkbar__action:disabled:hover, .bulkbar__action[aria-disabled="true"]:hover { background: none; }
/* dock d'ancrage + entrée/sortie (agence-workspace#55), pilotés par `bulk-bar`.
   z-index 70 = au-dessus du sidenav mobile (60), sous la pile de toasts (80). */
.bulkbar-dock { position: fixed; z-index: 70; left: 0; right: 0;
  bottom: calc(env(safe-area-inset-bottom, 0px) + var(--s-4));
  display: flex; justify-content: center; padding: 0 var(--s-2); pointer-events: none; }
.bulkbar-dock > .bulkbar { pointer-events: auto; max-width: 100%; overflow-x: auto; }
.bulkbar { transition: opacity .2s ease, transform .2s ease; }
.bulkbar.is-hidden { opacity: 0; transform: translateY(8px); pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .bulkbar { transition: none; } }

/* ===== Barre de distribution empilée + légende ===== (mono : accent en paliers d'opacité) */
.distbar { display: flex; height: 10px; border-radius: var(--r-full); overflow: hidden; background: var(--fill); }
.distbar__seg { height: 100%; }
.distbar-legend { display: flex; gap: var(--s-3); flex-wrap: wrap; margin-top: var(--s-2); }
.distbar-legend__item { display: inline-flex; align-items: center; gap: var(--s-1-5); font-size: var(--fs-xs); color: var(--text-weak); }
.distbar-legend__swatch { width: 10px; height: 10px; flex: none; border-radius: 2px; }

/* ===== Import wizard ===== = composition (stepper + fileinput + progress), pas de primitive dédiée */

/* ============================================================
   BATCH 12 — dashboard & flow (widgets cross-app : app-biz + Roalink)
   Le fill de marque utilise --accent (= couleur d'agence swappée)
   ============================================================ */

/* ===== Stat / KPI card ===== (valeur + delta via badge tinté + sous-libellé) */
.stat { display: flex; flex-direction: column; gap: var(--s-1); padding: var(--s-4); min-width: 180px;
  background: var(--surface); border: 1px solid var(--stroke-strong); border-radius: var(--r-container); }
.stat__label { font-size: var(--fs-sm); color: var(--text-weak); }
.stat__value { font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-2xl);
  color: var(--text-strong); line-height: 1.1; }
.stat__sub { font-size: var(--fs-xs); color: var(--text-weak); }
/* KPI en ligne, sans la carte bordée (agence-workspace#42) */
.stat--bare { background: none; border: none; padding: 0; min-width: 0; }

/* ===== Drawer / slide-over ===== (panneau latéral ; séparation par ombre, pas d'accent) */
.drawer { display: flex; flex-direction: column; height: 100%; background: var(--surface); box-shadow: var(--shadow-overlay); }
.drawer__header { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  padding: var(--s-4); border-bottom: 1px solid var(--stroke-weak); }
.drawer__title { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-lg); }
.drawer__body { flex: 1; overflow-y: auto; padding: var(--s-4); }
.drawer__footer { display: flex; justify-content: flex-end; gap: var(--s-2); padding: var(--s-4); border-top: 1px solid var(--stroke-weak); }
/* Coque slide-over pour le contrôleur `drawer` (agence-workspace#43) : overlay
   plein écran + backdrop + panneau qui glisse. Le CSS porte l'animation ; le
   contrôleur ne fait que basculer .is-open. */
.drawer-overlay { --drawer-w: 440px; position: fixed; inset: 0; z-index: var(--z-drawer); display: flex; justify-content: flex-end;
  pointer-events: none; visibility: hidden; }
.drawer-overlay.is-open { pointer-events: auto; visibility: visible; }
.drawer-overlay__backdrop { position: absolute; inset: 0; background: var(--scrim); opacity: 0; transition: opacity .2s ease; }
.drawer-overlay.is-open .drawer-overlay__backdrop { opacity: 1; }
.drawer-overlay > .drawer { position: relative; z-index: 1; width: min(var(--drawer-w), 100%); transform: translateX(100%); transition: transform var(--dur-base) ease; }
.drawer-overlay.is-open > .drawer { transform: translateX(0); }
.drawer-overlay--left { justify-content: flex-start; }
.drawer-overlay--left > .drawer { transform: translateX(-100%); }
/* Largeur pilotée par variable (agence-workspace#60) : la coque expose les crans (mesurés
   sur les 14 tiroirs app-biz : sm 600 formulaire, md 800, lg 1000, xl 90vw) ; l'app choisit. */
.drawer-overlay--sm { --drawer-w: 600px; }
.drawer-overlay--md { --drawer-w: 800px; }
.drawer-overlay--lg { --drawer-w: 1000px; }
.drawer-overlay--xl { --drawer-w: 90vw; }
@media (max-width: 639px) { .drawer-overlay > .drawer { width: 100%; } } /* sous le cran mobile : plein écran */
body.is-drawer-open { overflow: hidden; }
@media (prefers-reduced-motion: reduce) { .drawer-overlay__backdrop, .drawer-overlay > .drawer { transition: none; } }

/* ===== Timeline / activité ===== (feed vertical, pastille + connecteur) */
.timeline { display: flex; flex-direction: column; }
.timeline__item { display: flex; gap: var(--s-3); position: relative; padding-bottom: var(--s-4); }
.timeline__item::before { content: ""; position: absolute; left: 11px; top: 24px; bottom: 0; width: 1px; background: var(--stroke-weak); }
.timeline__item:last-child::before { display: none; }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__dot { width: 24px; height: 24px; flex: none; z-index: 1; border-radius: var(--r-full);
  background: var(--fill); border: 1px solid var(--stroke-strong); color: var(--text-weak);
  display: inline-flex; align-items: center; justify-content: center; }
.timeline__content { display: flex; flex-direction: column; gap: 2px; padding-top: 2px; }
.timeline__title { font-size: var(--fs-base); color: var(--text-strong); }
.timeline__time { font-size: var(--fs-xs); color: var(--text-weak); }

/* ===== Stepper / wizard ===== (étapes : done = accent, current = contour accent) */
.stepper { display: flex; align-items: center; gap: var(--s-2); }
.stepper__step { display: inline-flex; align-items: center; gap: var(--s-2); }
.stepper__dot { width: 24px; height: 24px; flex: none; border-radius: var(--r-full);
  display: inline-flex; align-items: center; justify-content: center; font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  border: 1px solid var(--stroke-strong); color: var(--text-weak); background: var(--bg); }
.stepper__label { font-size: var(--fs-sm); color: var(--text-weak); }
.stepper__line { flex: 1; min-width: 20px; height: 1px; background: var(--stroke-weak); }
.stepper__step.is-done .stepper__dot { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.stepper__step.is-current .stepper__dot { border-color: var(--accent); color: var(--accent); }
.stepper__step.is-current .stepper__label { color: var(--text-strong); font-weight: var(--fw-semibold); }
.stepper__step.is-done + .stepper__line { background: var(--accent); }

/* ===== Kanban / pipeline ===== (colonnes scroll-x + cartes ; le DS fournit la coque, le DnD reste JS) */
/* Coque PARTAGÉE (agence-workspace#40). Le métier (contenu des cartes, stages,
   drag-drop, filtres) reste dans chaque app ; ici uniquement le visuel. */
.kanban { --kanban-col-width: 288px; /* configurable par l'app (app-biz w-72/80) */
  display: flex; gap: var(--s-3); overflow-x: auto; align-items: flex-start; padding-bottom: var(--s-2); }
.kanban__col { flex: none; width: var(--kanban-col-width); display: flex; flex-direction: column; gap: var(--s-2);
  background: var(--fill); border-radius: var(--r-container); padding: var(--s-2); }
/* colonne d'état terminal (Gagné/Perdu) : atténuée + bordure pointillée */
.kanban__col--terminal { background: transparent; border: 1px dashed var(--stroke-strong); }
.kanban__col-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  padding: var(--s-1) var(--s-2); font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-strong); }
.kanban__count { font-size: var(--fs-xs); color: var(--text-weak); background: var(--surface); border-radius: var(--r-full); padding: 0 var(--s-1-5); }
/* slot dédié pour un total agrégé en tête de colonne (ex. Σ ARR) */
.kanban__col-total { padding: 0 var(--s-2) var(--s-1); font-size: var(--fs-xs); font-weight: var(--fw-medium);
  color: var(--text-weak); font-variant-numeric: tabular-nums; }
.kanban__card { background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-element);
  padding: var(--s-3); font-size: var(--fs-sm); color: var(--text-strong); cursor: grab; box-shadow: var(--shadow-raised); }
/* états pilotés par le contrôleur Stimulus partagé (gem-owned, pas de Tailwind) */
.kanban__card.is-dragging { opacity: .5; }
.kanban__col.is-drop-target { box-shadow: inset 0 0 0 2px var(--accent); }
.kanban__card-title { font-weight: var(--fw-semibold); }
.kanban__card-meta { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); margin-top: var(--s-2); }
.kanban__card-tags { display: flex; gap: var(--s-1); flex-wrap: wrap; margin-top: var(--s-2); }
/* navigation horizontale : la gem fournit les classes, le scroll reste JS côté app */
.kanban__nav, .table-grid__nav { display: inline-flex; gap: var(--s-1); }
.kanban__nav-btn, .table-grid__nav-btn { width: var(--size-sm); height: var(--size-sm); flex: none; display: inline-flex; align-items: center;
  justify-content: center; border: 1px solid var(--stroke-strong); border-radius: var(--r-element);
  background: var(--surface); color: var(--text-weak); cursor: pointer; }
.kanban__nav-btn:hover, .table-grid__nav-btn:hover { background: var(--neutral); color: var(--text-strong); }
.kanban__nav-btn:disabled, .table-grid__nav-btn[aria-disabled="true"] { opacity: .4; cursor: not-allowed; }
.table-grid__nav-btn[aria-disabled="true"]:hover { background: var(--surface); color: var(--text-weak); }
.table-grid__nav-btn[hidden] { display: none; }

/* ===== Resizable ===== (panneau redimensionnable ; parité Astryx `ResizeHandle` — agence-workspace).
   Poignée = filet 1px (::before = préhension 16px, ::after = pastille 3×32). Piloté par `resizable`. */
.resizable { display: flex; }
.resizable--vertical { flex-direction: column; }
.resizable__panel { flex: none; width: var(--resizable-size, 260px); min-width: 0; overflow: auto; }
.resizable--vertical .resizable__panel { width: auto; height: var(--resizable-size, 260px); }
.resizable__main { flex: 1; min-width: 0; overflow: auto; }
.resizable__handle { position: relative; flex: none; align-self: stretch; width: 1px; background: var(--stroke-weak); cursor: col-resize; touch-action: none; }
.resizable--vertical .resizable__handle { align-self: auto; width: auto; height: 1px; cursor: row-resize; }
.resizable__handle::before { content: ""; position: absolute; inset-block: 0; inset-inline-start: 50%; transform: translateX(-50%); width: var(--s-4); }
.resizable--vertical .resizable__handle::before { inset-block: auto; inset-inline: 0; top: 50%; transform: translateY(-50%); width: auto; height: var(--s-4); }
.resizable__handle::after { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 3px; height: var(--s-8); border-radius: var(--r-full); background: var(--stroke-strong); opacity: 0; transition: opacity .12s; }
.resizable--vertical .resizable__handle::after { width: var(--s-8); height: 3px; }
@media (hover: hover) { .resizable__handle:hover::after { opacity: 1; } }
.resizable__handle[data-resizing] { background: var(--stroke-strong); }
.resizable__handle[data-resizing]::after { opacity: 1; }
.resizable__handle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (pointer: coarse) { .resizable__handle::before { width: var(--s-6); } }
body.is-resizing, body.is-resizing * { cursor: col-resize !important; user-select: none; }

/* ===== Outline ===== (sommaire / table des matières avec scroll-spy ; parité Astryx `Outline`).
   Rail 2px + barre active glissante (indicateur de NAV, positionné en JS ; pas une bordure d'accent
   de carte). Piloté par `outline`. */
.outline { display: flex; gap: var(--s-0-5); position: relative; }
.outline__track { position: relative; order: -1; flex: none; width: 2px; background: var(--stroke-weak); border-radius: var(--r-full); }
.outline__indicator { position: absolute; inset-inline-start: 0; width: 2px; top: 0; height: 0; background: var(--accent);
  border-radius: var(--r-full); transition: top .13s var(--ease-out), height .13s var(--ease-out); pointer-events: none; }
.outline__list { display: flex; flex-direction: column; gap: var(--s-0-5); margin: 0; padding: 0; list-style: none; flex: 1; min-width: 0; }
.outline__link { display: block; padding: var(--s-1-5) var(--s-3); border-radius: var(--r-element); color: var(--text-weak);
  text-decoration: none; font-size: var(--fs-base); line-height: 1.4; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: background-color .12s, color .12s; }
@media (hover: hover) { .outline__link:hover { background: var(--neutral); color: var(--text-strong); } }
.outline__link.is-active { color: var(--text-strong); font-weight: var(--fw-semibold); }
.outline__link:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.outline--compact .outline__link { padding: var(--s-1) var(--s-2); font-size: var(--fs-sm); }
@media (prefers-reduced-motion: reduce) { .outline__indicator { transition: none; } }

/* ===== Grouped table ===== (lignes groupées par statut, header colspan collapsible) */
.table tr.table__group > td { background: var(--fill); cursor: pointer; padding: var(--s-2) var(--s-3); }
.table__group-toggle { display: flex; align-items: center; gap: var(--s-2); font-weight: var(--fw-semibold); color: var(--text-strong); }
.table__group-chevron { display: inline-flex; color: var(--text-weak); transition: transform .15s; transform: rotate(90deg); }
.table__group.is-collapsed .table__group-chevron { transform: rotate(0deg); }
.table__group-count { margin-left: var(--s-1); font-size: var(--fs-xs); color: var(--text-weak); }

/* ===== Funnel ===== (entonnoir : barres décroissantes, fill accent) */
.funnel { display: flex; flex-direction: column; gap: var(--s-1); }
.funnel__row { display: flex; align-items: center; gap: var(--s-3); }
.funnel__meta { flex: none; width: 90px; font-size: var(--fs-xs); color: var(--text-weak); }
.funnel__bar { height: 32px; min-width: 44px; border-radius: var(--r-element); background: var(--accent);
  color: var(--on-accent); display: flex; align-items: center; padding: 0 var(--s-3); font-size: var(--fs-sm); font-weight: var(--fw-semibold); }

/* ===== Chart · cadre ===== (le DS fournit carte + légende + axes/couleurs ; le tracé reste Chartkick/Chart.js) */
.chart { display: flex; flex-direction: column; gap: var(--s-3); padding: var(--s-4);
  background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container); }
.chart .table-wrap { border: none; border-radius: 0; }
.chart__head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); flex-wrap: wrap; }
.chart__title { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-base); }
.chart__legend { display: flex; gap: var(--s-3); flex-wrap: wrap; }
.chart__legend-item { display: inline-flex; align-items: center; gap: var(--s-1-5); font-size: var(--fs-xs); color: var(--text-weak); }
.chart__swatch { width: 10px; height: 10px; flex: none; border-radius: 2px; }
.chart__plot { height: 160px; display: flex; align-items: flex-end; gap: var(--s-2); }
.chart__bar { flex: 1; min-height: 4px; background: var(--accent); border-radius: var(--r-inner) var(--r-inner) 0 0; }

/* ===== Chart · tracé SVG ===== (graphique dessiné côté serveur, sans JS).
   Séries : trois teintes à 3:1 au moins sur la surface, claire comme sombre
   (WCAG 1.4.11), plus une teinte atténuée pour la période de comparaison.
   L'app génère le <svg class="chart__svg"> et pose les classes ci-dessous ;
   valeurs et mois restent écrits en texte, jamais portés par la couleur seule. */
:root { --chart-1: #2a78d6; --chart-2: #6f6e69; --chart-3: #0f6e56; --chart-muted: #b4b2a9; }
[data-theme="dark"] { --chart-1: #3987e5; --chart-2: #a3a29b; --chart-3: #1d9e75; --chart-muted: #5f5e5a; }
.chart--bare { padding: 0; background: none; border: none; }
.chart__svg { display: block; width: 100%; height: auto; overflow: visible; font-family: var(--font-body); }
.chart__svg text { fill: var(--text-weak); font-size: 13px; }
.chart__svg .chart__axis-label--current { fill: var(--text-strong); font-weight: var(--fw-semibold); }
.chart__svg .chart__value { fill: var(--text-strong); font-weight: var(--fw-semibold); }
.chart__svg .chart__icon { fill: none; stroke: var(--text-strong); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart__svg .chart__grid { stroke: var(--stroke-weak); }
.chart__svg .chart__band { fill: var(--neutral); }
.chart__series-1 { fill: var(--chart-1); stroke: var(--chart-1); background: var(--chart-1); }
.chart__series-2 { fill: var(--chart-2); stroke: var(--chart-2); background: var(--chart-2); }
.chart__series-3 { fill: var(--chart-3); stroke: var(--chart-3); background: var(--chart-3); }
.chart__series-muted { fill: var(--chart-muted); stroke: var(--chart-muted); background: var(--chart-muted); }
.chart__svg rect[class^="chart__series"] { stroke: none; }
.chart__svg .chart__line { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.chart__svg .chart__line--muted { stroke-dasharray: 6 4; stroke-width: 2; }
.chart__svg .chart__dot { stroke: var(--surface); stroke-width: 2; }
.chart__svg .chart__marker { fill: var(--text-strong); }
.chart__svg .chart__marker-num { fill: var(--surface); font-size: 12px; font-weight: var(--fw-semibold); }
.chart__legend--lg .chart__legend-item { font-size: var(--fs-base); }
.chart__legend-value { color: var(--text-strong); font-weight: var(--fw-semibold); }
/* Annotations : liste numérotée sous le tracé, reprise des pastilles. */
.chart__notes { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-1-5); font-size: var(--fs-base); }
.chart__note { display: flex; gap: var(--s-2); align-items: baseline; color: var(--text-strong); }
.chart__note-num { flex: none; width: 22px; height: 22px; border-radius: var(--r-full); background: var(--text-strong);
  color: var(--surface); display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold); }
/* Répartition d'un total en une barre (parts écrites dans la barre). */
.chart__split { display: flex; height: 36px; gap: 2px; border-radius: var(--r-inner); overflow: hidden; }
/* Texte en grand gras (19 px, 700) : texte large au sens WCAG, 3:1 suffit sur les trois séries. */
.chart__split-part { display: flex; align-items: center; justify-content: center; min-width: 32px;
  color: var(--surface); font-size: 19px; font-weight: 700; }
/* Mini-courbe décorative d'une tuile : la valeur est toujours écrite à côté. */
.sparkline { display: block; width: 100%; height: 26px; }
.sparkline polyline { fill: none; stroke: var(--chart-1); stroke-width: 1.5; stroke-linejoin: round; }
.sparkline circle { fill: var(--chart-1); }

/* Tuile de chiffre qui pilote un graphique : lien ou bouton, état aria-current. */
.stat--selectable { min-width: 0; background: var(--neutral); border-color: transparent; color: inherit;
  text-align: left; text-decoration: none; font: inherit; cursor: pointer; transition: border-color .12s, background-color .12s; }
.stat--selectable:hover { border-color: var(--stroke-strong); }
.stat--selectable[aria-current="true"] { background: var(--surface); border-color: var(--text-strong); }
.stat__delta { display: inline-flex; align-items: center; gap: var(--s-1); font-size: var(--fs-base); color: var(--text-weak); }

/* ===== Toolbar ===== (barre d'actions/filtres au-dessus des tables) */
.toolbar { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.toolbar__group { display: flex; align-items: center; gap: var(--s-2); }
.toolbar__spacer { flex: 1; }

/* ===== FilterPill ===== (agence-workspace#42 : pastille de filtre + compteur ; ouvre
   un .menu / .menu__item déjà fournis par la gem. État actif = inversé façon .btn--toggle) */
.filter-pill { display: inline-flex; align-items: center; gap: var(--s-1-5); height: var(--size-md); white-space: nowrap;
  padding: 0 var(--s-3); border: 1px solid var(--stroke-strong); border-radius: var(--r-full);
  background: var(--surface); color: var(--text-strong); font-family: var(--font-body);
  font-weight: var(--fw-medium); font-size: var(--fs-base); cursor: pointer;
  transition: background-color .12s, border-color .12s; }
.filter-pill:hover { background: var(--neutral); }
.filter-pill.is-active { background: var(--text-strong); border-color: var(--text-strong); color: var(--bg); }
.filter-pill svg { width: 16px; height: 16px; flex: none; color: var(--text-weak); }
.filter-pill.is-active svg { color: inherit; }
/* Déclencheur en <summary> (#82) : repli natif accessible, zéro JS. Reset du marqueur natif
   (même patron que .disclosure) + état OUVERT (sinon un menu déplié ne se distingue pas d'un
   fermé). Distinct de .is-active (valeur retenue, inversé) : [open] = simplement déplié. */
summary.filter-pill { list-style: none; }
summary.filter-pill::-webkit-details-marker { display: none; }
details[open] > .filter-pill { background: var(--neutral); border-color: var(--text-strong); }
.filter-pill__count { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px;
  padding: 0 var(--s-1); border-radius: var(--r-full); background: var(--accent); color: var(--on-accent);
  font-size: var(--fs-2xs); font-weight: var(--fw-semibold); }
/* sur pill active (fond navy), le compteur s'inverse pour rester lisible */
.filter-pill.is-active .filter-pill__count { background: var(--bg); color: var(--text-strong); }
/* PriorityPlus (contrôleur `priority-plus`) : une ligne sans barre de défilement, le surplus
   passe dans un menu « + N ». Prend la place restante de sa rangée ; overflow-x: clip ne coupe
   ni les menus ancrés (position fixed) ni l'anneau de focus vertical. */
.priority-plus { display: flex; align-items: center; gap: var(--s-2); flex: 1 1 0; min-width: 0; overflow-x: clip; }
.priority-plus > * { flex: none; min-width: 0; }
/* Élément épinglé rétréci par le contrôleur : la pastille cède, son libellé passe en ellipse. */
.priority-plus .filter-pill { min-width: 0; max-width: 100%; }
.priority-plus .filter-pill > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ===== DateRangeInput ===== (plage : début → fin + presets) */
.daterange { display: flex; flex-direction: column; gap: var(--s-1-5); }
.daterange__field { display: inline-flex; align-items: center; gap: var(--s-2); width: 100%; height: var(--size-md);
  padding: 0 var(--s-3); border: 1px solid var(--stroke-strong); border-radius: var(--r-element);
  background: var(--bg); font-size: var(--fs-base); color: var(--text-strong); cursor: pointer; }
.daterange__field.is-open, .daterange__field:focus-within { border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--ring); }
.daterange__field svg { color: var(--text-weak); margin-left: auto; }
.daterange__sep { color: var(--text-weak); }
.daterange__presets { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.daterange__preset { padding: var(--s-0-5) var(--s-2); border-radius: var(--r-inner); font-size: var(--fs-xs);
  color: var(--text-weak); background: var(--fill); cursor: pointer; border: none; }
.daterange__preset.is-active { background: var(--accent); color: var(--on-accent); }

/* ===== ClickableCard / SelectableCard ===== (carte interactive / sélectionnable) */
.card--clickable { cursor: pointer; transition: border-color var(--dur-fast), box-shadow var(--dur-fast), transform var(--dur-fast); }
.card--clickable:active { transform: translateY(1px); }
.card--clickable:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.card--selectable { cursor: pointer; position: relative; }
.card--selectable.is-selected { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
@media (hover: hover) {
  .card--clickable:hover { border-color: var(--text-weak); box-shadow: var(--shadow-raised); }
}

/* ===== MultiSelector ===== (multi-sélection : trigger + menu à cases) */
.multiselect { display: flex; flex-direction: column; gap: var(--s-1-5); }
.multiselect__trigger { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); width: 100%;
  min-height: var(--size-md); padding: var(--s-1) var(--s-2) var(--s-1) var(--s-3); border: 1px solid var(--stroke-strong);
  border-radius: var(--r-element); background: var(--bg); font-size: var(--fs-base); color: var(--text-strong); cursor: pointer; }
.multiselect__trigger.is-open { border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--ring); }
.multiselect__trigger svg { color: var(--text-weak); flex: none; }
.multiselect__values { display: flex; gap: var(--s-1); flex-wrap: wrap; align-items: center; }
.multiselect__placeholder { color: var(--text-weak); }

/* ===== Combobox ===== (agence-workspace#54 : sélection unique cherchable ; champ
   = .input, liste flottante d'options. Piloté par le contrôleur `combobox`.
   Valeurs alignées sur le Selector Astryx mesuré : dropdown radius-element (8) +
   bordure border-emphasized (--stroke-strong) + maxHeight 300 + padding s-1 ;
   item padding s-2 (8 partout) + gap s-2 + radius-element. */
.combobox { position: relative; }
.combobox__list { position: absolute; top: calc(100% + var(--s-1)); left: 0; right: 0; z-index: var(--z-dropdown);
  background: var(--surface); border: 1px solid var(--stroke-strong); border-radius: var(--r-element);
  box-shadow: var(--shadow-overlay); padding: var(--s-1); max-height: 300px; overflow-y: auto; }
.combobox__option { display: flex; align-items: center; gap: var(--s-2); width: 100%; padding: var(--s-2);
  border: none; background: none; text-align: left; border-radius: var(--r-element);
  font-family: var(--font-body); font-size: var(--fs-base); color: var(--text-strong); cursor: pointer; }
.combobox__option[hidden] { display: none; }
.combobox__option:hover, .combobox__option.is-active { background: var(--neutral); }
.combobox__empty { padding: var(--s-3); text-align: center; color: var(--text-weak); font-size: var(--fs-sm); }

/* ===== AlertDialog ===== .dialog--alert est défini dans le bloc Dialog (agence-workspace#59). */

/* ===== Thumbnail ===== (vignette carrée, radius 8) */
.thumbnail { width: 48px; height: 48px; border-radius: var(--r-element); object-fit: cover;
  border: 1px solid var(--stroke-weak); background: var(--fill); }

/* ===== AspectRatio ===== (utilitaire de ratio) */
.aspect { position: relative; width: 100%; aspect-ratio: 16 / 9; border-radius: var(--r-container); overflow: hidden; }
.aspect > * { position: absolute; inset: 0; width: 100%; height: 100%; }

/* ===== Tokenizer ===== (saisie multi-tags) */
.tokenizer { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: center; min-height: var(--size-md);
  padding: var(--s-1) var(--s-2); border: 1px solid var(--stroke-strong); border-radius: var(--r-element); background: var(--bg); }
.tokenizer:focus-within { border-color: var(--focus); box-shadow: inset 0 0 0 2px var(--ring); }
.tokenizer input { border: none; outline: none; background: none; flex: 1; min-width: 70px; height: 22px;
  font-family: var(--font-body); font-size: var(--fs-base); color: var(--text-strong); }
.token__x { display: inline-flex; cursor: pointer; opacity: .55; margin-left: 2px; }
.token__x:hover { opacity: 1; }

/* ===== Typeahead ===== réutilise .input + .menu (dropdown de suggestions) ===== */

/* ===== Lightbox ===== (overlay plein écran ; ici en aperçu encadré) */
.lightbox { position: relative; display: flex; align-items: center; justify-content: center; background: rgba(1,28,37,.92);
  border-radius: var(--r-container); overflow: hidden; }
.lightbox__img { max-width: 72%; max-height: 78%; border-radius: var(--r-element); box-shadow: var(--shadow-overlay); }
.lightbox__close { position: absolute; top: 12px; right: 12px; width: 32px; height: 32px; border-radius: var(--r-full);
  border: none; background: rgba(255,255,255,.16); color: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
.lightbox__nav { position: absolute; top: 50%; transform: translateY(-50%); width: 36px; height: 36px; border-radius: var(--r-full);
  border: none; background: rgba(255,255,255,.16); color: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; font-size: 18px; }
.lightbox__nav--prev { left: 12px; } .lightbox__nav--next { right: 12px; }

/* ===== TreeList ===== (arbo : items 32px, indentation par niveau) */
.tree { display: flex; flex-direction: column; }
.tree__item { display: flex; align-items: center; gap: var(--s-1-5); height: var(--size-md); padding: 0 var(--s-2);
  border-radius: var(--r-element); font-size: var(--fs-base); color: var(--text-strong); cursor: pointer; }
.tree__item:hover { background: var(--neutral); }
.tree__chevron { width: 16px; height: 16px; color: var(--text-weak); flex: none; display: inline-flex; }
.tree__item.is-open > .tree__chevron { transform: rotate(90deg); }
.tree__children { display: flex; flex-direction: column; }
.tree__children .tree__item { padding-left: var(--s-6); }

/* ===== OverflowList ===== (row + indicateur +N) */
.overflow-list { display: flex; align-items: center; gap: var(--s-2); flex-wrap: nowrap; overflow: hidden; }

/* ===== ContextMenu (clic droit) / MoreMenu (…) : réutilisent .menu + .icon-btn ===== */

/* ===== CodeBlock ===== (header langue + corps mono, radius 12) */
.codeblock { border: 1px solid var(--stroke-weak); border-radius: var(--r-container); overflow: hidden; background: var(--fill); }
.codeblock__head { display: flex; align-items: center; justify-content: space-between; padding: var(--s-1-5) var(--s-3);
  border-bottom: 1px solid var(--stroke-weak); font-size: var(--fs-xs); color: var(--text-weak); }
.codeblock__body { margin: 0; padding: var(--s-3); overflow-x: auto; color: var(--text-strong);
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: var(--fs-sm); line-height: 1.6; }

/* ===== Blockquote ===== (italique + guillemet, sans bordure latérale colorée) */
.blockquote { position: relative; margin: 0; padding-left: var(--s-6); font-style: italic; color: var(--text-weak);
  font-size: var(--fs-lg); line-height: 1.5; }
.blockquote::before { content: "\201C"; position: absolute; left: 0; top: -2px; font-family: var(--font-heading);
  font-size: 32px; line-height: 1; color: var(--stroke-strong); }

/* ===== Citation ===== (chip source : radius 8, bordure 1px, 12px) */
.citation { display: inline-flex; align-items: center; gap: var(--s-1); height: 20px; padding: 0 var(--s-2);
  border: 1px solid var(--stroke-weak); border-radius: var(--r-element); font-size: var(--fs-xs); color: var(--text-weak); text-decoration: none; }
.citation__num { display: inline-flex; align-items: center; justify-content: center; min-width: 14px; height: 14px;
  border-radius: var(--r-full); background: var(--neutral); font-size: 10px; font-weight: var(--fw-semibold); }

/* ===== Markdown / prose ===== */
.markdown { font-size: var(--fs-body); line-height: 1.6; color: var(--text-strong); }
.markdown h1, .markdown h2, .markdown h3 { font-family: var(--font-heading); margin: var(--s-4) 0 var(--s-2); }
.markdown h3 { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.markdown p { margin: 0 0 var(--s-3); }
.markdown ul, .markdown ol { margin: 0 0 var(--s-3); padding-left: var(--s-5); }
/* Un reset type preflight (list-style: none) effaçait puces et numéros. */
.markdown ul { list-style: disc; }
.markdown ol { list-style: decimal; }
.markdown li { margin-bottom: var(--s-1); }
.markdown li > ul, .markdown li > ol { margin: var(--s-1) 0 0; }
.markdown h4 { font-size: var(--fs-base); font-weight: var(--fw-semibold); margin: var(--s-3) 0 var(--s-1); }
.markdown hr { border: 0; border-top: 1px solid var(--stroke-weak); margin: var(--s-3) 0; }
.markdown table { display: block; max-width: 100%; overflow-x: auto; border-collapse: collapse; margin: 0 0 var(--s-3);
  font-size: var(--fs-sm); }
.markdown th, .markdown td { border: 1px solid var(--stroke-weak); padding: var(--s-1) var(--s-2); text-align: left;
  vertical-align: top; }
.markdown th { background: var(--fill); font-weight: var(--fw-semibold); }
.markdown > :last-child { margin-bottom: 0; }
.markdown strong { font-weight: var(--fw-semibold); }
.markdown a { color: var(--text-strong); text-decoration: underline; text-underline-offset: 2px; }
.markdown code { font-family: ui-monospace, Menlo, monospace; font-size: .875em; background: var(--fill);
  border: 1px solid var(--stroke-weak); border-radius: var(--r-inner); padding: 1px 4px; }
/* densité fil de discussion + inversion sur fond accent (agence-workspace#63) */
.markdown--sm { font-size: var(--fs-base); line-height: 1.5; }
.markdown--sm p, .markdown--sm ul, .markdown--sm ol, .markdown--sm table { margin-bottom: var(--s-2); }
.markdown--sm h1, .markdown--sm h2, .markdown--sm h3 { font-size: var(--fs-base); margin: var(--s-3) 0 var(--s-1); }
.markdown--invert, .markdown--invert :where(p, li, strong, em, h1, h2, h3, a) { color: var(--on-accent); }
.markdown--invert code { background: color-mix(in srgb, var(--on-accent) 15%, transparent);
  border-color: color-mix(in srgb, var(--on-accent) 30%, transparent); }

/* ===== Table riche : tri, sélection, action ===== */
.th-sort { display: inline-flex; align-items: center; gap: 4px; border: none; background: none; cursor: pointer;
  color: var(--text-weak); font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: inherit; }
.th-sort .arr { font-size: 9px; }
/* Tri serveur : .th-sort sur un lien, icône SVG (pas de glyphe), état lu sur th[aria-sort]. */
a.th-sort { text-decoration: none; }
.th-sort:hover, th[aria-sort="ascending"] .th-sort, th[aria-sort="descending"] .th-sort { color: var(--text-strong); }
.th-sort svg { width: 14px; height: 14px; flex: none; opacity: .55; }
th[aria-sort="ascending"] .th-sort svg, th[aria-sort="descending"] .th-sort svg { opacity: 1; }
.th-sort:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-inner); }
/* Flèche pilotée par data-dir (agence-workspace#62) : le contrôleur `table-sort` ne fait
   que poser l'attribut ; le CSS porte le glyphe (mécanique pure côté JS). */
.th-sort .arr[data-dir="asc"]::after  { content: "↑"; }
.th-sort .arr[data-dir="desc"]::after { content: "↓"; }
.icon-btn { width: 28px; height: 28px; border-radius: var(--r-element); border: none; background: none; cursor: pointer;
  color: var(--text-weak); display: inline-flex; align-items: center; justify-content: center; }
@media (hover: hover) { .icon-btn:hover:not(:disabled) { background: var(--neutral); color: var(--text-strong); } }
/* variantes (agence-workspace#63) : tailles, bordure, désactivé, focus, tactile 44px */
.icon-btn--sm { width: var(--size-sm); height: var(--size-sm); }
.icon-btn--md { width: var(--size-md); height: var(--size-md); }
.icon-btn--lg { width: var(--size-lg); height: var(--size-lg); }
.icon-btn--outline { border: 1px solid var(--stroke-weak); background: var(--surface); }
.icon-btn:disabled { opacity: .4; cursor: not-allowed; }
.icon-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
@media (pointer: coarse) { .icon-btn { min-width: 44px; min-height: 44px; } }
.table tbody tr.is-selected { --row-fill: var(--neutral); background: var(--row-fill); }

/* ===== Link ===== (mono : navy souligné) */
.link { color: var(--text-strong); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; cursor: pointer; }
.link:hover { opacity: .7; }

/* ===== Timestamp ===== */
.timestamp { color: var(--text-weak); font-size: var(--fs-base); }

/* ===== Clipboard ===== (agence-workspace#46 : swap de glyphe copy→check piloté
   CSS, sans lib d'icônes. L'host fournit ses 2 glyphes ; .is-copied est posé par
   le contrôleur `clipboard` le temps du feedback. Marche même icon-only.) */
.clipboard__done { display: none; }
.is-copied .clipboard__idle { display: none; }
.is-copied .clipboard__done { display: inline-flex; }

/* ===== Kbd ===== (touches clavier) */
.kbd { display: inline-flex; gap: 3px; }
.kbd .key { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: var(--r-inner); background: var(--fill); border: 1px solid var(--stroke-weak); border-bottom-width: 2px;
  font-family: var(--font-body); font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--text-weak); }

/* ===== FileInput ===== (choisir un fichier / glisser-déposer, bordure pointillée) */
.fileinput { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-3) var(--s-4);
  border: 1.5px dashed var(--stroke-strong); border-radius: var(--r-container); background: var(--fill);
  font-size: var(--fs-sm); color: var(--text-weak); }

/* ===== HoverCard ===== (survol : radius 12 + shadow overlay) */
.hovercard { background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container);
  box-shadow: var(--shadow-overlay); padding: var(--s-4); max-width: 300px; }

/* ===== CommandPalette ===== (dialog central : recherche + liste + footer) */
.cmdk { width: 480px; max-width: 100%; background: var(--surface); border: 1px solid var(--stroke-weak);
  border-radius: var(--r-container); box-shadow: var(--shadow-overlay); overflow: hidden; }
.cmdk__input { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--stroke-weak); color: var(--text-weak); }
.cmdk__input input { flex: 1; border: none; outline: none; background: none; font-family: var(--font-body); font-size: var(--fs-lg); color: var(--text-strong); }
/* état de chargement (agence-workspace#53) : spinner à droite de l'input, basculé
   par le contrôleur via .cmdk.is-loading (setLoading). Réutilise @keyframes spin. */
.cmdk.is-loading .cmdk__input::after { content: ""; flex: none; width: 14px; height: 14px;
  border: 2px solid var(--neutral); border-top-color: var(--accent); border-radius: var(--r-full);
  animation: spin .7s linear infinite; }
/* Valeurs relevées dans la source Astryx (CommandPalette*.tsx) : liste en colonne
   gap 2, item radius-inner (4) padding 8/12, groupe padding 4/12. L'item
   sélectionné prend un fond accent-muted (teinté), distinct du hover neutre. */
.cmdk__list { display: flex; flex-direction: column; gap: var(--s-0-5); padding: var(--s-1); max-height: 280px; overflow: auto; }
.cmdk__group { font-size: var(--fs-xs); color: var(--text-weak); text-transform: uppercase; letter-spacing: .06em; padding: var(--s-1) var(--s-3); }
.cmdk__item { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3); border-radius: var(--r-inner); font-size: var(--fs-base); color: var(--text-strong); cursor: pointer; }
.cmdk__item:hover { background: var(--neutral); }
.cmdk__item.is-active { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.cmdk__item.is-disabled { opacity: .4; cursor: not-allowed; }
.cmdk__item.is-disabled:hover { background: none; }
.cmdk__item .sp { margin-left: auto; }
.cmdk__empty { padding: var(--s-8) var(--s-4); text-align: center; color: var(--text-weak); font-size: var(--fs-sm); }
.cmdk__footer { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2) var(--s-4); border-top: 1px solid var(--stroke-weak); font-size: var(--fs-xs); color: var(--text-weak); }

/* ===== Calendar ===== (jours 28px ronds, 14px ; sélection = accent) */
.calendar { display: inline-flex; flex-direction: column; gap: var(--s-2); padding: var(--s-3);
  background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container); }
.calendar__head { display: flex; align-items: center; justify-content: space-between; padding: 0 var(--s-1);
  font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-base); }
.calendar__grid { display: grid; grid-template-columns: repeat(7, 28px); gap: 2px; }
.calendar__dow { width: 28px; text-align: center; font-size: var(--fs-xs); color: var(--text-weak); }
.calendar__day { width: 28px; height: 28px; border: none; background: none; border-radius: var(--r-full);
  font-family: var(--font-body); font-size: var(--fs-base); color: var(--text-strong); cursor: pointer; }
.calendar__day:hover { background: var(--neutral); }
.calendar__day.is-selected { background: var(--accent); color: var(--on-accent); }

/* ===== Chat ===== (radius 28 ; bulle user = neutral, assistant = ghost ; composer pill) */
.chat { display: flex; flex-direction: column; gap: var(--s-3); }
.chat__msg { display: flex; }
.chat__msg--user { justify-content: flex-end; }
.chat__bubble { border-radius: var(--r-page); font-size: var(--fs-body); line-height: 1.5; color: var(--text-strong); }
.chat__bubble--user { max-width: 85%; background: var(--neutral); padding: var(--s-2) var(--s-4); }
.chat__bubble--assistant { max-width: 100%; background: transparent; padding: var(--s-1) 0; }
.chat__meta { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-xs); color: var(--text-weak); margin-top: 2px; }
.chat__composer { display: flex; align-items: center; gap: var(--s-2); background: var(--bg);
  border: 1px solid var(--stroke-strong); border-radius: var(--r-page); padding: var(--s-1) var(--s-1) var(--s-1) var(--s-4); }
.chat__composer input, .chat__composer textarea { flex: 1; border: none; outline: none; background: none; resize: none;
  font-family: var(--font-body); font-size: var(--fs-base); color: var(--text-strong); height: 34px; line-height: 34px; }
.chat__send { width: 34px; height: 34px; border-radius: var(--r-full); border: none; background: var(--accent);
  color: var(--on-accent); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex: none; }

/* ===== Carousel ===== (scroll horizontal + boutons) */
.carousel { position: relative; }
.carousel__track { display: flex; gap: var(--s-2); overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.carousel__track::-webkit-scrollbar { display: none; }
.carousel__track > * { scroll-snap-align: start; flex: none; }
.carousel__btn { position: absolute; top: 50%; transform: translateY(-50%); width: 32px; height: 32px;
  border-radius: var(--r-full); border: 1px solid var(--stroke-weak); background: var(--surface); box-shadow: var(--shadow-raised);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center; color: var(--text-strong); }
.carousel__btn--prev { left: -12px; } .carousel__btn--next { right: -12px; }

/* ===== Deck ===== (séquence de panneaux plein écran : strip transform + chromes de navigation.
   Distinct de .carousel — ruban de cartes scroll-snap — et de .stepper — chrome seul, sans automate.
   Piloté par le contrôleur `deck` (agence-workspace#61). Les flèches réutilisent .btn/.carousel__btn. */
.deck { position: relative; overflow: hidden; }
.deck__track { display: flex; transform: translateX(calc(-100% * var(--deck-index, 0))); transition: transform .28s ease-out; }
.deck__page { flex: none; width: 100%; }
/* Pastilles de PAGINATION (distinctes de .dot, pastille d'état) */
.deck__dots { display: flex; align-items: center; gap: var(--s-1-5); }
.deck__dot { width: 6px; height: 6px; flex: none; padding: 0; border: none; cursor: pointer;
  border-radius: var(--r-full); background: var(--stroke-strong); transition: width .18s, background-color .18s; }
.deck__dot.is-current { width: 20px; background: var(--accent); }
.deck__counter { font-size: var(--fs-xs); color: var(--text-weak); font-variant-numeric: tabular-nums; }
.deck__nav { display: flex; align-items: center; gap: var(--s-3); }
.deck__nav [hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .deck__track { transition: none; } }
@media (pointer: coarse) { .deck__dot { min-height: 44px; background-clip: content-box; padding: 19px 0; } }

/* ===== MegaMenu ===== (panneau : radius 12 + shadow overlay ; item title + description) */
.megamenu { background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container);
  box-shadow: var(--shadow-overlay); padding: var(--s-2); min-width: 280px; display: grid; gap: var(--s-0-5); }
.megamenu__item { display: flex; gap: var(--s-3); padding: var(--s-2) var(--s-3); border-radius: var(--r-element);
  text-decoration: none; color: inherit; cursor: pointer; }
.megamenu__item:hover { background: var(--neutral); }
.megamenu__item .t { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-base); color: var(--text-strong); }
.megamenu__item .d { font-size: var(--fs-sm); color: var(--text-weak); }

/* ===== MetadataList ===== (détails clés : label 14/500 weak, valeur strong) */
.metadata { display: flex; flex-direction: column; gap: var(--s-2); }
.metadata__title { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-base); margin-bottom: var(--s-1); }
.metadata__item { display: grid; grid-template-columns: 110px 1fr; gap: var(--s-3); font-size: var(--fs-body); }
.metadata--wide .metadata__item { grid-template-columns: 160px 1fr; }
@media (max-width: 480px) {
  .metadata__item, .metadata--wide .metadata__item { grid-template-columns: 1fr; gap: var(--s-1); }
}
.metadata__label { color: var(--text-weak); font-weight: var(--fw-medium); font-size: var(--fs-base); }
.metadata__value { color: var(--text-strong); }
/* Rubrique à compléter : libellé normal, valeur teintée (même logique que .table__cell--missing). */
.metadata__item--missing .metadata__value { justify-self: start; padding: var(--s-1) var(--s-2); border-radius: var(--r-element);
  background: var(--warning-soft); color: var(--text-weak); }

/* ===== Toast ===== (fond inversé sombre, radius 12, padding 16, texte blanc) */
.toast { display: flex; align-items: center; gap: var(--s-2); background: #0a1317; color: #fff;
  border-radius: var(--r-container); padding: var(--s-4); font-size: var(--fs-base); box-shadow: var(--shadow-overlay); }
.toast--error { background: #7a0f1c; }
.toast > span, .toast__message { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.toast > button { display: inline-flex; align-items: center; justify-content: center; flex: none;
  min-height: 40px; padding: 0 var(--s-3); border: none; border-radius: var(--r-element);
  background: transparent; color: inherit; font: inherit; line-height: 1.25; cursor: pointer;
  transition: background-color .12s ease, color .12s ease; }
.toast__action { font-weight: var(--fw-semibold); white-space: nowrap; text-decoration: underline;
  text-underline-offset: 2px; }
/* Action principale du toast (« Tout archiver ») : bouton plein clair sur le fond
   sombre, lisible aussi sur .toast--error. Texte #0a1317 sur #fff = 18,6:1 ; fond du
   bouton contre le toast : 18,6:1 (normal) et 9,3:1 (erreur), au-delà du 3:1 des
   composants (WCAG 1.4.11). Sélecteurs en `.toast >` pour battre `.toast > button`. */
.toast > .toast__action--primary { background: #fff; color: #0a1317; text-decoration: none; }
.toast > .toast__action--primary:not(:disabled):hover { background: #dfe6e8; }
.toast > .toast__action--primary:focus-visible { outline-color: #fff; }
.toast__close { width: 40px; min-width: 40px; height: 40px; padding: 0; font-size: 20px; line-height: 1; }
.toast > button:disabled { opacity: .5; cursor: not-allowed; }
.toast > button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
@media (hover: hover) {
  .toast > button:not(:disabled):hover { background: rgba(255,255,255,.14); }
}
/* transition de sortie pour le contrôleur `undo-toast` (agence-workspace#43) */
.toast { transition: opacity .2s ease, transform .2s ease; }
.toast.is-leaving { opacity: 0; transform: translateY(8px); }
/* pile de toasts : conteneur du mode « client-driven » (agence-workspace#52).
   Ancré en bas-droite, grandit vers le haut (le plus récent près du coin). */
.toast-stack { position: fixed; z-index: var(--z-toast); bottom: var(--s-4); right: var(--s-4);
  display: flex; flex-direction: column; gap: var(--s-2); width: min(420px, calc(100vw - var(--s-8)));
  pointer-events: none; }
.toast-stack > .toast { pointer-events: auto; }
@media (max-width: 639px) {
  .toast-stack { left: var(--s-4); right: var(--s-4); width: auto; }
}

/* ===== Rating ===== (étoiles ; Astryx n'exporte pas de composant, pattern maison) */
.rating { display: inline-flex; gap: 2px; color: var(--accent); }
.rating .off { color: var(--stroke-strong); }

/* ===== Meter ===== (jauge étiquetée) */
.meter { display: flex; flex-direction: column; gap: var(--s-1); }
.meter__head { display: flex; justify-content: space-between; font-size: var(--fs-sm); }
.meter__head .lbl { color: var(--text-weak); }
.meter__track { height: 8px; border-radius: var(--r-full); background: var(--neutral); overflow: hidden; }
.meter__fill { height: 100%; border-radius: var(--r-full); background: var(--accent); }

/* ===== Dropzone ===== (zone de dépôt, bordure pointillée sur tous les côtés) */
.dropzone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s-2);
  padding: var(--s-6); border: 1.5px dashed var(--stroke-strong); border-radius: var(--r-container);
  background: var(--fill); color: var(--text-weak); font-size: var(--fs-base); text-align: center; cursor: pointer; }
.dropzone:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--fill)); }
/* états de FICHIER + liste (agence-workspace#65), pilotés par le contrôleur `dropzone` */
.dropzone.is-dragover { border-color: var(--accent); border-style: solid;
  background: color-mix(in srgb, var(--accent) 10%, var(--fill)); }
.dropzone.is-disabled { opacity: .5; pointer-events: none; cursor: default; }
.dropzone__list { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-3); }
.dropzone__item { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-2) var(--s-3);
  border: 1px solid var(--stroke-weak); border-radius: var(--r-element); background: var(--surface); }
.dropzone__item.is-rejected { border-color: var(--error); }
.dropzone__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s-1); }
.dropzone__name { font-size: var(--fs-base); color: var(--text-strong); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dropzone__meta { font-size: var(--fs-xs); color: var(--text-weak); font-variant-numeric: tabular-nums; }
.thumbnail--label { display: grid; place-content: center; font-size: var(--fs-2xs); font-weight: var(--fw-bold); color: var(--text-weak); text-transform: uppercase; }

/* ===== Slider ===== (piste neutral, thumb 20px accent) */
.slider { position: relative; height: 20px; display: flex; align-items: center; }
.slider__track { height: 6px; width: 100%; border-radius: var(--r-full); background: var(--neutral); }
.slider__fill { height: 100%; border-radius: var(--r-full); background: var(--accent); }
.slider__thumb { position: absolute; top: 50%; width: 20px; height: 20px; border-radius: var(--r-full);
  background: var(--accent); transform: translate(-50%, -50%); box-shadow: 0 1px 2px rgba(0,0,0,.3); }

/* ===== Champ avec affixe/icône (DateInput, NumberInput, recherche) ===== */
.input-affix { position: relative; display: flex; align-items: center; }
.input-affix .input { padding-right: var(--s-9); }
/* Même géométrie que le chevron du .select : icône de 16 px à --s-3 du bord, texte à --s-9. */
.input-affix__icon { position: absolute; right: var(--s-3); color: var(--text-weak); pointer-events: none; display: flex; }
.input-affix__icon svg { width: 16px; height: 16px; flex: none; }
/* Icône à gauche (#82) : convention recherche. Bascule le padding réservé et le côté de l'icône. */
.input-affix--left .input { padding-left: var(--s-9); padding-right: var(--s-3); }
.input-affix--left .input-affix__icon { left: var(--s-3); right: auto; }

/* ===== Layout : SideNav, TopNav, AppShell ===== (item nav 32px, radius 8, sélectionné = neutral) */
/* Valeurs relevées dans la source Astryx (SideNav.tsx / SideNavItem.tsx) :
   width 260, paddingInline spacing-2 (8px), gap MESURE 2px entre items (rowGap reel du conteneur),
   indentation des enfants paddingInlineStart spacing-6 (24px). */
.sidenav { width: 260px; height: 100%; display: flex; flex-direction: column; gap: var(--s-0-5);
  padding: var(--s-2); background: var(--surface); border-right: 1px solid var(--stroke-weak); }
.sidenav__header { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2); margin-bottom: var(--s-1);
  font-family: var(--font-heading); font-weight: var(--fw-bold); font-size: var(--fs-base); color: var(--text-strong); }
.sidenav__header svg { width: 24px; height: 24px; flex: none; }
.sidenav__header--menu { cursor: pointer; border-radius: var(--r-element); border: none; background: none; width: 100%; text-align: left; }
.sidenav__header--menu:hover { background: var(--neutral); }
.sidenav__header-chevron { margin-left: auto; color: var(--text-weak); display: inline-flex; }
.sidenav__section { display: flex; flex-direction: column; gap: var(--s-0-5); margin-top: var(--s-3); }
.sidenav__section:first-of-type { margin-top: 0; }
.sidenav__section-heading { padding: var(--s-1) var(--s-2); font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-weak); }
.sidenav__item { display: flex; align-items: center; gap: var(--s-2); width: 100%; height: var(--size-md);
  padding: 0 var(--s-2); border: none; background: none; cursor: pointer; text-decoration: none;
  border-radius: var(--r-element); font-family: var(--font-body); font-weight: var(--fw-normal);
  font-size: var(--fs-base); color: var(--text-weak); }
.sidenav__item svg { width: 18px; height: 18px; flex: none; }
.sidenav__item:hover { background: var(--neutral); color: var(--text-strong); }
/* Mesure Astryx : libelle d'item en 14px poids 400. Seul l'item actif monte
   en poids, c'est lui qui doit ressortir.
   Actif ≠ survol : le survol reste neutre (--neutral), l'actif porte la teinte
   de marque (--brand-soft) — sinon les deux états partagent exactement le même
   fond et « où je suis » disparaît dès qu'on survole la liste. */
.sidenav__item.is-selected, .sidenav__item[aria-current="page"] { background: var(--brand-soft); color: var(--text-strong); font-weight: var(--fw-medium); }
.sidenav__item.is-selected svg, .sidenav__item[aria-current="page"] svg { color: var(--accent); } /* actif distinct (à la Astryx) */
.sidenav__trailing { margin-left: auto; font-size: var(--fs-xs); color: var(--text-weak); }
.sidenav__nested { display: flex; flex-direction: column; gap: var(--s-0-5); padding-left: var(--s-6); }
/* Groupe = item PARENT repliable (anatomie du template Shell · Side Nav :
   pas de titre de section en majuscules, le groupe est lui-même un item). */
.sidenav__group { display: flex; flex-direction: column; gap: var(--s-0-5); }
/* Astryx utilise un chevronDown qui pivote : deplie = vers le bas, replie = vers la droite. */
.sidenav__chevron { margin-left: auto; display: inline-flex; color: var(--text-weak); transition: transform .15s; }
.sidenav__group.is-collapsed .sidenav__chevron { transform: rotate(-90deg); }
.sidenav__group.is-collapsed .sidenav__nested { display: none; }
/* En-tête de groupe qui est AUSSI une destination (#81, nav en drill-in : Linear/Height/Notion).
   Un <button> ne peut pas vivre dans un <a> → le conteneur habillé (.sidenav__item) porte deux
   enfants frères nus : le lien s'étire (navigue), le chevron se cale à droite (replie). Survol du
   chevron distinct de celui de la ligne pour montrer les deux cibles. */
.sidenav__item--split { padding-right: var(--s-1); }
.sidenav__item--split > a { display: flex; align-items: center; gap: var(--s-2); flex: 1; min-width: 0;
  color: inherit; text-decoration: none; }
.sidenav__item--split > .sidenav__chevron { border: none; background: none; cursor: pointer;
  padding: var(--s-1); border-radius: var(--r-inner); color: var(--text-weak); }
.sidenav__item--split > .sidenav__chevron:hover { background: var(--fill); }
/* Séparateur entre groupes (remplace les en-têtes de section). */
.sidenav__divider { height: 1px; background: var(--stroke-weak); margin: var(--s-2) var(--s-1); }
/* Pastille de statut en fin d'item (endContent du template). */
.sidenav__status { margin-left: auto; width: 8px; height: 8px; flex: none; border-radius: var(--r-full); background: var(--text-weak); }
.sidenav__status--success { background: #22c55e; }
.sidenav__status--warning { background: #f59e0b; }
.sidenav__status--error   { background: var(--error); }
.sidenav__status--accent  { background: var(--accent); }
.sidenav__footer { margin-top: auto; padding-top: var(--s-2); border-top: 1px solid var(--stroke-weak); }
/* Menu de compte en pied de sidenav (agence-workspace#51) : structure + style ;
   le contenu (items, rôles, libellés) reste app-side. Le menu s'ouvre vers le HAUT. */
.sidenav__account-wrap { position: relative; }
.sidenav__account { display: flex; align-items: center; gap: var(--s-2); width: 100%; padding: var(--s-2);
  border: none; background: none; cursor: pointer; border-radius: var(--r-element); text-align: left; color: var(--text-strong); }
.sidenav__account:hover { background: var(--neutral); }
.sidenav__account-identity { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.sidenav__account-identity .name  { font-weight: var(--fw-semibold); font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidenav__account-identity .email { font-size: var(--fs-xs); color: var(--text-weak); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidenav__account-chevron { margin-left: auto; color: var(--text-weak); display: inline-flex; flex: none; }
.sidenav--collapsed .sidenav__account { justify-content: center; padding: 0; }
.sidenav__account-menu { position: absolute; bottom: calc(100% + var(--s-1)); left: 0; right: 0;
  background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container);
  box-shadow: var(--shadow-overlay); padding: var(--s-1); min-width: 220px; }
.sidenav__account-menu .sidenav__account-identity { padding: var(--s-2) var(--s-3); gap: 2px; }
.sidenav__account-heading { padding: var(--s-1) var(--s-3); font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-weak); }
/* rail replié (icônes seules) — coque gemifiée (agence-workspace#49), pilotée par
   le contrôleur `sidenav`. Le rail est une affordance DESKTOP : bornée à ≥1024px
   (agence-workspace#66), sinon le tiroir mobile hérite de la largeur 64px + des
   libellés masqués via l'état .sidenav--collapsed persisté en localStorage →
   tiroir inutilisable. Complémentaire du bloc .sidenav--panel (max-width:1023px).
   Spécificité .sidenav.sidenav--collapsed pour battre un utilitaire w-64 côté app. */
[data-sidenav-target="logoMini"] { display: none; } /* mini logo masqué par défaut (toutes largeurs) */
@media (min-width: 1024px) {
  .sidenav.sidenav--collapsed { width: 64px; }
  .sidenav--collapsed .sidenav__item { justify-content: center; padding: 0; }
  .sidenav--collapsed .sidenav__label, .sidenav--collapsed .sidenav__trailing,
  .sidenav--collapsed .sidenav__section-heading, .sidenav--collapsed .sidenav__header span,
  .sidenav--collapsed [data-sidenav-target="label"] { display: none; } /* masque AUSSI les libellés d'éléments app (⌘K, footer) */
  /* sous-menu de groupe : retirer l'indentation de 24px en rail (sinon icônes décalées),
     et le chevron n'a pas de sens en rail. */
  .sidenav--collapsed .sidenav__nested { padding-left: 0; }
  .sidenav--collapsed .sidenav__chevron { display: none; }
  /* Item --split (#81) en rail : chevron masqué → le lien reprend la largeur, on recentre l'icône. */
  .sidenav--collapsed .sidenav__item--split > a { justify-content: center; }
  /* swap logo plein <-> mini + rotation de l'icône de collapse (targets du contrôleur) */
  .sidenav--collapsed [data-sidenav-target="logoFull"] { display: none; }
  .sidenav--collapsed [data-sidenav-target="logoMini"] { display: inline-flex; }
  .sidenav--collapsed [data-sidenav-target="collapseIcon"] { transform: scaleX(-1); }
  /* Header en rail : logoMini (24px) et bouton de repli (40px) ne tiennent pas
     côte à côte dans les 48px utiles d'un rail de 64px → pile verticale centrée,
     la marque au-dessus, le bouton dessous. Et le masque générique `span`
     ci-dessus ne doit avaler ni un logoMini hébergé DANS le header (cas
     documenté par le contrôleur) et son fallback interne, ni le collapseIcon —
     que la gem stylise elle-même en rail (scaleX(-1)) et qui est la seule
     affordance de réouverture. */
  .sidenav--collapsed .sidenav__header { flex-direction: column; align-items: center; gap: var(--s-1); padding: var(--s-2) 0; }
  .sidenav--collapsed .sidenav__header [data-sidenav-target="logoMini"],
  .sidenav--collapsed .sidenav__header [data-sidenav-target="logoMini"] span:not([hidden]),
  .sidenav--collapsed .sidenav__header [data-sidenav-target="collapseIcon"] { display: inline-flex; }
}
/* tooltip de rail : indispensable quand le libellé est masqué. Rendu en `fixed`
   par le contrôleur `sidenav` (PAS un ::after) — un ::after serait CLIPPÉ par
   l'overflow du rail scrollable (cf. leçon placement dropdown #45). Utiliser
   data-tooltip, jamais title (sinon double tooltip natif du navigateur). */
.sidenav__tip { position: fixed; z-index: var(--z-tooltip); pointer-events: none; white-space: nowrap;
  background: var(--text-strong); color: var(--bg); padding: var(--s-1) var(--s-2);
  border-radius: var(--r-element); font-size: var(--fs-xs); font-weight: var(--fw-medium);
  box-shadow: var(--shadow-raised); opacity: 0; transition: opacity .1s ease; }
/* panel responsive OPTIONNEL : off-canvas mobile <-> statique desktop */
.sidenav--panel { transition: width .2s ease, transform .2s ease; }
.sidenav-overlay { display: none; }
@media (max-width: 1023px) {
  .sidenav--panel { position: fixed; inset: 0 auto 0 0; z-index: var(--z-modal); transform: translateX(-100%); }
  .sidenav--panel.is-open { transform: translateX(0); }
  .sidenav-overlay.is-open { display: block; position: fixed; inset: 0; z-index: calc(var(--z-modal) - 1); background: rgba(1,28,37,.4); }
}
@media (prefers-reduced-motion: reduce) { .sidenav--panel { transition: none; } }

/* Topbar (agence-workspace#50) : coque sticky translucide, hauteur réelle, slot
   hamburger mobile. Le contenu (recherche, cloche, nom) reste app-side. */
.topnav { display: flex; align-items: center; gap: var(--s-3); height: var(--topnav-h, 56px); padding: 0 var(--s-4);
  position: sticky; top: 0; z-index: var(--z-sticky);
  background: color-mix(in srgb, var(--surface) 85%, transparent); backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--stroke-weak); }
.topnav__brand { font-family: var(--font-heading); font-weight: var(--fw-bold); letter-spacing: -.02em; }
.topnav__end { margin-left: auto; display: flex; align-items: center; gap: var(--s-3); }
.topnav__mobile-toggle { display: inline-flex; align-items: center; justify-content: center; width: var(--size-md); height: var(--size-md);
  border: none; background: none; color: var(--text-strong); cursor: pointer; border-radius: var(--r-element); flex: none; }
.topnav__mobile-toggle:hover { background: var(--neutral); }
@media (min-width: 1024px) { .topnav__mobile-toggle { display: none; } } /* caché en desktop (lg+) */

.appshell { display: grid; grid-template-columns: 260px 1fr; grid-template-rows: var(--topnav-h, 56px) 1fr; height: 100%; }
.appshell > .sidenav { grid-row: 1 / -1; height: 100%; }
.appshell > .topnav { grid-column: 2; }
.appshell__main { grid-column: 2; overflow: auto; padding: var(--s-4); background: var(--bg); }
/* La colonne suit le rail replié (#80) — sinon 196px de trou mort quand .sidenav passe à 64px.
   Bornée à ≥1024px comme .sidenav.sidenav--collapsed (le tiroir mobile porte aussi l'état
   persisté). 64px = miroir de .sidenav.sidenav--collapsed, à garder en phase. */
@media (min-width: 1024px) {
  .appshell:has(> .sidenav--collapsed) { grid-template-columns: 64px 1fr; }
}
/* Topnav OPTIONNEL (#80) : une sidenav qui porte l'identité (.sidenav__header) n'a pas de
   .topnav → pas de rangée vide de 56px imposée en haut du contenu. */
.appshell:not(:has(> .topnav)) { grid-template-rows: 1fr; }

.avatar-group { display: inline-flex; }
.avatar-group > .avatar { box-shadow: 0 0 0 2px var(--bg); margin-left: -8px; }
.avatar-group > .avatar:first-child { margin-left: 0; }
.avatar-group__more { background: var(--neutral); color: var(--text-weak); font-size: 12px; }

/* ===== Overlays (Dialog, Menu/Dropdown, Popover, Tooltip) =====
   Selon la doc Astryx : conteneurs flottants = radius container (12) + shadow overlay ;
   tooltip = fond inversé (navy). (Convention documentée, pas capture pixel : portals.) */
/* ----- Dialog : peau de l'élément <dialog> NATIF (agence-workspace#59) -----
   Anatomie vérifiée sur la SOURCE Astryx (Dialog.tsx + Layout{Header,Content,Footer}.tsx) :
   - conteneur : PAS de bordure — l'ombre porte l'élévation ; radius-container (12) ; bg surface.
   - ::backdrop : scrim + blur(2px) (Astryx = 2px, pas 4).
   - padding UNIFORME s-4 (16) sur header/body/footer (Astryx : les 4 vars de padding = spacing-4).
   - filets edge-to-edge : header en bas, footer en haut, en --stroke-weak (= Astryx --color-border).
   - header : align FLEX-START (le titre peut passer à la ligne), gap s-3.
   La peau visuelle est sur `.dialog` (marche sur un <div> inline — vitrine, aperçu) ; la mécanique du
   modal natif (position fixe, top-layer, backdrop) sur `dialog.dialog`. */
.dialog { background: var(--surface); border-radius: var(--r-container); box-shadow: var(--shadow-overlay);
  color: var(--text-strong); padding: 0; max-width: var(--dialog-w, 440px); }
dialog.dialog { position: fixed; inset: 0; margin: auto; overscroll-behavior: contain;
  width: min(100% - var(--s-8), var(--dialog-w, 440px)); max-height: calc(100dvh - var(--s-8)); }
dialog.dialog[open] { display: flex; flex-direction: column; }
dialog.dialog::backdrop { background: var(--scrim); backdrop-filter: blur(2px); }

/* Échelle de largeurs (var --dialog-w — comptage réel des 45 vues app-biz : lg 18×, md 10×, xl 5×, sm 4×). */
.dialog--sm   { --dialog-w: 384px; }
.dialog--lg   { --dialog-w: 512px; }
.dialog--xl   { --dialog-w: 576px; }
.dialog--wide { --dialog-w: 896px; }

.dialog__header { flex: none; display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-3); padding: var(--s-4); border-bottom: 1px solid var(--stroke-weak); }
.dialog__title { font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-xl);
  line-height: 1.4; color: var(--text-strong); }
.dialog__close { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: var(--size-md); height: var(--size-md); border: none; background: none; cursor: pointer;
  border-radius: var(--r-element); color: var(--text-weak); }
.dialog__body { flex: 1; overflow-y: auto; padding: var(--s-4); font-size: var(--fs-base); }
.dialog__footer { flex: none; display: flex; align-items: center; justify-content: flex-end;
  gap: var(--s-2); padding: var(--s-4); border-top: 1px solid var(--stroke-weak); }
@media (hover: hover) { .dialog__close:hover { background: var(--fill); color: var(--text-strong); } }

/* AlertDialog (confirm_controller) : carte auto-portée, SANS barres à filets. Reste identique à
   l'ancienne (padding s-6, corps/pied en marges) pour ne casser aucun adoptant. */
.dialog--alert { --dialog-w: 400px; padding: var(--s-6); }
.dialog--alert .dialog__body { flex: none; overflow: visible; padding: 0; margin-top: var(--s-2); color: var(--text-weak); }
.dialog--alert .dialog__footer { padding: 0; margin-top: var(--s-5); border-top: none; }

/* Sheet : panneau latéral encastré (convention roalink FORM_DRAWER, 22 vues). Pas d'équivalent Astryx
   (fidélité au niveau tokens) : radius-page (28, géométrie Astryx), bordure PLEINE sur le panneau
   flottant (pas de bordure mono-côté), scrim partagé. Plein écran < 640px, ancré à droite au-delà. */
dialog.dialog--sheet { inset: 0 0 0 auto; margin: 0; width: 100vw; max-width: 100vw;
  height: 100dvh; max-height: 100dvh; border: none; border-radius: 0; }
@media (min-width: 640px) {
  dialog.dialog--sheet { inset: var(--s-3) var(--s-3) var(--s-3) auto;
    /* --sheet-w = point d'entrée largeur (#76), pendant de --dialog-w. Défaut = valeur
       historique (aucun adoptant ne bouge). Tiroir étroit : style="--sheet-w: 380px". */
    width: min(calc(100vw - var(--s-6)), var(--sheet-w, clamp(544px, 40vw, 704px))); max-width: none;
    height: auto; max-height: none; border: 1px solid var(--stroke-weak); border-radius: var(--r-page); }
}

/* Entrée : translate + scale + fade. Astryx anime la source en medium-max (550ms) ; la gem unifie les
   entrées de composant sur --dur-base (230, fast-max) — comme drawer/toast/sidenav. scale .95 et le
   décalage 16px viennent d'Astryx (enterDirectional, direction par défaut y=16). */
@keyframes dialog-in { from { opacity: 0; transform: translateY(var(--s-4)) scale(.95); } }
@keyframes sheet-in  { from { transform: translateX(100%); } }
dialog.dialog[open]        { animation: dialog-in var(--dur-base) var(--ease-out); }
dialog.dialog--sheet[open] { animation: sheet-in  var(--dur-base) var(--ease-out); }
@media (prefers-reduced-motion: reduce) {
  dialog.dialog[open], dialog.dialog--sheet[open] { animation: none; }
}

/* ----- Disclosure : peau du <details>/<summary> NATIF (agence-workspace#59) -----
   Pas d'équivalent Astryx (son Collapsible est un <button>+<div> JS ; ici c'est le natif). Fidélité au
   niveau tokens, alignée sur `.collapsible` (mêmes paddings/radius/chevron). L'app gère ouvrir/fermer/
   persister ; zéro JS gem. */
.disclosure { background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container);
  overflow: hidden; interpolate-size: allow-keywords; }
.disclosure > summary { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  padding: var(--s-3) var(--s-4); cursor: pointer; user-select: none; list-style: none;
  font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-lg); color: var(--text-strong); }
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.disclosure__chevron { flex: none; color: var(--text-weak); transition: transform .15s; transform: rotate(90deg); }
.disclosure:not([open]) > summary .disclosure__chevron { transform: rotate(0); }
.disclosure__content { padding: 0 var(--s-4) var(--s-4); font-size: var(--fs-base); color: var(--text-weak); }
@media (hover: hover) { .disclosure > summary:hover { background: var(--fill); } }
/* Ouverture animée sans JS. Les moteurs sans ::details-content ouvrent d'un coup (fonctionnel, sans transition). */
.disclosure::details-content { block-size: 0; overflow: clip;
  transition: block-size .24s ease, content-visibility .24s ease allow-discrete; }
.disclosure[open]::details-content { block-size: auto; }
@media (prefers-reduced-motion: reduce) { .disclosure::details-content { transition: none; } }

/* Reset du marqueur natif sur `.sidenav__group` (classe gem #49 employée en <details> par 2 apps ; le
   portail le corrigeait localement — on le rapatrie dans la gem). */
.sidenav__group > summary { list-style: none; cursor: pointer; }
.sidenav__group > summary::-webkit-details-marker { display: none; }

.menu { background: var(--surface); border: 1px solid var(--stroke-weak); border-radius: var(--r-container);
  box-shadow: var(--shadow-overlay); padding: var(--s-1); min-width: 200px; }
/* Item à --s-2 : avec le --s-1 du menu et sa bordure, le texte tombe à 13 px du bord,
   comme celui du champ, de la pastille ou du .combobox__option au-dessus duquel il s'ouvre. */
.menu__item { display: flex; align-items: center; gap: var(--s-2); width: 100%; padding: var(--s-2);
  border: none; background: none; cursor: pointer; border-radius: var(--r-element); font-family: var(--font-body);
  font-size: var(--fs-base); color: var(--text-strong); text-align: left; }
.menu__item:hover { background: var(--neutral); }
.menu__item.is-disabled { opacity: .4; cursor: not-allowed; }
.menu__item.is-disabled:hover { background: none; }
.menu__item--danger { color: var(--error); }
/* État « valeur retenue » (#78) : même traitement que .cmdk__item.is-active (accent 10 %),
   la sémantique d'un menu de filtre = valeur choisie, pas surbrillance de navigation clavier. */
.menu__item.is-active { background: color-mix(in srgb, var(--accent) 10%, transparent); font-weight: var(--fw-medium); }
.menu__shortcut { margin-left: auto; font-size: var(--fs-xs); color: var(--text-weak); }
.menu__sep { height: 1px; background: var(--stroke-weak); margin: var(--s-1) 0; }
/* Intitulé de groupe, non cliquable (role="presentation") : « Compte », « Contrôles »… */
.menu__heading { padding: var(--s-2) var(--s-2) var(--s-1); font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  color: var(--text-weak); }
/* Longue liste (filtre « Assigné ») : hauteur bornée, défilement interne sans fermer un menu ancré. */
.menu--scroll { max-height: min(32rem, calc(100dvh - 8rem)); overflow-y: auto; }
/* Coche de la valeur retenue dans un menu à choix unique : posée sur chaque option, visible sur .is-active. */
.menu__check { margin-left: auto; flex: none; visibility: hidden; }
.menu__item.is-active .menu__check { visibility: visible; }

.tooltip { display: inline-flex; background: var(--text-strong); color: var(--bg); border-radius: var(--r-element);
  padding: var(--s-1) var(--s-2); font-size: var(--fs-xs); font-weight: var(--fw-medium); box-shadow: var(--shadow-raised); }
/* Bulle du contrôleur `tooltip` : dans document.body, en fixed, hors de tout overflow. */
.tooltip--floating { position: fixed; z-index: var(--z-tooltip); pointer-events: none; display: block;
  max-width: min(320px, calc(100vw - 16px)); white-space: normal; overflow-wrap: anywhere; }
.tooltip--floating[hidden] { display: none; }

/* ============================================================
   BATCH #63 — briques de densité (CSS pur, cross-app)
   ============================================================ */
/* Médaillon : icône ou initiales sur pastille (avatar de repli, puce, tuile).
   Teinte dérivée par color-mix sur --accent → juste pour toute agence + en sombre. */
.medallion { display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--size-md); height: var(--size-md); border-radius: var(--r-full);
  background: var(--fill); color: var(--text-weak);
  font-family: var(--font-heading); font-weight: var(--fw-semibold); font-size: var(--fs-xs); }
.medallion--sm { width: var(--size-sm); height: var(--size-sm); font-size: var(--fs-2xs); }
.medallion--md { width: var(--size-md); height: var(--size-md); }
.medallion--lg { width: var(--size-lg); height: var(--size-lg); }
.medallion--xl { width: 48px; height: 48px; font-size: var(--fs-base); }
.medallion--square { border-radius: var(--r-container); }
.medallion--accent { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); }
.medallion--outline { box-shadow: inset 0 0 0 1px var(--stroke-weak); }

/* État vide encadré (.empty reste la colonne nue ; --panel en fait un panneau). */
.empty--panel { border: 1px solid var(--stroke-weak); border-radius: var(--r-container);
  background: var(--fill); padding: var(--s-12) var(--s-6); }
.empty--panel .empty__title { font-family: var(--font-body); font-size: var(--fs-base); font-weight: var(--fw-semibold); }
.empty--panel .medallion { background: var(--bg); }
.empty--inline { padding: var(--s-6) var(--s-4); }
.empty__action { margin-top: var(--s-4); }

/* Chiffres tabulaires : décision DS (indépendante de Tailwind). */
.num, .stat__value, .meter__head, .filter-pill__count, .sidenav__trailing,
.kanban__count, .notif-bell__badge, .table td[align="right"], .table th[align="right"] {
  font-variant-numeric: tabular-nums; }

/* Barre de défilement discrète (theme-aware). .carousel__track garde son masquage. */
.scroll-subtle, .kanban, .table-wrap, .drawer__body, .cmdk__list, .appshell__main, .tabs--fill {
  scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--text-weak) 22%, transparent) transparent; }
.scroll-subtle::-webkit-scrollbar, .kanban::-webkit-scrollbar, .table-wrap::-webkit-scrollbar,
.drawer__body::-webkit-scrollbar, .cmdk__list::-webkit-scrollbar, .appshell__main::-webkit-scrollbar,
.tabs--fill::-webkit-scrollbar { width: var(--s-2); height: var(--s-2); }
.scroll-subtle::-webkit-scrollbar-track, .kanban::-webkit-scrollbar-track, .table-wrap::-webkit-scrollbar-track,
.drawer__body::-webkit-scrollbar-track, .cmdk__list::-webkit-scrollbar-track, .appshell__main::-webkit-scrollbar-track,
.tabs--fill::-webkit-scrollbar-track { background: transparent; }
.scroll-subtle::-webkit-scrollbar-thumb, .kanban::-webkit-scrollbar-thumb, .table-wrap::-webkit-scrollbar-thumb,
.drawer__body::-webkit-scrollbar-thumb, .cmdk__list::-webkit-scrollbar-thumb, .appshell__main::-webkit-scrollbar-thumb,
.tabs--fill::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--text-weak) 20%, transparent); border-radius: var(--r-full); }
@media (hover: hover) {
  .scroll-subtle:hover::-webkit-scrollbar-thumb, .kanban:hover::-webkit-scrollbar-thumb, .table-wrap:hover::-webkit-scrollbar-thumb,
  .drawer__body:hover::-webkit-scrollbar-thumb, .cmdk__list:hover::-webkit-scrollbar-thumb, .appshell__main:hover::-webkit-scrollbar-thumb,
  .tabs--fill:hover::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--text-weak) 38%, transparent); } }

/* ============================================================
   BATCH #58 — socle (mouvement / a11y / Turbo)
   ============================================================ */
/* Entrée de contenu (courbe + durée Astryx). 12 paliers = boilerplate mécanique
   jusqu'à ce que sibling-index() soit dispo. */
@keyframes ui-fade-in-up { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
.animate-in { animation: ui-fade-in-up var(--dur-slow) var(--ease-out) both; }
.stagger > * { animation: ui-fade-in-up var(--dur-slow) var(--ease-out) both; }
.stagger > :nth-child(1) { animation-delay: 0ms; }   .stagger > :nth-child(2) { animation-delay: 50ms; }
.stagger > :nth-child(3) { animation-delay: 100ms; } .stagger > :nth-child(4) { animation-delay: 150ms; }
.stagger > :nth-child(5) { animation-delay: 200ms; } .stagger > :nth-child(6) { animation-delay: 250ms; }
.stagger > :nth-child(7) { animation-delay: 300ms; } .stagger > :nth-child(8) { animation-delay: 350ms; }
.stagger > :nth-child(9) { animation-delay: 400ms; } .stagger > :nth-child(10) { animation-delay: 450ms; }
.stagger > :nth-child(11) { animation-delay: 500ms; } .stagger > :nth-child(12) { animation-delay: 550ms; }

/* Turbo Drive (refresh morph) : barre de progression + view-transitions natives */
.turbo-progress-bar { height: 2px; background: var(--brand); }
@view-transition { navigation: auto; }

/* a11y — reduced-motion UNIVERSEL : couvre tout ce qu'une app ajoute (les blocs
   ciblés plus haut sont désormais subsumés). */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after { transition-duration: .01ms !important; animation-duration: .01ms !important;
    animation-iteration-count: 1 !important; scroll-behavior: auto !important; }
}
/* focus-visible de repli en @layer base : les sélecteurs unlayered de la gem
   l'emportent, une app surcharge sans !important. */
@layer base { :focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } }
/* coarse étendu aux cibles cliquables restantes (la promesse 44px du README) */
@media (pointer: coarse) {
  .icon-btn, .pagination__btn, .tab, .menu__item, .combobox__option, .megamenu__item, .list__item,
  .sidenav__item, .topnav__mobile-toggle, .filter-pill, .kanban__nav-btn, .table-grid__nav-btn,
  .stepper__step, .bulkbar__action, .toast > button { min-height: 44px; }
  .icon-btn, .pagination__btn, .topnav__mobile-toggle, .kanban__nav-btn, .table-grid__nav-btn,
  .stepper__step, .toast__close { min-width: 44px; }
  .sortable__handle { min-width: 44px; min-height: 44px; }
  .swatch__item { width: 44px; height: 44px; padding: 10px; background-clip: content-box; }
}
/* skip-link (hôte naturel : la coque .sidenav) ; l'ancre #main-content reste à l'app */
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { position: fixed; left: var(--s-4); top: var(--s-4); z-index: var(--z-tooltip);
  display: inline-flex; align-items: center; height: var(--size-lg); padding: 0 var(--s-4);
  border-radius: var(--r-element); background: var(--primary-bg); color: var(--primary-fg);
  font-size: var(--fs-base); font-weight: var(--fw-semibold); box-shadow: var(--shadow-overlay); }

/* ============================================================
   BATCH #57 — coque de page
   ============================================================ */
/* Overline (étiquette majuscules) : une recette. Les 3 consommateurs existants
   (.cmdk__group, .sidenav__section-heading, .sidenav__account-heading) délèguent
   typo + couleur ici (source-order : ce bloc, plus bas, gagne ; ils ne gardent
   que leur padding). Micro-changements assumés : cmdk__group 400→semibold,
   section-heading .04→.06em. Opt-in (roalink écarte les capitales chez lui). */
.overline, .cmdk__group, .sidenav__section-heading, .sidenav__account-heading {
  font-family: var(--font-body); font-weight: var(--fw-semibold); letter-spacing: var(--tracking-caps);
  /* L'utilitaire Tailwind `overline` (text-decoration) porte le même nom : la primitive l'annule. */
  text-decoration: none;
  text-transform: uppercase; color: var(--text-weak); }
.overline, .cmdk__group, .sidenav__section-heading { font-size: var(--fs-xs); }
.overline--xs, .sidenav__account-heading { font-size: var(--fs-2xs); }

/* Page header : l'anatomie recomposée par les 3 apps, avec la troncature que 2 oublient. */
.page-header { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s-4); margin-bottom: var(--s-6); }
.page-header__title { min-width: 0; }
.page-header__title > .title-page { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.page-header__subtitle { margin-top: var(--s-1); font-size: var(--fs-base); color: var(--text-weak); }
.page-header__actions { flex: none; display: flex; align-items: center; gap: var(--s-2); }
@media (max-width: 639px) {
  .page-header { flex-direction: column; align-items: stretch; gap: var(--s-3); }
  .page-header__actions { flex-wrap: wrap; }
}

/* ===== Focus (Astryx réel : outline 2px accent, offset 3px) ===== */
.btn:focus-visible, .promo-cta:focus-visible, .segmented__item:focus-visible, .switch:focus-visible,
.checkbox:focus-visible, .radio:focus-visible, .token:focus-visible, .status:focus-visible,
.stat--selectable:focus-visible {
  outline: 2px solid var(--focus); outline-offset: 3px; }

/* ===== Tactile (Astryx : pointer coarse → 44px + texte 16px) ===== */
@media (pointer: coarse) {
  .btn, .promo-cta, .input, .select, .segmented__item { min-height: 44px; }
  button.status { min-height: 44px; }
  .btn, .input, .select, .textarea { font-size: max(1rem, var(--fs-base)); }
  .switch { width: 44px; height: 26px; } .switch::after { width: 22px; height: 22px; }
  .switch:checked::after { transform: translateX(18px); }
  .checkbox, .radio { width: 22px; height: 22px; }
}

/* ===== Hover (seulement sur pointeur hover-capable) ===== */
@media (hover: hover) {
  .btn--primary:hover { filter: brightness(.96); }
  .btn--secondary:hover, .btn--ghost:hover { background: color-mix(in srgb, var(--neutral) 60%, var(--stroke-weak)); }
  .btn--destructive:hover { filter: brightness(.94); }
  .btn--success:hover, .btn--warning:hover { filter: brightness(.94); }
  .promo-cta:hover { box-shadow: var(--shadow-overlay); }
  .segmented__item:hover:not([aria-selected="true"]) { color: var(--text-strong); }
}

@media (prefers-reduced-motion: reduce) {
  .btn, .promo-cta, .switch, .switch::after, .segmented__item, .input, .select, .spinner, .save-status[data-state="saving"]::before { transition: none; animation: none; }
}
