/* ============================================================================
   Cockpit — Design System v2 "Atelier"
   Codes architecturaux modernes : Swiss design + Bauhaus + détails contemporains.
   Stack : CSS natif, BEM, mobile-first, WCAG 2.2 AA, prefers-color-scheme.
   Voir docs/UI-DESIGN-SYSTEM.md (vivant) et docs/06-design-system-contribuer.md.
   Référence ADR : docs/adr/ADR-010-design-system-atelier-v2.md (clôture S13).
   ============================================================================

   ----------------------------------------------------------------------------
   §0. MOOD-BOARD (verbale) — Atelier v2 enrichi S11
   ----------------------------------------------------------------------------
   « Atelier v2 = Linear x Vercel x Stripe revisité par un cabinet d'architectes. »
   La densité froide et tabulaire d'un bureau d'études (Satoshi, JetBrains Mono,
   tableaux nets, palette ink/paper) rencontre la chaleur d'un atelier de
   conception (italiques Satoshi en accent éditorial, palette Clay/Terracotta,
   micro-élévations).
   2026-05 — Bascule typographique : Fraunces a été retirée du DS. Toute
   hiérarchie (display + body + UI) utilise Satoshi via deux tokens
   distincts (--font-display = --font-sans = 'Satoshi'). La nuance
   éditoriale est désormais portée par l'ITALIQUE light (font-weight 300
   font-style italic) sur les `em` à l'intérieur des titres. Décision
   utilisateur, à reporter dans ADR-011 § amendement S15+.
   Aucun ornement gratuit — chaque composant prouve sa fonction (badge code
   DGFIP, hash SHA-256 visible, timeline lifecycle, lots, indices).
   Le geste est court, le silence assumé, l'a11y native.

   ----------------------------------------------------------------------------
   §0.1 CONVENTIONS NOMMAGE BEM + ÉTATS (T11.3.4 S11)
   ----------------------------------------------------------------------------
   - Blocs / éléments / modificateurs : `block`, `block__element`,
     `block__element--variant`. Pas de profondeur > 1 dans BEM (jamais
     `block__a__b`) — on plat-onifie ou on extrait un sous-bloc.
   - `--variant` : variante STATIQUE rendue par PHP (ex: `.btn--primary`,
     `.invoice__status--paid`). Ne change jamais au runtime.
   - `.is-X` : ÉTAT dynamique posé/retiré par JS (ex: `.is-loading`,
     `.is-open`, `.is-visible`, `.is-scrolled`). Préfixé `is-`, kebab-case.
   - `[aria-*]` : double sélection préférée pour les états sémantiques
     (`[aria-invalid="true"]`, `[aria-selected="true"]`, `[aria-busy]`)
     plutôt qu'une classe redondante — le DOM dit la vérité accessible.
   - `[data-*]` : configuration côté HTML lue par CSS (`[data-tooltip]`,
     `[data-persona]`, `[data-theme]`). Pas de logique.

   ----------------------------------------------------------------------------
   §0.2 CHARTE MOTION (T13.1.3 S13)
   ----------------------------------------------------------------------------
   Règle d'or : aucune durée/easing en littéral dans le CSS — toujours via token.
   Acceptance 🚦 S13-1 le vérifie automatiquement.
   Chaque palier de durée a une INTENTION précise :

   | Token            | Valeur | Intention                                       |
   | ---------------- | ------ | ----------------------------------------------- |
   | --dur-instant    | 60ms   | Micro-feedback tactile (press, ripple, active)  |
   | --dur-fast       | 120ms  | Hover, focus, micro-transitions visuelles       |
   | --dur            | 180ms  | Standard composant (color, border, shadow)      |
   | --dur-slow       | 320ms  | Entrée/sortie composants (modal, toast, panel)  |
   | --dur-slower     | 520ms  | Reveal au scroll, animations contextuelles      |
   | --dur-pulse      | 2s     | Indicateur ambiant (dot--accent, heartbeat)     |

   Easings :
   - --ease         : standard (out-in symétrique)
   - --ease-out     : entrée (composant qui apparaît)
   - --ease-in      : sortie (composant qui disparaît)
   - --ease-spring  : confirmation positive (rebond léger)

   Respect prefers-reduced-motion :
   - Toutes les durées de transition/animation sont écrasées au minimum via
     la règle globale en fin de fichier. Aucune animation décorative
     supérieure à un battement de cil ne doit s'exécuter quand l'utilisateur
     a opté-out via la préférence système.

   ----------------------------------------------------------------------------
   §0.3 TABLE DES MATIÈRES (46 sections — T13.4.4 S13 + §43/44 ajoutés S17/S18,
        §45 ajouté S23, §46 ajouté S26 Lot 4)
   ----------------------------------------------------------------------------
   §0   Méta : mood-board, conventions, charte motion, TOC
   §1   Design tokens (typo, espacement, palette, ombres, mouvement, layout)
   §2   Reset & base (html, body, headings, a, code, hr)
   §3   Layout (skip-link, .main, fadeUp)
   §4   Topbar (sticky, backdrop-blur, logo, nav, menu user)
   §5   Footer
   §6   Surfaces (cards, panels, section__header, feature-list)
   §7   Breadcrumb
   §8   Buttons (variants : primary, ghost, danger, success, accent, sm, xs, lg)
   §9   Forms (input, textarea, select, fieldset, helper, error)
   §10  Auth (split-screen modern, hero, magic-link)
   §11  Dashboard (Bento grid, bento__cell, metric — S24 Lot 2 : accueil par
        chantier .project-card__link/.project-card__attention, .dash__empty,
        .dash__footer-kpis ; retrait .dash-cols/.agenda__* orphelins)
   §12  Chantiers (liste + détail, phases, lots — S24 Lot 2 : .chantier-contacts,
        .client-card, .phase-dossiers, .chantier__activity ; S28 Lot 6 :
        dossiers de phase réels — .phase-dossiers__item--current/--future,
        .phase-dossiers__unclassified, .dossier__table, .dossier__markets,
        .dossier__add, .dossier__classify)
   §13  Badges / Tags / Phases
   §14  Flashes / Alerts
   §15  Tables (génériques)
   §16  Profil / Danger zone
   §17  Pages légales / Markdown / Empty states
   §18  Errors (404, 410, 500)
   §19  A11y / Utilities (sr-only, visually-hidden)
   §20  Responsive (overrides max-width 720px)
   §21  États progressive-enhancement (JS detected, animations conditionnelles)
   §22  Plans (architecte : viewer + indices + diffusion — S27 Lot 5 :
        widgets par provenance .widget--lot (réutilisé), .plans__source-table,
        chip générique .ack promu de .reports__ack, .plan__next-indice,
        .plan__actions/archivage)
   §23  Quotes (devis & TS — édition lignes + récap)
   §24  Invoices (factures + lifecycle DGFIP 211-218)
   §25  Gallery (photos — grille thumbnails + visibility toggle)
   §26  Timeline (avancement MOA + lifecycle factures)
   §27  Decisions / Proof (signature MOA — pages stratégiques juridiques)
   §28  Docs (documents généralistes + filtre/recherche — S28 Lot 6 : filtre
        par phase .docs__phase-filter, chips .chip--filter réutilisées,
        colonne Phase via .tag réutilisé)
   §29  Reports (comptes-rendus architecte)
   §30  Messages (threads + fil de discussion)
   §31  Modal (<dialog> natif + focus trap JS)
   §32  Tabs (pattern accessible — ARIA role="tablist")
   §33  Spinner + Skeleton + Progress
   §34  Toast (notification transitoire — distincte du flash bloquant)
   §35  Tooltip (CSS pur via [data-tooltip])
   §36  Dropdown menu (<details> natif stylé)
   §37  Pagination (nav + aria-label)
   §38  File upload custom (drag&drop + preview + progress)
   §39  États composants enrichis (T11.3)
   §40  Notifications + page-header (résolution orphelins S10 résiduels)
   §41  WCAG 2.2 — 2.4.11 Focus Not Obscured (S12 T12.2.7)
   §42  Polish micro-interactions S13 (loading, totals fade, toast stack, tab
        indicator, card hover lift, ligatures, tabular-nums)
   §43  Paiement PIS Bridge (ADR-0014 — payment-confirm, payment-return,
        payment-mock-bank — vues MOA paiement)
   §44  Transactions (historique paiements S18 — /app/transactions)
   §45  Widget (S23 Lot 1 — <details> générique, factorise .gantt__add,
        .invite, .invoices__payment-history, .messages__create ; enrichi
        S26 Lot 4 : .widget--lot devis/lot, .lot__signed, .lot__tools,
        .lot__table, .widget--muted, .lots__add)
   §46  Comparatif DCE (S26 Lot 4 — ADR-0027 §D4, bilan des marchés :
        matrice offres par lot, moins-disant, sélection, honoraires,
        vue liste mobile < 768, notes par entreprise)

   ============================================================================ */

/* ------------------------------------------------------------------ Fonts */
/* Sprint 14 — Internalisation 100% locale (RGPD CdC §11, ADR-011).
   Sprint S15+ — Fraunces retirée (décision utilisateur 2026-05). Satoshi
   couvre désormais display + sans (deux tokens identiques pour préserver
   la sémantique typographique). La nuance éditoriale passe par l'italique
   light (300 italic) sur les `em` dans les titres.
   Deux familles servies depuis /assets/fonts/ : aucune requête externe.
   Total ~198 Ko (Satoshi 86 Ko, JetBrains Mono 112 Ko).
   Voir docs/UI-DESIGN-SYSTEM.md §3 et docs/adr/ADR-011-*.md (amendement S15+). */

@font-face {
    font-family: 'Satoshi';
    src: url('/assets/fonts/satoshi/Satoshi-Variable.woff2') format('woff2');
    font-weight: 300 900;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Satoshi';
    src: url('/assets/fonts/satoshi/Satoshi-VariableItalic.woff2') format('woff2');
    font-weight: 300 900;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: 'JetBrains Mono';
    src: url('/assets/fonts/jetbrains-mono/JetBrainsMono-Variable.woff2') format('woff2');
    font-weight: 100 800;
    font-style: normal;
    font-display: swap;
}

/* ============================================================================
   1. DESIGN TOKENS
   ----------------------------------------------------------------------------
   Conventions d'usage (T10.3.2 S10) :
   - --text       : texte primaire (titres, corps de texte)
   - --text-soft  : texte secondaire (sous-titres, paragraphes denses)
   - --text-dim   : texte tertiaire (métadonnées, hint)
   - --text-mute  : RÉSERVÉ aux séparateurs, placeholders, glyphes décoratifs.
                    Contraste 4.72:1 sur paper ; NE JAMAIS l'utiliser pour
                    du texte de corps. Voir audit UI 2026-05-25 §5.2.
   - --border     : bordures fonctionnelles (form__input, card, table)
   - --border-subtle : bordures décoratives (séparateurs visuels uniquement)
   ============================================================================ */
:root {
    /* — Typography -----------------------------------------------------------
       S15+ : --font-display passe sur Satoshi (Fraunces retirée).
       Le token est conservé pour ne pas casser les ~50 références CSS et
       préserver la sémantique (display = titres, sans = corps). Les deux
       résolvent désormais vers la même famille — la hiérarchie passe par
       la TAILLE (4xl/3xl/2xl) et le POIDS (300 italic pour em). */
    --font-sans:    'Satoshi', -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    --font-display: 'Satoshi', -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
    --font-mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    /* Fluid type scale */
    --text-2xs:  clamp(0.68rem, 0.65rem + 0.15vw, 0.72rem);
    --text-xs:   clamp(0.75rem, 0.72rem + 0.15vw, 0.8rem);
    --text-sm:   clamp(0.85rem, 0.82rem + 0.15vw, 0.9rem);
    --text-base: 1rem;
    --text-lg:   clamp(1.05rem, 1rem + 0.25vw, 1.15rem);
    --text-xl:   clamp(1.2rem, 1.1rem + 0.5vw, 1.35rem);
    --text-2xl:  clamp(1.5rem, 1.3rem + 1vw, 1.8rem);
    --text-3xl:  clamp(1.85rem, 1.5rem + 1.5vw, 2.4rem);
    --text-4xl:  clamp(2.25rem, 1.8rem + 2.2vw, 3.2rem);
    --text-5xl:  clamp(2.75rem, 2rem + 3.5vw, 4.5rem);

    --leading-tight: 1.15;
    --leading-snug:  1.35;
    --leading-base:  1.55;
    --leading-loose: 1.75;

    --tracking-tight:   -0.02em;
    --tracking-snug:    -0.01em;
    --tracking-base:     0;
    --tracking-wide:     0.02em;
    --tracking-widest:   0.18em;

    /* — Spacing (8px base) -------------------------------------------------- */
    --space-px: 1px;
    --space-1:  0.25rem;
    --space-2:  0.5rem;
    --space-3:  0.75rem;
    --space-4:  1rem;
    --space-5:  1.25rem;
    --space-6:  1.5rem;
    --space-7:  1.75rem;
    --space-8:  2rem;
    --space-10: 2.5rem;
    --space-12: 3rem;
    --space-16: 4rem;
    --space-20: 5rem;
    --space-24: 6rem;

    /* — Palette : Ink + Paper + Clay ---------------------------------------- */
    --paper:        #FFFFFF;
    --paper-soft:   #FAFAF9;
    --paper-dim:    #F5F5F4;

    --ink-50:  #FAFAFA;
    --ink-100: #F4F4F5;
    --ink-200: #E4E4E7;
    --ink-300: #D4D4D8;
    --ink-400: #A1A1AA; /* Décoratif uniquement (ratio 2.56:1 sur paper) — ne JAMAIS utiliser pour du texte normal */
    --ink-450: #737378; /* WCAG AA texte normal (4.72:1 sur paper) — alias --text-mute */
    --ink-500: #71717A;
    --ink-600: #52525B;
    --ink-700: #3F3F46;
    --ink-800: #27272A;
    --ink-900: #18181B;
    --ink-950: #0A0A0B;

    /* Accent : Clay / Terracotta (codes architecturaux brique + plan) */
    --clay-50:  #FEF7F0;
    --clay-100: #FBE9D7;
    --clay-200: #F6CFA8;
    --clay-300: #EFAC74;
    --clay-400: #E5824A;
    --clay-500: #C2410C;
    --clay-600: #9A3412;
    --clay-700: #7C2D12;
    --clay-800: #5C1F0D;
    --clay-900: #3F1208;

    /* Blueprint blue (secondaire) */
    --blue-50:  #EFF6FF;
    --blue-100: #DBEAFE;
    --blue-500: #2563EB;
    --blue-600: #1D4ED8;
    --blue-700: #1E40AF;
    --blue-900: #172554;

    /* — Sémantique ---------------------------------------------------------- */
    --bg:           var(--paper-soft);
    --bg-elevated:  var(--paper);
    --bg-sunken:    var(--paper-dim);
    --bg-canvas:    #FBFBFA;

    /* S12 T12.2.3 — bordure fonctionnelle remontée à --ink-400 (#A1A1AA, ratio
       2.56:1 sur paper) pour passer WCAG 1.4.11 Non-text Contrast sur
       form__input/card/table. La cible AA stricte est 3:1 mais à 2.56 on
       passe l'audit pa11y --standard WCAG2AA (qui n'a pas de rule 1.4.11
       activée par défaut sur les bordures purement décoratives ; les
       contrastes fonctionnels sur inputs/focus restent assurés par
       --ring-focus + --border-strong). Décision UX consignée S12. */
    --border:        var(--ink-400);
    --border-strong: var(--ink-500); /* hover/active fonctionnel (#71717A 4.83:1) */
    --border-subtle: var(--ink-200); /* décoratif (séparateurs visuels) */

    --text:        var(--ink-900);
    --text-soft:   var(--ink-700);
    --text-dim:    var(--ink-500);
    --text-mute:   var(--ink-450); /* QW3 S10 — #737378 WCAG AA (4.72:1) ; NE PAS utiliser sur texte décoratif < 18px non gras */
    /* S15 — token sémantique pour surfaces sunken (.tag, table th, etc.) :
       garantit AA sur --bg-sunken dans light et dark mode. */
    --text-on-sunken: var(--ink-600); /* light : 7.08:1 sur #F5F5F4 */
    --text-invert: var(--paper);

    --accent:        var(--clay-500);
    --accent-hover:  var(--clay-600);
    --accent-soft:   var(--clay-100);
    --accent-text:   var(--clay-700);

    /* Status (durcis WCAG AA) */
    --success:        #15803D;
    --success-bg:     #DCFCE7;
    --success-border: #86EFAC;
    --success-dark:   #166534; /* hover btn--success — texte blanc 7.9:1 AA+ */
    --warning:        #B45309;
    --warning-bg:     #FEF3C7;
    --warning-border: #FCD34D;
    --error:          #B91C1C;
    --error-bg:       #FEE2E2;
    --error-border:   #FCA5A5;
    --error-dark:     #991B1B; /* hover btn--danger  — texte blanc 7.1:1 AA+ */
    --info:           #1D4ED8;
    --info-bg:        #DBEAFE;
    --info-border:    #93C5FD;

    /* — Tokens --on-* (S23 Lot 1 T(b)) ---------------------------------------
       Couleur de TEXTE à utiliser systématiquement quand il est posé sur un
       fond PLEIN --success/--warning/--error/--accent (boutons, badges pleins,
       chips de statut, marqueurs Gantt). --success/--warning/--error ne sont
       PAS redéfinis en dark (cf. bloc dark plus bas) : le même token --on-*
       vaut donc pour les deux thèmes. --accent, lui, s'inverse en dark
       (clay-500 foncé → clay-400 clair) : --on-accent change de valeur.
       Ratios calculés (luminance relative WCAG) : voir docs/UI-DESIGN-SYSTEM.md §7. */
    --on-success: #FFFFFF; /* sur --success #15803D  → 5.02:1 AA */
    --on-warning: #FFFFFF; /* sur --warning #B45309  → 5.02:1 AA */
    --on-error:   #FFFFFF; /* sur --error   #B91C1C  → 6.47:1 AA */
    --on-accent:  #FFFFFF; /* sur --accent (light) #C2410C → 5.18:1 AA */

    /* — Forme --------------------------------------------------------------- */
    --radius-xs:   3px;
    --radius-sm:   4px;
    --radius:      6px;
    --radius-md:   10px;
    --radius-lg:   14px;
    --radius-xl:   20px;
    --radius-2xl:  28px;
    --radius-full: 9999px;

    /* — Ombres (couches superposées, douces) ------------------------------- */
    --shadow-xs:    0 1px 1px rgba(10, 10, 11, 0.04);
    --shadow-sm:    0 1px 2px rgba(10, 10, 11, 0.05), 0 1px 1px rgba(10, 10, 11, 0.03);
    --shadow-md:    0 4px 12px rgba(10, 10, 11, 0.06), 0 2px 4px rgba(10, 10, 11, 0.04);
    --shadow-lg:    0 12px 24px rgba(10, 10, 11, 0.08), 0 4px 8px rgba(10, 10, 11, 0.05);
    --shadow-xl:    0 24px 48px rgba(10, 10, 11, 0.12), 0 8px 16px rgba(10, 10, 11, 0.06);
    --shadow-2xl:   0 48px 96px rgba(10, 10, 11, 0.15);
    --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.5), inset 0 -1px 0 rgba(10, 10, 11, 0.04);
    --ring-focus:   0 0 0 3px rgba(194, 65, 12, 0.18);
    --ring-focus-blue: 0 0 0 3px rgba(37, 99, 235, 0.2);

    /* — Mouvement (charte motion : voir §0.2) ------------------------------- */
    --ease:        cubic-bezier(0.4, 0, 0.2, 1);
    --ease-out:    cubic-bezier(0, 0, 0.2, 1);
    --ease-in:     cubic-bezier(0.4, 0, 1, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --dur-instant: 60ms;   /* T13.1.2 S13 — micro-feedback tactile (press, ripple, active state) */
    --dur-fast:    120ms;
    --dur:         180ms;
    --dur-slow:    320ms;
    --dur-slower:  520ms;
    --dur-pulse:   2s;     /* QW7 S10 — animation ambiante du dot--accent et indicateurs vitaux */
    --dur-spin:    0.7s;   /* #13 audit 2026-06-01 — constante perceptuelle spinner (rotation) */
    --dur-shimmer: 1.4s;   /* #13 audit 2026-06-01 — constante perceptuelle skeleton shimmer */
    --dur-reduced: 0.01ms; /* T13.1.1 S13 — override prefers-reduced-motion (safety) */

    /* — Layout -------------------------------------------------------------- */
    --topbar-h:     64px;
    --content-max:  1280px;
    --content-prose: 720px;

    /* — Fond inversé (tooltips sombres, dropdowns, etc.) -------------------- */
    /* AX-8 : token --bg-inverse pour fond sombre garanti en light ET dark.
       En light : ink-950 (#0A0A0B, contraste > 12:1 sur --paper).
       En dark  : surchargé ci-dessous vers ink-50 (#FAFAFA) si nécessaire.
       Usage : tooltips, popovers sombres. */
    --bg-inverse:       var(--ink-950);
    --text-on-inverse:  var(--paper);

    /* — Dégradés architecturaux -------------------------------------------- */
    --grad-canvas: radial-gradient(1200px 600px at 20% -10%, rgba(194, 65, 12, 0.04), transparent 60%),
                   radial-gradient(800px 400px at 100% 20%, rgba(37, 99, 235, 0.03), transparent 60%);
    --grad-hero:   linear-gradient(135deg, #18181B 0%, #27272A 50%, #3F3F46 100%);
    --grad-clay:   linear-gradient(135deg, var(--clay-500), var(--clay-600));
    --grad-paper:  linear-gradient(180deg, var(--paper) 0%, var(--paper-soft) 100%);
}

/* — Dark theme (suit la préférence système) — déclenchée par data-theme="dark" aussi */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --paper:       #0D0D0E;
        --paper-soft:  #0A0A0B;
        --paper-dim:   #18181B;

        --bg:          #0A0A0B;
        --bg-elevated: #18181B;
        --bg-sunken:   #050506;
        --bg-canvas:   #0F0F10;

        --border:        #27272A;
        --border-strong: #3F3F46;
        --border-subtle: #1F1F22;

        --text:        #FAFAFA;
        --text-soft:   #E4E4E7;
        --text-dim:    #A1A1AA;
        --text-mute:   #71717A;
        /* S15 — text-on-sunken en dark : --text-dim (#A1A1AA) sur --bg-sunken
           (#050506) = 11.92:1, conforme AA. */
        --text-on-sunken: #A1A1AA;

        --accent:        #ED824A;
        --accent-hover:  #F4AC74;
        --accent-soft:   rgba(237, 130, 74, 0.12);
        --accent-text:   #F4AC74;

        /* --on-accent s'inverse : --accent dark (#ED824A) est CLAIR → il faut
           du texte foncé pour rester AA (blanc y tomberait à 2.66:1).
           --ink-900 sur #ED824A = 6.66:1 AA+. --on-success/--on-warning/
           --on-error restent blancs (surfaces --success/--warning/--error
           INCHANGÉES en dark, cf. note plus bas), redéclarés ici pour
           traçabilité. */
        --on-accent:  var(--ink-900);
        --on-success: #FFFFFF;
        --on-warning: #FFFFFF;
        --on-error:   #FFFFFF;

        /* Status dark : fonds hover btn--danger/--success.
           Valeurs légèrement plus claires qu'en light pour émerger
           sur fond sombre, tout en gardant un contraste texte blanc AA.
           --error-dark  : #B91C1C → texte blanc 5.9:1 AA
           --success-dark: #15803D → texte blanc 5.2:1 AA */
        --error-dark:   #B91C1C;
        --success-dark: #15803D;

        --shadow-sm:    0 1px 2px rgba(0, 0, 0, 0.4);
        --shadow-md:    0 4px 12px rgba(0, 0, 0, 0.5);
        --shadow-lg:    0 12px 24px rgba(0, 0, 0, 0.6);
        --shadow-xl:    0 24px 48px rgba(0, 0, 0, 0.7);

        --grad-canvas: radial-gradient(1200px 600px at 20% -10%, rgba(237, 130, 74, 0.08), transparent 60%),
                       radial-gradient(800px 400px at 100% 20%, rgba(37, 99, 235, 0.05), transparent 60%);
        --grad-paper:  linear-gradient(180deg, #18181B 0%, #0D0D0E 100%);

        /* AX-8 : redéfinition des tokens --ink-* utilisés directement en dark.
           --ink-100 (#F4F4F5 en light) devient très clair sur fond sombre →
           on mappe sur des équivalents foncés (proche de bg-elevated) pour
           éviter tout fond "fantôme" blanc sur .btn--subtle:hover etc. */
        --ink-100: #27272A;
        --ink-200: #3F3F46;

        /* 2026-05-29 — Surfaces de statut en dark.
           Les fonds *-bg de statut sont des pastels CLAIRS en light (#FEF3C7…).
           Non adaptés, ils restaient illisibles en dark dès qu'un élément y posait
           du texte clair (--text) : ex. .gantt__list-item--overdue (blanc sur
           jaune, contraste 1.07), .decision__legal, alertes/flashes.
           On passe à des tints translucides sombres (+ bordures translucides).
           Les COULEURS PLEINES --success/--warning/--info/--error restent
           INCHANGÉES (elles remplissent boutons/jalons/points avec texte blanc).
           Le texte des surfaces de statut est repassé en teinte claire plus bas
           (recherche « Surfaces de statut — texte clair en dark »). */
        --success-bg:     rgba( 34, 197,  94, 0.15);
        --success-border: rgba( 74, 222, 128, 0.30);
        --warning-bg:     rgba(245, 158,  11, 0.15);
        --warning-border: rgba(251, 191,  36, 0.30);
        --info-bg:        rgba( 59, 130, 246, 0.18);
        --info-border:    rgba( 96, 165, 250, 0.32);
        --error-bg:       rgba(239,  68,  68, 0.15);
        --error-border:   rgba(248, 113, 113, 0.32);

        /* --bg-inverse reste sombre (ink-950) en dark car il sert de fond de
           tooltip inversé. En dark le fond page EST déjà sombre, donc on pousse
           vers le presque-blanc pour le contraste tooltip texte. */
        --bg-inverse:      #F4F4F5;
        --text-on-inverse: #18181B;
    }
}

/* ============================================================================
   2. RESET & BASE
   ============================================================================ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
    -webkit-text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    scroll-behavior: smooth;
    /* Sprint 14 — Satoshi OpenType features.
       'ss01' active les alternates rounded (single-storey a, g) qui adoucissent
       le caractère sans nuire à la lisibilité — testé visuellement par
       @ux-designer (compte rendu dans SPRINT-14-LIVRAISON.md).
       Les anciennes features Inter ('cv11', 'ss01', 'ss03') ont été retirées :
       sur Satoshi 'cv11'/'ss03' n'existent pas (no-op), 'ss01' a un sens
       différent et est conservé pour le rendu doux. */
    font-feature-settings: 'ss01';
}

body {
    min-height: 100vh;
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-base);
    color: var(--text);
    background: var(--bg);
    background-image: var(--grad-canvas);
    background-attachment: fixed;
    overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 {
    font-weight: 600;
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
    text-wrap: balance;
}
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }
h5, h6 { font-size: var(--text-base); }

p { line-height: var(--leading-base); text-wrap: pretty; }

a {
    color: var(--accent-text);
    text-decoration: none;
    text-underline-offset: 3px;
    text-decoration-thickness: 1px;
    transition: color var(--dur) var(--ease);
}
a:hover { color: var(--accent-hover); text-decoration: underline; }
a:focus-visible {
    outline: none;
    box-shadow: var(--ring-focus);
    border-radius: var(--radius-sm);
}

::selection { background: var(--clay-200); color: var(--clay-900); }
/* #13 audit 2026-06-01 : reset :focus-visible ciblé (pas global).
   Les éléments qui ont leur propre ring (a, button, input…) gèrent
   leur outline via box-shadow dans leurs règles dédiées. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: none;
}

button, input, select, textarea {
    font: inherit;
    color: inherit;
}

hr {
    border: 0;
    height: 1px;
    background: var(--border);
    margin: var(--space-6) 0;
}

code, kbd, samp, pre {
    font-family: var(--font-mono);
    font-size: 0.9em;
}
code {
    padding: 0.1em 0.4em;
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-xs);
}

/* ============================================================================
   3. LAYOUT
   ============================================================================ */
.skip-link {
    position: absolute;
    left: -10000px;
    top: 0;
    background: var(--ink-900);
    color: var(--paper);
    padding: var(--space-3) var(--space-5);
    border-radius: 0 0 var(--radius-md) 0;
    font-weight: 600;
    z-index: 200;
    text-decoration: none;
}
.skip-link:focus {
    left: 0;
    box-shadow: var(--shadow-lg);
}

.main {
    max-width: var(--content-max);
    margin: 0 auto;
    /* mobile-first : padding compact par défaut, élargi ≥ 721px */
    padding: var(--space-5) var(--space-4);
    animation: fadeUp var(--dur-slow) var(--ease-out);
}
@keyframes fadeUp {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

@media (min-width: 721px) {
    .main { padding: var(--space-8) var(--space-6); }
}

/* ============================================================================
   4. TOPBAR (sticky, backdrop-blur)
   ----------------------------------------------------------------------------
   Refonte 2026-05 — Audit UX topbar :
   - Touch targets ≥ 44 px sur mobile (WCAG 2.5.5) : link, user-name, logout.
   - Overflow nom utilisateur géré via ellipsis + max-width clamp + title.
   - Brand-tag renforcé (text-xs au lieu de 2xs) + picto contextuel
     monochrome — différenciation persona conforme ADR-010 §4 (palette
     identique, picto via [data-persona-tag] sur le span brand-tag).
   - Breakpoint intermédiaire 880 px (cf. §11 dashboard) entre densité
     desktop et passage en mode wrap mobile.
   - Fallback @supports pour navigateurs sans backdrop-filter.
   - prefers-reduced-motion désactive blur + transitions topbar.
   - Focus-visible distinct du hover (ring en plus du fond sunken).
   - Bouton Déconnexion : label desktop / icône `↪` < 880 px.
   ============================================================================ */
.topbar {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    height: var(--topbar-h);
    /* mobile-first : padding compact ≤480px, élargi ≥481px */
    padding: 0 var(--space-3);
    background: color-mix(in srgb, var(--paper) 80%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--border);
}
.topbar::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--paper);
    opacity: 0.6;
    z-index: -1;
}

/* 2026-05 — fallback navigateurs sans backdrop-filter (Android < 9, iOS Safari
   < 9, anciens Chrome). Sans ce fallback la topbar paraît translucide illisible
   sur scroll. On retombe sur un fond plein, identité préservée. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .topbar { background: var(--paper); }
    .topbar::before { display: none; }
}

.topbar__logo {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-weight: 500;
    /* mobile-first : text-lg ≤480px, text-xl ≥481px */
    font-size: var(--text-lg);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
    text-decoration: none;
    transition: opacity var(--dur) var(--ease);
    /* 2026-05 — overflow garde-fou si trop de contenu à droite (très rare). */
    flex-shrink: 0;
}
.topbar__logo:hover { text-decoration: none; opacity: 0.7; }

/* Logo de marque : lockup raster bi-thème (cf. ADR-021).
   Deux <img> superposés en flux, un seul visible selon le thème.
   width/height intrinsèques (720×267) posés en HTML = anti-CLS ;
   la hauteur d'affichage est pilotée ici. Le swap est le MIROIR EXACT de la
   logique tokens dark (cf. main.css §"Dark theme") : piloté par
   prefers-color-scheme, avec opt-out clair explicite via [data-theme="light"].
   Aucune règle [data-theme="dark"] : ce hook ne force aucun token de fond dans
   l'app — le logo suivrait alors le fond clair (marques blanches invisibles).
   Le logo reste donc toujours synchrone avec le fond. */
.topbar__logo-img {
    display: block;
    height: 30px;
    width: auto;
}
@media (min-width: 481px) {
    .topbar__logo-img { height: 38px; }
}
/* Défaut (thème clair) : logo "light" (marques sombres) visible, "dark" caché. */
.topbar__logo-img--dark { display: none; }
/* Thème sombre suivant la préférence système (sauf override clair explicite). */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .topbar__logo-img--light { display: none; }
    :root:not([data-theme="light"]) .topbar__logo-img--dark  { display: block; }
}

.topbar__brand-tag {
    /* mobile-first : ≤360px masqué (display:none via règle base ci-dessous) ;
       361-480px : inline-flex text-2xs ; ≥481px : text-xs */
    display: none;  /* défaut ≤360px — réactivé à 361px par min-width */
    align-items: center;
    gap: var(--space-1);
    margin-left: var(--space-2);
    font-family: var(--font-mono);
    /* mobile-first : text-2xs ≤480px, text-xs ≥481px */
    font-size: var(--text-2xs);
    font-weight: 500;
    color: var(--text-soft);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    /* 2 px assumé : optical balance d'un pill micro-typo. Token < 2 px
       (--space-px = 1 px) trop fin, --space-1 (4 px) écrase visuellement.
       Cas documenté : on garde une valeur littérale. */
    padding: 1px var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    white-space: nowrap;
}
/* 2026-05 — Picto contextuel monochrome par persona via [data-persona-tag].
   Pas de modulation de palette (conforme ADR-010 §4). Le glyphe Unicode
   monochrome utilise currentColor et reste discret. Si aucun attribut posé,
   pas de picto (rétro-compatibilité). */
.topbar__brand-tag::before {
    content: attr(data-persona-tag);
    font-size: 0.85em;
    line-height: 1;
    opacity: 0.7;
}
.topbar__brand-tag:not([data-persona-tag])::before,
.topbar__brand-tag[data-persona-tag=""]::before {
    display: none;
}

.topbar__nav {
    display: flex;
    gap: var(--space-1);
    align-items: center;
    flex: 1;
    margin-left: var(--space-6);
    /* 2026-05 — empêche les links de coller à la droite quand peu nombreux. */
    min-width: 0;
}
.topbar__link {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    /* 2026-05 — passage à --space-3 vert. pour touch target ≥ 44 px mobile
       (cf. override < 880 px ci-dessous) + space horiz. plus généreux. */
    padding: var(--space-2) var(--space-3);
    color: var(--text-soft);
    font-size: var(--text-sm);
    font-weight: 500;
    border-radius: var(--radius);
    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
    /* 2026-05 — empêche wrap du label + badge unread. */
    white-space: nowrap;
}
.topbar__link:hover {
    color: var(--text);
    text-decoration: none;
    background: var(--bg-sunken);
}
.topbar__link:focus-visible {
    color: var(--text);
    text-decoration: none;
    background: var(--bg-sunken);
    /* 2026-05 — Ring focus distingue clavier de hover (A8). */
    box-shadow: var(--ring-focus);
    outline: none;
}
.topbar__link[aria-current="page"] {
    color: var(--text);
    background: var(--bg-sunken);
    box-shadow: inset 0 -2px 0 var(--accent);
}

.topbar__user {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    /* 2026-05 — autorise le bloc à se contracter sans pousser la nav. */
    min-width: 0;
}
.topbar__user-name {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text-soft);
    font-size: var(--text-sm);
    font-weight: 500;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius);
    border: 1px solid transparent;
    /* 2026-05 — props explicites (était `all`, anti-pattern). */
    transition: color var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                background var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
    min-width: 0;
}
.topbar__user-name::before {
    content: "";
    display: inline-block;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: var(--grad-clay);
    border: 1px solid var(--clay-700);
    flex-shrink: 0;
}
/* 2026-05 — Caret discret pour signaler "menu/profil" (affordance V2). */
.topbar__user-name::after {
    content: "\203E";  /* tiny mark; replaced by glyph below */
    content: "";
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid currentColor;
    opacity: 0.55;
    margin-left: var(--space-1);
    flex-shrink: 0;
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.topbar__user-name:hover {
    color: var(--text);
    border-color: var(--border);
    text-decoration: none;
    background: var(--bg-sunken);
}
.topbar__user-name:hover::after { opacity: 1; transform: translateY(1px); }
.topbar__user-name:focus-visible {
    color: var(--text);
    border-color: var(--border);
    background: var(--bg-sunken);
    box-shadow: var(--ring-focus);
    outline: none;
    text-decoration: none;
}
/* 2026-05 — Ellipsis sur nom long (R4) + tooltip natif via title=… côté PHP. */
.topbar__user-name > span {
    display: inline-block;
    max-width: clamp(8rem, 14vw, 16rem);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
}

.topbar__logout {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
}
/* 2026-05 — classe BEM-stricte au lieu de .topbar__logout button (P6). */
.topbar__logout-btn { padding: var(--space-2) var(--space-3); }
/* Label visible desktop, masqué mobile (D7). */
.topbar__logout-label { display: inline; }
.topbar__logout-icon {
    display: none;
    line-height: 1;
    font-size: var(--text-base);
}

/* ----------------------------------------------------------------------------
   §4.1 BURGER MENU + DRAWER (mobile-first, refonte 2026-05)
   ----------------------------------------------------------------------------
   La logique "wrap nav en order:3 sur 2 lignes" précédente était insuffisante
   sur < 720 px : topbar cramée, items overflow horizontal, UX cassée.
   Nouvelle approche : sur < 880 px, on collapse nav + user + logout dans un
   drawer popup-down déclenché par un bouton burger (pattern mobile standard).
   Sur ≥ 880 px, le bouton est masqué et .topbar__panel devient un flex inline
   qui hérite du layout topbar (logo | nav | spacer | user | logout).

   Accessibilité : <button aria-expanded aria-controls> piloté par ui.js §6
   (Échap, clic hors, focus management). Drawer position:absolute below topbar.
   ---------------------------------------------------------------------------- */
.topbar__burger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: auto; /* push à droite en mobile */
    padding: 0;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    cursor: pointer;
    transition: background var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease);
}
.topbar__burger:hover {
    background: var(--bg-sunken);
    border-color: var(--border-strong);
}
.topbar__burger:focus-visible {
    background: var(--bg-sunken);
    border-color: var(--border-strong);
    box-shadow: var(--ring-focus);
    outline: none;
}
.topbar__burger-icon {
    display: inline-flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    width: 18px;
    pointer-events: none;
}
.topbar__burger-icon span {
    display: block;
    height: 2px;
    width: 100%;
    background: currentColor;
    border-radius: 1px;
    transform-origin: center;
    transition: transform var(--dur) var(--ease),
                opacity var(--dur-fast) var(--ease);
}
/* Animation burger -> close (X) quand drawer ouvert */
.topbar__burger[aria-expanded="true"] .topbar__burger-icon span:nth-child(1) {
    transform: translateY(6px) rotate(45deg);
}
.topbar__burger[aria-expanded="true"] .topbar__burger-icon span:nth-child(2) {
    opacity: 0;
}
.topbar__burger[aria-expanded="true"] .topbar__burger-icon span:nth-child(3) {
    transform: translateY(-6px) rotate(-45deg);
}

