/* =============================================================================
   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);
  --brand-soft: color-mix(in srgb, var(--brand) 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 {
  width: 100%; height: var(--size-md); padding: 0 var(--s-2); 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; }
.input--lg { height: var(--size-lg); }               /* champ haut (36px), taille lg des pages d'entree */
.textarea { height: auto; min-height: 70px; padding: var(--s-1) var(--s-2); resize: vertical; line-height: 1.5; }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--ring); }
.input[aria-invalid="true"] { border-color: var(--error); }
.input:disabled, .select:disabled { opacity: .5; cursor: not-allowed; background: var(--fill); }
.select { appearance: none; padding-right: var(--s-8);
  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; }
.segmented__item[aria-selected="true"] { background: var(--surface); color: var(--text-strong); box-shadow: var(--shadow-raised); }

/* ===== 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); }
.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 { background: var(--stroke-strong); }

/* ===== 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-strong, pas d'ombre) */
.card { background: var(--surface); border: 1px solid var(--stroke-strong);
  border-radius: var(--r-container); padding: var(--s-4); }
.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); }
.table td { padding: var(--s-2) var(--s-3); color: var(--text-strong); border-top: 1px solid var(--stroke-weak); }
.table tbody tr:hover { background: var(--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 */
.table-wrap { 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) { background: color-mix(in srgb, var(--text-strong) 2%, transparent); }
/* 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.row--warning       { background: color-mix(in srgb, var(--warning) 9%, transparent); }
.table tbody tr.row--warning:hover { background: color-mix(in srgb, var(--warning) 13%, transparent); }
.table tbody tr.row--danger        { background: color-mix(in srgb, var(--error) 9%, transparent); }
.table tbody tr.row--danger:hover  { background: color-mix(in srgb, var(--error) 13%, transparent); }

/* ===== 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 = élément dédié aligné sur la SOURCE Astryx (Tab) : barre 2px
   INSÉRÉE de spacing-3 de chaque côté (sous le texte, pas bord-à-bord), coins
   arrondis (radius-full), posée sur le rail à bottom:-1px, en fondu. Remplace le
   box-shadow pleine largeur à angles droits (générique « AI slop »). */
.tab::after { content: ""; position: absolute; bottom: -1px; left: var(--s-3); right: var(--s-3);
  height: 2px; border-radius: var(--r-full); 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); }
.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); }

/* ===== 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); }

/* ===== 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) */
.grid { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-sm); }
.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); }
.grid td { padding: 0; border-bottom: 1px solid var(--stroke-weak); border-right: 1px solid var(--stroke-weak); }
.grid tr td:last-child { border-right: none; }
.grid__cell { display: block; min-height: var(--size-md); padding: var(--s-2) var(--s-3); cursor: text; color: var(--text-strong); }
.grid__cell:focus, .grid__cell.is-editing { outline: 2px solid var(--accent); outline-offset: -2px; background: var(--bg); }
.grid__cell.is-saved { animation: grid-saved .9s ease; }
@keyframes 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; }
/* 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 { display: inline-flex; gap: var(--s-1); }
.kanban__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 { background: var(--neutral); color: var(--text-strong); }
.kanban__nav-btn:disabled { opacity: .4; cursor: not-allowed; }

/* ===== 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-strong); border-radius: var(--r-container); }
.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; }

/* ===== 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);
  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); }

/* ===== 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 { margin: 0 0 var(--s-3); padding-left: var(--s-5); }
.markdown li { margin-bottom: var(--s-1); }
.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 { margin-bottom: var(--s-2); }
.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; }
/* 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 { background: var(--neutral); }

/* ===== 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__label { color: var(--text-weak); font-weight: var(--fw-medium); font-size: var(--fs-base); }
.metadata__value { color: var(--text-strong); }

/* ===== 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; }
.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-8); }
.input-affix__icon { position: absolute; right: 10px; 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-8); padding-right: var(--s-2); }
.input-affix--left .input-affix__icon { left: 10px; 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. */
.sidenav__item.is-selected, .sidenav__item[aria-current="page"] { background: var(--neutral); 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); }
}
/* 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; }
.menu__item { display: flex; align-items: center; gap: var(--s-2); width: 100%; padding: var(--s-2) var(--s-3);
  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; }

.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); }

/* ============================================================
   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,
  .stepper__step, .bulkbar__action, .toast > button { min-height: 44px; }
  .icon-btn, .pagination__btn, .topnav__mobile-toggle, .kanban__nav-btn,
  .stepper__step, .toast__close { min-width: 44px; }
}
/* 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);
  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 {
  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; }
  .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; }
}
