/*
 * Dashboard V2 — theme cohérent inspiré du frontend Next.js
 * (palette HSL, dark mode, spacing tokens, typo tabular-nums).
 *
 * Tous les composants V2 (KpiTile, ChartCard, DataTable, ...) consomment
 * ces variables. Pour passer en dark mode : data-theme="dark" sur <html>.
 */

:root {
    /* Palette officielle eZengo : --bs-primary du theme Tabler legacy = #00bac7
       (cyan brand). On garde la même couleur pour V2 pour assurer la cohérence
       visuelle entre les deux interfaces (boutons cyan, badges, liens). */
    --v2-primary-50: 184 95% 96%;   /* fond très pâle pour hover state */
    --v2-primary-100: 184 80% 88%;  /* badge background subtil */
    --v2-primary-200: 184 78% 78%;  /* bordures cyan claires */
    --v2-primary-300: 184 76% 64%;  /* accent cyan clair sur fond sombre (sidebar) */
    --v2-primary-500: 184 100% 39%; /* #00bac7 ezengo brand cyan */
    --v2-primary-600: 184 100% 33%; /* hover plus foncé */
    --v2-primary-700: 184 100% 27%;
    --v2-primary-800: 184 100% 25%; /* encre AA (4,60:1) sur le muted-bg CHAUD du thème luxury,
                                       qui redéfinit le token : mesuré au rendu, pas sur la valeur déclarée */

    --v2-success-50: 142 76% 95%;
    --v2-success-500: 142 71% 45%; /* #22c55e */
    --v2-success-600: 142 76% 36%;
    --v2-success-700: 142 72% 29%; /* vert sombre, contraste AA (≥4,5:1) sur blanc pour petit texte gras */

    --v2-danger-50: 0 86% 95%;
    --v2-danger-500: 0 84% 60%; /* #ef4444 */
    --v2-danger-600: 0 72% 51%;
    --v2-danger-700: 0 72% 47%;   /* encre AA (4,59:1) sur danger-50 */

    /* ─── Brique : « bas mais honnête », jamais une erreur ───
       Règle maison : le ROUGE (--v2-danger) est réservé aux VRAIES erreurs.
       Une valeur simplement basse ou en baisse — un taux d'acceptation
       médiocre, un delta KPI négatif, un segment qui décroche — se colore en
       BRIQUE : on désature la couleur, on ne baisse pas le seuil.
       Cette teinte était recopiée à la main dans 15 règles (tickers, jauges,
       pastilles, chips, radars) sans jamais porter de nom : impossible de la
       faire évoluer d'un seul geste, et rien n'empêchait une 16e règle de
       dériver d'un ou deux points. Elle est désormais un jeton. */
    --v2-brick: 8 50% 44%;        /* encre : texte, chiffres, pourcentages */
    --v2-brick-500: 8 52% 50%;    /* aplat : points, segments de jauge */
    --v2-brick-50: 10 45% 96%;    /* fond de chip très clair */

    --v2-warning-50: 38 92% 95%;
    --v2-warning-500: 38 92% 50%; /* #f59e0b */
    --v2-warning-600: 32 95% 44%;
    --v2-warning-800: 26 90% 26%; /* texte amber sombre, contraste AA sur warning-50 */

    /* Palette « qualité » de l'onglet Complétude — alignée sur les statuts --dc
       (valide = vert complet, bidon = ambre partiel). -ink = variante texte (contraste). */
    --v2-dc-valid: 152 56% 34%;
    --v2-dc-valid-ink: 152 56% 30%;
    --v2-dc-junk: 32 86% 40%;

    --v2-info-50: 199 89% 95%;
    --v2-info-500: 199 89% 48%; /* #0ea5e9 */
    --v2-info-600: 200 98% 39%;
    --v2-info-700: 200 98% 31%;   /* encre AA (5,44:1) sur info-50 */

    /* Surfaces neutres (light mode) */
    --v2-bg: 210 20% 98%;       /* #f8fafc fond page */
    --v2-card: 0 0% 100%;       /* blanc cards */
    --v2-foreground: 222 47% 11%; /* slate-900 texte */
    --v2-muted: 215 16% 47%;    /* slate-500 texte secondaire */
    --v2-border: 220 13% 91%;   /* slate-200 bordures */
    --v2-muted-bg: 210 20% 96%; /* slate-100 fond muted */

    /* === Strict spacing scale (Axe 2) ===
       Premium dashboards (Stripe, Linear, Vercel) use a fixed 4px scale.
       NEVER use ad-hoc values like 0.45rem or 13px — only these tokens.
       Naming follows Tailwind for muscle-memory familiarity. */
    --v2-space-1: 0.25rem;   /*  4px */
    --v2-space-2: 0.5rem;    /*  8px */
    --v2-space-3: 0.75rem;   /* 12px */
    --v2-space-4: 1rem;      /* 16px — base */
    --v2-space-5: 1.25rem;   /* 20px */
    --v2-space-6: 1.5rem;    /* 24px */
    --v2-space-8: 2rem;      /* 32px */
    --v2-space-10: 2.5rem;   /* 40px */
    --v2-space-12: 3rem;     /* 48px */
    --v2-space-16: 4rem;     /* 64px */

    /* === Échelle typographique (audit 2026-07-25) ===
       MÊME RÈGLE QUE LES ESPACEMENTS CI-DESSUS : jamais de valeur ad hoc,
       uniquement ces barreaux.

       Elle manquait, et ça se voyait. Le fichier portait 57 tailles
       distinctes sur 367 déclarations — dont SEIZE tassées entre 0.6875rem
       et 0.95rem, soit 11 px à 15 px. Ces nombres bâtards (0.72, 0.74, 0.78,
       0.82, 0.84, 0.86…) trahissaient leur origine : des multiplicateurs
       choisis composant par composant, jamais une échelle. Sur une même page,
       l'œil ne lit pas « une autre police » — il lit six tailles presque
       identiques mais pas tout à fait, et trouve ça bancal sans savoir dire
       pourquoi.

       Les barreaux ne sont pas inventés : ce sont les quatre valeurs déjà
       DOMINANTES du fichier (13 px, 14 px, 12 px, 11 px — 139 déclarations à
       elles seules), prolongées vers le haut et vers le bas là où il faut de
       la résolution. Le pas d'1 px est volontaire dans la bande 9-14 px : ce
       backoffice affiche de la donnée dense, c'est là que vivent les trois
       quarts de son texte, et un pas géométrique y écraserait des
       distinctions utiles. Au-dessus de 16 px, où chaque taille sert un rôle
       unique (wordmark, anneau de jauge, valeur Live), les barreaux
       s'espacent.

       Nommage numérique = la valeur en pixels, comme --v2-space-N. */
    --v2-text-9: 0.5625rem;   /*  9px — suffixe d'unité, badge micro */
    --v2-text-10: 0.625rem;   /* 10px */
    --v2-text-11: 0.6875rem;  /* 11px — micro-libellés, capitales espacées */
    --v2-text-12: 0.75rem;    /* 12px — méta, sous-lignes de cellule */
    --v2-text-13: 0.8125rem;  /* 13px — contenu de tableau (la + fréquente) */
    --v2-text-14: 0.875rem;   /* 14px — texte courant */
    --v2-text-16: 1rem;       /* 16px — titres de section */
    --v2-text-18: 1.125rem;   /* 18px */
    --v2-text-20: 1.25rem;    /* 20px — valeur de tuile KPI */
    --v2-text-22: 1.375rem;   /* 22px */
    --v2-text-24: 1.5rem;     /* 24px */
    --v2-text-28: 1.75rem;    /* 28px — wordmark */
    --v2-text-32: 2rem;       /* 32px — anneau de jauge, hero */

    /* === Strict border tokens (Axe 2) ===
       3 levels — never anything else. Subtle for inside-card dividers,
       default for card borders, strong for emphasis (focused row). */
    --v2-border-subtle: 220 13% 95%;
    --v2-border-default: 220 13% 91%;
    --v2-border-strong: 220 13% 80%;

    /* === Strict radius tokens (Axe 2) ===
       3 levels: sm for chips/badges, md for buttons/inputs/small cards,
       lg for big cards. Premium = consistency, not variety. */
    /* ⛔ Les deux barreaux du bas et la pastille MANQUAIENT (audit 2026-07-25).
       L'échelle démarrait à 6px alors que 47 déclarations utilisaient 1 à 5px —
       et pour de bonnes raisons : barres de jauge, pistes de progression,
       marques de légende. Un rayon de 6px sur une barre haute de 3px la
       transforme en pilule. Les auteurs contournaient donc l'échelle parce
       qu'elle ne descendait pas assez bas, pas par négligence.
       La pastille, elle, comptait 53 usages (999px et 9999px mêlés) sans
       aucun token — le cas le plus fréquent du fichier était le seul non
       nommé.
       ⚠ Les valeurs de sm/md/lg/xl sont INCHANGÉES : 99 usages en dépendent. */
    --v2-radius-2xs: 2px;  /* marques de légende, segments de jauge */
    --v2-radius-xs: 4px;   /* pistes et remplissages de barres fines */
    --v2-radius-sm: 6px;   /* small inline elements */
    --v2-radius-md: 8px;   /* buttons, inputs, small cards */
    --v2-radius-lg: 12px;  /* large cards, modals */
    --v2-radius-xl: 16px;  /* hero cards (rare) */
    --v2-radius-full: 999px; /* pastille : badges, barres, avatars */

    /* Backwards-compat alias — the codebase already uses --v2-radius. */
    --v2-radius: var(--v2-radius-md);

    /* === Strict shadow tokens (Axe 2) ===
       Premium dashboards barely use shadows — ONE soft shadow for elevation,
       a stronger one for modals/popovers, that's it. The trick is using
       borders + background contrast instead of shadows for visual depth. */
    --v2-shadow-xs: 0 1px 2px 0 rgb(15 23 42 / 0.04);

    /* Couleurs-types des alertes rebill (cycle / drop / chronic), nommées
       pour sortir du hard-code des pills d'alerte (audit theming). */
    --v2-alert-cycle: 343 80% 35%;
    --v2-alert-drop: 26 91% 37%;
    --v2-alert-chronic: 224 64% 33%;

    /* Échelle z-index nommée — remplace les littéraux 9999/9998. Tout le
       reste de la feuille plafonne à 1080, donc toasts/pills passent juste
       au-dessus, sans changement d'empilement. */
    --v2-z-sticky: 100;
    --v2-z-dropdown: 1000;
    --v2-z-modal: 1080;
    --v2-z-pill: 1085;
    --v2-z-toast: 1090;
    --v2-shadow-sm: 0 1px 3px 0 rgb(15 23 42 / 0.06), 0 1px 2px -1px rgb(15 23 42 / 0.04);
    --v2-shadow-md: 0 4px 12px -2px rgb(15 23 42 / 0.08), 0 2px 4px -2px rgb(15 23 42 / 0.04);
    --v2-shadow-lg: 0 12px 32px -8px rgb(15 23 42 / 0.12), 0 4px 8px -4px rgb(15 23 42 / 0.06);

    /* Backwards-compat aliases */
    --v2-shadow: var(--v2-shadow-sm);

    /* === Animation tokens (used by Axe 5 polish) === */
    --v2-ease: cubic-bezier(0.16, 1, 0.3, 1);  /* "expo-out" — modern feel */
    --v2-duration-fast: 120ms;
    --v2-duration: 180ms;
    --v2-duration-slow: 240ms;
}

/* ⛔ MODE SOMBRE RETIRÉ le 2026-07-25 — il n'a jamais pu s'activer.
 *
 * Deux raisons cumulées, chacune suffisante :
 *   1. AUCUN basculeur n'existait — zéro `data-theme` dans templates/ et
 *      dans assets/controllers/. Rien ne posait jamais l'attribut.
 *   2. Le bloc vivait ICI, AVANT le thème « luxury » (~l.4800) qui redéfinit
 *      inconditionnellement --v2-bg, --v2-card, --v2-foreground, --v2-muted,
 *      --v2-border et --v2-muted-bg. Même l'attribut posé à la main, le
 *      luxury gagnait : les 6 tokens redéfinis ici étaient exactement les 6
 *      qu'il écrase.
 *
 * Décision du propriétaire du produit : à refaire proprement plus tard, pas
 * à rafistoler. Les valeurs sont conservées ci-dessous pour que ce travail
 * reparte d'une base plutôt que de zéro :
 *
 *     --v2-bg: 222 47% 8%;        (slate-950 quasi noir)
 *     --v2-card: 217 33% 13%;     (slate-900)
 *     --v2-foreground: 210 40% 98%;
 *     --v2-muted: 215 20% 65%;
 *     --v2-border: 217 33% 22%;
 *     --v2-muted-bg: 217 33% 18%;
 *
 * Trois conditions pour que la reprise fonctionne :
 *   - placer le bloc APRÈS le thème luxury, pas avant ;
 *   - couvrir TOUS les tokens que le luxury redéfinit, pas seulement ces 6
 *     (sinon on obtient un fond sombre avec des bordures claires) ;
 *   - ajouter le basculeur qui pose `data-theme` et le persiste.
 */

/* === Premium typography (Axe 1) ===
   Inter Variable is the de facto font of modern dashboards (Stripe, Linear,
   Vercel, Mercury). Its glyphs are denser and more open than Open Sans,
   the optical-size axis (opsz) auto-adjusts at large/small sizes, and the
   stylistic alternates (cv11, ss03) give zero/six/nine/IL distinct shapes
   so we never confuse 0 vs O or 1 vs l in IDs/amounts.

   font-feature-settings cheat sheet:
     - 'ss01' : single-storey 'a' (more geometric, Söhne-like)
     - 'cv11' : alternative 0/6/9 (clear distinction)
     - 'cv09' : single-storey 'g'
     - 'tnum' : tabular-nums (every digit same width — kills column drift)
     - 'calt' : contextual alternates (default on, kept explicit)
     - 'liga' : standard ligatures (fi, fl…)
*/
body.v2-dashboard {
    /* Geist (Vercel) as the single V2 family. Hierarchy via size + weight +
       tracking — no display serif. The CSS variable cascades to descendants
       through the lockdown rule further down. */
    font-family: var(--v2lux-font-body);
    font-feature-settings: 'ss01', 'tnum', 'calt';
    font-optical-sizing: auto;
    font-synthesis: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    /* Slight negative tracking — what makes premium dashboards feel "tight". */
    letter-spacing: -0.011em;

    background: hsl(var(--v2-bg));
    color: hsl(var(--v2-foreground));
    /* Critical: kill horizontal scroll on the page. The legacy Bootstrap
       sidebar (.navbar-vertical.navbar-expand-lg) stays expanded down to 992px
       and reserves ~260px even on tablet (1024px); without overflow-x:hidden,
       the body grows beyond the viewport.
       NOTE: use `clip` not `hidden` so we don't promote body to a scroll
       container (the `overflow-x: hidden` quirk forces `overflow-y: auto`
       which breaks `position: sticky` inside the page). */
    overflow-x: clip;
}

/* Headings — tighter tracking + heavier weight, the Stripe/Linear feel */
body.v2-dashboard h1,
body.v2-dashboard h2,
body.v2-dashboard h3,
body.v2-dashboard h4,
body.v2-dashboard h5,
body.v2-dashboard h6,
body.v2-dashboard .v2-page-header__title,
body.v2-dashboard .v2-chart-card__title {
    font-family: inherit;
    letter-spacing: -0.022em;
    font-weight: 600;
    color: hsl(var(--v2-foreground));
}

/* All numeric/code-ish elements — force tabular-nums so amounts and IDs
   never drift across rows. This is the #1 thing people notice in tables. */
body.v2-dashboard .v2-table,
body.v2-dashboard .v2-kpi__value,
body.v2-dashboard .v2-cell-amount,
body.v2-dashboard .v2-info-grid dd,
body.v2-dashboard input[type="date"],
body.v2-dashboard input[type="number"],
body.v2-dashboard time {
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'cv11', 'ss01', 'tnum';
}

/* === Font enforcement (Axe 1 — defensive) ===
   Bootstrap's theme.bundle.css ships explicit font-family:"Open Sans" on
   `select`, `.form-control`, and via `--bs-font-sans-serif`. Even though
   our `body.v2-dashboard` selector wins by specificity, individual element
   selectors can leak Open Sans into their children if Bootstrap targets
   them directly (e.g. `.form-control`, `select`, `pre`, `code`). We use
   `!important` here as a SAFETY BELT — there's a single allowed monospace
   zone (.v2-jv-line inside JSON viewer) which sets its own family AFTER. */
body.v2-dashboard,
body.v2-dashboard input,
body.v2-dashboard select,
body.v2-dashboard textarea,
body.v2-dashboard button,
body.v2-dashboard .form-control,
body.v2-dashboard .form-select,
body.v2-dashboard .btn,
body.v2-dashboard .v2-table,
body.v2-dashboard .v2-table thead th,
body.v2-dashboard .v2-table tbody td,
body.v2-dashboard .v2-table tbody th,
body.v2-dashboard .v2-info-grid,
body.v2-dashboard .v2-info-grid dt,
body.v2-dashboard .v2-info-grid dd,
body.v2-dashboard .v2-cell-id,
body.v2-dashboard .v2-cell-sub,
body.v2-dashboard .v2-cell-amount,
body.v2-dashboard .v2-cell-muted,
body.v2-dashboard .v2-kpi__value,
body.v2-dashboard .v2-kpi__label,
body.v2-dashboard .v2-side-panel,
body.v2-dashboard .v2-side-panel__value,
body.v2-dashboard .v2-side-panel__label,
body.v2-dashboard .v2-section__title,
body.v2-dashboard .v2-section__subtitle,
body.v2-dashboard .v2-hero,
body.v2-dashboard .v2-hero__amount,
body.v2-dashboard .v2-hero__label,
body.v2-dashboard .v2-hero__meta,
body.v2-dashboard .v2-page-header__title,
body.v2-dashboard .v2-page-header__subtitle,
body.v2-dashboard .v2-chart-card__title,
body.v2-dashboard .v2-chart-card__subtitle,
body.v2-dashboard .v2-badge,
body.v2-dashboard .v2-flag,
body.v2-dashboard .v2-section-label,
body.v2-dashboard .v2-empty-state__title,
body.v2-dashboard .v2-empty-state__hint,
body.v2-dashboard .v2-empty-text,
body.v2-dashboard .v2-notif-accordion .accordion-button,
body.v2-dashboard .v2-notif-accordion .v2-notif-id,
body.v2-dashboard .v2-notif-accordion .v2-notif-url,
body.v2-dashboard .v2-toast,
body.v2-dashboard .v2-modal,
body.v2-dashboard .v2-modal .modal-title,
body.v2-dashboard .v2-modal .modal-body {
    font-family: var(--v2lux-font-body) !important;
    letter-spacing: -0.011em;
    font-feature-settings: 'ss01', 'tnum', 'calt';
}

/* Headings keep tighter tracking */
body.v2-dashboard h1,
body.v2-dashboard h2,
body.v2-dashboard h3,
body.v2-dashboard h4,
body.v2-dashboard h5,
body.v2-dashboard h6 {
    letter-spacing: -0.022em !important;
}

/* === FONT LOCKDOWN ===
   Force Inter on EVERY element under .v2-dashboard. This is a wildcard
   `* { font-family: inherit !important }` rule that prevents any future
   inline `style="font-family: ..."` or third-party CSS from leaking a
   different font into the dashboard. Combined with the body.v2-dashboard
   rule above (which sets the actual Inter family), every descendant
   inherits Inter automatically. NO exceptions outside the JSON viewer. */
body.v2-dashboard,
body.v2-dashboard * {
    /* Single-family Geist — décision 2026-05-18. La hiérarchie passe par
       taille / weight / tracking, pas par changement de famille. */
    font-family: var(--v2lux-font-body) !important;
}

/* Monospace ONLY pour le code / kbd / valeurs purement techniques.
   Les chiffres tabulaires d'UI restent en Geist + tabular-nums (Geist
   supporte cv11/tnum nativement). */
body.v2-dashboard code,
body.v2-dashboard pre,
body.v2-dashboard kbd {
    font-family: var(--v2lux-font-mono) !important;
}

/* Tabular figures pour les colonnes de montants / IDs / compteurs —
   reste en Geist mais avec font-variant-numeric pour aligner les chiffres. */
body.v2-dashboard .v2-tabular,
body.v2-dashboard .v2lux-kpi-tile__value,
body.v2-dashboard .v2-kpi-tile__value,
body.v2-dashboard .v2-money,
body.v2-dashboard td.v2-num,
body.v2-dashboard .v2-cell-amount {
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum', 'cv11';
}

/* THE ONLY ALLOWED MONOSPACE ZONE — JSON viewer payload rendering.
   Loaded LATER in the file so this rule wins over the lockdown above.

   ⛔ `.v2-jv-line` ne suffisait PAS (audit 2026-07-25). Le visualiseur rend
   ses jetons de syntaxe dans des `.jv-key`, `.jv-bracket`, `.jv-string`…
   qui ne sont pas tous des descendants d'une `.v2-jv-line`. Le conteneur
   `.v2-jv` déclare bien le monospace, mais SANS `!important` : le verrou
   universel `body.v2-dashboard * { font-family: … !important }` le battait.

   Mesuré sur /dashboard/call/902200 : le conteneur en Geist Mono, son
   contenu en Geist — un JSON rendu en police proportionnelle, dont les clés
   et les valeurs ne s'alignent pas d'une ligne à l'autre. C'est le « JSON
   dans une police, le site dans une autre » signalé par le propriétaire du
   produit.

   On couvre donc `.v2-jv` et TOUTE sa descendance, plutôt que d'énumérer les
   classes de jetons une à une — le visualiseur en ajouterait une demain et
   le trou reviendrait, silencieux. */
body.v2-dashboard .v2-jv,
body.v2-dashboard .v2-jv *,
body.v2-dashboard .v2-jv-line,
body.v2-dashboard .v2-jv-line *,
body.v2-dashboard pre.v2-code,
body.v2-dashboard pre.v2-code * {
    font-family: var(--v2lux-font-mono) !important;
}
html:has(body.v2-dashboard) {
    overflow-x: clip;
}

/* === Sidebar density (V2 only) ===
   The legacy Bootstrap sidebar uses .display-2 on the brand (96px line-height)
   plus generous .nav-link padding. With 8 nav items + 3 collapsible groups,
   it overflowed the viewport even on a 1080p screen → vertical scrollbar.
   We kill the brand line-height, shrink the brand padding, and tighten the
   nav-link spacing so everything fits without scroll on ≥720px tall screens.
   V1 pages (no .v2-dashboard class on body) keep the original spacing. */
body.v2-dashboard #mainNavbar {
    /* Bootstrap's .scrollbar adds an always-visible scrollbar track even when
       content fits — strip it in V2. The sidebar still scrolls if content
       genuinely exceeds the viewport, but only when needed. */
    scrollbar-width: thin;          /* Firefox */
}
body.v2-dashboard #mainNavbar::-webkit-scrollbar {
    width: 6px;                     /* WebKit — narrower than default 17px */
}
body.v2-dashboard #mainNavbar::-webkit-scrollbar-thumb {
    background: hsl(0 0% 100% / 0.08);
    border-radius: var(--v2-radius-2xs);
}

body.v2-dashboard .v2-brand {
    margin: 0;
    padding: 0.75rem 1rem 0.5rem;   /* was ~24px top from h1.display-2 */
    line-height: 1;                 /* was ~5rem from .display-2 */
    text-align: center;             /* center the logo within the sidebar */
    width: 100%;
}
body.v2-dashboard .v2-brand a {
    display: inline-block;          /* shrink-wrap around the img so text-align centers it */
}
body.v2-dashboard .v2-brand img {
    display: block;
    margin: 0 auto;                 /* belt-and-braces — also center if the parent ever changes */
}

body.v2-dashboard .navbar.navbar-vertical .navbar-nav .nav-link {
    /* Default Tabler/Bootstrap pad : 0.75rem vertical = 12px. Shrink to 8px
       so 11 nav items take ~352px instead of ~480px. */
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
}
body.v2-dashboard .navbar.navbar-vertical .navbar-nav .nav-link svg {
    /* Streamline-Ultimate icons inside nav-links default to 20×20 — slightly
       overkill at 8px padding; 16×16 is the Stripe/Linear standard. */
    width: 16px;
    height: 16px;
}

body.v2-dashboard .navbar.navbar-vertical .nav-item.dropdown .collapse .nav-link {
    /* Sub-items in collapsible groups (Réconciliations / Autres) — even
       tighter so 5 sub-items fit in ~150px. */
    padding-top: 0.35rem;
    padding-bottom: 0.35rem;
    font-size: var(--v2-text-14);
}

body.v2-dashboard .navbar.navbar-vertical .nav-item small {
    /* Section labels ("Backoffice V2", "Admin technique") — kill the
       default mt-3 margin (16px) that was pushing content below the fold. */
    margin-top: 0.5rem !important;
    margin-bottom: 0.25rem !important;
}

body.v2-dashboard .navbar.navbar-vertical hr {
    /* Section separator <hr> in the sidebar — kill its default 16px margin. */
    margin-top: 0.5rem;
    margin-bottom: 0.25rem;
}

/* === Sidebar override (tablet narrow) ===
   Between 992-1199px the Bootstrap sidebar takes ~260px which leaves only
   ~750px for content. Shrink to 200px AND match the main's margin-left
   so the content doesn't overlap the sidebar nor leave a gap. */
@media (min-width: 992px) and (max-width: 1199px) {
    body.v2-dashboard .navbar.navbar-vertical.navbar-expand-lg {
        width: 200px !important;
        min-width: 200px !important;
    }
    /* Only applies to LEGACY pages (no luxury body class). On luxury V2
       the sidebar handles tablet via translateX(-100%) <1024px (no narrow
       sidebar mode), so this override would only break the layout. */
    body.v2-dashboard:not(.v2-design-luxury) main {
        margin-left: 200px !important;
    }
}

/* === V2 Topbar (premium combined header) ===
   Single horizontal row : [page title + breadcrumb] [page actions] [notif + avatar].
   Replaces both the legacy `_header.html.twig` (which had `py-6` = 64px
   padding for just 2 icons) AND the separate `.v2-page-header` block —
   saves ~140-180px above the fold on every V2 page.

   IMPORTANT — sidebar offset : Bootstrap's Tabler theme sets
   `.navbar-vertical.navbar-expand-lg ~ main { margin-left: 16.25rem }` so
   <main> aligns past the fixed sidebar. But that rule is `~ main`, so it
   does NOT apply to <header>. Without an explicit margin-left, the topbar
   starts at left:0 and the title gets hidden behind the sidebar.
   We replicate the offset manually below. */
.navbar-vertical.navbar-expand-lg ~ header.v2-topbar,
.navbar-vertical.navbar-expand-md ~ header.v2-topbar,
.navbar-vertical.navbar-expand-sm ~ header.v2-topbar,
.navbar-vertical.navbar-expand-xs ~ header.v2-topbar,
.navbar-vertical.navbar-expand-xl ~ header.v2-topbar,
.navbar-vertical.navbar-expand-xxl ~ header.v2-topbar {
    margin-left: 16.25rem;          /* 260px — matches main's offset */
}
/* Tablet narrow (992-1199px) — match the V2 200px sidebar override */
@media (min-width: 992px) and (max-width: 1199px) {
    body.v2-dashboard header.v2-topbar {
        margin-left: 200px !important;
    }
}
/* Mobile (<992px) — sidebar collapses to hamburger, no offset needed */
@media (max-width: 991px) {
    body.v2-dashboard header.v2-topbar {
        margin-left: 0 !important;
    }
}

body.v2-dashboard header.v2-topbar,
.v2-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--v2-space-4);
    padding: var(--v2-space-2) var(--v2-space-5);
    background: hsl(var(--v2-card));
    border-bottom: 1px solid hsl(var(--v2-border-subtle));
    margin: 0;
    min-height: 52px;
}
.v2-topbar__title {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
}
.v2-topbar__heading {
    font-size: var(--v2-text-18);          /* 18px — modern dashboards keep titles tight */
    font-weight: 600;
    letter-spacing: -0.022em;
    line-height: 1.2;
    color: hsl(var(--v2-foreground));
    margin: 0;
}
.v2-topbar__breadcrumb {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    line-height: 1.3;
    /* Remove the bottom margin that breadcrumbs sometimes carry */
}
.v2-topbar__breadcrumb > * {
    margin-bottom: 0 !important;
}
.v2-topbar__actions {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-topbar__user {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-topbar__icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-subtle));
    color: hsl(var(--v2-foreground));
    text-decoration: none;
    transition: background var(--v2-duration-fast) var(--v2-ease),
                border-color var(--v2-duration-fast) var(--v2-ease);
}
.v2-topbar__icon-btn:hover {
    background: hsl(var(--v2-muted-bg));
    border-color: hsl(var(--v2-border-strong));
    color: hsl(var(--v2-primary-600));
}
.v2-topbar__badge {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 18px;
    height: 18px;
    border-radius: var(--v2-radius-md);
    background: hsl(var(--v2-danger-500));
    color: white;
    font-size: var(--v2-text-10);
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 4px;
    line-height: 1;
    border: 2px solid hsl(var(--v2-card));
}
.v2-topbar .dropdown-menu {
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2-shadow-md);
    padding: var(--v2-space-1);
    margin-top: var(--v2-space-1);
}
.v2-topbar .dropdown-item {
    border-radius: var(--v2-radius-sm);
    font-size: var(--v2-text-14);
    padding: var(--v2-space-2) var(--v2-space-3);
}
.v2-topbar .dropdown-item:hover {
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-primary-600));
}

/* Override legacy header styling on V2 pages — make absolutely sure the
   old `_header.html.twig` (if it ever leaks) doesn't add 128px of padding. */
body.v2-dashboard header.container-fluid {
    padding-top: var(--v2-space-2) !important;
    padding-bottom: var(--v2-space-2) !important;
    margin-bottom: 0 !important;
}

.v2-main {
    /* FLUID full-width layout — matches the legacy V1 dashboard which also
       uses no max-width (just margin-left for the sidebar). On 3440px ultra-
       wide screens this gives V2 the same edge-to-edge feel as V1.
       Top padding reduced because the title is now in the topbar (not here). */
    padding: var(--v2-space-4) var(--v2-space-6);   /* 16px x 24px (was 24x32) */
    margin: 0;
    /* Critical: prevent grid children with intrinsic min-content (tables, code, long URLs)
       from forcing the main to grow horizontally beyond the viewport. */
    min-width: 0;
    /* `overflow-x: clip` instead of `hidden` so we don't create a vertical
       scroll context (which would break `position: sticky` on descendants
       like the rebill toolbar / tabs / table headers). `clip` is supported
       on every evergreen browser (Chrome 90+, FF 81+, Safari 16+). */
    overflow-x: clip;
    box-sizing: border-box;
    /* Strict width — never grow beyond the parent main element. */
    width: 100%;
}
@media (min-width: 1920px) {
    .v2-main {
        /* On very wide screens (4K, ultra-wide), bump padding so dense tables
           and KPI grids breathe a little without going edge-to-edge. */
        padding: 1.75rem 2.5rem;
    }
}
@media (max-width: 1024px) {
    .v2-main {
        padding: 1rem 1.25rem;
    }
}
@media (max-width: 480px) {
    .v2-main {
        padding: 0.75rem 0.75rem;
    }
}

/* On wide screens, KPI grid can fit MORE than 5 cards. Bump up the breakpoint
   so 5 cols stays the default (instead of stretching weirdly). */
@media (min-width: 1920px) {
    .v2-grid--kpis {
        /* Keep 5 cols even on 4K — wider tiles stay readable, no awkward 6/7 col. */
        grid-template-columns: repeat(5, minmax(0, 1fr));
    }
}

/* === KpiTile (Axe 2 — strict tokens, premium feel) ===
   Tight padding so 5 tiles fit comfortably above the fold. The value
   is the hero — content-sized height (no forced min-height which created
   bottom whitespace on tiles with short hints). */
.v2-kpi {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    padding: var(--v2-space-3) var(--v2-space-4);   /* 12px x 16px */
    box-shadow: var(--v2-shadow-xs);
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-1);                         /* 4px between label/value/hint */
    transition: box-shadow var(--v2-duration) var(--v2-ease),
                border-color var(--v2-duration) var(--v2-ease),
                transform var(--v2-duration) var(--v2-ease);
    /* Allow grid cell to shrink below content size — prevents 1 large value
       from forcing 5 KPIs into 3+2 layout. min-width:0 is the unblock trick. */
    min-width: 0;
    overflow: hidden;
}
.v2-kpi:hover {
    box-shadow: var(--v2-shadow-sm);
    border-color: hsl(var(--v2-border-strong) / 0.5);
}

.v2-kpi__label {
    /* Flex plutôt que flux inline : l'icône de gauche et le « i » d'aide ne
       doivent pas être entraînés par un libellé qui revient à la ligne.
       AVANT, le libellé était coupé à l'ellipse (« Tendance CA (mois → m… »,
       « Nouveaux clients accepté… ») sur quatre onglets. Tronquer le nom d'un
       indicateur dans un backoffice de paiement oblige à survoler pour savoir
       ce qu'on lit, et PRODUCT.md interdit le texte qui déborde. Les tuiles
       d'une même grille s'alignent de toute façon sur la plus haute. */
    display: flex;
    align-items: flex-start;
    gap: 5px;
    font-size: var(--v2-text-12);
    font-weight: 500;
    text-transform: none;
    letter-spacing: -0.005em;
    color: hsl(var(--v2-muted));
}
.v2-kpi__label > svg,
.v2-kpi__label .v2-kpi__help {
    flex-shrink: 0;
    margin-top: 1px;
}
.v2-kpi__label-text {
    min-width: 0;
    text-wrap: pretty;
}

/* Aide contextuelle ⓘ des KPI : discret au repos, lisible au survol/focus.
   Tooltip natif (title) + aria-label — pas de JS, accessible clavier. */
.v2-kpi__help {
    display: inline-flex;
    vertical-align: -2px;
    margin-left: 2px;
    color: hsl(var(--v2-muted) / 0.7);
    cursor: help;
}
.v2-kpi__help:hover,
.v2-kpi__help:focus-visible {
    color: hsl(var(--v2-primary-600));
    outline: none;
}

.v2-kpi__value {
    /* Numbers stay on ONE line + must FIT inside the tile (no ellipsis).
       Responsive font via media queries (more predictable than clamp() inside
       a container-typed parent which has inconsistent vw resolution).
       Default = 20px. Media queries below shrink at narrow widths and grow
       at wider widths so even "1 638 013,56 €" fits without truncation. */
    font-size: var(--v2-text-20);                  /* 20px default — fits 12-char amounts at 185px tile */
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.022em;
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'cv11', 'ss01', 'tnum';
    color: hsl(var(--v2-foreground));
    /* ⛔ « No ellipsis, never hide it » était un vœu, pas un fait (audit
       2026-07-25) : le parent .v2-kpi porte `overflow: hidden`, donc au-delà
       de ~13 caractères la valeur était coupée NET, sans « … » — un CA de
       1 234 567 890,00 € se lisait « 1 234 567 8 », faux d'un facteur 100 et
       sans aucun signe que le nombre continuait. Sur un écran financier, une
       troncature muette est pire qu'une troncature visible.
       L'ellipsis rend la coupe lisible ; le title= du gabarit rend la valeur
       entière récupérable au survol. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Tablet narrow (200px sidebar = ~1100-1200px content for 5 tiles) */
@media (min-width: 992px) and (max-width: 1199px) {
    body.v2-dashboard .v2-kpi__value {
        font-size: var(--v2-text-18);             /* 18px */
    }
}
/* Wider screens (4K, ultra-wide) — let values breathe with more room */
@media (min-width: 1440px) {
    body.v2-dashboard .v2-kpi__value {
        font-size: var(--v2-text-24);               /* 24px */
    }
}
@media (min-width: 1920px) {
    body.v2-dashboard .v2-kpi__value {
        font-size: var(--v2-text-28);              /* 28px on 4K */
    }
}

.v2-kpi__hint {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.v2-kpi__delta {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-1);
    font-size: var(--v2-text-12);
    font-weight: 600;
    padding: 2px var(--v2-space-2);
    border-radius: var(--v2-radius-sm);
    align-self: flex-start;
    letter-spacing: -0.005em;
}

/* Deltas de tendance = CONTEXTE informatif (variation vs période précédente),
   PAS une alerte. On utilise donc des tons POSÉS et désaturés, distincts des
   couleurs sémantiques danger/success réservées aux vrais états d'erreur/succès.
   Une baisse banale (-1 %) ne doit pas s'afficher dans le rouge d'alarme — ça
   donne une « perception de bug » côté client (retour utilisateur 2026-06-15). */
.v2-kpi__delta--up {
    color: hsl(var(--v2-forest));       /* vert sapin posé (≈6:1 sur le fond clair) */
    background: hsl(var(--v2-forest-50));
}

.v2-kpi__delta--down {
    color: hsl(var(--v2-brick));         /* brique désaturée, pas le rouge d'alarme */
    background: hsl(var(--v2-brick-50));
}

.v2-kpi__delta--flat {
    color: hsl(var(--v2-muted));
    background: hsl(var(--v2-muted-bg));
}

/* === ChartCard (Axe 2 — premium card) ===
   Subtle border + barely-there shadow. Premium feel = NOT a heavy box.
   The card should melt into the page, content does the talking. */
.v2-chart-card {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-xs);
    overflow: hidden;
    transition: box-shadow var(--v2-duration) var(--v2-ease),
                border-color var(--v2-duration) var(--v2-ease);
}
.v2-chart-card:hover {
    box-shadow: var(--v2-shadow-sm);
}

.v2-chart-card__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    padding: var(--v2-space-3) var(--v2-space-5) var(--v2-space-2);  /* 12px x 20px x 8px (was 20-24-12) */
    gap: var(--v2-space-4);
}

.v2-chart-card__title {
    font-size: var(--v2-text-14);
    font-weight: 600;
    letter-spacing: -0.014em;
    color: hsl(var(--v2-foreground));
    margin: 0;
}

.v2-chart-card__subtitle {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    margin: var(--v2-space-1) 0 0;
    letter-spacing: -0.005em;
}

.v2-chart-card__body {
    padding: var(--v2-space-2) var(--v2-space-4) var(--v2-space-5);
    /* No horizontal scroll — content must fit. Table is responsive via
       column hiding (.v2-col-priority-*) at narrower viewports. */
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
}

/* === DataTable ===
   Wrap any v2-table in <div class="v2-table-scroll"> for horizontal overflow.
   The table itself becomes scrollable instead of pushing the whole page wider. */
.v2-table-scroll {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    border-radius: var(--v2-radius-lg);
}
.v2-table {
    width: 100%;
    min-width: 600px; /* readable threshold; below this the wrapper scrolls horizontally */
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    overflow: hidden;
    border-collapse: separate;
    border-spacing: 0;
}
/* When a v2-table is inside a Shared:ChartCard body, it inherits
   the card's overflow:hidden — wrap it in v2-table-scroll for horizontal scroll. */

.v2-table thead th {
    background: hsl(var(--v2-muted-bg) / 0.5);
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-11);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    padding: var(--v2-space-3) var(--v2-space-4);
    text-align: left;
    border-bottom: 1px solid hsl(var(--v2-border-default));
}

.v2-table tbody td {
    padding: var(--v2-space-3) var(--v2-space-4);
    border-bottom: 1px solid hsl(var(--v2-border-subtle));
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-14);
    font-variant-numeric: tabular-nums;
    transition: background var(--v2-duration-fast) var(--v2-ease);
}

.v2-table tbody tr:last-child td {
    border-bottom: 0;
}

.v2-table tbody tr {
    transition: background var(--v2-duration-fast) var(--v2-ease);
}
.v2-table tbody tr:hover td {
    background: hsl(var(--v2-muted-bg) / 0.5);
}

/* === Badge === */
.v2-badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: var(--v2-text-12);
    font-weight: 600;
    padding: 0.25rem 0.625rem;
    border-radius: var(--v2-radius-full);
    line-height: 1;
}

.v2-badge--success {
    color: hsl(var(--v2-success-700));
    background: hsl(var(--v2-success-50));
}

.v2-badge--danger {
    color: hsl(var(--v2-danger-700));
    background: hsl(var(--v2-danger-50));
}

.v2-badge--warning {
    color: hsl(var(--v2-warning-800));
    background: hsl(var(--v2-warning-50));
}

.v2-badge--info {
    color: hsl(var(--v2-info-700));
    background: hsl(var(--v2-info-50));
}

.v2-badge--default {
    color: hsl(var(--v2-muted));
    background: hsl(var(--v2-muted-bg));
}

/* === ConfirmDialog (override Bootstrap) === */
.v2-modal .modal-content {
    border: 0;
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-lg);
}

/* === Page header (legacy, kept for backwards compat) ===
   The page title is now rendered inside `.v2-topbar` (top header bar).
   This `.v2-page-header` block is no longer used by V2 templates but the
   styles are kept in case any external page still uses the old structure. */

/* === Grid utilities (Axe 2 — strict gap tokens) === */
.v2-grid {
    display: grid;
    gap: var(--v2-space-4);
}

.v2-grid--kpis {
    /* Force 5 cols on desktop so the strip never collapses to 3+2 or 4+1.
       Intermediate breakpoint at 1280→4 cols, 1100→3 cols smooths the transition. */
    grid-template-columns: repeat(5, minmax(0, 1fr));
}
@media (max-width: 1280px) {
    .v2-grid--kpis {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}
@media (max-width: 1100px) {
    .v2-grid--kpis {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
@media (max-width: 768px) {
    .v2-grid--kpis {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}
@media (max-width: 480px) {
    .v2-grid--kpis {
        grid-template-columns: 1fr;
    }
}

.v2-grid--cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 768px) {
    .v2-grid--cols-2 {
        grid-template-columns: 1fr;
    }
}

/* Payloads JSON côte à côte (requête ↔ réponse PSP) : la comparaison en
   vis-à-vis n'a de valeur que si chaque colonne reste lisible. En dessous de
   1200 px, deux colonnes de JSON deviennent des couloirs — on empile alors
   pour rendre à chaque payload TOUTE la largeur de la page. */
.v2-grid--payloads {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 1200px) {
    .v2-grid--payloads {
        grid-template-columns: 1fr;
    }
}

.v2-grid--cols-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 1100px) {
    .v2-grid--cols-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
    .v2-grid--cols-3 { grid-template-columns: 1fr; }
}

.v2-grid--cols-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (max-width: 1280px) {
    .v2-grid--cols-4 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
    .v2-grid--cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 540px) {
    .v2-grid--cols-4 { grid-template-columns: 1fr; }
}

/* === Mobile Safari quirks ===
   Prevent iOS Safari from auto-scaling text size on orientation change
   (it bumps font-size by ~110% on landscape which scrambles tabular-nums
   alignment). Combined with the viewport meta this is the duo that
   makes V2 look identical between iPhone Safari and desktop Chrome. */
html,
body.v2-dashboard {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

/* iOS native date inputs render their picker out of band — make sure the
   form-control container can shrink on small screens so the picker doesn't
   force the toolbar wider than the viewport. */
@media (max-width: 600px) {
    body.v2-dashboard input[type="date"].form-control {
        min-width: 0;
        width: 100%;
    }
}

/* === Chart card extras (Sprint Bridge-B refactor v2) === */

/* Right-aligned badge in chart card header (e.g. "X transactions" counter). */
.v2-chart-card__badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.25rem 0.6rem;
    border-radius: var(--v2-radius-full);
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-13);
    font-weight: 500;
    white-space: nowrap;
}

/* Wraps a Chart.js canvas. Without this, Chart.js can set a pixel width
   that overflows the grid cell on responsive breakpoints. */
.v2-chart-canvas-wrapper {
    position: relative;
    width: 100%;
    overflow: hidden;
}
.v2-chart-canvas-wrapper > canvas {
    max-width: 100% !important;
    height: 100% !important;
}

/* === Donut + side-list layout (Next.js parity) ===
   Renders as : [donut canvas] [legend list with label / value / pct]. */
.v2-donut-row {
    display: grid;
    grid-template-columns: 1fr 1.3fr;
    gap: 1rem;
    align-items: center;
    min-height: 240px;
}
@media (max-width: 768px) {
    .v2-donut-row {
        grid-template-columns: 1fr;
    }
}
.v2-donut-canvas-wrapper {
    position: relative;
    width: 100%;
    height: 240px;
    overflow: hidden;
}
.v2-donut-canvas-wrapper > canvas {
    max-width: 100% !important;
    height: 100% !important;
}

.v2-legend-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    /* Plus de max-height + scroll. Quand il y a > 7 prestataires, on ne
       montre que les 7 plus gros et on agrège le reste dans une ligne
       "+ N autres" (faite côté Twig avec |slice). Le donut reste complet. */
    overflow: hidden;
}
.v2-legend-list__item--more {
    /* Ligne "+ N autres" — un peu plus discrète que les vrais items */
    color: hsl(var(--v2-muted));
    font-style: italic;
    font-size: var(--v2-text-13);
    padding-top: 0.4rem;
    margin-top: 0.2rem;
    border-top: 1px dashed hsl(var(--v2-border));
}
.v2-legend-list__item--more .v2-legend-list__dot {
    background: repeating-linear-gradient(
        45deg,
        hsl(var(--v2-muted)) 0 2px,
        transparent 2px 5px
    ) !important;
}
.v2-legend-list__item {
    display: grid;
    grid-template-columns: 0.7rem 1fr auto auto;
    align-items: center;
    gap: 0.6rem;
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-foreground));
    padding: 0.2rem 0;
    border-bottom: 1px solid hsl(var(--v2-border) / 0.6);
}
.v2-legend-list__item:last-child {
    border-bottom: none;
}
.v2-legend-list__dot {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: var(--v2-radius-full);
    flex-shrink: 0;
}
.v2-legend-list__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: hsl(var(--v2-foreground));
}
.v2-legend-list__value {
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.v2-legend-list__pct {
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
    text-align: right;
    min-width: 2.5rem;
}

/* ════════════════════════════════════════════════════════════════
   Premium UI pass — buttons, inputs, forms, filter bars
   Scoped to .v2-main so we don't bleed into the legacy Twig dashboard.
   ════════════════════════════════════════════════════════════════ */

/* === Buttons (Axe 2 — premium: tighter, sharper, single transition) === */
.v2-main .btn {
    border-radius: var(--v2-radius-md);
    font-weight: 500;
    font-size: var(--v2-text-14);
    padding: var(--v2-space-2) var(--v2-space-4);
    transition: all var(--v2-duration-fast) var(--v2-ease);
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    line-height: 1.2;
    border-width: 1px;
    letter-spacing: -0.005em;
    /* Premium subtle highlight at top — gives buttons depth without shadow */
    position: relative;
}
.v2-main .btn-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 38px;
    padding: var(--v2-space-2) var(--v2-space-3);
    font-size: var(--v2-text-13);
    border-radius: var(--v2-radius-sm);
    gap: var(--v2-space-1);
}
.v2-main .btn-lg {
    padding: var(--v2-space-3) var(--v2-space-5);
    font-size: var(--v2-text-14);
    border-radius: var(--v2-radius-md);
}

.v2-main .btn-primary {
    background: hsl(var(--v2-primary-500));
    border-color: hsl(var(--v2-primary-500));
    color: white;
}
.v2-main .btn-primary:hover,
.v2-main .btn-primary:focus {
    background: hsl(var(--v2-primary-600));
    border-color: hsl(var(--v2-primary-600));
    color: white;
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.2);
}

.v2-main .btn-outline-secondary {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
}
.v2-main .btn-outline-secondary:hover,
.v2-main .btn-outline-secondary:focus {
    background: hsl(var(--v2-muted-bg));
    border-color: hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
    box-shadow: 0 0 0 3px hsl(var(--v2-border) / 0.5);
}

/* === Cyan accent buttons (eZengo brand) === */
.v2-main .btn-outline-primary {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-primary-500) / 0.55);
    color: hsl(var(--v2-primary-600));
    font-weight: 500;
}
.v2-main .btn-outline-primary:hover,
.v2-main .btn-outline-primary:focus {
    background: hsl(var(--v2-primary-50));
    border-color: hsl(var(--v2-primary-500));
    color: hsl(var(--v2-primary-700));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.18);
}

.v2-main .btn-info,
.v2-modal .btn-info {
    background: hsl(var(--v2-primary-500));
    border-color: hsl(var(--v2-primary-500));
    color: white;
    font-weight: 500;
}
.v2-main .btn-info:hover,
.v2-main .btn-info:focus,
.v2-modal .btn-info:hover,
.v2-modal .btn-info:focus {
    background: hsl(var(--v2-primary-600));
    border-color: hsl(var(--v2-primary-600));
    color: white;
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.2);
}

.v2-main .btn-outline-info {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-primary-500) / 0.55);
    color: hsl(var(--v2-primary-600));
}
.v2-main .btn-outline-info:hover,
.v2-main .btn-outline-info:focus {
    background: hsl(var(--v2-primary-50));
    border-color: hsl(var(--v2-primary-500));
    color: hsl(var(--v2-primary-700));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.18);
}

/* Light variant — used in V1 for "clear" — keep neutral but with cyan focus */
.v2-main .btn-light,
.v2-modal .btn-light {
    background: hsl(var(--v2-muted-bg));
    border-color: hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
}
.v2-main .btn-light:hover,
.v2-main .btn-light:focus {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-primary-500) / 0.55);
    color: hsl(var(--v2-primary-700));
}

.v2-main .btn-outline-danger {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-danger-500) / 0.4);
    color: hsl(var(--v2-danger-600));
}
.v2-main .btn-outline-danger:hover,
.v2-main .btn-outline-danger:focus {
    background: hsl(var(--v2-danger-50));
    border-color: hsl(var(--v2-danger-500));
    color: hsl(var(--v2-danger-600));
    box-shadow: 0 0 0 3px hsl(var(--v2-danger-500) / 0.18);
}

.v2-main .btn-outline-warning {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-warning-500) / 0.4);
    color: hsl(var(--v2-warning-600));
}
.v2-main .btn-outline-warning:hover,
.v2-main .btn-outline-warning:focus {
    background: hsl(var(--v2-warning-50));
    border-color: hsl(var(--v2-warning-500));
    color: hsl(var(--v2-warning-600));
}

.v2-main .btn-outline-success {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-success-500) / 0.4);
    color: hsl(var(--v2-success-600));
}
.v2-main .btn-outline-success:hover,
.v2-main .btn-outline-success:focus {
    background: hsl(var(--v2-success-50));
    border-color: hsl(var(--v2-success-500));
    color: hsl(var(--v2-success-600));
}

.v2-main .btn-danger,
.v2-modal .btn-danger {
    background: hsl(var(--v2-danger-500));
    border-color: hsl(var(--v2-danger-500));
    color: white;
}
.v2-main .btn-danger:hover,
.v2-main .btn-danger:focus,
.v2-modal .btn-danger:hover,
.v2-modal .btn-danger:focus {
    background: hsl(var(--v2-danger-600));
    border-color: hsl(var(--v2-danger-600));
    color: white;
}

.v2-main .btn-warning,
.v2-modal .btn-warning {
    background: hsl(var(--v2-warning-500));
    border-color: hsl(var(--v2-warning-500));
    color: white;
}

.v2-main .btn-success,
.v2-modal .btn-success {
    background: hsl(var(--v2-success-500));
    border-color: hsl(var(--v2-success-500));
    color: white;
}

.v2-modal .btn-secondary {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
}

.v2-main .btn-group .btn + .btn {
    margin-left: -1px;
}
.v2-main .btn-group .btn:focus {
    z-index: 2;
}

/* === Form labels — premium typography === */
.v2-main .form-label {
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: hsl(var(--v2-foreground) / 0.72);
    margin-bottom: 0.4rem;
    display: block;
}

/* === Form controls (Axe 2 — premium inputs/selects) ===
   No box-shadow on default state (premium = clean), thin border, subtle bg,
   cyan focus ring scaled to ~3px. */
.v2-main .form-control,
.v2-main .form-select {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-14);
    border-radius: var(--v2-radius-md);
    padding: var(--v2-space-2) var(--v2-space-3);
    line-height: 1.4;
    transition: border-color var(--v2-duration-fast) var(--v2-ease),
                box-shadow var(--v2-duration-fast) var(--v2-ease),
                background var(--v2-duration-fast) var(--v2-ease);
    box-shadow: none;
    width: 100%;
    letter-spacing: -0.005em;
}
.v2-main .form-control:hover,
.v2-main .form-select:hover {
    border-color: hsl(var(--v2-border-strong));
}
.v2-main .form-control::placeholder {
    /* Alpha relevée de 0.6 → 0.9 : à 0.6 le contraste placeholder tombait à
       2,56:1 (sous la cible WCAG AA 4,5:1 fixée dans PRODUCT.md). À 0.9 = 4,73:1,
       tout en restant plus clair que le texte saisi. */
    color: hsl(var(--v2-muted) / 0.9);
    letter-spacing: -0.005em;
}
.v2-main .form-control:focus,
.v2-main .form-select:focus {
    outline: none;
    border-color: hsl(var(--v2-primary-500));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.15);
    background: hsl(var(--v2-card));
}
.v2-main .form-control:disabled,
.v2-main .form-control[readonly],
.v2-main .form-select:disabled {
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-muted));
    cursor: not-allowed;
}

.v2-main .form-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2364748b'%3E%3Cpath d='M3.204 5L8 10.481 12.796 5 14 6.227 8 13 2 6.227 3.204 5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
    background-size: 12px;
    padding-right: 2.4rem;
}

.v2-main .form-text,
.v2-main small.form-text {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-12);
    margin-top: 0.3rem;
    display: block;
}

.v2-main .invalid-feedback,
.v2-main .form-error,
.v2-main .alert-danger {
    color: hsl(var(--v2-danger-600));
    font-size: var(--v2-text-13);
}
.v2-main .alert-danger {
    background: hsl(var(--v2-danger-50));
    border: 1px solid hsl(var(--v2-danger-500) / 0.3);
    border-radius: var(--v2-radius);
    padding: 0.75rem 1rem;
}

.v2-main .form-check {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding-left: 0;
}
.v2-main .form-check-input {
    margin: 0;
    width: 1.05rem;
    height: 1.05rem;
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-xs);
    cursor: pointer;
    flex-shrink: 0;
}
.v2-main .form-check-input:checked {
    background-color: hsl(var(--v2-primary-500));
    border-color: hsl(var(--v2-primary-500));
}
.v2-main .form-check-input:focus {
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.18);
    outline: none;
}

/* === Premium form layout === */
.v2-form-section {
    padding: 1.25rem 1.5rem;
    border-bottom: 1px solid hsl(var(--v2-border));
}
.v2-form-section:last-of-type {
    border-bottom: 0;
}
.v2-form-section__title {
    font-size: var(--v2-text-14);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    margin: 0 0 0.25rem;
}
.v2-form-section__hint {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    margin: 0 0 1rem;
}
.v2-form-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem 1.25rem;
}
.v2-form-grid--full {
    grid-template-columns: 1fr;
}
@media (max-width: 768px) {
    .v2-form-grid {
        grid-template-columns: 1fr;
    }
}

.v2-form-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.6rem;
    padding: 1rem 1.5rem;
    border-top: 1px solid hsl(var(--v2-border));
    background: hsl(var(--v2-muted-bg) / 0.4);
}
.v2-form-footer__hint {
    margin-right: auto;
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
}

/* === Filter bar (Axe 2 — strict token spacing) === */
.v2-filter-bar {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2-shadow-xs);
    padding: var(--v2-space-3) var(--v2-space-4);
    margin-bottom: var(--v2-space-3);
    display: grid;
    gap: var(--v2-space-3);
    grid-template-columns: 1fr;
}
.v2-filter-bar__row {
    display: grid;
    gap: var(--v2-space-3);
    grid-template-columns: var(--v2-filter-cols, repeat(auto-fit, minmax(180px, 1fr)));
    align-items: end;
}
/* Filter bars degrade gracefully — many small selects compressed to <140px
   become unusable. Below 1100px, ignore the inline --v2-filter-cols and let
   the bar wrap with auto-fit minmax(180px, 1fr). */
@media (max-width: 1100px) {
    .v2-filter-bar__row {
        grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)) !important;
    }
}
@media (max-width: 480px) {
    .v2-filter-bar__row {
        grid-template-columns: 1fr !important;
    }
}
.v2-filter-bar__count {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
    font-weight: 500;
    padding-top: 0.5rem;
    border-top: 1px solid hsl(var(--v2-border));
}
.v2-filter-bar__count strong {
    color: hsl(var(--v2-primary-600));
    font-weight: 700;
}

.v2-filter-bar .form-label {
    margin-bottom: 0.3rem;
    color: hsl(var(--v2-foreground) / 0.72);
    font-size: var(--v2-text-13);
    font-weight: 600;
}

/* "Filtered" state — adds a cyan accent ring to inputs/selects with non-default values.
   Triggered by adding `.v2-filtered` class server-side, OR via :not(:placeholder-shown). */
.v2-filter-bar .form-control.v2-filtered,
.v2-filter-bar .form-select.v2-filtered {
    border-color: hsl(var(--v2-primary-500));
    box-shadow: 0 0 0 2px hsl(var(--v2-primary-500) / 0.12);
    background-color: hsl(var(--v2-primary-50) / 0.4);
}
.v2-filter-bar .form-control:not(:placeholder-shown):not([type="date"]) {
    border-color: hsl(var(--v2-primary-500) / 0.55);
}

/* Period selector — keep solid cyan for the active button (inherited from .btn-primary)
   AND use cyan-tinted outline for the inactive ones (apply class .v2-period-group on the wrapper). */
.v2-period-group .btn {
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

/* === Nav tabs (bootstrap) — premium accent === */
.v2-main .nav-tabs {
    border-bottom: 1px solid hsl(var(--v2-border));
    gap: 0.25rem;
}
.v2-main .nav-tabs .nav-link {
    border: 0;
    background: transparent;
    color: hsl(var(--v2-muted));
    font-weight: 500;
    font-size: var(--v2-text-14);
    padding: 0.65rem 1rem;
    border-radius: 0;
    border-bottom: 2px solid transparent;
    transition: color 0.12s ease, border-color 0.12s ease;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
}
.v2-main .nav-tabs .nav-link:hover {
    color: hsl(var(--v2-foreground));
    border-bottom-color: hsl(var(--v2-border));
}
.v2-main .nav-tabs .nav-link.active {
    color: hsl(var(--v2-primary-600));
    border-bottom-color: hsl(var(--v2-primary-500));
    background: transparent;
}

/* === Modal (ConfirmDialog) premium polish === */
.v2-modal .modal-header {
    border-bottom: 1px solid hsl(var(--v2-border));
    padding: 1rem 1.5rem;
}
.v2-modal .modal-title {
    font-size: var(--v2-text-16);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
}
.v2-modal .modal-body {
    padding: 1rem 1.5rem;
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-14);
    line-height: 1.55;
}
.v2-modal .modal-footer {
    border-top: 1px solid hsl(var(--v2-border));
    padding: 0.85rem 1.25rem;
    background: hsl(var(--v2-muted-bg) / 0.4);
    gap: 0.5rem;
}

/* === ChartCard with body padding 0 (when used as a form wrapper) === */
.v2-chart-card--form .v2-chart-card__body {
    padding: 0;
}

/* === Breadcrumb premium (used in detail page templates) === */
.v2-breadcrumb {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: var(--v2-text-14);
    margin-bottom: 0.4rem;
    flex-wrap: wrap;
}
.v2-breadcrumb__item {
    color: hsl(var(--v2-muted));
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    transition: color 0.12s ease;
}
.v2-breadcrumb__item:hover {
    color: hsl(var(--v2-primary-600));
}
.v2-breadcrumb__separator {
    color: hsl(var(--v2-border));
    font-weight: 400;
    user-select: none;
}
.v2-breadcrumb__current {
    color: hsl(var(--v2-foreground));
    font-weight: 500;
}

/* Meta-row under the page title (registered date, counts, etc.) */
.v2-page-header__meta {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-muted));
}
.v2-page-header__meta-item {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.v2-page-header__meta-sep {
    color: hsl(var(--v2-border));
    user-select: none;
}

/* The action buttons on page header — stack on narrow screens */
.v2-page-header > div:last-child {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    justify-content: flex-end;
}
@media (max-width: 768px) {
    .v2-page-header {
        flex-direction: column;
        align-items: flex-start;
    }
    .v2-page-header > div:last-child {
        width: 100%;
        justify-content: flex-start;
    }
}

/* === Sortable column header buttons === */
.v2-sort-btn {
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    color: inherit;
    font: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    transition: color 0.12s ease;
}
.v2-sort-btn:hover {
    color: hsl(var(--v2-foreground));
}
.v2-sort-btn--active {
    color: hsl(var(--v2-primary-600));
}

/* === JSON Viewer (json-viewer Stimulus controller, Sprint F V1→V2 merge) === */
.v2-jv-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.5rem 0.75rem;
    border: 1px solid hsl(var(--v2-border));
    border-bottom: 0;
    border-radius: var(--v2-radius) var(--v2-radius) 0 0;
    background: hsl(var(--v2-muted-bg));
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
}
.v2-jv-toolbar__type {
    font-family: var(--v2lux-font-mono);
    font-weight: 600;
    color: hsl(var(--v2-primary-600));
    background: hsl(var(--v2-primary-50));
    padding: 0.15rem 0.5rem;
    border-radius: var(--v2-radius-sm);
}
.v2-jv-copy {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-12);
    padding: 0.25rem 0.5rem;
    border-radius: var(--v2-radius-sm);
    cursor: pointer;
    transition: background 0.12s ease;
}
.v2-jv-copy:hover { background: hsl(var(--v2-muted-bg)); }
.v2-jv-copy--ok {
    background: hsl(var(--v2-success-50));
    color: hsl(var(--v2-success-600));
    border-color: hsl(var(--v2-success-500) / 0.4);
}

.v2-jv {
    margin: 0;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: 0 0 var(--v2-radius) var(--v2-radius);
    padding: 0.75rem 0.9rem;
    font-family: var(--v2lux-font-mono);
    /* ⛔ PAS de max-height (retiré le 2026-07-25). Un plafond de 520 px
       enfermait le payload dans une petite fenêtre à ascenseur : pour lire une
       réponse PSP un peu longue, il fallait scroller DANS un cadre, au milieu
       d'une page qui scrolle déjà — inconfortable et source d'erreur quand on
       compare requête et réponse. Le payload s'affiche donc en entier ; les
       blocs <details> du viewer restent là pour replier ce qu'on ne lit pas.
       Seul l'axe horizontal garde un ascenseur, pour les valeurs très longues
       (jetons, URLs) qui ne doivent pas élargir la page. */
    font-size: var(--v2-text-14);
    line-height: 1.5;
    color: hsl(var(--v2-foreground));
    overflow-x: auto;
    /* No more white-space:pre — line breaks come from .jv-line block divs.
       This eliminates the double-spacing caused by `\n` + display:block. */
    white-space: normal;
    tab-size: 2;
}
.v2-jv--raw {
    white-space: pre-wrap;
    word-break: break-all;
    line-height: 1.4;
}

/* JSON tokens — punchy, high-contrast palette for readability */
.v2-jv .jv-key { color: hsl(214 95% 38%); font-weight: 600; }       /* deep blue */
.v2-jv .jv-string { color: hsl(142 71% 28%); }                        /* deep green */
.v2-jv .jv-number { color: hsl(28 92% 38%); font-weight: 500; }       /* deep orange */
.v2-jv .jv-bool { color: hsl(280 65% 42%); font-weight: 600; }        /* purple */
.v2-jv .jv-null { color: hsl(var(--v2-muted)); font-style: italic; }
.v2-jv .jv-bracket { color: hsl(var(--v2-foreground)); font-weight: 700; }
.v2-jv .jv-comma { color: hsl(var(--v2-muted)); margin-right: 0.05rem; }
.v2-jv .jv-colon { color: hsl(var(--v2-muted)); margin-right: 0.15rem; }

/* Each row is a tightly-packed block — no \n needed between lines. */
.v2-jv .jv-line {
    display: block;
    padding: 0;
    margin: 0;
    min-height: 1.35em;
    word-break: break-word;
}
.v2-jv .jv-line:hover {
    background: hsl(var(--v2-muted-bg) / 0.6);
    border-radius: var(--v2-radius-2xs);
}

/* Collapsible objects/arrays via native <details> */
.v2-jv .jv-collapsible {
    display: inline;
}
.v2-jv .jv-collapsible > summary {
    display: inline;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.v2-jv .jv-collapsible > summary::-webkit-details-marker { display: none; }
.v2-jv .jv-collapsible > summary::before {
    content: '▾';
    color: hsl(var(--v2-primary-500));
    font-size: var(--v2-text-10);
    margin-right: 0.25rem;
    display: inline-block;
}
.v2-jv .jv-collapsible:not([open]) > summary::before {
    content: '▸';
}
.v2-jv .jv-summary-meta {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-11);
    font-style: italic;
    margin-left: 0.4rem;
    font-weight: 400;
}
.v2-jv .jv-collapsible:not([open]) > .jv-line {
    display: none;
}

/* Notifications accordion polish (Bootstrap defaults are too noisy) */
.v2-notif-accordion .accordion-button {
    background: hsl(var(--v2-card));
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-14);
    padding: 0.65rem 1rem;
    box-shadow: none;
    border-radius: 0;
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.v2-notif-accordion .accordion-button:not(.collapsed) {
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-foreground));
    box-shadow: none;
}
.v2-notif-accordion .accordion-item {
    border: 1px solid hsl(var(--v2-border));
    margin-bottom: 0.5rem;
    border-radius: var(--v2-radius);
    overflow: hidden;
}
.v2-notif-accordion .accordion-body {
    padding: 1rem;
    background: hsl(var(--v2-card));
}

/* === Premium link styles (Sprint F V1→V2 merge — visibility pass) ===
   Apply .v2-link for inline links (with underline-on-hover) or .v2-link-btn
   for the "ghost button" style ("Voir tout le call →"). All V2 detail pages
   should use these instead of inline color: hsl(--v2-primary-500). */
.v2-link {
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
    border-bottom: 1px dashed hsl(var(--v2-primary-500) / 0.3);
    transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
    padding: 0.05rem 0.1rem;
    border-radius: var(--v2-radius-2xs);
}
.v2-link:hover {
    color: hsl(var(--v2-primary-700));
    border-bottom-color: hsl(var(--v2-primary-500));
    background: hsl(var(--v2-primary-50));
}
.v2-link--strong {
    font-weight: 600;
    border-bottom-style: solid;
}

/* Ghost button — for action links inside card headers ("Voir tout le call →"). */
.v2-link-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    color: hsl(var(--v2-primary-600));
    background: hsl(var(--v2-primary-50));
    border: 1px solid hsl(var(--v2-primary-500) / 0.25);
    padding: 0.35rem 0.7rem;
    border-radius: var(--v2-radius);
    font-size: var(--v2-text-13);
    font-weight: 500;
    text-decoration: none;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
    white-space: nowrap;
}
.v2-link-btn:hover {
    background: hsl(var(--v2-primary-100));
    border-color: hsl(var(--v2-primary-500));
    color: hsl(var(--v2-primary-700));
}

/* Auto-style anchors inside data lists / dl elements — these were too plain
   before. Color picks the primary blue and on-hover gets a soft background. */
.v2-chart-card__body dl a:not(.btn):not(.v2-link-btn) {
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
    font-weight: 500;
    border-bottom: 1px dashed hsl(var(--v2-primary-500) / 0.3);
    transition: all 0.12s ease;
    padding: 0.05rem 0.15rem;
    border-radius: var(--v2-radius-2xs);
}
.v2-chart-card__body dl a:not(.btn):not(.v2-link-btn):hover {
    background: hsl(var(--v2-primary-50));
    border-bottom-color: hsl(var(--v2-primary-500));
    color: hsl(var(--v2-primary-700));
}

/* Same auto-style on table cell links. */
.v2-table tbody a:not(.btn):not(.v2-link-btn) {
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
    font-weight: 500;
    transition: color 0.12s ease;
}
.v2-table tbody a:not(.btn):not(.v2-link-btn):hover {
    color: hsl(var(--v2-primary-700));
    text-decoration: underline;
}

/* === Generic table cell utilities — font cohesion ===
   All list/data tables share the same font-family (Geist, inherited from body)
   and the same base size (0.875rem from .v2-table tbody td).
   These classes only adjust weight / color / sub-size for semantic emphasis.
   Kept generic (cell-*) so they work in any list (Transactions, Sites, Users…). */

/* Primary identifier (link or text) — cyan when link, medium weight, NO monospace */
/* Identifiant de ligne (ID, nom, rang, mois de cohorte…).
   ⚠ Cette classe est portée AUSSI BIEN par des <a> que par des <span> inertes.
   Elle colorait tout en cyan et soulignait au survol sans distinction, donc la
   moitié des tableaux du backoffice promettait un clic qui n'existait pas :
   sur « Récurrence : drop-off par rang », les six libellés Achat initial /
   Rebill 1… ressemblaient à des liens et ne menaient nulle part.
   L'affordance de lien est donc désormais réservée aux vrais liens (voir la
   règle suivante). Les lignes réellement cliquables au niveau du <tr> gardent
   leur signal propre : cursor pointer + fond au survol
   (`.v2-table tbody tr:hover`).
   `.v2-tx-id` a été retiré du sélecteur : zéro usage dans templates/. */
.v2-cell-id {
    display: inline-block;
    font-family: inherit;          /* Geist, not monospace */
    font-size: inherit;            /* 0.875rem from td */
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: hsl(var(--v2-foreground));
    text-decoration: none;
    line-height: 1.2;
}
/* Le descendant `a .v2-cell-id` couvre le cas où le <span> est enveloppé dans
   un <a> parent (liste des partenaires : avatar + nom dans le même lien). */
a.v2-cell-id,
a .v2-cell-id {
    color: hsl(var(--v2-primary-600));
}
a.v2-cell-id:hover,
a:hover .v2-cell-id {
    color: hsl(var(--v2-primary-700));
    text-decoration: underline;
}

/* Sub-text under primary cell (TID truncated, company name, mail, website…) */
.v2-cell-sub,
.v2-tx-sub {
    font-family: inherit;
    font-size: var(--v2-text-12);            /* 12px — tight under the main 14px */
    font-weight: 400;
    color: hsl(var(--v2-muted));
    line-height: 1.3;
    margin-top: 0.125rem;
    font-variant-numeric: tabular-nums;
    /* Truncate gracefully when wide values like full TIDs are dropped in */
    max-width: 28ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Amount / numeric cell — readable medium weight, tabular for column alignment */
.v2-cell-amount,
.v2-tx-amount {
    font-family: inherit;
    font-size: inherit;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: hsl(var(--v2-foreground));
    white-space: nowrap;
}

/* Muted secondary cell (counts of sites, etc.) — same size as base, just muted */
.v2-cell-muted {
    font-family: inherit;
    font-size: inherit;
    color: hsl(var(--v2-muted));
}

/* ── Rebill · onglet Complétude des données ─────────────────────────────── */

/* Palette de statut — un seul jeu de variables pilote toute la couleur :
   --dc = teinte forte (texte/jauge/icône), --dc-bg = fond clair (chips/pastilles). */
/* Teintes de statut. Elles ne portent plus que la ligne de cause et le bandeau
   de synthèse : les pastilles sont passées au composant maison `Shared:Badge`,
   qui a ses propres rampes. Les valeurs sont alignées sur les tokens du système
   (--v2-success-600, --v2-warning-600, --v2-danger-600) au lieu des teintes
   locales d'origine : l'onglet affichait deux verts pour la même sémantique,
   le sien à 152° et le canonique à 142°, à 200 px l'un de l'autre. */
.v2-dc-banner--complete,
.v2-dc-row--complete { --dc: var(--v2-success-700); --dc-bg: 152 46% 95%; --dc-line: 152 40% 80%; }
.v2-dc-banner--partial,
.v2-dc-row--partial,
.v2-dc-row--invalid  { --dc: var(--v2-warning-800); --dc-bg: 40 92% 94%;  --dc-line: 38 82% 78%; }
.v2-dc-row--missing  { --dc: var(--v2-danger-700);  --dc-bg: 6 78% 96%;   --dc-line: 6 72% 84%; }
.v2-dc-banner--factor,
.v2-dc-row--factor   { --dc: var(--v2-danger-700);  --dc-bg: 348 82% 96%; --dc-line: 348 70% 85%; }

/* Bandeau de synthèse — pleine couleur de statut. */
.v2-dc-banner {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    margin-bottom: var(--v2-space-4);
    padding: 0.85rem 1.1rem;
    border: 1px solid hsl(var(--dc-line));
    border-radius: var(--v2-radius-lg);
    background: hsl(var(--dc-bg));
}
.v2-dc-banner__icon {
    display: flex;
    flex-shrink: 0;
    color: hsl(var(--dc));
}
.v2-dc-banner__text {
    margin: 0;
    font-size: var(--v2-text-13);
    line-height: 1.5;
    color: hsl(var(--v2-foreground));
}
.v2-dc-banner__text strong { color: hsl(var(--dc)); }

/* Scorecard — une carte par élément de donnée. */
/* Barre d'action du scorecard : bascule « Tout déplier / replier » (pilote
   tous les <details> via le contrôleur Stimulus details-group). */
.v2-dc-toolbar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--v2-space-3);
}
.v2-dc-toolbar__toggle {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 12px;
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-full);
    cursor: pointer;
    transition: background var(--v2-duration, 160ms) var(--v2-ease, ease),
                border-color var(--v2-duration, 160ms) var(--v2-ease, ease);
}
.v2-dc-toolbar__toggle:hover {
    background: hsl(var(--v2-muted-bg));
    border-color: hsl(var(--v2-border-strong));
}
.v2-dc-toolbar__toggle:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
}
.v2-dc-toolbar__icon {
    display: inline-flex;
    color: hsl(var(--v2-muted));
}
@media (prefers-reduced-motion: reduce) {
    .v2-dc-toolbar__toggle { transition: none; }
}

/* ── Liste de complétude : une ligne dépliable par élément ──────────────────
   Remplace la grille de cartes + la liste de disclosures qui traitaient la MÊME
   donnée de deux façons. La grille de cartes identiques et les carrés d'icônes
   teintés sont deux motifs explicitement bannis par PRODUCT.md, et la maison
   résout « N métriques proportionnelles » en lignes partout ailleurs (Paybox,
   MID/PSP, Santé opérationnelle).
   La ligne est un <details> natif : clavier et lecteur d'écran sans une ligne
   de JS. C'est ce qui a fait préférer la liste au <table> sémantique, qui
   aurait imposé de remplacer la disclosure par du script. */
.v2-dc-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.v2-dc-row {
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius);
    background: hsl(var(--v2-card));
    overflow: hidden;
}
.v2-dc-row__summary {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    cursor: pointer;
    list-style: none;
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-foreground));
    transition: background var(--v2-duration, 160ms) var(--v2-ease, ease);
}
.v2-dc-row__summary::-webkit-details-marker { display: none; }
.v2-dc-row__summary:hover { background: hsl(var(--v2-muted-bg) / 0.5); }
.v2-dc-row__summary:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: -2px;
}
/* Icône sans fond teinté : le canon maison (`.v2-insight__icon`) est un glyphe
   coloré sur fond neutre. Le statut est déjà porté par la pastille et par le
   chiffre, la teinte de fond était une redondance criarde. */
.v2-dc-row__icon {
    display: inline-flex;
    flex-shrink: 0;
    color: hsl(var(--dc, var(--v2-muted)));
}
.v2-dc-row__name {
    flex: 0 0 auto;
    min-width: 9rem;
    font-size: var(--v2-text-13);
}
.v2-dc-row__pct {
    margin-left: auto;
    flex-shrink: 0;
    font-size: var(--v2-text-16);
    font-variant-numeric: tabular-nums;
    color: hsl(var(--dc, var(--v2-foreground)));
}
.v2-dc-row__pct small { font-size: 0.7em; margin-left: 1px; }
.v2-dc-row .v2-dc-meter { flex: 0 1 140px; margin: 0; }
.v2-dc-row__split {
    flex: 0 0 auto;
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.v2-dc-row__chevron {
    display: inline-flex;
    flex-shrink: 0;
    color: hsl(var(--v2-muted));
    transition: transform var(--v2-duration, 160ms) var(--v2-ease, ease);
}
.v2-dc-row[open] .v2-dc-row__chevron { transform: rotate(180deg); }
.v2-dc-row__body {
    padding: 0 12px 12px;
    border-top: 1px solid hsl(var(--v2-border-subtle));
    padding-top: 10px;
}
/* Sous 720px la ligne se replie en deux niveaux : la ventilation chiffrée passe
   à la ligne plutôt que de comprimer la jauge jusqu'à l'illisible. */
@media (max-width: 720px) {
    .v2-dc-row__summary { flex-wrap: wrap; }
    .v2-dc-row__name { min-width: 0; flex: 1 1 auto; }
    .v2-dc-row__split { flex-basis: 100%; order: 10; }
    .v2-dc-row .v2-dc-meter { flex-basis: 100%; order: 9; }
}
@media (prefers-reduced-motion: reduce) {
    .v2-dc-row__summary,
    .v2-dc-row__chevron { transition: none; }
}

.v2-dc-meter {
    height: 9px;
    border-radius: var(--v2-radius-full);
    background: hsl(var(--v2-muted-bg));
    overflow: hidden;
}

/* Jauge 3 segments (complétude « vraie ») : valide (vert) puis bidon (ambre)
   posés sur le track « absent » (gris muted). Largeurs inline (template).
   Couleurs alignées sur les statuts complete/partial des cartes. */
.v2-dc-meter--3 {
    display: flex;
    gap: 1px;
    position: relative;
}
/* Marqueur du seuil de fiabilité configuré, posé sur la jauge à `left: seuil%`
   (style inline). Trait vertical sombre — l'absolu le sort du flux flex, donc il
   n'affecte pas les segments. La légende au-dessus explicite ce qu'il représente. */
.v2-dc-meter__threshold {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    margin-left: -1px;
    background: hsl(var(--v2-foreground) / 0.8);
    z-index: 2;
}
.v2-dc-meter__seg {
    display: block;
    height: 100%;
    transition: width var(--v2-duration, 160ms) var(--v2-ease, ease);
}
.v2-dc-meter__seg--valid { background: hsl(var(--v2-dc-valid)); }
.v2-dc-meter__seg--junk  { background: hsl(var(--v2-dc-junk)); }

/* Flag informationnel « données figées » (§4bis) — note neutre (gris), JAMAIS
   un encart coloré d'alerte : une valeur figée est souvent juste du test. */
.v2-dc-frozen {
    display: flex;
    gap: var(--v2-space-2, 8px);
    align-items: flex-start;
    margin-top: var(--v2-space-4, 16px);
    padding: var(--v2-space-3, 12px) var(--v2-space-4, 16px);
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius, 10px);
    background: hsl(var(--v2-muted-bg));
}
.v2-dc-frozen__icon {
    color: hsl(var(--v2-muted));
    margin-top: 1px;
    flex-shrink: 0;
}
.v2-dc-frozen__title {
    margin: 0 0 2px;
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
}
.v2-dc-frozen__text {
    margin: 0;
    font-size: var(--v2-text-12);
    line-height: 1.5;
    color: hsl(var(--v2-muted));
}
.v2-dc-frozen__text strong {
    color: hsl(var(--v2-foreground));
    font-weight: 600;
}
/* Callouts « facteur » (§5) : la causalité « comble ce champ → +X pts ».
   Ton affirmatif mais sobre — accent vert sur le gain, fond légèrement teinté. */
.v2-dc-factors {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-2, 8px);
    margin-bottom: var(--v2-space-4, 16px);
}
.v2-dc-factor {
    display: flex;
    gap: var(--v2-space-2, 8px);
    align-items: flex-start;
    padding: var(--v2-space-3, 12px) var(--v2-space-4, 16px);
    border: 1px solid hsl(var(--v2-primary-500) / 0.18);
    border-radius: var(--v2-radius, 10px);
    background: hsl(var(--v2-primary-50) / 0.5);
}
.v2-dc-factor__icon { color: hsl(var(--v2-dc-valid)); margin-top: 1px; flex-shrink: 0; }
.v2-dc-factor__text {
    margin: 0;
    font-size: var(--v2-text-13);
    line-height: 1.55;
    color: hsl(var(--v2-foreground));
}
.v2-dc-factor__gain { color: hsl(var(--v2-dc-valid-ink)); }
.v2-dc-factor__body { display: flex; flex-direction: column; gap: 3px; }
.v2-dc-factor__trend {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
}
.v2-dc-factor__trend svg { flex-shrink: 0; }
.v2-dc-factor__trend--up { color: hsl(var(--v2-dc-valid-ink)); }
/* Rappel des facteurs dans l'onglet Alertes + CTA vers Complétude. */
.v2-dc-alertlink { display: flex; flex-direction: column; gap: var(--v2-space-3, 12px); }
.v2-dc-alertlink__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.v2-dc-alertlink__item {
    display: flex; gap: 8px; align-items: center;
    font-size: var(--v2-text-13); color: hsl(var(--v2-foreground));
}
.v2-dc-alertlink__icon { color: hsl(var(--v2-dc-junk)); flex-shrink: 0; }
.v2-dc-alertlink__cta {
    align-self: flex-start;
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 12px;
    font-size: var(--v2-text-13); font-weight: 500;
    color: hsl(var(--v2-foreground));
    background: hsl(var(--v2-background, 0 0% 100%));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius, 10px);
    cursor: pointer;
    transition: background 160ms ease, border-color 160ms ease;
}
.v2-dc-alertlink__cta:hover { background: hsl(var(--v2-primary-50) / 0.6); border-color: hsl(var(--v2-primary-500) / 0.3); }

.v2-dc-mini {
    display: grid;
    grid-template-columns: 56px 1fr 48px;
    align-items: center;
    gap: var(--v2-space-2);
    font-size: var(--v2-text-12);
}
.v2-dc-mini__label { color: hsl(var(--v2-muted)); }
.v2-dc-mini__track {
    height: 7px;
    border-radius: var(--v2-radius-full);
    background: hsl(var(--v2-muted-bg));
    overflow: hidden;
}
.v2-dc-mini__fill {
    display: block;
    height: 100%;
    border-radius: var(--v2-radius-full);
    background: hsl(212 70% 54%);
}
.v2-dc-mini__val {
    text-align: right;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: hsl(var(--v2-foreground));
}
.v2-dc-row__audit {
    display: flex;
    align-items: flex-start;
    gap: 0.4rem;
    margin: 0.15rem 0 0;
    font-size: var(--v2-text-12);
    line-height: 1.5;
    color: hsl(var(--v2-muted));
}
.v2-dc-row__audit svg { flex-shrink: 0; margin-top: 2px; }
.v2-dc-row__audit strong { color: hsl(var(--v2-foreground)); }
.v2-dc-row__audit--factor { color: hsl(var(--dc)); }
.v2-dc-row__audit--factor strong { color: hsl(var(--dc)); }

/* Distill : le second niveau (critère, diversité, comparaison avec/sans) est
   replié sous les cartes « à surveiller » — la carte montre par défaut le
   headline décisionnel (% + jauge + cause), le reste s'ouvre au clic. Natif
   <details> : accessible clavier + lecteur d'écran, sans round-trip serveur. */

@media (prefers-reduced-motion: reduce) {
}

/* Distill : les éléments complets restent repliés sous les cartes des éléments
   en problème, mais chacun se déplie au clic (<details>) pour révéler son
   panneau de stats — la vue reste compacte ET inspectable à la demande. */

/* Élément complet dépliable. Replié = ligne compacte ; ouvert = révèle le
   panneau de stats partagé (macro statPanel). Le panneau lit --dc / --dc-bg
   (accent par statut) : on les déclare ici en vert « complet » puisque la
   disclosure n'est pas une .v2-dc-card--complete. Valeurs alignées sur
   .v2-dc-card--complete pour un rendu identique. */

@media (prefers-reduced-motion: reduce) {
}

/* Ligne « Valide = [critères] » : explique, par élément, ce que « valide »
   signifie (les règles diffèrent d'un élément à l'autre). Discrète, muted. */
.v2-dc-criteria {
    margin: -2px 0 2px;
    font-size: var(--v2-text-12);
    line-height: 1.45;
    color: hsl(var(--v2-muted));
}
.v2-dc-criteria__key {
    font-weight: 600;
    color: hsl(var(--v2-dc-valid-ink));
}

/* Ligne « Diversité » (§4bis) : la donnée varie-t-elle entre clients (réelle)
   ou est-elle figée pour tout le monde (templatée) ? Couleur par verdict. */
.v2-dc-diversity {
    display: flex;
    align-items: flex-start;
    gap: 5px;
    margin: 0 0 2px;
    font-size: var(--v2-text-12);
    line-height: 1.4;
    color: hsl(var(--v2-muted));
}
.v2-dc-diversity svg { flex-shrink: 0; margin-top: 1px; }
.v2-dc-diversity strong { font-weight: 600; color: hsl(var(--v2-foreground)); }
.v2-dc-diversity--frozen,
.v2-dc-diversity--frozen strong { color: hsl(var(--v2-danger-600)); }
.v2-dc-diversity--watch,
.v2-dc-diversity--watch strong { color: hsl(var(--v2-warning-800)); }
.v2-dc-diversity--varied strong { color: hsl(var(--v2-dc-valid-ink)); }

/* Légende du seuil de fiabilité — en tête du scorecard. Le trait `__mark`
   reprend le marqueur posé sur chaque jauge, pour que le lien soit évident. */
.v2-dc-legend {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px 10px;
    margin: 0 0 var(--v2-space-4);
    padding: var(--v2-space-2) var(--v2-space-3);
    font-size: var(--v2-text-13);
    line-height: 1.5;
    color: hsl(var(--v2-muted));
    background: hsl(var(--v2-muted-bg));
    border-radius: var(--v2-radius-md);
}
.v2-dc-legend strong { color: hsl(var(--v2-foreground)); }
.v2-dc-legend__mark {
    width: 2px;
    height: 14px;
    flex-shrink: 0;
    border-radius: var(--v2-radius-2xs);
    background: hsl(var(--v2-foreground) / 0.8);
}
.v2-dc-backlink { margin: 0 0 var(--v2-space-3); }

.v2-dc-legend__link {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 11px;
    font-size: var(--v2-text-12);
    font-weight: 500;
    /* Encre AA : ce contrôle est posé sur `--v2-muted-bg`, où le cyan de marque
       ne mesurait que 2,84:1. C'est le pire contraste de l'onglet, et c'est un
       élément INTERACTIF, donc celui qu'il faut le moins laisser passer. */
    color: hsl(var(--v2-primary-800));
    background: transparent;
    border: 1px solid hsl(var(--v2-primary-500) / 0.3);
    border-radius: var(--v2-radius-full);
    cursor: pointer;
    transition: background 160ms ease, border-color 160ms ease;
}
.v2-dc-legend__link:hover {
    background: hsl(var(--v2-primary-50) / 0.6);
    border-color: hsl(var(--v2-primary-500) / 0.5);
}
@media (prefers-reduced-motion: reduce) {
    .v2-dc-legend__link { transition: none; }
}

/* Sub-text variants — same .v2-cell-sub base, just different accent
   for technical (Inter+tabular-nums), brand (cyan), error (red) hints.
   Premium = ONE font family. We use tabular-nums for technical alignment
   instead of switching to monospace. */
.v2-cell-sub--mono {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    line-height: 1.3;
    margin-top: 0.125rem;
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'cv11', 'ss01', 'tnum';
}
/* Sous-texte qui doit rester LISIBLE ENTIER : la troncature par défaut convient
   à un TID, pas à une phrase dont le chiffre est justement l'information (ex.
   « + 29 autres libellés · celui-ci couvre 35,6 % » sous un message de refus).
   Même primitive, largeur libérée. */
.v2-cell-sub--wrap {
    display: block;
    max-width: none;
    overflow: visible;
    text-overflow: clip;
    white-space: normal;
}

.v2-cell-sub--accent {
    color: hsl(var(--v2-primary-600));
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    font-weight: 500;
    /* ⛔ `inline-flex` neutralise l'ellipse héritée de .v2-cell-sub (audit
       2026-07-25) : sur /dashboard/difference le PSP « Trustpayment Pionex
       PROD » était coupé net sur « Trustpaym », sans « … » ni title, alors
       que les sous-lignes voisines (simples div) s'ellipsaient bien — le
       nom du PSP devenait irrécupérable. .v2-cell-sub--mono, également en
       inline-flex, portait déjà ce triplet ; --accent l'avait juste oublié.
       `min-width: 0` est indispensable : sans lui l'item flex refuse de
       se réduire sous sa taille de contenu et rien ne se déclenche. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-cell-sub--danger {
    color: hsl(var(--v2-danger-600));
    font-size: var(--v2-text-11);
    margin-top: 0.25rem;
    line-height: 1.25;
    font-weight: 500;
    max-width: 32ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Operation cell — icon + label on same baseline */
.v2-op {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-weight: 500;
    color: hsl(var(--v2-foreground));
}

/* Inline flag chips for 3DS / 1-click / Direct */
.v2-flag {
    display: inline-flex;
    align-items: center;
    padding: 0.05rem 0.4rem;
    font-size: var(--v2-text-10);
    font-weight: 700;
    letter-spacing: 0.04em;
    border-radius: var(--v2-radius-xs);
    border: 1px solid transparent;
    text-transform: uppercase;
}
.v2-flag--3ds {
    background: hsl(142 71% 45% / 0.12);
    color: hsl(142 71% 28%);
    border-color: hsl(142 71% 45% / 0.3);
}
.v2-flag--1c {
    background: hsl(38 92% 50% / 0.12);
    color: hsl(38 92% 32%);
    border-color: hsl(38 92% 50% / 0.3);
}

/* === Rich table (FIXED layout — no wasted gaps between columns) ===
   `table-layout: fixed` makes columns honor their assigned widths exactly,
   instead of `auto` which inflates columns to consume "extra" space and
   creates ugly gaps between cell content and the next column. Combined with
   explicit % widths summing to 100% on the <th>, the table always fills
   100% of its container with NO empty horizontal space inside rows.

   ⛔ Ce layout fixe vivait ICI, donc s'appliquait à TOUTE table --rich
   (audit 2026-07-25) — y compris aux trois qui n'ont jamais eu de carte de
   largeurs. Sans carte, `fixed` divise la place en parts ÉGALES : sur
   /dashboard/difference, 9 colonnes à 102 px, la colonne Δ (qui n'affiche
   qu'un « – ») aussi large que « Site / Partenaire » dont le nom de site
   était tronqué. Et le `min-width: 0` annulait le plancher de 600 px de
   .v2-table, donc la carte ne défilait JAMAIS : elle se comprimait jusqu'à
   couper le texte (96 cellules rognées à 1280 px, `overflow: hidden` sans
   ellipse).

   Le layout fixe est désormais porté par --cols, que seules les tables
   réellement cartographiées déclarent. Les autres retrouvent le layout
   auto (colonnes dimensionnées par leur contenu) et le plancher de scroll.
   Toute nouvelle table --rich doit donc ajouter --cols ET sa carte, ou ne
   déclarer ni l'un ni l'autre. */
.v2-table--rich {
    width: 100%;
}
.v2-table--cols {
    table-layout: fixed;
    min-width: 0;
}
.v2-table--rich tbody td {
    padding-top: var(--v2-space-3);
    padding-bottom: var(--v2-space-3);
    vertical-align: top;
    /* No mid-word breaking — keeps "Paiement", "Autorisation" intact.
       Long technical strings (TIDs, emails) still wrap via overflow-wrap on
       the .v2-cell-sub class which has its own ellipsis. */
    word-break: normal;
    overflow-wrap: break-word;
    /* With fixed table-layout, cells must contain their overflow rather than
       expand. Sub-text uses its own ellipsis; main cell text wraps cleanly. */
    overflow: hidden;
}
/* Primary cell text (links like site name, client name) — single line
   with ellipsis so column widths stay strict. */
.v2-table--rich tbody td > a:not(.btn) {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: middle;
}

/* Operation cell label NEVER wraps mid-word (Paiement / Autorisation
   are too short to need wrapping anyway). */
.v2-table--rich .v2-op,
.v2-table--rich .v2-op span {
    white-space: nowrap;
}
/* Flags (3DS / 1-click / Direct) inline, no wrap */
.v2-table--rich .v2-flag {
    white-space: nowrap;
}
/* The "Source" sub-info wraps in the Client cell instead of overflowing */
.v2-table--rich .v2-cell-sub {
    max-width: 100%;
}

/* Per-column FIXED widths (sum = 100%). With `table-layout: fixed` the
   browser respects these exactly — content overflowing a cell gets
   truncated with the cell's text-overflow rules (ellipsis on sub-text). */
.v2-col-ref     { width: 14%; }   /* Référence + date + tid */
.v2-col-site    { width: 17%; }   /* Site / Partner / PSP */
.v2-col-op      { width: 12%; }   /* Operation + flags */
.v2-col-state   { width: 14%; }   /* Badge + error message */
.v2-col-amount  { width: 12%; }   /* Amount + card */
.v2-col-client  { width: 22%; }   /* Name + email + IP/country */
.v2-col-actions { width: 9%; min-width: 88px; }
/* Total: 100% — no wasted gaps */

/* When Client is hidden (priority-tertiary, <1280px), redistribute its
   22% across the remaining columns proportionally. Client width 22% goes:
   +6% Site, +5% State, +5% Amount, +3% Op, +2% Ref, +1% Actions. */
@media (max-width: 1280px) {
    .v2-col-ref     { width: 16%; }
    .v2-col-site    { width: 23%; }
    .v2-col-op      { width: 15%; }
    .v2-col-state   { width: 19%; }
    .v2-col-amount  { width: 17%; }
    .v2-col-actions { width: 10%; }

    /* ⛔ Cette règle MANQUAIT (audit 2026-07-25). Les largeurs ci-dessus
       somment 100 % en supposant Client masqué — mais rien ne le masquait :
       la table réclamait 122 %, chaque colonne perdait ~18 % de sa largeur et
       `overflow: hidden` coupait le texte en plein milieu d'un mot. Le
       commentaire de la section « Card grid » décrivait déjà ce masquage
       comme acquis. */
    .v2-table--rich .v2-col-priority-tertiary {
        display: none;
    }
}

/* When Site/Operation also hidden (≤980px), redistribute again */
@media (max-width: 979px) {
    .v2-col-ref     { width: 30%; }
    .v2-col-state   { width: 30%; }
    .v2-col-amount  { width: 25%; }
    .v2-col-actions { width: 15%; }

    /* ⛔ Idem : sans cette règle la table réclamait 160 % de sa largeur
       (100 % + Site 23 % + Op 15 % + Client 22 %). */
    .v2-table--rich .v2-col-priority-secondary {
        display: none;
    }
}

/* === Sticky Actions column (always visible) ===
   The "voir le détail" / refund icons must NEVER be clipped, even on a
   narrow viewport where the table overflows horizontally. We pin the last
   column to the right edge of the card body using `position: sticky`.
   This is the same pattern Stripe uses for transaction tables. */
.v2-table--rich thead th.v2-col-actions,
.v2-table--rich tbody td:last-child {
    position: sticky;
    right: 0;
    background: hsl(var(--v2-card));
    /* Subtle left-edge shadow to mark the boundary when the row scrolls
       behind it — a premium touch (Stripe / Linear pattern). */
    box-shadow: -3px 0 6px -3px hsl(var(--v2-foreground) / 0.06);
    z-index: 4;
}
.v2-table--rich thead th.v2-col-actions {
    background: hsl(var(--v2-muted-bg) / 0.5);
    z-index: 12;                     /* Above the sticky thead row */
}
/* Hover state — the sticky cell needs the same row hover background or
   it will look "out of sync" with the rest of the row. */
.v2-table--rich tbody tr:hover td:last-child {
    background: hsl(var(--v2-primary-50) / 0.5);
}

/* Safety net : horizontal scroll on the card body so the table can be
   wider than the card. With the sticky Actions column above, the user
   keeps access to the action buttons no matter the scroll position. */
.v2-chart-card:has(.v2-table--rich) > .v2-chart-card__body {
    overflow-x: auto;
}

/* ════════════════════════════════════════════════════════════════
   Card grid — tablet & mobile only (<900px viewport)
   On true tablet/mobile, the 7-col table doesn't make sense. Each <tr> is
   reflowed into a CSS grid where each row becomes a card with a clear
   visual hierarchy. On desktop (≥900px) the table view is kept — it stays
   readable because columns hide progressively via the priority classes
   (`.v2-col-priority-tertiary` ≤1280px hides Client, `.v2-col-priority-secondary`
   ≤980px hides Site/Op) and any residual overflow scrolls inside the card.
   ════════════════════════════════════════════════════════════════ */
@media (max-width: 900px) {
    /* Re-flow the table into a vertical stack of cards */
    .v2-table--rich {
        display: block;
        background: transparent;
        border: 0;
        box-shadow: none;
        min-width: 0;
    }
    .v2-table--rich thead {
        display: none;                       /* No headers in card mode */
    }
    .v2-table--rich tbody {
        display: flex;
        flex-direction: column;
        gap: var(--v2-space-2);
    }
    .v2-table--rich tbody tr {
        display: grid;
        grid-template-columns: 1fr auto;
        grid-template-areas:
            "ref     amount"
            "site    site"
            "op      state"
            "client  client"
            "actions actions";
        gap: var(--v2-space-2) var(--v2-space-3);
        background: hsl(var(--v2-card));
        border: 1px solid hsl(var(--v2-border-default));
        border-radius: var(--v2-radius-md);
        padding: var(--v2-space-3) var(--v2-space-4);
        box-shadow: var(--v2-shadow-xs);
        transition: box-shadow var(--v2-duration) var(--v2-ease),
                    border-color var(--v2-duration) var(--v2-ease);
    }
    .v2-table--rich tbody tr:hover {
        box-shadow: var(--v2-shadow-sm);
        border-color: hsl(var(--v2-primary-500) / 0.4);
    }
    .v2-table--rich tbody tr td {
        display: block;
        border: 0;
        padding: 0;
        background: transparent;
    }
    /* Map cells to grid areas — order from the template :
         1=Ref+Date  2=Site/Partner  3=Operation  4=State  5=Amount  6=Client  7=Actions */
    .v2-table--rich tbody td:nth-child(1) { grid-area: ref; }
    .v2-table--rich tbody td:nth-child(2) { grid-area: site; }
    .v2-table--rich tbody td:nth-child(3) { grid-area: op; }
    .v2-table--rich tbody td:nth-child(4) { grid-area: state; text-align: right; }
    .v2-table--rich tbody td:nth-child(5) { grid-area: amount; text-align: right; align-self: start; }
    .v2-table--rich tbody td:nth-child(6) { grid-area: client; }
    .v2-table--rich tbody td:nth-child(7) { grid-area: actions; text-align: right; }

    /* Override the v2-col-priority-* "display: none" — in card mode we WANT
       ces cellules (ce sont des lignes de texte dans la carte, plus des
       colonnes). `secondary` est inclus depuis que le masquage ≤979px existe
       réellement : le mode carte vit SOUS ce palier, il perdrait sinon Site
       et Opération, deux zones que sa grille place explicitement. */
    .v2-table--rich tbody td.v2-col-priority-tertiary,
    .v2-table--rich tbody td.v2-col-priority-secondary {
        display: block !important;
    }

    /* Card-specific styling refinements */
    .v2-table--rich tbody tr:hover td:first-child {
        background: transparent;             /* kill the desktop hover bg */
    }
    /* Sub-text wraps naturally in cards (vs ellipsis in narrow columns) */
    .v2-table--rich .v2-cell-sub {
        max-width: 100%;
        white-space: normal;
    }
    /* Amount stays prominent */
    .v2-table--rich .v2-cell-amount {
        font-size: var(--v2-text-16);                /* 17px — bigger in card view */
        font-weight: 700;
    }
    /* ID link feels more like a card header */
    .v2-table--rich .v2-cell-id {
        font-size: var(--v2-text-16);
    }
    /* Empty state cell takes full width */
    .v2-table--rich tbody td[colspan] {
        grid-column: 1 / -1;
        text-align: center;
    }
    /* Pagination block underneath shouldn't get the card treatment */
    .v2-table--rich tbody tr:has(.v2-empty-state) {
        background: transparent;
        border: 0;
        box-shadow: none;
        padding: 0;
    }
}

/* Even narrower — single column inside the card (state goes below operation) */
@media (max-width: 560px) {
    .v2-table--rich tbody tr {
        grid-template-columns: 1fr;
        grid-template-areas:
            "ref"
            "amount"
            "site"
            "op"
            "state"
            "client"
            "actions";
    }
    .v2-table--rich tbody td:nth-child(4) { text-align: left; }
    .v2-table--rich tbody td:nth-child(5) { text-align: left; }
    .v2-table--rich .v2-cell-amount {
        font-size: var(--v2-text-20);                  /* even bigger on phone */
    }
}

/* When in card mode (≤900px), the surrounding ChartCard body shouldn't add
   extra padding/border (the cards have their own). Make the wrapper transparent. */
@media (max-width: 900px) {
    .v2-chart-card:has(.v2-table--rich) {
        background: transparent;
        border: 0;
        box-shadow: none;
        overflow: visible;
    }
    .v2-chart-card:has(.v2-table--rich) > .v2-chart-card__header {
        padding: 0 0 var(--v2-space-3);     /* No side padding, less bottom */
    }
    .v2-chart-card:has(.v2-table--rich) > .v2-chart-card__body {
        padding: 0;
        overflow: visible;
    }
}

/* ════════════════════════════════════════════════════════════════
   Calls technical table (/dashboard/call) — same rich system, parité
   /dashboard/transaction.
   Réutilise v2-table--rich (table-layout fixed, colonne Actions sticky,
   reflow carte en mobile) avec des colonnes propres aux logs d'appels API,
   pour que la page atteigne la même présentation que les transactions :
   famille unique Geist (AUCUN monospace par cellule), colonne Référence
   menée par un #id cyan, et le même comportement responsive.
   Ajouté 2026-06-02 (polish parité call / transaction).
   ════════════════════════════════════════════════════════════════ */
.v2-col-call-ref     { width: 15%; }   /* #id + date + uuid      */
.v2-col-call-url     { width: 27%; }   /* URL endpoint (primary) */
.v2-col-call-site    { width: 17%; }   /* site / partenaire      */
.v2-col-call-ip      { width: 12%; }   /* IP client              */
.v2-col-call-code    { width: 9%;  }   /* badge code HTTP        */
.v2-col-call-dur     { width: 9%;  }   /* durée ms               */
.v2-col-call-actions { width: 11%; min-width: 88px; }
/* total = 100% — aucune marge horizontale perdue, aucun débordement */

/* Plancher de la table des appels — même raison que .v2-table--diff : --cols
   apporte `min-width: 0`, donc sans plancher la table se comprime au lieu de
   laisser défiler son wrapper, et les colonnes finissent par rogner leur
   contenu.

   ⚠ Ce 800 px n'est PAS une estimation : il est DÉRIVÉ de la contrainte
   déclarée juste au-dessus. Pour que la colonne Actions obtienne les 88 px
   qu'elle réclame en occupant 11 % de la table, celle-ci doit mesurer au
   minimum 88 / 0,11 = 800 px. En-deçà, la largeur demandée et la largeur
   accordée se contredisent.
   (Contrairement au plancher des Écarts, celui-ci n'a pas pu être mesuré sur
   la page : elle est réservée aux administrateurs.) */
.v2-table--calls { min-width: 800px; }

/* ════════════════════════════════════════════════════════════════
   Écarts de réconciliation (/dashboard/difference) — 9 colonnes.
   Même système cartographié que transactions et appels.

   Les pourcentages ne sont pas choisis à l'estime : ils viennent de la
   largeur RÉELLEMENT nécessaire à chaque colonne, mesurée sur les données
   de production (total 982 px), puis normalisée. D'où le plancher à
   1020 px : à neuf colonnes cette table NE TIENT PAS sous ~1330 px de
   fenêtre, et c'est précisément le cas où le wrapper .v2-table-scroll doit
   défiler au lieu de laisser les colonnes s'écraser.
   Vérifié à 1024 / 1280 / 1600 px : zéro cellule rognée, la carte défile,
   la page jamais.
   ════════════════════════════════════════════════════════════════ */
.v2-table--diff             { min-width: 1060px; }
.v2-col-diff-ref            { width: 12.5%; }   /* #id + dates + TID Ez/PSP */
.v2-col-diff-site           { width: 14.5%; }   /* site + partenaire + PSP  */
.v2-col-diff-type           { width: 11%; }     /* badges type d'écart      */
.v2-col-diff-state          { width: 10.5%; }   /* badge état transaction   */
.v2-col-diff-amount         { width: 10%; }     /* montants (×2)            */
.v2-col-diff-delta          { width: 9%; }      /* Δ — voir ci-dessous      */
.v2-col-diff-status         { width: 10.5%; }   /* badge + assigné          */
.v2-col-diff-actions        { width: 12%; }     /* groupe de 2 boutons      */
/* total = 100% */

/* ⚠ La colonne Δ valait 7 % et le plancher 1020 px, soit 71 px dont 39 px de
   boîte de contenu une fois les paddings retirés. La mesure en production ne
   signalait aucune troncature — mais UNIQUEMENT parce que tous les écarts
   affichaient « – ». Un écart réel, « +12,50 € », réclame ~58 px et il est
   INSÉCABLE : séparateur de milliers et espace avant l'euro sont des espaces
   insécables, et .v2-cell-amount est en white-space: nowrap sans ellipse.
   Il aurait été rogné net le jour où un écart apparaît.
   Δ passe donc à 9 % et le plancher à 1060 px, chaque colonne conservant une
   marge sur son besoin mesuré (la plus juste, État et Statut, garde 3,3 px). */

/* Reflow carte (<900px) — grid areas propres aux écarts. Même override que
   .v2-table--calls : la map générique v2-table--rich est câblée pour les SEPT
   colonnes des transactions et assigne par nth-child, donc sur nos NEUF
   colonnes le Δ atterrissait dans la zone « actions » pendant que les vrais
   boutons tombaient hors gabarit, auto-placés en fin de carte.

   ⛔ Et surtout : `min-width: 0`. Le plancher de 1020 px ci-dessus est déclaré
   au niveau racine, APRÈS le bloc mode carte qui pose min-width: 0 sur
   .v2-table--rich. À spécificité égale (une classe de chaque côté) c'est
   l'ordre source qui tranche, donc le plancher l'emportait jusque dans le mode
   carte et forçait la pile de cartes à 1020 px — ~645 px de débordement sur un
   écran de 375 px. Les deux classes ici l'emportent sans dépendre de l'ordre. */
@media (max-width: 900px) {
    .v2-table--rich.v2-table--diff {
        min-width: 0;
    }
    .v2-table--rich.v2-table--diff tbody tr {
        grid-template-areas:
            "ref     ezengo"
            "site    site"
            "type    etat"
            "psp     delta"
            "statut  statut"
            "actions actions";
    }
    .v2-table--rich.v2-table--diff tbody td:nth-child(1) { grid-area: ref;     text-align: left; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(2) { grid-area: site;    text-align: left; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(3) { grid-area: type;    text-align: left; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(4) { grid-area: etat;    text-align: right; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(5) { grid-area: ezengo;  text-align: right; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(6) { grid-area: psp;     text-align: left; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(7) { grid-area: delta;   text-align: right; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(8) { grid-area: statut;  text-align: left; }
    .v2-table--rich.v2-table--diff tbody td:nth-child(9) { grid-area: actions; text-align: right; }
}

/* URL endpoint — famille unique Geist (jamais monospace), chiffres tabular
   pour aligner les caractères, troncature ellipsis dans la colonne fixe.
   Remplace l'ancien <code> (Geist Mono) qui jurait avec le reste du tableau. */
.v2-call-url {
    display: block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum', 'cv11';
}
/* IP client — même famille unique, juste tabular pour aligner les octets. */
.v2-call-ip {
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum', 'cv11';
    white-space: nowrap;
}
/* Unité « ms » discrète accolée à la durée (équivalent visuel du suffixe
   monétaire sur la colonne Montant des transactions). */
.v2-call-unit {
    font-weight: 400;
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    margin-left: 2px;
}

/* Reflow carte (<900px) — grid areas propres aux appels. Override de la map
   générique v2-table--rich (câblée pour les 7 colonnes des transactions) pour
   que chaque cellule d'appel tombe dans la bonne zone. La spécificité (classe
   .v2-table--calls en plus) l'emporte sur les règles génériques. */
@media (max-width: 900px) {
    /* ⛔ Le plancher de 800 px doit être LEVÉ en mode carte, sinon la pile de
       cartes est forcée à 800 px sur un écran de 375 et déborde de plus de
       400 px. C'est exactement la régression commise sur .v2-table--diff : le
       plancher est déclaré au niveau racine, donc APRÈS ce bloc, et à
       spécificité égale l'ordre source le ferait gagner. Les deux classes ici
       l'emportent sans dépendre de l'ordre. */
    .v2-table--rich.v2-table--calls {
        min-width: 0;
    }
    .v2-table--rich.v2-table--calls tbody tr {
        grid-template-areas:
            "ref     code"
            "url     url"
            "site    dur"
            "ip      ip"
            "actions actions";
    }
    .v2-table--rich.v2-table--calls tbody td:nth-child(1) { grid-area: ref;     text-align: left; }
    .v2-table--rich.v2-table--calls tbody td:nth-child(2) { grid-area: url;     text-align: left; }
    .v2-table--rich.v2-table--calls tbody td:nth-child(3) { grid-area: site;    text-align: left; }
    .v2-table--rich.v2-table--calls tbody td:nth-child(4) { grid-area: ip;      text-align: left; }
    .v2-table--rich.v2-table--calls tbody td:nth-child(5) { grid-area: code;    text-align: left; }
    .v2-table--rich.v2-table--calls tbody td:nth-child(6) { grid-area: dur;     text-align: right; }
    .v2-table--rich.v2-table--calls tbody td:nth-child(7) { grid-area: actions; text-align: right; }
    /* En carte l'URL occupe toute la largeur : on la laisse wrapper (pas d'ellipsis) */
    .v2-table--rich.v2-table--calls .v2-call-url { white-space: normal; }
}

/* Encore plus étroit — colonne unique empilée */
@media (max-width: 560px) {
    .v2-table--rich.v2-table--calls tbody tr {
        grid-template-columns: 1fr;
        grid-template-areas:
            "ref"
            "url"
            "site"
            "ip"
            "code"
            "dur"
            "actions";
    }
    .v2-table--rich.v2-table--calls tbody td:nth-child(5),
    .v2-table--rich.v2-table--calls tbody td:nth-child(6) { text-align: left; }
}

/* === Lucide icons (Axe 4 — premium icon styling) ===
   Lucide ships at 24x24 with stroke-width:2. Premium dashboards use 1.75
   for a slightly thinner, more refined look. Force inheritance of currentColor
   so icons in buttons/links pick up the text color. */
body.v2-dashboard svg.iconify,
body.v2-dashboard .v2-main svg {
    stroke-width: 1.75;
    color: currentColor;
    flex-shrink: 0;
    vertical-align: middle;
}

/* === Section system (Axe 3 — Stripe/Linear-style flat sections) ===
   The premium pattern: instead of 8 stacked cards (each a box with its own
   shadow + border), use lightweight "sections" — title + thin divider +
   content directly on the page. Cards reserved for interactive elements
   (KPI tile, chart, table, form). #5 visually dramatic improvement. */
.v2-section {
    margin-bottom: var(--v2-space-8);
}
.v2-section:last-child {
    margin-bottom: 0;
}
.v2-section__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--v2-space-4);
    padding-bottom: var(--v2-space-3);
    margin-bottom: var(--v2-space-4);
    border-bottom: 1px solid hsl(var(--v2-border-subtle));
}
.v2-section__title {
    font-size: var(--v2-text-16);
    font-weight: 600;
    letter-spacing: -0.014em;
    color: hsl(var(--v2-foreground));
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-section__subtitle {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    margin: 2px 0 0;
    letter-spacing: -0.005em;
}
.v2-section__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-section__body {
    /* No box, no shadow — content flows directly on the page background */
}

/* === Detail-page layout (Axe 3 — Stripe pattern: 2/3 main + 1/3 sidebar) ===
   For pages like /transactions/{id}, /sites/{id}, /companies/{id}.
   Main column = the content people read (identity, error, payloads).
   Side column = quick metadata (status, amount, links). Stacks on tablet. */
.v2-detail-grid {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
    gap: var(--v2-space-6);
    align-items: start;
}
@media (max-width: 1100px) {
    .v2-detail-grid {
        grid-template-columns: 1fr;
        gap: var(--v2-space-4);
    }
}
.v2-detail-grid__main {
    min-width: 0;
}
.v2-detail-grid__side {
    min-width: 0;
    position: sticky;
    top: var(--v2-space-4);
}
@media (max-width: 1100px) {
    .v2-detail-grid__side {
        position: static;
    }
}

/* === Hero block (compact premium banner) ===
   Single-line layout : label + amount + meta + score gauge inline.
   Reduced padding / font-size to avoid wasting vertical space above
   the fold — the goal is to keep KPIs visible without scroll. */
.v2-hero {
    background: linear-gradient(180deg,
        hsl(var(--v2-card)) 0%,
        hsl(var(--v2-muted-bg) / 0.4) 100%);
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    padding: var(--v2-space-3) var(--v2-space-5);    /* 12px x 20px (was 24x24) */
    margin-bottom: var(--v2-space-4);                /* 16px (was 24) */
    display: flex;
    align-items: center;                              /* center align (was flex-start) */
    gap: var(--v2-space-5);
    flex-wrap: wrap;
    box-shadow: var(--v2-shadow-xs);
}
.v2-hero__main {
    flex: 1 1 240px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-1);                          /* tight spacing inside */
}
.v2-hero__label {
    font-size: var(--v2-text-12);
    text-transform: none;
    letter-spacing: -0.005em;
    font-weight: 500;
    color: hsl(var(--v2-muted));
}
.v2-hero__amount {
    font-size: clamp(1.5rem, 1.6vw + 0.5rem, 2rem);  /* 24-32px (was 30-42) */
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.025em;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'cv11', 'ss01', 'tnum';
    margin: 0;
}
.v2-hero__badges {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    flex-wrap: wrap;
    margin-top: var(--v2-space-1);
}
.v2-hero__meta {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    flex-wrap: wrap;
    font-size: var(--v2-text-12);                               /* slightly smaller (was 0.8125) */
    color: hsl(var(--v2-muted));
}
.v2-hero__meta-item {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-1);
}
.v2-hero__meta-sep {
    color: hsl(var(--v2-border-strong));
}
.v2-hero__actions {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-2);
    align-items: stretch;
    min-width: auto;                                  /* removed forced 200px */
}
@media (max-width: 768px) {
    .v2-hero__actions {
        min-width: 100%;
        flex-direction: row;
        flex-wrap: wrap;
    }
}

/* === Side panel (Axe 3 — used in detail-grid__side) ===
   A vertical list of label/value pairs WITHOUT being a card. Just a thin
   left border (cyan accent) for the title, then plain key/value pairs.
   Mimics the "Order details" sidebar in Stripe. */
.v2-side-panel {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-5);
    padding: var(--v2-space-5);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-xs);
}
.v2-side-panel__group {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-2);
}
.v2-side-panel__group-title {
    font-size: var(--v2-text-11);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: hsl(var(--v2-muted));
    margin: 0;
}
.v2-side-panel__row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--v2-space-3);
    font-size: var(--v2-text-14);
}
.v2-side-panel__label {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-13);
    flex-shrink: 0;
}
.v2-side-panel__value {
    color: hsl(var(--v2-foreground));
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    text-align: right;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.v2-side-panel__value a {
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
}
.v2-side-panel__value a:hover {
    text-decoration: underline;
    color: hsl(var(--v2-primary-700));
}

/* ════════════════════════════════════════════════════════════════
   Axe 5 — Premium polish
   The 1% details that make 50% of the "this feels handcrafted" feeling.
   ════════════════════════════════════════════════════════════════ */

/* === Universal focus ring (cyan, never browser default) ===
   Premium dashboards have a SINGLE focus style — visible but not jarring.
   2px outline with 2px offset is the modern standard (Vercel, Linear). */
body.v2-dashboard *:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
    border-radius: var(--v2-radius-sm);
    transition: outline-offset var(--v2-duration-fast) var(--v2-ease);
}
body.v2-dashboard button:focus-visible,
body.v2-dashboard a:focus-visible {
    outline-offset: 3px;
}
/* Inputs already have their own focus ring via box-shadow — kill double ring */
body.v2-dashboard input:focus-visible,
body.v2-dashboard select:focus-visible,
body.v2-dashboard textarea:focus-visible {
    outline: none;
}

/* === Sticky table headers (premium UX in long lists) === */
.v2-table thead th {
    position: sticky;
    top: 0;
    z-index: 10;
    /* Backup background to mask scrolling rows underneath */
    background-clip: padding-box;
}

/* === Premium row hover — entire tr highlighted, smooth transition === */
.v2-table tbody tr {
    cursor: default;
}
.v2-table tbody tr:hover {
    background: hsl(var(--v2-primary-50) / 0.5);
}
.v2-table tbody tr:hover td {
    background: transparent;
}

/* === Premium empty states === */
.v2-empty-state {
    text-align: center;
    padding: var(--v2-space-12) var(--v2-space-6);
    color: hsl(var(--v2-muted));
}
.v2-empty-state__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-muted));
    margin-bottom: var(--v2-space-4);
}
.v2-empty-state__title {
    font-size: var(--v2-text-16);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    letter-spacing: -0.014em;
    margin: 0 0 var(--v2-space-1);
}
.v2-empty-state__hint {
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-muted));
    margin: 0;
    max-width: 32rem;
    margin-left: auto;
    margin-right: auto;
}

/* === Skeleton loaders (premium loading state, not spinners) === */
@keyframes v2-skeleton {
    0% { background-position: -200px 0; }
    100% { background-position: calc(200px + 100%) 0; }
}
.v2-skeleton {
    display: inline-block;
    background: linear-gradient(90deg,
        hsl(var(--v2-muted-bg)) 0%,
        hsl(var(--v2-muted-bg) / 0.6) 50%,
        hsl(var(--v2-muted-bg)) 100%);
    background-size: 200px 100%;
    background-repeat: no-repeat;
    border-radius: var(--v2-radius-sm);
    height: 1em;
    width: 100%;
    min-width: 60px;
    animation: v2-skeleton 1.4s ease-in-out infinite;
    color: transparent;
    user-select: none;
}
.v2-skeleton--lg { height: 1.5em; }
.v2-skeleton--xl { height: 2em; }
.v2-skeleton--circle { border-radius: 50%; aspect-ratio: 1; }

/* === Modal animations (premium fade + slide-up) === */
.v2-main .modal.fade .modal-dialog,
.modal.fade .v2-modal .modal-dialog {
    transition: transform var(--v2-duration) var(--v2-ease),
                opacity var(--v2-duration) var(--v2-ease);
    transform: translateY(20px);
    opacity: 0;
}
.v2-main .modal.show .modal-dialog,
.modal.show .v2-modal .modal-dialog {
    transform: translateY(0);
    opacity: 1;
}
.v2-modal .modal-content {
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-lg);
}

/* === Page enter animation (subtle fade-in on page load) === */
@keyframes v2-fade-in-up {
    from {
        opacity: 0;
        transform: translateY(8px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
body.v2-dashboard .v2-main > * {
    animation: v2-fade-in-up var(--v2-duration-slow) var(--v2-ease) backwards;
}
body.v2-dashboard .v2-main > *:nth-child(1) { animation-delay: 0ms; }
body.v2-dashboard .v2-main > *:nth-child(2) { animation-delay: 40ms; }
body.v2-dashboard .v2-main > *:nth-child(3) { animation-delay: 80ms; }
body.v2-dashboard .v2-main > *:nth-child(4) { animation-delay: 120ms; }
body.v2-dashboard .v2-main > *:nth-child(5) { animation-delay: 160ms; }
body.v2-dashboard .v2-main > *:nth-child(n+6) { animation-delay: 200ms; }

/* === Toast notifications (premium, top-right corner) === */
.v2-toast-container {
    position: fixed;
    top: var(--v2-space-4);
    right: var(--v2-space-4);
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-2);
    pointer-events: none;
}
.v2-toast {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2-shadow-md);
    padding: var(--v2-space-3) var(--v2-space-4);
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    min-width: 280px;
    max-width: 420px;
    pointer-events: auto;
    animation: v2-toast-in var(--v2-duration) var(--v2-ease);
    font-size: var(--v2-text-14);
}
@keyframes v2-toast-in {
    from { transform: translateX(100%) ; opacity: 0; }
    to   { transform: translateX(0); opacity: 1; }
}
.v2-toast--success {
    border-color: hsl(var(--v2-success-500) / 0.5);
}
.v2-toast--danger {
    border-color: hsl(var(--v2-danger-500) / 0.5);
}
.v2-toast--info {
    border-color: hsl(var(--v2-primary-500) / 0.5);
}

/* === Premium scrollbar (subtle, modern) === */
body.v2-dashboard *::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}
body.v2-dashboard *::-webkit-scrollbar-track {
    background: transparent;
}
body.v2-dashboard *::-webkit-scrollbar-thumb {
    background: hsl(var(--v2-border-strong) / 0.5);
    border-radius: var(--v2-radius-xs);
    border: 2px solid transparent;
    background-clip: padding-box;
}
body.v2-dashboard *::-webkit-scrollbar-thumb:hover {
    background: hsl(var(--v2-border-strong) / 0.8);
    background-clip: padding-box;
    border: 2px solid transparent;
}

/* === Selection color (premium cyan instead of browser blue) === */
body.v2-dashboard ::selection {
    background: hsl(var(--v2-primary-500) / 0.25);
    color: hsl(var(--v2-foreground));
}

/* === Reduce motion : respect user preference === */
@media (prefers-reduced-motion: reduce) {
    body.v2-dashboard *,
    body.v2-dashboard *::before,
    body.v2-dashboard *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* ════════════════════════════════════════════════════════════════
   Live Component loading indicators — premium feedback
   When a filter changes, the user must SEE that something is happening.
   3 visual cues stack:
     1. Top progress bar (YouTube/Linear style) — 2px cyan, shimmer animation
     2. Table dim (.v2-loading) — 60% opacity + pointer-events:none
     3. Inline spinner in filter inputs that triggered the change
   ════════════════════════════════════════════════════════════════ */

/* === Top progress bar (premium, vibrant) ===
   4px tall, saturated cyan with glow + sliding indeterminate animation.
   Pattern : YouTube / Vercel / Linear premium loaders. Stays at the very
   top of the viewport so it's always seen even when scrolled. */
.v2-progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    z-index: var(--v2-z-toast);
    background: hsl(var(--v2-primary-500) / 0.12);
    pointer-events: none;
    overflow: hidden;
    opacity: 0;
    transition: opacity 200ms var(--v2-ease);
}
.v2-progress-bar.is-loading,
.v2-progress-bar.is-done {
    opacity: 1;
}

/* The fill — uses a sliding gradient via translateX for smooth animation
   (more performant than animating background-position). */
.v2-progress-bar__fill {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 30%;
    background: linear-gradient(90deg,
        transparent 0%,
        hsl(var(--v2-primary-500) / 0.3) 15%,
        hsl(var(--v2-primary-500)) 50%,
        hsl(var(--v2-primary-500) / 0.3) 85%,
        transparent 100%);
    box-shadow: 0 0 12px hsl(var(--v2-primary-500) / 0.6),
                0 1px 4px hsl(var(--v2-primary-500) / 0.4);
    transform: translateX(-100%);
    will-change: transform;
}
.v2-progress-bar.is-loading .v2-progress-bar__fill {
    animation: v2-progress-slide 1.2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
.v2-progress-bar.is-done .v2-progress-bar__fill {
    width: 100%;
    transform: translateX(0);
    animation: none;
    opacity: 0;
    transition: opacity 250ms var(--v2-ease) 100ms;
}
@keyframes v2-progress-slide {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(400%); }
}

/* === Loading status pill (floating, very visible) ===
   Appears bottom-right when ANY live component is loading. Content area
   uses a backdrop blur + cyan border + a spinning icon + "Mise à jour…"
   text. Once it appears the user can't miss it. */
.v2-progress-pill {
    position: fixed;
    bottom: var(--v2-space-6);
    right: var(--v2-space-6);
    z-index: var(--v2-z-pill);
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    padding: var(--v2-space-2) var(--v2-space-4);
    background: hsl(var(--v2-card));
    border: 1.5px solid hsl(var(--v2-primary-500));
    border-radius: var(--v2-radius-full);
    box-shadow: var(--v2-shadow-lg),
                0 0 0 4px hsl(var(--v2-primary-500) / 0.08);
    color: hsl(var(--v2-primary-700));
    font-size: var(--v2-text-14);
    font-weight: 600;
    pointer-events: none;
    opacity: 0;
    transform: translateY(12px) scale(0.95);
    transition: opacity 180ms var(--v2-ease),
                transform 220ms var(--v2-ease);
}
.v2-progress-pill.is-loading {
    opacity: 1;
    transform: translateY(0) scale(1);
}
.v2-progress-pill__spinner {
    width: 14px;
    height: 14px;
    border: 2px solid hsl(var(--v2-primary-500) / 0.25);
    border-top-color: hsl(var(--v2-primary-500));
    border-radius: 50%;
    animation: v2-spin 700ms linear infinite;
}
.v2-progress-pill__success {
    color: hsl(var(--v2-success-600));
}
.v2-progress-pill.is-success {
    border-color: hsl(var(--v2-success-500));
    color: hsl(var(--v2-success-700));
    box-shadow: var(--v2-shadow-md),
                0 0 0 4px hsl(var(--v2-success-500) / 0.08);
}

/* === Tactile button feedback (instant ripple effect) ===
   Provides IMMEDIATE visual response the moment the user clicks, before
   any AJAX request even starts. Combined with the loading shimmer below
   (which kicks in ~10ms after the click when busy attribute appears),
   the feedback chain is : click → ripple → shimmer → fade-in. */
body.v2-dashboard .btn:active,
body.v2-dashboard button:active,
body.v2-dashboard .v2-tx-chip:active {
    transform: scale(0.97);
    transition: transform 80ms var(--v2-ease);
}

/* When a primary button just got clicked AND its component is now busy,
   add a brief cyan glow ring to highlight which control triggered the
   request. Subtle but very satisfying. */
body.v2-dashboard [busy] .btn-primary,
body.v2-dashboard [busy] .v2-tx-chip.is-active {
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.25);
}

/* === Loading state on Live Components — premium shimmer + dim ===
   Symfony UX Live Components 2.x sets the `busy` attribute on the component
   ROOT during a request. We hook visual feedback via this selector.

   Premium effects layered:
     1. Subtle dim (opacity 0.7 — readable but clearly inactive)
     2. Pointer-events disabled (no double-click spam)
     3. Cyan shimmer wave that sweeps across the content (premium signal)
     4. Pulse animation on KPI numbers (they "breathe" while loading)
*/
body.v2-dashboard [busy],
body.v2-dashboard [data-live-is-loading],
[data-live-component].v2-loading,
.v2-loading {
    opacity: 0.7;
    pointer-events: none;
    user-select: none;
    position: relative;
    transition: opacity var(--v2-duration-fast) var(--v2-ease);
}

/* Cyan shimmer wave — sweeps across the entire component during loading.
   Uses ::before so it overlays without disturbing the layout. */
body.v2-dashboard [busy]::before,
.v2-loading::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(110deg,
        transparent 30%,
        hsl(var(--v2-primary-500) / 0.08) 45%,
        hsl(var(--v2-primary-500) / 0.18) 50%,
        hsl(var(--v2-primary-500) / 0.08) 55%,
        transparent 70%);
    background-size: 200% 100%;
    animation: v2-loading-shimmer 1.4s ease-in-out infinite;
    pointer-events: none;
    z-index: 1;
    border-radius: inherit;
}
@keyframes v2-loading-shimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Top progress bar on the Live root — feedback AT the interaction point.
   The "Mise à jour" pill sits bottom-right, but on the heavy analytics tabs
   the user's eyes are at the top (period buttons, tab nav) the moment they
   trigger a reload. This slim indeterminate bar rides the component's top
   edge so the "working…" signal lands where they're already looking
   (Linear / Vercel pattern, cf. PRODUCT.md). Reuses v2-progress-slide.
   The [busy] root is position:relative, so this anchors to its top. */
body.v2-dashboard [busy]::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    height: 2.5px;
    width: 28%;
    background: linear-gradient(90deg,
        transparent,
        hsl(var(--v2-primary-500)) 50%,
        transparent);
    box-shadow: 0 0 8px hsl(var(--v2-primary-500) / 0.55);
    animation: v2-progress-slide 1.15s cubic-bezier(0.4, 0, 0.2, 1) infinite;
    z-index: 6;
    pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
    /* No travelling segment for reduced-motion: a static full-width bar still
       signals "loading" without movement. */
    body.v2-dashboard [busy]::after {
        animation: none;
        width: 100%;
        background: hsl(var(--v2-primary-500) / 0.6);
        box-shadow: none;
    }
}

/* KPI values pulse during loading — the numbers "breathe" gently */
body.v2-dashboard [busy] .v2-kpi__value {
    animation: v2-loading-pulse 1.4s ease-in-out infinite;
}
@keyframes v2-loading-pulse {
    0%, 100% { opacity: 0.6; }
    50%      { opacity: 1; }
}

/* Smooth fade-in on the new content when loading completes — Live Component
   re-renders the entire component HTML, so this animates the freshness. */
body.v2-dashboard [data-live-component]:not([busy]) {
    animation: v2-content-fade-in 250ms var(--v2-ease);
}
@keyframes v2-content-fade-in {
    from { opacity: 0.5; transform: translateY(2px); }
    to   { opacity: 1;   transform: translateY(0); }
}
[data-live-component]:not(.v2-loading),
.v2-loading-target:not(.v2-loading) {
    transition: opacity var(--v2-duration-slow) var(--v2-ease);
}

/* Animated dotted spinner overlay — appears CENTERED on dimmed elements
   after a small delay (so quick requests don't flash). */
.v2-loading::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 32px;
    height: 32px;
    margin: -16px 0 0 -16px;
    border: 2px solid hsl(var(--v2-border-default));
    border-top-color: hsl(var(--v2-primary-500));
    border-radius: 50%;
    animation: v2-spin 700ms linear infinite;
    pointer-events: none;
    /* Show after 200ms — quick refresh shouldn't flash a spinner */
    opacity: 0;
    animation-delay: 0ms;
    transition: opacity var(--v2-duration) var(--v2-ease) 200ms;
}
.v2-loading.v2-loading--show-spinner::after,
[data-loading-min-duration].v2-loading::after {
    opacity: 1;
}
@keyframes v2-spin {
    to { transform: rotate(360deg); }
}

/* === Heatmap table (Cohorts + LTV) — premium dense matrix ===
   Wrapper allows horizontal scroll inside the chart-card body without
   forcing the whole page wider. Cells use small font + tight padding to
   show 24+ months of cohort data without overwhelming the page. */
.v2-heatmap-wrapper {
    overflow-x: auto;
    margin: 0 calc(var(--v2-space-4) * -1);
    padding: 0 var(--v2-space-4);
    -webkit-overflow-scrolling: touch;
}
.v2-table--heatmap {
    font-size: var(--v2-text-12);
    min-width: 0;
}
.v2-table--heatmap thead th {
    font-size: var(--v2-text-10);
    padding: var(--v2-space-2) var(--v2-space-2);
    white-space: nowrap;
}
.v2-table--heatmap tbody td {
    padding: var(--v2-space-2) var(--v2-space-2);
}
.v2-heatmap-cell {
    text-align: center;
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    border-radius: var(--v2-radius-xs);
    transition: transform var(--v2-duration-fast) var(--v2-ease);
}
.v2-heatmap-cell:hover {
    transform: scale(1.05);
    z-index: 2;
    position: relative;
    box-shadow: var(--v2-shadow-sm);
}

/* === Transaction filters (Apple/Stripe ergonomics) ===
   3 zones logiques empilées dans une seule card :
     1. Period presets (7 boutons cyan + Custom)
     2. Search hero (input-group avec icône, divider, type select)
     3. Filter chips (selects compacts, placeholders descriptifs)
   + footer (count + reset/refresh/export)
   Total ~150-170px, intuitive, scannable au coup d'œil. */
.v2-tx-filters {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2-shadow-xs);
    padding: var(--v2-space-3) var(--v2-space-4);
    margin-bottom: var(--v2-space-3);
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
}
.v2-tx-filters__zone-label {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-1);
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(var(--v2-foreground) / 0.6);
    flex-shrink: 0;
}

/* ── Zone 1 : Period presets ── */
.v2-tx-filters__period {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-tx-filters__custom-dates {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-1);
    margin-left: var(--v2-space-2);
    padding-left: var(--v2-space-2);
    border-left: 1px solid hsl(var(--v2-border-default));
}
.v2-tx-filters__custom-dates .form-control {
    width: 145px !important;
    padding: 0.3rem 0.5rem !important;
    font-size: var(--v2-text-13) !important;
}
.v2-tx-filters__date-sep {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
    padding: 0 var(--v2-space-1);
}

/* ── Zone 2 : Search hero — input-group avec icône + divider + type select ── */
.v2-tx-filters__search-row {
    display: flex;
    width: 100%;
}
.v2-tx-search-group {
    display: flex;
    align-items: stretch;
    width: 100%;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    transition: border-color var(--v2-duration-fast) var(--v2-ease),
                box-shadow var(--v2-duration-fast) var(--v2-ease);
    overflow: hidden;
}
.v2-tx-search-group:focus-within {
    border-color: hsl(var(--v2-primary-500));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.15);
}
.v2-tx-search-group.is-active {
    border-color: hsl(var(--v2-primary-500));
    background: hsl(var(--v2-primary-50) / 0.3);
}
.v2-tx-search-group__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 var(--v2-space-3);
    color: hsl(var(--v2-muted));
    flex-shrink: 0;
}
.v2-tx-search-group.is-active .v2-tx-search-group__icon {
    color: hsl(var(--v2-primary-600));
}
.v2-tx-search-group__input {
    flex: 1 1 0;
    min-width: 0;
    border: 0;
    background: transparent;
    padding: var(--v2-space-2) 0;
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-foreground));
    font-family: inherit;
    letter-spacing: -0.005em;
}
.v2-tx-search-group__input:focus {
    outline: none;
}
.v2-tx-search-group__input::placeholder {
    color: hsl(var(--v2-muted) / 0.65);
}
.v2-tx-search-group__divider {
    width: 1px;
    background: hsl(var(--v2-border-default));
    margin: var(--v2-space-1) 0;
}
.v2-tx-search-group__type {
    flex: 0 0 auto;
    border: 0;
    background: transparent;
    padding: var(--v2-space-2) var(--v2-space-3);
    padding-right: 2.2rem;
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    cursor: pointer;
    font-family: inherit;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2364748b'%3E%3Cpath d='M3.204 5L8 10.481 12.796 5 14 6.227 8 13 2 6.227 3.204 5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.6rem center;
    background-size: 10px;
}
.v2-tx-search-group__type:focus {
    outline: none;
    color: hsl(var(--v2-primary-700));
}

/* ── Zone 3 : Filter chips (compact selects with descriptive placeholders) ── */
.v2-tx-filters__chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--v2-space-2);
}
.v2-tx-chip {
    flex: 1 1 auto;
    max-width: 220px;
    min-width: 130px;
    padding: 0.4rem 2rem 0.4rem 0.75rem !important;
    font-size: var(--v2-text-13) !important;
    height: auto;
    line-height: 1.3;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    color: hsl(var(--v2-foreground));
    cursor: pointer;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2364748b'%3E%3Cpath d='M3.204 5L8 10.481 12.796 5 14 6.227 8 13 2 6.227 3.204 5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.65rem center;
    background-size: 10px;
    transition: border-color var(--v2-duration-fast) var(--v2-ease),
                background-color var(--v2-duration-fast) var(--v2-ease),
                color var(--v2-duration-fast) var(--v2-ease);
    font-family: inherit;
}
.v2-tx-chip:hover {
    border-color: hsl(var(--v2-border-strong));
}
.v2-tx-chip:focus {
    outline: none;
    border-color: hsl(var(--v2-primary-500));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.15);
}
.v2-tx-chip:disabled {
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-muted));
    cursor: not-allowed;
}
/* Active state — chip turned cyan when a non-default value is selected */
.v2-tx-chip.is-active {
    border-color: hsl(var(--v2-primary-500));
    background-color: hsl(var(--v2-primary-50) / 0.5);
    color: hsl(var(--v2-primary-700));
    font-weight: 500;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2300a3a8'%3E%3Cpath d='M3.204 5L8 10.481 12.796 5 14 6.227 8 13 2 6.227 3.204 5z'/%3E%3C/svg%3E");
}

/* ── Footer ── */
.v2-tx-filters__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--v2-space-3);
    padding-top: var(--v2-space-2);
    border-top: 1px dashed hsl(var(--v2-border-subtle));
    flex-wrap: wrap;
}
.v2-tx-filters__count {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-13);
    font-weight: 500;
}
.v2-tx-filters__count strong {
    color: hsl(var(--v2-primary-600));
    font-weight: 700;
}
.v2-tx-filters__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    flex-wrap: wrap;
}

/* Backwards-compat: keep .v2-filtered cyan border for the date inputs in the
   custom-dates panel + any other still using the legacy class. */
.v2-tx-filters .form-control.v2-filtered,
.v2-tx-filters .form-select.v2-filtered {
    border-color: hsl(var(--v2-primary-500)) !important;
    background-color: hsl(var(--v2-primary-50) / 0.4) !important;
}

/* Responsive : on very narrow viewports, stack chips full-width */
@media (max-width: 640px) {
    .v2-tx-chip {
        flex: 1 1 100%;
        max-width: none;
    }
    .v2-tx-filters__custom-dates {
        margin-left: 0;
        padding-left: 0;
        border-left: 0;
        width: 100%;
    }
    .v2-tx-filters__custom-dates .form-control {
        flex: 1 1 0;
        width: auto !important;
    }
}

/* === Page toolbar (compact single-row : context + period selector) ===
   GENERIC pattern used on Overview, Company/Site/User detail pages, and
   any V2 page that needs a context strip + period filter. Single source
   of truth for visual consistency. Kept tight (8x12 padding) so the KPI
   tiles below it stay above the fold. */
.v2-page-toolbar,
.v2-overview-toolbar {                /* alias for back-compat */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--v2-space-3);
    padding: var(--v2-space-2) var(--v2-space-3);
    margin-bottom: var(--v2-space-3);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-subtle));
    border-radius: var(--v2-radius-md);
    flex-wrap: wrap;
}

/* LEFT slot — context info (greeting on overview, badges + meta on detail) */
.v2-page-toolbar__context,
.v2-overview-toolbar__greet {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-muted));
    flex-wrap: wrap;
}
.v2-page-toolbar__context strong,
.v2-overview-toolbar__greet strong {
    color: hsl(var(--v2-foreground));
    font-weight: 600;
}
.v2-page-toolbar__context a {
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
    font-weight: 500;
}
.v2-page-toolbar__context a:hover {
    color: hsl(var(--v2-primary-700));
    text-decoration: underline;
}
.v2-page-toolbar__sep {
    color: hsl(var(--v2-border-strong));
}

/* RIGHT slot — period selector (cyan buttons + uppercase tiny label) */
.v2-page-toolbar__period,
.v2-overview-toolbar__period {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-page-toolbar__label,
.v2-overview-toolbar__label {
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(var(--v2-foreground) / 0.6);
}

/* ── Date-range picker (bouton « Personnalisé » + panneau flottant). Ouverture
   côté client (contrôleur Stimulus `daterange`) : aucun re-render Live tant
   qu'on n'a pas cliqué « Appliquer ». Le panneau est position: fixed, placé par
   le contrôleur sous le bouton et au niveau z-dropdown → jamais clippé par la
   toolbar. ── */
.v2-daterange {
    position: relative;
    display: inline-flex;
}
.v2-daterange__pop {
    position: fixed;
    z-index: var(--v2-z-dropdown);
    min-width: 16rem;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
    padding: var(--v2-space-4);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-md);
}
.v2-daterange__pop[hidden] {
    display: none;
}
@media (prefers-reduced-motion: no-preference) {
    .v2-daterange.is-open .v2-daterange__pop {
        animation: v2-daterange-in 160ms cubic-bezier(0.16, 1, 0.3, 1);
    }
}
@keyframes v2-daterange-in {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: translateY(0); }
}
.v2-daterange__field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin: 0;
}
.v2-daterange__field-label {
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(var(--v2-muted));
}
.v2-daterange__field .form-control {
    width: 100%;
    max-width: none;
}
.v2-daterange__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--v2-space-2);
    padding-top: var(--v2-space-3);
    border-top: 1px solid hsl(var(--v2-border-subtle));
}

/* OPTIONAL right-most slot — page-specific actions (Éditer / Désactiver…)
   On detail pages with admin actions. Comes AFTER the period selector. */
.v2-page-toolbar__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    flex-wrap: wrap;
}
.v2-page-toolbar__actions .btn {
    /* Slightly smaller buttons in the toolbar so they don't dominate */
    padding: var(--v2-space-1) var(--v2-space-3);
    font-size: var(--v2-text-13);
}

/* === Rebill toolbar (compact single-row filter bar) ===
   Replaces the 2-row .v2-filter-bar on the Rebill page — collapses
   company select + env buttons + period buttons into one horizontal
   strip, saving ~70px of vertical space above the fold. */
.v2-rebill-toolbar {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    padding: var(--v2-space-2) var(--v2-space-3);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2-shadow-xs);
    margin-bottom: var(--v2-space-3);
    flex-wrap: wrap;
}
.v2-rebill-toolbar__company {
    min-width: 220px;
    max-width: 360px;
    flex: 0 1 auto;
}
.v2-rebill-toolbar__group {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-rebill-toolbar__label {
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(var(--v2-foreground) / 0.6);
}

/* === Performance Score widget (Summary tab — premium gauge) ===
   Circular score 0..100 with tone-based gradient ring.
   The conic-gradient fakes a circular progress bar : 0% = full gray ring,
   100% = full cyan ring. Center hole shows the numeric value. */
/* Compact horizontal layout — gauge + label side by side instead of stacked */
.v2-perf-score {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--v2-space-3);
}
.v2-perf-score__ring {
    --score: 0;
    --ring-color: hsl(var(--v2-primary-500));
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: conic-gradient(
        var(--ring-color) calc(var(--score) * 1%),
        hsl(var(--v2-border-default)) 0
    );
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    position: relative;
    flex-shrink: 0;
}
.v2-perf-score__ring::before {
    content: '';
    position: absolute;
    inset: 5px;
    border-radius: 50%;
    background: hsl(var(--v2-card));
}
.v2-perf-score__value {
    position: relative;
    font-size: var(--v2-text-20);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.02em;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
}
.v2-perf-score__suffix {
    position: relative;
    font-size: var(--v2-text-9);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: hsl(var(--v2-muted));
    margin-top: 1px;
}
.v2-perf-score__label {
    font-size: var(--v2-text-11);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: hsl(var(--v2-muted));
    white-space: nowrap;
}
/* Tone variants — color the ring based on score band */
.v2-perf-score--success .v2-perf-score__ring { --ring-color: hsl(var(--v2-success-500)); }
.v2-perf-score--warning .v2-perf-score__ring { --ring-color: hsl(var(--v2-warning-500)); }
.v2-perf-score--danger  .v2-perf-score__ring { --ring-color: hsl(var(--v2-danger-500));  }
.v2-perf-score--muted   .v2-perf-score__ring { --ring-color: hsl(var(--v2-muted));       }

.v2-perf-score__value::first-letter {
    /* Center the layout vertically — fake using flex column inside the ring */
}

/* Stack value + suffix vertically inside the ring center */
.v2-perf-score__ring {
    flex-direction: column;
}

/* === Performance breakdown bars (Summary tab) === */
.v2-perf-breakdown {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
}
.v2-perf-row {
    display: grid;
    grid-template-columns: minmax(120px, 1fr) minmax(0, 3fr) auto;
    align-items: center;
    gap: var(--v2-space-3);
}
.v2-perf-row__label {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-foreground));
    font-weight: 500;
}
.v2-perf-row__bar {
    height: 8px;
    background: hsl(var(--v2-muted-bg));
    border-radius: var(--v2-radius-full);
    overflow: hidden;
    position: relative;
}
.v2-perf-row__bar-fill {
    height: 100%;
    border-radius: var(--v2-radius-full);
    transition: width var(--v2-duration-slow) var(--v2-ease);
}
.v2-perf-row__bar-fill--success { background: linear-gradient(90deg, hsl(var(--v2-success-500) / 0.7), hsl(var(--v2-success-500))); }
.v2-perf-row__bar-fill--warning { background: linear-gradient(90deg, hsl(var(--v2-warning-500) / 0.7), hsl(var(--v2-warning-500))); }
.v2-perf-row__bar-fill--danger  { background: linear-gradient(90deg, hsl(var(--v2-danger-500) / 0.7),  hsl(var(--v2-danger-500))); }
.v2-perf-row__bar-fill--muted   { background: hsl(var(--v2-muted) / 0.4); }

.v2-perf-row__value {
    font-size: var(--v2-text-13);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: hsl(var(--v2-muted));
    min-width: 56px;
    text-align: right;
}

/* === Insight list (Summary tab — algorithmic recommendations) === */
/* Masqué visuellement, lu par les lecteurs d'écran (titres de section,
   labels de contexte). Pattern visually-hidden standard. */
.v2-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}
.v2-insight-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
}
.v2-insight {
    display: flex;
    gap: var(--v2-space-3);
    padding: var(--v2-space-3) var(--v2-space-4);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-subtle));
    border-radius: var(--v2-radius-md);
    transition: border-color var(--v2-duration) var(--v2-ease),
                box-shadow var(--v2-duration) var(--v2-ease);
}
.v2-insight:hover {
    box-shadow: var(--v2-shadow-xs);
}
/* Ton porté par une bordure pleine douce + tint de fond + icône colorée,
   plus de stripe latéral (ban Impeccable). */
.v2-insight--success { border-color: hsl(var(--v2-success-500) / 0.4); background: hsl(var(--v2-success-50) / 0.45); }
.v2-insight--info,
.v2-insight--primary { border-color: hsl(var(--v2-primary-500) / 0.4); background: hsl(var(--v2-primary-50) / 0.45); }
.v2-insight--warning { border-color: hsl(var(--v2-warning-500) / 0.4); background: hsl(var(--v2-warning-50) / 0.45); }
.v2-insight--danger  { border-color: hsl(var(--v2-danger-500) / 0.4);  background: hsl(var(--v2-danger-50)  / 0.45); }

.v2-insight__icon {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-subtle));
}
.v2-insight--success .v2-insight__icon { color: hsl(var(--v2-success-600)); }
.v2-insight--info    .v2-insight__icon,
.v2-insight--primary .v2-insight__icon { color: hsl(var(--v2-primary-600)); }
.v2-insight--warning .v2-insight__icon { color: hsl(var(--v2-warning-600)); }
.v2-insight--danger  .v2-insight__icon { color: hsl(var(--v2-danger-600));  }

.v2-insight__content {
    flex: 1 1 0;
    min-width: 0;
}
.v2-insight__title {
    font-size: var(--v2-text-14);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    margin-bottom: var(--v2-space-1);
    letter-spacing: -0.014em;
}
.v2-insight__body {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    line-height: 1.5;
}
.v2-insight__cta {
    margin-top: var(--v2-space-2);
}

/* === LTV table (premium dense matrix) ===
   Sticky cohort column so the user keeps the row context while scrolling
   horizontally through the M+0 → M+N month offsets. */
.v2-table--ltv {
    font-size: var(--v2-text-12);
}
.v2-table--ltv .v2-ltv-sticky {
    position: sticky;
    left: 0;
    background: hsl(var(--v2-card));
    z-index: 5;
    box-shadow: 1px 0 0 hsl(var(--v2-border-subtle));
    min-width: 92px;
}
.v2-table--ltv thead .v2-ltv-sticky {
    background: hsl(var(--v2-muted-bg) / 1);
    z-index: 11;
}
.v2-table--ltv tbody tr:hover .v2-ltv-sticky {
    background: hsl(var(--v2-primary-50) / 0.5);
}
.v2-table--ltv tbody td {
    padding: var(--v2-space-2) var(--v2-space-3);
    white-space: nowrap;
}

/* === Alert list (Rebill alerts tab) ===
   Modern card-list pattern. Each alert has a subtle left-border accent
   matching its severity (warning/danger). */
.v2-alert-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
}
.v2-alert-list__item {
    background: hsl(var(--v2-warning-50) / 0.4);
    border: 1px solid hsl(var(--v2-warning-500) / 0.32);
    border-radius: var(--v2-radius-md);
    padding: var(--v2-space-3) var(--v2-space-4);
    transition: border-color var(--v2-duration) var(--v2-ease),
                box-shadow var(--v2-duration) var(--v2-ease);
}
.v2-alert-list__item:hover {
    border-color: hsl(var(--v2-warning-500) / 0.5);
    box-shadow: var(--v2-shadow-xs);
}
.v2-alert-list__header {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    margin-bottom: var(--v2-space-1);
}
.v2-alert-list__body {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
    line-height: 1.5;
    margin-left: calc(var(--v2-space-3) + 60px); /* indent past the badge */
}
@media (max-width: 640px) {
    .v2-alert-list__body {
        margin-left: 0;
    }
}

/* === Nav tabs Rebill — premium underline (Linear/Vercel style) ===
   Already styled by .v2-main .nav-tabs above, but the Rebill page has 9 tabs
   so we need them tight + scroll on mobile. */
/* Touch : sur pointeur grossier (tactile/stylet), les contrôles compacts
   (boutons période, onglets, select partenaire) passent à 44px de haut —
   cible tactile confortable — sans alourdir le rendu souris (pointer: fine). */
@media (pointer: coarse) {
    [data-testid="v2-analytics-rebill"] .btn-group .btn,
    [data-testid="v2-analytics-rebill"] .nav-tabs .nav-link,
    [data-testid="v2-analytics-rebill"] .form-select-sm {
        min-height: 44px;
    }
}
body.v2-dashboard .nav-tabs {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    flex-wrap: nowrap;
}
body.v2-dashboard .nav-tabs::-webkit-scrollbar {
    display: none;
}
body.v2-dashboard .nav-tabs .nav-link {
    white-space: nowrap;
    flex-shrink: 0;
}

/* === Inline filter spinner ===
   Tiny right-edge spinner inside a select/input that triggered the change.
   Bootstrap selects have a built-in caret; we replace it with a spinner
   only while loading to give per-field feedback. */
.v2-filter-bar .form-control.v2-filter-loading,
.v2-filter-bar .form-select.v2-filter-loading {
    border-color: hsl(var(--v2-primary-500));
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300bac7' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M21 12a9 9 0 1 1-6.219-8.56'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.85rem center;
    background-size: 14px 14px;
    animation: v2-spin-bg 700ms linear infinite;
}
@keyframes v2-spin-bg {
    /* Rotate the SVG via background-position trick — actually rotate via filter
       since we can't transform a background. Use opacity pulse instead. */
    0%, 100% { background-color: hsl(var(--v2-card)); }
    50%      { background-color: hsl(var(--v2-primary-50) / 0.4); }
}

/* === Info grid (DL label/value pairs) — used everywhere on detail pages
   Replaces every inline `dl style="display: grid;..."` in templates.
   Single source of truth for label/value typography — no more font-family
   monospace overrides per cell.

   Rules:
     - All text uses inherited Geist (no font-family override anywhere)
     - Tabular-nums on values for column alignment of digits/IDs
     - Labels are small + uppercase + muted
     - Values use foreground color, normal weight 400 (500 only when emphasis needed)
*/
.v2-info-grid {
    margin: 0;
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.55rem 1.25rem;
    align-items: baseline;
    font-size: var(--v2-text-14);          /* SAME as .v2-table tbody td — uniformity */
    font-family: inherit;          /* Geist, never monospace */
}
.v2-info-grid--quad {
    grid-template-columns: max-content 1fr max-content 1fr;
}
@media (max-width: 640px) {
    .v2-info-grid--quad {
        grid-template-columns: max-content 1fr;
    }
}
.v2-info-grid dt {
    margin: 0;
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-12);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    align-self: center;
    line-height: 1.4;
}
.v2-info-grid dd {
    margin: 0;
    color: hsl(var(--v2-foreground));
    font-family: inherit;          /* Geist, NO monospace */
    font-size: var(--v2-text-14);
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
    word-break: break-word;
}
.v2-info-grid dd a {
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
    font-weight: 500;
}
.v2-info-grid dd a:hover {
    color: hsl(var(--v2-primary-700));
    text-decoration: underline;
}
/* Long technical values (URLs especially) — wrap nicely without losing readability */
.v2-info-grid dd.v2-info-grid__url {
    word-break: break-all;
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
}
/* Error / danger value — red but same font */
.v2-info-grid dd.v2-info-grid__danger {
    color: hsl(var(--v2-danger-600));
    font-weight: 500;
}
/* Hero value — big amount displayed prominently (NO font-family change) */
.v2-info-grid dd.v2-info-grid__hero {
    font-size: var(--v2-text-24);
    font-weight: 700;
    line-height: 1.1;
    color: hsl(var(--v2-foreground));
}

/* Small uppercase muted section heading — replaces all inline `<h5 style="font-size: var(--v2-text-12);text-transform:uppercase;...">` */
.v2-section-label {
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: hsl(var(--v2-foreground) / 0.72);
    margin: 0 0 0.5rem;
    font-family: inherit;
}

/* Muted "empty" placeholder text — same font as everything else */
.v2-empty-text {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
    margin: 0;
    padding: 1rem;
    font-family: inherit;
}

/* Notification accordion header — keep all in Geist, just vary weight/color */
.v2-notif-accordion .accordion-button {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
    font-family: inherit;
    font-size: var(--v2-text-14);
}
.v2-notif-accordion .v2-notif-id {
    color: hsl(var(--v2-muted));
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    font-family: inherit;
    font-size: var(--v2-text-14);
}
.v2-notif-accordion .v2-notif-url {
    color: hsl(var(--v2-primary-600));
    font-family: inherit;
    font-size: var(--v2-text-13);
    max-width: 380px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-left: auto;
}

/* === Sprint Recon-Ops-A : UnwiredSourcePlaceholder ===
   Card affiché quand une source externe (Trust / Mandato / Paytend) n'a
   pas son fetcher câblé. Réutilisé sur toutes les pages du cockpit
   reconciliation. Doit se distinguer visuellement d'un état "vide" normal
   pour que l'utilisateur comprenne que c'est un état de configuration,
   pas un manque de données. */
.v2-unwired-placeholder {
    display: flex;
    align-items: flex-start;
    gap: var(--v2-space-3);
    padding: var(--v2-space-4);
    background: hsl(45 100% 96%);
    border: 1px dashed hsl(45 80% 60% / 0.5);
    border-radius: var(--v2-radius-md);
}
.v2-unwired-placeholder__icon {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: hsl(45 100% 90%);
    color: hsl(38 92% 35%);
    border-radius: var(--v2-radius-sm);
}
.v2-unwired-placeholder__body { flex: 1; min-width: 0; }
.v2-unwired-placeholder__title {
    font-weight: 600;
    color: hsl(var(--v2-warning-ink));
    margin-bottom: 0.25rem;
    font-size: var(--v2-text-16);
}
.v2-unwired-placeholder__hint {
    color: hsl(38 60% 35%);
    font-size: var(--v2-text-14);
    line-height: 1.4;
}

/* Inline tag form (compact: true) — fits in table cells / KPI tiles. */
.v2-unwired-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.15rem 0.5rem;
    font-size: var(--v2-text-12);
    background: hsl(45 100% 95%);
    color: hsl(38 92% 30%);
    border: 1px dashed hsl(45 80% 70%);
    border-radius: var(--v2-radius-sm);
    font-weight: 500;
    white-space: nowrap;
}

/* === Sprint Recon-Ops-D : job progress bars === */
.v2-job-progress {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.v2-job-progress__track {
    width: 100%;
    height: 6px;
    background: hsl(var(--v2-border));
    border-radius: var(--v2-radius-2xs);
    overflow: hidden;
}
.v2-job-progress__bar {
    height: 100%;
    transition: width 0.3s ease;
}
.v2-job-error-block {
    margin-bottom: var(--v2-space-2);
    padding: var(--v2-space-2);
    background: hsl(0 84% 96%);
    border: 1px solid hsl(0 84% 60% / 0.4);
    border-radius: var(--v2-radius-sm);
}
.v2-job-error-block strong {
    color: hsl(0 84% 35%);
    text-transform: uppercase;
    font-size: var(--v2-text-12);
    letter-spacing: 0.05em;
}
.v2-job-error-block ul {
    margin: 0.25rem 0 0;
    padding-left: 1.2rem;
    font-size: var(--v2-text-14);
    color: hsl(0 60% 25%);
}

/* === Sprint Recon-Ops-C : monthly calendar grid (Daily Tracking) ===
   31-day calendar — 7 columns on desktop (week-style), reduces to 5 on
   tablet narrow, then 4 on mobile small. Cells have fixed minimum height
   so the heatmap reads consistently across sizes. */
.v2-recon-month-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: var(--v2-space-2);
}
.v2-recon-month-grid__cell {
    display: block;
    padding: var(--v2-space-2);
    border-radius: var(--v2-radius-sm);
    text-align: center;
    text-decoration: none;
    color: inherit;
    min-height: 56px;
    border: 2px solid hsl(0 0% 90%);
    background: hsl(0 0% 96%);
    transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.v2-recon-month-grid__cell:hover {
    transform: translateY(-1px);
    box-shadow: 0 2px 8px hsl(184 100% 39% / 0.15);
}
.v2-recon-month-grid__cell--today {
    border-color: hsl(184 100% 39%);
}
.v2-recon-month-grid__day {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    font-weight: 600;
}
.v2-recon-month-grid__amount {
    font-size: var(--v2-text-11);
    margin-top: 0.15rem;
}
@media (max-width: 900px) {
    .v2-recon-month-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
    .v2-recon-month-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: var(--v2-space-1);
    }
    .v2-recon-month-grid__cell {
        min-height: 48px;
        padding: var(--v2-space-1) 0.25rem;
    }
    .v2-recon-month-grid__amount { font-size: var(--v2-text-10); }
}

/* === Sprint Recon-Ops-D : import drag-drop zone === */
.v2-recon-import-dz {
    border: 2px dashed hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    padding: var(--v2-space-6);
    text-align: center;
    background: hsl(0 0% 99%);
    transition: background 0.15s ease, border-color 0.15s ease;
    cursor: pointer;
}
.v2-recon-import-dz:hover {
    border-color: hsl(184 100% 39% / 0.6);
    background: hsl(184 100% 98%);
}
.v2-recon-import-dz--active {
    border-color: hsl(184 100% 39%);
    background: hsl(184 100% 95%);
}
.v2-recon-import-dz__icon {
    color: hsl(var(--v2-muted));
    margin-bottom: var(--v2-space-2);
}
.v2-recon-import-dz__label {
    font-weight: 600;
    margin-bottom: 0.25rem;
}
.v2-recon-import-dz__hint {
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
}
.v2-recon-import-status {
    margin-top: var(--v2-space-3);
    padding: var(--v2-space-2) var(--v2-space-3);
    border-radius: var(--v2-radius-sm);
    font-size: var(--v2-text-14);
}
.v2-recon-import-status--info { background: hsl(199 89% 95%); color: hsl(199 89% 25%); }
.v2-recon-import-status--success { background: hsl(142 71% 95%); color: hsl(142 71% 20%); }
.v2-recon-import-status--warning { background: hsl(45 100% 95%); color: hsl(var(--v2-warning-ink)); }
.v2-recon-import-status--danger { background: hsl(0 84% 96%); color: hsl(0 60% 30%); }
.v2-recon-import-progress {
    margin-top: var(--v2-space-2);
    width: 100%;
    height: 4px;
    background: hsl(var(--v2-border));
    border-radius: var(--v2-radius-2xs);
    overflow: hidden;
}
.v2-recon-import-progress__bar {
    height: 100%;
    width: 0%;
    background: hsl(184 100% 39%);
    transition: width 0.2s ease;
}

/* === Sprint Recon-Ops follow-up : KPI delta tooltip discoverability ===
   Every KPI delta badge carries a `title=` tooltip surfacing the previous
   period's absolute value + the precise variation type (relative % or
   percentage points). `cursor: help` is applied uniformly so the affordance
   is consistent across ALL tiles ; the dotted underline marker is reserved
   for `pp` (rate KPIs) where the unit subtlety matters most. */
.v2-kpi__delta {
    cursor: help;
}
.v2-kpi__delta--pp {
    text-decoration: underline dotted;
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
}

/* Reusable LivePagination footer (Jobs, Imports). Replaces inline styles
   that were duplicated across the cockpit. */
.v2-live-pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: var(--v2-space-2);
    padding-top: 1rem;
    border-top: 1px solid hsl(var(--v2-border));
    margin-top: 1rem;
}
.v2-live-pagination__cursor {
    display: inline-flex;
    align-items: center;
    padding: 0 1rem;
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
}

/* ════════════════════════════════════════════════════════════════════════
 *
 *  V2 LUXURY SHELL — refonte design 2026-05-09 (frontend-design skill)
 *
 *  Direction artistique : réfiné / luxury / soft.
 *  Inspirations : Linear Brand, Apple Newsroom, Editorial digital
 *  premium SaaS — typographie distinctive + atmosphère ivory.
 *
 *  Cette couche overrride :
 *    - le shell layout (.v2-dashboard, body)
 *    - la sidebar (nouveau .v2lux-sidebar — remplace .navbar-vertical)
 *    - le topbar (.v2lux-topbar — remplace .v2-topbar)
 *
 *  Ne touche PAS aux composants existants (KpiTile, ChartCard, DataTable,
 *  Badge, KPI strip, etc.) — ils héritent automatiquement des nouvelles
 *  variables typographiques/couleurs.
 *
 * ════════════════════════════════════════════════════════════════════════ */

:root {
    /* === Luxury palette (warm light theme) ====================================
       Override des tokens neutres : on passe d'un slate gris très "SaaS Inter
       2024" à une palette ivory + ink + accents chauds. Le cyan eZengo
       (--v2-primary-500) est conservé tel quel — il fait l'identité de la
       marque, on ne le touche pas. */

    /* Background : ivoire chaud, papier. Légèrement teinté warm pour éviter
       le gris bleuté générique. */
    --v2-bg: 38 32% 96%;          /* #faf6ee — ivory cream */
    --v2-bg-elevated: 0 0% 100%;  /* cards / surface au-dessus du fond */
    --v2-card: 0 0% 100%;

    /* Texte : noir doux, jamais pur. Ressemble plus à un noir "encre" */
    --v2-foreground: 24 18% 12%;  /* #221d18 ink black */
    --v2-muted: 24 9% 39%;        /* #6e655c muted warm gray — assombri d'un cran (marge contraste AA) */
    --v2-muted-2: 24 6% 56%;      /* texte tertiaire (kbd, helpers) */

    /* Bordures : très douces, presque invisibles.

       ⛔ Seuls DEUX des quatre tokens de bordure étaient réchauffés ici
       (audit 2026-07-25). `--v2-border-default` (40 usages) et
       `--v2-border-subtle` (20) restaient sur la teinte slate 220 de :root,
       jamais redéfinis — soit 60 usages en gris-BLEU au milieu d'un thème
       chaud, contre 91 en gris-chaud.

       Mesuré sur /dashboard avant correctif : rgb(229,231,235) sur 6
       éléments (froid) et rgb(230,224,219) sur 4 (chaud), côte à côte.
       Aucun détecteur ne pouvait le voir : les deux sont des tokens
       déclarés, la dérive est DANS le système, pas à côté.

       L'écart de clarté entre `subtle` et `default` est conservé tel quel
       (+4 points, comme dans :root) : on change la teinte, pas la
       hiérarchie visuelle qu'ils encodent. */
    --v2-border: 30 18% 88%;      /* #e8e0d3 — soft warm border */
    --v2-border-default: 30 18% 88%;  /* aligné sur --v2-border */
    --v2-border-subtle: 30 18% 92%;   /* +4 pts de clarté, comme dans :root */
    --v2-border-strong: 30 14% 78%;
    --v2-muted-bg: 36 28% 93%;    /* #f3ecdf — subtle warm tint pour blocks */

    /* Accent rare ocre — utilisé pour les highlights / hover backgrounds
       ponctuels. Jamais dominant : le primary cyan reste roi. */
    --v2-accent-50: 22 60% 95%;
    --v2-accent-500: 22 65% 58%;  /* #d3815b ocre warmth */

    /* Ombres "luxury" : layered, douces, inspirées du design éditorial */
    --v2lux-shadow-xs: 0 1px 2px hsl(24 30% 10% / 0.04);
    --v2lux-shadow-sm: 0 2px 6px hsl(24 30% 10% / 0.06), 0 1px 2px hsl(24 30% 10% / 0.04);
    --v2lux-shadow-md: 0 8px 24px hsl(24 30% 10% / 0.08), 0 2px 6px hsl(24 30% 10% / 0.04);
    --v2lux-shadow-lg: 0 24px 48px hsl(24 30% 10% / 0.12), 0 8px 16px hsl(24 30% 10% / 0.06);

    /* Motion — easing inspiré de la gravité naturelle (ease-out-expo) */
    --v2lux-ease: cubic-bezier(0.16, 1, 0.3, 1);
    --v2lux-duration-fast: 150ms;
    --v2lux-duration-base: 250ms;
    --v2lux-duration-slow: 400ms;

    /* Layout dimensions — la sidebar fait EXACTEMENT 16.25rem (260px) pour
       matcher le sélecteur legacy `.navbar-vertical ~ main { margin-left:
       16.25rem }` qui s'applique encore au <main> parent (le navbar legacy
       est en `display:none` mais reste dans le DOM, donc le sélecteur sibling
       continue de matcher). Sans cet alignement, on doublait la margin-left
       du contenu et le tableau dépassait à droite. */
    --v2lux-sidebar-width: 16.25rem;     /* 260px — match legacy ~ main */
    --v2lux-topbar-height: 64px;
    --v2lux-content-max: 1440px;

    /* Typographie — single-family Geist. Décision 2026-05-18 : un seul
       système pour la cohérence (retrait Fraunces serif + JetBrains Mono).
       La hiérarchie passe désormais par taille + weight + tracking, pas
       par changement de famille.
       --v2lux-font-display reste exposé comme alias de --body pour les
       composants qui le référencent encore, sans diverger en serif. */
    /* ⛔ BASCULE 2026-07-25 : Open Sans, decision du proprietaire du produit.
       Le site portait DEUX polices — Geist sur les 54 pages du dashboard,
       Open Sans sur les surfaces V1 (tunnel de paiement, pages d'erreur).
       Le porteur de carte voyait donc une autre police que le marchand.

       Repointer CE token suffit : le verrou `body.v2-dashboard * {
       font-family: var(--v2lux-font-body) !important }` le consomme, donc
       les ~93 `font-family` en dur des gabarits restent inertes et n'ont pas
       besoin d'etre nettoyees pour que la bascule prenne. On les nettoiera
       ensuite, a froid.

       CE QU'ON GAGNE, mesure avant de decider : Open Sans a des chiffres
       TABULAIRES natifs — ses 10 chiffres font la meme chasse, avec ou sans
       `font-variant-numeric`. Geist en depend strictement : son « 1 » fait
       5,4 px contre 9,3 px pour son « 0 », soit 3,9 px d'ecart. 538 elements
       du dashboard portent aujourd'hui `font-variant-numeric: normal` et ne
       s'alignent donc PAS ; en Open Sans ils s'alignent d'eux-memes.
       Les 81 declarations `tabular-nums` deviennent redondantes mais restent
       en place : elles ne coutent rien et protegeront la prochaine bascule.

       ⚠ La police est chargee en AXE VARIABLE (`wght@300..800`). Avec la
       liste de poids figes precedente, la graisse 500 n'existait pas et
       rendait exactement comme 400 — or 117 declarations l'utilisent, dont
       la valeur des tuiles KPI. Voir base.html.twig et _shell.html.twig. */
    --v2lux-font-body: 'Open Sans', -apple-system, BlinkMacSystemFont,
                       'Segoe UI', system-ui, sans-serif;
    --v2lux-font-display: var(--v2lux-font-body);

    /* Geist Mono RESTE, et uniquement pour le code : visualiseur JSON,
       balises code/pre/kbd, raccourcis clavier. Open Sans n'a pas de
       variante monospace, et un JSON en police proportionnelle est illisible
       — clés et valeurs cessent de s'aligner d'une ligne a l'autre. Un
       monospace est un outil de lecture, pas un choix de marque. */
    --v2lux-font-mono: 'Geist Mono', ui-monospace, 'SF Mono',
                       'Cascadia Code', Menlo, monospace;

    /* === Tokens issus de l'audit couleurs du 2026-07-25 =====================
       18 valeurs qui se repetaient a l'identique sans jamais etre nommees.
       TOUTES a leur valeur EXACTE : creer le token et remplacer le litteral
       donne un rendu strictement identique, au pixel. Ce lot est de la
       maintenabilite, pas une correction visuelle.

       Convention maison : triplette HSL nue, consommee via hsl(var(--x)) ou
       hsl(var(--x) / alpha) — ce qui permet de garder l'alpha a l'usage. */

    /* Sidebar sombre — 10 roles distincts, 26 usages */
    --v2lux-sidebar-ink: 38 25% 90%;
    --v2lux-sidebar-ink-bright: 38 28% 92%;
    --v2lux-sidebar-ink-dim: 38 18% 55%;
    --v2lux-sidebar-ink-muted: 38 18% 60%;
    --v2lux-sidebar-surface: 210 30% 14%;
    --v2lux-sidebar-surface-hover: 210 32% 17%;
    --v2lux-sidebar-glow: 210 30% 22%;
    --v2lux-sidebar-rule: 210 30% 18%;
    --v2lux-sidebar-line: 210 28% 20%;
    --v2lux-sidebar-line-hover: 210 32% 28%;

    /* Dataviz BINS / Analytics Rebill — teinte 180, a NE PAS rabattre sur
       --v2-primary (teinte 184) : ce sont deux familles distinctes. */
    --v2-bins-drill: 180 50% 45%;
    --v2-bins-drill-soft: 180 35% 55%;
    --v2-databar-teal: 180 65% 45%;
    --v2-databar-teal-lo: 180 60% 70%;

    /* Divers */
    --v2-warning-ink: 38 92% 25%;   /* palier manquant : --v2-warning-800 partage
                                       la clarte mais pas la teinte (26 vs 38) */
    --v2-shadow-ink: 24 18% 12%;    /* meme valeur que --v2-foreground, role
                                       different : une ombre n'est pas une encre */
    --v2-forest: 150 36% 32%;       /* moitie VERTE de la paire delta KPI — la
                                       moitie rouge (--v2-brick) etait deja
                                       tokenisee, celle-ci non */
    --v2-forest-50: 150 28% 95%;
}

/* === Body — atmosphère générale ===========================================
   Grain SVG inline subtil pour casser le côté plat des aplats de couleur.
   Dégradé radial très léger pour donner une profondeur sans créer de halo. */

body.v2-dashboard.v2-design-luxury {
    font-family: var(--v2lux-font-body);
    font-feature-settings: 'cv11', 'ss01', 'ss03';
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: hsl(var(--v2-foreground));
    background-color: hsl(var(--v2-bg));

    /* Atmosphere : subtle radial gradient + SVG noise grain.
       The grain is encoded inline so it doesn't need a network round-trip.
       Opacity is purposefully low (0.4) — it should feel like paper texture,
       not like a TV broadcasting from 1985. */
    background-image:
        radial-gradient(ellipse 90% 60% at 50% -10%, hsl(38 50% 88% / 0.5) 0%, transparent 60%),
        url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
    background-attachment: fixed;
    background-size: cover, 180px 180px;
}

/* === V2 LUXURY SIDEBAR ==================================================
   Override total de la sidebar Bootstrap navbar-vertical legacy. Notre sidebar
   est positionnée fixe à gauche, fond ivory plus saturé que le body, avec
   une bordure droite très douce. Dimensions calibrées pour 31 items en
   navigation sans scroll (sur 13" min).

   Le legacy `.navbar-vertical` est neutralisé (display:none) parce que le
   shell V2 inclut désormais `_sidebar_v2.html.twig` à la place. */

body.v2-dashboard.v2-design-luxury #mainNavbar.navbar-vertical { display: none !important; }

.v2lux-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    width: var(--v2lux-sidebar-width);
    z-index: 100;
    display: flex;
    flex-direction: column;
    padding: var(--v2-space-5) var(--v2-space-3) var(--v2-space-3);
    background-color: hsl(38 30% 94%);   /* slightly more saturated than body */
    border-right: 1px solid hsl(var(--v2-border));
    box-shadow: 1px 0 0 hsl(38 25% 100% / 0.6) inset;  /* inner highlight */

    /* Sidebar scrollable au wheel/trackpad si le contenu dépasse 100vh
       (ex. avec toutes les sections expanded sur un laptop 13"). La
       scrollbar visuelle reste masquée — préférence utilisateur : pas
       de barre qui pollue le design. Le scroll-chaining est bloqué pour
       que le wheel sur la sidebar ne fasse pas défiler la page derrière. */
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    font-family: var(--v2lux-font-body);

    /* Scrollbar visuelle complètement masquée (cross-browser) */
    scrollbar-width: none;
    -ms-overflow-style: none;
}
/* Pas de scrollbar visible — overflow:hidden ci-dessus rend ces règles
   redondantes mais on garde un fallback `display: none` au cas où un
   descendant ré-active overflow:auto par accident. */
.v2lux-sidebar::-webkit-scrollbar { display: none; width: 0; height: 0; }

/* Brand : logotype + wordmark. La marque "eZ" est en serif Fraunces avec
   un soupçon d'italique pour l'asseoir comme une signature éditoriale. */

.v2lux-sidebar__brand {
    padding: 0 var(--v2-space-3) var(--v2-space-5);
    margin-bottom: var(--v2-space-4);
    border-bottom: 1px solid hsl(var(--v2-border) / 0.6);
}
.v2lux-brand {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    color: hsl(var(--v2-foreground));
    text-decoration: none;
    transition: opacity var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-brand:hover { opacity: 0.75; color: hsl(var(--v2-foreground)); }
.v2lux-brand__mark {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    border-radius: var(--v2-radius-md);
    background: linear-gradient(135deg, hsl(var(--v2-primary-500)), hsl(var(--v2-primary-700)));
    color: white;
    font-family: var(--v2lux-font-display);
    font-weight: 600;
    font-size: var(--v2-text-16);
    font-style: italic;
    letter-spacing: -0.02em;
    box-shadow: var(--v2lux-shadow-sm),
                inset 0 1px 0 hsl(0 0% 100% / 0.2);
}
.v2lux-brand__wordmark {
    display: flex;
    flex-direction: column;
    line-height: 1.1;
}
.v2lux-brand__name {
    font-family: var(--v2lux-font-display);
    font-weight: 500;
    font-size: var(--v2-text-18);
    letter-spacing: -0.01em;
    font-variation-settings: 'opsz' 24, 'SOFT' 30;
    color: hsl(var(--v2-foreground));
}
.v2lux-brand__sub {
    font-family: var(--v2lux-font-body);
    font-size: var(--v2-text-11);
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: hsl(var(--v2-muted));
    margin-top: 2px;
}

/* Search affordance — bouton style command palette */
.v2lux-sidebar__search {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    width: 100%;
    padding: 8px 12px;
    margin: 0 0 var(--v2-space-5);
    background-color: hsl(var(--v2-bg-elevated));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    color: hsl(var(--v2-muted));
    font-family: var(--v2lux-font-body);
    font-size: var(--v2-text-13);
    cursor: pointer;
    transition: all var(--v2lux-duration-fast) var(--v2lux-ease);
    box-shadow: var(--v2lux-shadow-xs);
}
.v2lux-sidebar__search:hover {
    background: hsl(0 0% 100%);
    border-color: hsl(var(--v2-border-strong));
    color: hsl(var(--v2-foreground));
    box-shadow: var(--v2lux-shadow-sm);
}
.v2lux-sidebar__search-label { flex: 1; text-align: left; }
.v2lux-sidebar__kbd {
    font-family: var(--v2lux-font-mono);
    font-size: var(--v2-text-11);
    padding: 2px 6px;
    background: hsl(var(--v2-muted-bg));
    border: 1px solid hsl(var(--v2-border));
    border-bottom-width: 2px;
    border-radius: var(--v2-radius-xs);
    color: hsl(var(--v2-muted));
}
@keyframes v2lux-pulse {
    0%, 100% { box-shadow: var(--v2lux-shadow-sm); }
    50% { box-shadow: 0 0 0 4px hsl(var(--v2-primary-500) / 0.15), var(--v2lux-shadow-sm); }
}
.v2lux-sidebar__search--pulse { animation: v2lux-pulse 600ms var(--v2lux-ease); }

/* Nav structure */
.v2lux-sidebar__nav {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-5);
    padding-bottom: var(--v2-space-5);
}
.v2lux-nav-group { display: flex; flex-direction: column; gap: 2px; }

.v2lux-nav-group__label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 12px var(--v2-space-2);
    font-family: var(--v2lux-font-body);
    /* Unifié avec les micro-titres plats du haut (.v2lux-nav-flat__divider) :
       casse normale + cyan, plus de MAJUSCULES espacées. La seule différence
       entre un groupe repliable et un titre plat est désormais le chevron. */
    font-size: var(--v2-text-13);                 /* aligné sur les items (.v2lux-nav-item) — plus « trop petit » */
    font-weight: 600;
    letter-spacing: 0.01em;
    text-transform: none;
    color: hsl(var(--v2-primary-300) / 0.7);
}
.v2lux-nav-group__chip {
    text-transform: lowercase;
    letter-spacing: 0.04em;
    font-style: italic;
    font-family: var(--v2lux-font-display);
    font-weight: 500;
    font-size: var(--v2-text-10);
    color: hsl(var(--v2-primary-500));
    padding: 1px 6px;
    background: hsl(var(--v2-primary-50));
    border: 1px solid hsl(var(--v2-primary-100));
    border-radius: var(--v2-radius-full);
}

.v2lux-nav-group__items { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 1px; }

.v2lux-nav-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 12px;
    border-radius: var(--v2-radius-sm);
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-13);
    font-weight: 450;
    text-decoration: none;
    transition: background-color var(--v2lux-duration-fast) var(--v2lux-ease),
                color var(--v2lux-duration-fast) var(--v2lux-ease),
                transform var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-nav-item__icon {
    flex-shrink: 0;
    color: hsl(var(--v2-muted-2));
    transition: color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-nav-item:hover {
    background: hsl(38 25% 100% / 0.7);
    color: hsl(var(--v2-foreground));
}
.v2lux-nav-item:hover .v2lux-nav-item__icon { color: hsl(var(--v2-foreground)); }

.v2lux-nav-item--active {
    background: hsl(0 0% 100%);
    color: hsl(var(--v2-foreground));
    font-weight: 500;
    box-shadow: var(--v2lux-shadow-xs),
                inset 0 0 0 1px hsl(var(--v2-border));
}
/* Le filet cyan de 3px (`::before`) a été retiré le 2026-07-18 : c'est un
   side-stripe border, explicitement banni par PRODUCT.md. Il créait en plus
   DEUX grammaires d'« actif » dans le même rail — un item de sous-section
   arborait un filet lumineux quand l'ancre de la page réellement ouverte
   n'avait rien. L'état actif repose désormais partout sur la même base :
   fond plein, texte contrasté, icône cyan. */
.v2lux-nav-item--active .v2lux-nav-item__icon { color: hsl(var(--v2-primary-500)); }

/* Footer : user identity + back-to-V1 link */
.v2lux-sidebar__footer {
    margin-top: auto;
    padding-top: var(--v2-space-3);
    border-top: 1px solid hsl(var(--v2-border) / 0.6);
}
.v2lux-sidebar__user {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    padding: var(--v2-space-2) 12px;
    margin-bottom: var(--v2-space-2);
}
.v2lux-sidebar__avatar {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: grid;
    place-items: center;
    background: linear-gradient(135deg, hsl(var(--v2-accent-500)), hsl(22 50% 45%));
    color: white;
    font-family: var(--v2lux-font-display);
    font-weight: 600;
    font-size: var(--v2-text-14);
    font-variation-settings: 'opsz' 14;
    box-shadow: inset 0 1px 0 hsl(0 0% 100% / 0.2),
                var(--v2lux-shadow-xs);
}
.v2lux-sidebar__user-meta { min-width: 0; flex: 1; }
.v2lux-sidebar__user-name {
    font-size: var(--v2-text-13);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2lux-sidebar__user-role {
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    font-style: italic;
    font-family: var(--v2lux-font-display);
}
.v2lux-sidebar__legacy {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    color: hsl(var(--v2-muted-2));
    font-size: var(--v2-text-11);
    text-decoration: none;
    border-radius: var(--v2-radius-sm);
    transition: color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-sidebar__legacy:hover { color: hsl(var(--v2-muted)); }

/* === V2 LUXURY TOPBAR ====================================================
   Sticky sous le shell, backdrop-blur pour atmosphère premium. Le titre
   est en Fraunces serif — c'est l'élément le plus distinctif de l'identité
   visuelle. Sous-titre en italique pour le caractère éditorial. */

.v2lux-topbar {
    position: sticky;
    top: 0;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--v2-space-4);
    padding: 0 var(--v2-space-8);
    height: var(--v2lux-topbar-height);
    margin-left: var(--v2lux-sidebar-width);
    background: hsl(var(--v2-bg) / 0.92);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid hsl(var(--v2-border) / 0.6);
    /* Force GPU compositor layer pour éviter le flash blanc au scroll :
       sans cette promotion explicite, position:sticky + backdrop-filter
       provoquait un re-composite à chaque frame de scroll, et le browser
       montrait brièvement le background semi-transparent SANS le blur
       appliqué (donc plus clair, plus blanc, "flash"). Avec translateZ(0)
       + will-change, le topbar vit dans son propre GraphicsLayer GPU et
       n'est plus recomposité au scroll. */
    transform: translateZ(0);
    will-change: transform;
    /* Background-opacity remontée de 0.85 → 0.92 : si malgré la promotion
       GPU un frame se rend sans le blur, le background reste assez opaque
       pour ne pas laisser passer le blanc du body en dessous. */
}
.v2lux-topbar__title {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
.v2lux-topbar__heading {
    margin: 0;
    font-family: var(--v2lux-font-display);
    font-weight: 450;
    font-size: var(--v2-text-20);
    letter-spacing: -0.015em;
    line-height: 1.2;
    color: hsl(var(--v2-foreground));
    /* Fraunces variable axes : optical size + softness for screen reading */
    font-variation-settings: 'opsz' 24, 'SOFT' 50;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2lux-topbar__breadcrumb {
    font-family: var(--v2lux-font-display);
    font-style: italic;
    font-size: var(--v2-text-12);
    font-weight: 350;
    color: hsl(var(--v2-muted));
    line-height: 1.3;
    margin-top: 2px;
    font-variation-settings: 'opsz' 14, 'SOFT' 30;
    /* Un sous-titre long ne doit JAMAIS wrapper : la topbar a une hauteur
       fixe (64px) sans clip, un wrap multi-lignes déborde par-dessus le
       contenu (constaté en prod sur le hub Paybox, colonne d'un mot qui
       chevauchait la carte). Une ligne, ellipse — comme le heading. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2lux-topbar__breadcrumb p,
.v2lux-topbar__breadcrumb .v2-page-header__subtitle {
    margin: 0;
    font-size: inherit;
    font-style: inherit;
    font-family: inherit;
    color: inherit;
    line-height: inherit;
}
.v2lux-topbar__actions { display: flex; align-items: center; gap: var(--v2-space-2); }
.v2lux-topbar__right { display: flex; align-items: center; gap: var(--v2-space-2); }

.v2lux-topbar__icon-btn {
    position: relative;
    display: inline-grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: var(--v2-radius-md);
    background: transparent;
    border: 1px solid transparent;
    color: hsl(var(--v2-muted));
    cursor: pointer;
    transition: all var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-topbar__icon-btn:hover {
    background: hsl(var(--v2-bg-elevated));
    border-color: hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
    box-shadow: var(--v2lux-shadow-xs);
}
.v2lux-topbar__icon-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.25);
    border-color: hsl(var(--v2-primary-500));
}
.v2lux-topbar__badge {
    position: absolute;
    top: -2px;
    right: -2px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    display: grid;
    place-items: center;
    border-radius: var(--v2-radius-full);
    background: hsl(var(--v2-accent-500));
    color: white;
    font-family: var(--v2lux-font-mono);
    font-size: var(--v2-text-10);
    font-weight: 600;
    line-height: 1;
    border: 2px solid hsl(var(--v2-bg));
    box-shadow: var(--v2lux-shadow-xs);
}

/* Dropdown réfiné — override Bootstrap */
.v2lux-dropdown {
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    padding: var(--v2-space-2);
    box-shadow: var(--v2lux-shadow-md);
    background: hsl(var(--v2-bg-elevated));
    min-width: 200px;
    margin-top: 6px;
}
.v2lux-dropdown__item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border-radius: var(--v2-radius-sm);
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-13);
    transition: background var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-dropdown__item:hover {
    background: hsl(var(--v2-muted-bg));
}
.v2lux-dropdown__item--danger { color: hsl(var(--v2-danger-500)); }
.v2lux-dropdown__item--danger:hover { background: hsl(var(--v2-danger-50)); color: hsl(var(--v2-danger-600)); }
.v2lux-dropdown__divider {
    height: 1px;
    background: hsl(var(--v2-border));
    margin: var(--v2-space-1) 0;
}

/* === Main content area — décalage sidebar + max-width généreux ============= */

body.v2-dashboard.v2-design-luxury .v2-main {
    margin-left: var(--v2lux-sidebar-width);
    padding: var(--v2-space-8) var(--v2-space-8) var(--v2-space-12);
    max-width: none;        /* le content fill l'espace mais respecte sa propre max-width interne */
}
body.v2-dashboard.v2-design-luxury .v2-main > * {
    max-width: var(--v2lux-content-max);
    margin-left: auto;
    margin-right: auto;
}

/* Hide legacy footer in V2 — pas dans la grammaire visuelle de notre shell */
body.v2-dashboard.v2-design-luxury main > footer { display: none; }

/* === Page header (légèrement re-stylé pour le serif accent) =============== */
body.v2-dashboard.v2-design-luxury .v2-page-header__title {
    font-family: var(--v2lux-font-display);
    font-weight: 450;
    letter-spacing: -0.015em;
    font-variation-settings: 'opsz' 36, 'SOFT' 40;
}
body.v2-dashboard.v2-design-luxury .v2-page-header__subtitle {
    font-family: var(--v2lux-font-display);
    font-style: italic;
    font-weight: 350;
    color: hsl(var(--v2-muted));
}

/* === Composants partagés — léger refresh pour matcher le shell =============
   On garde leur structure intacte, on ajuste juste fonts + bordures pour
   qu'ils se sentent à leur place dans le nouveau shell. */

body.v2-dashboard.v2-design-luxury {
    /* KpiTile labels en serif pour la cohérence éditoriale */
}
body.v2-dashboard.v2-design-luxury .v2-kpi__label {
    font-family: var(--v2lux-font-body);
    text-transform: none;
    letter-spacing: -0.005em;
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: hsl(var(--v2-muted));
}
body.v2-dashboard.v2-design-luxury .v2-kpi__value {
    font-family: var(--v2lux-font-display);
    font-variation-settings: 'opsz' 36, 'SOFT' 30;
    font-weight: 500;
    font-feature-settings: 'tnum' 1;
}
body.v2-dashboard.v2-design-luxury .v2-kpi__hint {
    font-family: var(--v2lux-font-display);
    font-style: italic;
    font-weight: 350;
}
/* Onglet Complétude — rattrapage du refresh luxury.
   Ce bloc est une LISTE BLANCHE de sélecteurs : une surface qui n'y figure pas
   garde l'ancienne peau. Les classes `.v2-dc-*` n'y avaient jamais été
   ajoutées, donc l'onglet rendait ses chiffres en 700 et ses titres en 600,
   sans réglage d'axes optiques, quand tout le reste du site est passé en 500
   avec `opsz`/`SOFT`. Résultat : plus lourd, plus brut, « pas au niveau du
   reste du site » — une divergence par OMISSION, jamais décidée par personne.
   ⚠ Si tu ajoutes une surface au produit, pense à l'inscrire ici. */
body.v2-dashboard.v2-design-luxury .v2-dc-row__pct {
    font-family: var(--v2lux-font-display);
    font-variation-settings: 'opsz' 36, 'SOFT' 30;
    font-weight: 500;
    font-feature-settings: 'tnum' 1;
}
body.v2-dashboard.v2-design-luxury .v2-dc-row__name {
    font-family: var(--v2lux-font-display);
    font-variation-settings: 'opsz' 24, 'SOFT' 30;
    font-weight: 500;
}
/* Onglet « Demande » — inscrit ici DÈS sa création, précisément pour ne pas
   rejouer l'oubli de Complétude. Le payload JSON reste en mono : c'est du
   code, pas de l'éditorial, la display face le rendrait illisible. */
body.v2-dashboard.v2-design-luxury .v2-llm-verdict,
body.v2-dashboard.v2-design-luxury .v2-llm-checks {
    font-family: var(--v2lux-font-display);
    font-variation-settings: 'opsz' 18, 'SOFT' 30;
    font-weight: 400;
}
body.v2-dashboard.v2-design-luxury .v2-llm-subhead,
body.v2-dashboard.v2-design-luxury .v2-llm-label {
    font-family: var(--v2lux-font-body);
    text-transform: none;
    letter-spacing: -0.005em;
    font-weight: 500;
}
/* Le champ de question et les constats : même peau que le reste. Le chiffre
   d'un constat prend les axes optiques des valeurs chiffrées du site. */
body.v2-dashboard.v2-design-luxury .v2-llm-input,
body.v2-dashboard.v2-design-luxury .v2-llm-chip {
    font-family: var(--v2lux-font-body);
    letter-spacing: -0.005em;
}
body.v2-dashboard.v2-design-luxury .v2-llm-constats__chiffre {
    font-family: var(--v2lux-font-display);
    font-variation-settings: 'opsz' 18, 'SOFT' 30;
    font-weight: 500;
    font-feature-settings: 'tnum' 1;
}
body.v2-dashboard.v2-design-luxury .v2-chart-card,
body.v2-dashboard.v2-design-luxury .v2-card {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2lux-shadow-xs);
    transition: box-shadow var(--v2lux-duration-base) var(--v2lux-ease),
                transform var(--v2lux-duration-base) var(--v2lux-ease);
}
body.v2-dashboard.v2-design-luxury .v2-chart-card:hover,
body.v2-dashboard.v2-design-luxury .v2-card:hover {
    box-shadow: var(--v2lux-shadow-sm);
}
body.v2-dashboard.v2-design-luxury .v2-chart-card__title {
    font-family: var(--v2lux-font-display);
    font-weight: 500;
    font-size: var(--v2-text-14);
    letter-spacing: -0.01em;
    font-variation-settings: 'opsz' 24, 'SOFT' 30;
}

/* === Mobile / tablet adaptation =============================================
   < 1024px : sidebar repliable, transformée en drawer. */

@media (max-width: 1024px) {
    .v2lux-sidebar {
        transform: translateX(-100%);
        transition: transform var(--v2lux-duration-base) var(--v2lux-ease);
    }
    .v2lux-sidebar--open { transform: translateX(0); }
    .v2lux-topbar,
    body.v2-dashboard.v2-design-luxury .v2-main {
        margin-left: 0;
    }
    .v2lux-topbar { padding: 0 var(--v2-space-4); }
    body.v2-dashboard.v2-design-luxury .v2-main { padding: var(--v2-space-4); }
}

/* Topbar reflow on phones (QA mobile fix) : la barre sticky a une hauteur fixe ;
   sur ≤640px le sous-titre descriptif passe à la ligne et les actions de page
   débordaient par-dessus. On laisse la barre grandir et on renvoie les actions
   sur leur propre rangée pleine largeur. Le titre peut passer à la ligne (plus
   d'ellipse « Plans de co… »). */
@media (max-width: 640px) {
    .v2lux-topbar {
        height: auto;
        min-height: var(--v2lux-topbar-height);
        flex-wrap: wrap;
        padding-top: var(--v2-space-2);
        padding-bottom: var(--v2-space-2);
        row-gap: var(--v2-space-2);
    }
    .v2lux-topbar__heading {
        white-space: normal;
        overflow: visible;
        text-overflow: clip;
    }
    .v2lux-topbar__actions {
        order: 5;
        flex: 1 1 100%;
        justify-content: flex-start;
        flex-wrap: wrap;
    }
}

/* === Reduced motion — respecter la préférence utilisateur ================ */
@media (prefers-reduced-motion: reduce) {
    .v2lux-sidebar,
    .v2lux-nav-item,
    .v2lux-nav-item__icon,
    .v2lux-topbar__icon-btn,
    .v2lux-sidebar__search,
    body.v2-dashboard.v2-design-luxury .v2-chart-card,
    body.v2-dashboard.v2-design-luxury .v2-card {
        transition: none !important;
        animation: none !important;
    }
}

/* === V2 LUXURY — overrides 2026-05-09 ===================================
   Patch correctif suite aux retours utilisateur sur la première itération :
   1. Layout : le contenu débordait à droite (double margin-left héritée du
      legacy `.navbar-vertical ~ main` ET de notre `.v2-main`)
   2. Sidebar : passage en palette sombre (proche du legacy Tabler dark)
      pour ne pas trancher visuellement avec la production actuelle
   3. Logo : restauration du vrai logo eZengo (LogoTexteBlanc.png)
      ⚠ CADUC depuis le 2026-07-18 : le PNG a été remplacé par un wordmark
      typographique (cf. `.v2lux-brand__wordmark` plus bas). Le fichier reste
      dans assets/img/logo/ mais n'est plus référencé par aucune vue.
   4. Design switcher : nouveau bouton dans le topbar pour basculer entre
      la production actuelle et le réfiné 2026
   ========================================================================== */

/* --- 1. Layout fix : margin-left explicite sur <main> --- */
/* En mode luxury, le shell N'INCLUT PAS le `<nav id="mainNavbar.navbar-vertical">`
   legacy (il inclut `_sidebar_v2.html.twig` à la place). Donc le sélecteur
   Bootstrap `.navbar-vertical ~ main { margin-left: 16.25rem }` NE MATCH PAS
   et le <main> reste à `margin-left: 0` → le tableau passe sous notre sidebar
   fixed. Fix : on impose explicitement la margin sur <main>. */
body.v2-dashboard.v2-design-luxury main {
    margin-left: var(--v2lux-sidebar-width);
}
/* Tablet narrow (992-1199px) : si on garde un sidebar plus étroit (200px)
   il faut aussi adapter ici. Pour l'instant on garde 16.25rem partout. */
@media (max-width: 1024px) {
    /* Tablet / mobile : sidebar slides off-screen (translateX(-100%) — see
       `.v2lux-sidebar` rule at the same breakpoint), so <main> must reclaim
       the full viewport width too. The hamburger button in the topbar lets
       the user bring the sidebar back over the content as an overlay. */
    body.v2-dashboard.v2-design-luxury main {
        margin-left: 0;
    }
}
/* .v2-main hérite simplement du parent <main> — pas de double margin.
   Width: 100% pour fill le main, et max-width: none pour empêcher les
   centrages forcés sur les tableaux (qui doivent respirer jusqu'au bord). */
body.v2-dashboard.v2-design-luxury main > .v2-main,
body.v2-dashboard.v2-design-luxury main > div.v2-main {
    margin-left: 0;
    margin-right: 0;
    max-width: 100%;
    width: 100%;
}
body.v2-dashboard.v2-design-luxury .v2-main > *,
body.v2-dashboard.v2-design-luxury .v2-main > div {
    max-width: none;
    margin-left: 0;
    margin-right: 0;
}

/* --- 2. Sidebar sombre — palette ink + accent cyan brand eZengo --- */
/* On garde la structure (Fraunces, espacements, accordions) mais on inverse
   la palette : fond bleu marine quasi noir, texte ivoire/blanc cassé, accent
   cyan eZengo (#00b4c0 ≈ hsl(184 100% 39%)) pour les actifs. */
body.v2-dashboard.v2-design-luxury .v2lux-sidebar {
    /* Navy profond `#0a1626` (= rgb(10,22,38) = hsl(214 58% 9%)). Approfondi le
       2026-05-30 : l'ancien `#0e2238` (parité avec le `.bg-dark` Tabler de la
       sidebar legacy V1) paraissait délavé/pâle maintenant que V2 a sa propre
       identité (Geist + cyan #00bac7 + direction Linear). Le navy plus sombre
       densifie la sidebar et fait ressortir le logo + les accents cyan. */
    background-color: #0a1626;
    border-right-color: hsl(209 50% 18%);
    box-shadow: inset -1px 0 0 hsl(209 45% 22% / 0.6),
                4px 0 24px -8px hsl(209 60% 5% / 0.4);
    color: hsl(var(--v2lux-sidebar-ink));

    /* SCROLLBAR INVISIBLE — la sidebar reste scrollable au wheel/trackpad
       pour les écrans étroits, mais aucune barre visuelle ne pollue le
       design. Préférence utilisateur : pas de scrollbar dans une page,
       on veut que ça respire en sticky. */
    scrollbar-width: none;     /* Firefox */
    -ms-overflow-style: none;  /* IE / Edge legacy */
}
body.v2-dashboard.v2-design-luxury .v2lux-sidebar::-webkit-scrollbar {
    display: none;             /* Chrome / Safari */
    width: 0;
    height: 0;
}

/* COMPACTAGE : on resserre légèrement le rythme vertical pour qu'avec 28
   items la sidebar tienne dans 100vh sur un laptop 1080p (1080-64=1016px
   utiles). Padding sidebar et items réduits ~25% sans casser la lisibilité. */
body.v2-dashboard.v2-design-luxury .v2lux-sidebar {
    padding: var(--v2-space-3) var(--v2-space-2) var(--v2-space-2);
}
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__brand {
    padding: 0 var(--v2-space-2) var(--v2-space-3);
    margin-bottom: var(--v2-space-3);
}
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__search {
    padding: 6px 10px;
    margin-bottom: var(--v2-space-3);
    font-size: var(--v2-text-12);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-section {
    margin-bottom: var(--v2-space-2);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-section__label {
    padding: 4px var(--v2-space-3) 2px;
    font-size: var(--v2-text-10);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-item {
    padding: 6px 10px;
    font-size: var(--v2-text-13);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-sub__item {
    padding: 4px 10px 4px 32px;
    font-size: var(--v2-text-12);
}

/* Brand : on remplace l'ancien mark "eZ" par le vrai logo PNG.
   Centré, breath réduit pour ne pas alourdir la sidebar. */
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__brand {
    border-bottom-color: hsl(var(--v2lux-sidebar-rule));
    text-align: left;
}
body.v2-dashboard.v2-design-luxury .v2lux-brand {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    color: hsl(var(--v2lux-sidebar-ink));
}
body.v2-dashboard.v2-design-luxury .v2lux-brand:hover {
    opacity: 0.85;
    color: hsl(var(--v2lux-sidebar-ink));
}
/* Wordmark de la sidebar : même dessin que la page de connexion et que la
   planche de marque (le « Z » en cyan, seule touche de marque). Il remplace le
   PNG LogoTexteBlanc, dont la règle `.v2lux-brand__logo` a été retirée avec lui.
   Le texte hérite du blanc chaud pose par `.v2lux-brand` juste au-dessus. */
body.v2-dashboard.v2-design-luxury .v2lux-brand__wordmark {
    display: block;
    font-family: var(--v2lux-font-body);
    font-size: var(--v2-text-22);
    font-weight: 650;
    letter-spacing: -0.03em;
    line-height: 1.1;
}
/* `--v2-primary-300` est le token documenté « accent cyan clair sur fond
   sombre (sidebar) » : 13:1 sur le navy, là où le cyan de la version claire
   serait trop éteint. */
body.v2-dashboard.v2-design-luxury .v2lux-brand__wordmark-accent {
    color: hsl(var(--v2-primary-300));
}
body.v2-dashboard.v2-design-luxury .v2lux-brand__sub {
    color: hsl(38 20% 60%);
    font-size: var(--v2-text-10);
    letter-spacing: 0.12em;
    margin-top: 0;
}

/* Search affordance — adapté au fond sombre */
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__search {
    background-color: hsl(var(--v2lux-sidebar-surface));
    border-color: hsl(var(--v2lux-sidebar-line));
    color: hsl(38 20% 65%);
    box-shadow: inset 0 1px 0 hsl(var(--v2lux-sidebar-glow) / 0.4);
}
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__search:hover {
    background-color: hsl(var(--v2lux-sidebar-surface-hover));
    border-color: hsl(var(--v2lux-sidebar-line-hover));
    color: hsl(var(--v2lux-sidebar-ink));
}
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__kbd {
    background-color: hsl(210 35% 18%);
    border-color: hsl(210 30% 25%);
    color: hsl(var(--v2lux-sidebar-ink-dim));
}

/* Section labels : "Réconciliation", "Admin", etc. */
body.v2-dashboard.v2-design-luxury .v2lux-nav-section__label {
    color: hsl(38 18% 50%);
}

/* Nav items : texte clair, hover discret, actif cyan eZengo */
body.v2-dashboard.v2-design-luxury .v2lux-nav-item {
    color: hsl(38 22% 78%);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-item:hover {
    background-color: hsl(210 30% 16%);
    color: hsl(38 30% 95%);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-item.is-active,
body.v2-dashboard.v2-design-luxury .v2lux-nav-item--active {
    /* Aligné sur l'état actif du nav plat du haut (.v2lux-nav-flat__link.is-active)
       pour une seule grammaire d'« actif » dans toute la sidebar : texte blanc,
       fond primary plein, poids 600.
       2026-07-18 : le dégradé qui s'éteignait à 70 % de la ligne est remplacé
       par un fond plein (mesuré : ΔE 1,62 en fin de ligne, sous le seuil de
       perception — la moitié droite de l'item actif était indiscernable du
       fond). L'accent latéral ::before a été retiré : c'est un side-stripe,
       banni par PRODUCT.md. */
    background: hsl(var(--v2-primary-500) / 0.14);
    color: #fff;
    font-weight: 600;
    /* Pas d'encadré sur la sidebar sombre : le box-shadow de l'actif de base
       (fond blanc + bordure inset claire + ombre) est un artefact thème-clair qui
       dessine une boîte lourde, faisant paraître l'item actif plus gros que les
       items de base. Sur le thème sombre, l'actif se lit par le fond cyan +
       l'accent latéral ::before + le texte blanc — MÊME taille que la base. */
    box-shadow: none;
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-item__icon {
    color: hsl(var(--v2lux-sidebar-ink-dim));
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-item:hover .v2lux-nav-item__icon {
    color: hsl(38 25% 85%);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-item.is-active .v2lux-nav-item__icon,
body.v2-dashboard.v2-design-luxury .v2lux-nav-item--active .v2lux-nav-item__icon {
    color: #fff;
}

/* Sub-items (collapse children) : un peu plus discrets */
body.v2-dashboard.v2-design-luxury .v2lux-nav-sub__item {
    color: hsl(var(--v2lux-sidebar-ink-muted));
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-sub__item:hover {
    background-color: hsl(var(--v2lux-sidebar-surface));
    color: hsl(38 28% 88%);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-sub__item.is-active,
body.v2-dashboard.v2-design-luxury .v2lux-nav-sub__item--active {
    color: hsl(184 90% 70%);
    background-color: hsl(184 70% 25% / 0.12);
}

/* === Nav sub-menu container (ul) — reset list defaults and add a subtle
   vertical guide on the left to visually anchor the sub-items under their
   parent nav item. Used for Analytics Rebill tab navigation. */
.v2lux-nav-sub {
    list-style: none;
    margin: 4px 0 6px 22px;          /* indent ≈ icon width of parent */
    padding: 4px 0 4px 8px;
    border-left: 1px solid hsl(var(--v2lux-sidebar-glow) / 0.7);
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.v2lux-nav-sub__item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 10px;
    border-radius: var(--v2-radius-sm);
    text-decoration: none;
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-12);
    transition: background 80ms ease, color 80ms ease;
}
.v2lux-nav-sub__item:hover { color: hsl(var(--v2-foreground)); }
.v2lux-nav-sub__icon { flex-shrink: 0; color: currentColor; opacity: 0.8; }
.v2lux-nav-sub__item--active .v2lux-nav-sub__icon { opacity: 1; }
.v2lux-nav-sub__item--active {
    font-weight: 600;
}

/* Analytics Rebill — item row : lien + bouton chevron côte à côte.
   Pattern identique aux section-headers (chevron-down / rotate -90 collapsed). */
.v2lux-nav-item-row {
    display: flex;
    align-items: center;
    gap: 2px;
}
.v2lux-nav-item-row .v2lux-nav-item {
    flex: 1;
    min-width: 0;
}
/* Bouton toggle du sous-menu — discret, ne prend que l'espace du chevron */
.v2lux-nav-sub__toggle {
    flex-shrink: 0;
    background: transparent;
    border: 0;
    padding: 5px 6px;
    border-radius: var(--v2-radius-xs);
    cursor: pointer;
    color: hsl(var(--v2-muted));
    display: flex;
    align-items: center;
    justify-content: center;
    transition: color 80ms ease, background 80ms ease;
    line-height: 0;
}
.v2lux-nav-sub__toggle:hover {
    color: hsl(var(--v2-foreground));
    background: hsl(var(--v2lux-sidebar-glow) / 0.5);
}
/* Chevron : expo-out sur le rotate (pas de spring/overshoot — cf. DESIGN.md
   « Don't use spring easing »). Aligné sur .v2lux-nav-group__chevron. */
.v2lux-nav-sub__chevron {
    transform-origin: center;
    transition: transform var(--v2lux-duration-base) var(--v2-ease),
                color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-nav-item-wrap--collapsed .v2lux-nav-sub__chevron {
    transform: rotate(-90deg);
}
/* Sous-menu : transition max-height identique aux items de section */
.v2lux-nav-sub {
    overflow: hidden;
    max-height: 320px;
    transition: max-height 0.4s var(--v2lux-spring-soft, cubic-bezier(0.4, 0, 0.2, 1)),
                opacity 0.2s ease,
                margin-top 0.4s var(--v2lux-spring-soft, cubic-bezier(0.4, 0, 0.2, 1)),
                margin-bottom 0.4s var(--v2lux-spring-soft, cubic-bezier(0.4, 0, 0.2, 1));
}
.v2lux-nav-item-wrap--collapsed .v2lux-nav-sub {
    max-height: 0;
    opacity: 0;
    margin-top: 0;
    margin-bottom: 0;
    pointer-events: none;
}

/* Footer sidebar (user mini-card) : adapté au sombre */
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__footer {
    border-top-color: hsl(var(--v2lux-sidebar-rule));
}
body.v2-dashboard.v2-design-luxury .v2lux-user-mini {
    background-color: hsl(var(--v2lux-sidebar-surface));
    border-color: hsl(var(--v2lux-sidebar-line));
    color: hsl(38 25% 88%);
}
body.v2-dashboard.v2-design-luxury .v2lux-user-mini:hover {
    background-color: hsl(var(--v2lux-sidebar-surface-hover));
    border-color: hsl(var(--v2lux-sidebar-line-hover));
}
body.v2-dashboard.v2-design-luxury .v2lux-user-mini__name {
    color: hsl(var(--v2lux-sidebar-ink-bright));
}
body.v2-dashboard.v2-design-luxury .v2lux-user-mini__role {
    color: hsl(var(--v2lux-sidebar-ink-dim));
}

/* Footer sidebar — actual template classes (the .v2lux-user-mini rules
   above target a different markup variant). The user identity sits on a
   dark navy background, so foreground must be light to stay readable. */
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__user-name {
    color: hsl(var(--v2lux-sidebar-ink-bright));
}
body.v2-dashboard.v2-design-luxury .v2lux-sidebar__user-role {
    color: hsl(var(--v2lux-sidebar-ink-muted));
}

/* --- 3. Design switcher menu (topbar dropdown) --- */
.v2lux-design-menu {
    min-width: 320px;
    padding: 12px 8px;
}
.v2lux-design-menu__label {
    font-family: var(--v2lux-font-display);
    font-size: var(--v2-text-11);
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: hsl(var(--v2-muted));
    padding: 4px 12px 8px;
}
.v2lux-design-menu__item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px !important;
    border-radius: var(--v2-radius-md);
    cursor: pointer;
    transition: background-color var(--v2lux-duration-fast) var(--v2lux-ease);
    border: 1px solid transparent;
    background: transparent;
    width: 100%;
    text-align: left;
}
.v2lux-design-menu__item:hover {
    background-color: hsl(38 30% 92%);
}
.v2lux-design-menu__item--active {
    background-color: hsl(38 35% 95%);
    border-color: hsl(var(--v2-border));
}
.v2lux-design-menu__swatch {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: var(--v2-radius-sm);
    border: 1px solid hsl(var(--v2-border));
    box-shadow: var(--v2lux-shadow-xs);
}
.v2lux-design-menu__swatch--legacy {
    background: linear-gradient(135deg,
        hsl(210 30% 18%) 0%, hsl(210 30% 18%) 50%,
        hsl(0 0% 98%) 50%, hsl(0 0% 98%) 100%);
}
.v2lux-design-menu__swatch--luxury {
    background: linear-gradient(135deg,
        hsl(210 40% 11%) 0%, hsl(210 40% 11%) 50%,
        hsl(38 50% 95%) 50%, hsl(38 50% 95%) 100%);
}
.v2lux-design-menu__text {
    flex: 1;
    min-width: 0;
}
.v2lux-design-menu__name {
    font-family: var(--v2lux-font-body);
    font-weight: 500;
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-foreground));
    line-height: 1.3;
    display: flex;
    align-items: center;
    gap: 8px;
}
.v2lux-design-menu__chip {
    font-size: var(--v2-text-9);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 2px 6px;
    border-radius: var(--v2-radius-full);
    background: linear-gradient(90deg,
        hsl(184 80% 40%), hsl(184 70% 30%));
    color: white;
}
.v2lux-design-menu__hint {
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    line-height: 1.4;
    margin-top: 2px;
}
.v2lux-design-menu__check {
    flex-shrink: 0;
    color: hsl(184 90% 40%);
}

/* --- 4. Quand le design legacy est actif, on cache notre sidebar/topbar
   luxury et on REMET le legacy navbar visible. Symétrique de l'override
   `display:none` qu'on applique sur le navbar legacy en mode luxury. --- */
body.v2-dashboard.v2-design-legacy .v2lux-sidebar,
body.v2-dashboard.v2-design-legacy .v2lux-topbar {
    display: none !important;
}
body.v2-dashboard.v2-design-legacy #mainNavbar.navbar-vertical {
    display: flex !important;
}

/* === Sidebar collapsible sections (patch 2026-05-09 — itération 3) =========
   Suite à la demande user de pouvoir replier les sections de la sidebar
   pour libérer de la place verticale (la sidebar ayant un overflow:hidden,
   le scroll wheel y est désactivé — la seule façon de "tasser" est donc
   de replier les sections inutiles). État persisté en localStorage.
   La section qui contient l'item actif est force-expanded au load et son
   header refuse de toggle (animation pulse pour signaler le refus). */

/* Le label était un <div> flex avant — maintenant c'est un <span> à
   l'intérieur du <button class="v2lux-nav-group__toggle">. On reset le
   button pour qu'il se comporte comme l'ancien div header. */
.v2lux-nav-group__toggle {
    display: flex;
    align-items: center;            /* centrage vertical du contenu dans la bande */
    gap: 8px;
    width: 100%;
    min-height: 34px;               /* bandes de hauteur uniforme -> alignement net */
    border: 0;
    /* Plus de bandeau plein : un en-tête de section est un CONTENEUR, il ne doit
       jamais peser plus lourd que la destination sur laquelle l'utilisateur se
       trouve. La bande `background: hsl(--v2-foreground / 0.04)` sur toute la
       largeur, combinée à la graisse 600 et à l'icône cyan, écrasait
       visuellement l'ancre active (dont le fond, lui, se dissout à mi-ligne).
       L'en-tête redevient un simple libellé ; seul le survol le matérialise. */
    padding: 6px 12px;
    margin-bottom: 2px;
    border-radius: var(--v2-radius-md);
    background: transparent;
    cursor: pointer;
    text-align: left;
    color: hsl(var(--v2-primary-300) / 0.62);
    font-family: inherit;
    transition: color var(--v2lux-duration-fast) var(--v2lux-ease),
                background-color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-nav-group__toggle:hover {
    color: hsl(var(--v2-foreground));
}
.v2lux-nav-group__toggle:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
    border-radius: var(--v2-radius-xs);
}
/* Le label hérite maintenant des styles tipo via le button parent.
   On préserve le flex pour que le chip "live" reste à droite du texte. */
.v2lux-nav-group__toggle .v2lux-nav-group__label {
    display: flex;
    align-items: center;
    justify-content: flex-start;  /* OVERRIDE le space-between hérité de .v2lux-nav-group__label : icône + texte collés */
    gap: 10px;                    /* = écart icône/texte des items -> labels alignés */
    flex: 1 1 auto;               /* pousse compteur + chevron à droite */
    min-width: 0;
    padding: 0;                   /* OVERRIDE le padding 0 12px 8px hérité (padding-bas 8px) qui poussait le contenu vers le HAUT -> centrage vertical net dans la bande (vérifié MCP) */
    color: inherit;               /* hérite du toggle */
}
/* Texte du libellé : ellipsis propre quand trop long (« Journaux & technique »,
   libellés DE plus longs) au lieu d'une coupe mi-mot. */
.v2lux-nav-group__label-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Icône de section (option A) : remplace l'ancien filet cyan ::before — repère
   visuel par section, scannable (vs labels nus). Accent de marque cyan. */
.v2lux-nav-group__icon {
    flex: none;
    width: 16px;            /* = icône des items (.v2lux-nav-item__icon) -> alignement vertical */
    height: 16px;
    color: hsl(var(--v2-primary-300));
    opacity: 0.9;
}

/* Chevron — rotate selon l'état collapsed. La transition spring est
   définie dans le bloc "patch itération 4" plus bas (ligne ~5057+) ;
   ici on garde uniquement les props qui ne dépendent pas du timing
   spring (color, opacity, transform-origin). */
.v2lux-nav-group__chevron {
    flex-shrink: 0;
    transform-origin: center;
    color: hsl(var(--v2-muted));
    opacity: 0.7;
}
.v2lux-nav-group__toggle:hover .v2lux-nav-group__chevron {
    opacity: 1;
}
.v2lux-nav-group--collapsed .v2lux-nav-group__chevron {
    transform: rotate(-90deg);
}
/* `@keyframes v2lux-chevron-blocked` + `.v2lux-nav-group__chevron--blocked`
   retirés le 2026-07-18 : code mort. Cette secousse ambre signalait un refus
   de repli, mais le garde-fou qui bloquait le repli de la section active a
   été supprimé en v5 (2026-05-17) — plus aucun JS n'ajoute la classe depuis. */

/* Items — collapse via max-height + opacity. max-height assez grand pour
   le plus gros groupe (Cockpit Ops, 11 items + sous-menu Rebill 9 items).
   Valeur généreuse pour ne jamais couper du contenu.
   Pas de display:none parce que ça casserait la transition. */
.v2lux-nav-group__items {
    overflow: hidden;
    max-height: 1400px;
    opacity: 1;
    transition: max-height var(--v2lux-duration-base) var(--v2lux-ease),
                opacity var(--v2lux-duration-fast) var(--v2lux-ease),
                margin-top var(--v2lux-duration-base) var(--v2lux-ease),
                margin-bottom var(--v2lux-duration-base) var(--v2lux-ease);
}
.v2lux-nav-group--collapsed .v2lux-nav-group__items {
    max-height: 0;
    opacity: 0;
    margin-top: 0;
    margin-bottom: 0;
    pointer-events: none;
}

/* Mode dark : le toggle hérite du label dark mais il faut un hover
   spécifique pour qu'il se distingue bien sur fond ink.
   Contraste : `hsl(38 22% 62%)` sur `hsl(210 40% 11%)` ≈ 5.6:1 → WCAG AA
   pour du texte UI (bumped depuis hsl(38 18% 50%) qui était à 4.0:1). */
/* Le bandeau clair permanent (`background: hsl(0 0% 100% / 0.05)`) a sauté le
   2026-07-18. Sur la sidebar sombre, quatre bandes pleine largeur toujours
   allumées pesaient plus lourd que l'ancre de la page courante : on repérait
   les CONTENEURS avant la DESTINATION. Le fond ne se matérialise plus qu'au
   survol, où il porte une vraie information (« cet en-tête est cliquable »). */
body.v2-dashboard.v2-design-luxury .v2lux-nav-group__toggle {
    color: hsl(38 22% 66%);                  /* un cran en retrait des items de nav */
    background: transparent;
}
/* Note : le fond de survol est défini plus bas (bloc « Hover affordance »,
   même spécificité mais plus tardif dans le fichier, donc gagnant). On ne
   pose ici que la couleur pour ne pas laisser une déclaration morte. */
body.v2-dashboard.v2-design-luxury .v2lux-nav-group__toggle:hover {
    color: hsl(var(--v2lux-sidebar-ink-bright));
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-group__chevron {
    color: hsl(38 22% 60%);
}

/* === Sidebar collapse — vision intelligente (patch 2026-05-09 itération 4)
   Améliorations sur le collapse :
   1. Easing spring (bounce léger) pour un feel plus organique
   2. Stagger des items à l'expand (cascade top→bottom)
   3. Compteur d'items dans le header collapsed (ex. "Réconciliation 5")
   4. Toolbar "Replier / Déplier" en haut de la nav
   5. Chevron qui scale + rotate avec overshoot
   ========================================================================== */

/* --- Easing tokens spécifiques au collapse --- */
:root {
    /* Le spring "dur" (overshoot 56 %) a été retiré le 2026-05-30 : interdit
       par DESIGN.md (« Don't use spring easing »). Les chevrons utilisent
       désormais --v2-ease (expo-out). Seul le spring-soft (overshoot léger)
       subsiste, réservé aux transitions de hauteur/opacité (expand/collapse),
       où un settle doux reste acceptable. */
    --v2lux-spring-soft: cubic-bezier(0.5, 1.25, 0.4, 1);
}

/* --- Toolbar collapse/expand all : RETIRÉ —
   Le markup `<div class="v2lux-sidebar__toolbar">` a été supprimé du
   template Twig (préférence utilisateur : sidebar default-collapsed,
   pas de boutons globaux). Les classes `.v2lux-sidebar__toolbar` et
   `.v2lux-sidebar__tool` ne sont plus utilisées. À ré-ajouter depuis
   le backup `.backups/2026-05-09-collapse-smart/dashboard-v2.css.bak`
   si on remet la toolbar un jour. */

/* --- Compteur d'items dans le header : RETIRÉ le 2026-07-18 ---
   `.v2lux-nav-group__count` affichait le NOMBRE DE LIENS d'une section, et
   passait en cyan vif une fois la section repliée — ce qui faisait de nombres
   sans portée les éléments les plus lumineux du rail. Dans un backoffice de
   paiement, un nombre en pastille se lit comme une charge de travail :
   « Réconciliation 8 » se lisait comme 8 écarts en attente. La forme entrait
   de surcroît en collision avec la pastille ambre d'Analytics Rebill, qui
   compte de vrais champs à fiabiliser. Les pastilles sont désormais réservées
   aux compteurs actionnables. Le JS qui remplissait le span
   (`_populateCounts`) a été retiré dans le même lot. */

/* ─── Section active : la section qui contient la page courante est surlignée
   (repère « où je suis »). :has() — dégrade proprement si non supporté. ─── */
.v2lux-nav-group:has(.v2lux-nav-item--active) > .v2lux-nav-group__toggle {
    background: hsl(var(--v2-primary-500) / 0.12);
    color: hsl(var(--v2-foreground));
}
.v2lux-nav-group:has(.v2lux-nav-item--active) > .v2lux-nav-group__toggle .v2lux-nav-group__icon {
    color: hsl(var(--v2-primary-500));
    opacity: 1;
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-group:has(.v2lux-nav-item--active) > .v2lux-nav-group__toggle {
    background: hsl(var(--v2-primary-500) / 0.16);
    color: hsl(38 30% 94%);
}
body.v2-dashboard.v2-design-luxury .v2lux-nav-group:has(.v2lux-nav-item--active) > .v2lux-nav-group__toggle .v2lux-nav-group__icon {
    color: hsl(var(--v2-primary-300));
}

/* ─── Entrée du menu (« pas animé ») : les ancrages puis les sections glissent et
   fondent en cascade légère depuis la gauche. Reduced-motion -> aucun mouvement. ─── */
@media (prefers-reduced-motion: no-preference) {
    .v2lux-nav-flat__item,
    .v2lux-nav-group {
        animation: v2lux-nav-in 0.34s cubic-bezier(0.22, 1, 0.36, 1) both;
    }
    .v2lux-nav-flat__item:nth-child(2) { animation-delay: 40ms; }
    .v2lux-nav-flat__item:nth-child(3) { animation-delay: 80ms; }
    .v2lux-nav-flat__item:nth-child(4) { animation-delay: 120ms; }
    .v2lux-nav-group:nth-of-type(1) { animation-delay: 150ms; }
    .v2lux-nav-group:nth-of-type(2) { animation-delay: 185ms; }
    .v2lux-nav-group:nth-of-type(3) { animation-delay: 220ms; }
    .v2lux-nav-group:nth-of-type(4) { animation-delay: 255ms; }
    .v2lux-nav-group:nth-of-type(n+5) { animation-delay: 290ms; }
}
@keyframes v2lux-nav-in {
    from { opacity: 0; transform: translateX(-6px); }
    to   { opacity: 1; transform: none; }
}

/* --- Chevron : expo-out pour le rotate (pas de spring — cf. DESIGN.md) --- */
.v2lux-nav-group__chevron {
    transition: transform var(--v2lux-duration-base) var(--v2-ease),
                color var(--v2lux-duration-fast) var(--v2lux-ease),
                opacity var(--v2lux-duration-fast) var(--v2lux-ease);
}

/* --- Items : stagger à l'expand pour effet cascade --- */
/* Quand expanded : chaque item apparaît avec un délai croissant.
   Quand collapsed : tous disparaissent en même temps (fast).
   Le délai utilise nth-child donc pas besoin de toucher au markup Twig. */
.v2lux-nav-group__items > li {
    transition: opacity 0.25s var(--v2lux-spring-soft),
                transform 0.3s var(--v2lux-spring-soft);
    opacity: 1;
    transform: translateX(0);
}
/* Cascade à l'expand — incrément 25ms par item, max ~12 items. */
.v2lux-nav-group__items > li:nth-child(1)  { transition-delay: 0.05s; }
.v2lux-nav-group__items > li:nth-child(2)  { transition-delay: 0.07s; }
.v2lux-nav-group__items > li:nth-child(3)  { transition-delay: 0.09s; }
.v2lux-nav-group__items > li:nth-child(4)  { transition-delay: 0.11s; }
.v2lux-nav-group__items > li:nth-child(5)  { transition-delay: 0.13s; }
.v2lux-nav-group__items > li:nth-child(6)  { transition-delay: 0.15s; }
.v2lux-nav-group__items > li:nth-child(7)  { transition-delay: 0.17s; }
.v2lux-nav-group__items > li:nth-child(8)  { transition-delay: 0.19s; }
.v2lux-nav-group__items > li:nth-child(9)  { transition-delay: 0.21s; }
.v2lux-nav-group__items > li:nth-child(10) { transition-delay: 0.23s; }
.v2lux-nav-group__items > li:nth-child(11) { transition-delay: 0.25s; }
.v2lux-nav-group__items > li:nth-child(12) { transition-delay: 0.27s; }

/* État collapsed : items invisibles + slide left-out, instantané (pas de délai) */
.v2lux-nav-group--collapsed .v2lux-nav-group__items > li {
    opacity: 0;
    transform: translateX(-6px);
    transition-delay: 0s;
    transition-duration: 0.12s;
}

/* --- Container items : le max-height anime aussi en spring --- */
.v2lux-nav-group__items {
    transition: max-height 0.4s var(--v2lux-spring-soft),
                opacity 0.2s var(--v2lux-ease),
                margin-top 0.4s var(--v2lux-spring-soft),
                margin-bottom 0.4s var(--v2lux-spring-soft);
}

/* ═══ Verdict sur les 11 constats « layout-transition » (audit 2026-07-25)
   Le detecteur signale toute animation de width / height / max-height comme
   un risque de saccade. Les 11 ont ete examines un par un ; AUCUN n'est un
   defaut, et cette note existe pour qu'on cesse de les re-lever :

   6 sont des BARRES DE DONNEES (.v2-dc-meter__seg, .v2-perf-row__bar-fill,
     .v2-job-progress__bar, .v2-recon-import-progress__bar,
     .v2-bins-board__bar-fill, .v2-acq-board__bar-fill). La largeur EST la
     donnee : un transform: scaleX() deformerait le contenu et les bordures.

   3 sont ces accordeons de sidebar, en max-height. Mesure en production :
     le plus grand groupe fait 347 px de contenu pour un plafond de 1400 px —
     aucune coupure. La conversion en grid-template-rows: 0fr/1fr serait plus
     reguliere (l'animation s'arrete aujourd'hui a ~100 ms sur 400, la part
     visible etant atteinte tot), mais elle exige un element enveloppe dans le
     balisage : un <ul> passe en display:grid repartirait ses <li> sur des
     rangees distinctes et 0fr ne s'appliquerait qu'a la premiere. Changement
     de balisage sur la navigation de 57 pages, pour un gain non mesurable a
     l'oeil : non retenu.
     ⚠ Le plafond de .v2lux-nav-sub est a 320 px. Un sous-menu qui le
     depasserait serait COUPE en silence (overflow: hidden). A surveiller si
     l'on y ajoute des entrees.

   1 est stroke-width sur un point SVG (.v2-bm-strip-dot) : propriete de
     peinture SVG, pas de mise en page du document.

   1 est un FAUX POSITIF (.v2-stl-bar__fill) : le detecteur y lit un
     `width: 100%` statique, pas une transition. */

/* ⛔ Ces trois transitions manquaient à l'appel de prefers-reduced-motion
   (audit 2026-07-25). Le fichier honore ce réglage à 24 autres endroits ; le
   dépliage des sections de la sidebar était le seul oubli — et c'est celui
   qui utilise `--v2lux-spring-soft`, une courbe qui DÉPASSE sa valeur
   d'arrivée (1.25 > 1). Un utilisateur ayant demandé à réduire les
   animations voyait donc le panneau rebondir à chaque ouverture de section.
   On coupe la transition plutôt que de l'adoucir : l'état final est le même,
   il est simplement atteint immédiatement. */
@media (prefers-reduced-motion: reduce) {
    .v2lux-nav-sub,
    .v2lux-nav-group__items,
    .v2lux-nav-group__items > li {
        transition: none;
    }
}

/* --- Hover affordance : le toggle réagit visuellement avant le click --- */
.v2lux-nav-group__toggle {
    border-radius: var(--v2-radius-sm);
    transition: background-color var(--v2lux-duration-fast) var(--v2lux-ease),
                color var(--v2lux-duration-fast) var(--v2lux-ease),
                transform var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-nav-group__toggle:hover {
    background-color: hsl(var(--v2-bg-elevated) / 0.5);
}
/* Seul fond que porte un en-tête de section (le bandeau permanent a été retiré
   le 2026-07-18) : il doit donc être franchement perceptible, sinon l'en-tête
   ne signale plus du tout qu'il est cliquable. */
body.v2-dashboard.v2-design-luxury .v2lux-nav-group__toggle:hover {
    background-color: hsl(0 0% 100% / 0.07);
}
.v2lux-nav-group__toggle:active {
    transform: scale(0.985);
}

/* End of V2 LUXURY SHELL ==================================================== */
/* === Command palette (⌘K) — V2 Luxury Shell === */
.v2lux-cmdk {
    position: fixed;
    inset: 0;
    z-index: 1080;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 12vh;
}

.v2lux-cmdk[hidden] { display: none; }

.v2lux-cmdk__backdrop {
    position: absolute;
    inset: 0;
    background: rgb(15 23 42 / 0.55);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}

.v2lux-cmdk__panel {
    position: relative;
    width: min(560px, calc(100vw - 2rem));
    max-height: 70vh;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-lg);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: v2lux-cmdk-in 140ms ease-out;
}

@keyframes v2lux-cmdk-in {
    from { opacity: 0; transform: translateY(-8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0)    scale(1); }
}

.v2lux-cmdk__input-wrap {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.875rem 1rem;
    border-bottom: 1px solid hsl(var(--v2-border));
}

.v2lux-cmdk__input-icon { color: hsl(var(--v2-muted)); flex-shrink: 0; }

.v2lux-cmdk__input {
    flex: 1;
    border: 0;
    outline: 0;
    background: transparent;
    font-family: var(--v2lux-font-body);
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-foreground));
    padding: 0;
}

.v2lux-cmdk__input::placeholder { color: hsl(var(--v2-muted)); }

.v2lux-cmdk__kbd {
    font-family: var(--v2lux-font-mono);
    font-size: var(--v2-text-11);
    padding: 0.125rem 0.375rem;
    border-radius: var(--v2-radius-sm);
    background: hsl(var(--v2-muted-bg));
    border: 1px solid hsl(var(--v2-border));
    color: hsl(var(--v2-muted));
    line-height: 1.2;
}

.v2lux-cmdk__results {
    list-style: none;
    margin: 0;
    padding: 0.375rem;
    overflow-y: auto;
    flex: 1;
    min-height: 0;
}

.v2lux-cmdk__result {
    display: grid;
    grid-template-columns: 20px 1fr auto;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--v2-radius-sm);
    cursor: pointer;
    color: hsl(var(--v2-foreground));
    transition: background 80ms ease;
}

.v2lux-cmdk__result--active,
.v2lux-cmdk__result:hover {
    background: hsl(var(--v2-muted-bg));
}

.v2lux-cmdk__result-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: hsl(var(--v2-muted));
}

.v2lux-cmdk__result-icon svg { width: 14px; height: 14px; }

.v2lux-cmdk__result-label {
    font-size: var(--v2-text-14);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.v2lux-cmdk__result-group {
    font-size: var(--v2-text-11);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: hsl(var(--v2-muted));
    font-weight: 600;
}

.v2lux-cmdk__empty {
    padding: 1.5rem;
    text-align: center;
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
}

.v2lux-cmdk__footer {
    display: flex;
    gap: 1rem;
    padding: 0.5rem 1rem;
    border-top: 1px solid hsl(var(--v2-border));
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    background: hsl(var(--v2-muted-bg) / 0.4);
}

.v2lux-cmdk__footer kbd { margin-right: 0.25rem; }

/* =================================================================
   Analytics Rebill — sticky scroll affordances
   -----------------------------------------------------------------
   Layered sticky stack so the user always sees CONTEXT while scrolling :
     - The Luxury topbar is already sticky (top: 0, z-index: 50)
     - The page toolbar (company/env/period) sticks under the topbar
     - The tabs nav sticks under the toolbar
     - The table <thead> sticks at the bottom of the stack
   Each layer has a backdrop-filtered background so content shows
   through subtly without bleeding through to the next layer.
   ================================================================= */

/* Toolbar (company + env + period) — sticky right under the topbar.
   Scoped to the rebill page via class so other pages aren't affected. */
/* Barre de contexte unifiée (toolbar marchand+période + bandeau d'épingle sur
   une ligne). Sticky portée par le wrapper ; les deux cartes en flex. */
.v2-rebill-context {
    position: sticky;
    top: 64px; /* topbar height */
    z-index: 30;
    background: hsl(var(--v2-bg));
    display: flex;
    align-items: stretch;
    gap: var(--v2-space-3);
    flex-wrap: wrap;
    padding: 8px 0;
    margin: -8px 0 var(--v2-space-3);
}
/* Le toolbar (sélecteur marchand + période) a plus de contenu que le bandeau
   d'épingle : il prend la part large (flex 2) pour tenir sur UNE ligne au lieu
   de faire wrapper « Période » en 2e ligne ; le pin prend la part étroite. */
.v2-rebill-context > .v2-rebill-toolbar { margin-bottom: 0; flex: 2 1 480px; }
.v2-rebill-context > .v2-pin-context { margin-bottom: 0; flex: 1 1 320px; }

/* Tabs nav inside the rebill page — sticks under the toolbar */
[data-testid="v2-analytics-rebill"] .nav-tabs {
    position: sticky;
    top: calc(64px + 56px); /* topbar + toolbar */
    z-index: 25;
    background: hsl(var(--v2-bg));
    padding-top: 4px;
    margin-top: 0;
}

/* Note on sticky <thead> : we tried but it conflicts with the wrapper's
   `overflow-x: auto` (needed for narrow viewports). Setting overflow on
   one axis forces a scroll context on the other in CSS, which prevents
   `position: sticky` inside from anchoring to the page. The toolbar +
   tabs sticky stack covers the main use case (navigating long tables
   while keeping the controls reachable); we accept that column headers
   scroll out of view. If we need them later, the proper fix is to split
   the table into header / body tables synced via JS — too much for now. */

/* On small viewports the toolbar/tabs sticky stack eats too much vertical
   real estate. Below 900px we let them scroll naturally — the user is
   likely on a tablet/phone where scrolling is fast anyway. */
@media (max-width: 900px) {
    .v2-rebill-context { position: static; }
    [data-testid="v2-analytics-rebill"] .nav-tabs { position: static; }
}

/* =================================================================
   Mobile hamburger + sidebar backdrop
   ================================================================= */
.v2lux-topbar__hamburger {
    display: none; /* visible only on <1024px via media query below */
    align-items: center;
    justify-content: center;
    /* Aligné sur .v2lux-topbar__icon-btn (cloche, user) : 40px, bordure
       transparente au repos, même couleur + hover -> barre du haut cohérente
       (le burger ne ressort plus comme un bouton à part, plus petit et bordé).
       L'espacement au titre est porté par le gap du topbar (pas de margin). */
    width: 40px;
    height: 40px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--v2-radius-md);
    color: hsl(var(--v2-muted));
    cursor: pointer;
    transition: all var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2lux-topbar__hamburger:hover {
    background: hsl(var(--v2-bg-elevated));
    border-color: hsl(var(--v2-border));
    color: hsl(var(--v2-foreground));
    box-shadow: var(--v2lux-shadow-xs);
}
.v2lux-topbar__hamburger:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.25);
    border-color: hsl(var(--v2-primary-500));
}

.v2lux-sidebar-backdrop {
    position: fixed;
    inset: 0;
    background: rgb(15 23 42 / 0.55);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    z-index: 39; /* sidebar is z-index 40+, backdrop just below */
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--v2lux-duration-base, 200ms) var(--v2lux-ease, ease);
}
.v2lux-sidebar-backdrop--visible {
    opacity: 1;
    pointer-events: auto;
}

@media (max-width: 1024px) {
    .v2lux-topbar__hamburger { display: inline-flex; }
    .v2lux-sidebar {
        z-index: 41; /* above backdrop */
        box-shadow: 4px 0 20px rgb(0 0 0 / 0.25);
    }
}
@media (min-width: 1025px) {
    /* On desktop the backdrop must never appear (could happen if it got
       toggled visible then user resized the viewport). */
    .v2lux-sidebar-backdrop { display: none; }
}

/* =================================================================
   Analytics Rebill — progressive column hiding for the dense tables
   so we stop relying on horizontal scroll at common viewport widths.
   Columns are tagged via nth-child since we can't add classes to
   live-rendered <td> easily. Priority order (least → most important
   to hide first) :
     - Part volume (col 14)  — derivable
     - Réseaux    (col 4)   — already implicit via Type badges
     - BINs       (col 5)   — implementation detail
     - Tentatives (col 8)   — user mostly cares about result, not attempts
     - Pays       (col 2)   — usually FRA
   We KEEP : Banque (1), Type (3), Clients (6), Payants (7), Acceptées (9),
            Refusées (10), Acceptance (11), CA encaissé (12), CA moy (13)
   ================================================================= */

/* Breakpoints calibrated on EFFECTIVE table width (≈ viewport − 380px
   overhead = sidebar 260 + page padding 48 + chart-card padding ~70).
   Hide one column at a time as space tightens; min-width comes from the
   template's inline style as a hard floor (720px). */

/* 1500-1699px : hide "Part volume" (col 14) */
@media (max-width: 1699px) {
    .v2-rebill-banks-table thead th:nth-child(14),
    .v2-rebill-banks-table tbody td:nth-child(14) { display: none; }
}

/* 1300-1499px : also hide "Réseaux" (col 4) */
@media (max-width: 1499px) {
    .v2-rebill-banks-table thead th:nth-child(4),
    .v2-rebill-banks-table tbody td:nth-child(4) { display: none; }
}

/* 1100-1299px : also hide "BINs" (col 5) + "Tentatives" (col 8) */
@media (max-width: 1299px) {
    .v2-rebill-banks-table thead th:nth-child(5),
    .v2-rebill-banks-table tbody td:nth-child(5),
    .v2-rebill-banks-table thead th:nth-child(8),
    .v2-rebill-banks-table tbody td:nth-child(8) { display: none; }
}

/* <1100px : also hide "Pays" (col 2) */
@media (max-width: 1100px) {
    .v2-rebill-banks-table thead th:nth-child(2),
    .v2-rebill-banks-table tbody td:nth-child(2) { display: none; }
}

/* <768px (phone) : drop "Type(s)" badges + "Refusées" */
@media (max-width: 768px) {
    .v2-rebill-banks-table thead th:nth-child(3),
    .v2-rebill-banks-table tbody td:nth-child(3),
    .v2-rebill-banks-table thead th:nth-child(10),
    .v2-rebill-banks-table tbody td:nth-child(10) { display: none; }
}

/* Brands table (8 cols) — drop "Part volume" early, "BINs distincts" on phone */
@media (max-width: 1299px) {
    .v2-rebill-brands-table thead th:nth-child(8),
    .v2-rebill-brands-table tbody td:nth-child(8) { display: none; }
}
@media (max-width: 768px) {
    .v2-rebill-brands-table thead th:nth-child(2),
    .v2-rebill-brands-table tbody td:nth-child(2) { display: none; }
}

/* Recover ~50px of table real estate by reducing chart-card body padding
   on the rebill page on small/medium viewports. */
@media (max-width: 1399px) {
    [data-testid="v2-analytics-rebill"] .v2-chart-card__body {
        padding-left: var(--v2-space-3);
        padding-right: var(--v2-space-3);
    }
}

/* Harden : les tables denses (Récurrence 8 col, Mensuel, funnel du Résumé)
   défilent horizontalement DANS leur carte au lieu de casser la mise en page
   sur écran étroit. On cible les bodies qui contiennent directement une
   .v2-table (donc ni les donuts, ni les heatmaps qui ont déjà leur wrapper).
   overflow-x:auto n'affiche une barre que si le contenu déborde réellement. */
[data-testid="v2-analytics-rebill"] .v2-chart-card:has(> .v2-chart-card__body > .v2-table) > .v2-chart-card__body {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}

/* Cell density on medium screens — claim back some pixels.
   Two tiers : 1499px starts tightening padding, 1199px also shrinks font. */
@media (max-width: 1499px) {
    .v2-rebill-banks-table thead th,
    .v2-rebill-banks-table tbody td,
    .v2-rebill-brands-table thead th,
    .v2-rebill-brands-table tbody td {
        padding-left: var(--v2-space-2);
        padding-right: var(--v2-space-2);
    }
}
@media (max-width: 1199px) {
    .v2-rebill-banks-table thead th,
    .v2-rebill-banks-table tbody td,
    .v2-rebill-brands-table thead th,
    .v2-rebill-brands-table tbody td {
        font-size: var(--v2-text-13);
    }
}

/* Hard cap : the table must never exceed its wrapper's available width.
   Without this the natural column widths can blow past the calculated
   min-width and cause horizontal scroll even after column hiding. The
   browser auto-shrinks columns proportionally when this kicks in. */
.v2-rebill-banks-table,
.v2-rebill-brands-table {
    width: 100%;
    max-width: 100%;
    table-layout: auto;
}

/* ── Company list avatar (Sprint Bridge-B) ───────────────────────────────── */
.v2-company-name-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    text-decoration: none;
    color: inherit;
}
.v2-company-name-link .v2-cell-id {
    text-decoration: none;
}
.v2-company-name-link:hover .v2-cell-id {
    color: hsl(var(--v2-primary-500));
    text-decoration: underline;
}
.v2-company-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    border-radius: var(--v2-radius-sm);
    background: hsl(var(--v2-primary-500) / 0.1);
    border: 1px solid hsl(var(--v2-primary-500) / 0.2);
    font-family: var(--v2lux-font-display);
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(184 80% 32%); /* brand cyan darkened for AA contrast on light bg */
    line-height: 1;
    user-select: none;
}
.v2-company-row--inactive { opacity: 0.55; }
.v2-company-row--inactive:hover { opacity: 0.85; }

/* ── Bootstrap Tooltip contenu carte (v2-bstt) ───────────────────────────── */
/* Ces règles s'appliquent DANS le .tooltip-inner de Bootstrap               */
.v2-bstt {
    text-align: left;
    min-width: 160px;
}
.v2-bstt__row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    padding: 2px 0;
    font-size: var(--v2-text-12);
    line-height: 1.4;
}
.v2-bstt__row + .v2-bstt__row {
    border-top: 1px solid rgba(255,255,255,0.12);
    margin-top: 2px;
    padding-top: 4px;
}
.v2-bstt__label {
    font-size: var(--v2-text-11);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    opacity: 0.65;
    flex-shrink: 0;
}
.v2-bstt__badge {
    display: inline-block;
    padding: 1px 5px;
    font-size: var(--v2-text-10);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    border: 1px solid rgba(255,255,255,0.3);
    border-radius: var(--v2-radius-2xs);
    opacity: 0.9;
}
/* Message d'erreur PSP complet (tooltip d'état liste transactions) : texte libre,
   passe à la ligne au lieu d'être tronqué comme la cellule. */
.v2-bstt__msg {
    margin-top: 4px;
    padding-top: 4px;
    border-top: 1px solid rgba(255,255,255,0.12);
    max-width: 260px;
    font-size: var(--v2-text-12);
    line-height: 1.45;
    white-space: normal;
    word-break: break-word;
}

/* ── Badge info carte ─────────────────────────────────────────────────────── */
.v2-card-info-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 13px;
    height: 13px;
    border-radius: 50%;
    background: hsl(var(--v2-primary-500) / 0.12);
    border: 1px solid hsl(var(--v2-primary-500) / 0.35);
    color: hsl(var(--v2-primary-500));
    font-family: var(--v2lux-font-body);
    font-size: var(--v2-text-9);
    font-weight: 700;
    font-style: italic;
    line-height: 1;
    flex-shrink: 0;
    user-select: none;
    transition: background-color 120ms ease, border-color 120ms ease;
}
.v2-card-trigger:hover .v2-card-info-badge {
    background: hsl(var(--v2-primary-500) / 0.22);
    border-color: hsl(var(--v2-primary-500) / 0.6);
}

/* ── Note explicative d'une carte dont la métrique n'est pas encore mesurable
   Remplace la grille d'axes nue qui s'affichait quand une courbe n'a qu'un
   point : celle-ci était indiscernable d'une requête plantée. Le ton est
   informatif, pas alarmant, d'où le fond neutre plutôt qu'un jaune d'alerte :
   il n'y a rien de cassé, la donnée n'a simplement pas fini d'exister. */
.v2-cohort-note {
    margin: 0 0 var(--v2-space-3);
    padding: 10px 12px;
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    background: hsl(var(--v2-muted) / 0.06);
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-13);
    line-height: 1.55;
    max-width: 78ch;
    text-wrap: pretty;
}

/* ── Marqueur « en cours » sur une cohorte du mois courant ─────────────────
   Une cohorte née sur le mois en cours a un volume réel mais pas définitif, et
   aucune rétention calculable. Elle était purement masquée, ce qui faisait
   disparaître des mois entiers d'acquisition ; elle est maintenant affichée
   avec ce marqueur. Ton neutre volontaire : ce n'est ni une erreur ni une
   alerte, juste un chiffre qui n'a pas fini de se remplir. */
.v2-cohort-wip {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 6px;
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-full);
    background: hsl(var(--v2-muted) / 0.08);
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-11);
    font-weight: 500;
    line-height: 1.4;
    white-space: nowrap;
    vertical-align: middle;
    cursor: help;
}

/* ── Icône « i » d'état (Transactions récentes, page site) ─────────────────
   Révèle le motif de refus PSP + la carte utilisée. C'est un <button> et non
   un <span> pour être atteignable au clavier : le tooltip Bootstrap est armé
   sur `hover focus`, donc le focus suffit à l'ouvrir. */
.v2-state-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    flex-shrink: 0;
    border: 1px solid hsl(var(--v2-border));
    border-radius: 50%;
    background: transparent;
    color: hsl(var(--v2-muted));
    cursor: help;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
.v2-state-info:hover {
    background: hsl(var(--v2-primary-500) / 0.12);
    border-color: hsl(var(--v2-primary-500) / 0.45);
    color: hsl(var(--v2-primary-500));
}
.v2-state-info:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
}
/* Refus : l'icône porte l'information la plus utile de la ligne (le motif),
   on la teinte donc — c'est une vraie erreur, pas une valeur basse. */
.v2-state-info--danger {
    border-color: hsl(var(--v2-danger-500) / 0.4);
    color: hsl(var(--v2-danger-600));
}
.v2-state-info--danger:hover {
    background: hsl(var(--v2-danger-500) / 0.12);
    border-color: hsl(var(--v2-danger-500) / 0.65);
    color: hsl(var(--v2-danger-600));
}
@media (prefers-reduced-motion: reduce) {
    .v2-state-info {
        transition: none;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Bins tab — refonte 2026-05-18
   Goal : remplacer la pile de 3 tables denses par une vue éditoriale
   synthétique : ruban KPI, carte du monde, leaderboards, drill-down.
   Aesthetic : Bloomberg meets Linear — densité contrôlée, big numbers,
   cyan accent sur les data points premium. Cohérent avec Luxury Shell.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ─── Phrase d'usage de l'onglet ─────────────────────────────────────────── */
.v2-bins-intro {
    margin: 0 0 var(--v2-space-4);
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-muted));
    max-width: 75ch;
    text-wrap: pretty;
}

/* ─── Ruban KPI (4 tiles) ────────────────────────────────────────────────── */
.v2-bins-kpi-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--v2-space-3);
    margin-bottom: var(--v2-space-5);
}
@media (max-width: 1024px) {
    .v2-bins-kpi-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 600px) {
    .v2-bins-kpi-grid { grid-template-columns: 1fr; }
}

.v2-bins-kpi {
    position: relative;
    padding: var(--v2-space-4) var(--v2-space-5);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    transition: transform var(--v2lux-duration-fast) var(--v2lux-ease),
                box-shadow var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bins-kpi:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 16px hsl(var(--v2-shadow-ink) / 0.05);
}
/* Accent tile (1ère colonne) : filet vertical cyan + fond très légèrement teinté. */
.v2-bins-kpi--accent {
    background: linear-gradient(180deg, hsl(var(--v2-databar-teal) / 0.04), hsl(var(--v2-card)) 40%);
}
.v2-bins-kpi--accent::before {
    content: '';
    position: absolute;
    left: 0; top: 14%; bottom: 14%;
    width: 3px;
    background: hsl(var(--v2-primary-500));
    border-radius: 0 2px 2px 0;
}

.v2-bins-kpi__eyebrow {
    font-size: var(--v2-text-12);
    font-weight: 500;
    text-transform: none;
    letter-spacing: -0.005em;
    color: hsl(var(--v2-muted));
    margin-bottom: var(--v2-space-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.v2-bins-kpi__value {
    font-size: var(--v2-text-28);
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum', 'cv11';
    margin-bottom: var(--v2-space-2);
}
.v2-bins-kpi__hint {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    line-height: 1.4;
}

/* ─── Geo row (map à gauche, leaderboards à droite) ──────────────────────── */
.v2-bins-geo-row {
    display: grid;
    grid-template-columns: minmax(0, 1.7fr) minmax(280px, 1fr);
    gap: var(--v2-space-4);
    margin-bottom: var(--v2-space-5);
}
@media (max-width: 1100px) {
    .v2-bins-geo-row { grid-template-columns: 1fr; }
}

/* ─── Map card ───────────────────────────────────────────────────────────── */
.v2-bins-map-card {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.v2-bins-map-card__header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--v2-space-4);
    padding: var(--v2-space-4) var(--v2-space-5) var(--v2-space-3);
    border-bottom: 1px solid hsl(var(--v2-border-subtle));
}
.v2-bins-map-card__title {
    font-size: var(--v2-text-14);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    margin: 0 0 var(--v2-space-1);
    letter-spacing: -0.01em;
}
.v2-bins-map-card__subtitle {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    margin: 0;
}
.v2-bins-map-card__toggle {
    flex-shrink: 0;
    padding: var(--v2-space-2) var(--v2-space-3);
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    background: hsl(var(--v2-muted-bg));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    cursor: pointer;
    transition: background var(--v2lux-duration-fast) var(--v2lux-ease),
                color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bins-map-card__toggle:hover {
    background: hsl(var(--v2-primary-500) / 0.08);
    color: hsl(var(--v2-primary-700));
    border-color: hsl(var(--v2-primary-500) / 0.3);
}
.v2-bins-map-card__body {
    flex: 1;
    min-height: 420px;
    padding: var(--v2-space-3);
    display: flex;
    align-items: center;
    justify-content: center;
    /* Soft ocean tint that frames the choropleth — gives the map physical
       presence rather than floating on the page. */
    background: linear-gradient(180deg, hsl(195 45% 96%) 0%, hsl(195 35% 92%) 100%);
    position: relative;
}
.v2-bins-map-card__body::after {
    /* Inner shadow at the edges of the map area — adds depth without a
       hard border. */
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    box-shadow: inset 0 0 24px hsl(195 30% 70% / 0.18);
    border-radius: inherit;
}
.v2-bins-map svg {
    width: 100%;
    height: 100%;
    max-height: 460px;
}
/* Override jsvectormap's `.jvm-region { fill: var(--bs-gray-300); }` so the
   default (no-data) fill for V2 is our warm cream beige instead of Bootstrap's
   pale grey. Per-region colored fills are then applied inline via the
   controller (style.fill, which wins over this rule). */
.v2-bins-map .jvm-region {
    fill: hsl(38 22% 89%);
}
.v2-bins-map__marker-label {
    font-family: var(--v2lux-font-body);
    font-size: var(--v2-text-10);
    font-weight: 700;
    fill: hsl(180 80% 22%);
    stroke: hsl(0 0% 100%);
    stroke-width: 3;
    paint-order: stroke fill;
    letter-spacing: 0.02em;
    pointer-events: none;
    text-transform: uppercase;
}
.v2-world-map__svg {
    width: 100%;
    height: 100%;
    max-height: 440px;
    display: block;
    transition: none; /* viewBox interpolation handled by Stimulus */
}
.v2-world-map__countries path {
    transition: fill 200ms ease-out, opacity 120ms ease-out;
}
.v2-world-map__countries path:hover {
    opacity: 0.85;
    stroke: hsl(var(--v2-primary-700));
    stroke-width: 1;
}
.v2-bins-map-card__legend {
    display: flex;
    align-items: center;
    gap: var(--v2-space-4);
    padding: var(--v2-space-3) var(--v2-space-5);
    border-top: 1px solid hsl(var(--v2-border-subtle));
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    flex-wrap: wrap;
}
.v2-bins-map-card__legend-row {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    flex: 1;
    min-width: 240px;
}
.v2-bins-map-card__legend-gradient {
    flex: 1;
    height: 8px;
    border-radius: var(--v2-radius-xs);
    background: linear-gradient(90deg,
        hsl(180 50% 80%) 0%,
        hsl(180 65% 60%) 40%,
        hsl(180 75% 45%) 75%,
        hsl(180 85% 32%) 100%);
}
.v2-bins-map-card__legend-label {
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}
.v2-bins-map-card__legend-marker {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
.v2-bins-map-card__legend-dot {
    display: inline-block;
    border-radius: var(--v2-radius-full);
    background: hsl(180 75% 40%);
    border: 2px solid hsl(0 0% 100%);
    box-shadow: 0 0 0 1px hsl(180 50% 50% / 0.3);
}
.v2-bins-map-card__legend-dot--sm { width: 6px; height: 6px; border-width: 1.5px; }
.v2-bins-map-card__legend-dot--md { width: 10px; height: 10px; }
.v2-bins-map-card__legend-dot--lg { width: 16px; height: 16px; }

/* ════════════════════════════════════════════════════════════════════════════
   Markets Bar+Map — module "Distribution géographique" V3 (bars-hero).
   Refonte 2026-05-18 (it. 3) : la barre devient le héros, la carte devient
   le contexte. Length = sqrt(volume capté), color = tier acceptance. Un seul
   gradient bar continu remplace l'ancienne tier-legend "4-col-symmetric".
   Spotlight pane reste pour le détail + drill-down CTA. Map strip = optional
   collapsible <details>, pas affiché par défaut.
   ════════════════════════════════════════════════════════════════════════════ */
.v2-bm-module {
    --bm-paper:        hsl(38 32% 96%);
    --bm-paper-warm:   hsl(38 28% 92%);
    --bm-paper-deep:   hsl(38 24% 86%);
    --bm-ink:          hsl(24 22% 16%);
    --bm-ink-mute:     hsl(24 14% 38%);
    --bm-ink-soft:     hsl(24 10% 55%);
    --bm-rule:         hsl(38 20% 82%);
    --bm-accent:       hsl(180 70% 38%);

    /* Ajouts de l'audit 2026-07-25 — le module tokenisait les remplissages de
       palier mais pas les ENCRES, et declarait l'accent sans son lavis.
       ⛔ --bm-accent-wash n'est PAS --bm-accent : rgb(51,204,204) contre
       rgb(29,165,165). Valeurs exactes, rendu identique. */
    --bm-accent-wash:      180 60% 50%;
    --bm-track:            hsl(38 22% 92%);
    --bm-tier-watch-ink:   hsl(24 65% 38%);
    --bm-tier-concern-ink: hsl(12 65% 32%);

    /* Rampe santé rouge→ambre→vert (DESIGN.md « One Health Scale Rule ») —
       alignée sur les tokens sémantiques de la marque (success/warning/danger).
       Le cyan est réservé à la valeur/volume, jamais à « bonne acceptance ». */
    --bm-tier-excellent: hsl(142 71% 38%);
    --bm-tier-healthy:   hsl(142 64% 47%);
    --bm-tier-watch:     hsl(38 92% 50%);
    --bm-tier-concern:   hsl(0 78% 55%);
    --bm-tier-unknown:   hsl(38 14% 65%);

    background: var(--bm-paper);
    border: 1px solid var(--bm-paper-deep);
    border-radius: var(--v2-radius-lg);
    overflow: hidden;
    color: var(--bm-ink);
}

.v2-bm-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: var(--v2-space-5);
    padding: var(--v2-space-5) var(--v2-space-6) var(--v2-space-3);
    border-bottom: 1px solid var(--bm-rule);
}
.v2-bm-header__titles { min-width: 0; }
.v2-bm-header__right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--v2-space-2);
    flex-shrink: 0;
}
.v2-bm-back {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    background: var(--bm-paper);
    border: 1px solid var(--bm-paper-deep);
    border-radius: var(--v2-radius-full);
    color: var(--bm-ink-mute);
    font-size: var(--v2-text-12);
    font-weight: 500;
    cursor: pointer;
    letter-spacing: 0.005em;
    transition: all var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bm-back:hover {
    background: hsl(var(--bm-accent-wash) / 0.08);
    border-color: hsl(var(--bm-accent-wash) / 0.4);
    color: hsl(180 70% 28%);
}
.v2-bm-back span {
    font-size: var(--v2-text-14);
    line-height: 1;
}
.v2-bm-eyebrow__pill {
    display: inline-block;
    padding: 2px 8px;
    background: hsl(var(--bm-accent-wash) / 0.12);
    border: 1px solid hsl(var(--bm-accent-wash) / 0.30);
    border-radius: var(--v2-radius-full);
    color: hsl(180 70% 30%);
    font-size: var(--v2-text-10);
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-right: 8px;
    vertical-align: middle;
}
/* En mode zoomed, le titre passe en uppercase pour rappeler qu'on est dans
   un contexte spécifique (vue dérivée, pas la vue racine). */
.v2-bm-module--zoomed .v2-bm-eyebrow {
    text-transform: none;
    letter-spacing: 0.005em;
    font-size: var(--v2-text-13);
    color: var(--bm-ink);
    font-weight: 600;
}
.v2-bm-eyebrow {
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: var(--bm-accent);
    text-transform: none;
    letter-spacing: -0.005em;
    margin: 0 0 4px;
}
.v2-bm-title {
    font-size: var(--v2-text-18);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: -0.015em;
    color: var(--bm-ink);
    margin: 0 0 4px;
}
.v2-bm-subtitle {
    font-size: var(--v2-text-12);
    color: var(--bm-ink-mute);
    margin: 0;
    font-variant-numeric: tabular-nums;
}

.v2-bm-scale {
    flex-shrink: 0;
    width: 200px;
}
.v2-bm-scale__gradient {
    display: block;
    height: 6px;
    border-radius: var(--v2-radius-full);
    background: linear-gradient(90deg,
        var(--bm-tier-concern)   0%,
        var(--bm-tier-watch)    35%,
        var(--bm-tier-healthy)  65%,
        var(--bm-tier-excellent) 100%);
}
.v2-bm-scale__ticks {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: var(--v2-text-9);
    color: var(--bm-ink-soft);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

.v2-bm-bars {
    list-style: none;
    margin: 0;
    padding: var(--v2-space-2) var(--v2-space-3);
    display: flex;
    flex-direction: column;
}
/* Repli de la liste marchés : masque les rangs 13+ tant que non déplié. Les
   rows restent dans le DOM (lus par le contrôleur world-map). Cf. la LiveAction
   toggleBinsExpanded + le bouton .v2-bm-showmore ci-dessous. */
.v2-bm-bars:not(.is-expanded) .v2-bm-row:nth-child(n+13) {
    display: none;
}
.v2-bm-showmore {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    margin: var(--v2-space-3) 0 var(--v2-space-1);
    padding: var(--v2-space-2) var(--v2-space-4);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    font-size: var(--v2-text-13);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    cursor: pointer;
    transition: background 150ms var(--v2-ease), border-color 150ms var(--v2-ease);
}
.v2-bm-showmore:hover {
    background: hsl(var(--v2-muted-bg));
    border-color: hsl(var(--v2-border-strong));
}
.v2-bm-showmore:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
}
.v2-bm-row {
    /* Le row contient le button (la bar) + le panneau accordion details.
       Cliquer sur le button toggle le `hidden` du panneau — pas de layout
       shift sur les rows AU-DESSUS, seules celles d'EN-DESSOUS bougent
       (comportement attendu d'un accordion). */
    border-left: 2px solid transparent;
    transition: background var(--v2lux-duration-fast) var(--v2lux-ease),
                border-left-color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bm-row + .v2-bm-row {
    margin-top: 1px;
    box-shadow: 0 -1px 0 hsl(38 20% 86%);
}
.v2-bm-row.is-highlight,
.v2-bm-row.is-open {
    background: hsl(38 32% 98%);
    border-left-color: var(--bm-accent);
}
.v2-bm-bar {
    display: grid;
    grid-template-columns: 24px 34px minmax(110px, 1.3fr) minmax(120px, 4fr) minmax(72px, 0.85fr) minmax(80px, 0.9fr) 52px 16px;
    align-items: center;
    column-gap: var(--v2-space-3);
    padding: 9px 12px;
    width: 100%;
    background: transparent;
    border: none;
    cursor: pointer;
    text-align: inherit;
    font: inherit;
    color: inherit;
    border-radius: var(--v2-radius-sm);
    font-variant-numeric: tabular-nums;
    outline: none;
}
.v2-bm-bar:focus-visible {
    outline: 2px solid var(--bm-accent);
    outline-offset: -2px;
}
.v2-bm-bar__chev {
    color: var(--bm-ink-soft);
    font-size: var(--v2-text-14);
    text-align: center;
    transition: transform var(--v2lux-duration-base) var(--v2lux-ease);
    line-height: 1;
}
.v2-bm-row.is-open .v2-bm-bar__chev {
    transform: rotate(180deg);
    color: var(--bm-accent);
}
.v2-bm-bar__rank {
    font-size: var(--v2-text-11);
    font-weight: 700;
    color: var(--bm-ink-soft);
    text-align: right;
    letter-spacing: 0.04em;
}
.v2-bm-bar__iso {
    font-family: var(--v2lux-font-mono, var(--v2lux-font-body));
    font-size: var(--v2-text-11);
    font-weight: 600;
    color: var(--bm-accent);
    letter-spacing: 0.08em;
}
.v2-bm-bar__name {
    font-size: var(--v2-text-13);
    font-weight: 500;
    color: var(--bm-ink);
    letter-spacing: -0.005em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-bm-bar__track {
    position: relative;
    height: 9px;
    background: var(--bm-track);
    border-radius: var(--v2-radius-2xs);
    overflow: hidden;
}
.v2-bm-bar__fill {
    position: absolute;
    inset: 0 auto 0 0;
    border-radius: var(--v2-radius-2xs);
    transition: filter var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bm-tier--excellent .v2-bm-bar__fill { background: var(--bm-tier-excellent); }
.v2-bm-tier--healthy   .v2-bm-bar__fill { background: var(--bm-tier-healthy); }
.v2-bm-tier--watch     .v2-bm-bar__fill { background: var(--bm-tier-watch); }
.v2-bm-tier--concern   .v2-bm-bar__fill { background: var(--bm-tier-concern); }
.v2-bm-tier--unknown   .v2-bm-bar__fill { background: var(--bm-tier-unknown); }
.v2-bm-bar.is-highlight .v2-bm-bar__fill { filter: saturate(1.15) brightness(1.05); }
.v2-bm-bar__tx {
    text-align: right;
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: var(--bm-ink-mute);
    white-space: nowrap;
}
.v2-bm-bar__amount {
    text-align: right;
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: var(--bm-ink);
    letter-spacing: -0.005em;
}
.v2-bm-bar__pct {
    text-align: right;
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: var(--bm-ink-mute);
}
.v2-bm-tier--excellent .v2-bm-bar__pct,
.v2-bm-tier--healthy   .v2-bm-bar__pct { color: hsl(142 55% 30%); }
.v2-bm-tier--watch     .v2-bm-bar__pct { color: var(--bm-tier-watch-ink); }
.v2-bm-tier--concern   .v2-bm-bar__pct { color: var(--bm-tier-concern-ink); font-weight: 600; }

/* Inline accordion under each row — affiche les détails du pays sans
   créer de spotlight pane qui jumpe la layout. Les rows au-dessus restent
   strictement en place. */
.v2-bm-row__details {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--v2-space-5);
    align-items: center;
    padding: var(--v2-space-3) 12px var(--v2-space-4);
    background: hsl(38 30% 95%);
    border-top: 1px solid var(--bm-rule);
    animation: v2-bm-details-in 200ms var(--v2lux-ease);
}
@keyframes v2-bm-details-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.v2-bm-details__left {
    display: grid;
    gap: var(--v2-space-2);
    min-width: 0;
}
.v2-bm-details__accbar {
    position: relative;
    height: 7px;
    background: var(--bm-track);
    border-radius: var(--v2-radius-full);
    overflow: hidden;
}
.v2-bm-details__accfill { position: absolute; inset: 0 auto 0 0; border-radius: var(--v2-radius-full); }
.v2-bm-tier--excellent .v2-bm-details__accfill { background: var(--bm-tier-excellent); }
.v2-bm-tier--healthy   .v2-bm-details__accfill { background: var(--bm-tier-healthy); }
.v2-bm-tier--watch     .v2-bm-details__accfill { background: var(--bm-tier-watch); }
.v2-bm-tier--concern   .v2-bm-details__accfill { background: var(--bm-tier-concern); }
.v2-bm-details__accmeta {
    display: flex;
    align-items: baseline;
    gap: var(--v2-space-3);
    margin: 0;
    font-variant-numeric: tabular-nums;
}
.v2-bm-details__accmeta strong { font-size: var(--v2-text-14); font-weight: 600; color: var(--bm-ink); }
.v2-bm-details__accstatus {
    font-size: var(--v2-text-10);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 2px 8px;
    border-radius: var(--v2-radius-full);
    background: var(--bm-paper);
    border: 1px solid var(--bm-paper-deep);
}
.v2-bm-tier--excellent .v2-bm-details__accstatus { color: hsl(142 65% 28%); }
.v2-bm-tier--healthy   .v2-bm-details__accstatus { color: hsl(142 52% 36%); }
.v2-bm-tier--watch     .v2-bm-details__accstatus { color: var(--bm-tier-watch-ink); }
.v2-bm-tier--concern   .v2-bm-details__accstatus { color: var(--bm-tier-concern-ink); }
.v2-bm-details__stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--v2-space-4);
    list-style: none;
    margin: 0;
    padding: 6px 0 0;
    border-top: 1px solid var(--bm-rule);
}
.v2-bm-details__stats li { display: flex; flex-direction: column; gap: 1px; min-width: 80px; }
.v2-bm-details__stats span {
    font-size: var(--v2-text-10);
    color: var(--bm-ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}
.v2-bm-details__stats strong {
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: var(--bm-ink);
    font-variant-numeric: tabular-nums;
}
.v2-bm-details__right { flex-shrink: 0; }
.v2-bm-details__cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--bm-accent);
    border: none;
    border-radius: var(--v2-radius-md);
    color: hsl(38 32% 98%);
    font-size: var(--v2-text-13);
    font-weight: 600;
    cursor: pointer;
    transition: background var(--v2lux-duration-fast) var(--v2lux-ease);
    white-space: nowrap;
}
.v2-bm-details__cta:hover { background: hsl(180 75% 32%); }
.v2-bm-details__cta span { font-size: var(--v2-text-16); line-height: 1; }

/* Carte choroplèthe — toujours visible, en dessous des bars. Pas de
   collapsible : la carte est un outil de lecture, pas une découverte. */
.v2-bm-strip-wrapper {
    position: relative;
    padding: var(--v2-space-3) var(--v2-space-6) var(--v2-space-4);
    background: var(--bm-paper-warm);
    border-top: 1px solid var(--bm-rule);
}
.v2-bm-strip-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--v2-space-3);
    margin-bottom: var(--v2-space-2);
}
.v2-bm-strip-label {
    font-size: var(--v2-text-10);
    font-weight: 600;
    color: var(--bm-ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin: 0;
}
.v2-bm-strip-toggle {
    flex-shrink: 0;
    border: 1px solid var(--bm-rule);
    background: transparent;
    border-radius: var(--v2-radius-full);
    font-size: var(--v2-text-11);
    font-weight: 600;
    padding: 3px 12px;
    color: var(--bm-ink-soft);
    cursor: pointer;
    transition: color var(--v2lux-duration-fast) var(--v2lux-ease),
                border-color var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bm-strip-toggle:hover,
.v2-bm-strip-toggle:focus-visible {
    color: var(--bm-ink);
    border-color: var(--bm-ink-soft);
}
.v2-bm-strip {
    width: 100%;
}
.v2-bm-strip-svg {
    width: 100%;
    max-height: 320px;
    display: block;
}
/* Choroplèthe : pays sans activité = papier discret ; pays actifs = teinte du
   palier d'acceptation (mêmes seuils que les barres — une seule grammaire).
   Teintes volontairement claires pour que les points de volume et les
   frontières restent lisibles par-dessus. */
.v2-bm-strip-country { fill: hsl(38 20% 89%); stroke: hsl(38 24% 96%); stroke-width: 0.45; }
.v2-bm-strip-country--has-data { cursor: pointer; stroke: hsl(38 32% 99%); stroke-width: 0.6; }
.v2-bm-strip-country--excellent { fill: color-mix(in srgb, var(--bm-tier-excellent) 52%, white); }
.v2-bm-strip-country--healthy   { fill: color-mix(in srgb, var(--bm-tier-healthy) 45%, white); }
.v2-bm-strip-country--watch     { fill: color-mix(in srgb, var(--bm-tier-watch) 55%, white); }
.v2-bm-strip-country--concern   { fill: color-mix(in srgb, var(--bm-tier-concern) 60%, white); }
.v2-bm-strip-country--unknown   { fill: color-mix(in srgb, var(--bm-tier-unknown) 45%, white); }
.v2-bm-strip-country--has-data:hover {
    filter: saturate(1.35) brightness(0.96);
}
/* Le point encode le volume encaissé — encre neutre, la couleur appartient
   au remplissage du pays. pointer-events:none : le pays dessous porte le
   hover et le clic (cible bien plus large qu'un point de 3 px). */
.v2-bm-strip-dot {
    fill: hsl(222 30% 24% / 0.82);
    stroke: hsl(38 32% 98%);
    stroke-width: 1.2;
    pointer-events: none;
    transition: stroke-width var(--v2lux-duration-fast) var(--v2lux-ease),
                transform var(--v2lux-duration-fast) var(--v2lux-ease);
    transform-origin: center;
    transform-box: fill-box;
}
.v2-bm-strip-dot.is-highlight {
    stroke-width: 2.2;
    transform: scale(1.3);
}
.v2-bm-strip-dot.is-dim { fill-opacity: 0.45; }
/* Tooltip carte — positionné par le contrôleur (transform), jamais cliquable,
   clampé aux bords du wrapper. */
.v2-bm-tip {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 30;
    pointer-events: none;
    min-width: 185px;
    max-width: 260px;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-subtle));
    border-radius: var(--v2-radius-md);
    box-shadow: 0 10px 28px hsl(222 30% 12% / 0.16);
    padding: var(--v2-space-2) var(--v2-space-3);
    font-size: var(--v2-text-12);
    line-height: 1.45;
}
.v2-bm-tip__name {
    font-weight: 700;
    font-size: var(--v2-text-13);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--v2-space-2);
    margin-bottom: 3px;
}
.v2-bm-tip__chip {
    font-size: var(--v2-text-10);
    font-weight: 600;
    white-space: nowrap;
    border-radius: var(--v2-radius-full);
    padding: 1px 8px;
    color: hsl(38 32% 99%);
}
.v2-bm-tip__chip--excellent { background: var(--bm-tier-excellent); }
.v2-bm-tip__chip--healthy   { background: var(--bm-tier-healthy); }
.v2-bm-tip__chip--watch     { background: var(--bm-tier-watch); color: hsl(24 60% 16%); }
.v2-bm-tip__chip--concern   { background: var(--bm-tier-concern); }
.v2-bm-tip__chip--unknown   { background: var(--bm-tier-unknown); color: hsl(24 30% 18%); }
.v2-bm-tip__row {
    display: flex;
    justify-content: space-between;
    gap: var(--v2-space-4);
}
.v2-bm-tip__hint {
    margin-top: 4px;
    font-size: var(--v2-text-11);
    color: var(--bm-ink-soft);
}
@media (max-width: 640px) {
    .v2-bm-strip-svg { max-height: 210px; }
    .v2-bm-tip { display: none; } /* tactile : le tap ouvre la fiche, pas de hover */
}
@media (prefers-reduced-motion: reduce) {
    .v2-bm-strip-dot,
    .v2-bm-strip-toggle,
    .v2-bm-strip-country--has-data { transition: none; }
    .v2-bm-strip-dot--focus { animation: none; }
}
/* Mode focus (zoom sur un seul pays) : le dot est plus grand, halo plus
   visible, pour faire l'effet "tu es ici". */
.v2-bm-strip-dot--focus {
    fill: hsl(180 75% 38%);
    stroke-width: 3;
    filter: drop-shadow(0 0 4px hsl(180 70% 50% / 0.55));
    animation: v2-bm-focus-pulse 2.5s ease-in-out infinite;
    transform-origin: center;
    transform-box: fill-box;
}
@keyframes v2-bm-focus-pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.12); }
}
.v2-bm-empty {
    padding: var(--v2-space-6);
    text-align: center;
    color: var(--bm-ink-mute);
    font-size: var(--v2-text-14);
    margin: 0;
}

/* ─── Leaderboards (top banques + top acceptance) ────────────────────────── */
.v2-bins-leaderboards {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-4);
}
.v2-bins-board {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    padding: var(--v2-space-4) var(--v2-space-5);
}
.v2-bins-board__title {
    font-size: var(--v2-text-14);
    font-weight: 600;
    text-transform: none;
    letter-spacing: -0.005em;
    color: hsl(var(--v2-foreground));
    margin: 0 0 var(--v2-space-4);
}
.v2-bins-board__list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
}
.v2-bins-board__item {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-1);
}
.v2-bins-board__row {
    display: grid;
    grid-template-columns: 1rem 1fr auto;
    gap: var(--v2-space-3);
    align-items: baseline;
}
.v2-bins-board__rank {
    font-size: var(--v2-text-11);
    font-weight: 600;
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.v2-bins-board__name {
    font-size: var(--v2-text-14);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-bins-board__value {
    font-size: var(--v2-text-14);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
}
.v2-bins-board__value--strong {
    color: hsl(160 50% 35%);
}
.v2-bins-board__bar {
    height: 4px;
    border-radius: var(--v2-radius-2xs);
    background: hsl(var(--v2-muted-bg));
    overflow: hidden;
    margin-left: calc(1rem + var(--v2-space-3));
}
.v2-bins-board__bar-fill {
    height: 100%;
    background: linear-gradient(90deg, hsl(var(--v2-databar-teal-lo)), hsl(var(--v2-databar-teal)));
    border-radius: var(--v2-radius-2xs);
    transition: width 480ms cubic-bezier(0.22, 1, 0.36, 1);
}
.v2-bins-board__bar-fill--success {
    background: linear-gradient(90deg, hsl(160 50% 70%), hsl(160 55% 40%));
}
.v2-bins-board__meta {
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    margin-left: calc(1rem + var(--v2-space-3));
    font-variant-numeric: tabular-nums;
}
.v2-bins-board__empty {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    font-style: italic;
    text-align: center;
    padding: var(--v2-space-5) 0;
}

/* ─── Onglet Acquisition — duo « top sources × motifs de refus » ─────────
   Même langage visuel que les leaderboards Banques & marchés (rang, barre,
   méta), classes dédiées pour ne pas coupler les deux onglets. */
.v2-acq-duo {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--v2-space-4);
}
@media (max-width: 1024px) {
    .v2-acq-duo { grid-template-columns: 1fr; }
}
.v2-acq-board {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
}
.v2-acq-board__item {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-1);
}
.v2-acq-board__row {
    display: grid;
    grid-template-columns: 1rem 1fr auto;
    gap: var(--v2-space-3);
    align-items: baseline;
}
.v2-acq-board__rank {
    font-size: var(--v2-text-11);
    font-weight: 600;
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.v2-acq-board__name {
    font-size: var(--v2-text-14);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-acq-board__name em {
    font-style: normal;
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    margin-left: var(--v2-space-2);
}
.v2-acq-board__value {
    font-size: var(--v2-text-14);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.v2-acq-board__bar {
    height: 4px;
    border-radius: var(--v2-radius-2xs);
    background: hsl(var(--v2-muted-bg));
    overflow: hidden;
    margin-left: calc(1rem + var(--v2-space-3));
}
.v2-acq-board__bar-fill {
    height: 100%;
    background: linear-gradient(90deg, hsl(var(--v2-databar-teal-lo)), hsl(var(--v2-databar-teal)));
    border-radius: var(--v2-radius-2xs);
    transition: width 480ms cubic-bezier(0.22, 1, 0.36, 1);
}
.v2-acq-board__bar-fill--danger {
    background: linear-gradient(90deg, hsl(0 65% 78%), hsl(0 70% 55%));
}
.v2-acq-board__meta {
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    margin-left: calc(1rem + var(--v2-space-3));
    font-variant-numeric: tabular-nums;
}
.v2-acq-board__empty {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
    font-style: italic;
    text-align: center;
    padding: var(--v2-space-5) 0;
}
.v2-acq-board__cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--v2-space-3);
    background: transparent;
    border: 0;
    border-bottom: 1px solid hsl(var(--v2-primary-500) / 0.45);
    padding: 2px 0;
    font-size: var(--v2-text-13);
    font-weight: 500;
    color: hsl(var(--v2-primary-600));
    cursor: pointer;
}
.v2-acq-board__cta:hover,
.v2-acq-board__cta:focus-visible {
    border-bottom-color: hsl(var(--v2-primary-600));
}
.v2-acq-partial {
    font-size: var(--v2-text-11);
    font-weight: 500;
    color: hsl(var(--v2-warning-600));
}
@media (prefers-reduced-motion: reduce) {
    .v2-acq-board__bar-fill { transition: none; }
}

/* ─── Details collapsible (drill-down tables) ────────────────────────────── */
.v2-bins-details {
    margin-top: var(--v2-space-5);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    overflow: hidden;
    transition: border-color var(--v2lux-duration-base) var(--v2lux-ease),
                box-shadow var(--v2lux-duration-base) var(--v2lux-ease);
}
.v2-bins-details:not(.is-open) {
    border-color: hsl(var(--v2-bins-drill-soft) / 0.45);
    box-shadow: 0 1px 0 hsl(var(--v2-bins-drill-soft) / 0.08),
                0 4px 16px hsl(var(--v2-bins-drill-soft) / 0.06);
}
.v2-bins-details:not(.is-open):hover {
    border-color: hsl(var(--v2-bins-drill) / 0.7);
    box-shadow: 0 2px 0 hsl(var(--v2-bins-drill) / 0.12),
                0 8px 24px hsl(var(--v2-bins-drill) / 0.1);
}
.v2-bins-details.is-open {
    box-shadow: 0 2px 12px hsl(var(--v2-shadow-ink) / 0.04);
}
.v2-bins-details__summary {
    appearance: none;
    width: 100%;
    border: none;
    background: transparent;
    text-align: left;
    font-family: inherit;
    list-style: none;
    cursor: pointer;
    padding: var(--v2-space-4) var(--v2-space-5);
    font-size: var(--v2-text-14);
    font-weight: 500;
    color: hsl(var(--v2-foreground));
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: var(--v2-space-4);
    user-select: none;
    transition: background var(--v2lux-duration-fast) var(--v2lux-ease);
}
.v2-bins-details__summary::-webkit-details-marker { display: none; }
.v2-bins-details__summary:hover {
    background: hsl(var(--v2-bins-drill) / 0.04);
}
.v2-bins-details__summary:focus-visible {
    outline: 2px solid hsl(var(--v2-bins-drill));
    outline-offset: -2px;
}
.v2-bins-details__summary-left {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-3);
    min-width: 0;
}
.v2-bins-details__summary-icon {
    width: 18px;
    height: 18px;
    color: hsl(180 50% 40%);
    flex-shrink: 0;
}
.v2-bins-details__summary-text {
    display: inline-flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}
.v2-bins-details__summary-text strong {
    font-weight: 600;
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-foreground));
    letter-spacing: -0.005em;
}
.v2-bins-details__summary-text em {
    font-style: normal;
    font-size: var(--v2-text-12);
    font-weight: 400;
    color: hsl(var(--v2-muted));
    letter-spacing: 0;
}
.v2-bins-details__summary-cta {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    padding: 6px 12px;
    background: hsl(var(--v2-bins-drill) / 0.08);
    border: 1px solid hsl(var(--v2-bins-drill) / 0.25);
    border-radius: var(--v2-radius-full);
    font-size: var(--v2-text-12);
    font-weight: 500;
    color: hsl(180 50% 30%);
    white-space: nowrap;
    transition: all var(--v2lux-duration-fast) var(--v2lux-ease);
    flex-shrink: 0;
}
.v2-bins-details__summary:hover .v2-bins-details__summary-cta {
    background: hsl(var(--v2-bins-drill) / 0.15);
    border-color: hsl(var(--v2-bins-drill) / 0.5);
    color: hsl(180 50% 25%);
}
.v2-bins-details__chevron {
    font-size: var(--v2-text-14);
    color: inherit;
    transition: transform var(--v2lux-duration-base) var(--v2lux-ease);
    line-height: 1;
}
.v2-bins-details.is-open .v2-bins-details__chevron {
    transform: rotate(180deg);
}
.v2-bins-details > *:not(.v2-bins-details__summary) {
    margin: 0 var(--v2-space-5) var(--v2-space-5);
}
.v2-bins-details > twig\:Shared\:ChartCard,
.v2-bins-details > .v2-chart-card {
    margin: var(--v2-space-3) var(--v2-space-5) var(--v2-space-5);
}
.v2-bins-map__fallback {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    min-height: 380px;
    padding: var(--v2-space-6);
    text-align: center;
    color: hsl(var(--v2-muted));
    font-size: var(--v2-text-14);
    line-height: 1.5;
    gap: var(--v2-space-2);
}
.v2-bins-map__fallback p {
    margin: 0;
}
.v2-bins-map__fallback p:first-child {
    font-weight: 600;
    color: hsl(var(--v2-foreground));
}

/* ─── Bins map — jsvectormap container + tooltip ──────────────────────────── */
.v2-bins-map {
    width: 100%;
    height: 100%;
    min-height: 380px;
}
.v2-bins-map .jvm-container {
    background: transparent !important;
}
.v2-bins-tooltip {
    font-family: var(--v2lux-font-body);
    padding: var(--v2-space-3) var(--v2-space-4);
    min-width: 160px;
}
.v2-bins-tooltip__country {
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: hsl(38 30% 96%);
    letter-spacing: -0.01em;
    margin-bottom: var(--v2-space-1);
}
.v2-bins-tooltip__amount {
    font-size: var(--v2-text-14);
    font-weight: 600;
    color: hsl(180 65% 65%);
    font-variant-numeric: tabular-nums;
    margin-bottom: 2px;
}
.v2-bins-tooltip__meta {
    font-size: var(--v2-text-11);
    color: hsl(38 18% 78%);
    font-variant-numeric: tabular-nums;
}
/* Override jsvectormap default tooltip style to match Luxury Shell. */
.jvm-tooltip {
    background: hsl(var(--v2-shadow-ink) / 0.96) !important;
    border: 1px solid hsl(var(--v2-databar-teal) / 0.4) !important;
    border-radius: var(--v2-radius-md) !important;
    padding: 0 !important;
    box-shadow: 0 8px 24px hsl(var(--v2-shadow-ink) / 0.25) !important;
    font-family: var(--v2lux-font-body) !important;
    color: hsl(38 30% 96%) !important;
    pointer-events: none;
    z-index: 1000;
}

/* ─────────────────────────────────────────────────────────────────────────
   Overview toolbar — Partner + Site filters (added 2026-05-27).
   NOTE: this file is the served CSS but drifts from assets/styles/dashboard-v2.css.
   Long-term: reconcile the two sources (asset-map compile or symlink).
   ───────────────────────────────────────────────────────────────────────── */
.v2-overview-toolbar__filters {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-overview-toolbar__filters .form-select {
    width: auto;
    min-width: 130px;
    max-width: 180px;
    font-size: var(--v2-text-14);
    flex: 0 0 auto;
}
@media (max-width: 768px) {
    .v2-overview-toolbar__filters {
        flex-wrap: wrap;
        gap: var(--v2-space-1);
    }
    .v2-overview-toolbar__filters .form-select { min-width: 110px; }
}

/* ─────────────────────────────────────────────────────────────────────────
   Bootstrap-Select fix (2026-05-27) — the V1 profile page (and any other
   page using `.selectpicker`) renders the dropdown trigger as a button with
   `color: #fff` for some reason, on a `#fff` background → unreadable.
   Force a dark color on the visible label so the timezone is selectable.
   ───────────────────────────────────────────────────────────────────────── */
.bootstrap-select > button.dropdown-toggle,
.bootstrap-select > button.dropdown-toggle .filter-option,
.bootstrap-select > button.dropdown-toggle .filter-option-inner,
.bootstrap-select > button.dropdown-toggle .filter-option-inner-inner {
    color: hsl(var(--v2-foreground)) !important;
    background-color: #fff !important;
    border-color: hsl(var(--v2-border, 220 13% 91%)) !important;
}
.bootstrap-select > button.dropdown-toggle:hover {
    background-color: hsl(var(--v2-bg-elevated, 0 0% 96%)) !important;
}
.bootstrap-select .dropdown-menu {
    font-size: var(--v2-text-13);
    max-height: 320px;
    overflow-y: auto;
}
.bootstrap-select .dropdown-menu li a {
    color: hsl(var(--v2-foreground));
}
.bootstrap-select .dropdown-menu .bs-searchbox input {
    color: hsl(var(--v2-foreground));
    background-color: #fff;
    border: 1px solid hsl(var(--v2-border, 220 13% 91%));
    border-radius: var(--v2-radius-sm);
    padding: 4px 8px;
    font-size: var(--v2-text-13);
}

/* Bootstrap-Select with btn-outline-secondary specifically — higher specificity */
.v2-main .bootstrap-select .btn-outline-secondary,
.bootstrap-select .btn-outline-secondary.dropdown-toggle,
.bootstrap-select button.btn-outline-secondary.dropdown-toggle .filter-option-inner-inner {
    color: hsl(var(--v2-foreground)) !important;
    background-color: #fff !important;
    border-color: hsl(220 13% 91%) !important;
}
.bootstrap-select .btn-outline-secondary.dropdown-toggle:hover,
.bootstrap-select .btn-outline-secondary.dropdown-toggle:focus,
.bootstrap-select .btn-outline-secondary.dropdown-toggle:active,
.bootstrap-select.show .btn-outline-secondary.dropdown-toggle {
    color: hsl(var(--v2-foreground)) !important;
    background-color: #f5f5f4 !important;
}

/* === Cockpit Live ======================================================= */
.v2-live__toolbar { margin-bottom: var(--v2-space-4); }

.v2-live-status {
    padding: var(--v2-space-3) var(--v2-space-4);
    border-radius: var(--v2-radius-lg);
    border: 1px solid hsl(var(--v2-border-default));
    background: hsl(var(--v2-card));
    box-shadow: var(--v2-shadow-xs);
    margin-bottom: var(--v2-space-4);
    font-size: var(--v2-text-14);
    position: sticky;
    top: var(--v2-space-2);
    z-index: 20;
}
.v2-live-status__bar {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
}
.v2-live-status__dot {
    width: 10px; height: 10px; border-radius: 50%;
    background: hsl(var(--v2-muted));
    flex-shrink: 0;
}
.v2-live-status--success .v2-live-status__dot { background: hsl(var(--v2-success-500)); }
.v2-live-status--warning .v2-live-status__dot { background: hsl(var(--v2-warning-500)); }
.v2-live-status--danger  .v2-live-status__dot { background: hsl(var(--v2-danger-500)); }
.v2-live-status__spacer { flex: 1 1 auto; }
.v2-live-status__sep { color: hsl(var(--v2-border-strong)); }
.v2-live-status__clock { font-variant-numeric: tabular-nums; font-weight: 600; }
.v2-live-status__freshness { color: hsl(var(--v2-muted)); }

.v2-live-ticker { list-style: none; margin: 0; padding: 0; }
.v2-live-ticker__row {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    gap: var(--v2-space-3);
    align-items: center;
    padding: var(--v2-space-2) 0;
    border-bottom: 1px solid hsl(var(--v2-border-subtle));
    font-size: var(--v2-text-13);
}
.v2-live-ticker__time { color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; }
.v2-live-ticker__amount { font-weight: 600; font-variant-numeric: tabular-nums; }

.v2-live-watch__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--v2-space-2); }
.v2-live-watch__item { display: flex; align-items: center; gap: var(--v2-space-2); }
.v2-live-watch__reason { color: hsl(var(--v2-muted)); font-size: var(--v2-text-13); }
.v2-live-watch__icon { color: hsl(var(--v2-warning-500)); vertical-align: -2px; margin-left: 4px; }

@media (max-width: 768px) {
    .v2-live-status { flex-wrap: wrap; }
    .v2-live-ticker__row { grid-template-columns: auto 1fr auto; }
    .v2-live-ticker__state { grid-column: 3; }
    .v2-live-replay { flex-wrap: wrap; }
    .v2-live-replay__exit { margin-left: 0; }
}

/* Cockpit Live — bandeau « replay » (visualisation d'un jour passé) */
.v2-live-replay {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    padding: var(--v2-space-2) var(--v2-space-4);
    margin-bottom: var(--v2-space-4);
    border-radius: var(--v2-radius-lg);
    border: 1px solid hsl(var(--v2-warning-500) / 0.35);
    background: hsl(var(--v2-warning-500) / 0.08);
    color: hsl(36 55% 26%);
    font-size: var(--v2-text-13);
}
.v2-live-replay svg { color: hsl(var(--v2-warning-600)); flex-shrink: 0; }
.v2-live-replay strong { font-weight: 650; }
.v2-live-replay__exit {
    margin-left: auto;
    flex-shrink: 0;
    font-weight: 600;
    color: hsl(var(--v2-primary-600));
    text-decoration: none;
    white-space: nowrap;
}
.v2-live-replay__exit:hover { text-decoration: underline; }

/* === Cockpit Live — mur de graphiques (Grafana-style, thème clair) ======== */
.v2-live-charts { margin-bottom: var(--v2-space-4); }
.v2-live-charts__row { margin-bottom: var(--v2-space-4); }
.v2-live-charts__row:last-child { margin-bottom: 0; }

.v2-live-charts__row--hero {
    display: grid;
    grid-template-columns: minmax(0, 2.4fr) minmax(0, 1fr);
    gap: var(--v2-space-4);
}
@media (max-width: 1100px) {
    .v2-live-charts__row--hero { grid-template-columns: 1fr; }
}

.v2-live-gauge__body {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--v2-space-3);
    min-height: 240px;
}
.v2-live-gauge__ring { flex-direction: column; gap: var(--v2-space-2); }
.v2-live-gauge__ring .v2-perf-score__ring { width: 132px; height: 132px; }
.v2-live-gauge__ring .v2-perf-score__ring::before { inset: 8px; }
.v2-live-gauge__ring .v2-perf-score__value { font-size: var(--v2-text-32); }
.v2-live-gauge__ring .v2-perf-score__suffix { font-size: var(--v2-text-10); }
.v2-live-gauge__headline {
    margin: 0;
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    text-align: center;
}

.v2-live-paymix {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--v2-space-3);
}
@media (max-width: 540px) {
    .v2-live-paymix { grid-template-columns: 1fr; }
}

/* === Cockpit Live — bandeau stats compact (1 ligne) ====================== */
.v2-live-stats {
    display: grid;
    grid-template-columns: 1.6fr 1fr 1fr 1fr;
    gap: 1px;
    background: hsl(var(--v2-border-default));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    overflow: hidden;
    box-shadow: var(--v2-shadow-xs);
    margin-bottom: var(--v2-space-3);
}
.v2-live-stats__cell {
    background: hsl(var(--v2-card));
    padding: var(--v2-space-3) var(--v2-space-4);
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}
.v2-live-stats__cell--primary {
    background: linear-gradient(180deg, hsl(var(--v2-card)), hsl(var(--v2-muted-bg) / 0.45));
}
.v2-live-stats__label {
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(var(--v2-muted));
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.v2-live-stats__value {
    font-size: var(--v2-text-24);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
}
.v2-live-stats__cell--primary .v2-live-stats__value { font-size: var(--v2-text-32); }
.v2-live-stats__sub {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
@media (max-width: 900px) {
    .v2-live-stats { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 520px) {
    .v2-live-stats { grid-template-columns: 1fr; }
}

/* Ticker « en direct » horizontal (1 ligne, fondu à droite) */
.v2-live-tickerbar {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    padding: var(--v2-space-2) var(--v2-space-3);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-md);
    box-shadow: var(--v2-shadow-xs);
    margin-bottom: var(--v2-space-4);
    font-size: var(--v2-text-12);
    overflow: hidden;
}
.v2-live-tickerbar__label {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: 600;
    color: hsl(var(--v2-primary-600));
    text-transform: uppercase;
    font-size: var(--v2-text-11);
    letter-spacing: 0.05em;
}
.v2-live-tickerbar__track {
    display: flex;
    gap: var(--v2-space-5);
    overflow: hidden;
    flex: 1 1 auto;
    min-width: 0;
    -webkit-mask-image: linear-gradient(90deg, #000 90%, transparent);
    mask-image: linear-gradient(90deg, #000 90%, transparent);
}
.v2-live-tickerbar__item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    flex-shrink: 0;
}
.v2-live-tickerbar__time { color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; }
.v2-live-tickerbar__amount { font-weight: 600; font-variant-numeric: tabular-nums; }
.v2-live-tickerbar__dot--ok { color: hsl(var(--v2-success-600)); }
.v2-live-tickerbar__dot--ko { color: hsl(var(--v2-danger-500)); }

/* === Cockpit Live — stat strip 5 colonnes (tuile Santé) + feed « en direct » == */
.v2-live-stats--5 { grid-template-columns: 1.6fr 1fr 1fr 1fr 1fr; }
@media (max-width: 1100px) { .v2-live-stats--5 { grid-template-columns: 1fr 1fr 1fr; } }
@media (max-width: 700px)  {
    .v2-live-stats--5 { grid-template-columns: 1fr 1fr; }
    .v2-live-stats--5 .v2-live-stats__cell--primary { grid-column: 1 / -1; }
}

.v2-live-stats__unit { font-size: var(--v2-text-13); font-weight: 600; color: hsl(var(--v2-muted)); margin-left: 2px; }
.v2-live-stats__cell--health.v2-live-stats__cell--success .v2-live-stats__value { color: hsl(var(--v2-success-600)); }
.v2-live-stats__cell--health.v2-live-stats__cell--warning .v2-live-stats__value { color: hsl(var(--v2-warning-600)); }
.v2-live-stats__cell--health.v2-live-stats__cell--danger  .v2-live-stats__value { color: hsl(var(--v2-danger-600)); }
.v2-live-stats__cell--health.v2-live-stats__cell--muted   .v2-live-stats__value { color: hsl(var(--v2-muted)); }

/* Panneau « En direct » : feed vertical à droite de la courbe intraday */
.v2-live-feed__list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 320px;
    /* Stream défilable des dernières transactions (mur Grafana, page laissée
       ouverte). Le poll pousse les nouvelles en tête ; on peut remonter le fil. */
    overflow-y: auto;
    scrollbar-width: thin;
    -webkit-mask-image: linear-gradient(180deg, #000 92%, transparent);
    mask-image: linear-gradient(180deg, #000 92%, transparent);
}
.v2-live-feed__row {
    display: grid;
    grid-template-columns: auto 1fr auto auto;
    align-items: center;
    gap: var(--v2-space-2);
    padding: 6px 0;
    border-bottom: 1px solid hsl(var(--v2-border-subtle));
    font-size: var(--v2-text-13);
}
.v2-live-feed__row:last-child { border-bottom: 0; }
.v2-live-feed__time { color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; font-size: var(--v2-text-12); }
.v2-live-feed__site { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v2-live-feed__amount { font-weight: 600; font-variant-numeric: tabular-nums; }
.v2-live-feed__dot--ok { color: hsl(var(--v2-success-600)); }
.v2-live-feed__dot--ko { color: hsl(var(--v2-danger-500)); }

/* ════════════════════════════════════════════════════════════════════════
   CENTRE D'ALERTES — surfaces Réconciliation ↔ Pilotage rebill unifiées
   (cross-link pill + ajustements de cellules de table). Le Signal Net.
   ════════════════════════════════════════════════════════════════════════ */

/* Cross-link segmenté reliant les deux surfaces d'alertes ops eZengo. */
.v2-alert-nav {
    display: inline-flex;
    gap: var(--v2-space-1);
    padding: var(--v2-space-1);
    margin-bottom: var(--v2-space-5);
    background: hsl(var(--v2-muted-bg));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
}
.v2-alert-nav__item {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    padding: var(--v2-space-2) var(--v2-space-4);
    border-radius: var(--v2-radius-md);
    color: hsl(var(--v2-muted));
    text-decoration: none;
    transition: background var(--v2-duration-fast) var(--v2-ease), color var(--v2-duration-fast) var(--v2-ease);
}
.v2-alert-nav__item:hover {
    color: hsl(var(--v2-foreground));
    background: hsl(var(--v2-card) / 0.6);
}
.v2-alert-nav__item.is-active {
    background: hsl(var(--v2-card));
    color: hsl(var(--v2-primary-700));
    box-shadow: var(--v2-shadow-xs);
}
.v2-alert-nav__label { font-weight: 500; font-size: var(--v2-text-13); letter-spacing: -0.005em; }
.v2-alert-nav__item.is-active .v2-alert-nav__label { font-weight: 600; }

/* Toggle inline (checkbox) dans les filter-bars d'alertes. */
.v2-alert-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-2);
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-muted));
    cursor: pointer;
    user-select: none;
}
.v2-alert-toggle input[type="checkbox"] {
    accent-color: hsl(var(--v2-primary-600));
    width: 15px;
    height: 15px;
    cursor: pointer;
}

/* En-tête de colonne triable (bouton nu). */
.v2-th-sort {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    border-radius: var(--v2-radius-sm);
}
.v2-th-sort:hover { color: hsl(var(--v2-primary-600)); }
.v2-th-sort:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
    color: hsl(var(--v2-primary-600));
}

/* Date d'alerte : texte primaire NON cliquable (≠ v2-cell-id qui EST un lien,
   teinté cyan). Évite l'affordance trompeuse « ça ressemble à un lien ». */
.v2-cell-date {
    font-weight: 600;
    color: hsl(var(--v2-foreground));
    font-variant-numeric: tabular-nums;
}

/* Cellule description d'alerte : texte long, wrap maîtrisé. */
.v2-alert-desc {
    max-width: 360px;
    font-size: var(--v2-text-14);
    line-height: 1.45;
    color: hsl(var(--v2-muted));
    word-break: break-word;
}

/* Champ commentaire inline : discret au repos (la plupart des alertes n'ont pas
   de note), se matérialise au survol / focus. Évite la « grille de boîtes vides »
   qui vole l'attention sur les données. */
.v2-alert-comment {
    min-width: 170px;
    max-width: 220px;
    font-size: var(--v2-text-13);
    resize: vertical;
    background: transparent;
    border-color: hsl(var(--v2-border-subtle));
    transition: background var(--v2-duration-fast) var(--v2-ease), border-color var(--v2-duration-fast) var(--v2-ease);
}
.v2-alert-comment:hover {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-border-default));
}
.v2-alert-comment:focus {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-primary-500));
}

/* ════════════════════════════════════════════════════════════════════════
   BANDEAU D'ARCHÉTYPE MARCHAND — onglet Résumé Analytics rebill.
   Cadre la lecture : abonnements / clients fidèles / paiements ponctuels.
   ════════════════════════════════════════════════════════════════════════ */
.v2-archetype {
    display: flex;
    align-items: flex-start;
    gap: var(--v2-space-3);
    padding: var(--v2-space-3) var(--v2-space-4);
    background: hsl(var(--v2-muted-bg));
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
}
.v2-archetype__icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; flex-shrink: 0;
    border-radius: var(--v2-radius-md);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border));
    color: hsl(var(--v2-muted));
}
.v2-archetype__body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.v2-archetype__head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--v2-space-2); }
.v2-archetype__label { font-weight: 600; font-size: var(--v2-text-16); color: hsl(var(--v2-foreground)); }
.v2-archetype__tagline { font-size: var(--v2-text-14); color: hsl(var(--v2-muted)); }
.v2-archetype__propose {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: var(--v2-text-13); font-weight: 500;
    color: hsl(var(--v2-primary-700));
}
.v2-archetype__propose svg { flex-shrink: 0; }

/* Accent par archétype, porté par l'icône (pas de side-stripe). */
.v2-archetype--subscription .v2-archetype__icon { color: hsl(var(--v2-primary-600)); border-color: hsl(var(--v2-primary-500) / 0.4); background: hsl(var(--v2-primary-50)); }
.v2-archetype--repeat .v2-archetype__icon       { color: hsl(var(--v2-info-600));    border-color: hsl(var(--v2-info-500) / 0.4);    background: hsl(var(--v2-info-50)); }
.v2-archetype--oneshot .v2-archetype__icon      { color: hsl(var(--v2-warning-600)); border-color: hsl(var(--v2-warning-500) / 0.4); background: hsl(var(--v2-warning-50)); }
.v2-archetype__sep { color: hsl(var(--v2-muted-2)); font-weight: 400; }

/* ════════════════════════════════════════════════════════════════════════
   MICRO-INTERACTIONS (flagship) — retour tactile sur les boutons V2.
   Un léger enfoncement à l'appui confirme l'action. Conforme au registre
   produit : le mouvement porte un ÉTAT (active), pas de décoration. Appliqué
   à tout le dashboard V2 → sensation cohérente entre Alertes, Analytics et
   Écarts (pages sœurs de la réconciliation).
   ════════════════════════════════════════════════════════════════════════ */
body.v2-dashboard .btn {
    transition: color var(--v2-duration-fast) var(--v2-ease),
                background-color var(--v2-duration-fast) var(--v2-ease),
                border-color var(--v2-duration-fast) var(--v2-ease),
                box-shadow var(--v2-duration-fast) var(--v2-ease),
                transform 90ms var(--v2-ease);
}
body.v2-dashboard .btn:active:not(:disabled):not(.disabled) {
    transform: scale(0.975);
}

@media (prefers-reduced-motion: reduce) {
    body.v2-dashboard .btn { transition: color var(--v2-duration-fast) var(--v2-ease), background-color var(--v2-duration-fast) var(--v2-ease); }
    body.v2-dashboard .btn:active:not(:disabled):not(.disabled) { transform: none; }
}

/* ════════════════════════════════════════════════════════════════════════
   MOBILE (≤640px) — ergonomie téléphone de la toolbar d'overview.
   Sans viewport meta, ces règles ne se déclenchaient jamais sur iPhone (la
   page était rendue à 980px). Maintenant : slots empilés, filtres en pleine
   largeur (fini « Site (choisir partenaire… » tronqué), période à sa largeur
   naturelle (les 6 boutons courts tiennent sur 430px).
   ════════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
    .v2-overview-toolbar {
        flex-direction: column;
        align-items: stretch;
    }
    .v2-overview-toolbar__filters {
        flex-wrap: wrap;
        width: 100%;
    }
    .v2-overview-toolbar__filters .form-select {
        flex: 1 1 100%;
        min-width: 0;
        max-width: none;
    }
    .v2-overview-toolbar__period {
        flex-wrap: wrap;
    }
}

/* ════════════════════════════════════════════════════════════════════════
   MUR LIVE « Grafana » — grille DENSE de panneaux (auto-fit).
   Remplit toute la largeur : 1 col mobile → 2 tablette → 3-4 desktop large
   → 4-5 sur écran TV / ultra-wide. Plus l'écran est large, plus on empile de
   panneaux par rangée (au lieu des anciennes rangées figées de 2).
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-charts__dense {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
    gap: var(--v2-space-4);
    margin-bottom: var(--v2-space-4);
    /* Toutes les tuiles à la MÊME hauteur : 1fr égalise les rangées et les
       cartes s'étirent (au lieu de se dimensionner à leur contenu, ce que
       faisait `align-items: start` → tuiles en dents de scie). */
    grid-auto-rows: 1fr;
}
/* Carte en colonne flex pour que le corps remplisse la tuile étirée. */
.v2-live-charts__dense > .v2-chart-card {
    display: flex;
    flex-direction: column;
}

/* ════════════════════════════════════════════════════════════════════════
   MUR LIVE — zone du haut COMPACTE. Les filtres + le bandeau replay + la
   watch bar prenaient ~200px avant les graphes. On slim padding et marges
   (scopé .v2-live) pour faire remonter les graphiques au-dessus de la ligne
   de flottaison. Demande : « cette partie est trop grande ».
   ════════════════════════════════════════════════════════════════════════ */
.v2-live .v2-live__toolbar {
    margin-bottom: var(--v2-space-2);
    padding-top: var(--v2-space-1);
    padding-bottom: var(--v2-space-1);
}
.v2-live .v2-live-replay {
    margin-bottom: var(--v2-space-2);
    padding-top: var(--v2-space-1);
    padding-bottom: var(--v2-space-1);
}
.v2-live .v2-live-status {
    margin-bottom: var(--v2-space-2);
    padding-top: var(--v2-space-2);
    padding-bottom: var(--v2-space-2);
}
/* Strip KPI (Pulse) un cran plus serré sous la watch bar. */
.v2-live .v2-live-stats { margin-bottom: var(--v2-space-3); }

/* ════════════════════════════════════════════════════════════════════════
   MUR LIVE — nouveaux panneaux : heatmap acceptation horaire + table Top BIN.
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-heatmap {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
    gap: 4px;
}
.v2-live-heatmap__cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    border-radius: var(--v2-radius-sm);
    line-height: 1.1;
}
.v2-live-heatmap__hour { font-size: var(--v2-text-10); color: hsl(var(--v2-muted)); }
.v2-live-heatmap__val { font-size: var(--v2-text-12); font-weight: 600; font-variant-numeric: tabular-nums; color: hsl(var(--v2-foreground)); }
.v2-live-heatmap__cell--good { background: hsl(var(--v2-success-500) / 0.22); }
.v2-live-heatmap__cell--mid  { background: hsl(var(--v2-warning-500) / 0.28); }
/* Case « bad » : argile très diluée, pas le rouge plein --v2-danger qui faisait
   un damier d'alerte quand presque toutes les heures sont < 50 %. */
.v2-live-heatmap__cell--bad  { background: hsl(8 52% 50% / 0.16); }
.v2-live-heatmap__cell--none { background: hsl(var(--v2-muted-bg)); }
.v2-live-heatmap__cell--none .v2-live-heatmap__val { color: hsl(var(--v2-muted-2)); font-weight: 400; }

.v2-live-bins { font-size: var(--v2-text-13); }
.v2-live-bins th, .v2-live-bins td { padding: 4px 8px; }
.v2-live-bins__acc { font-weight: 600; font-variant-numeric: tabular-nums; }
.v2-live-bins__acc--good { color: hsl(var(--v2-success-600)); }
.v2-live-bins__acc--mid  { color: hsl(var(--v2-warning-600)); }
/* « bad » = brique posée, accordée aux barres accmix (pas le rouge d'alarme). */
.v2-live-bins__acc--bad  { color: hsl(var(--v2-brick)); }
.v2-live-bins__acc--none { color: hsl(var(--v2-muted)); }
/* Compacte la table BIN : le padding par défaut de .v2-table rendait les
   rangées trop hautes (panneau plus grand que les autres dans la grille). */
body.v2-dashboard .v2-live-bins thead th,
body.v2-dashboard .v2-live-bins tbody td { padding: 3px 8px; line-height: 1.35; }
body.v2-dashboard .v2-live-bins tbody td { font-size: var(--v2-text-13); }
/* BIN : colonnes fixes pour que Tx + Accept restent visibles même en panneau
   étroit (sinon la table déborde et seul le BIN se voit). */
body.v2-dashboard .v2-live-bins { width: 100%; table-layout: fixed; }
body.v2-dashboard .v2-live-bins th:first-child,
body.v2-dashboard .v2-live-bins td:first-child { width: 46%; }
body.v2-dashboard .v2-live-bins td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* .v2-table impose min-width:600px (pour le scroll des grandes tables) ; la
   table BIN est compacte (3 col) et tient dans son panneau → on lève ce plancher
   pour qu'elle ne déborde pas (Accept. était clippé hors panneau). */
body.v2-dashboard .v2-live-bins { min-width: 0; }
/* Nom de banque (col. 1) : peut être long → ellipsis, un peu plus large que
   l'ancien BIN (6 chiffres). */
body.v2-dashboard .v2-live-bins th:first-child,
body.v2-dashboard .v2-live-bins td:first-child { width: 54%; }

/* ════════════════════════════════════════════════════════════════════════
   MUR LIVE — cohérence : hauteur de panneau homogène. Les graphiques (canvas
   ~220px) donnent le rythme ; heatmap / table banques / mix paiement ont un
   contenu plus court et laissaient les cartes en dents de scie dans la grille.
   On impose un corps mini commun, et on centre verticalement les panneaux
   courts (heatmap, mix) pour éviter un gros vide en bas. Les tables restent
   en haut (lecture naturelle).
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-charts__dense .v2-chart-card__body {
    flex: 1 1 auto;
    min-height: 244px;
}
/* Centre verticalement le contenu (graphe, heatmap, mix) dans la tuile étirée
   pour éviter un vide en bas ; les tables (Banques émettrices) restent en haut
   pour une lecture naturelle. */
.v2-live-charts__dense .v2-chart-card__body:has(> .v2-live-heatmap),
.v2-live-charts__dense .v2-chart-card__body:has(> .v2-live-paymix),
.v2-live-charts__dense .v2-chart-card__body:has(> .v2-chart-canvas-wrapper) {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/* Delta d'acceptation vs hier sous le taux, dans le classement des sites. */
.v2-live-acc-delta {
    display: block;
    margin-top: 1px;
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
}

/* ════════════════════════════════════════════════════════════════════════
   BANDEAU « À surveiller » cliquable — pastille d'aide (?) + détail dépliable
   des sites flaggés (acceptation faible / volume en chute). Mêmes badges que
   le panneau « Sites à surveiller » → uniformité.
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-status__help {
    display: inline-flex;
    align-items: center;
    color: hsl(var(--v2-muted));
    cursor: help;
    border-radius: 50%;
}
.v2-live-status__help:hover,
.v2-live-status__help:focus-visible { color: hsl(var(--v2-primary-600)); outline: none; }

.v2-live-status__toggle {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    background: none;
    border: 0;
    padding: 2px 6px;
    margin-left: 2px;
    font: inherit;
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: hsl(var(--v2-warning-800));
    cursor: pointer;
    border-radius: var(--v2-radius-sm);
    transition: background var(--v2-duration-fast) var(--v2-ease);
}
.v2-live-status__toggle:hover { background: hsl(var(--v2-warning-50)); }
.v2-live-status__toggle:focus-visible { outline: 2px solid hsl(var(--v2-warning-500)); outline-offset: 1px; }
.v2-live-status--success .v2-live-status__toggle { color: hsl(var(--v2-primary-700)); }
.v2-live-status--success .v2-live-status__toggle:hover { background: hsl(var(--v2-primary-50)); }

.v2-live-status__detail {
    list-style: none;
    margin: var(--v2-space-3) 0 0;
    padding: var(--v2-space-3) 0 0;
    border-top: 1px solid hsl(var(--v2-border-subtle));
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-2);
}
.v2-live-status__detail-item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--v2-space-2);
}
.v2-live-status__site { text-decoration: none; }
.v2-live-status__reason { color: hsl(var(--v2-muted)); font-size: var(--v2-text-13); }
.v2-live-status__metric {
    font-size: var(--v2-text-12);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: hsl(var(--v2-muted));
}
.v2-live-status__metric--down { color: hsl(var(--v2-danger-600)); }
.v2-live-status__detail-empty { color: hsl(var(--v2-muted)); font-size: var(--v2-text-13); }
/* Filtres Partenaire/Site déplacés dans la topbar du mur Live (block page_actions). */
.v2-live-topfilter { max-width: 190px; min-width: 130px; font-size: var(--v2-text-14); width: auto; }

/* Sélecteur de plage glissante (topbar mur Live) — pills segmentées façon
   Grafana (1H · 12H · 24H). La topbar vit HORS de .v2-main : on redéclare donc
   les couleurs cyan ici (sinon les .btn-primary / .btn-outline-primary de
   .v2-main ne portent pas sur ces boutons). Hauteur calée sur les selects. */
.v2-live-rangepills { flex-shrink: 0; }
.v2-live-rangepills .btn {
    padding: 4px 11px;
    font-size: var(--v2-text-13);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.4;
}
.v2-live-rangepills .btn + .btn { margin-left: -1px; }
.v2-live-rangepills .btn-outline-primary {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-primary-500) / 0.5);
    color: hsl(var(--v2-primary-600));
}
.v2-live-rangepills .btn-outline-primary:hover,
.v2-live-rangepills .btn-outline-primary:focus-visible {
    background: hsl(var(--v2-primary-50));
    border-color: hsl(var(--v2-primary-500));
    color: hsl(var(--v2-primary-700));
}
.v2-live-rangepills .btn-primary,
.v2-live-rangepills .btn-primary:hover,
.v2-live-rangepills .btn-primary:focus-visible {
    background: hsl(var(--v2-primary-500));
    border-color: hsl(var(--v2-primary-500));
    color: #fff;
}
.v2-live-rangepills .btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.25);
    z-index: 2;
}

/* ════════════════════════════════════════════════════════════════════════
   FUSION « À surveiller » → tuile « Santé du flux » du Pulse.
   Compteur cliquable dans la tuile + panneau détail dépliable sous le strip.
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-stats__watch-toggle {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-weight: 600;
    color: hsl(var(--v2-warning-800));
    cursor: pointer;
}
.v2-live-stats__watch-toggle:hover { text-decoration: underline; }
.v2-live-stats__watch-toggle:focus-visible { outline: 2px solid hsl(var(--v2-warning-500)); outline-offset: 2px; border-radius: var(--v2-radius-sm); }
.v2-live-stats__watch-toggle svg { flex-shrink: 0; }

.v2-live-watchpanel {
    margin-bottom: var(--v2-space-4);
    padding: var(--v2-space-3) var(--v2-space-4);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-xs);
}
.v2-live-watchpanel__head {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    color: hsl(var(--v2-warning-800));
    font-size: var(--v2-text-14);
    margin-bottom: var(--v2-space-2);
}
.v2-live-watchpanel__head svg { color: hsl(var(--v2-warning-600)); flex-shrink: 0; }
.v2-live-watchpanel__hint { color: hsl(var(--v2-muted)); font-weight: 400; font-size: var(--v2-text-13); }

/* Liste en colonnes alignées (badge · raison · acceptation · delta) pour un
   scan vertical net — subgrid pour que chaque colonne s'aligne d'une ligne à
   l'autre malgré la largeur variable des pastilles de site. */
.v2-live-watchpanel__list {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr) max-content max-content;
    column-gap: var(--v2-space-4);
    row-gap: var(--v2-space-2);
    margin: 0;
    padding: 0;
    border-top: 0;
}
.v2-live-watchpanel__list .v2-live-status__detail-item {
    display: grid;
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
    align-items: center;
    column-gap: var(--v2-space-4);
    flex-wrap: nowrap;
}
.v2-live-watchpanel__list .v2-live-status__site { justify-self: start; }
.v2-live-watchpanel__list .v2-live-status__metric { justify-self: end; text-align: right; white-space: nowrap; }

/* ============================================================
   Tags sur la liste des sites
   Badges visibles par tous ; affectation (popover) + filtre =
   réservés aux admins eZengo (les tags pilotent le scoping
   d'accès des partenaires).
   ============================================================ */
.v2-tags-cell {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--v2-space-1);
    max-width: 280px;
}

.v2-badge--tag {
    color: hsl(var(--v2-primary-700));
    background: hsl(var(--v2-primary-50));
}

/* Bouton "+" : discret au repos, cyan au survol */
.v2-tag-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    border: 1px dashed hsl(var(--v2-border-strong));
    border-radius: var(--v2-radius-full);
    background: transparent;
    color: hsl(var(--v2-muted));
    cursor: pointer;
    transition: color var(--v2-duration-fast) var(--v2-ease),
                border-color var(--v2-duration-fast) var(--v2-ease),
                background var(--v2-duration-fast) var(--v2-ease);
}

.v2-tag-add:hover {
    color: hsl(var(--v2-primary-600));
    border-color: hsl(var(--v2-primary-300));
    background: hsl(var(--v2-primary-50));
}

.v2-tag-add:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 1px;
}

/* Popover natif : top-layer, jamais clippé par la table */
.v2-tag-popover {
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-lg);
    background: hsl(var(--v2-card));
    box-shadow: var(--v2-shadow-lg);
    padding: var(--v2-space-2);
    min-width: 210px;
    max-height: 18rem;
    overflow-y: auto;
}

/* Ancrage sous le "+" quand le navigateur supporte l'anchor positioning ;
   sinon le popover natif reste centré (dégradation acceptable). */
@supports (position-area: bottom) {
    .v2-tag-popover {
        margin: 0;
        position-area: bottom span-left;
        position-try-fallbacks: top span-left, bottom span-right;
    }
}

.v2-tag-popover__head {
    font-size: var(--v2-text-11);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: hsl(var(--v2-muted));
    padding: var(--v2-space-1) var(--v2-space-2) var(--v2-space-2);
}

.v2-tag-option {
    display: flex;
    align-items: center;
    gap: var(--v2-space-2);
    width: 100%;
    padding: var(--v2-space-2);
    border: 0;
    border-radius: var(--v2-radius-md);
    background: transparent;
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-13);
    text-align: left;
    cursor: pointer;
    transition: background var(--v2-duration-fast) var(--v2-ease);
}

.v2-tag-option svg {
    color: hsl(var(--v2-muted));
    flex-shrink: 0;
}

.v2-tag-option:hover {
    background: hsl(var(--v2-muted-bg));
}

.v2-tag-option:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: -2px;
}

.v2-tag-option.is-on {
    color: hsl(var(--v2-primary-700));
    font-weight: 600;
}

.v2-tag-option.is-on svg {
    color: hsl(var(--v2-primary-500));
}

.v2-tag-popover__empty {
    padding: var(--v2-space-2);
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-muted));
}

.v2-tag-popover__empty a {
    color: hsl(var(--v2-primary-600));
    font-weight: 600;
}

/* Création inline d'un tag depuis le popover */
.v2-tag-popover__create {
    display: flex;
    gap: var(--v2-space-1);
    align-items: center;
    margin-top: var(--v2-space-1);
    padding: var(--v2-space-2);
    border-top: 1px solid hsl(var(--v2-border));
}

.v2-tag-popover__create .form-control {
    flex: 1 1 auto;
    min-width: 0;
}

/* Entrée animée du popover — état, pas décoration. Coupée si reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
    .v2-tag-popover {
        transition: opacity var(--v2-duration-fast) var(--v2-ease),
                    transform var(--v2-duration-fast) var(--v2-ease),
                    overlay var(--v2-duration-fast) allow-discrete,
                    display var(--v2-duration-fast) allow-discrete;
    }
    .v2-tag-popover:not(:popover-open) {
        opacity: 0;
        transform: translateY(-4px);
    }
    @starting-style {
        .v2-tag-popover:popover-open {
            opacity: 0;
            transform: translateY(-4px);
        }
    }
}

/* ============================================================
   Page Tags (/dashboard/tag) — création inline + compteurs d'usage
   ============================================================ */
.v2-tag-create {
    margin-bottom: var(--v2-space-4);
}

.v2-tag-create__row {
    display: flex;
    gap: var(--v2-space-2);
    align-items: center;
    flex-wrap: wrap;
}

.v2-tag-create__row .form-control {
    flex: 0 1 340px;
    max-width: 340px;
}

/* En-tête de colonne triable */
.v2-sort {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-1);
    color: inherit;
    cursor: pointer;
    user-select: none;
}

.v2-sort svg {
    color: hsl(var(--v2-primary-500));
}

.v2-sort:hover {
    color: hsl(var(--v2-primary-600));
}

.v2-sort:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
    border-radius: var(--v2-radius-sm);
}

/* Compteur d'usage : sites (lien vers la liste filtrée) / plans (statique) */
.v2-tag-usage {
    display: inline-flex;
    align-items: center;
    gap: var(--v2-space-1);
    padding: 0.15rem 0.5rem;
    border-radius: var(--v2-radius-full);
    font-size: var(--v2-text-13);
    font-weight: 600;
    color: hsl(var(--v2-primary-700));
    background: hsl(var(--v2-primary-50));
    text-decoration: none;
    transition: background var(--v2-duration-fast) var(--v2-ease);
}

.v2-tag-usage svg {
    flex-shrink: 0;
}

.v2-tag-usage:hover {
    background: hsl(var(--v2-primary-100));
    color: hsl(var(--v2-primary-700));
}

.v2-tag-usage--static,
.v2-tag-usage--static:hover {
    color: hsl(var(--v2-muted));
    background: hsl(var(--v2-muted-bg));
    cursor: default;
}

/* ════════════════════════════════════════════════════════════════════════
   TUILE FUSIONNÉE « Acceptation par PSP & réseau » — deux sections (Réseau /
   PSP) en lignes tonées avec barre segmentée : la part capturée (verte / ambre /
   rouge) se lit sur un fond de refus, le plus bas EN TÊTE. Remplace deux cartes
   à barres « taux » peu parlantes, et libère une tuile pour les alertes.
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-accmix {
    display: flex;
    flex-direction: column;
    gap: var(--v2-space-3);
    width: 100%;
}
.v2-live-accmix__group {
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.v2-live-accmix__caption {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 3px;
}
.v2-live-accmix__caption-label {
    font-size: var(--v2-text-12);
    font-weight: 700;
    letter-spacing: 0.02em;
    color: hsl(var(--v2-muted));
}
.v2-live-accmix__avg {
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
}
.v2-live-accmix__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 76px 42px 60px;
    align-items: center;
    column-gap: var(--v2-space-2);
    padding: 3px 0;
}
.v2-live-accmix__name {
    font-size: var(--v2-text-13);
    color: hsl(var(--v2-foreground));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-live-accmix__track {
    position: relative;
    height: 9px;
    border-radius: var(--v2-radius-full);
    /* Piste NEUTRE : c'est la barre (vert/ambre/rouge selon le taux) qui porte
       le signal, pas un fond rouge permanent sur chaque ligne (qui alarmait sans
       informer). La part vide = refus, lisible par contraste. */
    background: hsl(var(--v2-muted-bg));
    overflow: hidden;
}
.v2-live-accmix__fill {
    position: absolute;
    inset: 0 auto 0 0;
    min-width: 2px;
    border-radius: var(--v2-radius-full);
}
.v2-live-accmix__fill--good { background: hsl(var(--v2-success-500)); }
.v2-live-accmix__fill--mid  { background: hsl(var(--v2-warning-500)); }
/* « bad » = acceptation basse : signal réel mais PAS le rouge d'alarme
   (--v2-danger) qui faisait un mur d'alerte « tout est cassé ». Brique posée,
   cohérente avec les deltas KPI. La sévérité reste lisible (rouge chaud). */
.v2-live-accmix__fill--bad  { background: hsl(8 56% 54%); }
.v2-live-accmix__val {
    font-size: var(--v2-text-13);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    text-align: right;
}
.v2-live-accmix__val--good { color: hsl(var(--v2-success-600)); }
.v2-live-accmix__val--mid  { color: hsl(var(--v2-warning-600)); }
/* Chiffre « bad » : brique posée (pas --v2-danger), accordé à la barre. */
.v2-live-accmix__val--bad  { color: hsl(var(--v2-brick)); }
.v2-live-accmix__vol {
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

/* ════════════════════════════════════════════════════════════════════════
   TUILE « Alertes — à surveiller » — décrochages non urgents sortis du bandeau.
   Lignes cliquables vers les transactions refusées du site (chaînage). Ton ambre
   (warning), distinct du rouge du bandeau urgent.
   ════════════════════════════════════════════════════════════════════════ */
.v2-live-watchtile__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
}
.v2-live-watchtile__list > li { border-top: 1px solid hsl(var(--v2-border-default)); }
.v2-live-watchtile__list > li:first-child { border-top: 0; }
.v2-live-watchtile__link {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
        "site metric cta"
        "motif motif motif";
    align-items: baseline;
    column-gap: var(--v2-space-2);
    row-gap: 1px;
    padding: var(--v2-space-2) 4px;
    margin: 0 -4px;
    text-decoration: none;
    color: hsl(var(--v2-foreground));
    border-radius: var(--v2-radius-sm);
    transition: background var(--v2-duration) var(--v2-ease);
}
.v2-live-watchtile__link:hover { background: hsl(var(--v2-warning-500) / 0.1); }
.v2-live-watchtile__link:focus-visible {
    outline: 2px solid hsl(var(--v2-warning-500));
    outline-offset: 1px;
}
.v2-live-watchtile__site {
    grid-area: site;
    font-size: var(--v2-text-13);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Identité ambre du tier « à surveiller » portée par l'icône du titre (élément
   graphique → 3:1 suffit) ; le TEXTE des métriques reste en encre / rouge pour
   l'AA (l'ambre à 0.82rem ne passe pas 4.5:1 sur blanc). */
.v2-live-watchtile__head svg { color: hsl(var(--v2-warning-600)); vertical-align: -2px; }
.v2-live-watchtile__metric {
    grid-area: metric;
    font-size: var(--v2-text-13);
    font-variant-numeric: tabular-nums;
    color: hsl(var(--v2-foreground));
    white-space: nowrap;
    text-align: right;
}
.v2-live-watchtile__drop { font-weight: 600; color: hsl(var(--v2-danger-600)); }
.v2-live-watchtile__cta {
    grid-area: cta;
    color: hsl(var(--v2-warning-600));
    font-weight: 700;
}
.v2-live-watchtile__motif {
    grid-area: motif;
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.v2-live-watchtile__more {
    margin-top: var(--v2-space-2);
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
}
.v2-live-watchtile__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--v2-space-2);
    height: 100%;
    min-height: 120px;
    color: hsl(var(--v2-success-600));
    font-size: var(--v2-text-14);
}

/* ════════════════════════════════════════════════════════════════════════
   /dashboard — centre d'alertes décrochage (URGENT + à surveiller), sous Top
   sites. Lignes datées + acquittables (2 niveaux : pris en charge / acquitté).
   ════════════════════════════════════════════════════════════════════════ */
.v2-alerts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.v2-alert {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 4px var(--v2-space-3);
    align-items: start;
    padding: var(--v2-space-3) var(--v2-space-1);
    border-top: 1px solid hsl(var(--v2-border-default));
}
.v2-alert:first-child { border-top: 0; }
.v2-alert--urgent { background: hsl(var(--v2-danger-500) / 0.045); }
.v2-alert--taken { opacity: 0.72; }
.v2-alert__badge {
    grid-row: 1;
    align-self: center;
    font-size: var(--v2-text-10);
    font-weight: 800;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 3px 7px;
    border-radius: var(--v2-radius-sm);
    white-space: nowrap;
}
.v2-alert__badge--urgent { color: hsl(var(--v2-card)); background: hsl(var(--v2-danger-600)); }
.v2-alert__badge--watch {
    color: hsl(var(--v2-warning-600));
    background: hsl(var(--v2-warning-50));
    border: 1px solid hsl(var(--v2-warning-500) / 0.4);
}
.v2-alert__body { grid-column: 2; min-width: 0; }
.v2-alert__title { font-weight: 600; font-size: var(--v2-text-14); }
.v2-alert__cause { color: hsl(var(--v2-danger-600)); font-weight: 500; }
.v2-alert__meta { margin-top: 2px; font-size: var(--v2-text-13); color: hsl(var(--v2-foreground)); font-variant-numeric: tabular-nums; }
.v2-alert__rate { font-weight: 700; }
.v2-alert__drop { color: hsl(var(--v2-danger-600)); font-weight: 600; }
.v2-alert__since { color: hsl(var(--v2-muted)); }
.v2-alert__sep { color: hsl(var(--v2-border-strong)); margin: 0 4px; }
.v2-alert__motif { margin-top: 2px; font-size: var(--v2-text-12); color: hsl(var(--v2-muted)); }
.v2-alert__motif code {
    font-family: var(--v2lux-font-mono);
    font-size: 0.92em;
    background: hsl(var(--v2-muted-bg));
    padding: 0.02rem 0.3rem;
    border-radius: var(--v2-radius-xs);
}
.v2-alert__taken-note { margin-top: 4px; font-size: var(--v2-text-12); color: hsl(var(--v2-success-600)); font-weight: 500; }
.v2-alert__actions {
    grid-column: 3;
    grid-row: 1;
    align-self: center;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: flex-end;
    align-items: center;
}
.v2-alert__refus { color: hsl(var(--v2-danger-600)); font-weight: 600; font-size: var(--v2-text-13); text-decoration: none; white-space: nowrap; }
.v2-alert__refus:hover { text-decoration: underline; }
.v2-alert__btn {
    font: inherit;
    font-size: var(--v2-text-12);
    font-weight: 600;
    padding: 4px 10px;
    border-radius: var(--v2-radius-sm);
    border: 1px solid hsl(var(--v2-border-default));
    background: hsl(var(--v2-card));
    color: hsl(var(--v2-foreground));
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--v2-duration) var(--v2-ease);
}
.v2-alert__btn:hover { background: hsl(var(--v2-muted-bg)); }
.v2-alert__btn:focus-visible { outline: 2px solid hsl(var(--v2-primary-500)); outline-offset: 1px; }
.v2-alert__btn--dismiss { color: hsl(var(--v2-muted)); }
.v2-alert__btn--reopen { color: hsl(var(--v2-primary-600)); border-color: transparent; background: transparent; }
.v2-alerts__dismissed { margin-top: var(--v2-space-2); border-top: 1px solid hsl(var(--v2-border-default)); padding-top: var(--v2-space-2); }
.v2-alerts__dismissed > summary { font-size: var(--v2-text-13); color: hsl(var(--v2-muted)); cursor: pointer; padding: 4px 0; }
.v2-alerts__dismissed > summary:hover { color: hsl(var(--v2-foreground)); }

/* ── Boutons d'acquittement sur la tuile « à surveiller » (contexte ambre) ── */
.v2-live-watchtile__ack {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 0 4px 8px;
}
.v2-live-watchtile__takenby { font-size: var(--v2-text-12); color: hsl(var(--v2-success-600)); font-weight: 500; }
.v2-live-watchtile__btn {
    font: inherit;
    font-size: var(--v2-text-12);
    font-weight: 600;
    padding: 2px 8px;
    border-radius: var(--v2-radius-sm);
    border: 1px solid hsl(var(--v2-border-default));
    background: hsl(var(--v2-card));
    color: hsl(var(--v2-foreground));
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--v2-duration) var(--v2-ease);
}
.v2-live-watchtile__btn:hover { background: hsl(var(--v2-warning-500) / 0.1); }
.v2-live-watchtile__btn:focus-visible { outline: 2px solid hsl(var(--v2-warning-500)); outline-offset: 1px; }
.v2-live-watchtile__btn--dismiss { color: hsl(var(--v2-muted)); }
.v2-live-watchtile__btn--reopen { color: hsl(var(--v2-primary-600)); border-color: transparent; background: transparent; }
.v2-live-watchtile__item--taken { opacity: 0.72; }

/* ──────────────────────────────────────────────────────────────────────────
   Réconciliation Paybox — couche couleur + motion (T29)
   Scopée [data-testid^="v2-paybox"] : ne touche AUCUNE autre surface V2.
   Outil financier → sobre. La couleur encode (accent de marque + sens), le
   mouvement révèle (entrée, cascade). Tout est réversible sous reduced-motion,
   et VISIBLE par défaut (l'animation n'est appliquée que sous no-preference, donc
   un rendu sans animation — print, headless figé, reduced-motion — reste lisible).
   ────────────────────────────────────────────────────────────────────────── */

/* — Couleur : accent de marque discret sur les cartes et tuiles KPI — */
[data-testid^="v2-paybox"] .v2-chart-card {
    border-top: 2px solid hsl(var(--v2-primary-500) / 0.55);
}
[data-testid^="v2-paybox"] .v2-grid--kpis .v2-kpi {
    border-top: 2px solid hsl(var(--v2-primary-500) / 0.4);
    background: linear-gradient(hsl(var(--v2-primary-500) / 0.035), transparent 60%);
    transition: border-color 0.18s ease-out, transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
[data-testid^="v2-paybox"] .v2-grid--kpis .v2-kpi:hover {
    border-top-color: hsl(var(--v2-primary-500) / 0.85);
}

/* — Couleur : lignes de tableau réactives au survol (scan facilité) — */
[data-testid^="v2-paybox"] .v2-table tbody tr {
    transition: background-color 0.12s ease-out;
}
[data-testid^="v2-paybox"] .v2-table tbody tr:hover {
    background: hsl(var(--v2-primary-500) / 0.05);
}

/* — Couleur : lien client plus affirmé au survol — */
[data-testid^="v2-paybox"] a.v2-lbp-client-link { transition: color 0.15s ease-out; }
[data-testid^="v2-paybox"] a.v2-lbp-client-link:hover strong { color: hsl(var(--v2-primary-600)); }

/* — Motion : révélation à l'entrée (cartes + tuiles), réduit-safe — */
@media (prefers-reduced-motion: no-preference) {
    [data-testid^="v2-paybox"] .v2-chart-card,
    [data-testid^="v2-paybox"] .v2-grid--kpis .v2-kpi {
        animation: v2pbx-rise 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
    }
    /* Stagger des tuiles KPI (l'œil suit la lecture gauche→droite). */
    [data-testid^="v2-paybox"] .v2-grid--kpis .v2-kpi:nth-child(2) { animation-delay: 60ms; }
    [data-testid^="v2-paybox"] .v2-grid--kpis .v2-kpi:nth-child(3) { animation-delay: 120ms; }
    [data-testid^="v2-paybox"] .v2-grid--kpis .v2-kpi:nth-child(4) { animation-delay: 180ms; }
    /* Stagger des cartes empilées (haut→bas). */
    [data-testid^="v2-paybox"] .v2-chart-card:nth-of-type(2) { animation-delay: 90ms; }
    [data-testid^="v2-paybox"] .v2-chart-card:nth-of-type(3) { animation-delay: 150ms; }
    [data-testid^="v2-paybox"] .v2-chart-card:nth-of-type(n+4) { animation-delay: 200ms; }

    /* Cascade du waterfall : chaque barre se remplit depuis la gauche — la
       largeur cible (inline) est préservée, on anime l'échelle horizontale. */
    [data-testid="v2-paybox-waterfall"] .v2-wf__bar {
        transform-origin: left center;
        animation: v2pbx-fill 0.55s cubic-bezier(0.22, 1, 0.36, 1) both;
    }
    [data-testid="v2-paybox-waterfall"] .v2-wf__row:nth-child(2) .v2-wf__bar { animation-delay: 70ms; }
    [data-testid="v2-paybox-waterfall"] .v2-wf__row:nth-child(3) .v2-wf__bar { animation-delay: 140ms; }
    [data-testid="v2-paybox-waterfall"] .v2-wf__row:nth-child(4) .v2-wf__bar { animation-delay: 210ms; }
    [data-testid="v2-paybox-waterfall"] .v2-wf__row.is-net .v2-wf__bar { animation-delay: 280ms; }
}

@keyframes v2pbx-rise {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}
@keyframes v2pbx-fill {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

/* ───────────────────────── Réconciliation Paybox (vue d'ensemble) — sections dépliables ─────────────────────────
   « Réponse d'abord » : le waterfall reste à découvert, le contrôle jour/jour et
   les lignes de règlement passent sous des <details> natifs (clavier + lecteur
   d'écran gratuits). Désencombre la page sans rien retirer. Scopé v2-paybox. */
[data-testid^="v2-paybox"] .v2-pbx-section {
    margin-top: var(--v2-space-5);
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
}
[data-testid^="v2-paybox"] .v2-pbx-section__head {
    display: flex;
    align-items: center;
    gap: var(--v2-space-3);
    padding: var(--v2-space-4);
    cursor: pointer;
    list-style: none;
    user-select: none;
    border-radius: var(--v2-radius-lg);
}
[data-testid^="v2-paybox"] .v2-pbx-section__head::-webkit-details-marker { display: none; }
[data-testid^="v2-paybox"] .v2-pbx-section__title { font-size: var(--v2-text-16); font-weight: 600; color: hsl(var(--v2-foreground)); }
[data-testid^="v2-paybox"] .v2-pbx-section__hint { font-size: var(--v2-text-13); color: hsl(var(--v2-muted)); }
[data-testid^="v2-paybox"] .v2-pbx-section__head::after {
    content: '';
    margin-left: auto;
    flex: 0 0 auto;
    width: 9px; height: 9px;
    border-right: 2px solid hsl(var(--v2-muted));
    border-bottom: 2px solid hsl(var(--v2-muted));
    transform: rotate(45deg);
    transition: transform 0.18s ease-out, border-color 0.18s ease-out;
}
[data-testid^="v2-paybox"] .v2-pbx-section[open] > .v2-pbx-section__head::after { transform: rotate(-135deg); }
[data-testid^="v2-paybox"] .v2-pbx-section__head:hover { background: hsl(var(--v2-primary-500) / 0.04); }
[data-testid^="v2-paybox"] .v2-pbx-section__head:hover::after { border-color: hsl(var(--v2-foreground)); }
[data-testid^="v2-paybox"] .v2-pbx-section__head:focus-visible { outline: 2px solid hsl(var(--v2-primary-500)); outline-offset: -2px; }
[data-testid^="v2-paybox"] .v2-pbx-section__body { padding: 0 var(--v2-space-4) var(--v2-space-4); border-top: 1px solid hsl(var(--v2-border-subtle)); }
@media (prefers-reduced-motion: no-preference) {
    [data-testid^="v2-paybox"] .v2-pbx-section[open] > .v2-pbx-section__body { animation: v2pbx-rise 0.35s cubic-bezier(0.22, 1, 0.36, 1) both; }
}

/* ───────────────────────── Radars compacts (démarrages + sous-performance) ─────────────────────────
   Refonte « hybride repliable → pastilles » : chaque radar = un <details> natif
   (zéro JS) ; en-tête repliable (radar sain = 1 ligne), déplié = nuage de
   pastilles, pire en tête. Tonalités : « bad » = brique posée (PAS --v2-danger,
   réservé aux vraies erreurs) ; « warn » = ambre ; « good » = sapin ;
   « neutral » = muet. Les classes .v2-ncw-row__acc--*, .v2-ncw-kind* et
   .v2-ncw-chip* (plus bas) restent : le drill (NewClientWatchDrill) les utilise. */
.v2-ncw { margin-bottom: var(--v2-space-4, 1.25rem); }

/* Carte repliable (= radar) — même chrome que .v2-chart-card */
.v2-ncw-card {
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-xs);
    overflow: hidden;
}
.v2-ncw-card + .v2-ncw-card { margin-top: 8px; }

/* Ligne-résumé (= <summary>) : titre + effectif + jauge + site le plus bas */
.v2-ncw-sum {
    list-style: none; cursor: pointer;
    display: flex; align-items: center; gap: 10px;
    padding: 9px 14px; min-height: 42px;
    outline: none;
}
.v2-ncw-sum::-webkit-details-marker { display: none; }
.v2-ncw-sum--static { cursor: default; }
.v2-ncw-sum:hover { background: hsl(var(--v2-muted-bg)); }
.v2-ncw-sum--static:hover { background: transparent; }
.v2-ncw-sum:focus-visible { box-shadow: inset 0 0 0 2px hsl(var(--v2-primary-500)); }
.v2-ncw-sum__chev { flex: 0 0 auto; width: 13px; height: 13px; color: hsl(var(--v2-muted)); transition: transform 0.18s ease-out; }
details[open] > .v2-ncw-sum .v2-ncw-sum__chev { transform: rotate(90deg); }
.v2-ncw-sum__title { flex: 0 0 auto; font-size: var(--v2-text-14); font-weight: 600; color: hsl(var(--v2-foreground)); white-space: nowrap; }
.v2-ncw-sum__count { flex: 0 0 auto; font-size: var(--v2-text-12); color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; }

/* Mini-jauge de verdicts (proportions) */
.v2-ncw-gauge { flex: 0 0 auto; display: flex; height: 7px; width: 44px; gap: 2px; border-radius: var(--v2-radius-full); overflow: hidden; }
.v2-ncw-gauge__seg { height: 100%; border-radius: var(--v2-radius-2xs); }
.v2-ncw-gauge__seg--bad     { background: hsl(var(--v2-brick-500)); }
.v2-ncw-gauge__seg--warn    { background: hsl(var(--v2-warning-500)); }
.v2-ncw-gauge__seg--good    { background: hsl(var(--v2-success-500)); }
.v2-ncw-gauge__seg--neutral { background: hsl(var(--v2-muted) / 0.5); }

/* Site « le plus bas » poussé à droite de la ligne-résumé */
.v2-ncw-sum__lead { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 7px; justify-content: flex-end; font-size: var(--v2-text-12); }
.v2-ncw-sum__lead-t { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: hsl(var(--v2-muted)); }
.v2-ncw-lead-pct { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-weight: 600; font-size: var(--v2-text-12); padding: 1px 7px; border-radius: var(--v2-radius-full); }
.v2-ncw-lead-pct--bad     { color: hsl(var(--v2-brick)); background: hsl(var(--v2-brick-50)); }
.v2-ncw-lead-pct--warn    { color: hsl(var(--v2-warning-800)); background: hsl(var(--v2-warning-500) / 0.13); }
.v2-ncw-lead-pct--good    { color: hsl(var(--v2-success-700)); background: hsl(var(--v2-success-500) / 0.13); }
.v2-ncw-lead-pct--neutral { color: hsl(var(--v2-muted)); background: hsl(var(--v2-muted-bg)); }

/* Corps déplié */
.v2-ncw-card__body { padding: 2px 14px 12px; border-top: 1px solid hsl(var(--v2-border-default)); }
.v2-ncw-sub { font-size: var(--v2-text-12); color: hsl(var(--v2-muted)); margin: 9px 0 10px; }
@media (prefers-reduced-motion: no-preference) {
    details[open] > .v2-ncw-card__body { animation: v2ncw-rise 0.3s cubic-bezier(0.22, 1, 0.36, 1) both; }
}
@keyframes v2ncw-rise { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* Nuage de pastilles (déplié) */
.v2-ncw-cloud { display: flex; flex-wrap: wrap; gap: 6px; }
.v2-ncw-pill {
    display: inline-flex; align-items: center; gap: 7px; min-width: 0; max-width: 100%;
    border: 1px solid hsl(var(--v2-border-default)); border-radius: var(--v2-radius-full);
    padding: 4px 11px 4px 9px;
    text-decoration: none; color: hsl(var(--v2-foreground)); font-size: var(--v2-text-13);
    transition: border-color 120ms ease-out, background-color 120ms ease-out;
}
.v2-ncw-pill:hover { border-color: hsl(var(--v2-border-strong)); background: hsl(var(--v2-muted-bg)); }
.v2-ncw-pill:focus-visible { outline: none; box-shadow: 0 0 0 2px hsl(var(--v2-primary-500)); }
.v2-ncw-pill__dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; }
.v2-ncw-pill__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.v2-ncw-pill__pct { flex: 0 0 auto; font-variant-numeric: tabular-nums; font-weight: 700; }
.v2-ncw-pill__sub { flex: 0 0 auto; font-size: var(--v2-text-11); color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; }
.v2-ncw-pill--bad  .v2-ncw-pill__dot { background: hsl(var(--v2-brick-500)); }
.v2-ncw-pill--bad  .v2-ncw-pill__pct { color: hsl(var(--v2-brick)); }
.v2-ncw-pill--warn .v2-ncw-pill__dot { background: hsl(var(--v2-warning-500)); }
.v2-ncw-pill--warn .v2-ncw-pill__pct { color: hsl(var(--v2-warning-800)); }
.v2-ncw-pill--good .v2-ncw-pill__dot { background: hsl(var(--v2-success-500)); }
.v2-ncw-pill--good .v2-ncw-pill__pct { color: hsl(var(--v2-success-700)); }
.v2-ncw-pill--neutral .v2-ncw-pill__dot { background: hsl(var(--v2-muted)); }
.v2-ncw-pill--neutral .v2-ncw-pill__pct { color: hsl(var(--v2-muted)); }

/* Couleurs de verdict + kind réutilisées par le drill (NewClientWatchDrill) — NE PAS retirer */
.v2-ncw-row__acc--bad     { color: hsl(var(--v2-brick)); }
.v2-ncw-row__acc--warn    { color: hsl(var(--v2-warning-600)); }
.v2-ncw-row__acc--good    { color: hsl(var(--v2-success-600)); }
.v2-ncw-row__acc--neutral { color: hsl(var(--v2-muted)); }
.v2-ncw-kind { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 3px; font-size: var(--v2-text-11); font-weight: 600; padding: 1px 8px; border-radius: var(--v2-radius-full); }
.v2-ncw-kind--new_site { color: hsl(var(--v2-success-600)); background: hsl(var(--v2-success-500) / 0.12); }
.v2-ncw-kind--reactivation { color: hsl(217 48% 44%); background: hsl(217 58% 95%); }

.v2-ncw-chip {
    flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px;
    font-size: var(--v2-text-12); font-weight: 600; padding: 4px 10px; border-radius: var(--v2-radius-md); white-space: nowrap;
}
.v2-ncw-chip--bad     { color: hsl(var(--v2-brick)); background: hsl(var(--v2-brick-50)); }
.v2-ncw-chip--warn    { color: hsl(var(--v2-warning-600)); background: hsl(var(--v2-warning-500) / 0.13); }
.v2-ncw-chip--good    { color: hsl(var(--v2-success-600)); background: hsl(var(--v2-success-500) / 0.13); }
.v2-ncw-chip--neutral { color: hsl(var(--v2-muted)); background: hsl(var(--v2-muted-bg)); }

/* Mobile : la ligne-résumé garde titre + effectif + jauge ; le site « le plus
   bas » (texte) s'efface sous ~520px pour ne pas écraser le titre. */
@media (max-width: 520px) {
    .v2-ncw-sum { gap: 8px; padding: 9px 12px; }
    .v2-ncw-sum__title { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    .v2-ncw-sum__lead-t { display: none; }
}

.v2-ncw-empty { text-align: center; padding: 22px 12px; color: hsl(var(--v2-muted)); }
.v2-ncw-empty p { margin: 8px 0 0; font-size: var(--v2-text-14); }

.v2-ncw-foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; margin-top: 12px; }
.v2-ncw-foot__more { font-size: var(--v2-text-12); color: hsl(var(--v2-muted)); }
.v2-ncw-foot__link { font-size: var(--v2-text-13); font-weight: 600; color: hsl(var(--v2-foreground)); text-decoration: none; display: inline-flex; align-items: center; gap: 4px; }
.v2-ncw-foot__link:hover { text-decoration: underline; }

/* Page dédiée */
.v2-ncw-page__intro { font-size: var(--v2-text-14); color: hsl(var(--v2-muted)); max-width: 70ch; margin: 0 0 16px; }
.v2-ncw-page__settings { margin-top: var(--v2-space-4, 1.25rem); }

/* ───────────────────────── Bandeaux défilants (tickers) ─────────────────────────
   Vue compacte dashboard/Live des radars : 1 ligne par radar, répartis sur la
   largeur (auto-fit). Le contenu est dupliqué ×2 dans le template → translateX
   -50 % boucle sans couture. Pause au survol/focus. reduced-motion = statique
   défilable à la main. Tons partagés bad/warn/good/neutral (bad = brique, jamais
   --v2-danger). Le décrochage (actionnable) ouvre une carte au clic. */
.v2-tkr-rail { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 8px; margin-bottom: var(--v2-space-4, 1.25rem); }
.v2-tkr {
    display: flex; align-items: center; min-width: 0; height: 44px;
    background: hsl(var(--v2-card)); border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg); box-shadow: var(--v2-shadow-xs); overflow: hidden;
}
.v2-tkr--urgent { border-color: hsl(8 52% 50% / 0.45); }
.v2-tkr__label {
    flex: 0 0 auto; display: flex; align-items: center; gap: 7px; height: 100%;
    padding: 0 12px; font-size: var(--v2-text-13); font-weight: 600; white-space: nowrap;
    color: hsl(var(--v2-foreground));
    border-right: 1px solid hsl(var(--v2-border)); background: hsl(var(--v2-muted-bg) / 0.45);
}
.v2-tkr__ldot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; }
.v2-tkr__ldot--bad { background: hsl(var(--v2-brick-500)); }
.v2-tkr__ldot--warn { background: hsl(var(--v2-warning-500)); }
.v2-tkr__ldot--good { background: hsl(var(--v2-success-500)); }
.v2-tkr__ldot--neutral { background: hsl(var(--v2-muted)); }
.v2-tkr__count { font-size: var(--v2-text-12); font-weight: 500; color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; }
.v2-tkr__vp { flex: 1 1 auto; min-width: 0; height: 100%; overflow: hidden; position: relative; }
.v2-tkr__vp::after { content: ""; position: absolute; top: 0; right: 0; height: 100%; width: 30px; background: linear-gradient(90deg, transparent, hsl(var(--v2-card))); pointer-events: none; }
.v2-tkr__track { display: flex; align-items: center; height: 100%; width: max-content; animation: v2tkr-scroll 30s linear infinite; }
.v2-tkr--urgent .v2-tkr__track { animation-duration: 20s; }
.v2-tkr:hover .v2-tkr__track, .v2-tkr:focus-within .v2-tkr__track { animation-play-state: paused; }
.v2-tkr__chip {
    display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
    height: 100%; padding: 0 14px; font: inherit; font-size: var(--v2-text-13);
    text-decoration: none; color: hsl(var(--v2-foreground)); border: 0; background: transparent; cursor: pointer;
}
.v2-tkr__chip:hover { background: hsl(var(--v2-muted-bg)); }
.v2-tkr__chip:focus-visible { outline: 2px solid hsl(var(--v2-primary-500)); outline-offset: -2px; }
.v2-tkr__dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%; }
.v2-tkr__nm { font-weight: 500; }
.v2-tkr__sc { font-size: var(--v2-text-12); color: hsl(var(--v2-muted)); }
.v2-tkr__pct { font-weight: 700; font-variant-numeric: tabular-nums; }
.v2-tkr__drop { font-size: var(--v2-text-12); font-weight: 600; color: hsl(var(--v2-brick)); }
.v2-tkr__sub { font-size: var(--v2-text-11); color: hsl(var(--v2-muted)); font-variant-numeric: tabular-nums; }
.v2-tkr__sep { flex: 0 0 auto; width: 1px; height: 15px; background: hsl(var(--v2-border)); }
.v2-tkr__dot--bad { background: hsl(var(--v2-brick-500)); }
.v2-tkr__chip--bad .v2-tkr__pct { color: hsl(var(--v2-brick)); }
.v2-tkr__dot--warn { background: hsl(var(--v2-warning-500)); }
.v2-tkr__chip--warn .v2-tkr__pct { color: hsl(var(--v2-warning-800)); }
.v2-tkr__dot--good { background: hsl(var(--v2-success-500)); }
.v2-tkr__chip--good .v2-tkr__pct { color: hsl(var(--v2-success-700)); }
.v2-tkr__dot--neutral { background: hsl(var(--v2-muted)); }
.v2-tkr__chip--neutral .v2-tkr__pct { color: hsl(var(--v2-muted)); }
.v2-tkr__empty { flex: 1 1 auto; display: flex; align-items: center; padding: 0 14px; font-size: var(--v2-text-12); color: hsl(var(--v2-muted)); }
@keyframes v2tkr-scroll { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) {
    .v2-tkr__track { animation: none; }
    .v2-tkr__vp { overflow-x: auto; }
    .v2-tkr__vp::after { display: none; }
}
/* Dans le rail, les composants .v2-ncw ne portent pas leur marge habituelle. */
.v2-tkr-rail > .v2-ncw { margin-bottom: 0; }
/* Dialog actionnable (carte décrochage ouverte au clic d'une vignette). */
.v2-tkr-dialog {
    position: relative;
    width: min(560px, calc(100vw - 32px));
    padding: 0; border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    background: hsl(var(--v2-card)); color: hsl(var(--v2-foreground));
    box-shadow: 0 24px 60px -18px rgba(15, 23, 42, 0.45);
}
.v2-tkr-dialog::backdrop { background: rgba(15, 23, 42, 0.45); }
.v2-tkr-dialog__close {
    position: absolute; top: 8px; right: 8px;
    border: 0; background: transparent; cursor: pointer; color: hsl(var(--v2-muted));
    display: flex; padding: 5px; border-radius: var(--v2-radius-sm); line-height: 1;
}
.v2-tkr-dialog__close:hover { background: hsl(var(--v2-muted-bg)); color: hsl(var(--v2-foreground)); }
.v2-tkr-dialog__body { padding: 16px; }
@media (prefers-reduced-motion: no-preference) {
    .v2-tkr-dialog[open] { animation: v2tkr-pop 0.18s cubic-bezier(0.22, 1, 0.36, 1); }
}
@keyframes v2tkr-pop { from { opacity: 0; transform: translateY(6px) scale(0.99); } to { opacity: 1; transform: none; } }

/* Panneau de réglages */
.v2-ncw-set__grid { display: flex; flex-direction: column; gap: 18px; }
.v2-ncw-set__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 7px; }
.v2-ncw-set__label { font-size: var(--v2-text-14); font-weight: 600; color: hsl(var(--v2-foreground)); display: inline-flex; align-items: center; gap: 6px; }
.v2-ncw-set__hint { font-size: var(--v2-text-12); color: hsl(var(--v2-muted-2)); }
.v2-ncw-set__val { font-size: var(--v2-text-16); font-weight: 700; font-variant-numeric: tabular-nums; }
.v2-ncw-set__val--bad  { color: hsl(var(--v2-brick)); }
.v2-ncw-set__val--warn { color: hsl(var(--v2-warning-600)); }
.v2-ncw-set__range { width: 100%; }
.v2-ncw-set__note { font-size: var(--v2-text-12); color: hsl(var(--v2-muted-2)); margin: 5px 0 0; }
.v2-ncw-set__num { max-width: 120px; }
.v2-ncw-set__row2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
.v2-ncw-seg { display: inline-flex; border: 1px solid hsl(var(--v2-border)); border-radius: var(--v2-radius-md); overflow: hidden; }
.v2-ncw-seg__btn { border: none; background: transparent; padding: 7px 16px; font-size: var(--v2-text-13); cursor: pointer; color: hsl(var(--v2-foreground)); transition: background-color 120ms ease-out; }
.v2-ncw-seg__btn + .v2-ncw-seg__btn { border-left: 1px solid hsl(var(--v2-border)); }
.v2-ncw-seg__btn:hover { background: hsl(var(--v2-muted-bg)); }
.v2-ncw-seg__btn--active { background: hsl(var(--v2-foreground)); color: hsl(var(--v2-background, 0 0% 100%)); }
.v2-ncw-set__preview { display: flex; align-items: center; gap: 9px; padding: 10px 12px; border: 1px solid hsl(var(--v2-border)); border-radius: var(--v2-radius-md); font-size: var(--v2-text-13); color: hsl(var(--v2-muted)); }
.v2-ncw-set__pv { font-weight: 600; }
.v2-ncw-set__pv--bad  { color: hsl(var(--v2-brick)); }
.v2-ncw-set__pv--warn { color: hsl(var(--v2-warning-600)); }
.v2-ncw-set__pv--good { color: hsl(var(--v2-success-600)); }
.v2-ncw-set__pv--neutral { color: hsl(var(--v2-muted)); }
.v2-ncw-set__foot { display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 16px; padding-top: 14px; border-top: 1px solid hsl(var(--v2-border)); }
.v2-ncw-set__saved { margin-right: auto; font-size: var(--v2-text-13); font-weight: 600; color: hsl(var(--v2-success-600)); display: inline-flex; align-items: center; gap: 5px; }

/* Drill marchand */
.v2-ncwd-head { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; padding: 14px 16px; border: 1px solid hsl(var(--v2-border)); border-radius: var(--v2-radius-md); margin-bottom: var(--v2-space-4, 1.25rem); }
.v2-ncwd-head--bad  { background: hsl(10 45% 97%); }
.v2-ncwd-head--warn { background: hsl(var(--v2-warning-500) / 0.06); }
.v2-ncwd-head__stats { display: flex; flex-wrap: wrap; gap: 22px; margin-left: auto; }
.v2-ncwd-head__stat { text-align: right; }
.v2-ncwd-head__num { display: block; font-size: var(--v2-text-16); font-weight: 700; font-variant-numeric: tabular-nums; color: hsl(var(--v2-foreground)); }
.v2-ncwd-head__lbl { display: block; font-size: var(--v2-text-11); color: hsl(var(--v2-muted-2)); }
.v2-ncwd-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--v2-space-4, 1.25rem); }
.v2-ncwd-table tbody tr:first-child td { font-weight: 600; }
.v2-ncwd-detail { margin-top: var(--v2-space-4, 1.25rem); }

@media (prefers-reduced-motion: reduce) {
    .v2-ncw-pill, .v2-ncw-sum__chev, .v2-ncw-seg__btn { transition: none; }
}

/* ── Panneau « Settlements » (versements LBP reçus) — partagé par la page
   client interne (PayboxClientReport) et le relevé marchand
   (PayboxClientStatement). Vert success = argent reçu, tabular-nums partout. ── */
/* Bande de stats du panneau (PAS des tuiles : valeurs plus petites + dividers)
   pour ne pas concurrencer les KpiTile de l'entête de page. */
.v2-stl-kpis {
    display: flex; flex-wrap: wrap;
    padding-bottom: var(--v2-space-4); border-bottom: 1px solid hsl(var(--v2-border));
}
.v2-stl-kpi { flex: 1 1 130px; display: flex; flex-direction: column; gap: 0.1rem; padding: 0 var(--v2-space-4); border-left: 1px solid hsl(var(--v2-border) / 0.65); }
.v2-stl-kpi:first-child { padding-left: 0; border-left: none; }
.v2-stl-lab { font-size: var(--v2-text-11); font-weight: 600; letter-spacing: 0.03em; text-transform: uppercase; color: hsl(var(--v2-foreground) / 0.6); }
.v2-stl-val { font-size: var(--v2-text-16); font-weight: 680; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.v2-stl-sub { font-size: var(--v2-text-11); color: hsl(var(--v2-foreground) / 0.6); }
.v2-stl-fresh { color: hsl(var(--v2-success-600)); font-weight: 600; }
.v2-stl-trend { padding: var(--v2-space-4) 0; border-bottom: 1px solid hsl(var(--v2-border)); }
.v2-stl-trend__head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: var(--v2-space-3); gap: var(--v2-space-3); }
.v2-stl-trend__head > span:first-child { font-size: var(--v2-text-13); font-weight: 600; }
.v2-stl-trend__leg { font-size: var(--v2-text-11); color: hsl(var(--v2-foreground) / 0.55); }
.v2-stl-bars { display: flex; align-items: stretch; gap: 0.5rem; height: 96px; }
.v2-stl-bar { flex: 1; height: 100%; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.v2-stl-bar__track { flex: 1; width: 58%; max-width: 34px; display: flex; align-items: flex-end; }
.v2-stl-bar__fill { display: block; width: 100%; min-height: 3px; border-radius: var(--v2-radius-xs) 4px 0 0; background: hsl(var(--v2-success-600) / 0.85); transition: height 0.5s cubic-bezier(0.22, 1, 0.36, 1); }
.v2-stl-bar.is-current .v2-stl-bar__fill { background: repeating-linear-gradient(45deg, hsl(var(--v2-success-600) / 0.85) 0 5px, hsl(var(--v2-success-600) / 0.4) 5px 10px); }
.v2-stl-bar.is-empty .v2-stl-bar__fill { background: hsl(var(--v2-border)); }
.v2-stl-bar__n { font-size: var(--v2-text-11); font-weight: 650; font-variant-numeric: tabular-nums; }
.v2-stl-bar__m { font-size: var(--v2-text-11); color: hsl(var(--v2-foreground) / 0.55); }
.v2-stl-list { padding-top: var(--v2-space-3); }
.v2-stl-list__head { display: flex; justify-content: space-between; font-size: var(--v2-text-11); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: hsl(var(--v2-foreground) / 0.55); margin-bottom: 0.3rem; }
.v2-stl-row { display: grid; grid-template-columns: 6.2rem 5rem 1fr max-content; align-items: center; gap: var(--v2-space-3); padding: 0.5rem 0; border-bottom: 1px solid hsl(var(--v2-border) / 0.45); }
.v2-stl-row:last-child { border-bottom: none; }
.v2-stl-row__date { font-size: var(--v2-text-13); font-weight: 600; font-variant-numeric: tabular-nums; }
.v2-stl-row__rel { font-size: var(--v2-text-12); color: hsl(var(--v2-foreground) / 0.55); }
.v2-stl-row__track { height: 6px; background: hsl(var(--v2-muted) / 0.16); border-radius: var(--v2-radius-2xs); overflow: hidden; }
.v2-stl-row__fill { display: block; height: 100%; border-radius: var(--v2-radius-2xs); background: hsl(var(--v2-success-600) / 0.5); }
.v2-stl-row__amt { font-size: var(--v2-text-14); font-weight: 650; font-variant-numeric: tabular-nums; color: hsl(var(--v2-success-600)); white-space: nowrap; }
@media (max-width: 640px) {
    .v2-stl-row { grid-template-columns: 5.4rem 1fr max-content; }
    .v2-stl-row__rel { display: none; }
    .v2-stl-kpi { flex-basis: 45%; border-left: none; padding: 0; }
}
@media (prefers-reduced-motion: reduce) { .v2-stl-bar__fill { transition: none; } }

/* ════════════════════════════════════════════════════════════════════════
   Login (.v2-login-*) — page de connexion portée sur le langage V2.
   Le template charge AUSSI le theme V1 (via importmap app), donc toutes les
   classes sont propres (.v2-login-*) et body.v2-login-page reprend la main
   sur la typo / le fond posés par theme.bundle.css. Sobre : une carte
   centrée, le logo, le cyan brand — moment de marque, mais bancaire.
   ════════════════════════════════════════════════════════════════════════ */
body.v2-login-page {
    margin: 0;
    min-height: 100vh;
    font-family: var(--v2lux-font-body);
    font-feature-settings: 'ss01', 'tnum', 'calt';
    font-synthesis: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    letter-spacing: -0.011em;
    color: hsl(var(--v2-foreground));
    /* Halo brand en tête de page (un cran plus présent qu'invisible), sinon
       fond neutre V2. */
    background: radial-gradient(70rem 32rem at 50% -12rem, hsl(var(--v2-primary-100)), transparent 68%),
                hsl(var(--v2-bg));
}
.v2-login-main {
    position: relative;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--v2-space-6) var(--v2-space-4);
}
.v2-login-topbar {
    position: absolute;
    top: var(--v2-space-4);
    right: var(--v2-space-4);
    color: hsl(var(--v2-muted));
}

.v2-login-card {
    width: 100%;
    max-width: 25rem;
    box-sizing: border-box;
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-default));
    border-radius: var(--v2-radius-lg);
    box-shadow: var(--v2-shadow-md);
    padding: var(--v2-space-8);
}

.v2-login-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--v2-space-3);
    margin-bottom: var(--v2-space-6);
}
.v2-login-wordmark {
    font-size: var(--v2-text-28);
    font-weight: 650;
    letter-spacing: -0.03em;
    color: hsl(var(--v2-foreground));
}
/* Le « Z » d'eZengo en cyan brand — la SEULE touche de marque, sobre : un
   wordmark typographique, pas de mark raster (moment de marque, mais bancaire). */
.v2-login-wordmark__accent { color: hsl(var(--v2-primary-600)); }

.v2-login-title {
    margin: 0 0 var(--v2-space-2);
    font-size: var(--v2-text-20);
    font-weight: 650;
    letter-spacing: -0.02em;
    text-align: center;
    color: hsl(var(--v2-foreground));
}
.v2-login-subtitle {
    margin: 0 0 var(--v2-space-6);
    font-size: var(--v2-text-14);
    text-align: center;
    color: hsl(var(--v2-muted));
}
.v2-login-session {
    margin: 0 0 var(--v2-space-4);
    font-size: var(--v2-text-13);
    text-align: center;
    color: hsl(var(--v2-muted));
}
.v2-login-session a { color: hsl(var(--v2-primary-700)); font-weight: 550; }

/* Erreur d'authentification — vraie erreur, donc --v2-danger légitime.
   Inline et persistante (role="alert"), référencée par aria-describedby. */
.v2-login-error {
    display: flex;
    align-items: flex-start;
    gap: var(--v2-space-2);
    margin-bottom: var(--v2-space-5);
    padding: var(--v2-space-3);
    border: 1px solid hsl(var(--v2-danger-500) / 0.35);
    border-radius: var(--v2-radius-md);
    background: hsl(var(--v2-danger-50));
    color: hsl(var(--v2-danger-600));
    font-size: var(--v2-text-13);
    font-weight: 500;
    line-height: 1.45;
}
.v2-login-error__icon { flex: none; margin-top: 0.1rem; }

.v2-login-field { margin-bottom: var(--v2-space-5); }
.v2-login-label {
    display: block;
    margin-bottom: var(--v2-space-2);
    font-size: var(--v2-text-13);
    font-weight: 550;
    color: hsl(var(--v2-foreground));
}
.v2-login-input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    height: 2.75rem;
    padding: 0 var(--v2-space-3);
    font-family: inherit;
    font-size: var(--v2-text-14);
    color: hsl(var(--v2-foreground));
    background: hsl(var(--v2-card));
    border: 1px solid hsl(var(--v2-border-strong));
    border-radius: var(--v2-radius-md);
    transition: border-color var(--v2-duration-fast) var(--v2-ease),
                box-shadow var(--v2-duration-fast) var(--v2-ease);
}
.v2-login-input::placeholder { color: hsl(var(--v2-muted) / 0.9); }
.v2-login-input:focus {
    outline: none;
    border-color: hsl(var(--v2-primary-500));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.18);
}

.v2-login-pw { position: relative; }
.v2-login-pw .v2-login-input { padding-right: 3rem; }
.v2-login-eye {
    position: absolute;
    top: 50%;
    right: 0.25rem;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;   /* cible tactile ≥44px (WCAG 2.5.5) */
    height: 2.75rem;
    padding: 0;
    border: 0;
    background: none;
    border-radius: var(--v2-radius-sm);
    color: hsl(var(--v2-muted));
    cursor: pointer;
}
.v2-login-eye:hover { color: hsl(var(--v2-foreground)); }
.v2-login-eye:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 1px;
}
/* Le thème V1 peignait un œil ::before sur [data-toggle-password] : l'attribut
   est retiré du template, on neutralise par sécurité — un seul œil = le SVG. */
.v2-login-eye::before { content: none; }
/* Bascule œil / œil barré pilotée par aria-pressed (login.js) — état visible. */
.v2-login-eye__off { display: none; }
.v2-login-eye[aria-pressed="true"] .v2-login-eye__on { display: none; }
.v2-login-eye[aria-pressed="true"] .v2-login-eye__off { display: block; }
/* Masque le contrôle de révélation NATIF (Edge) : le nôtre est la source de vérité. */
.v2-login-pw input[type="password"]::-ms-reveal,
.v2-login-pw input[type="password"]::-ms-clear { display: none; }

.v2-login-submit {
    width: 100%;
    height: 2.75rem;
    margin-top: var(--v2-space-2);
    border: 0;
    border-radius: var(--v2-radius-md);
    background: hsl(var(--v2-primary-600));
    color: #fff;
    font-family: inherit;
    font-size: var(--v2-text-14);
    font-weight: 600;
    letter-spacing: -0.01em;
    cursor: pointer;
    transition: background-color var(--v2-duration-fast) var(--v2-ease);
}
.v2-login-submit:hover { background: hsl(var(--v2-primary-700)); }
.v2-login-submit:focus-visible {
    outline: 2px solid hsl(var(--v2-primary-500));
    outline-offset: 2px;
}

.v2-login-help {
    margin: var(--v2-space-5) 0 0;
    font-size: var(--v2-text-13);
    text-align: center;
    color: hsl(var(--v2-muted));
}
.v2-login-help a {
    display: block;
    margin-top: var(--v2-space-1);
    color: hsl(var(--v2-primary-700));
    font-weight: 550;
}

.v2-login-footer {
    margin-top: var(--v2-space-8);
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
    text-align: center;
}

@media (max-width: 480px) {
    .v2-login-card { padding: var(--v2-space-6); }
}
@media (prefers-reduced-motion: reduce) {
    .v2-login-input,
    .v2-login-submit { transition: none; }
}
.v2-dc-detail__compare {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    padding-top: var(--v2-space-2);
    margin-top: 0.15rem;
    border-top: 1px solid hsl(var(--v2-border-subtle));
}
.v2-dc-detail__sublabel {
    margin: 0;
    font-size: var(--v2-text-11);
    font-weight: 600;
    letter-spacing: -0.005em;
    text-transform: none;
    color: hsl(var(--v2-muted));
}

/* ─────────────────────────────────────────────────────────────────────────
   Onglet « Demande » — analyse d'échecs + lecture assistée
   ─────────────────────────────────────────────────────────────────────────
   Né d'une demande marchand : ventiler les échecs par CODE (le message libre
   varie, le code non) et mesurer la durée réelle d'une préautorisation.
   Le bloc « lecture assistée » n'invente aucun chiffre : il interprète les
   agrégats calculés au-dessus. La contrainte de design qui en découle : il
   doit se lire comme un commentaire, jamais comme une source. D'où l'absence
   de tuile, de gros chiffre, de couleur propre — c'est du texte dans une
   carte, sous les chiffres qui font foi. */

/* Le résumé emprunte la ligne dépliable de Complétude, qui ancre son chevron
   via `__pct` (absent ici) : on remet la poussée à droite sur `__split`. */
.v2-llm-disclosure {
    margin-bottom: var(--v2-space-3);
}
.v2-llm-disclosure .v2-dc-row__split {
    margin-left: auto;
}

/* Ce qui part réellement sur le réseau. Affiché avant l'envoi, donc lisible :
   mono pour que la structure JSON se voie, et scroll propre plutôt qu'un
   retour à la ligne qui casserait l'indentation. */
.v2-llm-payload {
    margin: 0;
    max-height: 22rem;
    overflow: auto;
    padding: var(--v2-space-2);
    border-radius: var(--v2-radius-sm);
    background: hsl(var(--v2-muted-bg));
    font-family: var(--v2lux-font-mono);
    font-size: var(--v2-text-11);
    line-height: 1.6;
    color: hsl(var(--v2-foreground));
    tab-size: 2;
}

.v2-llm-verdict {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0 0 var(--v2-space-3);
    font-size: var(--v2-text-13);
    line-height: 1.6;
    color: hsl(var(--v2-foreground));
}

/* Bandeau d'état de l'analyse — MÊME FORME que `.v2-paybox-run`, l'autre surface
   de ce backoffice qui attend un traitement asynchrone (styles dans le <style>
   inline de PayboxSettlementList). On en reprend les trois règles qui comptent,
   et que ma première version enfreignait toutes les trois :
     · la couleur sémantique est portée par l'ICÔNE et la BORDURE, jamais par le
       corps de texte, qui reste en --v2-foreground ;
     · les teintes sont des superpositions `-500 / α`, pas les paliers `-50` qui
       ne sont lisibles qu'en thème clair ;
     · `min-width: 0` + `overflow-wrap` sur le texte, sinon le piège flex
       (min-width auto) fait déborder un identifiant de job ou un message PSP. */
.v2-llm-status {
    display: flex;
    align-items: flex-start;
    gap: var(--v2-space-2);
    padding: 0.55rem 0.8rem;
    margin: 0 0 var(--v2-space-3);
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-md);
    background: hsl(var(--v2-muted) / 0.06);
    color: hsl(var(--v2-foreground));
    font-size: var(--v2-text-14);
    line-height: 1.45;
}
.v2-llm-status svg { flex: none; margin-top: 0.12em; }
.v2-llm-status > span { min-width: 0; overflow-wrap: anywhere; }

.v2-llm-status.is-running { border-color: hsl(var(--v2-info-500) / 0.5); background: hsl(var(--v2-info-500) / 0.06); }
.v2-llm-status.is-running svg { color: hsl(var(--v2-info-600)); animation: v2-spin 1s linear infinite; }
/* Pause : bordure tiretée, comme `.is-never` chez le voisin — une attente
   suspendue, pas une erreur. L'icône ne tourne plus : plus rien ne se passe. */
.v2-llm-status.is-paused { border-style: dashed; }
.v2-llm-status.is-paused svg { color: hsl(var(--v2-muted)); }
.v2-llm-status.has-error { border-color: hsl(var(--v2-danger-500) / 0.5); background: hsl(var(--v2-danger-500) / 0.07); }
.v2-llm-status.has-error svg { color: hsl(var(--v2-danger-600)); }
.v2-llm-status.is-stale { border-color: hsl(var(--v2-warning-500) / 0.5); background: hsl(var(--v2-warning-500) / 0.07); }
.v2-llm-status.is-stale svg { color: hsl(var(--v2-warning-600)); }

@media (prefers-reduced-motion: reduce) {
    .v2-llm-status.is-running svg { animation: none; }
}

.v2-llm-subhead {
    margin: var(--v2-space-3) 0 0.4rem;
    font-size: var(--v2-text-11);
    font-weight: 600;
    color: hsl(var(--v2-muted));
}
.v2-llm-checks {
    margin: 0;
    padding-left: 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
    font-size: var(--v2-text-12);
    line-height: 1.6;
    color: hsl(var(--v2-foreground));
}

/* ─── Onglet « Demande » : question libre et cycle de vie de l'analyse ───────
   La question est le cœur de la fonctionnalité : c'est elle qui fait passer la
   page d'une réponse figée à un vrai outil d'investigation. Elle mérite donc
   la place et la lisibilité d'un champ principal, pas d'un accessoire. */

.v2-llm-label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: var(--v2-text-12);
    font-weight: 600;
    color: hsl(var(--v2-foreground));
}
.v2-llm-input {
    width: 100%;
    padding: 0.6rem 0.7rem;
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius);
    background: hsl(var(--v2-card));
    color: hsl(var(--v2-foreground));
    font-family: inherit;
    font-size: var(--v2-text-13);
    line-height: 1.6;
    resize: vertical;
    transition: border-color var(--v2-duration, 160ms) var(--v2-ease, ease);
}
.v2-llm-input::placeholder {
    /* Un placeholder doit rester lisible : même exigence AA que le corps de
       texte, pas le gris décoratif par défaut. */
    color: hsl(var(--v2-muted));
}
.v2-llm-input:hover { border-color: hsl(var(--v2-primary-500) / 0.5); }
.v2-llm-input:focus {
    outline: none;
    border-color: hsl(var(--v2-primary-500));
    box-shadow: 0 0 0 3px hsl(var(--v2-primary-500) / 0.15);
}

/* Les suggestions s'enroulent : ce sont des phrases entières, pas des mots-clés.
   Les tronquer les rendrait indevinables, donc inutiles. */
.v2-llm-suggestions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: var(--v2-space-3);
}
.v2-llm-chip {
    max-width: 100%;
    padding: 0.35rem 0.7rem;
    border: 1px solid hsl(var(--v2-border));
    border-radius: var(--v2-radius-full);
    background: hsl(var(--v2-muted-bg));
    color: hsl(var(--v2-muted));
    font-family: inherit;
    font-size: var(--v2-text-12);
    line-height: 1.45;
    text-align: left;
    cursor: pointer;
    transition: background var(--v2-duration, 160ms) var(--v2-ease, ease),
                color var(--v2-duration, 160ms) var(--v2-ease, ease),
                border-color var(--v2-duration, 160ms) var(--v2-ease, ease);
}
.v2-llm-chip:hover {
    background: hsl(var(--v2-card));
    border-color: hsl(var(--v2-primary-500) / 0.6);
    color: hsl(var(--v2-foreground));
}

.v2-llm-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: var(--v2-space-2);
}

/* Rappel de la question au-dessus du résultat : sans lui, une réponse relue
   plus tard ne dit plus à quoi elle répondait. */
.v2-llm-question-echo {
    margin: 0 0 var(--v2-space-2);
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-muted));
}

/* Le compteur de vérifications remplace le faux « moins d'une minute » : il dit
   ce qui se passe vraiment, sans promettre une durée qu'on ne maîtrise pas. */
.v2-llm-elapsed {
    margin-left: auto;
    padding-left: var(--v2-space-2);
    font-size: var(--v2-text-12);
    font-variant-numeric: tabular-nums;
    color: hsl(var(--v2-foreground) / 0.7);
    white-space: nowrap;
}
.v2-llm-jobid {
    margin: 0.4rem 0 0;
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
}
.v2-llm-jobid code {
    font-family: var(--v2lux-font-mono);
    font-size: 0.95em;
}

/* Constats : le chiffre d'abord, puis l'observation, puis la population dont
   il vient. Cet ordre est délibéré — il rend la vérification contre les
   tableaux ci-dessus mécanique plutôt qu'un travail de relecture. */
.v2-llm-constats {
    margin: 0 0 var(--v2-space-3);
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}
.v2-llm-constats li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    font-size: var(--v2-text-12);
    line-height: 1.55;
    color: hsl(var(--v2-foreground));
}
.v2-llm-constats__chiffre {
    flex-shrink: 0;
    min-width: 4.5rem;
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: hsl(var(--v2-primary-800));
}
.v2-llm-constats__pop {
    flex-shrink: 0;
    font-size: var(--v2-text-11);
    color: hsl(var(--v2-muted));
}

@media (max-width: 720px) {
    .v2-llm-elapsed { margin-left: 0; padding-left: 0; flex-basis: 100%; }
    .v2-llm-constats__chiffre { min-width: 0; }
}

/* Réponse en cours d'écriture (flux SSE Novawave). Discrète : c'est un état
   transitoire, remplacé par le rendu structuré dès le job terminé. */
.v2-llm-stream {
    margin: var(--v2-space-2) 0 0;
    font-size: var(--v2-text-13);
    line-height: 1.6;
    color: hsl(var(--v2-foreground) / 0.82);
    white-space: pre-wrap;
}
.v2-llm-stream:empty { display: none; }

/* ── Encodage visuel des tableaux d'analyse (onglet Assistant) ──────────────
   392 cellules de chiffres sans aucune aide visuelle : l'œil ne pouvait rien
   comparer, il fallait lire ligne à ligne. Deux encodages, pas plus — une
   barre pour la proportion, une teinte pour l'intensité. */

/* Part des échecs : barre + valeur. La barre donne le RANG d'un coup d'œil ;
   le chiffre reste lisible pour qui a besoin de la valeur exacte. */
.v2-cell-prop { text-align: right; }
.v2-prop {
    display: inline-grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-width: 120px;
}
.v2-prop__bar {
    height: 5px;
    border-radius: var(--v2-radius-2xs);
    background: hsl(var(--v2-foreground) / 0.07);
    overflow: hidden;
}
.v2-prop__bar > i {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: hsl(var(--v2-primary-600));
    /* Une part infime doit rester perceptible, sinon la barre ment par omission. */
    min-width: 2px;
}
.v2-prop__val { font-variant-numeric: tabular-nums; }

/* Taux d'échec : fond teinté proportionnel, via --taux (0-100) posé en Twig.
   ⚠ BRIQUE, PAS `--v2-danger`. Un taux d'échec élevé est une valeur basse
   honnête — la réalité du parc de cartes — pas une panne du système. Le rouge
   d'alerte reste réservé aux vraies erreurs, sinon il ne veut plus rien dire.
   L'encre passe au brique foncé au-delà de 50 % pour rester lisible sur le
   fond le plus dense. */
.v2-cell-rate {
    text-align: right;
    font-variant-numeric: tabular-nums;
    /* ⛔ PLAFOND ABAISSÉ DE 0.6 À 0.20. À 0.006 par point, les taux réels
       (67-97 %) sortaient à ~0,5 d'opacité : un mur rouge qui fait peur sans
       rien apprendre. `--taux` est désormais une POSITION normalisée 0-100 sur
       la plage observée, pas le taux brut — le plus bas reste presque blanc,
       le plus haut est net, et l'écart se lit. Teinte brique, jamais
       `--v2-danger` : un taux élevé est une réalité du parc de cartes. */
    background: hsl(8 50% 44% / calc(var(--taux, 0) * 0.002));
}
.v2-cell-rate:not([style*="--taux"]) { background: none; }

/* Garanties sous le champ de saisie : présentes, jamais bruyantes. */
.v2-llm-trust {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: var(--v2-space-3) 0 0;
    font-size: var(--v2-text-12);
    line-height: 1.55;
    /* 0.70 et non 0.62 : à 0.62 le ratio tombait pile sur 4,50:1,
       soit AA à l'arrondi près. 0.70 donne de la marge (5,78:1). */
    color: hsl(var(--v2-foreground) / 0.70);
    max-width: 78ch;
}
.v2-llm-trust svg { flex: none; margin-top: 2px; color: hsl(var(--v2-primary-600)); }

/* ── Origine d'un échec : banque / eZengo / non communiqué ──────────────────
   ⚠ ATTRIBUTION, PAS GRAVITÉ. Un `3ds_failed` est un porteur qui rate son
   authentification, un `expired` une préautorisation tombée avant capture :
   ni panne, ni faute. Ce que la couleur sépare, c'est ce que la BANQUE décide
   (hors de portée du marchand) de ce qui se règle CHEZ NOUS (réglable).
   Restreint volontairement : le cas dominant — le refus émetteur — reste sans
   couleur. Colorier la majorité, c'est ne rien distinguer du tout. */
.v2-origin { border-radius: var(--v2-radius-xs); padding: 1px 5px; }
.v2-origin--issuer { /* majorité : encre neutre, aucune teinte */ }
.v2-origin--internal {
    /* Paire déjà documentée AA dans les tokens : warning-800 sur warning-50. */
    background: hsl(var(--v2-warning-50));
    color: hsl(var(--v2-warning-800));
}
.v2-origin--unknown {
    background: hsl(var(--v2-foreground) / 0.05);
    color: hsl(var(--v2-foreground) / 0.55);
}

.v2-origin-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin: var(--v2-space-2) 0 0;
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-foreground) / 0.70);
}
.v2-origin-legend__item { display: inline-flex; align-items: center; gap: 6px; }
.v2-origin-legend .v2-origin { font-size: var(--v2-text-11); font-family: var(--v2lux-font-mono); }

/* Reste non affiché d'un tableau plafonné. Discret, mais jamais absent :
   un plafond silencieux se lit comme une liste complète. */
.v2-table-rest {
    margin: var(--v2-space-2) 0 0;
    font-size: var(--v2-text-12);
    color: hsl(var(--v2-foreground) / 0.70);
}