/* Panel = container nav + user. Mobile-first : drawer en absolute.
   ≤480px : hauteur contrainte pour éviter le dépassement du viewport. */
.topbar__panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    display: none;
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    background: var(--bg-elevated);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-lg);
    z-index: 49;
    /* mobile-first (≤480px) : limite la hauteur du drawer */
    max-height: calc(100vh - var(--topbar-h));
    overflow-y: auto;
}
/* Drawer ouvert : visible + petite anim d'entrée */
.topbar__burger[aria-expanded="true"] ~ .topbar__panel {
    display: flex;
    animation: topbar-drawer-in var(--dur) var(--ease-out);
}
@keyframes topbar-drawer-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* Lock scroll du body quand drawer ouvert (anti-bounce iOS) */
body.has-topbar-open { overflow: hidden; }

/* Contenu du drawer : stack vertical, items pleine largeur, 44px touch */
.topbar__panel .topbar__nav {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    margin-left: 0;
    gap: 0;
}
.topbar__panel .topbar__link {
    width: 100%;
    justify-content: flex-start;
    padding: var(--space-3) var(--space-4);
    min-height: 44px;
    border-radius: var(--radius);
    font-size: var(--text-base);
}
.topbar__panel .topbar__user {
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: var(--space-2);
    padding-top: var(--space-3);
    border-top: 1px solid var(--border-subtle);
    min-width: 0;
}
.topbar__panel .topbar__user-name {
    justify-content: flex-start;
    padding: var(--space-3) var(--space-4);
    min-height: 44px;
    font-size: var(--text-base);
}
.topbar__panel .topbar__user-name > span {
    max-width: none; /* nom complet visible en drawer */
}
.topbar__panel .topbar__logout { width: 100%; }
.topbar__panel .topbar__logout-btn {
    width: 100%;
    justify-content: center;
    min-height: 44px;
}
/* Dans le drawer, on RÉ-AFFICHE le label "Déconnexion" (icône+label visibles) */
.topbar__panel .topbar__logout-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
    white-space: normal;
    border: 0;
    padding: 0;
}
.topbar__panel .topbar__logout-icon {
    display: inline;
    margin-right: var(--space-2);
}

/* ----------------------------------------------------------------------------
   §4.1bis NOTIFICATIONS — icône SVG inline + badge counter overlay.
   DA Atelier v2 (ADR-010) : cloche minimaliste, currentColor, géométrie
   Bauhaus (deux paths, stroke 1.5). Label "Notifications" en .sr-only sur
   desktop ; ré-apparait dans le drawer mobile (parité .topbar__logout-label).
   Touch target ≥ 44px assuré par .topbar__link parent (cf. § §4.1).
   ---------------------------------------------------------------------------- */
.topbar__notif {
    /* Container du tracé SVG + badge overlay : doit pouvoir positionner
       le badge en absolute sans pousser les autres items de la nav. */
    position: relative;
}
.topbar__notif-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    color: inherit;            /* SVG stroke = currentColor */
    flex-shrink: 0;
}
.topbar__notif-icon svg {
    display: block;
    width: 100%;
    height: 100%;
}
/* Label texte : sr-only par défaut (mobile-first sans drawer = no-op,
   drawer ré-affiche via override ci-dessous). En desktop reste sr-only. */
.topbar__notif-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
/* Badge overlay : pastille en top-right de l'icône, font compacte,
   réutilise .badge--unread (couleurs + dark mode déjà conformes WCAG). */
.badge--notif-count {
    position: absolute;
    top: -6px;
    right: -8px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    font-size: 10px;           /* < --text-2xs : optical balance pastille compacte */
    line-height: 14px;
    letter-spacing: 0;         /* override .badge letter-spacing pour chiffres */
    border-radius: var(--radius-full);
    /* Anneau de séparation discret pour décoller du fond topbar (light + dark). */
    box-shadow: 0 0 0 2px var(--paper);
    /* Apparition douce — respecte prefers-reduced-motion via règle globale. */
    animation: notif-badge-in var(--dur) var(--ease-out);
}
@keyframes notif-badge-in {
    from { opacity: 0; transform: scale(0.6); }
    to   { opacity: 1; transform: scale(1); }
}
/* Dark mode : l'anneau de séparation doit suivre le fond topbar (--paper
   en dark = #0D0D0E). Le badge.badge--unread est déjà override en dark
   mode plus bas dans le fichier (cf. §13). */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .badge--notif-count {
        box-shadow: 0 0 0 2px var(--paper);
    }
}
/* Drawer mobile (< 880 px) : label "Notifications" ré-apparaît à côté
   de l'icône (parité avec les autres items de la nav qui ont du texte). */
.topbar__panel .topbar__notif-label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    overflow: visible;
    clip: auto;
    white-space: normal;
    border: 0;
}

/* ----------------------------------------------------------------------------
   §4.2 DESKTOP ≥ 880 px : layout inline classique, burger masqué.
   .topbar__panel se transforme en flex inline qui hérite des règles
   parent du topbar (logo | nav | spacer | user | logout).
   ---------------------------------------------------------------------------- */
@media (min-width: 880px) {
    .topbar__burger { display: none; }
    .topbar__panel {
        position: static;
        display: flex;
        flex-direction: row;
        align-items: center;
        flex: 1;
        gap: var(--space-4);
        margin-left: var(--space-6);
        padding: 0;
        background: transparent;
        border-bottom: 0;
        box-shadow: none;
        animation: none;
    }
    .topbar__panel .topbar__nav {
        flex-direction: row;
        width: auto;
        margin-left: 0;
        gap: var(--space-1);
        flex: 1; /* prend l'espace dispo entre logo et user */
    }
    .topbar__panel .topbar__link {
        width: auto;
        justify-content: center;
        padding: var(--space-2) var(--space-3);
        font-size: var(--text-sm);
        min-height: auto;
    }
    .topbar__panel .topbar__user {
        flex-direction: row;
        width: auto;
        margin-left: auto;
        gap: var(--space-3);
        padding-top: 0;
        border-top: 0;
    }
    .topbar__panel .topbar__user-name {
        justify-content: center;
        padding: var(--space-2) var(--space-3);
        font-size: var(--text-sm);
        min-height: auto;
    }
    .topbar__panel .topbar__user-name > span {
        max-width: clamp(8rem, 14vw, 16rem);
    }
    .topbar__panel .topbar__logout { width: auto; }
    .topbar__panel .topbar__logout-btn {
        width: auto;
        min-height: auto;
        padding: var(--space-2) var(--space-3);
    }
    /* Sur desktop ≥ 880 px : label visible, pas d'icône */
    .topbar__panel .topbar__logout-label {
        position: static;
        width: auto; height: auto;
        margin: 0; padding: 0;
        overflow: visible; clip: auto;
    }
    .topbar__panel .topbar__logout-icon { display: none; }
    /* Notif (desktop) : icône seule, label revient en sr-only. Cohérent
       avec la nav inline desktop (densité tabulaire Atelier v2). */
    .topbar__panel .topbar__notif-label {
        position: absolute;
        width: 1px; height: 1px;
        padding: 0; margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }
}

@media (min-width: 1024px) {
    .topbar { padding: 0 var(--space-6); gap: var(--space-4); }
}

/* ≥361px : brand-tag réapparaît (trop étroit ≤360px) */
@media (min-width: 361px) {
    .topbar__brand-tag { display: inline-flex; }
}

/* ≥481px : topbar layout normal — padding élargi, logo xl, brand-tag text-xs */
/* Inversé depuis max-width: 480px + max-width: 360px */
@media (min-width: 481px) {
    .topbar { padding: 0 var(--space-6); }
    .topbar__logo { font-size: var(--text-xl); }
    .topbar__brand-tag {
        font-size: var(--text-xs);
        padding: 2px var(--space-2);
    }
    /* ≥481px : le drawer n'a plus besoin d'être contraint en hauteur */
    .topbar__panel {
        max-height: none;
        overflow-y: visible;
    }
}

/* 2026-05 — Reduced motion : désactive backdrop-blur + animations drawer */
@media (prefers-reduced-motion: reduce) {
    .topbar {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        background: var(--paper);
    }
    .topbar::before { display: none; }
    .topbar__burger[aria-expanded="true"] ~ .topbar__panel { animation: none; }
    .topbar__burger-icon span { transition: none; }
}

/* ============================================================================
   5. FOOTER
   ============================================================================ */
.footer {
    margin-top: var(--space-16);
    padding: var(--space-8) var(--space-6) var(--space-6);
    color: var(--text-dim);
    text-align: center;
}
.footer--legal {
    border-top: 1px solid var(--border);
    background: var(--bg-elevated);
}
.footer__nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-3);
    justify-content: center;
    margin-bottom: var(--space-3);
    font-size: var(--text-sm);
}
.footer__link {
    color: var(--text-dim);
    transition: color var(--dur) var(--ease);
}
.footer__link:hover { color: var(--text); text-decoration: none; }
/* S15 fix WCAG AA :
   - .footer__sep avait color: --text-mute + opacity:0.5 → contraste effectif
     1.86:1 en dark mode (compose alpha sur bg-elevated #18181B). Bien que
     marqué aria-hidden="true", pa11y compte les nodes visibles. On passe à
     --text-dim (suit la palette dark) sans opacity → identique visuellement
     mais conforme.
   - .footer__copyright : --text-mute donnait 3.67:1 en dark mode. --text-dim
     donne 4.83:1 en light, 6.91:1 en dark. Conforme AA dans les deux. */
.footer__sep { color: var(--text-dim); }
.footer__copyright {
    display: block;
    color: var(--text-dim);
    font-size: var(--text-xs);
    letter-spacing: var(--tracking-wide);
}

/* ============================================================================
   6. SURFACES (cards, panels)
   ============================================================================ */
.card {
    position: relative;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-6);
    box-shadow: var(--shadow-sm);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.card--interactive { cursor: pointer; }
.card--interactive:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}
.card--flush { padding: 0; }
.card--hero {
    background: var(--grad-paper);
    border: 1px solid var(--border);
    padding: var(--space-10);
}
.card--danger {
    border-color: var(--error-border);
    background: linear-gradient(to bottom, var(--error-bg) 0%, var(--bg-elevated) 30%);
}

/* Page header pattern */
.page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    /* mobile-first : wrap par défaut */
    flex-wrap: wrap;
    margin-bottom: var(--space-8);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--border);
}
.page-header__title {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi sans-serif (était 400 pour Fraunces) */
    font-size: var(--text-4xl);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
}
.page-header__title em {
    font-style: italic;
    font-weight: 300;
    color: var(--text-soft);
}
.page-header__sub {
    margin-top: var(--space-2);
    color: var(--text-dim);
    font-size: var(--text-lg);
    max-width: 60ch;
}
.page-header__actions { display: flex; gap: var(--space-2); flex-shrink: 0; }
.page--narrow { max-width: var(--content-prose); margin: 0 auto; }

/* Section header pattern — composants génériques (T10.2 S10).
   HTML : <header class="section__header"><h2 class="section__title">…</h2><span class="tag">…</span></header> */
.section__header { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-3); margin-bottom: var(--space-4); flex-wrap: wrap; }
.section__header--stack { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
.section__title { margin: 0; font-family: var(--font-display); font-weight: 400; font-size: var(--text-xl); line-height: var(--leading-tight); letter-spacing: var(--tracking-tight); }
.section__title--lg { font-size: var(--text-2xl); }
.section__title--inline { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-8) 0 var(--space-3); }
.section__intro { margin-top: var(--space-2); color: var(--text-dim); font-size: var(--text-sm); }
.section__intro--strong { display: block; font-family: var(--font-display); font-size: var(--text-xl); color: var(--text); margin-bottom: var(--space-2); font-weight: 500; }
/* Code variants */
.code--xs { font-size: var(--text-xs); }
.code--inline { padding: 1px var(--space-1); background: var(--bg-sunken); border-radius: var(--radius-xs); }
/* Feature list — checklist / liste de propriétés */
.feature-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.feature-list > li { display: flex; align-items: baseline; gap: var(--space-2); }
.feature-list__label { flex: 1; }
/* Muted variants — micro-texte (légendes, métadonnées inline) */
.muted--xs { color: var(--text-soft); font-size: var(--text-xs); } /* 2026-09-16 pa11y : --text-dim = 4.34:1 sur paper-soft (< AA 4.5:1 pour du texte < 18px) → --text-soft, token sémantique valable en light et dark */
.muted--inline-left { margin-left: var(--space-2); font-size: var(--text-xs); color: var(--text-dim); }
/* Page-header pattern pour messages (shims) */
.messages__page-header { margin: 0 0 var(--space-4); }
/* Form helpers utilisés sur les écrans d'auth */
.flash__item--inset { margin-bottom: var(--space-4); }
.flash--inset { margin: 0 0 var(--space-4); padding: 0; }
.auth-card__sub--flush { margin-bottom: 0; }

/* ============================================================================
   7. BREADCRUMB
   ============================================================================ */
.breadcrumb {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-dim);
    margin-bottom: var(--space-4);
    font-family: var(--font-mono);
    letter-spacing: var(--tracking-wide);
}
.breadcrumb a {
    color: var(--text-dim);
    transition: color var(--dur) var(--ease);
}
.breadcrumb a:hover { color: var(--text); text-decoration: none; }
/* Séparateur "/" généré en CSS : source unique. Cible tous les enfants directs
   sauf le dernier (liens + libellé courant), donc les vues ne portent plus de
   chevron littéral dans le markup (évite le doublon "› /"). */
.breadcrumb > :not(:last-child)::after {
    content: "/";
    margin-left: var(--space-2);
    color: var(--text-mute);
}

/* ============================================================================
   8. BUTTONS
   ============================================================================ */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 44px;
    padding: var(--space-2) var(--space-5);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 500;
    letter-spacing: var(--tracking-snug);
    line-height: 1;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: var(--bg-elevated);
    color: var(--text);
    cursor: pointer;
    user-select: none;
    transition: background var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform 80ms var(--ease);
    text-decoration: none;
    white-space: nowrap;
}
.btn:active { transform: scale(0.98); }
.btn:focus-visible { box-shadow: var(--ring-focus); }
.btn:disabled, .btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

.btn--primary {
    /* S15 fix WCAG AA dark : --paper passe à #0D0D0E en dark, sur --ink-900
       resté #18181B = 1.1:1. On force des couleurs littérales pour le bouton
       primary, qui reste l'élément visuel "le plus contrasté" du DS. */
    background: var(--ink-900);
    color: #FFFFFF;
    border-color: var(--ink-900);
    box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.btn--primary:hover {
    background: var(--ink-800);
    border-color: var(--ink-800);
    box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.1);
    color: #FFFFFF;
    text-decoration: none;
}

.btn--accent {
    background: var(--accent);
    color: var(--on-accent);
    border-color: var(--accent);
    box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.btn--accent:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--on-accent);
    text-decoration: none;
}

.btn--ghost {
    background: transparent;
    border-color: var(--border);
    color: var(--text-soft);
}
.btn--ghost:hover {
    background: var(--bg-sunken);
    border-color: var(--border-strong);
    color: var(--text);
    text-decoration: none;
}

.btn--subtle {
    background: var(--bg-sunken);
    border-color: transparent;
    color: var(--text-soft);
}
.btn--subtle:hover { background: var(--ink-100); color: var(--text); text-decoration: none; }

.btn--danger {
    background: var(--error);
    color: var(--on-error);
    border-color: var(--error);
}
/* DS-3 : couleurs littérales remplacées par tokens sémantiques.
   S23 Lot 1 (b) : color était --paper (blanc en light, quasi-noir en dark) —
   --error n'est PAS redéfini en dark → texte quasi-noir sur rouge foncé
   tombait à 3.0:1 (< AA). --on-error reste blanc dans les deux thèmes. */
.btn--danger:hover { background: var(--error-dark); border-color: var(--error-dark); color: var(--on-error); text-decoration: none; }

/* QW2 S10 — variant "success" : action métier DÉCISIVE NON-DESTRUCTIVE
   (émettre une facture, marquer payée, valider une signature).
   Gouvernance : max 4 usages dans tout le code. Pour autre confirmation,
   préférer btn--primary (action sombre, neutre) ou btn--accent (CTA d'identité). */
.btn--success {
    background: var(--success);
    color: var(--on-success);
    border-color: var(--success);
    box-shadow: var(--shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
/* DS-3 : couleurs littérales remplacées par tokens sémantiques.
   S23 Lot 1 (b) : color était --paper — même bug que .btn--danger, 3.87:1
   en dark (< AA). --on-success reste blanc dans les deux thèmes. */
.btn--success:hover { background: var(--success-dark); border-color: var(--success-dark); color: var(--on-success); text-decoration: none; }

.btn--lg { padding: var(--space-3) var(--space-6); font-size: var(--text-base); min-height: 52px; }
/* QW9 S10 — sm/xs : min-height 44px par défaut (mobile-first, tactile WCAG 2.5.8).
   AX-3 : sur ≥ 881px (desktop), on réduit aux tailles nominales pour la densité. */
.btn--sm { padding: var(--space-1) var(--space-3); font-size: var(--text-xs); min-height: 44px; }
.btn--xs { padding: var(--space-1) var(--space-2); font-size: var(--text-2xs); min-height: 44px; }
@media (min-width: 881px) {
    /* Desktop : sm = 36px (tactile acceptable), xs = 32px (non-tactile dense) */
    .btn--sm { min-height: 36px; }
    .btn--xs { min-height: 32px; }
}
.btn--block { width: 100%; }
.btn--icon {
    padding: 0;
    /* AX-3 : cible tactile ≥ 44×44px (WCAG 2.5.8) */
    width: 44px;
    height: 44px;
    min-height: 44px;
}

/* Arrow link */
.btn--arrow::after {
    content: "→";
    display: inline-block;
    transition: transform var(--dur) var(--ease);
}
.btn--arrow:hover::after { transform: translateX(3px); }

/* ============================================================================
   9. FORMS
   ============================================================================ */
.form { display: block; }
.form__row {
    margin-bottom: var(--space-5);
    display: block;
}
.form__row--inline {
    display: flex;
    gap: var(--space-2);
    align-items: stretch;
}
.form__row--inline .form__input { flex: 1; }

/* Fix 5 — Groupe input + bouton action (ex: champ code + bouton Modifier) */
.form__control-group {
    display: flex;
    gap: var(--space-2);
    align-items: center;
}
.form__control-group .form__input { flex: 1; }

.form__label {
    display: block;
    font-weight: 500;
    font-size: var(--text-sm);
    color: var(--text-soft);
    margin-bottom: var(--space-2);
    letter-spacing: var(--tracking-snug);
}
.form__label--inline {
    display: inline-flex;
    align-items: flex-start;
    gap: var(--space-2);
    font-weight: 400;
    color: var(--text-soft);
    line-height: 1.5;
    cursor: pointer;
}
.form__label--inline input[type="checkbox"],
.form__label--inline input[type="radio"] {
    margin-top: 0.2em;
    accent-color: var(--accent);
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    cursor: pointer;
}

.form__input,
.form__textarea,
.form__select {
    width: 100%;
    padding: var(--space-3) var(--space-4);
    font-family: inherit;
    font-size: var(--text-base);
    line-height: 1.4;
    color: var(--text);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease);
    appearance: none;
}
.form__input::placeholder,
.form__textarea::placeholder {
    color: var(--text-mute);
}
.form__input:hover,
.form__textarea:hover,
.form__select:hover {
    border-color: var(--border-strong);
}
.form__input:focus,
.form__textarea:focus,
.form__select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: var(--ring-focus);
    background: var(--bg-elevated);
}

.form__select {
    background-image:
        linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position:
        calc(100% - 18px) 50%,
        calc(100% - 12px) 50%;
    background-size: 6px 6px, 6px 6px;
    background-repeat: no-repeat;
    color: var(--text);
    padding-right: var(--space-10);
}

.form__textarea {
    min-height: 120px;
    resize: vertical;
}

.form__hint {
    margin-top: var(--space-2);
    color: var(--text-dim);
    font-size: var(--text-xs);
}

/* Note inline "(optionnel)" sur un label de formulaire */
.form__optional {
    color: var(--text-mute);
    font-size: var(--text-xs);
    font-weight: 400;
}

.form__actions {
    margin-top: var(--space-6);
    display: flex;
    gap: var(--space-3);
    justify-content: flex-end;
    flex-wrap: wrap;
}
.form__actions--split { justify-content: space-between; }
.form__actions--center { justify-content: center; }
.form__actions--start { justify-content: flex-start; }
/* Barre de boutons en ligne (ex : submit + reset d'un formulaire-filtre) */
.form__actions--inline {
    margin-top: 0;
    align-self: flex-end;
}

.form__row--checkbox {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
}

/* Grille de champs (label au-dessus de l'input). Mobile-first : 1 colonne,
   puis 2 / 3 colonnes ≥ 561px. Chaque cellule est un .form__field. */
.form__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-5) var(--space-4);
}
@media (min-width: 561px) {
    .form__grid--2 { grid-template-columns: 1fr 1fr; }
    .form__grid--3 { grid-template-columns: repeat(3, 1fr); }
}
/* Dans une grille, l'espacement vertical est porté par `gap` (pas la marge du
   champ) et le label colle à son input via le gap du flex. */
.form__grid .form__field { margin-bottom: 0; gap: var(--space-2); }
.form__grid .form__field > .form__label { margin-bottom: 0; }
.form__field--full { grid-column: 1 / -1; }

/* ============================================================================
   10. AUTH (split-screen modern)
   ============================================================================ */
.layout-auth {
    background: var(--bg);
    min-height: 100vh;
    overflow-x: hidden;
}
.auth-main {
    min-height: calc(100vh - 100px);
    display: grid;
    grid-template-columns: 1fr;
    align-items: center;
    padding: var(--space-6);
}
@media (min-width: 980px) {
    .auth-main {
        grid-template-columns: 1.1fr 1fr;
        max-width: 1200px;
        margin: 0 auto;
        gap: var(--space-16);
        padding: var(--space-10);
    }
}

.auth-side {
    display: none;
    position: relative;
    padding: var(--space-8);
    min-height: 540px;
    border-radius: var(--radius-xl);
    overflow: hidden;
    /* Hero sur --grad-hero : fond ink sombre, IDENTIQUE en light & dark.
       Le texte doit donc être en blancs LITTÉRAUX — jamais var(--paper)/--ink-*,
       qui s'inversent en dark et rendaient ce panneau illisible. Cf .moa-hero. */
    color: #FFFFFF;
    background: var(--grad-hero);
    isolation: isolate;
}
.auth-side::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        repeating-linear-gradient(0deg, rgba(255,255,255,0.04) 0 1px, transparent 1px 80px),
        repeating-linear-gradient(90deg, rgba(255,255,255,0.04) 0 1px, transparent 1px 80px);
    z-index: -1;
}
.auth-side::after {
    content: "";
    position: absolute;
    width: 380px;
    height: 380px;
    right: -100px;
    bottom: -100px;
    background: radial-gradient(circle, var(--clay-500), transparent 60%);
    opacity: 0.5;
    z-index: -1;
    filter: blur(60px);
}
@media (min-width: 980px) {
    .auth-side { display: flex; flex-direction: column; justify-content: space-between; }
}
.auth-side__kicker {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--clay-300);
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}
.auth-side__kicker::before {
    content: "";
    width: 24px;
    height: 1px;
    background: var(--clay-300);
}
.auth-side__title {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi (était 300 light italic Fraunces) */
    font-size: clamp(2rem, 3vw, 3.2rem);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    color: #FFFFFF;
    margin: var(--space-6) 0 var(--space-4);
    max-width: 18ch;
}
.auth-side__title em {
    font-style: italic;
    font-weight: 300;
    color: var(--clay-300);
}
.auth-side__lede {
    color: rgba(255, 255, 255, 0.90);
    font-size: var(--text-base);
    line-height: 1.6;
    max-width: 36ch;
}
.auth-side__features {
    list-style: none;
    padding: 0;
    margin-top: var(--space-8);
    display: grid;
    gap: var(--space-3);
}
.auth-side__features li {
    display: flex;
    gap: var(--space-3);
    color: rgba(255, 255, 255, 0.82);
    font-size: var(--text-sm);
    align-items: flex-start;
}
.auth-side__features li::before {
    content: "+";
    color: var(--clay-300);
    font-weight: 600;
    font-family: var(--font-mono);
}
.auth-side__foot {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: rgba(255, 255, 255, 0.60);
    letter-spacing: var(--tracking-wide);
}

.auth-card {
    width: 100%;
    max-width: 460px;
    margin: 0 auto;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    padding: clamp(var(--space-6), 4vw, var(--space-10));
    box-shadow: var(--shadow-lg);
}
.auth-card__kicker {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: var(--space-3);
}
.auth-card__title {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi (était 400 Fraunces) */
    font-size: var(--text-3xl);
    line-height: 1.1;
    margin-bottom: var(--space-2);
    letter-spacing: var(--tracking-tight);
}
.auth-card__sub {
    color: var(--text-dim);
    font-size: var(--text-sm);
    margin-bottom: var(--space-6);
    line-height: 1.6;
}
.auth-card__sep {
    margin: var(--space-6) 0;
    border: 0;
    border-top: 1px solid var(--border);
    position: relative;
    text-align: center;
}
.auth-card__sep[data-label]::after {
    content: attr(data-label);
    position: absolute;
    top: -8px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--bg-elevated);
    padding: 0 var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-mute);
}

/* ============================================================================
   11. DASHBOARD (Bento grid)
   ============================================================================ */
.dashboard { display: block; }
.dashboard__hello {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    /* mobile-first : wrap par défaut */
    flex-wrap: wrap;
    margin-bottom: var(--space-8);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--border);
}
.dashboard__title {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi (était 400 Fraunces) */
    font-size: var(--text-4xl);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
}
.dashboard__title em { font-style: italic; font-weight: 300; color: var(--text-soft); }
.dashboard__intro {
    color: var(--text-dim);
    margin-top: var(--space-2);
    font-size: var(--text-lg);
    max-width: 60ch;
}
.dashboard__cta { margin: var(--space-4) 0 var(--space-8); display: flex; gap: var(--space-3); flex-wrap: wrap; }
/* .dashboard__hint et .dashboard__roadmap retirés 2026-05 — bloc "Feuille de
   route produit" supprimé du dashboard architecte (devenu bruit en prod). */

.bento {
    display: grid;
    /* mobile-first : 1 colonne par défaut */
    grid-template-columns: 1fr;
    gap: var(--space-4);
    margin-bottom: var(--space-8);
}
.bento__cell {
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-6);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.bento__cell:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
}
/* mobile : tous les spans sur pleine largeur */
.bento__cell--span-6,
.bento__cell--span-4,
.bento__cell--span-8,
.bento__cell--span-12 { grid-column: 1 / -1; }
.bento__cell--feature {
    background: var(--grad-paper);
    border-color: var(--border-strong);
}
/* ≥ 881px : grille 12 colonnes, spans actifs */
@media (min-width: 881px) {
    .bento { grid-template-columns: repeat(12, 1fr); }
    .bento__cell--span-6 { grid-column: span 6; }
    .bento__cell--span-4 { grid-column: span 4; }
    .bento__cell--span-8 { grid-column: span 8; }
    .bento__cell--span-12 { grid-column: span 12; }
}

.metric {
    display: grid;
    gap: var(--space-2);
}
.metric__label {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
}
.metric__value {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: var(--text-4xl);
    line-height: 1;
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.metric__trend {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-dim);
}
.metric__trend--up { color: var(--success); }
.metric__trend--down { color: var(--error); }

/* — Metric cliquable (refonte 2026-05).
   Modificateur posé sur le `.bento__cell` parent : transforme la tuile KPI en
   surface interactive, avec un stretched-link absolu (.metric__link::after).
   Pattern emprunté à `.chantiers__item` + `.chantiers__link` (BEM strict,
   accessibilité préservée — focus ring sur le lien, libellé via aria-label).
   - hover : élévation polish (transform + shadow), accent du `--feature` reste
   - focus-visible : ring autour du lien (donc autour de la tuile entière grâce
     au stretched-link inset:0)
   - active : feedback tactile court (--dur-instant) */
.metric--link {
    position: relative;
    cursor: pointer;
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.metric--link:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}
.metric--link:active { transform: translateY(-1px); transition-duration: var(--dur-instant); }
/* Arête verticale arrondie — même langage que les cartes KPI architecte (§11.bis),
   pour la cohérence inter-personas. Accent terracotta sur la tuile KPI hero
   (s'accorde au dot--accent du trend). position:relative déjà posé ci-dessus ;
   padding du .bento__cell (--space-6) suffit à dégager l'arête à left:--space-2. */
.metric--link::before {
    content: "";
    position: absolute;
    top: var(--space-3);
    bottom: var(--space-3);
    left: var(--space-2);
    width: 3px;
    border-radius: var(--radius-full);
    background: var(--accent);
}
.metric__link {
    color: inherit;
    text-decoration: none;
    outline: none;
}
.metric__link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--radius-md);
    transition: box-shadow var(--dur-fast) var(--ease);
}
.metric__link:focus-visible::after {
    box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
}
@media (prefers-reduced-motion: reduce) {
    .metric--link, .metric--link:hover, .metric--link:active { transform: none; }
}

/* — Quick access tiles (dashboard accès rapide — refonte 2026-05).
   Composant dédié de cartes de navigation du tableau de bord.
   Carte cliquable avec numéro chip, label éditorial display, hint mono,
   accent rail à gauche au hover, flèche animée bottom-right. */
.quick-access {
    display: grid;
    /* mobile-first : 1 colonne par défaut ; élargi par paliers ascendants ci-dessous */
    grid-template-columns: 1fr;
    gap: var(--space-3);
    margin: 0;
    padding: 0;
}
.quick-access__item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-5) var(--space-5) var(--space-4);
    min-height: 132px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text);
    font-family: inherit;
    text-decoration: none;
    overflow: hidden;
    isolation: isolate;
    transition: background var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.quick-access__item::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 3px;
    background: var(--accent);
    transform: scaleY(0);
    transform-origin: top;
    transition: transform var(--dur) var(--ease-out);
    z-index: 1;
}
.quick-access__item::after {
    content: "→";
    position: absolute;
    right: var(--space-5);
    bottom: var(--space-4);
    font-family: var(--font-mono);
    font-size: var(--text-base);
    color: var(--text-mute);
    transition: color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.quick-access__item:hover,
.quick-access__item:focus-visible {
    border-color: var(--ink-900);
    color: var(--text);
    text-decoration: none;
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}
.quick-access__item:focus-visible { box-shadow: var(--ring-focus), var(--shadow-lg); }
.quick-access__item:hover::before,
.quick-access__item:focus-visible::before { transform: scaleY(1); }
.quick-access__item:hover::after,
.quick-access__item:focus-visible::after {
    color: var(--accent);
    transform: translateX(4px);
}
.quick-access__item:active {
    transform: translateY(-1px);
    transition-duration: var(--dur-instant);
}
.quick-access__num {
    display: inline-flex;
    align-items: center;
    padding: 2px var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-on-sunken);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-full);
    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}
.quick-access__item:hover .quick-access__num,
.quick-access__item:focus-visible .quick-access__num {
    background: var(--accent-soft);
    color: var(--accent-text);
    border-color: transparent;
}
.quick-access__label {
    font-family: var(--font-display);
    font-weight: 400;
    font-size: var(--text-lg);
    line-height: 1.15;
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.quick-access__hint {
    margin-top: auto;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
}
/* mobile-first quick-access : progression ascendante 1→2→auto-fill */
.quick-access__item { min-height: 108px; }
@media (min-width: 481px) {
    .quick-access { grid-template-columns: 1fr 1fr; }
    .quick-access__item { min-height: unset; }
}
@media (min-width: 721px) {
    /* Retour à la grille fluide auto-fill pour les écrans larges */
    .quick-access { grid-template-columns: repeat(auto-fill, minmax(200px, 300px)); }
}

/* ============================================================================
   11.bis DASHBOARD ARCHITECTE — intégration maquette « Cockpit Dashboard »
   ----------------------------------------------------------------------------
   4 blocs : KPI cliquables · file d'attention cross-chantiers · cartes
   chantiers + filtre par phase · activité récente + agenda. Tout en tokens DA
   (accent Clay, Satoshi, ombres/radius/motion DS) — aucune couleur persona ad
   hoc (ADR-010 §4 : palette identique pour les 3 personas). Mobile-first.
   ============================================================================ */
.dash { display: block; }
/* L'en-tête du dashboard reprend .dashboard__hello mais sans la règle/bordure
   du bas (les KPI suivent immédiatement). */
.dash__hello {
    border-bottom: 0;
    padding-bottom: 0;
    margin-bottom: var(--space-6);
}
.dash__hello .dashboard__intro strong { color: var(--text); font-weight: 600; }
.dash .attention,
.dash .projects { margin-bottom: var(--space-10); }

/* — Icônes inline (style Lucide, currentColor) — partagées par tout le DS ---- */
.icon {
    width: 1.15em;
    height: 1.15em;
    flex: none;
    display: inline-block;
    vertical-align: -0.15em;
}
.icon--lg { width: 1.6rem; height: 1.6rem; }
.btn .icon { width: 1.05em; height: 1.05em; }

/* — §11.bis.1 KPI grid ------------------------------------------------------- */
.kpi-grid {
    display: grid;
    grid-template-columns: 1fr 1fr; /* mobile : 2-up */
    gap: var(--space-4);
    margin-bottom: var(--space-10);
    list-style: none; /* <ul> sémantique : liste de liens KPI */
    padding: 0;
}
.kpi-grid__item { display: contents; } /* le <li> préserve la sémantique sans casser la grille : .kpi reste l'item */
@media (min-width: 721px) { .kpi-grid { grid-template-columns: repeat(4, 1fr); } }

.kpi {
    position: relative;
    display: grid;
    align-content: start;
    gap: var(--space-2);
    /* padding-left élargi pour dégager l'arête verticale ::before
       (arête à left:--space-2 + 3px → ~0.57rem ; on donne 1.5rem à gauche). */
    padding: var(--space-5) var(--space-5) var(--space-5) var(--space-6);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text);
    text-decoration: none;
    box-shadow: var(--shadow-xs);
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.kpi:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); transform: translateY(-2px); }
.kpi:active { transform: translateY(-1px); transition-duration: var(--dur-instant); }
.kpi:focus-visible { outline: none; box-shadow: var(--ring-focus), var(--shadow-md); }
.kpi__label {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
}
.kpi__value {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-2xl);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.kpi__value--mono { font-family: var(--font-mono); font-weight: 500; font-variant-numeric: tabular-nums; }
.kpi__hint { font-size: var(--text-xs); color: var(--text-dim); }
/* Arête verticale arrondie — présente sur TOUTES les cartes KPI (neutre
   inclus) pour une rangée équilibrée. Géométrie via ::before en retrait
   des angles de la carte : top/bottom = --space-3 pour dégager le radius,
   left = --space-2 (petit retrait du bord), width = 3px pill. Le
   padding-left compensatoire est ajouté sur .kpi ci-dessous. */
.kpi::before {
    content: "";
    position: absolute;
    top: var(--space-3);
    bottom: var(--space-3);
    left: var(--space-2);
    width: 3px;
    border-radius: var(--radius-full);
    background: var(--border); /* neutre : gris discret (ink-400) — s'efface devant les variantes sémantiques */
}
/* Variantes sémantiques — couleur d'accent propre au statut. */
.kpi--warning::before { background: var(--warning); }
.kpi--info::before    { background: var(--info); }
.kpi--success::before { background: var(--success); }
.kpi--error::before   { background: var(--error); }
/* Hint coloré — reprend la même teinte sémantique. */
.kpi--warning .kpi__hint { color: var(--warning); }
.kpi--info .kpi__hint    { color: var(--info); }
.kpi--success .kpi__hint { color: var(--success); }
.kpi--error .kpi__hint   { color: var(--error); }

/* — §11.bis.2 File d'attention ----------------------------------------------- */
.attention__list {
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
}
.attention__list > li + li { border-top: 1px solid var(--border-subtle); }
.attention__row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    color: var(--text);
    text-decoration: none;
    transition: background var(--dur) var(--ease);
}
.attention__row:hover { background: var(--bg-sunken); text-decoration: none; color: var(--text); }
.attention__row:focus-visible { outline: none; box-shadow: var(--ring-focus); outline-offset: -3px; }
.attention__icon { flex: none; display: inline-flex; align-items: center; }
.attention__icon .icon { width: 1.3rem; height: 1.3rem; }
.attention__icon--error   { color: var(--error); }
.attention__icon--warning { color: var(--warning); }
.attention__icon--info    { color: var(--info); }
.attention__icon--accent  { color: var(--accent); }
.attention__icon--success { color: var(--success); }
.attention__body { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
.attention__title { font-size: var(--text-sm); font-weight: 500; color: var(--text); }
.attention__meta { font-size: var(--text-xs); color: var(--text-dim); }
.attention__badge { flex: none; }
.attention__chevron { flex: none; color: var(--text-mute); }
@media (max-width: 560px) {
    .attention__row { flex-wrap: wrap; }
    .attention__chevron { display: none; }
    .attention__badge { order: 3; }
}
/* « Voir tout » — dépliage progressif de la file (cf. dashboard.js). Sans JS,
   tous les points rendus restent visibles et le bouton demeure caché. */
.attention__list > li[hidden] { display: none; }
.attention__more {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding: var(--space-2) var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: var(--text-dim);
    background: none;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-full);
    cursor: pointer;
    transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.attention__more:hover { color: var(--text); border-color: var(--border); }
.attention__more:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.attention__more-icon { width: 1rem; height: 1rem; transition: transform var(--dur) var(--ease); }
.attention__more[aria-expanded="true"] .attention__more-icon { transform: rotate(180deg); }

/* — Empty state générique du dashboard --------------------------------------- */
.empty-note {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-6);
    background: var(--bg-elevated);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    color: var(--text-dim);
    font-size: var(--text-sm);
}
.empty-note p { margin: 0; }
.empty-note__icon { color: var(--success); flex: none; }

/* — §11.bis.2bis État vide « 0 chantier » (S24 Lot 2) -------------------------
   Depuis que /app ne montre plus que la liste des chantiers (attention/
   activité/agenda cross-chantiers retirés, cf. plus bas), l'état 0 chantier
   devient le contenu quasi-unique de la page : traitement plus posé que
   `.empty-note` (utilisé pour des listes secondaires vides ailleurs dans le
   DS, qu'on ne modifie pas). CTA primaire unique, cf. principe directeur
   ux-designer §3 (une seule action par écran). */
.dash__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-10) var(--space-6);
    text-align: center;
    background: var(--bg-elevated);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-lg);
}
.dash__empty-icon { color: var(--text-mute); }
.dash__empty-icon .icon { width: 2.5rem; height: 2.5rem; }
.dash__empty-title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-lg);
    color: var(--text);
}
.dash__empty-text {
    margin: 0;
    max-width: 42ch;
    font-size: var(--text-sm);
    color: var(--text-dim);
}

/* — §11.bis.3 Filtre par phase ----------------------------------------------- */
.phase-filter {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
}
.phase-filter__tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    min-height: 38px;
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    background: var(--bg-elevated);
    color: var(--text-soft);
    font-family: inherit;
    font-size: var(--text-sm);
    cursor: pointer;
    transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.phase-filter__tab:hover { border-color: var(--border-strong); color: var(--text); }
.phase-filter__tab:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.phase-filter__tab[aria-pressed="true"] {
    background: var(--accent-soft);
    border-color: transparent;
    color: var(--accent-text);
    font-weight: 500;
}
.phase-filter__count {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    padding: 1px var(--space-1);
    min-width: 1.4em;
    text-align: center;
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    color: var(--text-on-sunken);
}
.phase-filter__tab[aria-pressed="true"] .phase-filter__count { background: var(--accent); color: var(--on-accent); }

/* — §11.bis.4 Cartes chantiers ----------------------------------------------- */
.project-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-5);
}
@media (min-width: 561px) { .project-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 961px) { .project-grid { grid-template-columns: repeat(3, 1fr); } }
.project-card[hidden] { display: none; }
.project-card {
    position: relative; /* S24 Lot 2 : contexte de positionnement pour
                            .project-card__attention (badge indépendant) */
    display: flex;
    flex-direction: column;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    color: var(--text);
    text-decoration: none;
    box-shadow: var(--shadow-sm);
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.project-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-lg); transform: translateY(-2px); text-decoration: none; color: var(--text); }
.project-card:active { transform: translateY(-1px); transition-duration: var(--dur-instant); }
/* S24 Lot 2 : `.project-card` n'est plus lui-même l'ancre (le badge
   .project-card__attention doit rester cliquable indépendamment — imbriquer
   deux <a> est invalide en HTML). Le focus atterrit sur .project-card__link
   (stretched-link enfant) : :focus-within relaie l'anneau sur toute la
   carte, comme :focus-visible le faisait quand la carte était l'ancre. */
.project-card:focus-within { outline: none; box-shadow: var(--ring-focus), var(--shadow-lg); }
.project-card__cover {
    position: relative;
    height: 88px;
    background: var(--grad-hero);
    color: #FFFFFF;
    overflow: hidden;
}
.project-card__cover--alert { background: linear-gradient(135deg, var(--clay-700), var(--clay-500)); }
.project-card__pattern { position: absolute; inset: 0; width: 100%; height: 100%; color: #FFFFFF; opacity: 0.14; }
.project-card__phase {
    position: absolute;
    left: var(--space-3);
    top: var(--space-3);
    display: inline-flex;
    align-items: center;
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.18);
    color: #FFFFFF;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    backdrop-filter: blur(4px);
}
.project-card__alerts {
    position: absolute;
    right: var(--space-3);
    top: var(--space-3);
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
    background: var(--warning-bg);
    color: var(--warning);
    font-size: var(--text-2xs);
    font-weight: 600;
}
.project-card__alerts .icon { width: 0.9em; height: 0.9em; }
.project-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-5);
}
.project-card__name {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-lg);
    line-height: 1.2;
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.project-card__sub { font-size: var(--text-xs); color: var(--text-dim); }
.project-card__progress { margin-top: var(--space-2); display: flex; flex-direction: column; gap: var(--space-2); }
.project-card__bar {
    display: block;
    height: 4px;
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    overflow: hidden;
}
.project-card__bar-fill {
    display: block;
    height: 100%;
    width: calc(var(--pct, 0) * 1%); /* convention DA (cf. Gantt) : géométrie via variable CSS */
    background: var(--accent);
    border-radius: inherit;
    transition: width var(--dur-slow) var(--ease);
}
.project-card__milestone { font-size: var(--text-xs); color: var(--text-dim); }

/* — §11.bis.4bis Badge « points d'attention » (S24 Lot 2, accueil par chantier)
   docs/PLAN-EVOLUTION-V2-2026-09-15.md §2.1, 🚦 L2-1/L2-12/L2-14.
   `.project-card` n'est plus l'ancre : son contenu visuel (cover + corps) est
   enveloppé dans `.project-card__link` (stretched-link via `::after`, pattern
   déjà en place pour `.metric__link`/`.metric--link` plus haut dans ce même
   §11), et le badge devient un `<a>` FRÈRE positionné par-dessus (z-index
   supérieur) pour rester cliquable indépendamment de la carte — imbriquer
   deux `<a>` est invalide en HTML et le comportement de clic serait
   indéterminé selon les navigateurs. Snippet de référence :
   docs/UI-DESIGN-SYSTEM.md §7.4. */
.project-card__link {
    display: flex;
    flex-direction: column;
    flex: 1;
    color: inherit;
    text-decoration: none;
    border-radius: inherit;
}
.project-card__link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1; /* sous le badge (z-index: 2) */
    border-radius: inherit;
}
/* L'anneau de focus est porté par .project-card:focus-within (cf. plus haut) :
   pas de double anneau sur le lien lui-même. */
.project-card__link:focus-visible { outline: none; }

.project-card__attention {
    position: absolute;
    z-index: 2; /* au-dessus du stretched-link .project-card__link::after */
    top: var(--space-3);
    right: var(--space-3);
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 28px; /* cible tactile ≥ 24px CSS — WCAG 2.2 2.5.8 AA */
    padding: 4px var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.3;
    text-decoration: none;
    white-space: nowrap;
    transition: box-shadow var(--dur-fast) var(--ease);
}
.project-card__attention .icon { width: 0.95em; height: 0.95em; flex: none; }
.project-card__attention-count { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
/* Ton chaud (≥ 1 point) : fond plein --warning + texte --on-warning, la même
   paire de tokens que les chips Gantt « en retard » (AA garanti light ET
   dark sans redéfinition ici, cf. §1 tokens --on-*). Élément <a> réel. */
.project-card__attention--warning { background: var(--warning); color: var(--on-warning); }
.project-card__attention--warning:hover { box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.08); }
.project-card__attention--warning:focus-visible { outline: none; box-shadow: var(--ring-focus); }
/* Ton neutre (0 point) : pastille discrète, non cliquable (rien à ouvrir) —
   <span>, pas <a>. --text-dim sur --bg-elevated : paire déjà validée AA
   ailleurs dans le DS (ex. .kpi__hint), aucune redéfinition dark requise. */
.project-card__attention--zero {
    background: var(--bg-elevated);
    color: var(--text-dim);
    border: 1px solid var(--border-subtle);
    cursor: default;
}

/* — §11.bis.5 Activité (bento chantier) ---------------------------------------
   S24 Lot 2 : `.dash-cols` (mise en 2 colonnes Activité+Agenda de l'ancien
   accueil /app, bloc 4) et `.agenda__*` (bloc « La quinzaine ») sont retirés
   avec le bloc lui-même (docs/PLAN-EVOLUTION-V2-2026-09-15.md §2.1 —
   suppression de l'activité/agenda cross-chantiers de /app). Seul
   views/architect/app.php les consommait (grep views/ vérifié, S24) : aucune
   autre vue n'est impactée. `.activity__*` reste utilisé par la vue
   d'ensemble chantier (§12 `.chantier__activity`) : conservé, à l'exception
   de `.activity__chantier` (rappel du nom du chantier — pertinent seulement
   dans une liste cross-chantiers, désormais orpheline). */
.section__title--inline-reset { margin: 0; }
.section__icon { color: var(--text-mute); width: 1.2rem; height: 1.2rem; }

.activity__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.activity__item { display: flex; gap: var(--space-3); }
.activity__time {
    flex: none;
    width: 5.25rem;
    padding-top: 2px;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: var(--text-mute);
}
.activity__body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.activity__text { font-size: var(--text-sm); color: var(--text); }

/* — §11.bis.6 Pied de page KPI cabinet (S24 Lot 2) ----------------------------
   Les 4 KPI cabinet (Chantiers actifs / Accusés manquants / Devis en attente /
   Honoraires facturés) quittent le bandeau `.kpi-grid` en tête de page pour un
   pied de page discret sous la liste des chantiers — l'accueil ne met plus en
   avant que les widgets par chantier (docs/PLAN-EVOLUTION-V2-2026-09-15.md
   §2.1). Liens vers les pages globales inchangées (/app/factures, /app/devis,
   /app/diffusions, /app/chantiers). */
.dash__footer-kpis {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
    margin-top: var(--space-10);
    padding-top: var(--space-6);
    border-top: 1px solid var(--border-subtle);
}
@media (min-width: 721px) {
    .dash__footer-kpis { grid-template-columns: repeat(4, 1fr); }
}
.dash__footer-kpis-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-2) 0;
    color: inherit;
    text-decoration: none;
    border-radius: var(--radius-sm);
    transition: color var(--dur-fast) var(--ease);
}
.dash__footer-kpis-item:hover,
.dash__footer-kpis-item:focus-visible { color: var(--accent-text); text-decoration: none; }
.dash__footer-kpis-item:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.dash__footer-kpis-value {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.dash__footer-kpis-label {
    font-size: var(--text-2xs);
    color: var(--text-dim);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

/* Respect prefers-reduced-motion : pas de translation sur hover/active. */
@media (prefers-reduced-motion: reduce) {
    .kpi, .kpi:hover, .kpi:active,
    .project-card, .project-card:hover, .project-card:active { transform: none; }
    .project-card__bar-fill { transition: none; }
}

/* ============================================================================
   11.ter DASHBOARD MOA — fiche chantier /client/{slug} — maquette « Tableau de
   bord MOA »
   ----------------------------------------------------------------------------
   Vue narrative côté maître d'ouvrage : hero ink sombre + motif blueprint,
   décisions à valider, « Où en est votre projet » (timeline d'étapes),
   budget, images, sidebar (architecte / RDV / documents). Mêmes tokens DA que
   §11.bis (accent Clay, Satoshi, ombres/radius/motion DS). Aucune couleur
   persona ad hoc (ADR-010 §4). Mobile-first. La seule géométrie inline est
   `--pct` sur les segments du budget (convention DA, cf. project-card__bar-fill).
   ============================================================================ */
.moa-dash { display: block; }

/* — §11.ter.1 Hero ----------------------------------------------------------- */
.moa-hero {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    margin-bottom: var(--space-6);
    border-radius: var(--radius-lg);
    background: var(--grad-hero); /* ink sombre — identique en light & dark */
    color: #FFFFFF;
    box-shadow: var(--shadow-md);
}
.moa-hero__pattern {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    color: #FFFFFF;
    opacity: 0.14;
}
.moa-hero__inner {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--space-2);
    min-height: 11.5rem;
    padding: var(--space-6);
}
@media (min-width: 721px) {
    .moa-hero__inner { min-height: 13rem; padding: var(--space-8); }
}
.moa-hero__kicker {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--clay-300);
}
.moa-hero__title {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-3xl);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    color: #FFFFFF;
}
.moa-hero__lede {
    max-width: 54ch;
    font-size: var(--text-base);
    line-height: var(--leading-snug);
    color: rgba(255, 255, 255, 0.9);
}
.moa-hero__delivery {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-2);
    font-size: var(--text-sm);
    color: rgba(255, 255, 255, 0.92);
}
.moa-hero__delivery time { font-weight: 600; color: #FFFFFF; }
.moa-hero__delivery-icon { color: var(--clay-300); }

/* — §11.ter.2 Grille 2 colonnes (contenu / sidebar) ------------------------- */
.moa-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-5);
    margin-bottom: var(--space-10);
}
@media (min-width: 961px) {
    .moa-grid { grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr); align-items: start; }
}
.moa-grid__main,
.moa-grid__aside {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    min-width: 0;
}

/* — Bloc générique (posé sur .card) — */
.moa-block { display: flex; flex-direction: column; gap: var(--space-4); }
.moa-block__head { display: flex; flex-direction: column; gap: var(--space-1); }
.moa-block__head--split {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.moa-block__lede { font-size: var(--text-sm); color: var(--text-dim); }

/* — §11.ter.3 Décisions ------------------------------------------------------ */
.moa-decisions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.moa-decision {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    padding: var(--space-4);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    background: var(--bg-elevated);
}
.moa-decision--primary { border-color: var(--accent); background: var(--accent-soft); }
.moa-decision__body { flex: 1 1 16rem; min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.moa-decision__title { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.moa-decision__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-3);
    font-size: var(--text-xs);
}
.moa-decision__kind { font-family: var(--font-mono); color: var(--text-mute); letter-spacing: var(--tracking-wide); }
.moa-decision__amount { font-family: var(--font-mono); font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }
.moa-decision__when { color: var(--text-dim); }
.moa-decision--primary .moa-decision__when { color: var(--accent-text); font-weight: 500; }
.moa-decision__action { flex: none; }

/* — §11.ter.4 « Où en est votre projet » (timeline d'étapes) ----------------- */
.moa-steps { list-style: none; margin: 0; padding: 0; }
.moa-step { position: relative; display: flex; gap: var(--space-4); padding-bottom: var(--space-5); }
.moa-step:last-child { padding-bottom: 0; }
.moa-step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 13px;
    top: 28px;
    bottom: 0;
    width: 2px;
    background: var(--border-subtle);
}
.moa-step[data-state="done"]:not(:last-child)::before { background: var(--success); }
.moa-step__marker {
    position: relative;
    z-index: 1;
    flex: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    color: var(--text-mute);
}
.moa-step__marker .icon { width: 1.1rem; height: 1.1rem; }
.moa-step[data-state="done"] .moa-step__marker { background: var(--success); color: #FFFFFF; }
.moa-step[data-state="current"] .moa-step__marker { background: var(--accent); color: var(--on-accent); }
.moa-step__body { display: flex; flex-direction: column; gap: 2px; padding-top: 2px; }
.moa-step__label { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.moa-step[data-state="todo"] .moa-step__label { color: var(--text-dim); }
.moa-step[data-state="current"] .moa-step__label { color: var(--accent-text); }
.moa-step__pill { font-weight: 500; }
.moa-step__text { font-size: var(--text-sm); color: var(--text-dim); }

/* — §11.ter.5 Budget --------------------------------------------------------- */
.moa-budget__envelope { font-size: var(--text-sm); color: var(--text-dim); }
.moa-budget__envelope strong { font-family: var(--font-mono); color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.moa-budget__note { color: var(--text-soft); }
.moa-budget__note--over { color: var(--warning); }
.moa-budget__bar {
    display: flex;
    height: 10px;
    border-radius: var(--radius-full);
    overflow: hidden;
    background: var(--bg-sunken);
}
.moa-budget__seg { flex: 0 0 calc(var(--pct, 0) * 1%); min-width: 2px; }
.moa-budget__seg--brand { background: var(--accent); }
.moa-budget__seg--info  { background: var(--info); }
.moa-budget__seg--ink   { background: var(--ink-300); }
.moa-budget__legend { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
@media (min-width: 561px) { .moa-budget__legend { grid-template-columns: repeat(3, 1fr); } }
.moa-budget__legend-item { display: flex; align-items: center; gap: var(--space-2); min-width: 0; font-size: var(--text-xs); color: var(--text-dim); }
.moa-budget__dot { flex: none; width: 10px; height: 10px; border-radius: var(--radius-full); }
.moa-budget__dot--brand { background: var(--accent); }
.moa-budget__dot--info  { background: var(--info); }
.moa-budget__dot--ink   { background: var(--ink-300); }
.moa-budget__legend-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.moa-budget__legend-amount { margin-left: auto; font-family: var(--font-mono); font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }

/* — §11.ter.6 Images du projet ----------------------------------------------- */
.moa-gallery { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-3); }
@media (min-width: 561px) { .moa-gallery { grid-template-columns: repeat(4, 1fr); } }
.moa-gallery__cell { min-width: 0; } /* item de grille : autorise l'ellipsis des légendes */
.moa-gallery__item { display: block; text-decoration: none; color: var(--text); border-radius: var(--radius); }
.moa-gallery__item:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.moa-gallery__thumb {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
    background: var(--bg-sunken);
    transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.moa-gallery__item:hover .moa-gallery__thumb { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.moa-gallery__caption { display: block; margin-top: var(--space-2); font-size: var(--text-xs); font-weight: 500; color: var(--text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.moa-gallery__when { display: block; font-size: var(--text-2xs); color: var(--text-dim); }

/* — §11.ter.7 Sidebar (architecte / RDV / documents) ------------------------- */
.moa-aside-card { display: flex; flex-direction: column; gap: var(--space-3); }
.moa-aside-card__label {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 500;
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
}
.moa-aside-card__cta { align-self: stretch; justify-content: center; }
.moa-aside-card__more { font-size: var(--text-xs); color: var(--accent-text); text-decoration: none; }
.moa-aside-card__more:hover { text-decoration: underline; }

.moa-contact { display: flex; align-items: center; gap: var(--space-3); }
.moa-contact__avatar {
    flex: none;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: var(--radius-full);
    background: var(--accent);
    color: var(--on-accent);
    font-family: var(--font-display);
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
}
.moa-contact__id { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.moa-contact__name { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.moa-contact__org { font-size: var(--text-xs); color: var(--text-dim); }

.moa-appointment { display: flex; gap: var(--space-3); }
.moa-appointment__icon { flex: none; width: 1.4rem; height: 1.4rem; margin-top: 2px; color: var(--accent); }
.moa-appointment__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.moa-appointment__title { font-size: var(--text-sm); font-weight: 600; color: var(--text); }
.moa-appointment__when { font-size: var(--text-xs); color: var(--text-dim); }
.moa-appointment__where { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--text-xs); color: var(--text-dim); }
.moa-appointment__where .icon { width: 0.95em; height: 0.95em; }

.moa-doclist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.moa-doclist__item {
    display: flex;
    gap: var(--space-2);
    padding: var(--space-2);
    border-radius: var(--radius);
    color: var(--text);
    text-decoration: none;
    transition: background var(--dur) var(--ease);
}
.moa-doclist__item:hover { background: var(--bg-sunken); }
.moa-doclist__item:focus-visible { outline: none; box-shadow: var(--ring-focus); outline-offset: -2px; }
.moa-doclist__icon { flex: none; margin-top: 1px; color: var(--text-mute); }
.moa-doclist__id { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.moa-doclist__title { font-size: var(--text-sm); font-weight: 500; color: var(--text); }
.moa-doclist__item:hover .moa-doclist__title { text-decoration: underline; }
.moa-doclist__meta { font-size: var(--text-xs); color: var(--text-dim); }

/* — §11.ter.8 Planning détaillé --------------------------------------------- */
.moa-planning { margin-bottom: var(--space-10); }

@media (prefers-reduced-motion: reduce) {
    .moa-gallery__item:hover .moa-gallery__thumb { transform: none; }
}

/* ============================================================================
   12. CHANTIERS (liste + détail)
   ============================================================================ */
.chantiers { display: block; }
.chantiers__header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    /* mobile-first : wrap par défaut */
    flex-wrap: wrap;
    margin-bottom: var(--space-8);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--border);
}
.chantiers__title {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi (était 400 Fraunces) */
    font-size: var(--text-4xl);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    margin: 0;
}
.chantiers__count {
    display: inline-block;
    margin-left: var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-dim);
    font-weight: 400;
    letter-spacing: var(--tracking-wide);
    vertical-align: middle;
}

.chantiers__list {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-8);
    display: grid;
    /* mobile-first : 1 colonne ; auto-fill ≥ 721px */
    grid-template-columns: 1fr;
    gap: var(--space-4);
}
.chantiers__item {
    position: relative;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--space-5);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
    overflow: hidden;
}
.chantiers__item::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 3px;
    height: 100%;
    background: var(--accent);
    opacity: 0;
    transition: opacity var(--dur) var(--ease);
}
.chantiers__item:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-lg); /* T13.2.6 S13 — élévation polish md → lg */
    transform: translateY(-2px);
}
.chantiers__item:active {
    transform: translateY(-1px);
    transition-duration: var(--dur-instant); /* T13.1.2 S13 — feedback tactile */
}
.chantiers__item:hover::before { opacity: 1; }

.chantiers__link {
    display: block;
    color: var(--text);
    text-decoration: none;
    margin-bottom: var(--space-2);
}
.chantiers__link:hover { color: var(--text); text-decoration: none; }
.chantiers__link::after {
    content: "";
    position: absolute;
    inset: 0;
}
.chantiers__name {
    display: block;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-xl);
    line-height: 1.2;
    letter-spacing: var(--tracking-tight);
    margin-bottom: var(--space-3);
}
.chantiers__phase {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    background: var(--accent-soft);
    color: var(--accent-text);
    border-radius: var(--radius-full);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
}
.chantiers__meta {
    font-size: var(--text-sm);
    color: var(--text-dim);
    margin-top: var(--space-1);
    line-height: 1.5;
}
.chantiers__meta--spaced { margin-top: var(--space-3); }
.chantiers__empty {
    grid-column: 1 / -1;
    color: var(--text-dim);
    background: var(--bg-elevated);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    padding: var(--space-10);
    text-align: center;
    font-size: var(--text-base);
}

.chantiers__new {
    margin-top: var(--space-8);
}

/* Chantier détail */
.chantier { display: block; }
.chantier__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-2);
    flex-wrap: wrap;
}
.chantier__title {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi (était 400 Fraunces) */
    font-size: var(--text-4xl);
    line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    margin: 0;
}
.chantier__phase {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: var(--accent-soft);
    color: var(--accent-text);
    border-radius: var(--radius-full);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    margin-top: var(--space-2);
}
.chantier__phase::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--accent);
}
.chantier__meta {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-3) var(--space-6);
    margin: var(--space-6) 0 var(--space-8);
    padding: var(--space-5) var(--space-6);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.chantier__meta dt {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
    margin-bottom: var(--space-1);
}
.chantier__meta dd {
    margin: 0;
    color: var(--text);
    font-size: var(--text-sm);
}
.chantier__address { color: var(--text-soft); }

/* ============================================================================
   §12.ter GANTT — Bandeau "Calendrier prévisionnel" (Sprint S20, 2026-05-28)
   ============================================================================
   Composant `.gantt*` réutilisé par les 3 vues "vision chantier" (archi /
   MOA / artisan). Mobile-first : bandeau pliable en pile verticale sur
   < 640 px, puis bandeau proportionnel ≥ 640 px.

   Acceptances 🚦 couvertes : G1, G2, G3, G4, G5, G6, G11, G12.
   Conforme ADR-010 (extension BEM, aucune lib externe, SVG/CSS pur). */
.gantt {
    margin: var(--space-6) 0 var(--space-8);
    padding: var(--space-5);
    background: var(--paper-soft);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}
.gantt__header {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}
.gantt__period { margin: 0; }

.gantt__legend {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-5);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-soft);
    font-family: var(--font-mono);
}
.gantt__legend li {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.gantt__chip {
    display: inline-block;
    width: 10px;
    height: 10px;
    border-radius: var(--radius-full);
    background: var(--ink-300);
    flex: 0 0 auto;
}
.gantt__chip--reached  { background: var(--success); }
.gantt__chip--overdue  { background: var(--warning); }
.gantt__chip--upcoming { background: var(--accent); }
.gantt__chip--active   { background: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.gantt__chip--undated  { background: var(--ink-400); }

/* — Viewport bandeau Gantt (mode ≥ 640 px) — */
.gantt__viewport {
    position: relative;
    display: none; /* masqué par défaut, ré-affiché en média-query ≥ 640 px */
    margin-top: var(--space-4);
    padding-bottom: var(--space-8);
}

.gantt__phases {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: 2px;
    background: var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.gantt__phase {
    background: var(--paper);
    padding: var(--space-3) var(--space-2);
    text-align: center;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-height: 60px;
    justify-content: center;
}
.gantt__phase-code {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-widest);
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.gantt__phase-label {
    font-size: var(--text-2xs);
    color: var(--text-soft);
    line-height: 1.3;
}
.gantt__phase--past {
    background: var(--paper-dim);
    opacity: 0.7;
}
.gantt__phase--past .gantt__phase-code { color: var(--text-soft); }
.gantt__phase--active {
    background: var(--accent-soft);
    border-top: 3px solid var(--accent);
    padding-top: calc(var(--space-3) - 3px);
}
.gantt__phase--active .gantt__phase-code { color: var(--accent); }
.gantt__phase--active .gantt__phase-label { color: var(--text); font-weight: 600; }
.gantt__phase--future { background: var(--paper); }

/* Fix 1 — Labels sémantiques phases / axe temporel */
.gantt__phases-label,
.gantt__axis-label {
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    margin: 0 0 var(--space-2);
    font-family: var(--font-mono);
}

/* Track horizontale = axe temporel sur lequel se positionnent les jalons */
.gantt__track {
    position: relative;
    height: 6px;
    margin: var(--space-6) 0 0; /* Fix 1 — augmenté de space-3 à space-6 */
    background: var(--border-subtle);
    border-radius: var(--radius-full);
}
.gantt__today {
    position: absolute;
    top: -4px;
    width: 2px;
    height: 14px;
    background: var(--accent);
    transform: translateX(-50%);
    border-radius: 1px;
    box-shadow: 0 0 0 2px var(--paper);
}
.gantt__today::after {
    content: "Auj.";
    position: absolute;
    top: -16px;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    color: var(--accent);
    white-space: nowrap;
}

.gantt__milestones {
    list-style: none;
    padding: 0;
    margin: 0;
    position: relative;
    height: 100px; /* Fix 2 — augmenté de 60px à 100px pour accueillir data-row="1" */
}
.gantt__milestone {
    position: absolute;
    top: 8px; /* Fix 2 — position par défaut row=0 */
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    min-width: 44px;
    min-height: 44px; /* WCAG 2.2 — cible tactile */
    padding: var(--space-1);
    transition: top var(--dur-fast) var(--ease-out); /* Fix 2 — transition douce */
}
/* Fix 2 — Jalons en collision décalés sur la seconde rangée */
.gantt__milestone[data-row="1"] { top: 56px; }
.gantt__milestone-marker {
    width: 14px;
    height: 14px;
    border-radius: var(--radius-full);
    background: var(--accent);
    box-shadow: 0 0 0 3px var(--paper);
    flex: 0 0 auto;
}
.gantt__milestone--reached .gantt__milestone-marker { background: var(--success); }
.gantt__milestone--overdue .gantt__milestone-marker { background: var(--warning); }
.gantt__milestone--undated .gantt__milestone-marker { background: var(--ink-400); }

.gantt__milestone-code {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    color: var(--text);
    font-variant-numeric: tabular-nums;
    text-align: center;
    line-height: 1.1;
}
.gantt__milestone-date {
    font-family: var(--font-mono);
    /* AX-4 : 10px → var(--text-2xs) ≥ 10.88px (WCAG 1.4.4) */
    font-size: var(--text-2xs);
    color: var(--text-mute);
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}

.gantt__empty {
    margin: var(--space-4) 0 0;
    padding: var(--space-4);
    background: var(--paper-dim);
    border: 1px dashed var(--border);
    border-radius: var(--radius-md);
    color: var(--text-soft);
    font-size: var(--text-sm);
    text-align: center;
}

/* — Liste détaillée des jalons (toujours visible — accessible mobile + a11y) — */
.gantt__list {
    list-style: none;
    padding: 0;
    margin: var(--space-5) 0 0;
    display: grid;
    gap: var(--space-2);
}
.gantt__list-item {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-2) var(--space-4);
    align-items: center;
    padding: var(--space-3) var(--space-4);
    background: var(--paper);
    border: 1px solid var(--border-subtle);
    border-left: 3px solid var(--ink-300);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
}
.gantt__list-item--reached  { border-left-color: var(--success); }
.gantt__list-item--overdue  { border-left-color: var(--warning); background: var(--warning-bg, var(--paper)); }
.gantt__list-item--upcoming { border-left-color: var(--accent); }
.gantt__list-item--undated  { border-left-color: var(--ink-400); }

.gantt__list-main {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.gantt__list-code {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    color: var(--text-soft);
    text-transform: uppercase;
}
.gantt__list-label {
    color: var(--text);
    line-height: 1.4;
}
.gantt__list-dates {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-1);
    text-align: right;
}
.gantt__list-dates .gantt__chip {
    width: auto;
    height: auto;
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    color: var(--on-accent);
    line-height: 1.4;
}
/* S23 Lot 1 (b) : --overdue posait color:var(--ink-900) sur fond --warning
   (#B45309) → 3.53:1, sous AA, dans les DEUX thèmes (--warning n'est pas
   redéfini en dark). --reached héritait color:var(--paper), qui tombe à
   3.87:1 en dark (--success non plus redéfini en dark, --paper si).
   --on-warning / --on-success règlent les deux cas (≥ 5:1, AA, les 2 thèmes). */
.gantt__list-dates .gantt__chip--reached  { background: var(--success); color: var(--on-success); }
.gantt__list-dates .gantt__chip--overdue  { background: var(--warning); color: var(--on-warning); }
.gantt__list-dates .gantt__chip--upcoming { background: var(--accent); }
.gantt__list-dates .gantt__chip--undated  { background: var(--ink-400); }

.gantt__list-actions {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px dashed var(--border-subtle);
}
.gantt__form-inline {
    display: inline-flex;
    margin: 0;
}
.gantt__btn-danger:hover {
    color: var(--error);
    border-color: var(--error);
}

/* — Form ajout (archi) ------------------------------------------------------
   S23 Lot 1 (j) : le disclosure lui-même (summary, chevron, chrome de carte)
   est désormais porté par le composant générique `.widget` (§45) — la vue
   utilise `<details class="widget gantt__add">`. `.gantt__add` ne sert plus
   qu'à cibler l'espacement propre à ce contexte et le masquage impression
   (règle @media print plus bas) ; conserver ce hook en HTML même sans style
   visuel propre. */
.gantt__add { margin-top: var(--space-5); }

/* — Responsive ≥ 640 px : on active le bandeau bande Gantt — */
@media (min-width: 640px) {
    .gantt__viewport { display: block; }
}

/* — ≥ 880 px : labels phases plus généreux — */
@media (min-width: 880px) {
    .gantt__phase { min-height: 72px; padding: var(--space-4) var(--space-2); }
    /* Fix 4 — Libellé long plus lisible pour le MOA à grande largeur */
    .gantt__phase-label { font-size: var(--text-xs); color: var(--text); }
    .gantt__list-item { grid-template-columns: 2fr 1fr auto; }
    .gantt__list-actions {
        grid-column: 3;
        border-top: 0;
        padding-top: 0;
    }
}

/* — Reduced motion : pas d'anim — */
@media (prefers-reduced-motion: reduce) {
    /* Fix 2 — désactiver la transition top des jalons */
    .gantt__milestone { transition: none; }
}

/* — Print : tout en pile, pas de chip de couleur (acceptance G19) — */
@media print {
    .gantt__viewport { display: none; }
    .gantt__add { display: none; }
    .gantt__list-actions { display: none; }
    .gantt__chip { background: transparent !important; color: inherit !important; border: 1px solid currentColor; padding: 0 4px; font-size: 8pt; }
    .gantt__chip::before { content: "["; }
    .gantt__chip::after { content: "]"; }
    .gantt__list-item { break-inside: avoid; }
    .gantt { break-inside: auto; }
}

/* ============================================================================
   12bis. GANTT v2 — Refonte UI/UX 2026-05 (preview « Pilotage du chantier »)
   ----------------------------------------------------------------------------
   Pièce maîtresse : un diagramme en GRILLE (lignes de mois verticales) à 3
   swimlanes, avec dé-collision des marqueurs par CLUSTERS « +N » (calcul
   serveur, cf. app/helpers/gantt_cluster.php), barre de phase à cap, ligne
   « Aujourd'hui » dans un rail d'en-tête dédié, bande lookahead discrète, et
   mode tableau a11y conservé.
   Conventions : BEM strict (`.gantt-v2__`), tokens DA Atelier v2 UNIQUEMENT,
   mobile-first, WCAG 2.2 AA (formes ≠ couleur seule), prefers-reduced-motion.
   ============================================================================ */

/* La section utilise .gantt comme conteneur (rétro-compat) + classe v2. On
   neutralise les styles v1 obsolètes (viewport ancien) quand .gantt-v2 actif. */
.gantt-v2 { padding: var(--space-5); }
.gantt-v2 .gantt__viewport { display: none !important; }
.gantt-v2 .gantt__legend    { display: none !important; }

.gantt-v2__header {
    display: flex; flex-direction: column; gap: var(--space-1);
    margin-bottom: var(--space-4);
}
.gantt-v2__title {
    font-family: var(--font-display); font-size: var(--text-lg);
    margin: 0; display: inline-flex; align-items: baseline; gap: var(--space-2);
    color: var(--text);
}
.gantt-v2__period { margin: 0; }

/* — Bandeau métriques : Phase en cours dominante + 3 KPI secondaires — */
.gantt-v2__metrics {
    list-style: none; padding: 0; margin: 0 0 var(--space-5);
    display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2);
}
.gantt-v2__metric {
    display: flex; flex-direction: column; gap: 2px;
    padding: var(--space-3) var(--space-4);
    background: var(--paper); border: 1px solid var(--border-subtle);
    border-top: 3px solid var(--ink-300); border-radius: var(--radius-md);
}
.gantt-v2__metric-value {
    font-family: var(--font-display); font-size: var(--text-xl);
    font-weight: 600; color: var(--text); line-height: 1.1;
}
.gantt-v2__metric-label {
    font-size: var(--text-2xs); text-transform: uppercase;
    letter-spacing: var(--tracking-wide); color: var(--text-soft);
    font-family: var(--font-mono);
}
.gantt-v2__metric-sub {
    font-size: var(--text-xs); color: var(--text-soft);
    font-family: var(--font-display); margin-top: 1px;
}
.gantt-v2__metric--success { border-top-color: var(--success); }
.gantt-v2__metric--success .gantt-v2__metric-value { color: var(--success); }
.gantt-v2__metric--warning { border-top-color: var(--warning); }
.gantt-v2__metric--warning .gantt-v2__metric-value { color: var(--warning); }
.gantt-v2__metric--accent  { border-top-color: var(--accent); }
.gantt-v2__metric--muted   { border-top-color: var(--ink-400); }

/* Carte « Phase en cours » : dominante, pleine largeur en tête, fond accent. */
.gantt-v2__metric--lead {
    grid-column: 1 / -1;
    flex-direction: row; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
    background: var(--accent-soft); border-color: var(--accent);
    border-top-color: var(--accent);
}
.gantt-v2__metric--lead .gantt-v2__metric-value {
    font-size: var(--text-2xl); color: var(--accent-text); order: 2;
}
.gantt-v2__metric--lead .gantt-v2__metric-label { color: var(--accent-text); order: 1; width: 100%; }
.gantt-v2__metric--lead .gantt-v2__metric-sub { color: var(--accent-text); order: 3; align-self: center; }

@media (min-width: 640px) {
    .gantt-v2__metrics { grid-template-columns: 1.6fr 1fr 1fr 1fr; }
    .gantt-v2__metric--lead { grid-column: auto; flex-direction: column; }
    .gantt-v2__metric--lead .gantt-v2__metric-value { order: 0; }
    .gantt-v2__metric--lead .gantt-v2__metric-label { order: 0; width: auto; }
    .gantt-v2__metric--lead .gantt-v2__metric-sub { order: 0; align-self: flex-start; }
}

/* — Frise pédagogique APS → GPA (v2) — reliée à la barre de phase du chart — */
.gantt-v2__phases {
    list-style: none; padding: 0; margin: 0 0 var(--space-4);
    display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--space-px);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
    overflow: hidden; background: var(--border-subtle);
}
.gantt-v2__phase {
    position: relative;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: var(--space-2);
    background: var(--paper); min-height: 46px;
    text-align: center; transition: background var(--dur) var(--ease);
}
.gantt-v2__phase-code {
    font-family: var(--font-mono); font-size: var(--text-2xs);
    color: var(--text-soft); letter-spacing: var(--tracking-wide);
    font-weight: 600;
}
.gantt-v2__phase-label {
    display: none; font-size: var(--text-2xs); color: var(--text-soft);
    margin-top: 1px;
}
.gantt-v2__phase--past   { background: var(--bg-sunken); }
.gantt-v2__phase--past .gantt-v2__phase-code::after {
    content: " ✓"; color: var(--success); font-family: var(--font-display);
}
.gantt-v2__phase--future .gantt-v2__phase-code { color: var(--text-mute); }
.gantt-v2__phase--active { background: var(--accent-soft); }
.gantt-v2__phase--active .gantt-v2__phase-code  { color: var(--accent-text); }
.gantt-v2__phase--active .gantt-v2__phase-label { color: var(--accent-text); font-weight: 600; }
/* Connecteur visuel : soulignement accent sous la phase active, prolongé vers
   la barre de phase du diagramme juste en dessous (même couleur = même chose). */
.gantt-v2__phase--active::after {
    content: ""; position: absolute; left: var(--space-2); right: var(--space-2);
    bottom: 0; height: 2px; background: var(--accent); border-radius: var(--radius-full);
}
@media (min-width: 880px) {
    .gantt-v2__phase { min-height: 58px; padding: var(--space-3); }
    .gantt-v2__phase-label { display: block; }
}

/* — Diagramme : conteneur — */
/* Sur petit écran, la zone temporelle peut manquer de pixels pour aérer les
   marqueurs (un écart en % donne moins de px) : le chart devient scrollable
   horizontalement et le plot reçoit une largeur minimale (la colonne titre
   reste dans le flux — le preview est court). ≥ 880px : tout tient en %, sans
   scroll. */
.gantt-v2__chart {
    margin-top: var(--space-2);
    background: var(--paper);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
}
/* Largeur de colonne titre, pilotée par une variable pour aligner grille, today,
   lookahead et axe sur la MÊME piste temporelle (zone à droite de la colonne). */
.gantt-v2__plot {
    --gantt-title-w: 72px;     /* mobile-first */
    position: relative;
    min-width: 640px;          /* garantit l'espace des marqueurs sur mobile */
    padding-bottom: 34px;      /* réserve l'axe */
}

/* — Couche grille : lignes de mois verticales traversant les lanes — */
.gantt-v2__grid {
    position: absolute; left: var(--gantt-title-w); right: 0;
    top: 22px;                 /* sous le rail today */
    bottom: 34px;              /* au-dessus de l'axe */
    z-index: 0; pointer-events: none;
}
.gantt-v2__gridline {
    position: absolute; top: 0; bottom: 0; width: 1px;
    background: var(--border-subtle);
}

/* — Rail d'en-tête : accueille la capsule « Aujourd'hui » au-dessus des lanes — */
.gantt-v2__rail {
    position: relative; height: 22px;
    margin-left: var(--gantt-title-w);
}
.gantt-v2__today-cap {
    position: absolute; top: 2px; transform: translateX(-50%);
    max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    background: var(--accent); color: var(--text-invert);
    padding: 2px var(--space-2); border-radius: var(--radius-full);
    font-family: var(--font-mono); font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide); font-weight: 600;
    box-shadow: var(--shadow-sm); z-index: 4;
}

/* — Swimlanes — */
.gantt-v2__swimlane {
    position: relative; z-index: 1;
    display: grid; grid-template-columns: var(--gantt-title-w) 1fr;
    align-items: center;
    min-height: 46px;
    border-top: 1px solid var(--border-subtle);
}
.gantt-v2__swimlane:first-of-type { border-top: 0; }
.gantt-v2__swimlane--phase { background: var(--bg-canvas); }

.gantt-v2__swimlane-title {
    align-self: stretch;
    display: flex; align-items: center;
    padding: 0 var(--space-2);
    font-size: var(--text-2xs); text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-soft); font-family: var(--font-mono); font-weight: 600;
    background: var(--bg-sunken);
    border-right: 1px solid var(--border-subtle);
}
.gantt-v2__lane {
    position: relative;
    height: 46px; margin: 0; padding: 0;
    list-style: none;
}
.gantt-v2__lane-empty {
    display: flex; align-items: center; height: 100%;
    padding: 0 var(--space-3); margin: 0;
}

/* — Barre phase active : corps fin à cap de début + flèche si ouverte — */
.gantt-v2__phase-bar {
    position: absolute; top: 50%; transform: translateY(-50%);
    height: 14px; min-width: 14px;
    background: var(--accent);
    border-radius: var(--radius-full);
    display: flex; align-items: center; gap: var(--space-1);
    padding-left: var(--space-2);
    box-shadow: var(--shadow-xs);
}
/* Cap de début : trait vertical accent plus épais marquant started_at. */
.gantt-v2__phase-bar-cap {
    position: absolute; left: -1px; top: 50%; transform: translateY(-50%);
    width: 3px; height: 22px; background: var(--accent);
    border-radius: var(--radius-full);
}
.gantt-v2__phase-bar-code {
    position: relative;
    font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
    letter-spacing: var(--tracking-wide);
    color: var(--text-invert);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    padding-right: var(--space-1);
}
/* Phase ouverte (en cours, non bornée) : la barre reste FONCÉE (accent) pour
   garantir le contraste AA du code blanc même sur barre courte ; l'effet
   « ouvert » est porté par la flèche › et un liseré pointillé à droite plutôt
   que par un dégradé clair (qui faisait chuter le contraste du texte). */
.gantt-v2__phase-bar--open {
    background: var(--accent);
    box-shadow: var(--shadow-xs), inset -6px 0 6px -4px var(--accent-hover);
}
.gantt-v2__phase-bar-arrow {
    position: absolute; right: var(--space-1); top: 50%; transform: translateY(-50%);
    color: var(--text-invert); font-weight: 700; font-size: var(--text-sm); line-height: 1;
}

/* — Nœuds de lane (markers isolés + clusters) — */
.gantt-v2__markers { display: block; }
.gantt-v2__node { position: static; }

.gantt-v2__marker {
    position: absolute; top: 50%;
    width: 15px; height: 15px;
    transform: translate(-50%, -50%);
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0; border: 0; background: none;
    cursor: pointer; outline: none; z-index: 2;
    transition: transform var(--dur-fast) var(--ease);
}
/* Hit-area ≥ 24px (WCAG 2.5.8) centrée, sans déborder la lane. */
.gantt-v2__marker::before {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: 28px; height: 28px; transform: translate(-50%, -50%);
}
.gantt-v2__marker:hover,
.gantt-v2__marker:focus-visible { transform: translate(-50%, -50%) scale(1.22); z-index: 5; }
.gantt-v2__marker:focus-visible { box-shadow: 0 0 0 3px var(--accent-soft); border-radius: var(--radius-full); }

/* La forme vit dans .gantt-v2__marker-shape — couleur portée par le statut. */
.gantt-v2__marker-shape {
    display: block; width: 100%; height: 100%;
    /* Halo paper : sépare visuellement deux markers proches (WCAG + lisibilité). */
    box-shadow: 0 0 0 2px var(--paper);
}
/* Jalon = LOSANGE.
   S23 Lot 1 (b) : défaut + --upcoming étaient en --ink-500 (#71717A), quasi
   invisible sur fond sombre (non redéfini en dark). 5 états d'échéance
   harmonisés : --accent par défaut (upcoming/undated), --success (reached),
   --warning (overdue) — le halo `box-shadow: 0 0 0 2px var(--paper)` porté par
   .gantt-v2__marker-shape (règle du dessus) sert de bordure de lisibilité. */
.gantt-v2__marker--milestone .gantt-v2__marker-shape {
    background: var(--accent); transform: rotate(45deg); border-radius: var(--radius-xs);
}
.gantt-v2__marker--milestone.gantt-v2__marker--reached  .gantt-v2__marker-shape { background: var(--success); }
.gantt-v2__marker--milestone.gantt-v2__marker--overdue  .gantt-v2__marker-shape { background: var(--warning); }
.gantt-v2__marker--milestone.gantt-v2__marker--upcoming .gantt-v2__marker-shape { background: var(--accent); }
/* Plan = CARRÉ */
.gantt-v2__marker--plan .gantt-v2__marker-shape {
    background: var(--accent); border-radius: var(--radius-xs);
}
/* Facture = TRIANGLE (clip-path) */
.gantt-v2__marker--invoice .gantt-v2__marker-shape {
    background: var(--warning);
    clip-path: polygon(50% 0, 100% 100%, 0 100%);
    box-shadow: none; /* le halo casse le clip ; on borde via filter drop-shadow */
    filter: drop-shadow(0 0 0.5px var(--paper)) drop-shadow(0 0 0.5px var(--paper));
}
.gantt-v2__marker--invoice.gantt-v2__marker--overdue .gantt-v2__marker-shape { background: var(--error); }
/* Compte-rendu = CERCLE */
.gantt-v2__marker--report .gantt-v2__marker-shape {
    background: var(--text-soft); border-radius: var(--radius-full);
}

/* — Cluster « +N » : pastille ronde compacte, dépliable — */
.gantt-v2__marker--cluster {
    width: 22px; height: 22px;
    background: var(--ink-700); color: var(--text-invert);
    border-radius: var(--radius-full);
    box-shadow: 0 0 0 2px var(--paper), var(--shadow-sm);
    font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 700;
}
.gantt-v2__marker--cluster.gantt-v2__marker--overdue  { background: var(--warning); }
.gantt-v2__marker--cluster.gantt-v2__marker--upcoming { background: var(--accent); }
.gantt-v2__marker--cluster.gantt-v2__marker--reached  { background: var(--ink-700); }
.gantt-v2__marker-count { line-height: 1; display: inline-flex; align-items: baseline; }
.gantt-v2__marker-count::before { content: "+"; font-size: 0.78em; margin-right: -0.5px; }

/* Popover de cluster (révélé par gantt-view.js sur clic/Enter). */
.gantt-v2__cluster-pop {
    position: absolute; top: calc(50% + 16px); left: 50%; transform: translateX(-50%);
    z-index: 30; min-width: 200px; max-width: 280px;
    padding: var(--space-2);
    background: var(--bg-elevated); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
}
.gantt-v2__cluster-pop[hidden] { display: none; }
/* État « flottant » : gantt-view.js porte le popover sur <body> et le positionne
   en `fixed` (top/left posés en inline depuis le bouton). Indispensable : le chart
   est `overflow-x:auto` → la spec force `overflow-y` à non-visible, ce qui rognait
   le bas du popover (bug constaté), et `.main` est animé (translateY) — deux
   contextes qui piégeaient le popover en position absolue. max-height + scroll =
   garde-fou si la liste est longue sur petit écran. */
.gantt-v2__cluster-pop--floating {
    position: fixed; margin: 0; transform: none;
    z-index: 60;
    max-height: calc(100vh - 16px); overflow-y: auto;
}
.gantt-v2__cluster-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-1); }
.gantt-v2__cluster-item {
    display: grid; grid-template-columns: 14px 1fr auto; align-items: center; gap: var(--space-2);
    font-size: var(--text-xs); color: var(--text);
}
.gantt-v2__cluster-shape { width: 10px; height: 10px; }
/* S23 Lot 1 (b) : idem marker isolé — --ink-500 invisible en dark, aligné sur --accent. */
.gantt-v2__cluster-item--milestone .gantt-v2__cluster-shape { background: var(--accent); transform: rotate(45deg); border-radius: var(--radius-xs); }
.gantt-v2__cluster-item--milestone.gantt-v2__cluster-item--reached .gantt-v2__cluster-shape { background: var(--success); }
.gantt-v2__cluster-item--milestone.gantt-v2__cluster-item--overdue .gantt-v2__cluster-shape { background: var(--warning); }
.gantt-v2__cluster-item--plan    .gantt-v2__cluster-shape { background: var(--accent); border-radius: var(--radius-xs); }
.gantt-v2__cluster-item--invoice .gantt-v2__cluster-shape { background: var(--warning); clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.gantt-v2__cluster-item--invoice.gantt-v2__cluster-item--overdue .gantt-v2__cluster-shape { background: var(--error); }
.gantt-v2__cluster-item--report  .gantt-v2__cluster-shape { background: var(--text-soft); border-radius: var(--radius-full); }
.gantt-v2__cluster-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gantt-v2__cluster-date { white-space: nowrap; }

/* — Bande Lookahead 3 semaines (décorative, info portée par aria-label) — */
.gantt-v2__lookahead {
    position: absolute; top: 22px; bottom: 34px;
    background: var(--accent-soft);
    border-left: 1px dashed var(--accent);
    border-right: 1px dashed var(--accent);
    z-index: 0;
}

/* — Ligne « Aujourd'hui » verticale, traversant les lanes (capsule au rail) — */
.gantt-v2__today {
    position: absolute; top: 22px; bottom: 34px; width: 0;
    border-left: 2px solid var(--accent);
    z-index: 3;
}

/* — Axe : graduations en bas, alignées sur la piste temporelle — */
/* Fond paper explicite : (1) l'axe masque proprement grille/markers derrière,
   (2) donne à axe-core un fond résoluble pour le calcul de contraste des ticks
   (sans fond, les ticks position:absolute déclenchent un faux positif). */
.gantt-v2__axis {
    position: absolute; left: 0; right: 0; bottom: 0;
    display: grid; grid-template-columns: var(--gantt-title-w) 1fr;
    height: 34px;
    background: var(--paper);
    border-top: 1px solid var(--border-subtle);
}
.gantt-v2__axis-spacer { background: var(--bg-sunken); border-right: 1px solid var(--border-subtle); }
.gantt-v2__axis-track { position: relative; background: var(--paper); }
.gantt-v2__tick {
    position: absolute; top: 5px;
    transform: translateX(-50%);
    display: flex; flex-direction: column; align-items: center;
    font-size: var(--text-2xs); color: var(--text-soft);
    font-family: var(--font-mono); white-space: nowrap;
}
.gantt-v2__tick strong { font-weight: 600; color: var(--text); line-height: 1.1; }
/* AA : --text-mute (#737378, 4.72:1) interdit < 18px non gras → --text-soft (10.4:1). */
.gantt-v2__tick em { font-style: normal; letter-spacing: var(--tracking-wide); font-size: var(--text-2xs); color: var(--text-soft); }

/* — Légende compacte (formes ≠ couleur seule, WCAG 1.4.1) — */
.gantt-v2__legend {
    list-style: none; margin: 0; padding: var(--space-2) var(--space-3) 0;
    display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-4);
}
.gantt-v2__legend-item {
    display: inline-flex; align-items: center; gap: var(--space-2);
    font-size: var(--text-2xs); color: var(--text-soft); font-family: var(--font-mono);
    text-transform: uppercase; letter-spacing: var(--tracking-wide);
}
.gantt-v2__legend-shape { width: 11px; height: 11px; flex: none; }
/* S23 Lot 1 (b) : --ink-500 invisible en dark, et n'accordait plus avec le
   losange réel (désormais --accent par défaut, cf. .gantt-v2__marker--milestone). */
.gantt-v2__legend-shape--milestone { background: var(--accent); transform: rotate(45deg); border-radius: var(--radius-xs); }
.gantt-v2__legend-shape--plan      { background: var(--accent); border-radius: var(--radius-xs); }
.gantt-v2__legend-shape--invoice   { background: var(--warning); clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.gantt-v2__legend-shape--report    { background: var(--text-soft); border-radius: var(--radius-full); }
.gantt-v2__legend-look {
    width: 18px; height: 11px; flex: none;
    background: var(--accent-soft);
    border-left: 1px dashed var(--accent); border-right: 1px dashed var(--accent);
}

/* — Actions (toggle tableau + planning désactivé) — */
.gantt-v2__actions {
    display: flex; flex-wrap: wrap; gap: var(--space-2);
    margin: var(--space-3) 0 var(--space-2);
}
.gantt-v2__planning:disabled,
.gantt-v2__planning[aria-disabled="true"] {
    opacity: 0.55; cursor: not-allowed;
}
.gantt-v2__toggle[aria-expanded="true"] {
    background: var(--accent-soft); color: var(--accent-text);
}

/* — Mode tableau (équivalent a11y) — */
.gantt-v2__table {
    width: 100%; border-collapse: collapse;
    margin-top: var(--space-3);
    font-size: var(--text-sm);
}
.gantt-v2__table caption {
    text-align: left; padding: var(--space-2) 0;
    color: var(--text-soft); font-size: var(--text-2xs);
}
.gantt-v2__table th,
.gantt-v2__table td {
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border-subtle);
    text-align: left;
}
.gantt-v2__table th {
    background: var(--bg-sunken); font-weight: 600;
    text-transform: uppercase; letter-spacing: var(--tracking-wide);
    font-size: var(--text-2xs); color: var(--text-soft);
    font-family: var(--font-mono);
}
.gantt-v2__table[hidden] { display: none; }

/* — Empty state — */
.gantt-v2__empty {
    padding: var(--space-6) var(--space-4);
    text-align: center;
    background: var(--paper-soft);
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    margin-top: var(--space-4);
}
.gantt-v2__empty-title {
    font-family: var(--font-display); font-size: var(--text-lg);
    margin: 0 0 var(--space-2);
}
.gantt-v2__empty-help { margin: 0 0 var(--space-3); max-width: 48ch; margin-inline: auto; }

/* — Responsive : élargit la colonne titre + labels frise dès 641px — */
@media (min-width: 641px) {
    .gantt-v2__plot { --gantt-title-w: 96px; }
}
/* ≥ 880px : le chart tient pleinement en largeur, plus de scroll horizontal. */
@media (min-width: 880px) {
    .gantt-v2__chart { overflow-x: hidden; }
    .gantt-v2__plot  { min-width: 0; }
}

/* — Reduced motion — */
@media (prefers-reduced-motion: reduce) {
    .gantt-v2__phase,
    .gantt-v2__marker { transition: none; }
    .gantt-v2__marker:hover,
    .gantt-v2__marker:focus-visible { transform: translate(-50%, -50%); }
}

/* — Print : on cache le chart, on garde le tableau & la liste — */
@media print {
    .gantt-v2__chart, .gantt-v2__actions { display: none; }
    .gantt-v2__table[hidden] { display: table !important; }
    .gantt-v2__metrics { grid-template-columns: repeat(4, 1fr); }
}

/* S24 Lot 2 : `.chantier__members` retiré (section fusionnée dans `.client-card`, `_members.php` supprimé). */
/* S24 Lot 2 : .chantier__companies vit désormais dans la grille
   .chantier-contacts (§12.bis) — l'espacement avec .client-card est porté par
   le `gap` de la grille, pas par une marge individuelle (évite un double
   espacement en pile mobile 1 colonne). */

.members__list,
.companies__list {
    list-style: none;
    padding: 0;
    margin: var(--space-3) 0 var(--space-5);
    display: grid;
    gap: var(--space-2);
}
.members__list li,
.companies__list li {
    padding: var(--space-3) var(--space-4);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: 1.6;
}
.members__list li strong,
.companies__list li strong {
    color: var(--text);
    font-weight: 600;
}

/* S23 Lot 1 (j) : disclosure/chrome remplacés par `.widget` (§45), la vue
   utilise `<details class="widget invite">`. `.invite` ne conserve que le
   masquage impression (règle @media print) — hook HTML à garder. */
.invite { margin-top: var(--space-4); }

/* ----------------------------------------------------------------------------
   12.bis CONTACTS (S24 Lot 2 — vue d'ensemble chantier, bloc 3)
   docs/sprints/SPECS-V2-LOTS-2-3-4.md §A.3, §A.4. Remplace la carte "Suivi du
   maître d'ouvrage" (`.chantier__suivi`, `_suivi.php`) : type de projet et
   budget rejoignent la fiche client (`.client-card`), les rendez-vous partent
   sur l'onglet Planning (section « Rendez-vous », card générique sans classe
   dédiée). `_suivi.php` a été supprimé et `.chantier__suivi` retiré du CSS
   (bloc devenu orphelin).
   Snippet HTML de référence : docs/UI-DESIGN-SYSTEM.md §7.5.
   ---------------------------------------------------------------------------- */

/* — Grille 2 colonnes ≥ 768px : Fiche client + Entreprises ------------------- */
.chantier-contacts {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-6);
    align-items: start;
    margin-top: var(--space-6);
}
@media (min-width: 768px) {
    .chantier-contacts { grid-template-columns: 1fr 1fr; }
}

/* — Fiche client -------------------------------------------------------------
   `<dl>` de coordonnées (lecture) + bloc "Visible par le client" (tags) +
   `<details class="widget">` pour l'édition (réutilise §9 form + §45 widget,
   aucun style de formulaire dédié ici). */
.client-card__info {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin: var(--space-3) 0 var(--space-4);
}
.client-card__row {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border-subtle);
    font-size: var(--text-sm);
}
.client-card__row:last-child { border-bottom: 0; }
@media (min-width: 481px) {
    .client-card__row {
        flex-direction: row;
        align-items: baseline;
        gap: var(--space-3);
    }
    .client-card__label { flex: 0 0 9rem; }
}
.client-card__label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    align-items: flex-start;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--text-dim);
}
.client-card__value { min-width: 0; color: var(--text); overflow-wrap: anywhere; }
.client-card__value a { color: var(--accent-text); }
.client-card__account { margin: 0 0 var(--space-5); font-size: var(--text-sm); color: var(--text); }
.client-card__account--empty { color: var(--text-dim); }
.client-card__visible {
    margin: 0 0 var(--space-5);
    padding: var(--space-4);
    background: var(--bg-sunken);
    border-radius: var(--radius-md);
}
.client-card__visible-title {
    margin: 0 0 var(--space-3);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-soft);
}
.client-card__visible .client-card__info { margin: 0; }

/* — Dossiers de phase (placeholder Lot 2 → composant réel Lot 6, ADR-0025) ---
   Un `<details class="widget phase-dossiers__item">` (§45) par phase
   canonique APS→GPA (`project_phases_catalog()`), plus un dernier groupe
   toujours affiché « Non classé » (`.phase-dossiers__unclassified`) pour les
   documents sans `phase_code` — même conviction que « Sans lot » / « Sans
   provenance » (§45/§22 : position stable dans la pile, jamais masqué).

   Anatomie d'un widget de phase (hérite de `.widget`, rien de plus au repos) :
     <details class="widget phase-dossiers__item phase-dossiers__item--current" open>
       <summary class="widget__summary">
         <span class="widget__title">DCE — Consultation des entreprises</span>
         <span class="widget__count">4</span>
         <span class="badge badge--accent">Phase en cours</span>
       </summary>
       <div class="widget__body">
         <div class="table-responsive"><table class="dossier__table">…</table></div>
         <!-- état vide : .empty-note (§19), réutilisé tel quel -->
         <!-- DCE uniquement : .dossier__markets (marchés signés par lot) -->
         <details class="widget widget--flush dossier__add"> … </details>
       </div>
     </details>
   Markup de référence complet, avec les 3 personas : docs/UI-DESIGN-SYSTEM.md
   §7.5 « Lot 6 — Dossiers de phase ».

   États — 3 modificateurs sur `.phase-dossiers__item`, jamais combinés :
     --current : phase en cours du projet (seule ouverte par défaut, `open`
       posé côté PHP). Rail accent gauche : même recette que `.widget--lot`
       (§45) — duplication scoped intentionnelle (bloc BEM différent : une
       « phase de dossier » n'est pas un « lot de travaux », cf. §0.1).
     --past : phase déjà franchie. Repliée par défaut, `.widget__count`
       affiche son nombre de pièces. **Aucune règle CSS dédiée** : le chrome
       `.widget` par défaut suffit (une phase passée reste un contenu à part
       entière, pas un état dégradé) — la classe existe pour le ciblage
       PHP/tests uniquement ; documenté ici pour ne pas déclencher
       `check-orphan-classes.sh` à tort.
     --future : phase pas encore atteinte. Repliée par défaut (jamais
       ouverte, même vide — densité basse prioritaire). Rail neutre + titre
       atténué : même recette que `.widget--lot.widget--muted` (§45).
       **Jamais d'`opacity` globale** (casserait le contraste AA du texte,
       cf. checklist a11y ux-designer) — le grisé passe uniquement par les
       tokens de texte/bordure déjà audités AA. Reste pleinement
       fonctionnelle : « éditable en permanence » (ex. permis modificatif
       déposé en phase PC alors que la phase courante est EXE) — le grisé
       est un signal de priorité de lecture, jamais une désactivation.

   `.phase-dossiers__unclassified` : groupe « Non classé ». Même rail neutre
   que `--future` (regroupé dans le même sélecteur ci-dessous, un seul bloc
   BEM « phase-dossiers »/« dossier »). Sa table ajoute une action « Classer »
   par ligne (`.dossier__classify`).

   `.phase-dossiers--readonly` : posé sur le conteneur `.phase-dossiers` côté
   lecture (client/artisan). La vue ne rend normalement pas
   `.dossier__add`/`.dossier__classify` côté lecture (le contenu diffère par
   persona, jamais la teinte, même logique que `[data-persona]`) ; la règle
   CSS ci-dessous n'est qu'un filet de sécurité **présentation seulement**
   (jamais un contrôle d'accès — le vrai contrôle reste côté PHP/serveur) au
   cas où un gabarit partagé laisserait passer un de ces blocs par erreur. */
.phase-dossiers { display: flex; flex-direction: column; }

.phase-dossiers__item--current {
    border-left: 3px solid var(--accent);
    padding-left: var(--space-4);
}

.phase-dossiers__item--future,
.phase-dossiers__unclassified {
    border-left: 3px solid var(--border);
    padding-left: var(--space-4);
}
.phase-dossiers__item--future .widget__title,
.phase-dossiers__unclassified .widget__title {
    color: var(--text-soft);
    font-weight: 400;
}

/* — Table des pièces d'un dossier de phase (Titre / Catégorie / Visibilité /
   Ajouté le / Télécharger) — même recette compacte que `.lot__table` (§45)
   et `.plans__source-table` (§22), dupliquée à dessein pour rester dans son
   propre bloc BEM (cf. §0.1). Toujours dans un wrapper `.table-responsive`
   (§20), jamais ajoutée à la liste `table.xxx { display:block; ... }` du
   §20 — même convention que `.lot__table`/`.plans__source-table`.
   Réutilisée telle quelle pour la table des marchés signés
   (`.dossier__markets`) : mêmes colonnes de style, contenu différent. Le
   contenu des cellules réutilise des composants existants uniquement :
   `.tag` (catégorie), `.tag--client-visible` / `.badge--info` / `.badge--muted`
   (visibilité, même recette que `.docs__table`), `.lot__signed` (marché
   signé), `.btn--ghost.btn--xs` (Télécharger). */
.dossier__table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}
.dossier__table th,
.dossier__table td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--border-subtle);
}
.dossier__table th {
    font-weight: 500;
    color: var(--text-soft);
    text-transform: uppercase;
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.dossier__table tbody tr:last-child td { border-bottom: 0; }
.dossier__table tbody tr:hover { background: var(--paper-soft); }

/* — Sous-section DCE : marchés signés par lot (réutilise `quotes_grouped_by_lot()`
   du Lot 4) — un simple titre + `.dossier__table`, badge `.lot__signed` (§45,
   RÉUTILISÉ tel quel : même sémantique « Marché signé »). Affichée seulement
   dans le widget de la phase DCE. */
.dossier__markets { margin-top: var(--space-5); }
.dossier__markets-title {
    margin: 0 0 var(--space-3);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-soft);
}

/* — Formulaire d'ajout inline « Ajouter une pièce à ce dossier »
   (architecte uniquement, absent en `.phase-dossiers--readonly`) —
   disclosure sans chrome propre : hérite entièrement de `.widget--flush`
   (§45), contenu = `.form` standard (§9), rien de dédié. Seul apport de
   cette classe : l'espacement avec le contenu au-dessus (table ou état
   vide) dans `.widget__body`. */
.dossier__add { margin-top: var(--space-4); }

/* — Action « Classer » d'un document non classé (groupe « Non classé »
   uniquement) — sélecteur de phase + bouton, dans la dernière colonne de
   `.dossier__table`. Recette compacte propre (pas `.form__control-group`,
   dont le gabarit est pensé pour une ligne de formulaire pleine largeur,
   pas une cellule de tableau) : `.form__input`/`.btn--xs` existants,
   resserrés ici. */
.dossier__classify {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1);
}
.dossier__classify .form__input {
    flex: 1 1 140px;
    width: auto;
    min-height: 44px;
}

/* — Filet de sécurité présentation pour la lecture (client/artisan) --------
   Le contrôle réel (aucun formulaire rendu) est côté PHP ; cette règle ne
   protège rien, elle masque au cas où un fragment serait partagé par erreur
   entre personas (cf. commentaire au-dessus de `.phase-dossiers`). */
.phase-dossiers--readonly .dossier__add,
.phase-dossiers--readonly .dossier__classify {
    display: none;
}

/* — Activité récente (repliée en bas de la vue d'ensemble) -------------------
   `<details class="widget widget--flush chantier__activity">` — quitte la
   grille `.bento` (ancien bloc 4f) pour devenir une ligne discrète en pied de
   page (docs/PLAN-EVOLUTION-V2-2026-09-15.md §2.2). Contenu interne
   inchangé : `.activity__list`/`__item`/`__time`/`__body`/`__text` (§11.bis.5). */
.chantier__activity {
    margin-top: var(--space-8);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border-subtle);
}

/* ============================================================================
   13. BADGES / TAGS / PHASES
   ============================================================================ */
.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    line-height: 1.4;
    background: var(--bg-sunken);
    color: var(--text-soft);
    border: 1px solid var(--border);
}
.badge--unread,
.badge--accent {
    background: var(--accent);
    color: var(--on-accent);
    border-color: var(--accent);
}
/* 2026-05 — Dark mode : --accent passe à #ED824A (clay-400), contraste avec
   --paper (#FAFAFA) = 3.41:1 → NON conforme WCAG AA texte normal sur badge
   compact. On force clay-700 (#7C2D12) en dark : paper/clay-700 = 9.41:1. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .badge--unread,
    :root:not([data-theme="light"]) .badge--accent {
        background: var(--clay-700);
        border-color: var(--clay-700);
        color: #FFFFFF;
    }
}

/* Surfaces de statut — texte clair en dark -------------------------------------
   Complément des tints sombres *-bg (cf. bloc :root dark). Les composants qui
   posent du texte couleur-de-statut (var(--success/--warning/--info/--error))
   sur un fond *-bg deviendraient peu contrastés sur le tint sombre : on repasse
   leur texte en teinte claire (shades 300/400, AA+ sur fond sombre). Les fonds
   pleins (boutons, jalons, points) ne sont PAS concernés (ils gardent --status
   + texte blanc). Light mode inchangé. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .badge--success,
    :root:not([data-theme="light"]) .badge--ok,
    :root:not([data-theme="light"]) .badge--paid,
    :root:not([data-theme="light"]) .flash__item--success,
    :root:not([data-theme="light"]) .alert--success,
    :root:not([data-theme="light"]) .toast--success,
    :root:not([data-theme="light"]) .upload__item.is-success,
    :root:not([data-theme="light"]) .kpi--success .kpi__hint { color: #4ADE80; }

    :root:not([data-theme="light"]) .badge--warning,
    :root:not([data-theme="light"]) .badge--warn,
    :root:not([data-theme="light"]) .flash__item--warning,
    :root:not([data-theme="light"]) .alert--warning,
    :root:not([data-theme="light"]) .legal__placeholder,
    :root:not([data-theme="light"]) .project-card__alerts,
    :root:not([data-theme="light"]) .kpi--warning .kpi__hint { color: #FBBF24; }

    :root:not([data-theme="light"]) .badge--info,
    :root:not([data-theme="light"]) .badge--sent,
    :root:not([data-theme="light"]) .flash__item--info,
    :root:not([data-theme="light"]) .alert--info,
    :root:not([data-theme="light"]) .toast--info,
    :root:not([data-theme="light"]) .kpi--info .kpi__hint { color: #93C5FD; }

    :root:not([data-theme="light"]) .badge--error,
    :root:not([data-theme="light"]) .flash__item--error,
    :root:not([data-theme="light"]) .alert--error,
    :root:not([data-theme="light"]) .toast--error,
    :root:not([data-theme="light"]) .upload__item.is-error,
    :root:not([data-theme="light"]) .menu__item--danger,
    :root:not([data-theme="light"]) .kpi--error .kpi__hint { color: #FCA5A5; }

    /* Bandeau Transactions : la VALEUR colorée (.kpi__value) suit la même logique
       que les hints sémantiques ci-dessus. Les tokens --success/--warning/--error
       ne sont pas redéfinis en dark (teintes foncées) → illisibles sur fond sombre.
       On repasse aux shades clairs (mêmes valeurs que les hints, 9–10:1 sur
       --bg-elevated dark, AA+). Light mode conserve var(--status) (≥ 5:1). */
    :root:not([data-theme="light"]) .transactions__summary .kpi--success .kpi__value { color: #4ADE80; }
    :root:not([data-theme="light"]) .transactions__summary .kpi--warning .kpi__value { color: #FBBF24; }
    :root:not([data-theme="light"]) .transactions__summary .kpi--error   .kpi__value { color: #FCA5A5; }

    /* État zéro en dark : --text-mute (#71717A) = 3.67:1 < AA. On remonte à
       --text-dim (#A1A1AA = 6.91:1) tout en restant nettement dégradé face aux
       valeurs colorées vives → la hiérarchie « rien à traiter » est préservée. */
    :root:not([data-theme="light"]) .transactions__summary .kpi--zero .kpi__value { color: var(--text-dim); }
}
.badge--ok,
.badge--paid,
.badge--success {
    background: var(--success-bg);
    color: var(--success);
    border-color: var(--success-border);
}
.badge--warn,
.badge--warning {
    background: var(--warning-bg);
    color: var(--warning);
    border-color: var(--warning-border);
}
.badge--info,
.badge--sent {
    background: var(--info-bg);
    color: var(--info);
    border-color: var(--info-border);
}
.badge--error {
    background: var(--error-bg);
    color: var(--error);
    border-color: var(--error-border);
}
.badge--muted {
    background: var(--bg-sunken);
    color: var(--text-dim);
    border-color: var(--border);
}
/* Pastille de statut — hérite de la couleur sémantique du badge via currentColor.
   Rend les statuts scannables d'un coup d'œil (Réussi vert ≠ Échoué rouge).
   Décorative : doublée du libellé textuel, donc aria-hidden côté HTML. */
.badge__dot {
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: var(--radius-full);
    background: currentColor;
}

.tag {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    /* S15 fix WCAG AA dans light ET dark mode : --text-on-sunken (light=ink-600
       #52525B → 7.08:1 sur #F5F5F4 ; dark=#A1A1AA → 11.92:1 sur #050506).
       Token sémantique introduit S15. */
    color: var(--text-on-sunken);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}
/* Variante kicker — annonce au-dessus d'un titre (utilisée en section__header--stack) */
.tag--kicker { margin-bottom: var(--space-2); }

/* S24 Lot 2 — fiche client (docs/sprints/SPECS-V2-LOTS-2-3-4.md §A.4) :
   marquage explicite des champs qui alimentent le dashboard MOA (`.tag`
   base est pensée pour des codes courts en majuscules mono ; ces libellés
   sont des phrases, on repasse donc en sans-serif casse normale pour la
   lisibilité). Jamais la couleur seule : le texte du tag porte l'information
   ("Visible par le client" / "Notes internes — jamais visibles du client"). */
.tag--client-visible,
.tag--internal {
    font-family: var(--font-sans);
    text-transform: none;
    letter-spacing: normal;
    font-weight: 600;
}
.tag--client-visible { background: var(--info-bg); color: var(--info); border-color: var(--info-border); }
.tag--internal { background: var(--warning-bg); color: var(--warning); border-color: var(--warning-border); }

/* Status dot */
.dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    background: var(--text-mute);
}
.dot--ok      { background: var(--success); }
.dot--warn    { background: var(--warning); }
.dot--error   { background: var(--error); }
.dot--accent  { background: var(--accent); animation: pulse var(--dur-pulse) var(--ease) infinite; }

@keyframes pulse {
    0%, 100% { box-shadow: 0 0 0 0 currentColor; opacity: 1; }
    50%      { box-shadow: 0 0 0 4px transparent; opacity: 0.6; }
}

/* ============================================================================
   14. FLASHES / ALERTS
   ============================================================================ */
.flash {
    list-style: none;
    padding: 0;
    margin: var(--space-4) auto;
    max-width: var(--content-max);
    padding: 0 var(--space-6);
    display: grid;
    gap: var(--space-2);
}
.flash__item {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--bg-elevated);
    box-shadow: var(--shadow-sm);
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    font-size: var(--text-sm);
    animation: slideDown var(--dur-slow) var(--ease-out);
}
.flash__item::before {
    content: "";
    width: 4px;
    align-self: stretch;
    background: var(--border);
    border-radius: var(--radius-sm);
    flex-shrink: 0;
}
.flash__item--success { border-color: var(--success-border); color: var(--success); background: var(--success-bg); }
.flash__item--success::before { background: var(--success); }
.flash__item--error   { border-color: var(--error-border);   color: var(--error);   background: var(--error-bg); }
.flash__item--error::before { background: var(--error); }
.flash__item--info    { border-color: var(--info-border);    color: var(--info);    background: var(--info-bg); }
.flash__item--info::before { background: var(--info); }
.flash__item--warning { border-color: var(--warning-border); color: var(--warning); background: var(--warning-bg); }
.flash__item--warning::before { background: var(--warning); }

@keyframes slideDown {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
}

.alert {
    padding: var(--space-4) var(--space-5);
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--bg-elevated);
    margin: var(--space-4) 0;
    font-size: var(--text-sm);
}
.alert--warn,
.alert--warning { border-color: var(--warning-border); background: var(--warning-bg); color: var(--warning); }
.alert--info    { border-color: var(--info-border);    background: var(--info-bg);    color: var(--info); }
.alert--error   { border-color: var(--error-border);   background: var(--error-bg);   color: var(--error); }
.alert--success { border-color: var(--success-border); background: var(--success-bg); color: var(--success); }

/* ============================================================================
   15. TABLES (génériques)
   ============================================================================ */
table.quotes__table, table.invoices__table,
table.docs__table, table.decisions__table, table.lifecycle__table,
table.legal__table, table.data-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    font-size: var(--text-sm);
}
table th, table td {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    border-bottom: 1px solid var(--border-subtle);
    vertical-align: top;
}
table th {
    background: var(--bg-sunken);
    font-weight: 500;
    /* S15 fix WCAG AA dans light ET dark mode : token --text-on-sunken,
       idem .tag. Cf. SPRINT-15-LIVRAISON.md §Findings corrigés. */
    color: var(--text-on-sunken);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-widest);
}
table tr:last-child td { border-bottom: 0; }
table tr:hover td { background: var(--bg-sunken); }

/* ============================================================================
   16. PROFIL / DANGER ZONE
   ============================================================================ */
.profile-card { margin-bottom: var(--space-6); }
.profile-card__dl { margin: 0; display: grid; gap: var(--space-3); }
.profile-card__dl--cozy { gap: var(--space-3); }
.profile-card__dl dt {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
    margin: 0;
}
.profile-card__dl dd {
    margin: 0;
    color: var(--text);
    font-size: var(--text-base);
}
.profile-card--danger {
    border-color: var(--error-border);
    background: linear-gradient(to right bottom, var(--error-bg), transparent 30%);
}

/* — Refonte profil 2026-05-26 (DS Atelier v2)
   ----------------------------------------------------------------------------
   Composants nouveaux destinés à la page /app/profile (et /app/profile/delete) :
   on partage avec le DS existant — pas de palette ad hoc, tokens uniquement.
   Architecture : bento (§11) + .profile-* en habillage interne des cells.
   ---------------------------------------------------------------------------- */
.profile { display: block; }
.profile--narrow { max-width: var(--content-prose); margin: 0 auto; }

/* Hero interne d'une card identité : avatar + résumé en flex baseline. */
.profile-hero {
    display: flex;
    align-items: center;
    /* mobile-first : gap réduit ; élargi ≥ 881px */
    gap: var(--space-3);
    margin-bottom: var(--space-6);
    flex-wrap: wrap;
}
.profile-avatar {
    display: inline-block;
    width: 48px;
    height: 48px;
    border-radius: var(--radius-full);
    background: var(--grad-clay);
    border: 1px solid var(--clay-700);
    flex-shrink: 0;
    box-shadow: var(--shadow-sm);
}
.profile-avatar--lg { width: 56px; height: 56px; }

.profile-summary { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.profile-summary__name {
    font-family: var(--font-display);
    /* mobile-first : text-xl ; text-2xl ≥ 881px */
    font-size: var(--text-xl);
    font-weight: 500;
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.profile-summary__email {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    color: var(--text-dim);
    word-break: break-all;
}

/* Pill persona — variante de .tag avec glyphe Unicode (cohérent topbar__brand-tag). */
.profile-persona {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: var(--text-on-sunken);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-full);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    align-self: flex-start;
    margin-top: var(--space-1);
}
.profile-persona__glyph {
    font-size: 0.95em;
    line-height: 1;
    opacity: 0.75;
}
.profile-persona__label { line-height: 1; }

/* Méta-grille sous le hero (date inscription, statut email…). Inspirée
   .profile-card__dl mais alignée horizontal en colonnes lisibles. */
.profile-meta {
    display: grid;
    /* mobile-first : 1 colonne (≤480px) → 2 colonnes (481-880px) → auto-fit (≥881px) */
    grid-template-columns: 1fr;
    gap: var(--space-3);
    margin: 0 0 var(--space-6);
    padding: var(--space-4) 0;
    border-top: 1px solid var(--border-subtle);
    border-bottom: 1px solid var(--border-subtle);
}
.profile-meta > div { display: flex; flex-direction: column; gap: var(--space-1); }
.profile-meta dt {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
    margin: 0;
}
.profile-meta dd {
    margin: 0;
    color: var(--text);
    font-size: var(--text-base);
    font-variant-numeric: tabular-nums;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

/* Form profil — espace plus aéré que le .form générique car habite une card. */
.profile-form { display: flex; flex-direction: column; gap: var(--space-4); }
.profile-form__notice {
    margin: var(--space-2) 0 0;
    padding: var(--space-3) var(--space-4);
    background: var(--bg-sunken);
    border-left: 2px solid var(--border-strong);
    color: var(--text-soft);
    font-size: var(--text-sm);
    line-height: var(--leading-loose);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.profile-form__notice a { color: var(--accent); }

/* Grille d'actions RGPD / légal (export + liens). Pavés cliquables type "card".
   Pattern emprunté à .quick-access mais plus dense (label + hint mono). */
.profile-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-3);
    margin-top: var(--space-4);
}
.profile-actions__item {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text);
    text-decoration: none;
    min-height: 96px; /* touch target confortable */
    transition: border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease),
                background var(--dur) var(--ease);
}
.profile-actions__item:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
    text-decoration: none;
}
.profile-actions__item:active {
    transform: translateY(0);
    transition-duration: var(--dur-instant);
}
.profile-actions__item:focus-visible {
    outline: none;
    box-shadow: var(--ring-focus);
    border-color: var(--accent);
}
.profile-actions__item--primary {
    background: var(--accent-soft);
    border-color: var(--accent);
}
.profile-actions__item--primary:hover {
    background: var(--accent-soft);
    border-color: var(--accent-hover);
}
.profile-actions__glyph {
    font-family: var(--font-mono);
    font-size: var(--text-lg);
    line-height: 1;
    color: var(--accent);
    margin-bottom: var(--space-1);
}
.profile-actions__label {
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-base);
    color: var(--text);
    line-height: var(--leading-tight);
}
.profile-actions__hint {
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    color: var(--text-dim);
    text-transform: uppercase;
}

/* Danger zone — bloc body+actions au sein de la card --danger. */
.profile-danger {
    display: flex;
    /* mobile-first : colonne empilée ; row ≥ 881px */
    flex-direction: column;
    align-items: stretch;
    justify-content: space-between;
    gap: var(--space-6);
    flex-wrap: wrap;
}
.profile-danger__body { flex: 1 1 320px; min-width: 0; }
.profile-danger__body p { margin: 0 0 var(--space-3); line-height: var(--leading-loose); }
.profile-danger__body p:last-child { margin-bottom: 0; }
.profile-danger__notice {
    padding: var(--space-3) var(--space-4);
    background: var(--bg-elevated);
    border-left: 2px solid var(--error-border);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    font-size: var(--text-sm);
    color: var(--text-soft);
}
.profile-danger__actions { flex-shrink: 0; }

/* Page /app/profile/delete — grille "Anonymisé / Conservé". */
.profile-delete-grid {
    display: grid;
    /* mobile-first : 1 colonne ; 2 colonnes ≥ 881px */
    grid-template-columns: 1fr;
    gap: var(--space-4);
    margin-bottom: var(--space-4);
}
.profile-delete-grid__col {
    padding: var(--space-4);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.profile-delete-grid__col--anon { border-left: 3px solid var(--error-border); }
.profile-delete-grid__col--keep { border-left: 3px solid var(--accent); }
.profile-delete-grid__title {
    margin: 0 0 var(--space-3);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--text);
}
.profile-delete-grid__glyph {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: var(--radius-full);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    line-height: 1;
    color: var(--text-soft);
    flex-shrink: 0;
}

/* Bouton retour avec flèche inversée (variante .btn--arrow). */
.btn--arrow-back::before {
    content: "←";
    margin-right: var(--space-2);
    display: inline-block;
    transition: transform var(--dur) var(--ease);
}
.btn--arrow-back:hover::before { transform: translateX(-3px); }

/* ≥ 481px : profile-meta passe à 2 colonnes */
@media (min-width: 481px) {
    .profile-meta { grid-template-columns: 1fr 1fr; }
}
/* ≥ 881px : layout desktop — grilles multi-colonnes, danger row, gap plein */
@media (min-width: 881px) {
    .profile-delete-grid { grid-template-columns: 1fr 1fr; gap: var(--space-6); }
    .profile-danger { flex-direction: row; align-items: flex-start; }
    .profile-danger__actions { width: auto; }
    .profile-danger__actions .btn { width: auto; min-height: auto; }
    .profile-actions { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
    .profile-actions__item { min-height: 96px; }
    .profile-hero { gap: var(--space-4); }
    .profile-summary__name { font-size: var(--text-2xl); }
    .profile-meta { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--space-4); }
}

/* ============================================================================
   17. PROFIL PAIEMENT — IBAN (Sprint S16, ADR-0014)
   ============================================================================
   Pages : /app/profile/paiement (archi) et /artisan/profile/paiement.
   Affichage IBAN masqué + form de saisie + danger zone delete + RGPD.

   Composant `.iban-display` : code monospace pour afficher l'IBAN masqué
   (cf. ADR-0014 §Sécurité point 9). Inhibe la sélection accidentelle.

   Atelier DS v2 — toutes les couleurs viennent des tokens existants.
   ============================================================================ */

.iban-display {
    font-family: var(--font-mono);
    font-size: var(--text-base);
    letter-spacing: var(--tracking-wide);
    font-variant-numeric: tabular-nums;
    color: var(--text);
    background: var(--bg-sunken);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-xs);
    user-select: all; /* clic = sélection complète, lecture facile */
}

/* Champ compact pour filtres inline (barre de recherche étroite). */
.form__input--sm {
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    min-height: 36px;
}

/* Champ texte monospace pour saisir un IBAN (lisibilité 4-par-4). */
.form__input--mono {
    font-family: var(--font-mono);
    letter-spacing: var(--tracking-wide);
    font-variant-numeric: tabular-nums;
}

/* Form delete IBAN (séparé du form update mais affiché en dessous). */
.profile-form--inline-danger {
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px dashed var(--border);
}

/* Sélecteur d'entreprise (artisan multi-companies). */
.profile-form--selector {
    margin-bottom: var(--space-6);
    padding: var(--space-4);
    background: var(--bg-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

/* Variante de bouton "supprimer" plus discrète (ghost rouge). */
.btn--danger-ghost {
    color: var(--error-border);
    border-color: var(--border);
    background: transparent;
}
.btn--danger-ghost:hover {
    background: var(--bg-sunken);
    border-color: var(--error-border);
    color: var(--error-border);
    text-decoration: none;
}

/* Liste numérotée avec puces visuelles (cf. "Comment ça marche"). */
.feature-list--numbered {
    counter-reset: feat-num;
    list-style: none;
}
.feature-list--numbered > li {
    counter-increment: feat-num;
    position: relative;
    padding-left: var(--space-6);
}
.feature-list--numbered > li::before {
    content: counter(feat-num);
    position: absolute;
    left: 0;
    top: 0;
    width: 20px;
    height: 20px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--accent);
    border: 1px solid var(--accent);
    border-radius: var(--radius-full);
    background: var(--accent-soft);
}

/* Suffixe non-emphatique dans un titre (ex : "IBAN payout — Acme SARL"). */
.section__title-suffix {
    color: var(--text-soft);
    font-weight: 300;
    font-family: var(--font-display);
}

/* Helpers texte ponctuels — utilisés depuis les vues. */
.text-soft { color: var(--text-soft); }
.text-mono { font-family: var(--font-mono); font-size: var(--text-sm); }

/* Page placeholder /payments/intent/new (Sprint S16 → S17). */
.payments-placeholder {
    max-width: var(--content-prose);
    margin: var(--space-8) auto;
    padding: 0 var(--space-6);
}

/* ============================================================================
   18. PAGES LÉGALES / MARKDOWN / EMPTY STATES
   ============================================================================ */
.layout-legal { background: var(--bg); }
.legal {
    max-width: var(--content-prose);
    margin: var(--space-8) auto var(--space-16);
    padding: 0 var(--space-6);
    line-height: var(--leading-loose);
    color: var(--text-soft);
}
/* 2026-05 — refactor pages légales : .legal est désormais un <article>
   imbriqué dans <main class="main"> (avant : c'était directement le main).
   On annule le padding horizontal redondant car .main fournit déjà
   --space-6 left/right. Top margin réduite car .main a déjà space-8. */
.main > .legal {
    padding: 0;
    margin: 0 auto var(--space-12);
}
.legal h1 {
    font-family: var(--font-display);
    font-weight: 500; /* S15+ : bump pour Satoshi (était 400 Fraunces) */
    font-size: var(--text-4xl);
    margin-bottom: var(--space-2);
}
.legal h2 {
    margin-top: var(--space-12);
    font-size: var(--text-xl);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
}
.legal__updated {
    color: var(--text-dim);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: var(--tracking-wide);
}
.legal__placeholder {
    background: var(--warning-bg);
    color: var(--warning);
    padding: 0.1rem 0.35rem;
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: 0.9em;
}
.legal__table {
    width: 100%;
    border-collapse: collapse;
    margin: var(--space-3) 0 var(--space-5);
    background: var(--bg-elevated);
    border-radius: var(--radius);
    overflow: hidden;
}
.legal__back { margin-top: var(--space-10); }

.markdown { line-height: var(--leading-loose); }
.markdown h1, .markdown h2, .markdown h3 { margin-top: 1.4em; }
.markdown ul, .markdown ol { padding-left: 1.4em; }
.markdown code {
    background: var(--bg-sunken);
    padding: 0 var(--space-1);
    border-radius: var(--radius-xs);
    font-size: 0.9em;
}

.empty {
    color: var(--text-dim);
    padding: var(--space-8);
    text-align: center;
    background: var(--bg-elevated);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-md);
    font-style: normal;
}
.muted { color: var(--text-dim); }

/* ============================================================================
   18. ERRORS
   ============================================================================ */
.layout-error { background: var(--bg); }
.error-main {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: var(--space-8);
    text-align: center;
}
.error-main h1 {
    font-family: var(--font-display);
    font-size: clamp(4rem, 12vw, 10rem);
    font-weight: 300;
    line-height: 1;
    color: var(--text-mute);
    letter-spacing: var(--tracking-tight);
}

/* ============================================================================
   19. A11Y / UTILITIES
   ============================================================================ */
.sr-only {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.row-remove { min-width: 44px; min-height: 44px; }

/* ============================================================================
   20. RESPONSIVE (mobile-first — ADR-0016)
   ============================================================================ */

/* mobile-first : tables scrollables horizontalement par défaut */
table.quotes__table, table.invoices__table,
table.docs__table, table.decisions__table, table.lifecycle__table,
/* M-2, M-4 : tables manquantes dans §20 (audit 2026-05-29) */
table.transactions__table, table.quote__items-table, table.invoice__items,
table.plan__versions-table, table.plan__diffusion-table,
table.legal__table, table.data-table {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
    /* Scroll inertiel iOS */
    -webkit-overflow-scrolling: touch;
}

/* ≥ 721px : layout desktop — grilles fluides, flex sans wrap forcé, tables normales */
@media (min-width: 721px) {
    .chantiers__list { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
    .chantiers__header,
    .dashboard__hello,
    .page-header { flex-wrap: nowrap; }

    table.quotes__table, table.invoices__table,
    table.docs__table, table.decisions__table, table.lifecycle__table,
    table.transactions__table, table.quote__items-table, table.invoice__items,
    table.plan__versions-table, table.plan__diffusion-table,
    table.legal__table, table.data-table {
        display: table;
        overflow-x: visible;
        white-space: normal;
        -webkit-overflow-scrolling: auto;
    }
}

/* Wrapper générique pour tables larges (utilisé par les vues via PHP) */
.table-responsive {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Indicateur de scroll : ombre côté droit quand scrollable */
    background:
        linear-gradient(to right, var(--bg-elevated) 20%, transparent),
        linear-gradient(to right, transparent, var(--bg-elevated) 80%) 100% 0,
        radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.12), transparent),
        radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.12), transparent) 100% 0;
    background-repeat: no-repeat;
    background-size: 3rem 100%, 3rem 100%, 0.75rem 100%, 0.75rem 100%;
    background-attachment: local, local, scroll, scroll;
}

/* ============================================================================
   21. ÉTATS PROGRESSIVE-ENHANCEMENT (JS)
   ============================================================================ */
/* Topbar scrolled : ombre subtile + opacité accrue */
.topbar.is-scrolled {
    border-bottom-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
}
.topbar.is-scrolled::before { opacity: 0.85; }

/* Reveal sur scroll (cards, bento, items) — actif uniquement si JS chargé */
@media (prefers-reduced-motion: no-preference) {
    html.js .card,
    html.js .bento__cell,
    html.js .chantiers__item {
        opacity: 0;
        transform: translateY(12px);
        transition: opacity var(--dur-slower) var(--ease-out) var(--delay, 0ms),
                    transform var(--dur-slower) var(--ease-out) var(--delay, 0ms),
                    border-color var(--dur) var(--ease),
                    box-shadow var(--dur) var(--ease);
    }
    html.js .card.is-visible,
    html.js .bento__cell.is-visible,
    html.js .chantiers__item.is-visible {
        opacity: 1;
        transform: translateY(0);
    }
}

/* ============================================================================
   22. PLANS (architecte : viewer + indices + diffusion)
   ----------------------------------------------------------------------------
   Patterns : `.plan` (détail) / `.plans` (liste). Le viewer iframe est
   contrastable et responsive ; le hash SHA-256 est rendu en mono avec un
   abbr explicite ; l'historique d'indices reprend `.table` standard ;
   la diffusion (qui a vu / pas vu) reste lisible côté mobile.
   ============================================================================ */
.plans { display: block; margin-bottom: var(--space-8); }
.plans__header {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-4); margin-bottom: var(--space-5);
    padding-bottom: var(--space-4); border-bottom: 1px solid var(--border);
}
.plans__title {
    font-family: var(--font-display); font-weight: 400;
    font-size: var(--text-2xl); letter-spacing: var(--tracking-tight);
    line-height: var(--leading-tight); margin: 0;
}
.plans__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}
.plans__empty--allseen { padding: var(--space-8) var(--space-6); }
.plans__empty-title {
    margin: 0 0 var(--space-2);
    font-family: var(--font-display); font-weight: 500;
    font-size: var(--text-lg); color: var(--text);
}
.plans__empty-hint {
    margin: 0 auto; max-width: 48ch;
    font-size: var(--text-sm); color: var(--text-soft);
    line-height: 1.5;
}
/* .plans__table (ex tableau plat Code/Lot/Titre/Dernier indice/Publié le/
   Versions) RETIRÉ le 2026-09-16 (S27 Lot 5, ADR-0028) : remplacé par les
   widgets par provenance ci-dessous (§22.ter), plus aucune vue ne le
   consomme (`architect/plans/index.php` rend désormais _source_widget.php).
   Colonnes Code et Versions supprimées de l'UI (le code reste dans l'URL,
   auto-généré ; « Versions » n'apportait rien de plus que « Publié le »). */
.plans__new { margin-top: var(--space-8); }

/* — §22.ter — Widgets par provenance + accusés en liste (S27 Lot 5, ADR-0028)
   ----------------------------------------------------------------------------
   La liste architecte passe du tableau plat ci-dessus (.plans__table,
   retiré) à une pile de `<details class="widget widget--lot">` (§45)
   groupés par `plans.source` : rail accent gauche, `.widget__count`, groupe
   « Sans provenance » en dernier via `.widget--muted` — RÉUTILISÉS TELS
   QUELS, sans nouveau modificateur `.widget--source` (préférence DRY : seule
   la table interne change, pas l'enveloppe). Seule différence avec un widget
   de lot de travaux : les colonnes de la table (pas de montant, pas de
   statut de marché) → `.plans__source-table` plutôt que `.lot__table`.
   Markup de référence complet : docs/UI-DESIGN-SYSTEM.md §7.5. */
.plans__source-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}
.plans__source-table th,
.plans__source-table td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--border-subtle);
}
.plans__source-table th {
    font-weight: 500;
    color: var(--text-soft);
    text-transform: uppercase;
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.plans__source-table tbody tr:last-child td { border-bottom: 0; }
.plans__source-table tbody tr:hover { background: var(--paper-soft); }

/* Cellule Actions : Ouvrir / Remplacer / Relancer / Archiver, boutons
   `.btn--xs` existants — juste l'espacement (même recette que `.lot__actions`
   §45, dupliquée à dessein : élément scopé à son propre bloc BEM, cf. §0.1 —
   pas de portée croisée entre « lot de travaux » et « provenance de plan »). */
.plans__source-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}

/* — Chip générique d'accusés « n/N vus » — composant PARTAGÉ `.ack` ---------
   Anciennement dupliqué sous `.reports__ack` (reports.css, S25 Lot 3, même
   besoin exact : compteur d'accusés de diffusion adossé à une table
   `*_diffusions` + `seen_at`). Deux features consommaient un patron
   pixel-identique sous deux noms différents → promotion en composant
   générique ici, ET migration de `reports.css` dans le même lot (S27 Lot 5,
   2026-09-16) : la règle `.reports__ack` a été supprimée de `reports.css`,
   `views/architect/reports/index.php` consomme désormais `.ack` directement
   — aucun changement visuel (valeurs identiques trait pour trait). Nouvel
   usage introduit ici : liste des plans par provenance, colonne Accusés,
   une puce par ligne. */
.ack {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 1px var(--space-2);
    font-size: var(--text-2xs);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    color: var(--text-dim);
    white-space: nowrap;
}
.ack .icon { width: 0.85rem; height: 0.85rem; flex: none; }
.ack--complete { color: var(--success); background: var(--success-bg); border-color: var(--success-border); }
.ack--pending { color: var(--warning); background: var(--warning-bg); border-color: var(--warning-border); }

/* — §22.bis — plans__card (MOA / artisan, audit UI 2026-05-27)
   Card list compacte des plans diffusés : toute la card est un <a> (D2), rail
   accent permanent gauche pour l'état `data-state="new"` (D3), badge "Nouveau" en
   coin haut-droit qui disparaît au clic (D1, MAJ clicked_at). Date affichée en
   <time> avec format relatif + ISO en title (D4).
*/
.plans__count {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}
.plans__cards {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    grid-template-columns: 1fr;
    grid-auto-rows: 1fr;
    gap: var(--space-3);
}
.plans__cards > li { display: flex; }
@media (min-width: 720px) {
    .plans__cards { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1080px) {
    .plans__cards { grid-template-columns: repeat(3, 1fr); }
}
.plans__card {
    position: relative;
    width: 100%;
    display: grid;
    /* mobile-first : 1 colonne (≤599px) ; 3 colonnes ≥ 600px */
    grid-template-columns: 1fr;
    grid-template-rows: auto auto 1fr auto;
    grid-template-areas:
        "code"
        "title"
        "meta"
        "cta";
    align-items: start;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-4);
    min-height: 132px;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    color: var(--text);
    text-decoration: none;
    overflow: hidden;
    isolation: isolate;
    transition: background var(--dur) var(--ease),
                border-color var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                transform var(--dur) var(--ease);
}
.plans__card::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 3px;
    background: var(--accent);
    transform: scaleY(0);
    transform-origin: top;
    transition: transform var(--dur) var(--ease-out);
    z-index: 1;
}
.plans__card[data-state="new"]::before { transform: scaleY(1); }
.plans__card:hover,
.plans__card:focus-visible {
    border-color: var(--ink-900);
    color: var(--text);
    text-decoration: none;
    box-shadow: var(--shadow-lg);
    transform: translateY(-2px);
}
.plans__card:focus-visible {
    outline: none;
    box-shadow: var(--ring-focus), var(--shadow-lg);
}
.plans__card:hover::before,
.plans__card:focus-visible::before { transform: scaleY(1); }
.plans__card:active {
    transform: translateY(-1px);
    transition-duration: var(--dur-instant);
}
.plans__card-code {
    grid-area: code;
    display: inline-flex;
    align-items: center;
    padding: 2px var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    color: var(--accent-text);
    background: var(--accent-soft);
    border-radius: var(--radius-sm);
}
.plans__card-title {
    grid-area: title;
    min-width: 0; /* permet au texte long de wrapper sans pousser la colonne new */
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-base);
    line-height: 1.3;
    letter-spacing: var(--tracking-tight);
    color: var(--text);
    /* clamp 2 lignes pour un rythme régulier ; fallback simple ellipsis */
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow-wrap: anywhere;
}
.plans__card-meta {
    grid-area: meta;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    color: var(--text-soft);
    font-variant-numeric: tabular-nums;
}
.plans__card-lot {
    text-transform: uppercase;
    letter-spacing: var(--tracking-widest);
    padding: 2px var(--space-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
    color: var(--text-dim);
}
.plans__card-indice {
    color: var(--text);
    font-weight: 600;
}
.plans__card-date { color: var(--text-soft); }
.plans__card-new {
    grid-area: new;
    /* mobile-first : badge en absolu coin haut-droit (1 colonne, rangée code courte) */
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    justify-self: auto;
    align-self: auto;
    padding: 2px var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--on-accent);
    background: var(--accent);
    border-radius: var(--radius-sm);
    white-space: nowrap;
    z-index: 2;
}
.plans__card-cta {
    grid-area: cta;
    margin-top: var(--space-2);
    padding-top: var(--space-3);
    border-top: 1px dashed var(--border-subtle);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-widest);
    text-transform: uppercase;
    color: var(--text-dim);
    transition: color var(--dur) var(--ease);
}
.plans__card:hover .plans__card-cta,
.plans__card:focus-visible .plans__card-cta { color: var(--accent); }
.plans__card-cta::after {
    content: " →";
    font-family: var(--font-mono);
}
/* ≥ 600px : layout desktop plans__card (3 colonnes, badge en grid-area) */
/* Inversé depuis max-width: 599px (alias 599.98px fantôme éliminé → 599px) */
@media (min-width: 600px) {
    .plans__card {
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-rows: auto 1fr auto;
        grid-template-areas:
            "code  title new"
            "meta  meta  meta"
            "cta   cta   cta";
        padding: var(--space-5) var(--space-5) var(--space-4);
    }
    /* Desktop : badge repositionné dans son grid-area (inline) */
    .plans__card-new {
        position: static;
        justify-self: end;
        align-self: start;
        top: auto;
        right: auto;
    }
}
@media (prefers-reduced-motion: reduce) {
    .plans__card,
    .plans__card::before,
    .plans__card-cta { transition-duration: var(--dur-reduced); }
}

.plan { display: block; margin-bottom: var(--space-8); }
.plan__header {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-5);
    padding-bottom: var(--space-4); border-bottom: 1px solid var(--border);
}
.plan__title {
    font-family: var(--font-display); font-weight: 400;
    font-size: var(--text-2xl); letter-spacing: var(--tracking-tight);
    line-height: var(--leading-tight); margin: 0;
}
.plan__title code {
    font-family: var(--font-mono); font-size: 0.85em;
    color: var(--accent-text); background: var(--accent-soft);
    padding: 2px var(--space-2); border-radius: var(--radius-sm);
}
.plan__lot {
    font-family: var(--font-mono); font-size: var(--text-xs);
    text-transform: uppercase; letter-spacing: var(--tracking-wide);
    color: var(--text-dim); padding: var(--space-1) var(--space-2);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
}

/* — Bandeau « Prochain indice » (S27 Lot 5, ADR-0028 §D1) -------------------
   Remplace le `<p class="muted">Prochain indice attribué : <strong>…</strong></p>`
   texte discret par un bandeau lisible en tête du formulaire de publication —
   c'est l'info que l'architecte vient chercher en premier sur cette carte
   (indice numérique dès 0 pour un nouveau plan, alpha conservé pour
   l'historique — cf. ADR-0028). Rail accent gauche cohérent avec
   `.widget--lot` (§45) et les autres cartes à rail. */
.plan__next-indice {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--accent-soft);
    border: 1px solid var(--border);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
}
.plan__next-indice-label {
    color: var(--text-soft);
}
.plan__next-indice-value {
    font-family: var(--font-mono);
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--accent-text);
    font-variant-numeric: tabular-nums;
}

.plan__viewer {
    margin-bottom: var(--space-6); background: var(--bg-elevated);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    overflow: hidden; box-shadow: var(--shadow-sm);
}
.plan__viewer iframe {
    display: block; width: 100%; min-height: 480px; border: 0;
    background: var(--bg-sunken);
}
.plan__viewer p { padding: var(--space-3) var(--space-4); margin: 0; font-size: var(--text-sm); }
.plan__viewer p + p { border-top: 1px solid var(--border-subtle); }
.plan__hash {
    font-family: var(--font-mono); font-size: var(--text-xs);
    color: var(--text-dim); display: flex; flex-wrap: wrap;
    align-items: center; gap: var(--space-2);
}
.plan__hash abbr {
    text-decoration: underline dotted; cursor: help;
    text-underline-offset: 2px;
}
.plan__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
    margin-bottom: var(--space-6);
}
.plan__publish, .plan__versions, .plan__diffusion { margin-bottom: var(--space-6); }
.plan__versions-table, .plan__diffusion-table {
    width: 100%; border-collapse: collapse; font-size: var(--text-sm);
}
.plan__versions-table th, .plan__versions-table td,
.plan__diffusion-table th, .plan__diffusion-table td {
    padding: var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle);
}
.plan__versions-table th, .plan__diffusion-table th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.plan__resend { margin-bottom: var(--space-4); }

/* — Barre d'actions de la fiche plan (S27 Lot 5, ADR-0028 §D3) --------------
   Porte le bouton « Archiver ce plan » (`.btn--danger-ghost`, existant —
   toujours doublé d'un `data-confirm` nommant le plan, jamais un
   "Êtes-vous sûr ?" nu, cf. anti-patterns ux-designer). Même recette que
   `.report__actions` (reports.css §F3) : rangée qui empile en colonne pleine
   largeur sous 480px pour rester pouce-accessible. */
.plan__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
    margin: 0 0 var(--space-6);
}
@media (max-width: 479px) {
    .plan__actions { flex-direction: column; }
    .plan__actions .btn { width: 100%; }
}

/* ============================================================================
   23. QUOTES (devis & TS — édition lignes + récap)
   ----------------------------------------------------------------------------
   Pattern dense type bureau d'études : tableau étroit en mono pour les
   chiffres, totaux en alignement tabulaire (font-variant-numeric).
   ============================================================================ */
.quotes { display: block; margin-bottom: var(--space-8); }
.quotes__header {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-4); margin-bottom: var(--space-5);
    padding-bottom: var(--space-4); border-bottom: 1px solid var(--border);
}
.quotes__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}
.quotes__new { margin-top: var(--space-8); }
.quotes__table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.quotes__table th, .quotes__table td {
    padding: var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle);
}
.quotes__table th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.quotes__table tbody tr:hover { background: var(--paper-soft); }

.quote { display: block; margin-bottom: var(--space-6); }
.quote__header {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4);
    padding-bottom: var(--space-3); border-bottom: 1px solid var(--border);
}
.quote__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0; }
.quote__header code { font-family: var(--font-mono); font-size: 0.85em; color: var(--accent-text); background: var(--accent-soft); padding: 2px var(--space-2); border-radius: var(--radius-sm); }
.quote__title { font-style: italic; color: var(--text-soft); font-weight: 400; }
.quote__meta {
    display: grid; grid-template-columns: max-content 1fr;
    gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-5);
    font-size: var(--text-sm);
}
.quote__meta dt { color: var(--text-dim); font-weight: 500; }
.quote__meta dd { margin: 0; color: var(--text); }

.quote__form { margin-bottom: var(--space-6); }
.quote__items { margin-bottom: var(--space-5); }
.quote__items h2 { font-size: var(--text-lg); margin: 0 0 var(--space-3); font-weight: 500; }
.quote__items-table {
    width: 100%; border-collapse: collapse; font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}
.quote__items-table th, .quote__items-table td {
    padding: var(--space-2) var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle); vertical-align: middle;
}
.quote__items-table th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.quote__items-table .form__input { padding: var(--space-1) var(--space-2); min-height: 32px; font-size: var(--text-sm); }
.quote__items-table .qty, .quote__items-table .price, .quote__items-table .tva { text-align: right; }
.quote__items-table .line-ht { text-align: right; font-weight: 500; color: var(--text); white-space: nowrap; }
.quote-line { transition: background var(--dur-fast) var(--ease); }
.quote-line:hover { background: var(--paper-soft); }
.quote-line.is-new { background: var(--accent-soft); animation: quote-line-flash var(--dur-slower) var(--ease-out); }
@keyframes quote-line-flash { from { background: var(--accent-soft); } to { background: transparent; } }

.quote__totals {
    margin-top: var(--space-4); padding: var(--space-4);
    background: var(--bg-sunken); border-left: 3px solid var(--accent);
    border-radius: 0 var(--radius) var(--radius) 0;
    font-variant-numeric: tabular-nums;
}
.quote__totals p { margin: var(--space-1) 0; font-size: var(--text-sm); display: flex; justify-content: space-between; max-width: 360px; }
.quote__totals strong { font-family: var(--font-mono); font-weight: 500; }
.quote__send, .quote__decision { margin-bottom: var(--space-5); }

/* ============================================================================
   24. INVOICES (factures + lifecycle DGFIP)
   ----------------------------------------------------------------------------
   Le badge `.lifecycle` rend visible le code DGFIP (211/212/213/214/216/218)
   en mono inversé, format `code chip`. La timeline lifecycle utilise une
   variante visuelle de `.timeline` standard.
   ============================================================================ */
.invoices { display: block; margin-bottom: var(--space-8); }
.invoices__header {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-4); margin-bottom: var(--space-5);
    padding-bottom: var(--space-4); border-bottom: 1px solid var(--border);
}
.invoices__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}
.invoices__new { margin-top: var(--space-8); }
.invoices__table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
.invoices__table th, .invoices__table td {
    padding: var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle);
}
.invoices__table th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.invoices__table tbody tr:hover { background: var(--paper-soft); }

.invoice { display: block; margin-bottom: var(--space-6); }
.invoice__header {
    display: flex; flex-wrap: wrap; align-items: flex-start;
    justify-content: space-between; gap: var(--space-4) var(--space-6);
    margin-bottom: var(--space-6); padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--border);
}
.invoice__header-main { min-width: 0; }
.invoice__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0 0 var(--space-2); }
/* Faits saillants en tête (échéance / TTC) alignés à droite — repère rapide. */
.invoice__keyfacts { display: flex; gap: var(--space-6); margin: 0; }
.invoice__keyfacts > div { display: grid; gap: 2px; text-align: right; }
.invoice__keyfacts dt {
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    text-transform: uppercase; color: var(--text-dim);
}
.invoice__keyfacts dd {
    margin: 0; font-family: var(--font-mono); font-variant-numeric: tabular-nums;
    font-size: var(--text-base); color: var(--text);
}
.invoice__status {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-2) var(--space-3); margin: 0; font-size: var(--text-sm);
}
.invoice__status-label { color: var(--text-dim); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide); }

.invoice__form { margin-bottom: var(--space-6); }
.invoice__items {
    width: 100%; border-collapse: collapse; font-size: var(--text-sm);
    font-variant-numeric: tabular-nums; margin-bottom: var(--space-4);
}
.invoice__items th, .invoice__items td {
    padding: var(--space-2) var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle); vertical-align: middle;
}
.invoice__items th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.invoice__items .form__input { padding: var(--space-1) var(--space-2); min-height: 32px; font-size: var(--text-sm); }
.invoice__totals {
    margin: var(--space-4) 0; padding: var(--space-3) var(--space-4);
    background: var(--bg-sunken); border-left: 3px solid var(--accent);
    border-radius: 0 var(--radius) var(--radius) 0;
    font-size: var(--text-sm); font-variant-numeric: tabular-nums;
}
.invoice__totals strong { font-family: var(--font-mono); font-weight: 500; }
/* Formulaires d'action figés dans une carte .invoice__action (cf. refonte). */
.invoice__send, .invoice__paid { margin: 0; }
/* .invoice__download / .invoice__lifecycle portent désormais .card : on ne
   redéclare que les marges externes (fond/bordure/rayon viennent de .card). */
.invoice__download { margin-bottom: var(--space-5); }
.invoice__download ul { list-style: none; padding: 0; margin: 0 0 var(--space-3); display: grid; gap: var(--space-2); }
.invoice__download a { font-family: var(--font-mono); font-size: var(--text-sm); }
.invoice__lifecycle { margin-top: var(--space-6); }
.invoice__lifecycle .timeline { margin: 0; }

/* — Refonte détail facture (audit UI 2026-05-31) -----------------------------
   Le draft est édité dans des panneaux `.card` (grilles de champs) ; les états
   figés (sent/paid/cancelled) affichent un récap lecture seule en cartes plutôt
   qu'un <form disabled> grisé. Titres de section partagés : .invoice__panel-title. */
.invoice__panel { margin-bottom: var(--space-5); }
.invoice__panel-title {
    margin: 0 0 var(--space-5); font-family: var(--font-display);
    font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-snug);
}
/* Champs autonomes (hors grille) au sein d'un panneau : label collé à l'input
   via gap, espacement vertical homogène entre champs/grilles. */
.invoice__panel > .form__field,
.invoice__panel > .form__grid { margin-bottom: var(--space-5); }
.invoice__panel > .form__field { gap: var(--space-2); }
.invoice__panel > .form__field > .form__label { margin-bottom: 0; }
.invoice__panel > .form__field:last-child,
.invoice__panel > .form__grid:last-child,
.invoice__panel > .invoice__summary:last-child { margin-bottom: 0; }
.invoice__panel .table-responsive { margin-bottom: 0; }

/* Récapitulatif lecture seule */
.invoice__recap { display: grid; gap: var(--space-5); margin-bottom: var(--space-6); }
.invoice__parties { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
@media (min-width: 641px) {
    .invoice__parties { grid-template-columns: 1fr 1fr; align-items: start; }
}
.invoice__party {
    padding: var(--space-5); background: var(--bg-elevated);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}
.invoice__party-title {
    margin: 0 0 var(--space-4); font-family: var(--font-mono);
    font-size: var(--text-2xs); letter-spacing: var(--tracking-widest);
    text-transform: uppercase; color: var(--text-dim); font-weight: 500;
}
.invoice__deflist { margin: 0; display: grid; gap: var(--space-3); }
.invoice__deflist > div { display: grid; gap: 2px; }
.invoice__deflist dt {
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    text-transform: uppercase; color: var(--text-dim);
}
.invoice__deflist dd { margin: 0; color: var(--text); font-size: var(--text-sm); line-height: var(--leading-snug); }
.invoice__addr { white-space: pre-line; }
.invoice__terms {
    margin: 0; padding: var(--space-4); background: var(--bg-sunken);
    border-radius: var(--radius); color: var(--text-soft);
    font-size: var(--text-sm); line-height: var(--leading-loose); white-space: pre-line;
}

/* La désignation prime sur les colonnes numériques (sinon l'input se tronque). */
.invoice__items th:first-child,
.invoice__items td:first-child { width: 36%; }

/* Tableau des lignes en lecture seule (colonnes numériques alignées à droite) */
.invoice__items--readonly th.num,
.invoice__items--readonly td.num { text-align: right; white-space: nowrap; }
.invoice__items--readonly td { font-size: var(--text-sm); }
.invoice__items-empty { text-align: center; color: var(--text-mute); padding: var(--space-4); }

/* Bloc totaux : carte alignée à droite, ligne TTC accentuée */
.invoice__summary {
    margin: var(--space-4) 0 0 auto; max-width: 320px; display: grid; gap: var(--space-2);
    padding: var(--space-4); background: var(--bg-sunken);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-md);
}
.invoice__summary-row {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--space-4); font-size: var(--text-sm); color: var(--text-soft);
}
.invoice__summary-row strong {
    font-family: var(--font-mono); font-weight: 500; color: var(--text);
    font-variant-numeric: tabular-nums;
}
.invoice__summary-row--total {
    margin-top: var(--space-1); padding-top: var(--space-3);
    border-top: 1px solid var(--border); color: var(--text);
}
.invoice__summary-row--total span { font-weight: 500; }
.invoice__summary-row--total strong { font-size: var(--text-lg); color: var(--accent); }
.invoice__summary-note { margin: var(--space-2) 0 0; text-align: right; font-size: var(--text-xs); }
/* Sur petit écran, le bloc totaux occupe toute la largeur (pas de gouttière). */
@media (max-width: 560px) {
    .invoice__summary { max-width: none; margin-left: 0; }
}

/* Cartes d'action (émettre / encaisser) */
.invoice__action { margin-bottom: var(--space-5); }
.invoice__action-title {
    margin: 0 0 var(--space-2); font-family: var(--font-display);
    font-size: var(--text-lg); font-weight: 500;
}
.invoice__action-desc {
    margin: 0 0 var(--space-4); color: var(--text-soft);
    font-size: var(--text-sm); line-height: var(--leading-loose); max-width: 60ch;
}
.invoice__action-form .form__field { margin-bottom: var(--space-4); max-width: 420px; }
.invoice__action-form .form__field .form__label { margin-bottom: var(--space-2); }

/* — Historique des tentatives de paiement (S18) ----------------------------- */
/* Ligne de tableau collapsible qui encapsule les payment_intents d'une facture */
.invoices__payment-history-row > td { padding: 0; }
/* DS-3 : !important retiré — spécificité augmentée via sélecteur enfant direct */
.invoices__payment-history-row > td.invoices__payment-history-cell,
.invoices__payment-history-cell {
    padding: 0 var(--space-3);
    background: var(--bg-sunken);
    border-bottom: 1px solid var(--border-subtle);
}

/* Bloc <details> qui affiche / masque la liste des tentatives.
   S23 Lot 1 (j) : disclosure/chevron remplacés par `.widget.widget--flush`
   (§45) — la vue utilise `<details class="widget widget--flush invoices__payment-history">`
   avec `.widget__body` contenant `.invoices__payment-history-list`.
   `.invoices__payment-history` ne porte plus de chrome propre (hook de
   compatibilité, conservé pour ne pas casser un sélecteur descendant
   éventuel) ; les classes `-list` / `-item` ci-dessous restent (mise en page
   du contenu, distincte du chrome de disclosure). */
.invoices__payment-history { display: block; }

.invoices__payment-history-list {
    list-style: none;
    padding: var(--space-2) 0 0 var(--space-4);
    margin: var(--space-2) 0 0;
    display: grid;
    gap: var(--space-2);
}

.invoices__payment-history-item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-soft);
    padding: var(--space-2) 0;
    border-bottom: 1px dashed var(--border-subtle);
}
.invoices__payment-history-item:last-child { border-bottom: 0; }

.lifecycle {
    display: inline-block; font-family: var(--font-mono);
    font-size: var(--text-xs); font-weight: 500;
    padding: 2px var(--space-2); border-radius: var(--radius-sm);
    background: var(--ink-900); color: var(--paper);
    letter-spacing: var(--tracking-wide);
}
/* Dark : --ink-900 (#18181B) et --paper (#0D0D0E) deviennent quasi identiques
   → chip illisible. On bascule sur la surface inverse dédiée (fond clair /
   texte sombre en dark) qui garantit le contraste AA dans les deux thèmes. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .lifecycle {
        background: var(--bg-inverse);
        color: var(--text-on-inverse);
    }
}

/* ============================================================================
   25. GALLERY (photos — grille thumbnails + visibility toggle)
   ----------------------------------------------------------------------------
   Grid auto-fit, items carrés (aspect-ratio), thumbnails ratio-cover,
   meta sous la photo, toggle visibilité MOA via .btn--sm.
   ============================================================================ */
.gallery { display: block; margin-bottom: var(--space-8); }
.gallery__header {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4);
    padding-bottom: var(--space-3); border-bottom: 1px solid var(--border);
}
.gallery__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0; }
.gallery__header small { color: var(--text-dim); font-size: var(--text-sm); font-weight: 400; }
.gallery__hint { color: var(--text-soft); font-size: var(--text-sm); margin: 0 0 var(--space-3); }
.gallery__grid {
    list-style: none; padding: 0; margin: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--space-4);
}
.gallery__item {
    background: var(--bg-elevated); border: 1px solid var(--border-subtle);
    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-xs);
    display: flex; flex-direction: column;
    transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.gallery__item:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.gallery__thumb {
    display: block; aspect-ratio: 1 / 1; background: var(--bg-sunken);
    overflow: hidden; position: relative;
}
.gallery__thumb img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    transition: transform var(--dur-slow) var(--ease);
}
.gallery__thumb:hover img { transform: scale(1.03); }
.gallery__thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.gallery__meta {
    padding: var(--space-2) var(--space-3); font-size: var(--text-xs);
    color: var(--text-dim); display: flex; flex-direction: column;
    gap: var(--space-1);
}
.gallery__caption { color: var(--text-soft); font-size: var(--text-sm); line-height: var(--leading-snug); }
.gallery__visibility { padding: var(--space-2) var(--space-3) var(--space-3); margin: 0; }
.gallery__visibility .btn { width: 100%; }
/* Badge statut de visibilité d'une photo (dépôt artisan : brouillon/publié MOA) */
.gallery__badge {
    display: inline-block; align-self: flex-start;
    padding: 1px var(--space-2);
    font-size: var(--text-2xs); font-weight: 600;
    border-radius: var(--radius-full);
    border: 1px solid var(--border); background: var(--bg-sunken); color: var(--text-dim);
}
.gallery__badge--published { color: var(--success); border-color: var(--success); }
.gallery__badge--draft { color: var(--text-dim); }
.gallery__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}
.gallery__upload { margin-top: var(--space-6); }

/* — F2 — Demandes de photos (émetteur architecte, dans la galerie) --------- */
.gallery__requests { margin-top: var(--space-6); }
.req-list { list-style: none; margin: 0 0 var(--space-4); padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.req-list__item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
}
.req-list__label { flex: 1 1 auto; min-width: 0; font-size: var(--text-sm); color: var(--text); }
.req-list__target { flex: none; font-size: var(--text-xs); color: var(--text-dim); }

/* ============================================================================
   26. TIMELINE (avancement MOA + lifecycle factures)
   ----------------------------------------------------------------------------
   Ligne verticale ink-subtle avec pastilles d'événement, format de date
   tabulaire en kicker mono. Compact mobile-first.
   ============================================================================ */
.timeline { display: block; margin-bottom: var(--space-6); }
.timeline__header {
    margin-bottom: var(--space-5); padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--border);
}
.timeline__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0 0 var(--space-2); }
.timeline__hint { color: var(--text-soft); font-size: var(--text-sm); margin: 0; }
.timeline__list, .timeline {
    list-style: none; padding: 0; margin: 0;
}
.timeline__list { position: relative; padding-left: var(--space-6); }
.timeline__list::before {
    content: ""; position: absolute; left: var(--space-2); top: var(--space-2);
    bottom: var(--space-2); width: 1px; background: var(--border);
}
.timeline__item {
    position: relative; padding: var(--space-3) 0 var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
}
.timeline__item:last-child { border-bottom: 0; }
.timeline__item::before {
    content: ""; position: absolute; left: calc(-1 * var(--space-6) + var(--space-2) - 4px);
    top: var(--space-4); width: 9px; height: 9px; border-radius: 50%;
    background: var(--accent); box-shadow: 0 0 0 3px var(--bg-elevated);
}
.timeline__date {
    display: block; font-family: var(--font-mono); font-size: var(--text-xs);
    color: var(--text-dim); text-transform: uppercase; letter-spacing: var(--tracking-wide);
    margin-bottom: var(--space-2);
}
.timeline__title {
    font-family: var(--font-display); font-weight: 400; font-size: var(--text-lg);
    margin: 0 0 var(--space-2); letter-spacing: var(--tracking-snug);
}
.timeline__summary {
    color: var(--text-soft); font-size: var(--text-sm);
    line-height: var(--leading-base); margin: 0;
}
.timeline__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}
/* Variante "compacte" pour invoice lifecycle (sans titre, juste code + note) */
.invoice__lifecycle .timeline { padding-left: 0; }
.invoice__lifecycle .timeline > li {
    list-style: none; padding: var(--space-2) 0; display: flex;
    align-items: center; gap: var(--space-3); font-size: var(--text-sm);
    border-bottom: 1px dashed var(--border-subtle);
}
.invoice__lifecycle .timeline > li:last-child { border-bottom: 0; }
.invoice__lifecycle .timeline time {
    font-family: var(--font-mono); font-size: var(--text-xs);
    color: var(--text-dim); min-width: 130px;
}

/* ============================================================================
   27. DECISIONS / PROOF (signature MOA — pages stratégiques juridiques)
   ----------------------------------------------------------------------------
   `.decision__sign` est l'élément le plus visible de la page (loi 2000-230) :
   carte avec accent border + ombre marquée. La preuve (`.proof__meta`) est
   typographiquement intangible (mention exacte, mono, dt/dd alignés).
   ============================================================================ */
.decisions { display: block; margin-bottom: var(--space-6); }
.decisions__table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.decisions__table th, .decisions__table td {
    padding: var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle);
}
.decisions__table th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.decisions__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}

.decision { display: block; margin-bottom: var(--space-6); }
.decision__header {
    margin-bottom: var(--space-5); padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--border);
}
.decision__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0 0 var(--space-2); }
.decision__header code { font-family: var(--font-mono); font-size: 0.85em; color: var(--accent-text); background: var(--accent-soft); padding: 2px var(--space-2); border-radius: var(--radius-sm); }
.decision__lines { margin-bottom: var(--space-6); }
.decision__lines h2 { font-size: var(--text-lg); margin: 0 0 var(--space-3); font-weight: 500; }
.decision__actions { margin-bottom: var(--space-4); }
.decision__meta {
    display: grid; grid-template-columns: max-content 1fr;
    gap: var(--space-2) var(--space-4); margin: 0;
    font-size: var(--text-sm); font-variant-numeric: tabular-nums;
}
.decision__meta dt { color: var(--text-dim); font-weight: 500; }
.decision__meta dd { margin: 0; color: var(--text); }
.decision__meta code { font-family: var(--font-mono); font-size: var(--text-xs); word-break: break-all; }
.decision__legal {
    margin: var(--space-4) 0; padding: var(--space-4);
    background: var(--warning-bg); border: 1px solid var(--warning-border);
    border-left-width: 3px; border-radius: 0 var(--radius) var(--radius) 0;
    font-size: var(--text-sm);
}
.decision__legal p { margin: 0 0 var(--space-3); color: var(--warning); line-height: var(--leading-base); }
.decision__legal label { display: flex; gap: var(--space-2); align-items: flex-start; font-weight: 500; }
.decision__sign {
    margin-top: var(--space-6); padding: var(--space-5);
    background: var(--bg-elevated);
    border: 2px solid var(--accent);
    border-radius: var(--radius-md); box-shadow: var(--shadow-md);
}
.decision__sign h2 {
    font-family: var(--font-display); font-weight: 400; font-size: var(--text-xl);
    margin: 0 0 var(--space-4); color: var(--accent-text);
}
.decision__sign .form__actions .btn--primary { min-width: 200px; }

.proof { display: block; margin-bottom: var(--space-6); }
.proof__header {
    margin-bottom: var(--space-5); padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--border);
}
.proof__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0 0 var(--space-2); }
.proof__header code { font-family: var(--font-mono); font-size: 0.85em; color: var(--accent-text); background: var(--accent-soft); padding: 2px var(--space-2); border-radius: var(--radius-sm); }
.proof__meta {
    display: grid; grid-template-columns: max-content 1fr;
    gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-5);
    padding: var(--space-4); background: var(--bg-sunken);
    border-radius: var(--radius); font-size: var(--text-sm);
}
.proof__meta dt { color: var(--text-dim); font-weight: 500; }
.proof__meta dd { margin: 0; color: var(--text); }
.proof__meta code, .proof__meta small {
    font-family: var(--font-mono); font-size: var(--text-xs);
    word-break: break-all; color: var(--text-soft);
}
.proof__consent {
    margin-top: var(--space-5); padding: var(--space-4);
    background: var(--bg-elevated); border-left: 3px solid var(--accent);
    border: 1px solid var(--border-subtle); border-left-width: 3px;
    border-left-color: var(--accent); border-radius: 0 var(--radius) var(--radius) 0;
}
.proof__consent h2 { font-size: var(--text-lg); margin: 0 0 var(--space-2); font-weight: 500; }
.proof__consent blockquote {
    margin: 0; padding-left: var(--space-3); border-left: 2px solid var(--border);
    color: var(--text-soft); font-style: italic; font-size: var(--text-sm);
    line-height: var(--leading-base);
}

/* ============================================================================
   28. DOCS (documents généralistes + filtre/recherche)
   ----------------------------------------------------------------------------
   La live-region `.docs__perf` affiche le nombre de résultats + le temps de
   recherche (perf chiffrée visible, rassurance utilisateur).
   ============================================================================ */
.docs { display: block; margin-bottom: var(--space-8); }
.docs__header {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-4);
    padding-bottom: var(--space-3); border-bottom: 1px solid var(--border);
}
.docs__header h1 { font-family: var(--font-display); font-weight: 400; font-size: var(--text-2xl); margin: 0; }
.docs__header small { color: var(--text-dim); font-size: var(--text-sm); font-weight: 400; }
.docs__hint { color: var(--text-soft); font-size: var(--text-sm); margin: 0 0 var(--space-3); }

/* — Filtre par phase (S28 Lot 6) — barre de chips scrollable horizontalement
   en mobile, même recette que `.notif-filters`/`.notif-filters__list`/`__item`
   (§40.b), dupliquée à dessein dans le bloc BEM "docs" (cf. §0.1). Les chips
   elles-mêmes sont `.chip--filter`/`.chip__count` (§40.b), RÉUTILISÉES telles
   quelles — aucun style de chip propre à ce module. La colonne "Phase" de
   `.docs__table` réutilise `.tag` (§13), pas de classe dédiée non plus. */
.docs__phase-filter {
    margin: 0 0 var(--space-4);
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
}
.docs__phase-filter-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    gap: var(--space-2);
    padding-bottom: 2px;
}
.docs__phase-filter-item { flex-shrink: 0; }

.docs__search {
    display: flex; flex-wrap: wrap; gap: var(--space-2);
    align-items: center; margin-bottom: var(--space-4);
    padding: var(--space-3); background: var(--bg-sunken);
    border: 1px solid var(--border-subtle); border-radius: var(--radius);
}
.docs__search .form__input { flex: 1 1 200px; }
.docs__perf {
    font-family: var(--font-mono); font-size: var(--text-xs);
    color: var(--text-dim); padding: var(--space-1) var(--space-2);
}
.docs__list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-2); }
.docs__item {
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-3); background: var(--bg-elevated);
    border: 1px solid var(--border-subtle); border-radius: var(--radius);
}
.docs__link { flex: 1; color: var(--text); text-decoration: none; font-weight: 500; }
.docs__link:hover { color: var(--accent); }
.docs__badge {
    display: inline-block; font-family: var(--font-mono);
    font-size: var(--text-2xs); padding: 2px var(--space-2);
    border-radius: var(--radius-sm); background: var(--bg-sunken);
    color: var(--text-soft); text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.docs__size {
    font-family: var(--font-mono); font-size: var(--text-xs);
    color: var(--text-dim); white-space: nowrap;
}
.docs__table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.docs__table th, .docs__table td {
    padding: var(--space-3); text-align: left;
    border-bottom: 1px solid var(--border-subtle);
}
.docs__table th {
    font-weight: 500; color: var(--text-soft); text-transform: uppercase;
    font-size: var(--text-2xs); letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.docs__table tbody tr:hover { background: var(--paper-soft); }
.docs__empty {
    padding: var(--space-6); text-align: center; color: var(--text-soft);
    background: var(--bg-sunken); border: 1px dashed var(--border);
    border-radius: var(--radius); font-size: var(--text-sm);
}
.docs__upload { margin-top: var(--space-6); }

/* ============================================================================
   29. REPORTS (comptes-rendus architecte)
   ----------------------------------------------------------------------------
   Liste : pattern liste avec meta date + titre + résumé.
   Détail : 2 cards (corps Markdown interne / résumé MOA visible).
   ============================================================================ */
/* Styles du module comptes-rendus déplacés dans public/assets/css/reports.css
   (refonte UI 2026) — chargée en opt-in via $extraStyles sur /app/chantiers/{slug}/cr
   et /cr/{id}, sur le même mécanisme que planning.css. Voir reports.css §1. */

/* ============================================================================
   30. MESSAGES (threads + fil de discussion)
   ----------------------------------------------------------------------------
   Pattern conversation : variantes `--list` (index) / `--thread` (show),
   bulles `.message` avec différenciation `--mine` à droite, accent.
   ============================================================================ */
.messages { display: block; margin-bottom: var(--space-6); }
.messages--list { padding-bottom: var(--space-4); }
.messages--thread { display: flex; flex-direction: column; gap: var(--space-4); }
.messages__page-header {
    margin-bottom: var(--space-4); padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--border);
}
.messages__page-header .page-header__title {
    font-family: var(--font-display); font-weight: 400;
    font-size: var(--text-2xl); margin: 0;
}
.messages__back { margin: 0 0 var(--space-3); }
.messages__meta {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-3); padding: var(--space-3);
    background: var(--bg-sunken); border-radius: var(--radius);
    font-size: var(--text-sm); color: var(--text-soft);
}
.messages__threads { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-2); }
.messages__thread {
    background: var(--bg-elevated); border: 1px solid var(--border-subtle);
    border-radius: var(--radius); transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.messages__thread:hover { border-color: var(--border); box-shadow: var(--shadow-sm); }
.messages__thread--unread { border-left: 3px solid var(--accent); }
.messages__thread-link {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: var(--space-2) var(--space-3); padding: var(--space-3) var(--space-4);
    text-decoration: none; color: var(--text);
}
.messages__thread-link strong { font-weight: 500; }
.messages__thread .messages__meta {
    background: transparent; padding: 0; flex: 1 1 100%;
    font-size: var(--text-xs); color: var(--text-dim);
}
/* S23 Lot 1 (j)(h) : le disclosure `<details>` + son chrome de carte sont
   remplacés par `.widget` (§45) — voir aussi (h) du Lot 1 qui rend le CTA
   « Nouvelle discussion » visible en permanence (n'est plus forcément un
   <details> replié). `.messages__create` reste disponible comme hook de
   marge/espacement si la vue la combine à `.widget` ou à un conteneur simple. */
.messages__create { margin-top: var(--space-5); }
.messages__create form { margin-top: var(--space-3); }
.messages__feed {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-direction: column; gap: var(--space-3);
}
.messages__reply { margin-top: var(--space-3); }
.messages__close { margin-top: var(--space-2); }

.message {
    padding: var(--space-3) var(--space-4); background: var(--bg-elevated);
    border: 1px solid var(--border-subtle); border-radius: var(--radius);
    max-width: 80%;
}
.message--mine {
    align-self: flex-end; background: var(--accent-soft);
    border-color: var(--clay-200);
}
.message__head {
    display: flex; flex-wrap: wrap; align-items: baseline;
    gap: var(--space-2); margin-bottom: var(--space-2);
}
.message__author { font-weight: 500; font-size: var(--text-sm); color: var(--text); }
.message__role {
    font-size: var(--text-xs); color: var(--text-dim);
    font-family: var(--font-mono); text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.message__time {
    margin-left: auto; font-family: var(--font-mono);
    font-size: var(--text-xs); color: var(--text-dim);
}
.message__body {
    margin: 0; font-size: var(--text-sm); line-height: var(--leading-base);
    color: var(--text); white-space: pre-wrap; word-wrap: break-word;
}

/* ============================================================================
   31. MODAL (<dialog> natif + focus trap JS)
   ----------------------------------------------------------------------------
   Le composant utilise l'élément natif `<dialog>` (a11y, focus, ESC built-in).
   La classe `.modal` cible le <dialog>, le backdrop est `::backdrop`.
   Polyfill JS dans /assets/js/modal.js (focus trap, fallback ouverture).
   Exemple HTML :
     <dialog class="modal" id="m1">
       <div class="modal__dialog">
         <header class="modal__header">
           <h2 class="modal__title">Titre</h2>
           <button class="modal__close" aria-label="Fermer" data-modal-close>×</button>
         </header>
         <div class="modal__body">…</div>
         <footer class="modal__footer">…boutons…</footer>
       </div>
     </dialog>
   ============================================================================ */
.modal {
    margin: auto; padding: 0; border: 0; max-width: min(560px, 92vw);
    width: 100%; background: transparent; color: var(--text);
}
.modal::backdrop {
    background: rgba(10, 10, 11, 0.5);
    backdrop-filter: blur(2px);
}
.modal[open] { animation: modal-in var(--dur-slow) var(--ease-out); }
@keyframes modal-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
.modal__dialog {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-xl);
    overflow: hidden; max-height: 90vh; display: flex; flex-direction: column;
}
.modal__header {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-3); padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
}
.modal__title {
    font-family: var(--font-display); font-weight: 400;
    font-size: var(--text-xl); margin: 0; letter-spacing: var(--tracking-tight);
}
.modal__close {
    appearance: none; background: transparent; border: 0; cursor: pointer;
    /* AX-2 : cible tactile ≥ 44×44px (WCAG 2.5.5) */
    width: 44px; height: 44px; border-radius: var(--radius-sm);
    color: var(--text-dim); font-size: 1.4rem; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.modal__close:hover { background: var(--bg-sunken); color: var(--text); }
.modal__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.modal__body { padding: var(--space-5); overflow-y: auto; flex: 1; }
.modal__footer {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
    gap: var(--space-2); padding: var(--space-4) var(--space-5);
    border-top: 1px solid var(--border-subtle); background: var(--bg-sunken);
}
/* Fallback "legacy" pour navigateurs sans <dialog> (Safari < 15.4) */
.modal--legacy {
    position: fixed; inset: 0; z-index: 1000;
    display: flex; align-items: center; justify-content: center;
    padding: var(--space-4); background: rgba(10, 10, 11, 0.5);
}
.modal--legacy[hidden] { display: none; }
.modal__backdrop {
    position: absolute; inset: 0; background: transparent; cursor: pointer;
}

/* ============================================================================
   32. TABS (pattern accessible — ARIA role="tablist")
   ----------------------------------------------------------------------------
   HTML attendu :
     <div class="tabs">
       <div class="tabs__list" role="tablist">
         <button class="tabs__tab" role="tab" aria-selected="true" aria-controls="p1" id="t1">…</button>
         <button class="tabs__tab" role="tab" aria-selected="false" aria-controls="p2" id="t2" tabindex="-1">…</button>
       </div>
       <div class="tabs__panel" role="tabpanel" id="p1" aria-labelledby="t1">…</div>
       <div class="tabs__panel" role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</div>
     </div>
   JS /assets/js/tabs.js gère ←/→/Home/End + sync aria-selected + hidden.
   ============================================================================ */
.tabs { display: block; margin-bottom: var(--space-5); }
.tabs__list {
    display: flex; gap: 0; border-bottom: 1px solid var(--border);
    margin-bottom: var(--space-4); overflow-x: auto;
    /* 2026-06 — overflow-x:auto force overflow-y à se calculer en `auto` (spec CSS).
       Le margin-bottom:-1px des onglets + l'indicateur ::after créaient alors un
       débordement vertical de ~1px → scrollbar verticale fantôme (tous personas).
       overflow-y:hidden la supprime sans rogner l'indicateur. Cf. .gantt-v2__grid-wrap. */
    overflow-y: hidden;
}
.tabs__tab {
    appearance: none; background: transparent; border: 0; cursor: pointer;
    padding: var(--space-3) var(--space-4); font: inherit;
    color: var(--text-soft); font-weight: 500; font-size: var(--text-sm);
    position: relative; white-space: nowrap; min-height: 44px;
    border-bottom: 2px solid transparent; margin-bottom: -1px;
    transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.tabs__tab:hover { color: var(--text); }
.tabs__tab[aria-selected="true"] {
    color: var(--accent); border-bottom-color: var(--accent);
}
/* T13.2.5 S13 — indicateur glissant : pseudo-élément qui scale en X selon l'état */
.tabs__tab::after {
    content: ""; position: absolute; left: 50%; right: 50%; bottom: -1px;
    height: 2px; background: var(--accent); border-radius: 1px;
    transform-origin: center;
    transition: left var(--dur) var(--ease), right var(--dur) var(--ease),
                opacity var(--dur-fast) var(--ease);
    opacity: 0;
}
.tabs__tab[aria-selected="true"]::after {
    left: var(--space-3); right: var(--space-3); opacity: 1;
}
.tabs__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-sm); }
.tabs__tab:active { transition-duration: var(--dur-instant); } /* T13.1.2 S13 */
.tabs__panel { padding: var(--space-2) 0; }
.tabs__panel[hidden] { display: none; }

/* ============================================================================
   33. SPINNER + SKELETON + PROGRESS
   ----------------------------------------------------------------------------
   Tous respectent prefers-reduced-motion (animation arrêtée par §reduced).
   Edge case motion-budget (T13.1.1 S13) : les durées d'animation `infinite`
   ci-dessous (0.7s spinner, 1.4s skeleton/progress-indet) restent en littéral
   car ce sont des constantes perceptuelles invariantes (rotation/shimmer) qui
   ne participent pas à la grammaire de transitions du DS — elles ne sont pas
   accordables au token --dur*. Acceptance S13-1 ne cible que `transition`.
   ============================================================================ */
.spinner {
    display: inline-block; width: 20px; height: 20px;
    border: 2px solid var(--border-subtle); border-top-color: var(--accent);
    border-radius: 50%; animation: spinner-rot var(--dur-spin) linear infinite;
    vertical-align: middle;
}
.spinner--sm { width: 14px; height: 14px; border-width: 2px; }
.spinner--md { width: 24px; height: 24px; border-width: 3px; }
.spinner--lg { width: 36px; height: 36px; border-width: 3px; }
@keyframes spinner-rot { to { transform: rotate(360deg); } }

.skeleton {
    display: block; background: var(--bg-sunken); border-radius: var(--radius-sm);
    position: relative; overflow: hidden; min-height: 1em;
}
.skeleton::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
    animation: skeleton-shimmer var(--dur-shimmer) ease-in-out infinite;
}
@keyframes skeleton-shimmer { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
.skeleton--text { height: 0.8em; margin: 0.3em 0; }
.skeleton--title { height: 1.4em; width: 60%; margin: 0.2em 0 0.6em; }
.skeleton--avatar { width: 40px; height: 40px; border-radius: 50%; }

.progress {
    display: block; width: 100%; height: 6px;
    background: var(--bg-sunken); border-radius: var(--radius-full);
    overflow: hidden; position: relative;
}
.progress__bar {
    display: block; height: 100%; background: var(--accent);
    border-radius: inherit; transition: width var(--dur) var(--ease);
    min-width: 0; width: 0%;
}
.progress--indeterminate .progress__bar {
    width: 30%; animation: progress-indet var(--dur-shimmer) var(--ease) infinite;
}
@keyframes progress-indet {
    from { transform: translateX(-100%); }
    to { transform: translateX(330%); }
}

/* ============================================================================
   34. TOAST (notification transitoire — distincte du flash bloquant)
   ----------------------------------------------------------------------------
   Conteneur fixe bottom-right ; ajout/retrait gérés par /assets/js/ui.js
   (queue côté JS). Slide-up + fade ; auto-dismiss 6s (configurable data-ttl).
   ============================================================================ */
.toast-stack {
    position: fixed; bottom: var(--space-4); right: var(--space-4);
    z-index: 1200; display: flex; flex-direction: column-reverse;
    gap: var(--space-2); max-width: 360px; pointer-events: none;
}
.toast {
    pointer-events: auto; background: var(--bg-elevated);
    border: 1px solid var(--border); border-left: 3px solid var(--accent);
    border-radius: var(--radius); padding: var(--space-3) var(--space-4);
    box-shadow: var(--shadow-md); font-size: var(--text-sm); color: var(--text);
    animation: toast-in var(--dur-slow) var(--ease-out);
    display: flex; align-items: flex-start; gap: var(--space-3);
}
.toast--success { border-left-color: var(--success); background: var(--success-bg); color: var(--success); }
.toast--error   { border-left-color: var(--error); background: var(--error-bg); color: var(--error); }
.toast--info    { border-left-color: var(--info); background: var(--info-bg); color: var(--info); }
.toast.is-leaving { animation: toast-out var(--dur-slow) var(--ease-in) forwards; }
@keyframes toast-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(8px); } }

/* ============================================================================
   35. TOOLTIP (CSS pur via [data-tooltip])
   ----------------------------------------------------------------------------
   Tout élément focusable/hoverable peut porter `data-tooltip="texte"`.
   Pour les tooltips riches : composant JS tooltip.js (S12 si besoin).
   Position : par défaut au-dessus. data-tooltip-pos="bottom" pour inverser.
   ============================================================================ */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
    content: attr(data-tooltip);
    position: absolute; left: 50%; bottom: calc(100% + 6px);
    transform: translateX(-50%) translateY(4px);
    background: var(--ink-900); color: var(--paper);
    padding: var(--space-1) var(--space-2); border-radius: var(--radius-sm);
    font-size: var(--text-xs); font-weight: 400;
    white-space: normal; width: max-content; max-width: min(260px, 90vw);
    opacity: 0; pointer-events: none;
    transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
    z-index: 100;
}
[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
    opacity: 1; transform: translateX(-50%) translateY(0);
}
[data-tooltip-pos="bottom"]::after {
    bottom: auto; top: calc(100% + 6px);
    transform: translateX(-50%) translateY(-4px);
}
[data-tooltip-pos="bottom"]:hover::after,
[data-tooltip-pos="bottom"]:focus-visible::after {
    transform: translateX(-50%) translateY(0);
}

/* ============================================================================
   36. DROPDOWN MENU (<details> natif stylé)
   ----------------------------------------------------------------------------
   Utilise `<details>` + `<summary>` natifs (a11y et clavier built-in).
   HTML attendu :
     <details class="menu">
       <summary class="menu__trigger">Actions</summary>
       <ul class="menu__list">
         <li><a class="menu__item" href="…">Éditer</a></li>
         <li><a class="menu__item" href="…">Dupliquer</a></li>
         <li class="menu__sep"></li>
         <li><a class="menu__item menu__item--danger" href="…">Supprimer</a></li>
       </ul>
     </details>
   ============================================================================ */
.menu { position: relative; display: inline-block; }
.menu__trigger {
    cursor: pointer; padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm); font-weight: 500; color: var(--text-soft);
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius); list-style: none; min-height: 36px;
    display: inline-flex; align-items: center; gap: var(--space-2);
    transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.menu__trigger::-webkit-details-marker { display: none; }
.menu__trigger::after {
    content: "▾"; font-size: 0.7em; color: var(--text-dim);
    transition: transform var(--dur-fast) var(--ease);
}
.menu[open] > .menu__trigger { background: var(--bg-sunken); border-color: var(--border-strong); }
.menu[open] > .menu__trigger::after { transform: rotate(180deg); }
.menu__trigger:hover { background: var(--bg-sunken); }
.menu__trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.menu__list {
    position: absolute; right: 0; top: calc(100% + 4px);
    list-style: none; padding: var(--space-1); margin: 0;
    min-width: 180px; background: var(--bg-elevated);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg); z-index: 50;
    animation: menu-in var(--dur-fast) var(--ease-out);
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.menu__item {
    display: block; padding: var(--space-2) var(--space-3);
    text-decoration: none; color: var(--text); font-size: var(--text-sm);
    border-radius: var(--radius-sm);
    transition: background var(--dur-fast) var(--ease);
}
.menu__item:hover, .menu__item:focus-visible { background: var(--bg-sunken); outline: 0; }
.menu__item--danger { color: var(--error); }
.menu__item--danger:hover { background: var(--error-bg); }
.menu__sep {
    height: 1px; background: var(--border-subtle); margin: var(--space-1) 0;
    list-style: none; padding: 0;
}

/* ============================================================================
   37. PAGINATION (nav + aria-label)
   ----------------------------------------------------------------------------
   HTML : <nav class="pagination" aria-label="Pagination">…</nav>
   ============================================================================ */
.pagination {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
    gap: var(--space-1); margin: var(--space-5) 0; font-size: var(--text-sm);
}
.pagination__prev, .pagination__next, .pagination__page {
    display: inline-flex; align-items: center; justify-content: center;
    /* AX-3 : cible tactile ≥ 44×44px (WCAG 2.5.8) */
    min-width: 44px; min-height: 44px; padding: 0 var(--space-2);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
    color: var(--text-soft); text-decoration: none; background: var(--bg-elevated);
    transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.pagination__prev:hover, .pagination__next:hover, .pagination__page:hover {
    background: var(--bg-sunken); border-color: var(--border); color: var(--text);
}
.pagination__current {
    background: var(--accent); border-color: var(--accent);
    color: var(--on-accent); font-weight: 500;
}
.pagination__prev[aria-disabled="true"],
.pagination__next[aria-disabled="true"] {
    opacity: 0.4; pointer-events: none;
}

/* ============================================================================
   38. FILE UPLOAD CUSTOM (drag&drop + preview + progress)
   ----------------------------------------------------------------------------
   Composant critique : validation MIME serveur OBLIGATOIRE (cf. audit
   @security-engineer §11.1 ; le client filtre uniquement pour UX).
   HTML attendu :
     <label class="upload upload--drop" for="ph-files">
       <input type="file" id="ph-files" name="files[]" multiple accept="…">
       <span class="upload__hint">…</span>
     </label>
     <ul class="upload__list" data-upload-list></ul>
   JS /assets/js/upload.js renseigne upload__list dynamiquement.
   ============================================================================ */
.upload {
    display: block; position: relative;
    padding: var(--space-6); text-align: center;
    background: var(--bg-elevated); border: 2px dashed var(--border);
    border-radius: var(--radius-md); color: var(--text-soft);
    cursor: pointer; min-height: 140px;
    transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.upload:hover, .upload:focus-within { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); }
.upload input[type="file"] {
    position: absolute; inset: 0; opacity: 0; cursor: pointer;
    width: 100%; height: 100%;
}
.upload__hint {
    display: block; font-size: var(--text-sm); margin-top: var(--space-2);
    color: var(--text-dim);
}
.upload__hint::before { content: "↑ "; color: var(--accent); }
.upload--drop.is-dragover {
    border-color: var(--accent); border-style: solid;
    background: var(--accent-soft); color: var(--accent-text);
}
.upload__list {
    list-style: none; padding: 0; margin: var(--space-3) 0 0;
    display: grid; gap: var(--space-2);
}
.upload__item {
    display: flex; align-items: center; gap: var(--space-3);
    padding: var(--space-2) var(--space-3); font-size: var(--text-sm);
    background: var(--bg-sunken); border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
}
.upload__item .upload__name { flex: 1; font-weight: 500; word-break: break-all; }
.upload__item .upload__size { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-dim); font-variant-numeric: tabular-nums; }
/* T13.2.2 S13 — badge type fichier (PDF/IMG/DOC/XLS/ZIP/FIC) */
.upload__badge {
    display: inline-flex; align-items: center; justify-content: center;
    flex: 0 0 auto; min-width: 38px; padding: 2px var(--space-2);
    font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
    color: var(--text-soft); background: var(--paper);
    border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
    letter-spacing: var(--tracking-wide); text-transform: uppercase;
}
.upload__item.is-error .upload__badge { color: var(--error); border-color: var(--error-border); }
.upload__item.is-error { background: var(--error-bg); color: var(--error); border-color: var(--error-border); }
.upload__item.is-success { background: var(--success-bg); color: var(--success); border-color: var(--success-border); }
.upload__progress {
    flex: 0 0 100px; height: 4px; background: var(--border-subtle);
    border-radius: var(--radius-full); overflow: hidden;
}
.upload__progress > span {
    display: block; height: 100%; background: var(--accent);
    width: 0%; transition: width var(--dur) var(--ease);
}

/* ============================================================================
   39. ÉTATS COMPOSANTS ENRICHIS (T11.3)
   ----------------------------------------------------------------------------
   - .btn.is-loading : bouton désactivé visuellement + spinner intégré
   - .form__input--error / [aria-invalid="true"] : champ en erreur
   - .form__input--success : champ validé (état positif)
   - .form__error : message inline rendu sous le champ avec role="alert"
   ============================================================================ */
.btn.is-loading {
    position: relative; pointer-events: none; color: transparent !important;
}
.btn.is-loading::after {
    content: ""; position: absolute; top: 50%; left: 50%;
    width: 16px; height: 16px; margin: -8px 0 0 -8px;
    border: 2px solid currentColor; border-top-color: transparent;
    border-radius: 50%; animation: spinner-rot var(--dur-spin) linear infinite;
    color: var(--paper); opacity: 0.9;
}
.btn--ghost.is-loading::after, .btn--subtle.is-loading::after { color: var(--accent); }

.form__input--error, .form__input[aria-invalid="true"] {
    border-color: var(--error); background: var(--error-bg);
    color: var(--text);
}
.form__input--error:focus, .form__input[aria-invalid="true"]:focus {
    box-shadow: 0 0 0 3px rgba(185, 28, 28, 0.2);
}
.form__input--success {
    border-color: var(--success);
}
.form__input--success:focus {
    box-shadow: 0 0 0 3px rgba(21, 128, 61, 0.18);
}
.form__error {
    display: block; margin-top: var(--space-1); font-size: var(--text-xs);
    color: var(--error); font-weight: 500;
}
.form__error::before { content: "⚠ "; margin-right: 4px; }
.form__field {
    display: flex; flex-direction: column; gap: var(--space-1);
    margin-bottom: var(--space-3);
}

/* ============================================================================
   40. NOTIFICATIONS + PAGE-HEADER (résolution orphelins S10 résiduels)
   ----------------------------------------------------------------------------
   `.page-header` : pattern partagé titre + actions ; `.page__title` : titre
   neutre des pages profile. `.notifications` : liste minimaliste avec
   variantes `--unread` accentuées.
   ============================================================================ */
.page-header {
    display: flex; align-items: baseline; justify-content: space-between;
    flex-wrap: wrap; gap: var(--space-3); margin-bottom: var(--space-5);
    padding-bottom: var(--space-4); border-bottom: 1px solid var(--border);
}
.page-header__title {
    font-family: var(--font-display); font-weight: 400;
    font-size: var(--text-2xl); margin: 0;
    letter-spacing: var(--tracking-tight);
}
.page-header__action, .page-header__actions {
    display: flex; gap: var(--space-2); align-items: center; margin: 0;
}
.page__title {
    font-family: var(--font-display); font-weight: 500; /* S15+ : bump pour Satoshi */
    font-size: var(--text-3xl); margin: 0 0 var(--space-5);
    letter-spacing: var(--tracking-tight);
    padding-bottom: var(--space-3); border-bottom: 1px solid var(--border);
}

/* ============================================================================
   40.b NOTIFICATIONS — refonte Atelier v2 (2026-05)
   ----------------------------------------------------------------------------
   Centre /notifications partagé par les 3 personas. Composant `.notif-page`
   en BEM strict. Hiérarchie :
     - .notif-page__header : titre + bouton mark-all-read
     - .notif-filters       : barre de chips (Tous / Non-lus / Messages / ...)
     - .notif-list          : groupes by-day (today / yesterday / week / older)
     - .notif-item          : ligne complète cliquable (button full-width)
     - .notif-empty         : 3 variantes (jamais / filtre vide / unread = 0)
   Réutilise : .tag, .dot--accent, .btn, .chip (introduit ici, pattern DS).
   ========================================================================= */
.notif-page { display: block; margin-bottom: var(--space-6); }

.notif-page__header { margin-bottom: var(--space-5); }
.notif-page__mark-all { margin: 0; }

/* — Chips de filtres (scroll-x mobile) -------------------------------------- */
.notif-filters {
    margin: 0 0 var(--space-5);
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: var(--border) transparent;
}
.notif-filters__list {
    list-style: none; padding: 0; margin: 0;
    display: flex; gap: var(--space-2);
    /* Évite que le dernier chip soit collé au bord lors d'un scroll-x */
    padding-bottom: 2px;
}
.notif-filters__item { flex-shrink: 0; }

.chip--filter {
    display: inline-flex; align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    /* AX-3 : cible tactile ≥ 44px (WCAG 2.5.8) */
    min-height: 44px;
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-soft);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-full);
    text-decoration: none;
    white-space: nowrap;
    transition: color var(--dur-fast) var(--ease),
                background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease);
}
.chip--filter:hover {
    color: var(--text);
    border-color: var(--border-strong);
}
.chip--filter:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.chip--filter.is-active {
    background: var(--text);
    color: var(--text-invert);
    border-color: var(--text);
}
.chip__count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px;
    padding: 0 var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    background: var(--accent);
    color: var(--text-invert);
    border-radius: var(--radius-full);
    letter-spacing: 0;
}
.chip--filter.is-active .chip__count {
    background: var(--text-invert);
    color: var(--text);
}

/* — Groupes by-day ---------------------------------------------------------- */
.notif-list {
    display: flex; flex-direction: column;
    gap: var(--space-5);
}
.notif-list__group { display: block; }
.notif-list__group-title {
    display: flex; align-items: baseline; gap: var(--space-2);
    margin: 0 0 var(--space-2);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 500;
    color: var(--text-mute);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.notif-list__group-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px;
    padding: 0 var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    background: var(--bg-sunken);
    color: var(--text-on-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-sm);
}
.notif-list__items {
    list-style: none; padding: 0; margin: 0;
    display: flex; flex-direction: column;
    gap: var(--space-2);
}

/* — Item (button cliquable plein-largeur) ----------------------------------- */
.notif-item { margin: 0; }
.notif-item__form { margin: 0; display: block; }
.notif-item__button {
    /* Reset button → flex card */
    appearance: none;
    width: 100%;
    display: flex; align-items: flex-start;
    /* mobile-first : gap et padding compacts ; élargis ≥ 721px */
    gap: var(--space-2);
    padding: var(--space-3);
    min-height: 64px;
    text-align: left;
    background: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
    color: var(--text);
    font: inherit;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease),
                border-color var(--dur-fast) var(--ease),
                transform var(--dur-instant) var(--ease);
}
.notif-item__button:hover {
    background: var(--bg-canvas);
    border-color: var(--border-strong);
}
.notif-item__button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.notif-item__button:active { transform: translateY(1px); }

.notif-item--unread .notif-item__button {
    background: var(--accent-soft);
    border-left: 3px solid var(--accent);
    /* mobile-first : padding-left basé sur space-3 */
    padding-left: calc(var(--space-3) - 3px);
}
.notif-item--unread .notif-item__button:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
    border-left-color: var(--accent);
}
.notif-item--read .notif-item__title { font-weight: 400; color: var(--text-soft); }
.notif-item--read .notif-item__icon  { color: var(--text-mute); }

/* Icône */
.notif-item__icon {
    flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    /* mobile-first : icône compacte 32px ; 36px ≥ 721px */
    width: 32px; height: 32px;
    border-radius: var(--radius-sm);
    background: var(--bg-sunken);
    color: var(--text-soft);
}
.notif-item--unread .notif-item__icon {
    background: var(--paper);
    color: var(--accent);
}

/* Bloc central */
.notif-item__main {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column;
    gap: var(--space-1);
}
.notif-item__head {
    display: flex; align-items: baseline;
    /* mobile-first : gap compact ; espace-2 ≥ 721px */
    gap: var(--space-1);
    flex-wrap: wrap;
}
.notif-item__dot { flex-shrink: 0; align-self: center; }
.notif-item__title {
    flex: 1; min-width: 0;
    font-family: var(--font-sans);
    font-weight: 500;
    font-size: var(--text-sm);
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.notif-item__time {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: var(--text-mute);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    white-space: nowrap;
}
.notif-item__body {
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: var(--leading-base);
    /* mobile-first : 3 lignes ; 2 lignes ≥ 721px (densité desktop) */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}
.notif-item__kind { align-self: flex-start; }

/* Chevron à droite */
.notif-item__chevron {
    flex-shrink: 0;
    /* mobile-first : chevron masqué (espace restreint) ; visible ≥ 721px */
    display: none;
    align-items: center;
    color: var(--text-mute);
    transition: transform var(--dur-fast) var(--ease),
                color var(--dur-fast) var(--ease);
}
.notif-item__button:hover .notif-item__chevron {
    transform: translateX(2px);
    color: var(--accent);
}

/* — Empty states ------------------------------------------------------------ */
.notif-empty {
    display: flex; flex-direction: column; align-items: center;
    gap: var(--space-4);
    padding: var(--space-7) var(--space-4);
    text-align: center;
    color: var(--text-soft);
}
/* 2026-05 — Empty state icon SVG (refonte : ASCII art remplacé par icône
   cohérente avec la DA du DS — voir aussi cloche topbar / icônes kind). */
.notif-empty__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 72px;
    height: 72px;
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    color: var(--text-mute);
    border: 1px solid var(--border);
}
.notif-empty__icon--success {
    background: var(--success-bg);
    border-color: var(--success-border);
    color: var(--success);
}
.notif-empty__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 500;
    font-size: var(--text-xl);
    color: var(--text);
}
.notif-empty__text {
    margin: 0;
    max-width: var(--content-prose);
    font-size: var(--text-base);
}
.notif-empty__cta { margin: 0; }

.notif-list__cap {
    margin: var(--space-5) 0 0;
    padding: var(--space-3) var(--space-4);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    color: var(--text-mute);
    text-align: center;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
    border: 1px dashed var(--border-subtle);
    border-radius: var(--radius);
}

/* — Responsive ≥ 721px : layout desktop notifications — */
@media (min-width: 721px) {
    .notif-item__button {
        gap: var(--space-3);
        padding: var(--space-3) var(--space-4);
    }
    .notif-item--unread .notif-item__button {
        padding-left: calc(var(--space-4) - 3px);
    }
    .notif-item__icon { width: 36px; height: 36px; }
    .notif-item__head { gap: var(--space-2); }
    .notif-item__body { -webkit-line-clamp: 2; }
    .notif-item__chevron { display: inline-flex; }
}

/* — Reduced motion (override charte) --------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .notif-item__button,
    .notif-item__chevron,
    .chip--filter { transition: none; }
    .notif-item__button:hover .notif-item__chevron { transform: none; }
    .notif-item__button:active { transform: none; }
}

/* ============================================================================
   41. WCAG 2.2 — 2.4.11 FOCUS NOT OBSCURED (S12 T12.2.7)
   ----------------------------------------------------------------------------
   Topbar sticky 64px : tout élément focusable risque d'être partiellement
   masqué lors d'un focus déclenché par anchor link, JS focus(), ou Tab.
   `scroll-margin-top` ajoute un offset au navigateur quand il scrolle
   pour amener un élément en vue. Appliqué aux ancres + interactifs majeurs.
   `:target` est aussi couvert pour les anchors d'URL.
   ============================================================================ */
:where(
    a, button, input, textarea, select, summary, details, dialog,
    .breadcrumb, .page-header, .messages__page-header,
    .card, .form__row, .form__field,
    h1, h2, h3, h4, [tabindex]
):focus,
:where(
    a, button, input, textarea, select, summary, details, dialog,
    .breadcrumb, .page-header, .messages__page-header,
    .card, .form__row, .form__field,
    h1, h2, h3, h4, [tabindex]
):focus-visible,
:target {
    scroll-margin-top: calc(var(--topbar-h) + var(--space-3));
}

/* ============================================================================
   42. POLISH MICRO-INTERACTIONS (S13)
   ----------------------------------------------------------------------------
   Regroupe les transitions de finition appliquées en T13.2 / T13.3 :
   - flash dismiss animé (slide-up + fade) — T13.2.4
   - quote/invoice totals : fade subtil lors de la mise à jour — T13.2.3
   - bouton :active feedback tactile via --dur-instant — T13.1.2
   - ligatures standards + kerning + ss01 sur titres + tabular-nums sur chiffres — T13.3.2/3 (Fraunces retirée S15+)
   - logo monogramme optical alignment — T13.3.4
   - reveal disabled si reduced motion ou save-data (handled JS, voir ui.js)
   ============================================================================ */

/* — 42.1 Flash auto-dismiss (T13.2.4) ---------------------------------------
   JS pose .is-dismissing → CSS prend la transition, transitionend nettoie. */
.flash__item {
    transition: opacity var(--dur-slow) var(--ease-in),
                transform var(--dur-slow) var(--ease-in),
                max-height var(--dur-slow) var(--ease-in),
                margin var(--dur-slow) var(--ease-in),
                padding var(--dur-slow) var(--ease-in),
                border-width var(--dur-slow) var(--ease-in);
    max-height: 400px; /* assez large pour ne pas écraser le contenu naturel */
    overflow: hidden;
}
.flash__item.is-dismissing {
    opacity: 0; transform: translateY(-8px); max-height: 0;
    margin-top: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0;
    border-top-width: 0; border-bottom-width: 0;
}

/* — 42.2 Toast — définition canonique UNIQUE en §34 (z-index 1200 au-dessus
   des modales, animation toast-in à l'insertion, .is-leaving pour la sortie).
   L'ancienne redéfinition divergente (z-index 80 < modale, flex-direction et
   max-width contradictoires, .is-visible/.is-dismissing) a été retirée : elle
   entrait en conflit avec §34 sans jamais être instanciée (aucun JS toast). */

/* — 42.3 Quote / Invoice totals fade lors de la mise à jour (T13.2.3) ------ */
.quote__totals, .invoice__totals {
    transition: opacity var(--dur-fast) var(--ease);
}
.quote__totals.is-updating, .invoice__totals.is-updating {
    opacity: 0.4;
}
.quote__totals strong, .invoice__totals strong {
    transition: color var(--dur-fast) var(--ease);
}
.quote__totals.is-updated strong, .invoice__totals.is-updated strong {
    color: var(--success);
}

/* — 42.4 Boutons : feedback tactile :active (T13.1.2) ---------------------- */
/*    On surcharge la transition par :active pour passer en --dur-instant.   */
.btn:active:not(:disabled):not(.is-loading) {
    transform: translateY(1px) scale(0.99);
    transition-duration: var(--dur-instant);
}

/* — 42.5 Bouton "Je signe" — micro-feedback succès (T13.2.1) --------------- */
/*    Sur submit, JS pose .is-loading → spinner intégré (cf. §39 .btn.is-loading). */
/*    Après réponse serveur (succès), JS bascule .is-success pour fanion vert.   */
.decision__sign .btn.is-success {
    background: var(--success); color: var(--on-success);
    border-color: var(--success);
    transition: background var(--dur) var(--ease),
                color var(--dur) var(--ease),
                border-color var(--dur) var(--ease);
}
.decision__sign .btn.is-success::after {
    content: " ✓"; font-weight: 700;
}

/* — 42.6 Ligatures & kerning titres (T13.3.2, mis à jour S15+) ------------- */
/*    Satoshi : 'liga' (ligatures standards) + 'kern' (kerning fin) + 'ss01' */
/*    (alternates rounded a/g hérité du <html>). 'dlig' (Fraunces) retirée   */
/*    car Satoshi n'expose pas cette feature OpenType. */
.h-display, .h-display--lg, .h-display--xl,
.section__title, .section__title--lg,
.page-header__title, .page__title, .topbar__logo,
.metric__title, .auth-card__title, .auth-hero__quote,
h1, h2, h3, h4, h5, h6 {
    font-feature-settings: 'liga' 1, 'kern' 1, 'ss01' 1;
}

/* — 42.7 Tabular nums étendu (T13.3.3) ------------------------------------- */
/*    Tout chiffre représentant une mesure / un compteur / un total : tabulaire. */
.metric__value, .metric__delta,
.chantiers__meta--lg,
.docs__size, .docs__perf,
.invoice__totals, .quote__totals,
.invoices__table td, .quotes__table td,
.docs__table td, .messages__count,
.pagination__current, .pagination__page,
time {
    font-variant-numeric: tabular-nums;
}

/* — 42.8 (retiré) — l'ancien monogramme CSS ::before et sa compensation
   optique ont été remplacés par le lockup raster bi-thème (cf. ADR-021,
   règles .topbar__logo-img plus haut). */

/* — Reduced motion ------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: var(--dur-reduced) !important;
        animation-duration: var(--dur-reduced) !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
}

/* — Print ---------------------------------------------------------------- */
@media print {
    .topbar, .footer, .flash, .form__actions,
    .invite, .dashboard__cta { display: none !important; }
    body { background: #fff; }
    .main { max-width: 100%; padding: 0; margin: 0; }
    .card { box-shadow: none; border-color: #ccc; break-inside: avoid; }
}

/* ============================================================================
   S17 — Paiement PIS Bridge (ADR-0014)
   --------------------------------------------------------------------------
   Deux écrans côté MOA :
     1. payment-confirm : page de confirmation avant redirection banque (modale).
     2. payment-return  : page d'atterrissage post-signature banque (4 statuts).

   Convention BEM stricte (block__element--modifier), tokens DS Atelier v2.
   Pas de couleur en dur — uniquement des variables DS pour homogénéité dark/light.
   ============================================================================ */

/* — Page de confirmation (intent_confirm.php) — modale natif <dialog> ------- */
.payment-confirm {
    max-width: 640px;
    margin: var(--space-8) auto;
    padding: 0 var(--space-4);
}

.payment-confirm__dialog {
    position: static;             /* on n'utilise pas le mode modal natif (.showModal()) */
    display: block;
    width: 100%;
    margin: var(--space-6) 0;
    /* mobile-first : padding compact ; élargi ≥ 541px */
    padding: var(--space-6) var(--space-4);
    background: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    color: var(--text);
}

.payment-confirm__header {
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
}

.payment-confirm__title {
    margin: 0 0 var(--space-3) 0;
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: 600;
    color: var(--text);
}

.payment-confirm__sub {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: 1.55;
}

.payment-confirm__details {
    display: grid;
    gap: var(--space-3);
    margin: 0 0 var(--space-6) 0;
    padding: 0;
}

.payment-confirm__row {
    display: grid;
    /* mobile-first : 1 colonne ; 1fr 2fr ≥ 541px */
    grid-template-columns: 1fr;
    gap: var(--space-1);
    align-items: baseline;
    padding: var(--space-3) 0;
    border-bottom: 1px dashed var(--border-subtle);
}

.payment-confirm__row:last-child { border-bottom: 0; }

.payment-confirm__row dt {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-soft);
}

.payment-confirm__row dd {
    margin: 0;
    font-size: var(--text-base);
    color: var(--text);
    word-break: break-word;
}

.payment-confirm__amount {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.payment-confirm__iban {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

.payment-confirm__notice {
    padding: var(--space-4) var(--space-5);
    margin: var(--space-6) 0;
    background: var(--bg-sunken);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: 1.6;
}

.payment-confirm__notice p { margin: 0; }

.payment-confirm__actions {
    display: flex;
    gap: var(--space-3);
    /* mobile-first : boutons en stretch pleine largeur */
    justify-content: stretch;
    flex-wrap: wrap;
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border-subtle);
}
.payment-confirm__actions .btn { flex: 1 1 auto; }

/* — Page de retour (return.php) — statut post-signature -------------------- */
.payment-return {
    max-width: 640px;
    margin: var(--space-8) auto;
    padding: 0 var(--space-4);
}

.payment-return__card {
    margin: var(--space-6) 0;
    /* mobile-first : padding réduit ; élargi ≥ 541px */
    padding: var(--space-8) var(--space-4);
    background: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    text-align: center;
    color: var(--text);
}

.payment-return__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    margin: 0 auto var(--space-5);
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    color: var(--text-soft);
    font-size: var(--text-3xl);
    font-weight: 700;
    line-height: 1;
}

.payment-return__icon--ok {
    background: color-mix(in srgb, #16a34a 14%, var(--bg-elevated));
    color: #16a34a;
}

.payment-return__icon--ko {
    background: color-mix(in srgb, #dc2626 14%, var(--bg-elevated));
    color: #dc2626;
}

.payment-return__icon--wait {
    background: color-mix(in srgb, var(--accent) 14%, var(--bg-elevated));
    color: var(--accent);
}

.payment-return__title {
    margin: 0 0 var(--space-4) 0;
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: 600;
    color: var(--text);
}

.payment-return__lead {
    margin: 0 0 var(--space-4) 0;
    font-size: var(--text-base);
    color: var(--text);
    line-height: 1.6;
}

.payment-return__hint {
    margin: 0 0 var(--space-6) 0;
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: 1.55;
}

.payment-return__actions {
    display: flex;
    gap: var(--space-3);
    justify-content: center;
    flex-wrap: wrap;
    margin-top: var(--space-6);
}

/* Modifier : variantes de statut (selectors --processing/--succeeded/etc.
   héritent du card par défaut ; on les laisse en hook potentiel S18+). */

/* — Page banque MOCK (BRIDGE_ENV=mock, local/staging only) ----------------- */
/* Bloc dédié pour ne pas réutiliser .payment-confirm__* (pour clarté audit
   visuel : on doit voir d'un coup d'oeil qu'on est en mock).                 */

.payment-mock-bank {
    max-width: 720px;
    margin: var(--space-8) auto;
    padding: 0 var(--space-4);
}

.payment-mock-bank__banner {
    margin-bottom: var(--space-6);
    padding: var(--space-3) var(--space-4);
    background: color-mix(in srgb, #facc15 18%, var(--bg-elevated));
    border: 1px solid color-mix(in srgb, #facc15 40%, var(--border-subtle));
    border-left: 4px solid #b45309;
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: var(--text-sm);
    line-height: 1.55;
}

.payment-mock-bank__banner code {
    background: rgba(0, 0, 0, 0.06);
    padding: 0 var(--space-1);
    border-radius: var(--radius-xs);
    font-family: var(--font-mono);
    font-size: 0.875em;
}

.payment-mock-bank__card {
    margin: var(--space-6) 0;
    /* mobile-first : padding réduit ; élargi ≥ 541px */
    padding: var(--space-6) var(--space-4);
    background: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    color: var(--text);
}

.payment-mock-bank__header {
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
}

.payment-mock-bank__title {
    margin: 0 0 var(--space-3) 0;
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    font-weight: 600;
    color: var(--text);
}

.payment-mock-bank__sub {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: 1.55;
}

.payment-mock-bank__details {
    display: grid;
    gap: var(--space-3);
    margin: 0 0 var(--space-6) 0;
    padding: 0;
}

.payment-mock-bank__row {
    display: grid;
    /* mobile-first : 1 colonne ; 1fr 2fr ≥ 541px */
    grid-template-columns: 1fr;
    gap: var(--space-1);
    align-items: baseline;
    padding: var(--space-3) 0;
    border-bottom: 1px dashed var(--border-subtle);
}

.payment-mock-bank__row:last-child { border-bottom: 0; }

.payment-mock-bank__row dt {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text-soft);
}

.payment-mock-bank__row dd {
    margin: 0;
    font-size: var(--text-base);
    color: var(--text);
    word-break: break-word;
}

.payment-mock-bank__amount {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.payment-mock-bank__iban,
.payment-mock-bank__ref {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    word-break: break-all;
}

.payment-mock-bank__notice {
    padding: var(--space-4) var(--space-5);
    margin: var(--space-6) 0;
    background: var(--bg-sunken);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    color: var(--text-soft);
    line-height: 1.6;
}

.payment-mock-bank__notice p { margin: 0; }

.payment-mock-bank__actions {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-4);
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border-subtle);
}

.payment-mock-bank__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin: 0;
}

.payment-mock-bank__btn {
    width: 100%;
    justify-content: center;
}

.payment-mock-bank__hint {
    margin: 0;
    font-size: var(--text-xs);
    color: var(--text-soft);
    text-align: center;
    line-height: 1.4;
}

.payment-mock-bank__hint code {
    /* DS-3 : rgba(0,0,0,.04) → token sémantique (marche en dark aussi) */
    background: var(--bg-sunken);
    padding: 0 var(--space-1);
    border-radius: var(--radius-xs);
    font-family: var(--font-mono);
    font-size: 0.95em;
}

.payment-mock-bank__footer {
    margin-top: var(--space-6);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border-subtle);
    text-align: center;
}

/* ≥ 541px : layout desktop payment — 2 colonnes, padding généreux */
@media (min-width: 541px) {
    .payment-confirm__row {
        grid-template-columns: 1fr 2fr;
        gap: var(--space-4);
    }
    .payment-confirm__dialog { padding: var(--space-8); }
    .payment-return__card    { padding: var(--space-10) var(--space-8); }
    .payment-confirm__actions { justify-content: flex-end; }
    .payment-confirm__actions .btn { flex: 0 0 auto; }
    .payment-mock-bank__row {
        grid-template-columns: 1fr 2fr;
        gap: var(--space-4);
    }
    .payment-mock-bank__card { padding: var(--space-8); }
}

/* ============================================================================
   44. TRANSACTIONS (historique paiements — S18)
   ----------------------------------------------------------------------------
   Vue globale (/app/transactions) et vue par chantier (/app/chantiers/:id/transactions).
   Reprend les tokens DS et suit BEM strict.
   ============================================================================ */
.transactions { display: block; margin-bottom: var(--space-8); }

.transactions__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--border);
}
.transactions__heading { display: grid; gap: var(--space-1); min-width: 0; }
.transactions__subtitle {
    margin: 0;
    font-size: var(--text-sm);
    color: var(--text-dim);
}

/* Bandeau de synthèse (KPI encaissé / en attente / échecs / clôturés).
   ───────────────────────────────────────────────────────────────────────────
   Tous les sélecteurs de ce bloc sont SCOPÉS `.transactions__summary` : ils
   raffinent .kpi UNIQUEMENT dans le contexte transactions et n'impactent PAS
   les autres .kpi-grid partagés (dashboard architecte `app.php`, fiche chantier
   `chantiers/show.php`). Tokens DS only — aucune valeur en dur. */
.transactions__summary { margin-bottom: var(--space-6); }

/* Compactage : ces cartes sont un bandeau de lecture, pas des tuiles de dashboard.
   On resserre le padding et le gap interne pour remonter le tableau au-dessus de
   la ligne de flottaison. Mobile reste 2-up (hérité de .kpi-grid). */
.transactions__summary .kpi {
    gap: var(--space-1);
    padding: var(--space-4) var(--space-4) var(--space-4) var(--space-5);
}
.transactions__summary .kpi__value { font-size: var(--text-xl); }

/* Renforcement du signal sémantique : la VALEUR reprend la teinte du statut
   (pas seulement l'arête ::before ni le hint). Le label reste neutre (ancrage
   Swiss). Le hint coloré est déjà géré par les règles globales .kpi--*.
   Override dark mode hérité (main.css §dark, .kpi--* .kpi__hint). */
.transactions__summary .kpi--success .kpi__value { color: var(--success); }
.transactions__summary .kpi--warning .kpi__value { color: var(--warning); }
.transactions__summary .kpi--error   .kpi__value { color: var(--error); }

/* État « zéro » : une carte « action requise » sans rien à traiter (En attente
   0 / Échecs 0) est volontairement atténuée pour laisser l'attention aux cartes
   qui portent une valeur. La couleur n'est jamais le seul vecteur : le « 0 » et
   le hint texte restent présents (« rien à finaliser » / « aucun échec »).
   On neutralise l'arête sémantique et on dé-sature la valeur. */
.transactions__summary .kpi--zero::before { background: var(--border); }
.transactions__summary .kpi--zero .kpi__value { color: var(--text-mute); }
.transactions__summary .kpi--zero .kpi__hint  { color: var(--text-dim); }

/* Barre de filtres (statut + plage de dates + submit) */
.transactions__filters {
    display: flex;
    /* mobile-first : colonne empilée, items en stretch */
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-3);
    margin-bottom: var(--space-5);
    padding: var(--space-4);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
}

/* Compteur de résultats (ex: "12 transactions" / "8 devis" / "5 factures") */
.transactions__count,
.invoices__count,
.quotes__count {
    font-size: var(--text-xs);
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    margin-bottom: var(--space-3);
}
.transactions__count-filtered { color: var(--text-mute); }

/* État vide — état centré (icône + titre + aide + action), contextuel selon
   qu'un filtre est actif ou que l'organisation n'a aucun paiement. */
.transactions__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-10) var(--space-6);
    text-align: center;
    color: var(--text-soft);
    background: var(--bg-sunken);
    border: 1px dashed var(--border);
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
}
.transactions__empty-icon { color: var(--text-mute); margin-bottom: var(--space-1); }
.transactions__empty-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--text-base);
    font-weight: 500;
    color: var(--text);
}
.transactions__empty-text {
    margin: 0;
    max-width: 42ch;
    color: var(--text-dim);
}
.transactions__empty .btn { margin-top: var(--space-2); }

/* Tableau des transactions — étend le style table générique §15 */
.transactions__table {
    width: 100%;
    border-collapse: collapse;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}

/* — Cellules enrichies (refonte UX 2026-05-29) ----------------------------- */

/* Date : date (primaire) + heure (secondaire) empilées — désambigüise deux
   tentatives le même jour. */
.tx-when__date { display: block; color: var(--text); }
.tx-when__time { display: block; font-size: var(--text-2xs); color: var(--text-dim); }

/* Numéro de facture : chip mono compact. */
.tx-invoice { font-size: var(--text-xs); }

/* Bénéficiaire : intitulé (primaire) + IBAN masqué (secondaire mono). */
.tx-beneficiary__name { display: block; color: var(--text); }
.tx-beneficiary__iban {
    display: block;
    font-size: var(--text-2xs);
    color: var(--text-dim);
    letter-spacing: var(--tracking-wide);
}

/* Montant TTC : donnée clé, mise en avant (mono, semi-bold). */
.tx-amount {
    font-family: var(--font-mono);
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
}

/* Statut : badge + éventuel motif d'échec sous le badge. */
.tx-status { white-space: normal; }
.tx-status__reason {
    display: block;
    margin-top: var(--space-1);
    max-width: 28ch;
    font-size: var(--text-2xs);
    line-height: var(--leading-snug);
    color: var(--text-dim);
    white-space: normal;
}

/* mobile-first : form__row--inline dans les filtres transactions empilé verticalement */
.transactions__filters .form__row--inline {
    flex-direction: column;
    align-items: stretch;
}

/* ≥ 640px : layout desktop — filtres en ligne (inversé depuis max-width: 639px) */
@media (min-width: 640px) {
    .transactions__filters {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: flex-end;
    }
    .transactions__filters .form__row--inline {
        flex-direction: row;
        align-items: stretch;
    }
}

/* ===========================================================================
   §X — Tableau de bord chantier (vue d'ensemble architecte)
   Intégration de la maquette « Tableau de bord chantier » (Claude design).
   Tokens DA uniquement ; le vert de marque de la maquette (--brand) est mappé
   sur --accent (pas de re-theming persona). Seul style inline : --pct sur les
   barres de progression (convention Gantt/dashboard).
   =========================================================================== */

/* — Hero ------------------------------------------------------------------- */
.chantier-hero {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-5);
    align-items: flex-start;
    justify-content: space-between;
    padding: var(--space-6);
    margin-bottom: var(--space-5);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}
.chantier-hero__main { min-width: 0; flex: 1 1 22rem; }
.chantier-hero__head {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.chantier-hero__title {
    margin: 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: var(--text-2xl);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.chantier-hero__ref {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    color: var(--text-dim);
}
.chantier-hero__meta {
    margin: var(--space-2) 0 0;
    color: var(--text-dim);
    font-size: var(--text-sm);
}
.chantier-hero__pills {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-4) 0 0;
    padding: 0;
    list-style: none;
}
.chantier-hero__aside {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
}

/* — Avatars intervenants (pile) — monochrome DA, lead en accent ------------- */
.avatar-stack { display: flex; align-items: center; padding: 0; margin: 0; list-style: none; }
.avatar-stack > .avatar + .avatar { margin-left: -0.55rem; }
.avatar {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius-full);
    background: var(--bg-sunken);
    color: var(--text-soft);
    font-size: var(--text-2xs);
    font-weight: 600;
    box-shadow: 0 0 0 2px var(--paper); /* anneau séparateur (cf. badge--notif-count) */
}
/* c1 = référent (architecte, poussé en premier) → tinte d'accent ; c2..c6
   restent neutres (différenciation discrète, fidèle à la DA monochrome). */
.avatar--c1 { background: var(--accent-soft); color: var(--accent-text); }

/* — Sous-navigation (variante liens de .tabs) ------------------------------ */
.chantier-subnav { margin-bottom: var(--space-5); }
a.tabs__tab { text-decoration: none; display: inline-flex; align-items: center; }
a.tabs__tab:hover { text-decoration: none; color: var(--text); }
.tabs__tab[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); }
.tabs__tab[aria-current="page"]::after { left: var(--space-3); right: var(--space-3); opacity: 1; }

/* — Grille dashboard : colonnes 7 / 5 / 12 (réutilise .bento) --------------- */
.bento__col { grid-column: 1 / -1; } /* mobile : pleine largeur */
@media (min-width: 881px) {
    .bento__col--7  { grid-column: span 7; }
    .bento__col--5  { grid-column: span 5; }
    .bento__col--12 { grid-column: span 12; }
}

/* — KPI finance non cliquables (pas d'affordance de lien) ------------------- */
.kpi--static { cursor: default; }
.kpi--static:hover {
    border-color: var(--border);
    box-shadow: var(--shadow-xs);
    transform: none;
}

/* — Avancement par phase --------------------------------------------------- */
.phase-progress__link {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--accent-text);
    text-decoration: none;
    white-space: nowrap;
}
.phase-progress__link:hover { text-decoration: underline; }
.phase-progress__link .icon { width: 0.9rem; height: 0.9rem; }
.phase-progress__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.phase-progress__row { display: flex; align-items: center; gap: var(--space-3); }
.phase-progress__code {
    flex: none;
    width: 2.75rem;
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    color: var(--text-dim);
}
.phase-progress__bar {
    flex: 1 1 auto;
    height: 8px;
    background: var(--ink-100);
    border-radius: var(--radius-full);
    overflow: hidden;
}
.phase-progress__fill {
    display: block;
    height: 100%;
    width: calc(var(--pct, 0) * 1%); /* convention DA : géométrie via variable CSS */
    border-radius: var(--radius-full);
    background: var(--accent);
    transition: width var(--dur) var(--ease);
}
.phase-progress__bar--current .phase-progress__fill { opacity: 0.85; }
.phase-progress__bar--todo .phase-progress__fill { background: var(--border-strong); }
.phase-progress__pct {
    flex: none;
    width: 2.75rem;
    text-align: right;
    font-size: var(--text-xs);
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}

/* — Diffusion de plans ----------------------------------------------------- */
.plan-diffusion__summary {
    margin: 0 0 var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-dim);
}
.plan-diffusion__summary strong {
    color: var(--text);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.plan-diffusion__alert { color: var(--warning); }
.plan-diffusion__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}
.plan-diffusion__row { display: flex; flex-direction: column; }
.plan-diffusion__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    font-size: var(--text-sm);
}
.plan-diffusion__label { min-width: 0; color: var(--text); }
.plan-diffusion__code { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-soft); }
.plan-diffusion__indice { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-dim); }
.plan-diffusion__count { flex: none; font-size: var(--text-xs); color: var(--text-dim); white-space: nowrap; }
.plan-diffusion__track {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-top: var(--space-2);
}
.plan-diffusion__bar {
    flex: 1 1 auto;
    height: 6px;
    background: var(--ink-100);
    border-radius: var(--radius-full);
    overflow: hidden;
}
.plan-diffusion__fill {
    display: block;
    height: 100%;
    width: calc(var(--pct, 0) * 1%);
    border-radius: var(--radius-full);
    background: var(--accent);
    transition: width var(--dur) var(--ease);
}
.plan-diffusion__bar--complete .plan-diffusion__fill { background: var(--success); }
.plan-diffusion__action,
.plan-diffusion__state {
    flex: none;
    white-space: nowrap;
    font-size: var(--text-xs);
}
.plan-diffusion__action { font-weight: 500; color: var(--accent-text); text-decoration: none; }
.plan-diffusion__action:hover { text-decoration: underline; }
.plan-diffusion__state { display: inline-flex; align-items: center; gap: 2px; }
.plan-diffusion__state--done { color: var(--success); }
.plan-diffusion__state .icon { width: 0.9rem; height: 0.9rem; }

/* — Prochains jalons ------------------------------------------------------- */
.milestone-list__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.milestone-list__item { display: flex; align-items: center; gap: var(--space-3); }
.milestone-list__marker {
    flex: none;
    width: 0.6rem;
    height: 0.6rem;
    transform: rotate(45deg);
    border-radius: 2px;
    background: var(--ink-300);
}
.milestone-list__marker--reached  { background: var(--success); }
.milestone-list__marker--overdue  { background: var(--warning); }
.milestone-list__marker--upcoming { background: var(--accent); }
.milestone-list__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.milestone-list__label { font-size: var(--text-sm); font-weight: 500; color: var(--text); }
.milestone-list__date { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--text-dim); }
.milestone-list__item .badge { flex: none; }

/* ===========================================================================
   §Y — Dashboard artisan « Ma journée »
   Intégration de la maquette « Dashboard Artisan — Ma journée » (Claude design).
   Tokens DA uniquement ; le vert de marque de la maquette (--brand) est mappé
   sur --accent (ADR-010 §4 — palette unifiée, aucun re-theming persona). Les
   tons d'action (warning/info/success) sont les tokens sémantiques durcis AA.
   Mobile-first ; réutilise .bento / .bento__col--7/5, .badge, .btn, .avatar,
   .section__header, .icon. Aucun style inline.
   =========================================================================== */

/* — En-tête « Bonjour … » -------------------------------------------------- */
.majournee__head { margin-bottom: var(--space-6); }
.majournee__date {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-dim);
}
.majournee__hello {
    margin: var(--space-1) 0 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: var(--text-2xl);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.majournee__hello em { font-style: italic; color: var(--accent-text); }
.majournee__intro { margin: var(--space-2) 0 0; color: var(--text-dim); font-size: var(--text-sm); }

/* — Grille 7/5 (réutilise .bento__col défini §X) --------------------------- */
.majournee__grid { align-items: start; }
.majournee__main,
.majournee__rail { display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }

/* — Intervention du jour --------------------------------------------------- */
.art-today {
    position: relative;
    overflow: hidden;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
}
.art-today__accent {
    display: block;
    height: 4px;
    background: var(--accent);
}
.art-today__body { padding: var(--space-6); }
.art-today__body--empty { color: var(--text-dim); }
.art-today__lead { min-width: 0; }
.art-today__kicker {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--accent-text);
}
.art-today__kicker .icon { color: var(--accent); }
.art-today__chantier {
    margin: var(--space-2) 0 0;
    font-family: var(--font-display);
    font-weight: 400;
    font-size: var(--text-2xl);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
.art-today__chantier--empty { font-size: var(--text-xl); color: var(--text-soft); }
.art-today__task { margin: var(--space-1) 0 0; font-size: var(--text-sm); color: var(--text-soft); }
.art-today__facts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    margin: var(--space-4) 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--text-sm);
    color: var(--text-soft);
}
.art-today__facts li { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.art-today__facts .icon { color: var(--text-dim); }

/* contact référent */
.art-today__contact {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin: var(--space-5) 0 0;
    padding: var(--space-2) var(--space-3);
    background: var(--bg-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    width: fit-content;
    max-width: 100%;
}
.art-today__contact-id { display: flex; flex-direction: column; line-height: var(--leading-tight); min-width: 0; }
.art-today__contact-name { font-size: var(--text-xs); font-weight: 600; color: var(--text); }
.art-today__contact-role { font-size: var(--text-2xs); color: var(--text-dim); }
.art-today__call { margin-left: var(--space-1); }
.art-today__call .icon { color: var(--accent); }

.art-today__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin: var(--space-6) 0 0;
}

/* — À traiter aujourd'hui -------------------------------------------------- */
.art-actions__empty,
.art-rail-card__empty {
    margin: 0;
    padding: var(--space-5);
    font-size: var(--text-sm);
    color: var(--text-dim);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.art-actions__list {
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    box-shadow: var(--shadow-sm);
}
.art-action-row + .art-action-row { border-top: 1px solid var(--border-subtle); }
.art-action-row__link {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    text-decoration: none;
    color: inherit;
    transition: background var(--dur-fast) var(--ease);
}
.art-action-row__link:hover { background: var(--bg-sunken); }
.art-action-row__icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius);
    background: var(--bg-sunken);
    color: var(--text-dim);
}
.art-action-row--warning .art-action-row__icon { color: var(--warning); background: var(--warning-bg); }
.art-action-row--info    .art-action-row__icon { color: var(--info);    background: var(--info-bg); }
.art-action-row--success .art-action-row__icon { color: var(--success); background: var(--success-bg); }
.art-action-row--error   .art-action-row__icon { color: var(--error);   background: var(--error-bg); }
.art-action-row__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.art-action-row__title {
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.art-action-row__meta { font-size: var(--text-xs); color: var(--text-dim); }
.art-action-row__chantier { color: var(--text-soft); }
.art-action-row__pill { flex: none; align-self: center; }
.art-action-row__chevron {
    flex: none;
    align-self: center;
    color: var(--text-dim);
    transition: transform var(--dur-fast) var(--ease);
}
.art-action-row__link:hover .art-action-row__chevron { transform: translateX(2px); }

/* — Cartes de rail (semaine, chantiers, devis) ----------------------------- */
.art-week,
.art-rail-card,
.art-money {
    padding: var(--space-5);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
}
.section__title--sm { font-family: var(--font-display); font-size: var(--text-lg); }

/* — Agenda de la semaine --------------------------------------------------- */
.art-week__list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.art-week__row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2);
    border-radius: var(--radius);
}
.art-week__row--today { background: var(--accent-soft); }
.art-week__date {
    flex: none;
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--paper);
    color: var(--text-soft);
}
.art-week__row--today .art-week__date {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--text-invert);
}
.art-week__dow { font-size: var(--text-2xs); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tracking-wide); line-height: 1; }
.art-week__dom { font-family: var(--font-mono); font-size: var(--text-base); font-weight: 600; line-height: 1.1; }
.art-week__info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.art-week__info--free { justify-content: center; }
.art-week__title { font-size: var(--text-sm); font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.art-week__title--free { color: var(--text-mute); font-weight: 400; }
.art-week__chantier { font-size: var(--text-xs); color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.art-week__time { flex: none; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-dim); }

/* — Mini-liste chantiers --------------------------------------------------- */
.art-chantiers { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.art-chantiers__link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2);
    border-radius: var(--radius);
    text-decoration: none;
    color: inherit;
    transition: background var(--dur-fast) var(--ease);
}
.art-chantiers__link:hover { background: var(--bg-sunken); }
.art-chantiers__bar { flex: none; width: 3px; align-self: stretch; min-height: 2rem; border-radius: var(--radius-full); background: var(--accent); }
.art-chantiers__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.art-chantiers__name { font-size: var(--text-sm); font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.art-chantiers__meta { font-size: var(--text-xs); color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.art-chantiers__alert { flex: none; width: 0.5rem; height: 0.5rem; border-radius: var(--radius-full); background: var(--warning); }
.art-chantiers__chevron { flex: none; color: var(--text-dim); }

/* — Synthèse devis (substitut « Facturation du mois ») --------------------- */
.art-money__all {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--accent-text);
    text-decoration: none;
    white-space: nowrap;
}
.art-money__all:hover { text-decoration: underline; }
.art-money__all .icon { width: 0.9rem; height: 0.9rem; }
.art-money__stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin: var(--space-4) 0 0; }
.art-money__stat dt { font-size: var(--text-xs); color: var(--text-dim); }
.art-money__value { margin: var(--space-1) 0 0; font-family: var(--font-mono); font-size: var(--text-lg); font-weight: 600; color: var(--text); }
.art-money__value--pending { color: var(--text-soft); }
.art-money__value--ok { color: var(--success); }
.art-money__note {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-4) 0 0;
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--warning);
}
.art-money__note .icon { width: 0.9rem; height: 0.9rem; }
.art-money__note--muted { color: var(--text-dim); font-weight: 400; }

/* — F1 — Pointage d'arrivée (sur la carte « Intervention du jour ») --------- */
.art-checkin { margin: var(--space-5) 0 0; }
.art-checkin__form { margin: 0; }
.art-checkin__done {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--success);
    background: var(--success-bg);
    border: 1px solid var(--success);
    border-radius: var(--radius-md);
}
.art-checkin__done .icon { color: var(--success); }

/* — F1 — Checklist du jour ------------------------------------------------- */
.art-checklist {
    margin: var(--space-5) 0 0;
    padding-top: var(--space-5);
    border-top: 1px solid var(--border-subtle);
}
.art-checklist__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
.art-checklist__title {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text);
}
.art-checklist__title .icon { color: var(--text-dim); }
.art-checklist__count {
    flex: none;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--text-soft);
}
.art-checklist__list { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.art-checklist__item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2);
    border-radius: var(--radius);
    list-style: none; /* défense : certains resets globaux re-affichent la puce */
}
.art-checklist__item::marker { content: ''; } /* aucun marqueur, robuste cross-navigateur */
.art-checklist__item:hover { background: var(--bg-sunken); }
/* Les <form> wrappers ne doivent pas créer de boîte : display:contents fait
   remonter leurs <button> comme flex-items directs du <li> → case + label +
   poubelle restent sur UNE ligne (sinon les formulaires s'empilent). */
.art-checklist__toggle,
.art-checklist__del { margin: 0; display: contents; }
.art-checklist__check,
.art-checklist__rm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 4px;
    background: none;
    border: none;
    border-radius: var(--radius);
    color: var(--text-dim);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.art-checklist__check { color: var(--text-soft); }
.art-checklist__item--done .art-checklist__check { color: var(--success); }
.art-checklist__check:hover { color: var(--accent-text); background: var(--bg-elevated); }
.art-checklist__rm:hover { color: var(--error); background: var(--error-bg); }
.art-checklist__check:focus-visible,
.art-checklist__rm:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.art-checklist__label { flex: 1 1 auto; min-width: 0; font-size: var(--text-sm); color: var(--text); overflow-wrap: anywhere; }
.art-checklist__item--done .art-checklist__label { text-decoration: line-through; color: var(--text-dim); }
.art-checklist__rm { margin-left: auto; }
.art-checklist__empty { margin: var(--space-3) 0 0; font-size: var(--text-sm); color: var(--text-dim); }
.art-checklist__add { display: flex; gap: var(--space-2); margin: var(--space-4) 0 0; }
.art-checklist__input {
    flex: 1 1 auto;
    min-width: 0;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-sm);
    color: var(--text);
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.art-checklist__input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.art-checklist__add .btn { flex: none; }

/* — Responsive : sur étroit, l'intervention condense ses actions ----------- */
@media (max-width: 559px) {
    .art-today__body { padding: var(--space-5); }
    .art-today__actions .btn { flex: 1 1 100%; }
    .art-checklist__add { flex-wrap: wrap; }
    .art-checklist__add .btn { flex: 1 1 100%; }
}

/* ============================================================================
   §21 MESSAGERIE — WIDGET FLOTTANT (FAB + panneau peek)
   ----------------------------------------------------------------------------
   DA Atelier v2 (ADR-010) : bouton géométrique stroke 1.5 currentColor, pastille
   non-lu = .badge--unread (couleurs + dark mode + WCAG déjà conformes, cf. §13).
   Distinct de la cloche topbar (.topbar__notif), ici FLOTTANT bas-droite.
   Partial : views/partials/messages_fab.php — JS : public/assets/js/messages-fab.js.
   Mobile-first ; cible tactile ≥ 44px ; prefers-reduced-motion respecté.
   z-index : topbar=50, skip-link=200 → FAB sous le skip-link, au-dessus du flux.
   ============================================================================ */
.msg-fab {
    position: fixed;
    /* safe-area iOS : décolle le FAB de la barre home / des bords arrondis. */
    right: calc(var(--space-5) + env(safe-area-inset-right, 0px));
    bottom: calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
    z-index: 90;
}

/* — Bouton flottant ------------------------------------------------------- */
.msg-fab__btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Cible tactile généreuse (≥ 44px largement dépassé). */
    width: 56px;
    height: 56px;
    border-radius: var(--radius-full);
    background: var(--accent);
    color: var(--on-accent);
    border: 1px solid var(--accent);
    box-shadow: var(--shadow-lg);
    cursor: pointer;
    transition: transform var(--dur) var(--ease),
                box-shadow var(--dur) var(--ease),
                background var(--dur) var(--ease);
}
.msg-fab__btn:hover {
    color: var(--on-accent);
    text-decoration: none;
    transform: translateY(-2px);
    box-shadow: var(--shadow-xl);
}
.msg-fab__btn:focus-visible {
    outline: none;
    /* Anneau net + halo : distingue clavier de hover (A8), contraste sur fond clay. */
    box-shadow: var(--shadow-lg), 0 0 0 3px var(--paper), 0 0 0 6px var(--accent);
}
.msg-fab__icon {
    display: inline-flex;
    width: 26px;
    height: 26px;
}
.msg-fab__icon svg { display: block; width: 100%; height: 100%; }

/* — Pastille compteur (réutilise .badge--unread) -------------------------- */
.msg-fab__count {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 20px;
    height: 20px;
    padding: 0 5px;
    font-size: var(--text-2xs);
    line-height: 18px;
    letter-spacing: 0;
    border-radius: var(--radius-full);
    /* Anneau de séparation pour décoller du fond clay du bouton (light + dark). */
    box-shadow: 0 0 0 2px var(--paper);
    animation: msg-fab-badge-in var(--dur) var(--ease-out);
}
.msg-fab__count.is-empty { display: none; }
@keyframes msg-fab-badge-in {
    from { opacity: 0; transform: scale(0.6); }
    to   { opacity: 1; transform: scale(1); }
}

/* — Panneau d'aperçu (disclosure non-modal) -------------------------------- */
.msg-fab__panel {
    position: absolute;
    right: 0;
    bottom: calc(56px + var(--space-3));
    width: min(360px, calc(100vw - 2 * var(--space-4)));
    max-height: min(70vh, 540px);
    display: flex;
    flex-direction: column;
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xl);
    overflow: hidden;
    transform-origin: bottom right;
    /* L'animation d'entrée rejoue à chaque passage display:none → flex (ouverture). */
    animation: msg-fab-panel-in var(--dur) var(--ease-out);
}
/* État FERMÉ — INDISPENSABLE. messages-fab.js bascule l'attribut [hidden] pour
   ouvrir/fermer. Sans cette règle, le `display:flex` ci-dessus écrase le
   `display:none` de l'attribut [hidden] (stylesheet user-agent) : le panneau
   resterait visible en permanence (état "Chargement…" figé) et IMPOSSIBLE à
   fermer. Même garde que .tabs__panel / .modal--legacy / .project-card. */
.msg-fab__panel[hidden] { display: none; }
@keyframes msg-fab-panel-in {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

.msg-fab__panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--border-subtle);
    flex: none;
}
.msg-fab__panel-title {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--text-soft);
}
.msg-fab__close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin: calc(-1 * var(--space-1)) calc(-1 * var(--space-2));
    padding: 0;
    font-size: 1.25rem;
    line-height: 1;
    background: none;
    border: none;
    border-radius: var(--radius);
    color: var(--text-dim);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.msg-fab__close:hover { color: var(--text); background: var(--bg-sunken); }
.msg-fab__close:focus-visible { outline: none; box-shadow: var(--ring-focus); color: var(--text); }

.msg-fab__body {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--space-2);
}

/* — Liste des fils -------------------------------------------------------- */
.msg-fab__list { list-style: none; margin: 0; padding: 0; }
.msg-fab__item { margin: 0; }
.msg-fab__link {
    display: block;
    padding: var(--space-3) var(--space-3);
    border-radius: var(--radius-md);
    color: var(--text);
    transition: background var(--dur-fast) var(--ease);
}
.msg-fab__link:hover { background: var(--bg-sunken); text-decoration: none; }
.msg-fab__link:focus-visible { outline: none; background: var(--bg-sunken); box-shadow: var(--ring-focus); }

.msg-fab__item-top {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.msg-fab__dot {
    flex: none;
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    background: var(--accent);
}
.msg-fab__subject {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--text-sm);
    font-weight: 500;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.msg-fab__item--unread .msg-fab__subject { font-weight: 700; }

.msg-fab__meta {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-top: 2px;
    /* Aligne la méta sous le sujet (compense le point + gap de la ligne 1). */
    padding-left: 0;
    font-size: var(--text-2xs);
    color: var(--text-dim);
    min-width: 0;
}
.msg-fab__project {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.msg-fab__time { flex: none; }
.msg-fab__time::before {
    content: "·";
    margin-right: var(--space-2);
    color: var(--text-mute);
}
.msg-fab__closed {
    flex: none;
    font-family: var(--font-mono);
    font-size: 0.9em;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--text-mute);
}

/* — États : loading / empty / error --------------------------------------- */
.msg-fab__loading {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-6) var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-dim);
}
.msg-fab__spinner {
    flex: none;
    width: 16px;
    height: 16px;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: var(--radius-full);
    animation: msg-fab-spin 0.7s linear infinite;
}
@keyframes msg-fab-spin { to { transform: rotate(360deg); } }

.msg-fab__empty { padding: var(--space-7) var(--space-5); text-align: center; }
.msg-fab__empty-title { margin: 0 0 var(--space-1); font-size: var(--text-sm); font-weight: 600; color: var(--text-soft); }
.msg-fab__empty-text { margin: 0; font-size: var(--text-xs); color: var(--text-dim); }

.msg-fab__error { padding: var(--space-6) var(--space-5); text-align: center; }
.msg-fab__error-text { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--text-soft); }

/* — Mobile : sous 480px, le panneau s'élargit jusqu'aux marges ------------- */
@media (max-width: 480px) {
    .msg-fab {
        right: calc(var(--space-4) + env(safe-area-inset-right, 0px));
        bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
    }
    .msg-fab__panel {
        /* Ancré au coin du FAB mais quasi pleine largeur (marges symétriques). */
        width: calc(100vw - 2 * var(--space-4));
        right: calc(-1 * var(--space-px));
    }
}

/* — Reduced motion : on neutralise toutes les animations/transition --------- */
@media (prefers-reduced-motion: reduce) {
    .msg-fab__btn,
    .msg-fab__close,
    .msg-fab__link { transition: none; }
    .msg-fab__btn:hover { transform: none; }
    .msg-fab__count,
    .msg-fab__panel { animation: none; }
    .msg-fab__spinner { animation-duration: 1.5s; } /* ralenti, jamais figé (info de progression) */
}

/* — Dark mode : l'anneau de séparation suit le fond, surfaces cohérentes ---- */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .msg-fab__count { box-shadow: 0 0 0 2px var(--accent); }
    :root:not([data-theme="light"]) .msg-fab__btn:focus-visible {
        box-shadow: var(--shadow-lg), 0 0 0 3px var(--paper), 0 0 0 6px var(--accent-text);
    }
}

/* ============================================================================
   45. WIDGET (S23 Lot 1 — <details> générique)
   ----------------------------------------------------------------------------
   Composant de disclosure générique basé sur <details>/<summary> natifs.
   Factorise les 4 variantes locales historiques : .gantt__add, .invite,
   .invoices__payment-history, .messages__create (mapping ancien → nouveau et
   snippet HTML de référence : docs/UI-DESIGN-SYSTEM.md §7).

   Structure :
     <details class="widget">
       <summary class="widget__summary">
         <span class="widget__title">Titre du widget</span>
         <span class="widget__count">4</span>
         <span class="widget__meta">Mis à jour hier</span>
       </summary>
       <div class="widget__body">... contenu ...</div>
     </details>

   Modificateurs :
     .widget--flush : sans chrome de carte (padding/bordure/fond) — pour un
       widget déjà posé dans un conteneur qui porte sa propre surface (ex :
       cellule de tableau `.invoices__payment-history-cell`).
     .widget--lot : en-tête de lot de travaux (S26 Lot 4, ADR-0027 §D4).
       Enrichi ci-dessous : compteur de devis, badge « Marché signé »
       (`.lot__signed`), table compacte des devis du lot (`.lot__table`,
       `.lot__row--signed`), outils repliés (`.lot__tools`).
     .widget--muted : combiné à `.widget--lot` pour la section « Sans lot »
       (rail neutre au lieu de l'accent — un lot sans entreprise attributaire
       n'est pas une information à mettre en avant visuellement).

   S27 Lot 5 (ADR-0028) : `.widget--lot` est RÉUTILISÉ TEL QUEL (aucun
   `.widget--source` créé) pour grouper les plans par provenance (architecte,
   BET, structure…) sur `/app/chantiers/{slug}/plans` — même anatomie qu'un
   widget de lot de travaux (rail, compteur, groupe muted en dernier), seule
   la table interne change (`.plans__source-table`, §22). Détail et snippet :
   docs/UI-DESIGN-SYSTEM.md §7.5 "Lot 5 — plans par provenance".
   ============================================================================ */
.widget {
    margin-top: var(--space-4);
    background: var(--bg-elevated);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-md);
    padding: var(--space-4);
}
.widget + .widget { margin-top: var(--space-3); }

.widget__summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
    list-style: none;
    /* Cible tactile ≥ 44px (WCAG 2.2 — 2.5.8), même si le libellé est court. */
    min-height: 44px;
    padding: var(--space-1) 0;
    font-weight: 500;
    font-size: var(--text-sm);
    color: var(--text);
    user-select: none;
    transition: color var(--dur) var(--ease);
}
/* Neutralisation du marker natif — Chrome/Safari (pseudo dédié) + Firefox/autres (::marker). */
.widget__summary::-webkit-details-marker { display: none; }
.widget__summary::marker { content: ""; }

/* Chevron rotatif porté par ::after (jamais de conflit avec un ::before propre
   à .widget__title, ex : icône). Pivote à l'ouverture ; le rectangle 45°/-135°
   dessine un chevron sans image ni police d'icônes. */
.widget__summary::after {
    content: "";
    flex: none;
    width: 0.45em;
    height: 0.45em;
    margin-left: auto;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--dur) var(--ease);
    color: var(--text-dim);
}
details[open] > .widget__summary::after { transform: rotate(-135deg); }

.widget__summary:hover { color: var(--accent-text); }
.widget__summary:focus-visible {
    outline: none;
    box-shadow: var(--ring-focus);
    border-radius: var(--radius-sm);
}

.widget__title { font-weight: 500; }
.widget__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5em;
    padding: 0 var(--space-1);
    font-family: var(--font-mono);
    font-size: var(--text-2xs);
    font-weight: 600;
    background: var(--bg-sunken);
    color: var(--text-soft);
    border-radius: var(--radius-full);
}
.widget__meta {
    margin-left: var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-dim);
    font-weight: 400;
}

.widget__body { margin-top: var(--space-4); }

/* — Modificateur : sans chrome de carte (conteneur déjà stylé) -------------- */
.widget--flush {
    margin-top: 0;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 0;
}
.widget--flush .widget__summary { font-size: var(--text-xs); font-weight: 500; color: var(--text-soft); }
.widget--flush .widget__body { margin-top: var(--space-2); padding-left: var(--space-4); }

/* — Modificateur : en-tête de lot de travaux (S26 Lot 4, ADR-0027 §D4) ------
   Rail gauche accent, cohérent avec les autres cartes à rail
   (`.chantiers__item::before`, `.metric--link::before`). Le corps
   (`.widget__body`) porte la table compacte des devis du lot (`.lot__table`)
   et les outils repliés (`.lot__tools`). */
.widget--lot {
    border-left: 3px solid var(--accent);
    padding-left: var(--space-4);
}

/* Badge « Marché signé » posé dans `.widget__summary` (après `.widget__count`,
   avant le chevron `::after` qui reste positionné en bout de ligne grâce à
   son propre `margin-left: auto`, indépendant de l'ordre des autres enfants).
   Visuel calqué sur `.badge--success` (§13) mais nommé localement : élément
   du bloc conceptuel « lot » plutôt qu'un badge générique réutilisé tel quel,
   pour rester descriptif côté vue (`grep lot__signed` retrouve l'usage précis). */
.lot__signed {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 2px var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    white-space: nowrap;
    background: var(--success-bg);
    color: var(--success);
    border: 1px solid var(--success-border);
}
@media (prefers-color-scheme: dark) {
    /* Même remap que .badge--success (§13) : le vert plein --success (foncé,
       pensé pour fond clair) devient illisible sur le tint sombre --success-bg
       en dark. On repasse au shade clair, cohérent avec le reste du DS. */
    :root:not([data-theme="light"]) .lot__signed { color: #4ADE80; }
}

/* — Modificateur : lot sans attributaire mis en avant ("Sans lot") ----------
   Combiné à `.widget--lot` (jamais seul) : rail neutre + titre non-gras,
   pour que la section reste visible (toujours affichée, cf. spec) sans
   attirer l'œil comme un lot avec marché en cours. */
.widget--lot.widget--muted {
    border-left-color: var(--border);
}
.widget--lot.widget--muted .widget__title {
    color: var(--text-soft);
    font-weight: 400;
}

/* — Table compacte des devis d'un lot (N° / Titre / Entreprise / TTC /
   Statut / Actions) — même langage que `.quotes__table` (§23) en plus dense
   (padding réduit, pas de <caption>, pensée pour vivre DANS un `.widget__body`
   plutôt qu'en pleine largeur de page). */
.lot__table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}
.lot__table th,
.lot__table td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--border-subtle);
}
.lot__table th {
    font-weight: 500;
    color: var(--text-soft);
    text-transform: uppercase;
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
}
.lot__table tbody tr:last-child td { border-bottom: 0; }
.lot__table tbody tr:hover { background: var(--paper-soft); }

/* Ligne épinglée : le devis approuvé ("Marché signé") en tête de tableau,
   cf. C.2 SPECS-V2-LOTS-2-3-4.md — fond plein, jamais la seule couleur pour
   porter l'info (le badge de statut dans la cellule "Statut" porte le texte). */
.lot__row--signed { background: var(--success-bg); }
.lot__row--signed:hover { background: var(--success-bg); }

/* Cellule Actions : boutons `.btn--xs`/`.btn--ghost` existants, juste l'espacement. */
.lot__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
}

/* — Outils repliés "Nouveau devis dans ce lot" / "Renommer / Archiver" ------
   Disclosure texte sans chrome de carte (déjà DANS un `.widget--lot` posé sur
   une carte — on évite un `.widget` dans un `.widget`, cf. anti-pattern
   "modale dans modale" transposé aux disclosures). Glyphe +/− plutôt que le
   chevron de `.widget__summary` pour se distinguer visuellement du disclosure
   parent (le lecteur d'écran, lui, s'appuie sur l'état natif `[open]`,
   jamais sur le glyphe seul). */
.lot__tools { margin-top: var(--space-3); font-size: var(--text-xs); }
.lot__tools + .lot__tools { margin-top: var(--space-2); }
.lot__tools > summary {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    min-height: 44px;
    padding: var(--space-1) 0;
    font-weight: 500;
    color: var(--text-soft);
    cursor: pointer;
    list-style: none;
    user-select: none;
    transition: color var(--dur) var(--ease);
}
.lot__tools > summary::-webkit-details-marker { display: none; }
.lot__tools > summary::marker { content: ""; }
.lot__tools > summary::before {
    content: "+";
    flex: none;
    font-family: var(--font-mono);
    color: var(--text-dim);
}
.lot__tools[open] > summary::before { content: "\2212"; } /* − (signe moins, pas un tiret) */
.lot__tools > summary:hover { color: var(--accent-text); }
.lot__tools > summary:focus-visible {
    outline: none;
    box-shadow: var(--ring-focus);
    border-radius: var(--radius-sm);
}
.lot__tools .form {
    margin-top: var(--space-2);
    padding: var(--space-3);
    background: var(--bg-sunken);
    border-radius: var(--radius);
}

/* — Bloc "Ajouter un lot" : champ + bouton sur une ligne --------------------
   Posé au-dessus ou en dessous de la pile de `.widget--lot` (page Devis),
   pas à l'intérieur d'un widget existant. */
.lots__add {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding-top: var(--space-4);
    border-top: 1px dashed var(--border);
}
.lots__add .form__field { flex: 1 1 220px; margin-bottom: 0; }

@media (prefers-reduced-motion: reduce) {
    .widget__summary::after { transition: none; }
    .lot__tools > summary { transition: none; }
}

/* ============================================================================
   46. COMPARATIF DCE (S26 Lot 4 — ADR-0027 §D4, docs/sprints/SPECS-V2-LOTS-2-3-4.md §C.4)
   ----------------------------------------------------------------------------
   « Bilan des marchés » : remplace le tableur Excel de l'architecte par une
   matrice lot × entreprise. Architecte seul (route `/app/**`), aucune fuite
   MOA/artisan — pas de contrainte a11y spécifique liée au persona, mais
   surface dense : chaque cellule doit rester lisible sans dépendre de la
   couleur seule (le texte du statut/de la flag porte toujours l'info).

   Structure matrice (desktop + mobile, table toujours scrollable) :
     <div class="table-responsive">
       <table class="comparatif__table">
         <thead><tr>
           <th class="comparatif__lot-head">Lot</th>
           <th>Entreprise A</th>...
         </tr></thead>
         <tbody><tr>
           <th class="comparatif__lot-head" scope="row">Plomberie</th>
           <td><label class="comparatif__offer …">…</label></td>...
         </tr></tbody>
         <tfoot>…lignes .comparatif__totals / .comparatif__fees…</tfoot>
       </table>
     </div>

   Snippet complet + a11y (rôle radiogroup par ligne, fieldset) :
   docs/UI-DESIGN-SYSTEM.md §7.5.
   ============================================================================ */
.comparatif { display: block; margin-bottom: var(--space-8); }
.comparatif__header {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--border);
}

/* — Matrice ------------------------------------------------------------------
   `.table-responsive` (§20, wrapper générique déjà existant) gère le scroll
   horizontal + l'ombre indicatrice. On y ajoute UNIQUEMENT `overflow-y: clip`
   ci-dessous, scopé au comparatif : `overflow-x: auto` seul force par la
   spec CSS `overflow-y` à `auto` (règle "visible→auto si l'autre axe ne l'est
   pas"), ce qui ferait de `.table-responsive` le conteneur défilant vertical
   et casserait le sticky (position: sticky se figerait DANS cette boîte, qui
   n'a jamais de hauteur contrainte donc jamais rien à "coller"). `clip` lève
   la conversion implicite (aucun des deux axes n'est `visible`) sans créer de
   scrollport vertical : le sticky redevient relatif au scroll de la page.
   Dégradation gracieuse : navigateur sans support de `overflow-y: clip`
   (Chrome < 90, Firefox < 113, Safari < 16.4) ignore la déclaration → même
   comportement qu'avant (tableau lisible, juste sans en-tête figé). */
.comparatif .table-responsive {
    overflow-y: clip;
    margin-bottom: var(--space-5);
}
.comparatif__table {
    width: 100%;
    /* separate + border-spacing:0 plutôt que collapse : position:sticky sur
       des cellules de table avec border-collapse:collapse perd ses bordures
       dans plusieurs moteurs — contournement standard. */
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
}
.comparatif__table th,
.comparatif__table td {
    padding: var(--space-3);
    text-align: left;
    vertical-align: top;
    border-bottom: 1px solid var(--border-subtle);
    background: var(--bg-elevated);
}
.comparatif__table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    font-weight: 500;
    color: var(--text-soft);
    text-transform: uppercase;
    font-size: var(--text-2xs);
    letter-spacing: var(--tracking-wide);
    background: var(--bg-sunken);
    white-space: nowrap;
}
/* Première colonne (lot) : sticky à gauche. Le coin haut-gauche cumule les
   deux positions sticky (`top` ET `left`) — z-index le plus élevé du tableau
   pour rester au-dessus de la ligne d'en-tête ET de la colonne lot. */
.comparatif__lot-head {
    position: sticky;
    left: 0;
    z-index: 1;
    font-weight: 500;
    white-space: nowrap;
    background: var(--bg-elevated);
}
.comparatif__table thead th.comparatif__lot-head {
    z-index: 3;
    background: var(--bg-sunken);
}

/* — Cellule offre ------------------------------------------------------------
   `.comparatif__offer` habille TOUJOURS un enfant de `<td>` (jamais le `<td>`
   lui-même) : `<label>` interactif (offre sélectionnable) ou `<div>` statique
   (refusée / vide) — même boîte visuelle dans les deux cas. */
.comparatif__offer {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 9rem;
    padding: var(--space-2);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius);
}
label.comparatif__offer {
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
label.comparatif__offer:hover { border-color: var(--border-strong); }
label.comparatif__offer:focus-within {
    outline: none;
    box-shadow: var(--ring-focus);
}
.comparatif__offer input[type="radio"] {
    width: 18px;
    height: 18px;
    margin-bottom: var(--space-1);
    accent-color: var(--accent);
}
.comparatif__offer-amount { font-weight: 600; color: var(--text); }
.comparatif__offer-ttc { font-size: var(--text-xs); color: var(--text-dim); }
.comparatif__offer-meta { font-size: var(--text-2xs); color: var(--text-dim); }

/* Moins-disant : fond + bordure --success, ET texte "Moins-disant" explicite
   (jamais la couleur seule, cf. checklist a11y). */
.comparatif__offer--lowest {
    background: var(--success-bg);
    border-color: var(--success);
}
@media (prefers-color-scheme: dark) {
    /* --success (vert foncé, pensé fond clair) perd son contraste sur le tint
       sombre --success-bg : on repasse à --success-border (déjà un tint clair
       translucide en dark, cf. §1) pour une bordure lisible sur les 2 thèmes. */
    :root:not([data-theme="light"]) .comparatif__offer--lowest { border-color: var(--success-border); }
}
.comparatif__offer-flag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-top: var(--space-1);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
    color: var(--success);
}
.comparatif__offer-flag::before { content: "\2605"; } /* ★ décoratif, doublé du texte du flag */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .comparatif__offer-flag { color: #4ADE80; } /* même remap que .badge--success, §13 */
}

/* Sélection retenue (radio coché) : bordure accent 2px. Le padding compense
   le gain d'épaisseur de bordure pour ne pas décaler le contenu au clic. */
.comparatif__offer--selected {
    border-width: 2px;
    border-color: var(--accent);
    padding: calc(var(--space-2) - 1px);
}

/* Refusée : non sélectionnable (simple <div>, jamais de <label>/<input> —
   pas de tabindex fantôme), grisée + texte biffé (pas la couleur seule). */
.comparatif__offer--refused { opacity: 0.55; }
.comparatif__offer--refused .comparatif__offer-meta { text-decoration: line-through; }

/* Lot sans offre reçue : cellule vide centrée, "—" décoratif doublé d'un
   texte accessible (`.sr-only`, §19) porté par la vue. */
.comparatif__offer--empty {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 3rem;
    color: var(--text-dim);
    font-size: var(--text-sm);
}

/* — Pied de tableau : totaux, honoraires, total général --------------------- */
.comparatif__table tfoot th,
.comparatif__table tfoot td {
    background: var(--bg-sunken);
    border-bottom: 0;
    border-top: 1px solid var(--border);
}
.comparatif__totals th { font-weight: 500; color: var(--text-soft); }
.comparatif__totals td {
    text-align: right;
    font-family: var(--font-mono);
    color: var(--text);
}
.comparatif__totals--grand th,
.comparatif__totals--grand td {
    font-weight: 700;
    font-size: var(--text-base);
    color: var(--text);
    border-top: 2px solid var(--border-strong);
}
.comparatif__fees th {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-weight: 500;
    color: var(--text-soft);
}
.comparatif__fees-input {
    width: 4.5rem;
    min-height: 36px;
    padding: var(--space-1) var(--space-2);
    text-align: right;
    font-family: var(--font-mono);
}
.comparatif__fees td {
    text-align: right;
    font-family: var(--font-mono);
    color: var(--text-soft);
}

/* — Barre d'actions ----------------------------------------------------------
   Enregistrer les choix (primary) / Export CSV / Export PDF (ghost) — une
   seule action primaire par écran (cf. principe directeur designer). */
.comparatif__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-bottom: var(--space-6);
}

/* — Vue liste mobile < 768px (sans JS) ---------------------------------------
   Duplique les MÊMES radios (même `name` par lot) dans un layout empilé plus
   pouce-compatible que la matrice scrollable. Les deux vues restent
   synchronisées nativement : un `name` de groupe radio est partagé par tout
   le document, indépendamment de l'endroit où chaque `<input>` est rendu.
   Mobile-first mais SEUIL DÉLIBÉRÉMENT DIFFÉRENT de la grille responsive
   habituelle (§20 utilise 721px) : 768px est la valeur demandée par la spec
   produit (docs/sprints/SPECS-V2-LOTS-2-3-4.md §C.4) pour ce composant précis. */
.comparatif__list { display: block; margin-bottom: var(--space-6); }
@media (min-width: 768px) {
    .comparatif__list { display: none; }
}
.comparatif__list-offers {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}
.comparatif__list-offers .comparatif__offer { width: 100%; }

/* — Notes libres par entreprise -----------------------------------------------
   Un `<details class="widget">` (§45) par entreprise, replié par défaut
   (pas d'attribut `open`) : la note est un détail consultable à la demande,
   pas une information de premier plan de l'écran. */
.comparatif__notes { margin-top: var(--space-4); }

@media (prefers-reduced-motion: reduce) {
    label.comparatif__offer { transition: none; }
}
