/* === Reset === */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
/* Apply font on html too: a wrapper that resets font-family inside an
   iframe / embed would otherwise fall back to the UA default (Times). */
html { font-family: var(--font-sans); }

/* === Embed mode: stripped layout used by the graph's iframe-based
   "Nouvelle entité" panel. The sidebar, license banner and flashes
   are skipped in base.html; here we just tighten the chrome around
   the main content so the form fits comfortably inside an iframe. */
body.embed-mode .app { min-height: 0; }
body.embed-mode main { padding: 1rem 1.25rem; --main-pad-x: 1.25rem; max-width: 100%; }
body.embed-mode h1 { font-size: 1.05rem; margin-bottom: 0.8rem; }
body.embed-mode .crumbs { display: none; }
/* The "Annuler" link in form actions navigates the iframe away from the
   form (back to the entity list). The graph panel header has its own ✕,
   so hide the inline cancel to avoid the confusing dual exit. */
body.embed-mode .actions .btn.secondary,
body.embed-mode .actions a.btn { display: none; }
*:focus:not(:focus-visible) { outline: none; }

/* === Checkboxes === */
input[type=checkbox] {
    appearance: none; -webkit-appearance: none;
    width: 15px; height: 15px; min-width: 15px; min-height: 15px;
    flex-shrink: 0;
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
    vertical-align: middle;
    background-repeat: no-repeat;
    background-position: center;
}
input[type=checkbox]:hover { border-color: var(--primary); }
input[type=checkbox]:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--focus-ring-strong);
}
input[type=checkbox]:checked {
    background-color: var(--primary);
    border-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 4l2.5 2.5L9 1' stroke='white' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-size: 10px 8px;
}
input[type=checkbox]:indeterminate {
    background-color: var(--primary);
    border-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 2' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1h8' stroke='white' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
    background-size: 10px 2px;
}

/* === Scrollbar === */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--border-light); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-light); }
::-webkit-scrollbar-button { background: var(--border-light); width: 10px; height: 10px; }
::-webkit-scrollbar-button:vertical:decrement {
    background: var(--border-light)
    url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 6.5l3-3 3 3' stroke='%235a5f74' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat center;
}
::-webkit-scrollbar-button:vertical:increment {
    background: var(--border-light)
    url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 3.5l3 3 3-3' stroke='%235a5f74' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
    no-repeat center;
}
::-webkit-scrollbar-button:vertical:decrement:hover,
::-webkit-scrollbar-button:vertical:increment:hover { background-color: var(--border); }
* { scrollbar-width: thin; scrollbar-color: var(--border) var(--border-light); }

/* Theme tokens (`:root`, `[data-theme="light"]`, …) live in `themes.css`,
   loaded BEFORE this file by `base.html`. This file only contains layout
   and component rules — never raw color literals. */

body {
    font-family: var(--font-sans);
    background: var(--bg);
    color: var(--text);
    /* 14px / 1.55 — un cran plus aéré que la densité Bloomberg (13/1.45)
       qu'on avait. Les tableaux denses (data-excel) restent 13px via
       leurs propres règles, mais le contenu courant respire. */
    font-size: 14px;
    line-height: 1.55;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    /* Tabular numerals partout par défaut — alignement propre des
       colonnes de chiffres (montants, dates, durées) sans avoir à le
       redéclarer composant par composant. */
    font-variant-numeric: tabular-nums;
}

/* === Display titles (h1 hero, h1-h3 fiches) ===
   Serif éditorial pour les titres principaux — signal "presse économique
   haut de gamme" qui différencie de la SaaS générique tout-Inter.
   Les sous-titres / labels d'UI restent en sans (Inter Tight) pour la
   lisibilité dense. */
h1, h2.display, .hero-info h1, .page-header h1 {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: -0.015em;
}

/* === Layout ===
   Height cascade: html + body set to height 100% (line 3). `.app` then uses
   min-height:100vh for the normal case (content can push the page taller
   and scroll the window), AND height:100% kicks in on `.app:has(main.fullbleed)`
   so fullbleed pages get a strict viewport-bound height — no accumulated
   overflow, no scroll, the graph canvas fills exactly the window. */
.app {
    display: flex;
    min-height: 100vh;
    min-height: 100dvh;
    /* CSS `zoom` on <html> (desktop Ctrl+wheel) computes vh against the
       unzoomed viewport, then rescales — so a zoom != 1 makes 100vh
       overflow or under-fill. We divide by --zoom (set in JS when zoom
       changes) so the layout pixel count compensates: (100vh / z) * z = 100vh. */
    min-height: calc(100dvh / var(--zoom, 1));
}
.app:has(> main.fullbleed) {
    height: 100%;
    min-height: 0;   /* override the rule above so main can shrink to 100% */
}

/* Sidebar */
.sidebar {
    width: 240px;
    background: var(--sidebar);
    color: var(--sidebar-text);
    display: flex;
    flex-direction: column;
    padding: 1.25rem 0;
    flex-shrink: 0;
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    height: calc(100dvh / var(--zoom, 1));
    overflow-y: auto;
    overscroll-behavior: contain;
    transition: background 0.2s, color 0.2s;
}
.sidebar .brand {
    padding: 0 1.5rem;
    /* Wordmark Stellint en serif — alignement avec les titres de la
       marque, signal éditorial constant entre nav et contenu. */
    font-family: var(--font-display);
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--sidebar-brand);
    letter-spacing: -0.01em;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 1rem;
    position: relative;
    text-decoration: none;
    transition: opacity 0.15s;
    /* Vertically aligned with .gp-sidebar-header of the graph sidebar:
       .sidebar padding-top 1.25rem (20px) + 3.6rem (57.6px) = 77.6px,
       same as 1.1rem + (h1 24px + 0.15rem + p 19.2px) + 0.9rem. */
    min-height: 3.6rem;
}
.sidebar .brand:hover { opacity: 0.8; }
/* Rule aligned with the nav items (horizontal padding 0.75rem) rather than
   edge-to-edge as a classic border-bottom would be. */
.sidebar .brand::after {
    content: "";
    position: absolute;
    left: 0.75rem;
    right: 0.75rem;
    bottom: 0;
    height: 1px;
    background: var(--sidebar-border);
}
.sidebar .brand .logo {
    width: 42px;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.sidebar .brand .logo img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}
.sidebar nav { display: flex; flex-direction: column; padding: 0 0.75rem; gap: 1px; }
.sidebar nav a,
.sidebar nav a:visited {
    display: flex; align-items: center; gap: 0.65rem;
    padding: 0.4rem 0.75rem; border-radius: var(--radius-sm);
    color: var(--sidebar-nav); text-decoration: none; font-size: 0.82rem;
    font-weight: 500; letter-spacing: -0.01em;
    /* No transition here: the sidebar is fully repainted on every
       navigation, and the clicked link rapidly traverses multiple
       states (hover -> transparent -> active, plus the UA `:visited`
       flash resolved after the first paint). Any transition on `color`,
       `background` or `box-shadow` turns this micro-flash into a
       visible 150ms fade. Instantaneous hover state = intended here. */
}
.sidebar nav a svg {
    width: 17px; height: 17px; stroke-width: 2; flex-shrink: 0;
    opacity: 0.6;
}
.sidebar nav a:hover {
    background: var(--sidebar-hover);
    color: var(--sidebar-text-hover);
    box-shadow: inset 2px 0 0 var(--sidebar-border);
}
.sidebar nav a:hover svg { opacity: 0.9; }
.sidebar nav a.active {
    /* Active state en or champagne — signal de marque, pas le bleu
       commodity du SaaS générique. Tinte or 14% + filet 3px or à
       gauche + texte ivoire. */
    background: rgba(182, 151, 86, 0.14);
    color: var(--sidebar-text-active);
    box-shadow: inset 3px 0 0 var(--sidebar-active);
    font-weight: 600;
}
.sidebar nav a.active svg {
    opacity: 1;
    color: var(--sidebar-active);
}
.sidebar .nav-label {
    font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--sidebar-text-muted); padding: 1rem 0.85rem 0.35rem; font-weight: 600;
}

/* === Sidebar footer: theme toggle === */
.sidebar-foot {
    margin-top: auto;
    padding: 0.7rem 1rem;
    border-top: 1px solid var(--sidebar-border);
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.6rem;
}
/* Space between the last nav item and the border-top of
   `.sidebar-foot` - without this, "Archives" visually rests on the
   grey bar. Mirrors the top space between the ws-switcher
   (border-bottom) and the "Annuaire" label. */
.sidebar nav:last-of-type {
    padding-bottom: 1rem;
}
.sidebar-foot-label {
    font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--sidebar-text-muted); font-weight: 600;
}
.theme-toggle {
    position: relative;
    display: inline-flex; align-items: center; justify-content: space-between;
    width: 60px; height: 28px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    padding: 0; margin: 0;
    flex-shrink: 0;
    transition: background 0.2s, border-color 0.2s;
}
.theme-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.theme-toggle-icon {
    position: absolute; top: 0; bottom: 0;
    display: flex; align-items: center; justify-content: center;
    width: 28px;
    color: var(--text-muted);
    pointer-events: none;
    z-index: 1;
    transition: color 0.2s, opacity 0.2s;
}
.theme-toggle-icon.sun  { left: 2px; }
.theme-toggle-icon.moon { right: 2px; }
.theme-toggle-icon svg { width: 13px; height: 13px; stroke-width: 2.2; }
.theme-toggle-thumb {
    position: absolute; top: 1px; left: 2px;
    width: 24px; height: 24px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 50%;
    transform: translateX(32px);   /* dark default: thumb on the moon (right) */
    transition: transform 0.22s ease, background 0.22s, border-color 0.22s;
    box-shadow: var(--shadow-sm);
    pointer-events: none;
    z-index: 2;
}
[data-theme="light"] .theme-toggle-thumb { transform: translateX(0); }
/* The "active" icon (thumb side) takes the primary color */
.theme-toggle-icon.moon                       { color: var(--primary); }
[data-theme="light"] .theme-toggle-icon.moon  { color: var(--text-muted); }
[data-theme="light"] .theme-toggle-icon.sun   { color: var(--warning); }

/* Main */
main {
    flex: 1;
    /* Flex item default `min-width: auto` refuses to shrink below the
       min-content width of its children. A wide child (the stats detail
       dashboard, a large D3 matrix…) would then push `main` past its flex
       allocation, out from under the sidebar and off the viewport (≈196 px
       overflow). `min-width: 0` caps `main` at its allocated width; wide
       content is handled by each block's own overflow (`.table-wrap`) or
       clipped by `overflow-x: clip` below. */
    min-width: 0;
    padding: 1.1rem 1.5rem 1.5rem;
    /* Mirror of padding-inline — consumed by `.settings-page` (full-bleed
       via negative margin) so it never extends past `main`'s overflow-clip
       border. Keep in sync with `padding` in every override below. */
    --main-pad-x: 1.5rem;
    max-width: 100%;
    /* `overflow-x: clip` (vs `auto`) : empêche le débordement horizontal
       visuel SANS créer de scroll container. Auto aurait fait de `main`
       l'ancêtre de sticky pour `.list-toolbar` (scroll-aware toolbar) →
       sticky pinné sur main au lieu du viewport, donc inopérant car
       le scroll réel se fait sur window. `clip` garde la défense visuelle
       contre les contenus trop larges (le table-wrap a son propre
       overflow horizontal pour les grandes tables). */
    overflow-x: clip;
}
main.fullbleed {
    padding: 0;
    overflow: hidden;
    /* Use 100% (of .app, which is height:100% thanks to :has above) rather
       than 100vh: `vh` units interact badly with CSS zoom (Ctrl+wheel) and
       with reverse-proxy address bars on mobile. Percentage cascades are
       viewport-accurate at any zoom level. */
    height: 100%;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* Flash / license banner inside a fullbleed main must NOT steal flex-grow
   height — the graph owns the remaining space. */
main.fullbleed > .flash,
main.fullbleed > .license-banner { flex-shrink: 0; }
.page-header {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 1rem; margin-bottom: 0.9rem; padding-bottom: 0.7rem;
    border-bottom: 1px solid var(--border);
}
.page-header h1 {
    margin: 0;
    font-family: var(--font-display);
    font-size: 1.5rem; font-weight: 600;
    letter-spacing: -0.015em; display: flex; align-items: center; gap: 0.55rem;
    line-height: 1.2;
}
.page-header .subtitle {
    font-size: 0.85rem; color: var(--text-muted); margin-top: 0.3rem;
}

/* === Cards ===
   Padding `--space-5` (24px) pour respirer. Bordure très claire +
   ombre douce — sur ivoire chaud, l'élévation seule rendrait les
   cards floues, on garde une bordure 1px subtile. Radius 10px (lg)
   = card "principale", radius standard sur les sous-blocs internes. */
.card {
    background: var(--surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-lg);
    padding: var(--space-5);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--space-4);
}
.card h2 {
    margin: 0 0 1rem;
    /* H2 de card en serif display — chaque section est un titre
       éditorial, pas un label de form. */
    font-family: var(--font-display);
    font-size: 1rem; font-weight: 600;
    color: var(--text); display: flex; align-items: center; gap: 0.5rem;
    letter-spacing: -0.01em;
}
.card h2 svg { width: 16px; height: 16px; color: var(--text-muted); }
.card h2 .count {
    background: var(--border-light); color: var(--text-muted);
    padding: 0.1rem 0.5rem; border-radius: 999px; font-size: 0.75rem;
    font-weight: 600;
}

/* === Tables ===
   13px conservé sur les tableaux denses (data-excel) — la densité reste
   un atout pour les listes longues, c'est le contenu courant qui est
   passé à 14. Zebra warm crème (`--surface-2`) au lieu de `rgba white`
   qui virait au gris froid sur ivoire. */
.table-wrap { overflow-x: auto; margin: -0.4rem -0.4rem; padding: 0.4rem; }
table {
    width: 100%; border-collapse: separate; border-spacing: 0;
    font-size: 0.82rem;
}
th {
    text-align: left; padding: 0.5rem 0.75rem; font-weight: 600;
    font-size: 0.68rem; color: var(--text-muted);
    text-transform: uppercase; letter-spacing: 0.06em;
    /* Sticky header avec tinte navy 6% + filet or 1px en bas — signal
       "tableau d'actuaire" qui démarque le scroll de la zone de
       données. Pas une simple ligne grise. */
    background: linear-gradient(to bottom, rgba(10, 37, 64, 0.04), rgba(10, 37, 64, 0.06));
    border-bottom: 1px solid var(--accent-light);
    box-shadow: inset 0 -1px 0 var(--accent-light);
    white-space: nowrap; position: sticky; top: 0; z-index: 2;
}
th:first-child { border-top-left-radius: var(--radius); }
th:last-child { border-top-right-radius: var(--radius); }
td {
    padding: 0.55rem 0.75rem; border-bottom: 1px solid var(--border-light);
    color: var(--text); vertical-align: middle;
    /* `break-word` only breaks unbreakable tokens (URLs, very long IDs)
       when there's no other choice. The previous `anywhere` broke ANY
       word at ANY character — names became "A / l / o / i / s / e" once
       a column got narrow. */
    overflow-wrap: break-word;
}
/* First column gets a sane minimum so names don't wrap into vertical
   character columns when the viewport is < 1366px. Override per-table
   if a list has a different leading column. */
th:first-child, td:first-child { min-width: 180px; }
/* Zebra warm crème — sur ivoire les rows paires se distinguent sans
   créer la sensation "rayure carbone" du dark theme. */
tbody tr:nth-child(even) td { background: var(--surface-2); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out); }
/* Hover row : tinte or 6% — affordance prestige, ultra-subtile pour
   ne pas surcharger un scan rapide. */
tbody tr:hover td { background: rgba(182, 151, 86, 0.07); }
/* Stronger hover affordance: a left accent bar on rows that link to a
   detail page. `[data-href]` is set by the row-click hydrator in
   `app.js`; rows without it (purely informational) keep no pointer.
   Filet OR (vs primary navy) — signature de la marque sur l'action. */
tbody tr[data-href] { cursor: pointer; }
tbody tr[data-href]:hover td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
td a { color: var(--link); text-decoration: none; font-weight: 500; }
td a:hover { text-decoration: underline; color: var(--link-hover); }

/* === Excel-like table: sort + filter === */
th.th-sortable { cursor: default; user-select: none; }
th.th-sortable .th-content {
    display: flex; align-items: center; gap: 0.4rem;
}
th.th-sortable .th-label {
    cursor: pointer; flex: 1; transition: color 0.1s;
}
th.th-sortable .th-label:hover { color: var(--text); }
/* Tri actif : label en couleur de marque — cohérent avec la flèche
   `.th-sort` déjà bleue. */
th.th-sortable .th-label.is-sort-active {
    color: var(--primary);
}
/* Pour les modes "name" et "person-org", le libellé morphe (Prénom / Nom /
   Entreprise / texte d'origine). Le texte d'origine est conservé en
   `data-orig-label` côté JS et réutilisé ici comme ghost invisible qui
   réserve la largeur originale — la colonne ne rétrécit donc jamais en
   dessous de sa largeur d'origine, même quand le mot actif est plus court. */
th.th-sortable .th-label[data-orig-label] {
    position: relative;
    display: inline-block;
}
th.th-sortable .th-label[data-orig-label]::before {
    content: attr(data-orig-label);
    display: block;
    height: 0;
    overflow: hidden;
    visibility: hidden;
    pointer-events: none;
}
th.th-sortable .th-sort {
    font-size: 0.85rem; color: var(--primary);
    font-weight: 700; min-width: 10px;
    font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
    white-space: nowrap;
    /* Réserve la hauteur du glyphe même quand le contenu est vide,
       sinon la ligne flex du `.th-content` rétrécit de ~2.7px au passage
       trié → non-trié et produit un saut vertical du header. */
    display: inline-block;
    min-height: 1.2em;
    line-height: 1.2;
}
th.th-sortable .th-filter {
    background: transparent; border: 0;
    color: var(--text-light);
    padding: 0.2rem 0.3rem; border-radius: 4px;
    cursor: pointer; line-height: 0;
    box-shadow: none; transition: all 0.12s;
}
th.th-sortable .th-filter:hover {
    background: var(--bg); color: var(--text); transform: none; box-shadow: none;
}
th.th-sortable .th-filter.active {
    color: var(--primary); background: var(--primary-light);
}
th.th-sortable .th-filter svg { width: 12px; height: 12px; }

/* Filter dropdown.
   `fixed` (not `absolute`) — we anchor to the viewport so the JS doesn't
   need scrollX/Y math, and so the post-zoom values from
   getBoundingClientRect line up cleanly with style.left/top after
   dividing by the zoom factor. */
.thf-menu {
    position: fixed; z-index: 1000;
    width: 260px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    font-size: 0.82rem; color: var(--text);
    overflow: hidden;
    animation: thfIn 0.12s ease-out;
}
@keyframes thfIn {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
/* Toggle Prénom / Nom — apparaît uniquement dans les filtres des colonnes
   en mode "name" / "person-org". Bascule la base alpha-tree (clé d'initiale
   + tri intra-groupe) sans toucher au state des cases cochées. */
.thf-name-toggle {
    display: flex; gap: 2px;
    margin: 0.55rem 0.55rem 0;
    padding: 2px;
    background: var(--surface-2);
    border-radius: var(--radius);
}
.thf-name-btn {
    flex: 1;
    background: transparent;
    border: 0;
    padding: 0.32rem 0.55rem;
    border-radius: calc(var(--radius) - 2px);
    color: var(--text-muted);
    font: inherit; font-size: 0.76rem; font-weight: 500;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
    box-shadow: none;
}
.thf-name-btn:hover { color: var(--text); }
.thf-name-btn.is-active {
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-sm);
}
.thf-search { padding: 0.55rem; border-bottom: 1px solid var(--border-light); }
.thf-search input {
    width: 100%; padding: 0.4rem 0.6rem;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--text);
    font-size: 0.82rem; outline: none; font-family: inherit;
}
.thf-search input:focus { border-color: var(--primary); box-shadow: 0 0 0 2px var(--focus-ring); }
.thf-quick, .thf-actions {
    display: flex; align-items: center;
    padding: 0.3rem 0.55rem;
    border-bottom: 1px solid var(--border-light);
    gap: 0.3rem;
}
.thf-actions-sep { flex: 1; }
.thf-link {
    padding: 0.28rem 0.55rem; border-radius: 4px;
    cursor: pointer; font-size: 0.76rem; color: var(--text-muted);
    font-weight: 500; transition: all 0.1s;
    user-select: none;
}
.thf-link:hover { background: var(--bg); color: var(--text); }
.thf-link[data-action="expand"], .thf-link[data-action="collapse"] {
    font-size: 0.95rem; padding: 0.18rem 0.42rem; line-height: 1;
}
.thf-list {
    max-height: 320px; overflow-y: auto; padding: 0.3rem;
}
.thf-item {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.32rem 0.55rem; border-radius: 5px;
    cursor: pointer; font-size: 0.82rem; line-height: 1.3;
    color: var(--text); min-height: 26px; user-select: none;
}
.thf-item:hover { background: var(--bg); }
.thf-item input[type=checkbox] {
    width: 14px; height: 14px; margin: 0; padding: 0;
    accent-color: var(--primary); flex-shrink: 0; cursor: pointer;
}
.thf-item > span:last-child {
    flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.thf-buttons {
    display: flex; gap: 0.4rem; padding: 0.55rem;
    border-top: 1px solid var(--border-light);
}
.thf-buttons button {
    flex: 1; padding: 0.4rem 0.6rem;
    font-size: 0.78rem; font-weight: 600;
    border: 0; border-radius: var(--radius-sm);
    background: var(--primary); color: var(--on-primary);
    cursor: pointer; font-family: inherit;
    transition: all 0.12s; box-shadow: none;
}
.thf-buttons button:hover { background: var(--primary-hover); transform: none; box-shadow: none; }
.thf-buttons .ghost {
    background: transparent; color: var(--text-muted);
    border: 1px solid var(--border);
}
.thf-buttons .ghost:hover { background: var(--bg); color: var(--text); }

/* Hierarchical filter (dates, places, alphabet…) */
.thf-group + .thf-group { margin-top: 1px; }

.thf-branch { align-items: center; font-weight: 500; }
.thf-branch > .thf-branch-main {
    display: flex; align-items: center; gap: 0.4rem;
    flex: 1; min-width: 0;
    overflow: visible; white-space: normal;
}
.thf-branch-label {
    flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--text);
}
.thf-year-group  > .thf-branch,
.thf-place-group > .thf-branch,
.thf-alpha-group > .thf-branch {
    font-weight: 600; font-size: 0.84rem;
}
.thf-month-group > .thf-branch {
    font-weight: 500; font-size: 0.8rem;
}

.thf-caret {
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; flex-shrink: 0;
    color: var(--text-muted); cursor: pointer;
    font-size: 0.62rem; border-radius: 3px;
    transition: background 0.1s, color 0.1s, transform 0.12s ease;
}
.thf-caret:hover { background: var(--border); color: var(--text); }

.thf-children {
    padding-left: 0.75rem;
    margin-left: 0.55rem;
    border-left: 1px solid var(--border-light);
    margin-top: 1px; padding-bottom: 2px;
}

.thf-leaf {
    color: var(--text-muted); font-weight: 400;
    font-size: 0.8rem;
}
.thf-leaf:hover { color: var(--text); background: var(--bg); }

/* Counter (pill badge) on a group's branch */
.thf-badge {
    flex-shrink: 0;
    font-size: 0.68rem; font-weight: 600;
    font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
    color: var(--text-muted); background: var(--bg);
    border: 1px solid var(--border-light); border-radius: 999px;
    padding: 1px 7px; line-height: 1.4;
    min-width: 1.4rem; text-align: center;
    margin-left: auto;
}
.thf-badge-partial {
    color: var(--primary); background: var(--primary-light);
    border-color: transparent;
}
.thf-badge-zero {
    color: var(--text-light); background: transparent;
    border-style: dashed;
}

/* Leftover: old "muted" helper in a cell (e.g. "Aucun résultat") */
.thf-item .muted { font-size: 0.72rem; opacity: 0.75; }

.empty {
    color: var(--text-muted); text-align: center;
    padding: 2.5rem 1.5rem !important;
    font-size: 0.92rem; line-height: 1.6;
}
.empty .empty-cta {
    display: inline-flex; align-items: center; gap: 0.4rem;
    margin-top: 0.6rem;
    color: var(--primary); font-weight: 600; text-decoration: none;
    padding: 0.4rem 0.85rem;
    background: var(--primary-light);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    transition: background 0.15s, border-color 0.15s;
}
.empty .empty-cta:hover {
    background: var(--primary); color: var(--on-primary);
}
.empty-icon {
    display: block; margin: 0 auto 0.6rem;
    width: 32px; height: 32px;
    color: var(--text-light); opacity: 0.6;
}

/* === Buttons ===
   Default = navy plein (autorité). Accent = or champagne (CTA prestige
   genre « Convertir en client »). Secondary = surface bordée (action
   secondaire). Ghost = texte seul. Danger = bordeaux. */
.btn, button {
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: var(--primary); color: var(--on-primary);
    padding: 0.45rem 0.9rem; border-radius: var(--radius);
    text-decoration: none; border: 0; cursor: pointer;
    font-size: 0.82rem; font-weight: 500; font-family: inherit;
    letter-spacing: 0.01em;
    transition: background var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
    box-shadow: var(--shadow-sm);
    line-height: 1.1;
}
/* No `transform: translateY(-1px)` on hover/active — the 1 px lift moves
   the hit-area out from under the cursor when the pointer sits on the
   bottom edge, causing a hover→un-hover→hover flicker loop. The shadow
   bump is enough feedback. */
.btn:hover, button:hover { background: var(--primary-hover); box-shadow: var(--shadow); }
.btn:active, button:active { background: var(--primary-active); box-shadow: var(--shadow-sm); }
.btn:focus-visible, button:focus-visible {
    outline: none;
    box-shadow: var(--shadow-sm), 0 0 0 3px var(--focus-ring);
}
.btn svg { width: 14px; height: 14px; }
.btn.secondary {
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border);
    box-shadow: none;
}
.btn.secondary:hover { background: var(--surface-2); border-color: var(--border-strong); box-shadow: none; }
.btn.ghost { background: transparent; color: var(--text-muted); box-shadow: none; }
.btn.ghost:hover { background: var(--surface-2); color: var(--text); box-shadow: none; }
/* === Accent button (or champagne) ===
   Réservé aux CTAs prestige : "Convertir en client", "Signer", "Valider
   l'offre", "Inviter au gala". Sur ivoire le contraste or/encre suffit ;
   en dark le ratio reste ≥ 4.5:1 sur le texte encre noir. */
.btn.accent {
    background: var(--accent);
    color: var(--on-accent);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05), var(--shadow-sm);
}
.btn.accent:hover { background: var(--accent-hover); }
.btn.accent:active { background: var(--accent-active); }
.btn.sm { padding: 0.35rem 0.7rem; font-size: 0.78rem; }
.btn.danger { background: var(--danger); }
.btn.danger:hover { background: var(--danger); filter: brightness(1.1); }
.btn.danger.ghost { background: transparent; color: var(--danger); box-shadow: none; filter: none; }
.btn.danger.ghost:hover { background: var(--danger-light); color: var(--danger); }
.btn.icon { padding: 0.4rem; line-height: 0; }
.btn.icon svg { width: 14px; height: 14px; margin: 0; }

/* Inline delete form (icon button in table row) */
.delete-form { display: inline; margin: 0; }
.delete-form button {
    background: transparent; color: var(--text-light); border: 0;
    padding: 0.25rem 0.4rem; border-radius: 4px; cursor: pointer;
    box-shadow: none; line-height: 0;
}
.delete-form button:hover { background: var(--danger-light); color: var(--danger); transform: none; }
.delete-form svg { width: 14px; height: 14px; }

.actions-bar { display: flex; gap: 0.5rem; margin-bottom: 1.25rem; flex-wrap: wrap; }

/* === Forms === */
form .row { margin-bottom: 1.1rem; }
form label {
    display: block;
    /* Labels en uppercase muted — signal "field" sans agressivité,
       convention Private Banking (cf. fiches client papier de cabinets). */
    font-size: 0.7rem; color: var(--text-muted);
    margin-bottom: 0.4rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.04em;
}
form label:has(input[type=checkbox]) {
    display: flex; align-items: center; gap: 0.5rem; cursor: pointer;
    /* Override pour les checkboxes : pas d'uppercase, lecture naturelle */
    text-transform: none; letter-spacing: 0; font-size: 0.85rem;
    font-weight: 500; color: var(--text);
}
form input:not([type=checkbox]), form select, form textarea {
    width: 100%; padding: 0.6rem 0.85rem;
    border: 1px solid var(--border); border-radius: var(--radius);
    font-size: 0.9rem; font-family: inherit; color: var(--text);
    background: var(--surface);
    transition: border-color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out),
                background-color var(--duration-fast) var(--ease-out);
}
form select {
    appearance: none; -webkit-appearance: none;
    padding-right: 2rem; cursor: pointer;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='%238b8fa3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/></svg>");
    background-repeat: no-repeat;
    background-position: right 0.7rem center;
}
form select::-ms-expand { display: none; }
form input::placeholder, form textarea::placeholder { color: var(--text-light); }
form input:not([type=checkbox]):focus-visible, form select:focus-visible, form textarea:focus-visible {
    /* Focus ring or — signature de la marque. Le navy serait redondant
       avec la bordure ; l'or signale l'interaction sans alourdir. */
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
    background-color: var(--surface);
}
form textarea { min-height: 90px; resize: vertical; }
form .actions { display: flex; gap: 0.5rem; margin-top: 1.25rem; }

.search {
    display: flex; gap: 0.5rem; margin-bottom: 0.7rem; align-items: center;
    /* Discret : surface-2 (un cran plus discret que --surface), bordure
       light, pas d'ombre. Le pill doit chuchoter « recherche » sans
       voler la vedette au tableau. */
    background: var(--surface-2); padding: 0.35rem 0.75rem;
    border-radius: var(--radius); border: 1px solid var(--border-light);
    transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
}
.search:hover { background: var(--surface); border-color: var(--border); }
.search:focus-within {
    background: var(--surface);
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.search-spinner {
    width: 14px; height: 14px;
    border: 2px solid var(--border);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: search-spin 0.7s linear infinite;
    flex-shrink: 0;
    margin-right: 0.2rem;
}
.search-spinner[hidden] { display: none; }
@keyframes search-spin { to { transform: rotate(360deg); } }

/* Skeleton loader pattern — call by adding `.skeleton` to any block-level
   element. The shimmer animation hints "loading…" without a fake spinner.
   Used for table row placeholders rendered during initial paint of long
   lists, or for any content waiting on a fetch. */
.skeleton {
    position: relative;
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    overflow: hidden;
    color: transparent !important;
    user-select: none;
}
.skeleton::after {
    content: "";
    position: absolute; inset: 0;
    background: linear-gradient(90deg,
        transparent,
        rgba(255, 255, 255, 0.06),
        transparent);
    animation: skeleton-shimmer 1.4s linear infinite;
}
@keyframes skeleton-shimmer {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}
.search svg { width: 14px; height: 14px; color: var(--text-muted); flex-shrink: 0; }
.search input {
    flex: 1; padding: 0.25rem 0.2rem; border: 0;
    background: transparent; font-size: 0.85rem;
    color: var(--text); font-family: inherit;
    line-height: 1.4;
}
.search input::placeholder { color: var(--text-light); }
.search input:focus { outline: none; box-shadow: none; }
.search .hint {
    font-size: 0.75rem; color: var(--text-light);
    padding: 0.15rem 0.5rem; border: 1px solid var(--border);
    border-radius: 4px; flex-shrink: 0; font-family: ui-monospace, monospace;
}
.search-count {
    color: var(--text-muted); font-size: 0.8rem;
    padding: 0.15rem 0.55rem; background: var(--border-light);
    border-radius: 999px; flex-shrink: 0;
}
/* Free-text search filter on data-excel tables — !important to beat the
   inline display:"" that setupExcelTable writes after each filter pass.
   Cumulable avec le filtre Excel par colonne. */
tr.search-hide,
tr.filter-hide { display: none !important; }

/* === Tags / Badges ===
   Tags arrondis avec un point de couleur. Default = navy tinte +
   navy texte. Variants pipeline ajoutées : `.success` (won = or),
   `.info` (en cours = navy), `.danger` (perdu = bordeaux), `.warn`
   (pending = ambre), `.muted` (neutre archive). */
.tag {
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: var(--primary-pale); color: var(--primary);
    padding: 0.2rem 0.6rem; border-radius: 999px;
    font-size: 0.72rem; font-weight: 600; line-height: 1.4;
    letter-spacing: 0.01em;
}
.tag.warn    { background: var(--warning-pale); color: var(--warning); }
.tag.muted   { background: var(--border-light); color: var(--text-muted); }
.tag.danger  { background: var(--danger-pale); color: var(--danger); }
.tag.info    { background: var(--primary-pale); color: var(--primary); }
/* Variant "won" : remplace le vert success par l'or — convention
   Private Banking où la signature de mandat est un signal de
   prestige, pas un check vert SaaS. Le pill .success sur les pipelines
   prospects → clients passe en or. */
.tag.success {
    background: var(--accent-pale); color: var(--accent-active);
}
.tag::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%;
    background: currentColor; opacity: 0.7;
    flex-shrink: 0;   /* keep the dot at 6×6 even when the badge is squeezed
                         against its container's max-width — text gets ellipsized
                         instead. Was collapsing to 0 on long labels in /admin/types. */
}

/* === Avatar ===
   Initiales en serif display — petit signal "trad financière" qui
   rapproche d'une plaque gravée plutôt que d'un emoji rond SaaS.
   Default = dégradé navy (clients/contacts), .collab = dégradé or
   (collaborateurs internes — différenciation immédiate). */
.avatar {
    width: 28px; height: 28px; border-radius: 50%;
    background: linear-gradient(135deg, #1F3A5C, #0A2540);
    color: var(--on-primary);
    font-family: var(--font-display);
    font-weight: 600; font-size: 0.78rem;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    letter-spacing: 0.01em;
}
.avatar.lg { width: 44px; height: 44px; font-size: 1.1rem; }
.avatar.collab {
    background: linear-gradient(135deg, var(--accent-hover), var(--accent));
    color: var(--on-accent);
}

.cell-person { display: inline-flex; align-items: center; gap: 0.45rem; max-width: 100%; }
.cell-person .avatar { width: 20px; height: 20px; font-size: 0.6rem; }
/* Keep the name on a single line and clip with ellipsis instead of
   breaking per-character when the column is narrow. The cell-level
   `overflow-wrap: break-word` was still letting names break inside the
   inline-flex shrink space — `white-space: nowrap` here wins. */
.cell-person { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* === Stats grid (header info) === */
.stats {
    display: grid; gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    margin-bottom: 1.25rem;
}
.stat {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); padding: 1rem 1.15rem;
    box-shadow: var(--shadow-sm);
}
.stat .label {
    font-size: 0.72rem; color: var(--text-muted);
    text-transform: uppercase; letter-spacing: 0.04em;
    font-weight: 600; margin-bottom: 0.4rem;
}
.stat .value {
    /* Valeurs KPI en serif display + tabular nums — alignement
       d'actuaire, lisibilité chiffres. */
    font-family: var(--font-display);
    font-size: 1.5rem; font-weight: 600; color: var(--text);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}
.stat .value a { color: var(--text); text-decoration: none; }
.stat .value a:hover { color: var(--primary); }
.stat.compact .value { font-size: 0.95rem; font-weight: 500; }

/* === Flash messages === */
/* Flash messages — three categories injected by app.py's wrapper flash()
   based on message content. Success = green check, error = red triangle,
   info = blue circle. The base .flash rule keeps layout properties; colors
   come from the per-category modifier class. */
/* Toast-style flash stack — fixed overlay anchored top-center.
   Each flash slides in from above on mount, auto-dismisses after
   3.5s with a slide-up exit animation (managed by setupFlashes()
   in app.js). The stack itself is pointer-events:none so clicks pass
   through to the page; individual flashes re-enable pointer events. */
.flash-stack {
    position: fixed;
    top: 1rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1200;
    display: flex; flex-direction: column;
    gap: 0.5rem;
    pointer-events: none;
    width: max-content; max-width: calc(100vw - 2rem);
}
.flash {
    pointer-events: auto;
    padding: 0.75rem 1.15rem;
    border-radius: var(--radius);
    font-size: 0.875rem; font-weight: 500;
    display: flex; align-items: center; gap: 0.55rem;
    border: 1px solid transparent;
    background: var(--surface);
    box-shadow: var(--shadow-lg);
    animation: flash-in 0.22s ease-out;
}
.flash svg { flex-shrink: 0; }
.flash.is-leaving {
    animation: flash-out 0.28s ease-in forwards;
}
/* Toasts en surface blanche + filet coloré gauche — moins agressif que
   du fond saturé, plus présentable en démo client. */
.flash-success {
    color: var(--success);
    border-left: 3px solid var(--success);
    background: var(--success-pale);
}
.flash-error {
    color: var(--danger);
    border-left: 3px solid var(--danger);
    background: var(--danger-pale);
}
.flash-info {
    color: var(--primary);
    border-left: 3px solid var(--accent);
    background: var(--surface);
}
@keyframes flash-in {
    from { opacity: 0; transform: translateY(-14px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes flash-out {
    from { opacity: 1; transform: translateY(0); }
    to   { opacity: 0; transform: translateY(-14px); }
}

/* === Hero (detail page header) ===
   Plaque de présentation de la fiche : avatar large, titre serif, meta
   discret. Filet or 2px en haut comme un en-tête de page de garde. */
.hero {
    display: flex; align-items: center; gap: 1.25rem;
    padding: var(--space-5);
    background: var(--surface);
    border: 1px solid var(--border-light);
    border-top: 2px solid var(--accent);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--space-4);
}
.hero-info { flex: 1; min-width: 0; }
.hero-info h1 {
    /* Titre serif display — la fiche client est un document, pas un
       label d'écran. */
    font-family: var(--font-display);
    font-size: 1.55rem; font-weight: 600; margin: 0 0 0.4rem;
    letter-spacing: -0.015em; display: flex; align-items: center;
    gap: 0.6rem; line-height: 1.2;
}
.hero-info .meta {
    color: var(--text-muted); font-size: 0.82rem;
    display: flex; gap: 1rem; flex-wrap: wrap;
}
.hero-info .meta span { display: inline-flex; align-items: center; gap: 0.3rem; }
.hero-actions { display: flex; gap: 0.4rem; flex-shrink: 0; flex-wrap: wrap; }

.muted { color: var(--text-muted); font-size: 0.78rem; }

/* Color swatch (connections) */
.swatch {
    display: inline-block; width: 14px; height: 14px;
    border-radius: 4px; vertical-align: middle; margin-right: 0.35rem;
    border: 1px solid rgba(0,0,0,0.1);
}

/* Kind pill (organization / event types) */
.kind-pill {
    display: inline-block; padding: 0.12rem 0.55rem;
    border-radius: 999px; font-size: 0.7rem; font-weight: 600;
    color: #fff; line-height: 1.5; vertical-align: middle;
}

/* Direction marker (in/out) */
.dir-in  { color: var(--success); font-weight: 600; }
.dir-out { color: var(--primary); font-weight: 600; }

/* Compact info grid (header summaries) */
.info-grid {
    display: grid; gap: 0.8rem;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.info-grid .label {
    font-size: 0.7rem; color: var(--text-muted); font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 0.2rem;
}
.info-grid .value { font-size: 0.92rem; color: var(--text); }
.info-grid .value a { color: var(--link); text-decoration: none; }
.info-grid .value a:hover { text-decoration: underline; color: var(--link-hover); }

/* Catch-all for inline `<div class="value">` blocks used outside the
   info-grid (e.g. LinkedIn link on persons/detail, website on
   organizations/detail). Without this they fall back to the default
   blue/violet user-agent link colors, which ignore the theme. */
.value > a:not(.btn) { color: var(--link); text-decoration: none; }
.value > a:not(.btn):hover { text-decoration: underline; color: var(--link-hover); }

/* Section divider in pages */
.section-title {
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-muted); font-weight: 600; margin: 1.5rem 0 0.6rem;
}

/* Color picker inline */
.color-cell {
    display: inline-block; width: 18px; height: 18px;
    border-radius: 4px; vertical-align: middle; margin-right: 0.4rem;
    border: 1px solid rgba(0,0,0,0.1);
}

/* Inline alias add form */
.inline-form {
    display: flex; gap: 0.5rem; margin-top: 1rem;
    padding-top: 1rem; border-top: 1px solid var(--border-light);
}
.inline-form input { flex: 1; padding: 0.45rem 0.7rem; }
.inline-form button { flex-shrink: 0; }

/* Breadcrumb */
.crumbs {
    font-size: 0.8rem; color: var(--text-muted);
    margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.4rem;
}
.crumbs a { color: var(--text-muted); text-decoration: none; }
.crumbs a:hover { color: var(--primary); }
.crumbs .sep { color: var(--text-light); }

/* Responsive */
@media (max-width: 900px) {
    .sidebar { width: 64px; padding-top: 1rem; }
    .sidebar .brand span:not(.logo) { display: none; }
    .sidebar nav a span { display: none; }
    .sidebar .nav-label { display: none; }
    main { padding: 1.25rem; }
}

/* =============================================================================
   Sidebar resize — drag the right edge to change width.
   The width comes from the `--sidebar-w` CSS var on <html> (set by JS at
   drag end + read pre-paint by the inline head script for anti-FOUC).
   Default falls back to 240 px when the var isn't set. When the width
   drops below the container-query threshold, all text labels are hidden
   and the sidebar becomes icon-only — no separate toggle needed.
   ============================================================================= */
.sidebar {
    /* Override the static `width: 240px` from the base rule above. */
    width: var(--sidebar-w, 240px);
    /* container-type unlocks @container queries on the sidebar's
       inline-size — we use this further down to react to width without
       any JS-applied class. The base rule already gives `position:
       sticky` which doubles as a positioning context for the absolute
       resize handle, so no `position: relative` override needed. */
    container-type: inline-size;
    container-name: sidebar;
}
/* The width itself only animates outside an active drag — drag should
   feel direct. The `.sidebar-resizing` class is added by JS during
   mousemove and disables the transition. */
.sidebar:not(.sidebar-resizing) { transition: background 0.2s, color 0.2s, width 0.2s ease; }

/* Drag handle: 6 px strip living BETWEEN the sidebar and main, sitting
   exactly on the boundary (centered on it via the negative margin so the
   visible 3 px on each side feels symmetric). Sticky so it follows when
   the page scrolls. NOT inside the sidebar — keeping it outside avoids
   the `overflow-y: auto` scrollbar collision. Highlights on hover/drag
   with a primary tint, same visual language as the graph dashboard. */
.sidebar-resize {
    flex-shrink: 0;
    position: sticky;
    top: 0;
    height: 100vh;
    height: 100dvh;
    height: calc(100dvh / var(--zoom, 1));
    width: 6px;
    /* Centered on the sidebar/main boundary: -3 px on each side pulls
       both neighbours toward the handle so its visible 6 px straddle
       the boundary symmetrically (3 px over the sidebar, 3 px over
       main), instead of biting only into one of them. */
    margin-left: -3px;
    margin-right: -3px;
    cursor: col-resize;
    z-index: 10;
    background: transparent;
    transition: background 0.15s;
}
.sidebar-resize:hover,
.sidebar.sidebar-resizing ~ .sidebar-resize {
    background: var(--primary-light);
}

/* === Compact mode (sidebar narrower than the threshold) =================
   Triggered automatically by the container query — no JS class needed.
   The threshold (140 px) is just above what's required to fit the brand
   "CRM" label alongside the logo without overflow. Below that, hiding
   labels gives a clean icon-only column. */
@container sidebar (max-width: 140px) {
    .sidebar .brand span:not(.logo),
    .sidebar nav a span,
    .sidebar .nav-label,
    .sidebar .ws-switcher-btn span,
    .sidebar-foot-label,
    .sidebar-user-name { display: none; }

    .sidebar .brand { justify-content: center; padding: 0; }
    /* Keep the separator below the brand visible in compact mode -
       otherwise the ws-switcher visually sticks to the logo (the bar
       above the workspace must remain). */
    .sidebar .brand::after { left: 0.4rem; right: 0.4rem; }

    .sidebar .ws-switcher-btn { justify-content: center; padding: 0.5rem; }

    .sidebar nav a {
        justify-content: center;
        padding: 0.6rem;
        gap: 0;
    }

    .sidebar-foot {
        justify-content: center;
        padding: 0.7rem 0.4rem;
    }

    .sidebar-user {
        justify-content: center;
        padding: 0.7rem 0.4rem;
    }
    .sidebar-user .sidebar-logout-form { display: none; }
    .sidebar-user-link {
        justify-content: center;
        padding: 0; gap: 0;
    }
}

/* =============================================================================
   Multi-select picker (templates/_macros.html → multi_select)
   ============================================================================= */
.ms-picker {
    background: var(--bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
    display: flex; flex-direction: column;
}
.ms-picker:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--focus-ring); }

.ms-chips {
    display: flex; flex-wrap: wrap; gap: 0.35rem;
    padding: 0.5rem 0.6rem;
    border-bottom: 1px solid var(--border-light);
    min-height: 1.4rem;
}
.ms-chips:empty::before {
    content: "Aucune sélection";
    color: var(--text-light); font-size: 0.78rem; font-style: italic;
}
.ms-chip {
    display: inline-flex; align-items: center; gap: 0.3rem;
    background: var(--primary-light); color: var(--primary);
    border-radius: 999px; padding: 0.18rem 0.55rem 0.18rem 0.7rem;
    font-size: 0.78rem; font-weight: 500;
}
.ms-chip button {
    background: transparent; border: 0; cursor: pointer;
    color: var(--primary); padding: 0; line-height: 1;
    font-size: 1rem; opacity: 0.7; transition: opacity 0.1s;
}
.ms-chip button:hover { opacity: 1; }

.ms-search {
    border: 0; border-bottom: 1px solid var(--border-light);
    padding: 0.5rem 0.7rem; font-size: 0.85rem;
    background: var(--surface); color: var(--text);
    width: 100%; box-sizing: border-box;
}
.ms-search:focus { outline: none; background: var(--bg); }

.ms-list {
    max-height: 220px; overflow-y: auto;
    background: var(--surface);
}
.ms-item {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.4rem 0.7rem; cursor: pointer;
    font-size: 0.85rem; transition: background 0.1s;
}
.ms-item:hover { background: var(--surface-2); }
.ms-item input { width: auto; margin: 0; accent-color: var(--primary); }
.ms-item.ms-hidden { display: none; }
.ms-empty {
    padding: 0.8rem 0.7rem; color: var(--text-muted);
    font-size: 0.82rem; font-style: italic;
}

.ms-footer {
    display: flex; justify-content: space-between; align-items: center;
    padding: 0.4rem 0.7rem;
    border-top: 1px solid var(--border-light);
    background: var(--bg);
    font-size: 0.74rem; color: var(--text-muted);
}
.ms-footer .ms-count { font-weight: 600; }
.ms-footer .ms-clear {
    background: transparent; border: 0; cursor: pointer;
    color: var(--text-muted); font-size: 0.74rem; padding: 0.1rem 0.3rem;
    border-radius: var(--radius-sm); transition: color 0.1s;
}
.ms-footer .ms-clear:hover { color: var(--danger, #dc2626); }
.ms-footer .ms-clear:disabled { opacity: 0.4; cursor: default; }

/* =============================================================================
   Single-select picker (templates/_macros.html → single_select)
   Used as the consistent replacement for native <select> on person/entity
   pickers. Visual reference is the .ms-picker family above so the two
   widgets read as siblings.
   ============================================================================= */
.ss-picker { position: relative; width: 100%; }
.ss-picker .ss-button {
    width: 100%; text-align: left;
    background: var(--bg); color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.55rem 2.1rem 0.55rem 0.8rem;
    font-size: 0.9rem; font-family: inherit;
    /* Match the native form-input rendered height. The global `.btn, button`
       rule sets line-height: 1 for action buttons, which would make this
       picker ~6 px shorter than the <input>/<textarea> next to it on the
       same form row. Inheriting from body (1.5) lines them up. */
    line-height: 1.5;
    cursor: pointer;
    box-shadow: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='%238b8fa3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/></svg>");
    background-repeat: no-repeat;
    background-position: right 0.7rem center;
    transition: border-color 0.15s, box-shadow 0.15s;
}
.ss-picker .ss-button:hover { border-color: var(--text-muted); }
.ss-picker .ss-button.placeholder { color: var(--text-light); }
.ss-picker.open .ss-button {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
    background-color: var(--surface);
}
.ss-panel {
    position: absolute; top: calc(100% + 4px); left: 0; right: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    z-index: 100;
    display: none;
    overflow: hidden;
}
.ss-picker.open .ss-panel { display: flex; flex-direction: column; }
.ss-search {
    border: 0; border-bottom: 1px solid var(--border-light);
    padding: 0.55rem 0.7rem; font-size: 0.85rem;
    background: var(--bg); color: var(--text);
    width: 100%; box-sizing: border-box;
}
.ss-search:focus { outline: none; }
.ss-list { max-height: 16rem; overflow-y: auto; }
.ss-item {
    padding: 0.45rem 0.7rem;
    cursor: pointer;
    font-size: 0.85rem; line-height: 1.3;
    transition: background 0.05s;
    display: flex; align-items: center; gap: 0.5rem;
    color: var(--text);
}
.ss-item.ss-hidden, .ss-item.ss-cascade-hidden { display: none; }
.ss-item:hover, .ss-item.active { background: var(--surface-2); }
.ss-item.selected { color: var(--primary); font-weight: 500; }
/* Synthetic "+ Créer…" entries (extra_items in single_select macro) get
   a subtle visual treatment so they read as an action, not a regular row.
   Pinned to the bottom of the scrollable list via `position: sticky` so
   the user can always see / click it — including when their search query
   matches nothing. The opaque background prevents underlying items from
   bleeding through during scroll. */
.ss-item.ss-item-new {
    position: sticky;
    bottom: 0;
    background: var(--surface);
    color: var(--primary);
    border-top: 1px solid var(--border-light);
    font-weight: 500;
    z-index: 1;
}
.ss-item.ss-item-new:first-child { border-top: 0; }
.ss-item .ss-badge {
    flex-shrink: 0;
    font-size: 0.66rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.04em;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    background: var(--surface-2); color: var(--text-muted);
}
.ss-item .ss-label {
    flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ss-empty {
    padding: 0.6rem 0.75rem;
    color: var(--text-muted);
    font-size: 0.82rem; font-style: italic;
}

/* === Person picker (meeting / Communication forms) =======================
   Markup generated JS-side from the person_picker macro. Structure:
     .person-picker
       .pp-search-wrap > input.pp-search + .pp-results
       .pp-cards
         .pp-card (per person)
           .pp-head    : name + × button
           .pp-role    : label + role select
           .pp-new     : "New role" panel (hidden by default)
             .pp-sec   : sections (Organisation / Position / Period / Contact)   */

.person-picker { display: flex; flex-direction: column; gap: 0.7rem; }

/* --- Search + autocomplete --- */
.pp-search-wrap { position: relative; }
.pp-search {
    width: 100%; padding: 0.55rem 0.85rem;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--text);
    font-size: 0.9rem; font-family: inherit; outline: none;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.pp-search:focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-light);
}
.pp-results {
    position: absolute; left: 0; right: 0; top: 100%;
    margin-top: 4px; z-index: 50;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
    max-height: 300px; overflow-y: auto; padding: 0.25rem;
    animation: thfIn 0.1s ease-out;
}
.pp-result {
    display: flex; flex-direction: column; gap: 2px;
    padding: 0.5rem 0.7rem; border-radius: 4px;
    cursor: pointer; font-size: 0.86rem;
}
.pp-result:hover { background: var(--surface-2); }
.pp-result-name { color: var(--text); font-weight: 500; }
.pp-result-sub  { color: var(--text-muted); font-size: 0.76rem; }
.pp-no-match {
    padding: 0.6rem 0.7rem; color: var(--text-muted);
    font-style: italic; cursor: default;
}

/* --- Cards (one per person) --- */
.pp-cards { display: flex; flex-direction: column; gap: 0.55rem; }
.pp-card {
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); padding: 0.7rem 0.85rem;
    display: flex; flex-direction: column; gap: 0.55rem;
    transition: border-color 0.15s, background 0.15s;
}
.pp-card-new {
    border-color: var(--primary);
    background: var(--surface);
    box-shadow: 0 0 0 1px var(--primary-light);
}

.pp-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.5rem;
}
.pp-name {
    color: var(--text); text-decoration: none;
    font-weight: 600; font-size: 0.95rem;
}
.pp-name:hover { color: var(--primary); text-decoration: underline; }
.pp-remove {
    width: 28px; height: 28px; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0; color: var(--text-muted);
    border-radius: 4px; cursor: pointer; padding: 0;
    line-height: 0; box-shadow: none;
    transition: background 0.12s, color 0.12s;
}
.pp-remove svg { width: 14px; height: 14px; }
.pp-remove:hover {
    background: var(--danger-light); color: var(--danger); transform: none;
}

/* --- Role select (always visible on the card) --- */
.pp-role {
    display: flex; align-items: center; gap: 0.7rem;
}
.pp-role-lbl {
    flex-shrink: 0;
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-muted); font-weight: 700;
}
.pp-role-sel { flex: 1; min-width: 0; }

/* --- "New role" panel (hidden by default) --- */
.pp-new[hidden] { display: none !important; }
.pp-new {
    margin: 0; padding: 0.85rem 1rem;
    background: var(--bg); border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    display: flex; flex-direction: column; gap: 0.85rem;
}
.pp-new legend {
    padding: 0 0.4rem;
    font-size: 0.82rem; color: var(--text);
}
.pp-new legend strong { color: var(--primary); }

/* Internal sections */
.pp-sec { display: flex; flex-direction: column; gap: 0.45rem; }
.pp-sec-lbl {
    font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.07em;
    color: var(--text-muted); font-weight: 700;
    padding-bottom: 0.15rem;
    border-bottom: 1px solid var(--border-light);
}
.pp-sec-opt {
    text-transform: none; letter-spacing: 0;
    font-weight: 400; font-size: 0.78rem; color: var(--text-light);
}

/* Field grid (3 layouts: 2-col, title+type, period) */
.pp-row {
    display: grid; gap: 0.55rem;
}
.pp-row-2      { grid-template-columns: 1fr 1fr; }
.pp-row-titre  { grid-template-columns: 2fr 1fr; }
.pp-row-period { grid-template-columns: 130px 130px 1fr; align-items: end; }
.pp-row-ville  { grid-template-columns: 2fr 1fr; }

/* Stacked labels (thin title above the field) */
.pp-sec label {
    display: flex; flex-direction: column; gap: 0.25rem;
    font-size: 0.78rem; color: var(--text-muted); font-weight: 500;
    min-width: 0;
}
.pp-fld {
    display: inline-flex; align-items: center; gap: 0.3rem;
    color: var(--text-muted); font-weight: 600;
    font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.04em;
}
.pp-req { color: var(--danger); font-style: normal; font-weight: 700; }

/* Inputs / selects in the new-role panel */
.pp-sec input,
.pp-sec select {
    width: 100%;
    padding: 0.45rem 0.6rem; font-size: 0.86rem;
}

/* "Current" checkbox: horizontal align with label on the right */
.pp-check {
    flex-direction: row !important;
    align-items: center !important;
    gap: 0.5rem !important;
    color: var(--text) !important;
    font-weight: 500 !important;
    font-size: 0.84rem !important;
    align-self: end;
    padding-bottom: 0.45rem;
    cursor: pointer;
}
.pp-check input[type=checkbox] {
    width: auto !important;
    margin: 0;
    accent-color: var(--primary);
}

/* Help note at the bottom of the panel */
.pp-note {
    margin: 0; padding: 0.5rem 0.7rem;
    font-size: 0.78rem; color: var(--text-muted);
    background: var(--surface-2); border-left: 2px solid var(--primary);
    border-radius: 4px; line-height: 1.4;
}

/* Inline hint in a field label (e.g. "Lieu (pays/ville principale)") */
.pp-fld-hint {
    text-transform: none; letter-spacing: 0;
    font-weight: 400; font-size: 0.7rem; color: var(--text-light);
}

/* "+ Create new address" sub-panel — nested inside the Org section */
.pp-newloc[hidden] { display: none !important; }
.pp-newloc {
    margin-top: 0.55rem;
    padding: 0.7rem 0.8rem;
    background: var(--surface-2);
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    display: flex; flex-direction: column; gap: 0.5rem;
}
.pp-newloc-title {
    font-size: 0.76rem; color: var(--text);
    padding-bottom: 0.35rem;
    border-bottom: 1px solid var(--border-light);
}
.pp-newloc-title strong { color: var(--primary); }

/* === "+N persons" popover on multi-person cell badges === */
.tag.muted.person-more {
    cursor: help;
    transition: background 0.1s, color 0.1s, border-color 0.1s;
}
.tag.muted.person-more:hover {
    background: var(--primary-light);
    color: var(--primary);
    border-color: transparent;
}
.person-more-pop {
    /* `fixed` (not `absolute`) lets us use viewport coords from
       getBoundingClientRect directly, no scrollX/Y math needed — important
       for correctness when CSS zoom is applied on <html>. */
    position: fixed; z-index: 1100;
    min-width: 200px; max-width: 360px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
    padding: 0.3rem;
    font-size: 0.82rem;
    animation: thfIn 0.12s ease-out;
}
.pmp-row {
    display: flex; align-items: center; gap: 0.35rem;
    padding: 0.34rem 0.6rem;
    border-radius: 4px;
    overflow: hidden; white-space: nowrap;
    transition: background 0.1s;
}
.pmp-row:hover { background: var(--surface-2); }
.pmp-name {
    color: var(--text); text-decoration: none; font-weight: 500;
    overflow: hidden; text-overflow: ellipsis;
    flex-shrink: 1; min-width: 0;
}
.pmp-name:hover { color: var(--primary); text-decoration: underline; }
.pmp-org {
    color: var(--text-muted); text-decoration: none;
    overflow: hidden; text-overflow: ellipsis;
    flex-shrink: 1; min-width: 0; font-size: 0.78rem;
}
.pmp-org:hover { color: var(--primary); text-decoration: underline; }
.pmp-sep { color: var(--text-light); flex-shrink: 0; }

/* Inline company link in a list cell (meetings, comms) */
.cell-org-link {
    color: var(--text-muted); text-decoration: none; font-size: 0.85em;
    transition: color 0.1s;
}
.cell-org-link:hover { color: var(--primary); text-decoration: underline; }
.cell-org-sep { color: var(--text-light); margin: 0 0.05rem; }


/* === Mini neighbor graph (embedded on entity detail pages) ============== */
.mini-graph-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md, 8px);
    padding: 0.7rem 0.9rem;
    width: 320px;
    flex-shrink: 0;
    box-shadow: var(--shadow-sm, 0 1px 2px rgba(0,0,0,0.08));
    display: flex;
    flex-direction: column;
    position: relative;
}
.mini-graph-fs-btn {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    padding: 0.25rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    z-index: 2;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.mini-graph-fs-btn:hover {
    color: var(--text);
    border-color: var(--primary);
    background: var(--surface-2);
}
.mini-graph {
    width: 100%;
    flex: 1;
    display: flex;
    flex-direction: column;
}
.mini-graph-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.9rem;
    font-size: 0.72rem;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
    flex-shrink: 0;
}
.mini-graph-legend .mg-leg {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.mini-graph-legend .dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    display: inline-block;
}
.mini-graph-legend .dot-person       { background: var(--success); }
.mini-graph-legend .dot-organization { background: var(--primary); }
.mini-graph-legend .dot-event        { background: var(--warning); }
.mini-graph-legend .dot-meeting      { background: var(--primary-hover); }
.mini-graph-svg-box {
    flex: 1;
    min-height: 0;
    width: 100%;
}
.mini-graph svg {
    width: 100%;
    height: 100%;
    display: block;
    overflow: visible;
}
.mini-graph .mg-link {
    stroke: var(--border);
    stroke-width: 1.2;
    stroke-opacity: 0.85;
}
.mini-graph .mg-node circle {
    stroke: var(--surface);
    stroke-width: 1.5;
    transition: filter 0.1s, stroke-width 0.1s;
}
.mini-graph .mg-node:hover circle { filter: brightness(1.15); }
.mini-graph .mg-node-person       circle { fill: var(--success); }
.mini-graph .mg-node-organization circle { fill: var(--primary); }
.mini-graph .mg-node-event        circle { fill: var(--warning); }
.mini-graph .mg-node-meeting      circle { fill: var(--primary-hover); }
.mini-graph .mg-focal circle {
    stroke: var(--text);
    stroke-width: 2.5;
}
.mini-graph .mg-label {
    font-size: 11px;
    fill: var(--text);
    pointer-events: none;
    font-weight: 500;
}
.mini-graph-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--text-muted);
    font-size: 0.85rem;
    font-style: italic;
}

/* Detail-page top: hero + first card stacked on the left, mini-graph on the
   right spanning the combined height. */
.detail-top {
    display: flex;
    gap: 1rem;
    align-items: stretch;
    margin-bottom: 1.25rem;
}
.detail-top-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
.detail-top-main > .hero,
.detail-top-main > .card,
.detail-top-main > .kpi-strip { margin-bottom: 0; }

/* === KPI strip (detail page) ============================================
   Quick-read stats above the Informations card on a Person detail. The
   numbers come from the same timeline pass used by the filter pills, so
   no extra DB roundtrip. Compact, theme-tokenized, wraps to multiple
   lines below 600px. */
.kpi-strip {
    /* `auto-fill` + minmax means the strip flows fluidly across viewports:
       6 cols on desktop, 5/4/3 on tablet shrinks, 2 on phone, 1 on tiny.
       No per-breakpoint override needed — replaces the old triple-rule
       cascade at 720px (2-col) + 480px (1-col). */
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 0.4rem;
}
.kpi {
    display: flex; flex-direction: column; gap: 0.1rem;
    padding: 0.4rem 0.7rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    min-width: 0;
}
.kpi-value {
    font-size: 0.95rem; font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.01em;
    line-height: 1.1;
}
.kpi-label {
    font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-muted); font-weight: 600;
}
.kpi.kpi-warn .kpi-value { color: var(--warning); }
.kpi.kpi-soft .kpi-value { font-size: 0.82rem; font-weight: 600; }

.detail-top-graph {
    width: 380px;
    flex-shrink: 0;
    display: flex;
}
.detail-top-graph .mini-graph-card {
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
}

/* Stacked variant: vertical button column inside hero. */
.hero-actions.stacked {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
}
.hero-actions.stacked > .btn,
.hero-actions.stacked > form > .btn { width: 100%; justify-content: center; }

/* Card header row (title left, action button right). */
.card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.8rem;
}
.card-header h2 { margin: 0; }

@media (max-width: 1100px) {
    .detail-top { flex-direction: column; }
    .detail-top-graph { width: 100%; }
    .mini-graph-card { width: 100%; }
}

/* ============================================================================
   Multi-user additions (auth, workspaces, members, teams, role pills)
   ============================================================================ */

/* --- Auth standalone pages (login / bootstrap) — split-screen Private Banking
   Left panel = navy plein + or accent (la vitrine de la marque, ce qu'un
   prospect voit en démo). Right panel = formulaire ivoire chaud.
   Collapses to form-only under 900px. */
html, body.auth-page { height: 100%; }
body.auth-page { background: var(--bg); margin: 0; }
.auth-split {
    display: grid;
    grid-template-columns: minmax(320px, 1fr) 1.05fr;
    height: 100vh;
    height: 100dvh;
    width: 100%;
}

/* Left panel — navy plein avec décor constellation subtile.
   Texture : 2 spots radiaux or très transparents, pas un dégradé criard.
   La signature visuelle de la marque dès l'écran de connexion. */
.auth-hero {
    padding: 3rem 3.25rem;
    display: flex; flex-direction: column; justify-content: space-between;
    background:
        radial-gradient(900px circle at 20% 15%, rgba(182, 151, 86, 0.10), transparent 55%),
        radial-gradient(700px circle at 90% 100%, rgba(182, 151, 86, 0.08), transparent 60%),
        var(--primary);
    color: var(--on-primary);
    overflow: hidden; position: relative;
}
/* Filet or fin sur le bord intérieur droit — signature minérale */
.auth-hero::after {
    content: "";
    position: absolute; top: 8%; bottom: 8%; right: 0;
    width: 1px;
    background: linear-gradient(to bottom, transparent, var(--accent) 30%, var(--accent) 70%, transparent);
    opacity: 0.4;
}
.auth-hero-top { display: flex; align-items: center; gap: 1rem; position: relative; z-index: 1; }
.auth-logo { width: 48px; height: 48px; }
.auth-brand-text { display: flex; flex-direction: column; gap: 0.15rem; }
.auth-title {
    font-family: var(--font-display);
    font-weight: 600; font-size: 1.5rem;
    color: var(--on-primary);
    letter-spacing: -0.01em;
}
.auth-subtitle {
    color: var(--accent);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-weight: 500;
}
.auth-hero-pitch { max-width: 38ch; position: relative; z-index: 1; }
.auth-hero-pitch h2 {
    /* Tagline en serif large — c'est l'écriture de marque, ça prend
       ses aises. Italique pour la signature presse économique. */
    font-family: var(--font-display);
    font-style: italic;
    font-size: clamp(1.75rem, 2.4vw, 2.5rem);
    font-weight: 400; line-height: 1.2;
    margin: 0 0 1.25rem;
    color: var(--on-primary);
    letter-spacing: -0.015em;
}
/* Filet or court avant le paragraphe de body — pause éditoriale */
.auth-hero-pitch h2::after {
    content: "";
    display: block;
    width: 60px; height: 1px;
    background: var(--accent);
    margin-top: 1.25rem;
    opacity: 0.7;
}
.auth-hero-pitch p {
    color: rgba(250, 248, 243, 0.78);
    font-size: 1.02rem; line-height: 1.65;
    margin: 0;
}
.auth-hero-foot {
    color: rgba(250, 248, 243, 0.55);
    font-size: 0.75rem;
    position: relative; z-index: 1;
    letter-spacing: 0.02em;
}

/* Right panel — form, vertically centered so it looks balanced whether
   the form has 2 fields (login) or 4 (bootstrap). */
.auth-panel {
    display: flex; align-items: center; justify-content: center;
    padding: 2rem 3rem;
    overflow-y: auto;
    background: var(--bg);
}
.auth-form-wrap { width: 100%; max-width: 420px; }

.auth-heading {
    margin: 0 0 0.6rem;
    font-family: var(--font-display);
    font-size: 1.75rem; font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--text);
}
.auth-intro {
    color: var(--text-muted); margin: 0 0 2rem;
    line-height: 1.55; font-size: 0.95rem;
}
.auth-form { display: flex; flex-direction: column; gap: 1.1rem; }
.auth-form label { display: flex; flex-direction: column; gap: 0.4rem; }
.auth-form label > span {
    font-size: 0.7rem; color: var(--text-muted);
    font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.08em;
}
.auth-form input {
    padding: 0.75rem 1rem;
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius);
    font: inherit; font-size: 0.95rem;
    transition: border-color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
}
.auth-form input:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
    background: var(--surface);
}
.auth-form button {
    margin-top: 0.6rem;
    padding: 0.85rem 1rem;
    font-size: 0.95rem; font-weight: 600;
    justify-content: center;
    letter-spacing: 0.02em;
}
.auth-note {
    margin-top: 2rem; padding-top: 1.5rem;
    border-top: 1px solid var(--border-light);
    color: var(--text-muted); font-size: 0.85rem; line-height: 1.55;
}

/* Collapse to single column on narrow windows. Hero shrinks to a top
   strip with just the brand chip; the pitch and footer are hidden. */
@media (max-width: 900px) {
    .auth-split { grid-template-columns: 1fr; grid-template-rows: auto 1fr; height: auto; min-height: 100vh; min-height: 100dvh; }
    .auth-hero {
        padding: 1.5rem 1.75rem;
        flex-direction: row; align-items: center; justify-content: space-between;
    }
    .auth-hero::after { display: none; }
    .auth-hero-pitch, .auth-hero-foot { display: none; }
    .auth-panel { padding: 2.5rem 1.5rem; }
}

/* --- Workspace switcher (sidebar) --- */
.ws-switcher {
    padding: 0 0.75rem 0.75rem;
    border-bottom: 1px solid var(--sidebar-border);
    margin-bottom: 0.75rem;
}
.ws-switcher-btn {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.55rem 0.7rem;
    background: var(--sidebar-hover);
    border: 1px solid var(--sidebar-border);
    border-radius: var(--radius-sm);
    color: var(--sidebar-text-hover);
    text-decoration: none;
    font-size: 0.82rem; font-weight: 500;
    /* No transition: cf. pitfall #8 in CLAUDE.md - the button is
       repainted on every navigation, and any fade on `background`
       makes the micro-flash between hover and repaint visible. */
}
/* La sidebar reste navy en light ET dark — donc le hover doit rester dans
   la palette sidebar (overlay blanc plus saturé que --sidebar-hover) plutôt
   que sur un token de surface global. Sinon en light theme le bouton vire
   au crème éclatant et le texte ivoire devient illisible. */
.ws-switcher-btn:hover { background: rgba(255, 255, 255, 0.12); }
.ws-switcher-btn span:not(.ws-role):not(.ws-personal) {
    flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ws-personal { font-style: italic; color: var(--text-muted); }

/* Role pills */
.ws-role {
    display: inline-block;
    font-size: 0.68rem; font-weight: 600; padding: 0.18rem 0.55rem;
    border-radius: 999px; text-transform: uppercase; letter-spacing: 0.04em;
    white-space: nowrap;          /* never wrap "OWNER" / "ADMIN" / etc. */
    flex-shrink: 0;
}
/* Owner = or champagne (signal "propriétaire de workspace" en couleur de
   marque, signal de prestige). Admin = navy (autorité). Editor = forêt.
   Viewer = neutre. Superadmin = bordeaux (cap d'identification). */
.ws-role-owner      { background: var(--accent-pale);    color: var(--accent-active); }
.ws-role-admin      { background: var(--primary-pale);   color: var(--primary); }
.ws-role-editor     { background: var(--success-light);  color: var(--success); }
.ws-role-viewer     { background: var(--surface-2);      color: var(--text-muted); }
.ws-role-superadmin { background: var(--danger-light);   color: var(--danger); }

/* === Workspace cards (page /workspaces) =================================
   Grid of cards (one workspace = one card). The active card is marked
   by an indigo strip on the left (inset box-shadow, not border, to
   avoid shifting layout) + an "Actif" badge in the top-right. Inactive
   cards have a "Basculer ici" button at the bottom. */
.ws-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
    gap: 1rem;
    margin-bottom: 1.5rem;
}
.ws-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.1rem 1.2rem;
    box-shadow: var(--shadow-sm);
    display: flex; flex-direction: column;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.ws-card:hover { border-color: var(--border-strong); }
.ws-card.is-active {
    box-shadow: inset 4px 0 0 var(--primary), var(--shadow-sm);
    background: linear-gradient(90deg, var(--primary-light) 0%, var(--surface) 30%);
}
.ws-card-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 0.6rem;
    margin-bottom: 0.6rem;
}
.ws-card-title { min-width: 0; }   /* allow truncation if name is huge */
.ws-card-title h2 {
    margin: 0 0 0.2rem 0;
    font-size: 1.05rem; font-weight: 600;
    line-height: 1.25;
    overflow: hidden; text-overflow: ellipsis;
}
.ws-card-slug {
    font-family: ui-monospace, monospace;
    font-size: 0.72rem;
    color: var(--text-muted);
    background: var(--surface-2);
    padding: 0.1rem 0.45rem;
    border-radius: 4px;
}
.ws-card-badges {
    display: flex; flex-direction: column; align-items: flex-end;
    gap: 0.35rem;
    flex-shrink: 0;
}
.ws-card-active {
    display: inline-flex; align-items: center; gap: 0.3rem;
    font-size: 0.68rem; font-weight: 700;
    color: var(--primary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}
.ws-card-active svg { color: var(--primary); }
.ws-card-body {
    color: var(--text-muted); font-size: 0.85rem; line-height: 1.5;
    margin-bottom: 0.85rem;
    flex: 1;     /* push the action row to the bottom for consistent card height */
}
.ws-card-actions {
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.6rem;
    padding-top: 0.7rem;
    border-top: 1px solid var(--border-light);
}
.ws-card-links {
    display: flex; gap: 0.7rem; font-size: 0.82rem;
}
.ws-card-links a {
    color: var(--text-muted);
    text-decoration: none;
}
.ws-card-links a:hover {
    color: var(--primary);
    text-decoration: underline;
}
.ws-card-links.muted span {
    color: var(--text-light); font-style: italic;
    font-size: 0.78rem;
}
.ws-card-switch {
    flex-shrink: 0;
    white-space: nowrap;
}

.ws-personal {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem;
    padding: 0.9rem 1.1rem;
    background: var(--surface-2);
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    margin-bottom: 1.5rem;
}
.ws-personal-info { display: flex; flex-direction: column; gap: 0.2rem; }
.ws-personal-info strong { font-size: 0.92rem; }
.ws-personal-info .muted { font-size: 0.82rem; }

/* === Modules toggles (page /workspaces/<id>/settings) =================== */
.ws-modules-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
    gap: 0.7rem;
}
.ws-module {
    display: flex; align-items: flex-start; gap: 0.7rem;
    padding: 0.85rem 1rem;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: border-color 0.12s, background 0.12s;
}
.ws-module:hover { border-color: var(--border-strong); }
/* `:has(input:checked)` reflects checkbox state in real time — no JS needed.
   Supported in all evergreen browsers since late 2023. */
.ws-module:has(input:checked) {
    background: var(--primary-light);
    border-color: var(--primary);
}
.ws-module input[type="checkbox"] {
    flex-shrink: 0;
    margin-top: 0.2rem;
    width: 16px; height: 16px;
    accent-color: var(--primary);
    cursor: pointer;
}
.ws-module-content {
    display: flex; flex-direction: column; gap: 0.25rem;
    min-width: 0;
}
.ws-module-label {
    font-weight: 600; font-size: 0.92rem;
    color: var(--text);
}
.ws-module:has(input:checked) .ws-module-label { color: var(--primary); }
.ws-module-desc {
    font-size: 0.78rem;
    color: var(--text-muted);
    line-height: 1.45;
}

/* --- Sidebar user block --- */
.sidebar-user {
    /* No margin-top: auto here - `.sidebar-foot` consumes the free
       space and pushes itself to the bottom. If user has it too, both
       blocks share the free space and the theme bar ends up centered
       instead of sticking to the user block. */
    padding: 0.65rem 0.75rem;
    border-top: 1px solid var(--sidebar-border);
    display: flex; align-items: center; gap: 0.5rem;
}
.sidebar-user-link {
    display: flex; align-items: center; gap: 0.55rem;
    flex: 1; min-width: 0;
    text-decoration: none; color: var(--sidebar-text);
    font-size: 0.82rem;
}
.sidebar-user-link:hover { color: var(--sidebar-text-hover); }
.sidebar-user-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-logout-form { margin: 0; }
.sidebar-logout-btn {
    background: transparent; border: none; color: var(--sidebar-text-muted);
    cursor: pointer; padding: 0.35rem; border-radius: 4px;
    display: flex; align-items: center; justify-content: center;
    transition: color 0.15s, background 0.15s;
}
.sidebar-logout-btn:hover { color: var(--danger); background: var(--sidebar-hover); }

/* --- Badges --- */
.badge {
    display: inline-block;
    padding: 0.18rem 0.55rem;
    font-size: 0.72rem; font-weight: 500;
    border-radius: 6px;
    background: var(--primary-light); color: var(--primary);
}
.badge-super { background: rgba(239,68,68,0.15); color: var(--danger); }
.badge-warn  { background: var(--warning-light); color: var(--warning); }

/* --- Forms (inline, grid) --- */
.form-inline {
    display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
    margin: 0.5rem 0;
}
.form-inline input[type=text],
.form-inline input[type=email],
.form-inline input[type=password],
.form-inline select {
    padding: 0.45rem 0.65rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); color: var(--text); font: inherit;
}
.form-inline .inline-check { display: flex; align-items: center; gap: 0.3rem; font-size: 0.85rem; }

.form-grid {
    display: flex; flex-direction: column; gap: 1rem; max-width: 560px;
}
.form-grid label {
    display: flex; flex-direction: column; gap: 0.3rem;
}
.form-grid label > span { font-size: 0.82rem; color: var(--text-muted); font-weight: 500; }
.form-grid label > span small { font-weight: 400; color: var(--text-light); }
.form-grid input, .form-grid textarea, .form-grid select {
    padding: 0.55rem 0.75rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: var(--radius-sm); color: var(--text); font: inherit;
}
.form-grid textarea { resize: vertical; min-height: 72px; }

/* --- Buttons --- */
.btn {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.38rem 0.75rem;
    background: var(--surface-2); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    font: inherit; cursor: pointer; text-decoration: none;
    transition: background 0.15s, border-color 0.15s;
}
.btn:hover { background: var(--surface); border-color: var(--border); }
.btn-primary { background: var(--primary); color: var(--on-primary); border-color: var(--primary); }
.btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.btn-danger { background: var(--danger-light); color: var(--danger); border-color: var(--danger); }
.btn-danger:hover { background: var(--danger); color: var(--on-danger); }
.btn-sm { padding: 0.32rem 0.65rem; font-size: 0.82rem; }
.btn-xs { padding: 0.15rem 0.4rem; font-size: 0.72rem; }
form.inline { display: inline-block; margin: 0; }

/* --- Hints + row highlights --- */
.hint { margin-top: 0.5rem; color: var(--text-muted); font-size: 0.82rem; }
.row-active { background: var(--primary-light) !important; }
.muted { color: var(--text-muted); }
.kv { display: grid; grid-template-columns: auto 1fr; gap: 0.4rem 1.2rem; margin: 0; }
.kv dt { color: var(--text-muted); font-weight: 500; }
.kv dd { margin: 0; }

/* --- Teams (nested cards) --- */
.team-card {
    padding: 0.9rem 1rem; margin-bottom: 0.8rem;
    background: var(--surface-2); border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
}
.team-header {
    display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.team-header h3 { margin: 0; font-size: 1rem; font-weight: 600; }
.team-desc { margin: 0.2rem 0 0; color: var(--text-muted); font-size: 0.85rem; }
.team-members {
    list-style: none; margin: 0.6rem 0; padding: 0;
    display: flex; flex-direction: column; gap: 0.35rem;
}
.team-members li {
    display: flex; align-items: center; gap: 0.5rem;
    font-size: 0.88rem;
}

/* --- Readonly license banner ---
   Sticky so it stays visible when the user scrolls a long list. Composite
   layout: icon | message | CTA. The danger colors are already intentionally
   loud; the banner is a permanent piece of UI when displayed, not a toast. */
.license-banner {
    position: sticky; top: 0; z-index: 50;
    display: flex; align-items: center; gap: 0.9rem;
    background: linear-gradient(90deg,
        var(--danger) 0%,
        color-mix(in srgb, var(--danger) 85%, #000) 100%);
    color: var(--on-danger);
    padding: 0.8rem 1.5rem;
    font-size: 0.88rem;
    box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
}
.license-banner svg { flex-shrink: 0; }
.license-banner-text { flex: 1; line-height: 1.4; }
.license-banner-text strong { font-weight: 700; }
.license-banner-cta {
    flex-shrink: 0;
    color: var(--on-danger); text-decoration: none;
    background: rgba(255,255,255,0.18);
    padding: 0.4rem 0.9rem; border-radius: var(--radius-sm);
    font-weight: 600; white-space: nowrap;
    transition: background 0.15s;
}
.license-banner-cta:hover { background: rgba(255,255,255,0.3); }
.license-banner-cta-muted {
    background: transparent; font-weight: 500; opacity: 0.85;
    cursor: default;
}

/* Grey out + disable form submits inside read-only mode. Set by a <body>
   class added via the inline head script when license_state.valid is false
   AND the user is NOT superadmin. Viewers/editors see buttons but cannot
   interact — explains visually why clicking doesn't work. */
body.readonly-mode form button[type="submit"],
body.readonly-mode .btn-primary,
body.readonly-mode button.btn-danger {
    opacity: 0.5; cursor: not-allowed;
    filter: grayscale(0.3);
}
body.readonly-mode form button[type="submit"]:hover,
body.readonly-mode .btn-primary:hover,
body.readonly-mode button.btn-danger:hover {
    background: var(--surface-2);
}

/* ============================================================================
   Desktop mode (served from pywebview) — Linear/Notion-like polish
   ============================================================================
   Detected via the User-Agent or via a `desktop-mode` class we can set from
   desktop.py injecting CSS at load time. For now, an opt-in media query via
   CSS env variable approach: desktop.py injects `document.documentElement
   .classList.add('desktop-mode')`. Any of these styles applies only when
   that class is present.
*/

html.desktop-mode body { overscroll-behavior: none; }

/* Tighter, padding-reduced UI — desktop apps typically have denser chrome
   than a web app, because you've got the whole screen for yourself. */
html.desktop-mode .sidebar {
    padding-top: 0.6rem;
}
html.desktop-mode main {
    padding: 1rem 1.5rem;
}
html.desktop-mode .hero {
    padding: 0.8rem 1rem;
    margin-bottom: 1rem;
}

/* Remove the Inter font fallback chain — desktop uses the OS font for a
   native feel. Still keeps Inter if it's already loaded via the <link>. */
html.desktop-mode body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Variable",
                 "Segoe UI", "Inter", Roboto, sans-serif;
}

/* Smoother scrollbars (Chromium/WebView2 only — macOS/GTK ignores) */
html.desktop-mode ::-webkit-scrollbar { width: 10px; height: 10px; }
html.desktop-mode ::-webkit-scrollbar-track { background: transparent; }
html.desktop-mode ::-webkit-scrollbar-thumb {
    background: var(--border);
    border-radius: 6px;
}
html.desktop-mode ::-webkit-scrollbar-thumb:hover {
    background: var(--text-light);
}

/* Subtle inset shadow on window edges so the content doesn't touch the
   frame brutally (matches Notion / Linear). */
html.desktop-mode .app {
    box-shadow: inset 0 0 0 1px var(--border-light);
}

/* ============================================================================
   Danger zone - owner-only actions (archive, transfer).
   ============================================================================ */
.card.danger-zone {
    border-left: 3px solid var(--danger);
    background: color-mix(in srgb, var(--surface) 96%, var(--danger) 4%);
}
.card.danger-zone h2 {
    color: var(--danger);
}
.danger-zone-intro {
    color: var(--text-muted);
    font-size: 0.88rem;
    margin: 0 0 1.2rem;
}
.danger-zone-action {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 1.5rem;
    padding: 1rem 0;
    border-top: 1px solid var(--border-light);
}
.danger-zone-action:first-of-type { border-top: none; padding-top: 0; }
.danger-zone-action > div { flex: 1; min-width: 0; }
.danger-zone-action strong {
    display: block;
    color: var(--text);
    font-size: 0.95rem;
    margin-bottom: 0.3rem;
}
.danger-zone-action p {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.5;
    margin: 0;
}
.danger-zone-action form { flex-shrink: 0; }

/* === Tag variants used by module status badges (prospects, dossiers).
   Reuse the existing status tokens; the `info` variant taps the brand
   indigo since there's no dedicated info token. === */
.tag.success { background: var(--success-light); color: var(--success); }
.tag.danger  { background: var(--danger-light);  color: var(--danger);  }
.tag.info    { background: var(--primary-light); color: var(--primary); }

/* === Person activity timeline ===
   Vertical chronological feed on the Person detail page. Each row pairs
   a date column, an icon, and a textual body. The icon column also draws
   the connecting rail (1px line down the middle) via a pseudo-element. */
.timeline {
    list-style: none;
    margin: 0;
    padding: 0;
}
.tl-item {
    display: grid;
    grid-template-columns: 7.5rem 2rem 1fr;
    gap: 0.85rem;
    align-items: start;
    padding: 0.55rem 0;
    position: relative;
}
.tl-item + .tl-item { border-top: 1px solid var(--border-light); }
/* Whole-entry click affordance (data-href set in persons/detail.html,
   navigation handled by the shared [data-href] delegate in app.js).
   Negative inline margin so the hover background has breathing room
   without shifting the grid. Gold inset bar mirrors the table-row
   convention. */
.tl-item[data-href] {
    cursor: pointer;
    margin-left: -0.6rem;
    margin-right: -0.6rem;
    padding-left: 0.6rem;
    padding-right: 0.6rem;
    border-radius: var(--radius);
    transition: background var(--duration-fast) var(--ease-out);
}
.tl-item[data-href]:hover {
    background: var(--surface-2);
    box-shadow: inset 3px 0 0 var(--accent);
}
.tl-date {
    color: var(--text-muted);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    padding-top: 0.15rem;
}
.tl-icon {
    width: 2rem; height: 2rem;
    display: flex; align-items: center; justify-content: center;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-muted);
    flex-shrink: 0;
}
.tl-icon svg { width: 14px; height: 14px; }
/* Per-kind tint — uses background tokens already in the palette so
   themes (dark / light) follow without extra overrides. */
.tl-meeting        .tl-icon { background: var(--primary-light);  color: var(--primary); }
.tl-communication  .tl-icon { background: var(--success-light);  color: var(--success); }
.tl-event          .tl-icon { background: var(--warning-light);  color: var(--warning); }
.tl-dossier        .tl-icon { background: var(--primary-light);  color: var(--primary); }
.tl-prospect       .tl-icon { background: var(--success-light);  color: var(--success); }
/* Inline follow-up — discreet subtitle that sits in the right column of
   the meeting row (`.tl-body` grid). Yellow accent on the icon + the
   "due" label so it scans as a TODO without screaming. The action
   buttons sit immediately after the text — both rendered at exactly the
   same dimensions whether they are <a> or <button>. Goes muted with a
   strikethrough when done. */
.tl-followup {
    grid-area: fu;
    margin-top: 0.45rem;
    margin-right: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 0.6rem;
    font-size: 0.86rem;
    /* Real banner now — opaque amber-pale bg (stays distinct over the
       .tl-item[data-href] hover surface, cf. static/CLAUDE.md: -pale
       opaque on varied bg) + left accent. Was invisible inline text. */
    background: var(--warning-pale);
    border-left: 3px solid var(--warning);
    border-radius: var(--radius);
    padding: 0.5rem 0.7rem;
}
.tl-followup-text {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    min-width: 0;
    color: var(--text);
}
.tl-followup-text svg {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    /* Was --warning → orange icon on the --warning-pale banner = orange
       on orange, illisible. Dark ink on the pale bg, the amber identity
       stays on the bg + left accent bar. */
    color: var(--text);
}
.tl-followup-due {
    color: var(--text);
    font-weight: 600;
}
.tl-followup-action { color: var(--text); }
.tl-followup-done-at { color: var(--text-light); font-size: 0.76rem; }

/* Done = de-emphasized: drop the amber banner, go neutral. */
.tl-followup.is-done {
    background: var(--surface-2);
    border-left-color: var(--border-strong);
}
.tl-followup.is-done .tl-followup-text svg,
.tl-followup.is-done .tl-followup-due { color: var(--text-light); }
.tl-followup.is-done .tl-followup-action {
    color: var(--text-muted);
    text-decoration: line-through;
}

.tl-followup-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.tl-followup-actions form {
    display: inline-flex;
    margin: 0;
    padding: 0;
}
/* Shared sizing for both <button> and <a> variants — `appearance: none`
   strips the user-agent button defaults so the two render identically. */
.tl-followup-btn {
    appearance: none;
    box-sizing: border-box;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    color: var(--text);
    border-radius: var(--radius);
    width: 2rem;
    height: 2rem;
    padding: 0;
    margin: 0;
    line-height: 1;
    font: inherit;
    font-size: 1.05rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.tl-followup-btn:hover {
    border-color: var(--primary);
    color: var(--on-primary);
    background: var(--primary);
}
.tl-body {
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        "head fu"
        "summary fu";
    column-gap: 1.25rem;
    align-items: center;
}
.tl-headline { grid-area: head; }
.tl-summary  { grid-area: summary; }
.tl-headline { font-size: 0.92rem; }
.tl-label { font-weight: 600; color: var(--text); }
.tl-meta  { color: var(--text-muted); margin-left: 0.3rem; font-size: 0.85rem; }
.tl-summary {
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 0.15rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.tl-summary a { color: var(--text); }
.tl-summary a:hover { color: var(--primary); }
@media (max-width: 720px) {
    .tl-item { grid-template-columns: 5.5rem 1.6rem 1fr; gap: 0.6rem; }
    .tl-icon { width: 1.6rem; height: 1.6rem; }
    .tl-summary { white-space: normal; }
}

/* === Module sections (Dossiers/Prospects on Person detail) ===
   Two equal cards side by side; collapses to a single column under
   900 px. The `module-list` is a tight bullet-less list with each row
   showing a link + a status tag right-aligned. */
.modules-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
    margin: 1rem 0;
}
@media (max-width: 900px) { .modules-grid { grid-template-columns: 1fr; } }
.module-list {
    list-style: none;
    margin: 0;
    padding: 0;
}
.module-list li {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0;
}
.module-list li + li { border-top: 1px solid var(--border-light); }
.module-list li a { font-weight: 500; }

/* === Dossier members — small chip with avatar + name shown under the
   hero. Used to surface "who has access to this dossier". === */
.dossier-members {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
    margin-top: 0.5rem;
    font-size: 0.85rem;
}
.dossier-member {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.55rem 0.15rem 0.2rem;
    background: var(--surface-2);
    border-radius: 999px;
    color: var(--text);
}
.dossier-member .avatar.sm {
    width: 1.4rem; height: 1.4rem;
    font-size: 0.62rem;
}
.dossier-member-name { color: var(--text); }
.dossier-source {
    margin-top: 0.45rem;
    font-size: 0.85rem;
}
.dossier-source .muted { color: var(--text-muted); }
.dossier-source a {
    color: var(--text);
    border-bottom: 1px dashed var(--border);
}
.dossier-source a:hover { color: var(--primary); border-bottom-color: var(--primary); }

/* === Sections inside a card (e.g. Informations sub-blocks: LinkedIn,
   Notes, Prospect). Each section has a small uppercase label header,
   separated from the previous one by a thin top border. === */
.info-section + .info-section {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border-light);
}
.info-section .label {
    font-size: 0.7rem;
    color: var(--text-muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 0.4rem;
}
.info-section-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 0.5rem;
}
.info-section-action {
    font-size: 0.78rem;
    color: var(--text-muted);
    text-decoration: none;
}
.info-section-action:hover { color: var(--primary); }

/* Prospect mini-block inside the Informations card. Subtle inset
   background to visually contain the stage / apporteurs / notes group. */
.info-prospect {
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    padding: 0.7rem 0.85rem;
    margin-top: 0.5rem;
}
.info-prospect + .info-prospect { margin-top: 0.6rem; }
.info-prospect-row {
    display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem;
}
.info-prospect-edit {
    margin-left: auto;
    font-size: 0.8rem;
    color: var(--text-muted);
    text-decoration: none;
}
.info-prospect-edit:hover { color: var(--primary); }
.info-prospect-actions {
    margin-left: auto;
    display: inline-flex;
    gap: 0.75rem;
    font-size: 0.8rem;
}
.info-prospect-actions a { text-decoration: none; }
.info-prospect-actions a:hover { color: var(--primary); }

/* Prospects apportés PAR cette personne (perspective inverse du bloc
   .info-prospect ci-dessus). Liste verticale compacte de chips —
   avatar + nom de la personne ciblée + badge stage. Les won/lost sont
   atténués (track record historique, pas action en cours). */
.info-prospect-list {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-top: 0.4rem;
}
.brought-prospect-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.35rem 0.55rem;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    text-decoration: none;
    font-size: 0.85rem;
    transition: background var(--duration-fast) var(--ease-out);
}
.brought-prospect-chip:hover {
    background: var(--surface-3);
}
.brought-prospect-chip .avatar.sm {
    width: 1.5rem; height: 1.5rem; font-size: 0.7rem;
    flex: 0 0 auto;
}
.brought-prospect-chip .brought-prospect-name {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.brought-prospect-chip .tag {
    flex: 0 0 auto;
    font-size: 0.7rem;
}
/* Atténue les stages terminaux : le « + récent gagné/perdu » reste lisible
   mais ne tire pas l'attention autant qu'un prospect ouvert. */
.brought-prospect-chip.is-won,
.brought-prospect-chip.is-lost {
    opacity: 0.7;
}
.brought-prospect-chip.is-won:hover,
.brought-prospect-chip.is-lost:hover {
    opacity: 1;
}

/* === Person type picker on the create form. Pill-style radios that
   reveal extra sections (collaborateur role / prospect stage+apporteurs
   / client info) below the main fields via JS. === */
.type-picker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.2rem;
}
.type-pill {
    display: inline-flex;
    align-items: center;
    padding: 0.45rem 0.95rem;
    border: 1px solid var(--border);
    border-radius: 999px;
    cursor: pointer;
    color: var(--text-muted);
    font-size: 0.88rem;
    user-select: none;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.type-pill input { display: none; }
.type-pill:hover { border-color: var(--border-strong); color: var(--text); }
.type-pill:has(input:checked) {
    background: var(--primary-light);
    border-color: var(--primary);
    color: var(--primary);
    font-weight: 600;
}
.type-section {
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border-light);
}
.info-prospect-meta {
    margin-top: 0.5rem;
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 0.4rem; font-size: 0.85rem;
}
.info-prospect-notes {
    margin-top: 0.55rem;
    color: var(--text-muted);
    font-size: 0.86rem;
    white-space: pre-wrap;
}

/* === Person hero — current-role subtitle ===
   Lightweight grey line under the H1 to surface the person's current
   employment without resorting to a full table. */
.hero-role {
    color: var(--text-muted);
    font-size: 0.92rem;
    margin-top: 0.3rem;
}
.hero-role a { color: var(--text); }
.hero-role a:hover { color: var(--primary); }
.hero-role .muted { color: var(--text-light); }

/* === Activity card — toolbar with filter pills + Ajouter menu ===
   The toolbar sits at the top of `.activity-card`; pills and the menu
   wrap to a second row on narrow viewports. Pills are tinted indigo
   when active to match the rest of the brand. */
.activity-card { padding-top: 0.6rem; }
.activity-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 0.6rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--border-light);
}
.filter-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}
.filter-pill {
    appearance: none;
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-muted);
    border-radius: 999px;
    padding: 0.35rem 0.75rem;
    font-size: 0.83rem;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.filter-pill:hover {
    border-color: var(--border-strong);
    color: var(--text);
}
.filter-pill.is-active {
    background: var(--primary-light);
    border-color: var(--primary);
    color: var(--primary);
    font-weight: 600;
}
.filter-pill .filter-count {
    font-size: 0.72rem;
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.filter-pill.is-active .filter-count {
    background: var(--surface);
    color: var(--primary);
}

/* `<details>` dropdown — anchored to the right of the toolbar. We hide
   the native marker and render our own caret next to the label. */
.add-menu { position: relative; }
.add-menu summary {
    list-style: none;
    cursor: pointer;
    user-select: none;
}
.add-menu summary::-webkit-details-marker { display: none; }
.add-menu .caret {
    display: inline-block;
    margin-left: 0.2rem;
    transition: transform 0.12s;
    color: var(--text-muted);
}
.add-menu[open] .caret { transform: rotate(180deg); }
.add-menu-items {
    position: absolute;
    left: 0;
    top: calc(100% + 0.3rem);
    background: var(--surface-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    min-width: 12rem;
    padding: 0.3rem 0;
    z-index: 50;
}
.add-menu-items a {
    display: block;
    padding: 0.45rem 0.85rem;
    color: var(--text);
    font-size: 0.88rem;
    text-decoration: none;
}
.add-menu-items a:hover {
    background: var(--surface-2);
    color: var(--primary);
}

/* === /settings layout (templates/settings_base.html) === */
/* Two-column shell: secondary sidebar (sections + items) on the left,
   active content on the right. Inspired by the left panel of the
   /connections dashboard (`.gp-section`): small-caps uppercase
   headers, sections separated by a border, active item with side
   strip and indigo tint. */
.settings-page {
    /* Full-bleed inside `<main>`: cancel its padding-inline with a
       negative margin keyed off `--main-pad-x` (set on `main` next to
       every padding override) -> the settings sidebar touches the
       edge of the main area, and runs top-to-bottom like the CRM's
       main sidebar. Using the var (vs a hard -2.5rem like before)
       avoids overshooting `main`'s actual padding when responsive
       breakpoints shrink it — overshoot got clipped by `main`'s
       `overflow-x: clip` and ate the first ~22px of section labels.
       `.settings-main` restores padding for content. Vertical margin
       stays hardcoded: no top/bottom clip, so overshoot is invisible. */
    display: grid;
    grid-template-columns: 15rem 1fr;
    align-items: stretch;
    margin-block: -2rem;
    margin-inline: calc(-1 * var(--main-pad-x, 1.5rem));
    min-height: calc(100% + 4rem);   /* min(viewport, content height) */
}
/* Pattern modelled on the main sidebar (`.sidebar nav`): flex
   icon+label items, progressive icon opacity (0.6 -> 0.9 -> 1),
   left strip via inset box-shadow (not border, to keep alignment),
   primary-light bg when active. */
.settings-nav {
    background: var(--surface);
    border-right: 1px solid var(--border);
    /* Top padding is generous (vs 0.6rem side / 1rem bottom) so the first
       section header doesn't sit flush against the page top — the settings
       sidebar runs flush to the main border (full-bleed), so without this
       breathing room the « MON COMPTE » label feels glued to the edge. */
    padding: 2.25rem 0.6rem 1rem;
    display: flex; flex-direction: column;
}
/* Each group is a <details> — click summary toggles open/closed.
   Native marker hidden ; chevron drawn via ::after, rotates on open. */
.settings-nav-section {
    border-bottom: 1px solid var(--border-light);
}
.settings-nav-section:last-of-type { border-bottom: 0; }
.settings-nav-section > summary {
    list-style: none;       /* Firefox */
    cursor: pointer;
    user-select: none;
    display: flex; align-items: center; justify-content: space-between;
    gap: 0.6rem;
    padding: 0.7rem 0.85rem 0.55rem;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--text-muted);
    font-weight: 600;
    border-radius: var(--radius-sm);
    transition: background 0.12s, color 0.12s;
}
.settings-nav-section > summary::-webkit-details-marker { display: none; }
.settings-nav-section > summary:hover {
    background: var(--surface-2);
    color: var(--text);
}
.settings-nav-section[open] > summary { color: var(--text); }
/* Chevron — closed: ▸ (right), open: ▾ (down). Rotation 90° = state change. */
.settings-nav-section > summary::after {
    content: "";
    flex-shrink: 0;
    width: 8px; height: 8px;
    border-right: 1.6px solid currentColor;
    border-bottom: 1.6px solid currentColor;
    transform: rotate(-45deg);    /* points right by default */
    transition: transform 0.18s;
    margin-right: 2px;
}
.settings-nav-section[open] > summary::after {
    transform: rotate(45deg);     /* points down when open */
}
/* Items inside an open section get a small bottom margin block so
   they don't touch the next summary. */
.settings-nav-section[open] {
    padding-bottom: 0.4rem;
}
.settings-nav-item {
    display: flex; align-items: center; gap: 0.75rem;
    padding: 0.58rem 0.85rem;
    border-radius: var(--radius-sm);
    color: var(--text); text-decoration: none;
    font-size: 0.875rem; font-weight: 500; letter-spacing: -0.01em;
}
.settings-nav-item svg {
    width: 17px; height: 17px; stroke-width: 2;
    flex-shrink: 0;
    opacity: 0.6;
}
.settings-nav-item:hover {
    background: var(--surface-2);
    box-shadow: inset 2px 0 0 var(--border-strong);
}
.settings-nav-item:hover svg { opacity: 0.9; }
.settings-nav-item.active {
    background: var(--primary-light);
    color: var(--primary);
    font-weight: 600;
    box-shadow: inset 3px 0 0 var(--primary);
}
.settings-nav-item.active svg {
    opacity: 1;
    color: var(--primary);
}

.settings-main {
    /* Restore `<main>`'s usual padding (canceled by `.settings-page`'s
       full-bleed margin) so the content has its normal breathing room.
       Horizontal mirrors `--main-pad-x` so a narrow viewport tightens
       both edges in lockstep with the rest of the app. `min-width: 0`
       lets long tables (e.g. /admin/users) shrink within the grid cell
       instead of pushing the sidebar. */
    padding-block: 2rem;
    padding-inline: var(--main-pad-x, 1.5rem);
    min-width: 0;
}

@media (max-width: 900px) {
    .settings-page { grid-template-columns: 1fr; }
    .settings-nav { position: static; }
}

.types-list { display: flex; flex-direction: column; }
/* Single grid template shared by rows + add form so every column aligns
   vertically (Save under Save, hex under hex, etc.). The horizontal
   padding (0.55rem) is applied to both so columns sit at the same x
   coordinate even when the add form has a tinted background. */
.types-row, .types-add {
    display: grid;
    /* 5 columns — auto-save means no per-row [action] button. The
       [extra] column holds the × delete (data rows) or a placeholder
       (system rows / add form). Tight gap between [swatch] and
       [extra] (0.6rem) keeps the × right next to the swatch. */
    grid-template-columns:
        [preview] 9rem
        [name] minmax(6rem, 1fr)
        [hex] 5rem
        [swatch] 28px
        [extra] 28px;
    align-items: center;
    column-gap: 0.6rem;
    padding: 0.55rem 0.55rem;
    border-bottom: 1px solid var(--border-light);
    transition: background 0.4s;
}
/* Batch-save dirty marker - JS toggles `.is-dirty` while the row's
   inputs differ from the originally-loaded values. Subtle indigo strip
   on the left signals "unsaved change". */
.types-row.is-dirty, .types-add.is-dirty {
    box-shadow: inset 3px 0 0 var(--primary);
}
/* Confirmation flash (success/failure) after a batch Save.
   Auto-cleared after 600ms via setTimeout JS. */
.types-row.is-flash-ok, .types-add.is-flash-ok {
    background: var(--success-light);
}
.types-row.is-flash-error, .types-add.is-flash-error {
    background: var(--danger-light);
}

/* Page-level Save / Cancel toolbar in the page-header right side
   (cf. templates/admin/types.html). Buttons disabled when no form
   on the page is dirty ; Save shows a count of pending changes. */
.settings-actions {
    display: flex; gap: 0.4rem; align-items: center;
    flex-shrink: 0;
}
.settings-actions .btn[disabled] {
    opacity: 0.45; cursor: not-allowed;
}
.types-row:last-of-type { border-bottom: 0; }
/* The add form is a visually distinct « create new » zone — tinted bg
   + faint inner ring, so it reads as a different action zone (vs the
   read/edit list above). `box-shadow inset` instead of `border` so the
   1px ring doesn't shift the inner grid relative to the rows above (a
   real border would offset by 1px on each side and break the column
   alignment with the rows). */
.types-add {
    margin-top: 0.7rem;
    background: var(--surface-2);
    box-shadow: inset 0 0 0 1px var(--border);
    border-radius: var(--radius-sm);
    padding: 0.7rem 0.55rem;
}
.types-row-preview {
    overflow: hidden;
    display: flex; align-items: center; gap: 0.4rem;
    min-width: 0;     /* allow text-overflow inside */
}
.types-row-preview > [data-color-target] {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    max-width: 100%;
}
.types-row-name {
    min-width: 0;     /* lets the input shrink within its grid cell */
    padding: 0.4rem 0.6rem;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--text); font-size: 0.9rem;
    font-family: inherit;
}
.types-row-name:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
    background: var(--surface);
}
.types-row-hex {
    /* Right-aligned monospace hex code — same width across rows so they
       line up vertically. Non-clickable (the swatch button next to it
       is the trigger). */
    font-family: ui-monospace, monospace;
    font-size: 0.78rem;
    color: var(--text-muted);
    text-align: right;
    text-transform: lowercase;
}
.types-row-swatch {
    width: 28px; height: 28px; padding: 0;
    border: 2px solid var(--border); border-radius: var(--radius-sm);
    cursor: pointer;
}
.types-row-swatch:hover { border-color: var(--primary); }
.types-row-delete {
    width: 28px; height: 28px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: transparent; color: var(--text-light); cursor: pointer;
    transition: color 0.12s, border-color 0.12s, background 0.12s;
    justify-self: end;
}
.types-row-delete svg { width: 16px; height: 16px; display: block; }
.types-row-delete:hover {
    color: var(--danger); border-color: var(--danger);
    background: var(--danger-light);
}
.types-row-delete:focus-visible {
    outline: none; border-color: var(--danger);
    box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.18);
}
/* Reserves the [extra] grid column for system rows / add form so the
   action column above doesn't drift into it. */
.types-row-delete-placeholder { display: block; width: 28px; height: 1px; }

/* Color picker — swatch trigger + hex display + popover with preset swatches */
.color-picker-popover {
    position: fixed; z-index: 1100;
    background: var(--bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    /* Stronger shadow + faint primary glow to lift the popover off the page */
    box-shadow:
        0 0 0 1px rgba(108, 122, 237, 0.08),
        0 24px 48px -16px rgba(0, 0, 0, 0.55),
        0 12px 24px -12px rgba(0, 0, 0, 0.4);
    padding: 0.85rem;
    width: 17rem;
}
.color-picker-popover[hidden] { display: none; }

/* === HSV chooser (saturation/value square + hue strip) === */
.cp-hsv, .cp-hue { position: relative; margin-bottom: 0.5rem; }
.cp-hsv {
    width: 100%; height: 120px;
    border-radius: var(--radius-sm);
    overflow: hidden;
    cursor: crosshair;
    user-select: none;
}
.cp-sv { display: block; width: 100%; height: 100%; }
.cp-hue {
    width: 100%; height: 14px;
    border-radius: 999px;
    overflow: visible;
    cursor: ew-resize;
    user-select: none;
}
.cp-hue-strip {
    display: block; width: 100%; height: 100%;
    border-radius: 999px;
}
/* Draggable cursors on top of canvases — divs positioned via JS. The
   ring-style design keeps the picked color visible through the hole. */
.cp-cursor {
    position: absolute; pointer-events: none;
    border: 2px solid #fff;
    border-radius: 50%;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.4);
}
.cp-sv-cursor {
    width: 14px; height: 14px;
    transform: translate(-7px, -7px);   /* center on x,y */
}
.cp-hue-cursor {
    width: 14px; height: 18px;
    transform: translate(-7px, -2px);   /* center horizontally; nudge up 2px */
    border-radius: 4px;
    background: transparent;
}
.color-picker-section-label {
    font-size: 0.7rem;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
    margin: 0.1rem 0 0.4rem 0;
}
.color-picker-section-label[hidden] { display: none; }
.color-picker-swatches,
.color-picker-customs {
    display: grid; grid-template-columns: repeat(6, 1fr); gap: 0.35rem;
}
.color-picker-swatches { margin-bottom: 0.6rem; }
.color-picker-customs { margin-bottom: 0.2rem; }
.color-picker-sw,
.color-picker-custom-sw {
    width: 100%; aspect-ratio: 1; padding: 0;
    border: 2px solid transparent; border-radius: var(--radius-sm);
    cursor: pointer;
    position: relative;
}
.color-picker-sw:hover,
.color-picker-custom-sw:hover {
    border-color: var(--text); transform: scale(1.06);
}
/* Tiny "×" remove button on hover, top-right corner of a custom swatch.
   Always rendered (CSS opacity transition) so a memo'd click doesn't
   re-trigger layout. Stops propagation in JS so clicking × doesn't pick
   the color. */
.color-picker-custom-sw .cp-rm {
    position: absolute; top: -6px; right: -6px;
    width: 14px; height: 14px; border-radius: 50%;
    background: var(--surface-elevated); color: var(--text);
    border: 1px solid var(--border);
    font-size: 0.65rem; line-height: 12px; padding: 0;
    cursor: pointer;
    opacity: 0; transition: opacity 0.1s;
}
.color-picker-custom-sw:hover .cp-rm { opacity: 1; }
.color-picker-custom-sw .cp-rm:hover {
    color: var(--danger); border-color: var(--danger);
}
.color-picker-custom-row {
    display: flex; gap: 0.35rem; align-items: stretch;
    margin-bottom: 0.65rem;
}
.cp-current {
    flex: 0 0 auto;
    width: 28px; min-height: 28px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: #94a3b8;  /* fallback; updated by JS on open */
}
.color-picker-custom {
    flex: 1; padding: 0.35rem 0.5rem;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg); color: var(--text);
    font-size: 0.82rem; font-family: ui-monospace, monospace;
}
.color-picker-custom:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.color-picker-save {
    flex: 0 0 auto;
    width: 28px; height: auto;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface); color: var(--text);
    font-size: 1rem; line-height: 1; cursor: pointer;
}
.color-picker-save:hover {
    color: var(--primary); border-color: var(--primary);
}
.color-picker-save:disabled {
    color: var(--text-light); border-color: var(--border-light);
    cursor: not-allowed;
}

@media (max-width: 900px) {
    .types-page { grid-template-columns: 1fr; }
    .types-nav { position: static; flex-direction: row; flex-wrap: wrap; }
    .types-nav-item { flex: 1; min-width: 8rem; }
    /* Collapse the 6-col grid into a 2-col stack on narrow screens —
       preview spans the row, then a single line for inputs/actions. */
    .types-row, .types-add {
        grid-template-columns: 1fr auto;
        row-gap: 0.4rem;
    }
    .types-row-preview { grid-column: 1 / -1; }
    .types-row-name { grid-column: 1 / -1; }
}

/* === Stats - module-owned blocks (page /stats, bottom section) =========
   Architecture: each optional module contributes blocks via
   stats_blocks() on the Python side, rendered here by setupStatsBlocks() JS.
   Chart schema: kpi (large number), bar (horizontal), donut (pie). */
.stats-section-head {
    margin: 2rem 0 1rem 0;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border-light);
}
.stats-section-head h2 { margin: 0 0 0.25rem 0; font-size: 1.1rem; }
.stats-section-head p { margin: 0; font-size: 0.85rem; }

.stats-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
    gap: 1rem;
    margin-bottom: 1rem;
}
.stats-block {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1rem 1.1rem;
    box-shadow: var(--shadow-sm);
    display: flex; flex-direction: column;
    min-height: 9rem;
    /* Isolate each card's layout: when an async dashboard swap recomputes
       one chart's canvas/legend, the change can't ripple into neighbour
       cards or the grid above. Combined with the JS min-height pin on
       the body wrapper, eliminates the visible 1-frame jitter we'd see
       on every control click. */
    contain: layout;
}
.stats-block-head {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 0.6rem;
    margin-bottom: 0.7rem;
}
.stats-block-text { min-width: 0; }
.stats-block-text h3 {
    margin: 0 0 0.2rem 0;
    font-size: 0.9rem; font-weight: 600;
}
.stats-block-text .muted {
    margin: 0; font-size: 0.78rem; line-height: 1.35;
}
/* Modules tags container — multi-tags pour les blocs cross-module. */
.stats-block-modules {
    display: flex; flex-wrap: wrap; gap: 0.25rem;
    justify-content: flex-end;
    flex-shrink: 0;
}
.stats-block-module {
    font-size: 0.62rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-muted);
    background: var(--surface-2);
    padding: 0.15rem 0.45rem;
    border-radius: 999px;
    flex-shrink: 0;
    white-space: nowrap;
}
/* Clickable stats-block - the whole card is the click zone to open
   the fullscreen overlay (cf. `data-fullscreen-target` on the wrapper).
   Visible hover/focus to signal the affordance. */
.stats-block.is-clickable {
    cursor: pointer;
    transition: border-color 120ms ease, box-shadow 120ms ease,
                transform 120ms ease;
}
.stats-block.is-clickable:hover {
    border-color: var(--primary);
    box-shadow: var(--shadow);
    transform: translateY(-1px);
}
.stats-block.is-clickable:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* Fullscreen overlay host — wraps the legacy D3 dashboard. Hidden by
   default (relies on the [hidden] attribute). When toggled visible, it
   takes over the viewport. The contained D3 charts measure their
   container at first paint, so opening dispatches a `resize` event
   (cf. setupStatsFullscreen in app.js) which the legacy renderers
   already listen to. */
/* In-flow block inside <main> (already `flex:1` next to the 240px
   sidebar): the detail dashboard occupies the available width like any
   other page and scrolls with the window. No fixed positioning, no
   hardcoded sidebar offset. While it's open the regular grid is hidden
   so it takes over the content area instead of stacking under it. */
.stats-fullscreen-host[hidden] { display: none; }
body.stats-detail-open .page-header,
body.stats-detail-open .stats-grid { display: none; }
/* …mais une `.stats-grid` qui vit DANS un host plein écran ouvert (les
   dashboards module type #prospectsDashboard rangent leurs sous-blocs dans
   une grille) doit rester visible — sinon la règle générique ci-dessus les
   masque. Spécificité (0,4,0) > (0,2,0). */
/* Grille des dashboards module (#prospectsDashboard…) : 2 colonnes
   équilibrées (les KPI se mettent par paire, les graphes `wide` prennent
   toute la largeur) → pas de tiers blanc gaspillé comme avec auto-fill. */
.stats-fs-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;
    margin-bottom: 1rem;
}
.stats-fs-grid .stats-block-wide { grid-column: span 2; }
@media (max-width: 820px) {
    .stats-fs-grid { grid-template-columns: 1fr; }
    .stats-fs-grid .stats-block-wide { grid-column: auto; }
}
/* Rangée KPI en tête de section : jusqu'à 4 KPIs alignés horizontalement.
   La grille s'adapte au nombre réel via `data-count` (1 → 4 colonnes
   pleine largeur, 4 → 4 colonnes 1/4 chacune). Plus compact verticalement
   que le grid 2x2 par défaut, et les chiffres ressortent mieux côte à
   côte. Wrap à 2 colonnes sur tablette, 1 sur mobile. */
.stats-fs-kpi-row {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.8rem;
    margin-bottom: 1rem;
}
.stats-fs-kpi-row[data-count="1"] { grid-template-columns: 1fr; }
.stats-fs-kpi-row[data-count="2"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.stats-fs-kpi-row[data-count="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 1100px) {
    .stats-fs-kpi-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .stats-fs-kpi-row[data-count="3"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
    .stats-fs-kpi-row,
    .stats-fs-kpi-row[data-count="3"] { grid-template-columns: 1fr; }
}
/* Réduit la padding interne et la taille du chiffre quand la card KPI
   est en rangée (cards plus étroites — 1/4 de largeur au lieu d'1/2). */
.stats-fs-kpi-row .stats-block { padding: 0.9rem 1rem; }
.stats-fs-kpi-row .stats-kpi-value { font-size: 1.8rem; }
.stats-fs-bar {
    /* Compact frosted bar à la `.list-toolbar` — same DNA so the user
       reads the sticky state as "this is your control surface for the
       page". Holds the dashboard title (was a separate big H2 above),
       inline controls (unit toggle / year range / breakdown) and the
       right-aligned actions (Imprimer + ×). Translucent surface +
       backdrop-blur lets a hint of the content underneath bleed through
       so the bar feels light when pinned. Hover/focus opacifies the bg
       + lifts the shadow, matching the toolbar pattern. Sits at the
       very top of the host (`top: 0`) — no separate sticky header
       anymore, so no height measurement to maintain.
       `width: fit-content` + auto-margins keep the bar visually tight to
       its actual content and centered within `main`, same as
       `.list-toolbar` — avoids a stretched-edge-to-edge look that felt
       heavy. `max-width: 100%` keeps it from overflowing on narrow
       viewports (it just wraps onto a second row instead). */
    display: flex; flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    align-content: center;
    gap: 0.35rem var(--space-3);
    width: fit-content;
    max-width: 100%;
    margin: 0 auto var(--space-4);
    padding: 0.4rem 0.9rem;
    background: color-mix(in srgb, var(--surface) 60%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    backdrop-filter: blur(14px) saturate(150%);
    border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
    border-radius: 1.25rem;
    box-shadow: var(--shadow-sm);
    position: sticky;
    /* Same 0.6rem breath as `.list-toolbar` so the bar doesn't kiss the
       viewport edge when pinned — keeps a hairline of `main`'s bg above
       it and reads as "floating" rather than "stuck flush". */
    top: 0.6rem;
    z-index: var(--z-dropdown);
    transition: background var(--duration-base) var(--ease-out),
                border-color var(--duration-base) var(--ease-out),
                box-shadow   var(--duration-base) var(--ease-out);
}
.stats-fs-bar:hover,
.stats-fs-bar:focus-within {
    background: color-mix(in srgb, var(--surface) 80%, transparent);
    border-color: var(--border);
    box-shadow: var(--shadow);
}
/* Hint during async fragment fetch — the body fades to indicate stale
   state, the bar stays crisp so the user knows their click was caught.
   Quick fade so the eye registers the change without feeling slow. */
.stats-fs-loading .stats-fs-body {
    opacity: 0.55;
    transition: opacity var(--duration-fast) var(--ease-out);
    pointer-events: none;
}
/* Title inline at the leading edge, same weight/size as `.lt-title`. The
   subtitle hangs in muted text right after the title, separated by a thin
   space — gives narrative context without stealing visual weight. */
.stats-fs-bar-title {
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 600;
    margin: 0;
    color: var(--text);
    display: inline-flex;
    align-items: baseline;
    gap: 0.55rem;
    flex-shrink: 0;
    line-height: 1;
}
.stats-fs-bar-subtitle {
    font-family: var(--font-sans);
    font-size: 0.7rem;
    font-weight: 500;
    color: var(--text-muted);
    letter-spacing: 0;
    text-transform: none;
    /* Reserve a stable slot so a shorter variant ("Nombre · 2025") doesn't
       shrink the title group and pull the controls to the right of it
       across — selecting a single year would otherwise nudge the segmented
       / slider / select left by a few px each time. 8rem fits the longest
       expected combo ("AUM (€) · 2020–2026") with room to spare. */
    display: inline-block;
    min-width: 8rem;
    font-variant-numeric: tabular-nums;
}
/* Actions block pushed to the right by `margin-left: auto`. Stays compact
   and tight to the trailing edge regardless of how many controls fit
   between it and the title. */
.stats-fs-bar-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-left: auto;
    flex-shrink: 0;
}
/* Stacked variant — used by #statsDashboard (activity dashboard) which
   wants the original 2-row controls layout (data row / view row) preserved.
   Rows stack vertically inside the bar, each row laid out like a normal
   inline flex strip (label + control + label + control + …). Width still
   `fit-content` + centered like the single-row variant — the bar grows
   to the widest row's content. */
.stats-fs-bar-stacked {
    flex-direction: column;
    align-items: stretch;
    /* Slightly tighter padding-block since we stack 3 rows already — the
       outer padding compounds visually with each row's intrinsic line
       height. Keeps the bar from feeling chunky. */
    padding: 0.45rem 0.9rem;
    /* Tighter row gap than the wrapped single-row variant. */
    gap: 0.35rem;
}
.stats-fs-bar-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Gap horizontal resserré (0.55rem au lieu de var(--space-3) = 0.75rem)
       pour densifier la rangée de contrôles sans coller les groupes. */
    gap: 0.35rem 0.55rem;
    /* Each row is its own slot inside the bar's flex column; let it grow
       to the bar's full inner width so `margin-left: auto` (on actions /
       trailing buttons) reliably hugs the right edge of the row. */
    width: 100%;
}
.stats-fs-bar-row-head {
    /* The head row's actions block uses the same `margin-left: auto`
       pattern as the single-row variant — no extra rule needed, just
       make sure the row is flex (above). */
}
/* Controls row in the stacked variant — center all items so the strip
   reads as one cohesive cluster, regardless of the bar's width (which
   is sized to the widest row's intrinsic content). */
.stats-fs-bar-row-controls {
    justify-content: center;
}
/* Icon-only action buttons (Download, Imprimer). Same square footprint
   as `.stats-fullscreen-close` so the right side of the bar reads as one
   tight cluster of round controls. Tooltip via native `title=`, real
   label via `aria-label`. */
.stats-fs-bar-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.85rem; height: 1.85rem;
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                color      var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}
.stats-fs-bar-btn svg { width: 1rem; height: 1rem; }
.stats-fs-bar-btn:hover {
    background: var(--surface-2);
    color: var(--text);
    border-color: var(--border);
}
.stats-fs-bar-btn:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}
/* Inline label + control (was stacked vertically — too tall for a sticky
   bar). Mimics the toolbar's compact horizontal density. */
.stats-fs-control { display: flex; align-items: center; gap: 0.35rem; }
.stats-fs-control > span {
    font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--text-muted); font-weight: 600;
}
/* Fixed width (not just min) so picking a longer option doesn't widen
   the select and shift the bar layout. 14rem fits the longest current
   breakdown option ("Secteur de l'apporteur" ~ 22 chars + chevron) with
   enough breathing room to avoid truncation in any browser. */
.stats-fs-control .themed-select { width: 14rem; min-width: 14rem; }
/* Collaborator filter — labels carry their team suffix ("display_name —
   team_name"). Cappé à 13rem (ellipsis prend le relais sur les longs
   noms) pour économiser ~4rem dans la barre par rapport à l'ancien 17rem. */
.stats-fs-control #collabFilter {
    width: 13rem; min-width: 13rem;
    text-overflow: ellipsis;
}
/* Segmented buttons inside the sticky bar — un cran plus serré que la
   variante générique (padding 0.35rem 0.75rem). Gagne ~1rem par segmented
   sans nuire à la cliquabilité. Scopé à `.stats-fs-bar` pour ne pas
   affecter le reste de l'app. */
.stats-fs-bar .segmented > a,
.stats-fs-bar .segmented > button {
    padding: 0.3rem 0.55rem;
    font-size: 0.76rem;
}
/* Section d'un dashboard module (Performance / Refus / Pipeline actuel). */
.stats-fs-section { margin-bottom: 1.6rem; }
.stats-fs-section-head {
    display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
    margin-bottom: 0.8rem; padding-bottom: 0.4rem;
    border-bottom: 2px solid var(--border);
}
.stats-fs-section-head h3 { margin: 0; font-size: 1.05rem; }
.stats-fs-section-head .muted { font-size: 0.8rem; }
/* Contrôle « plage d'années » (double slider) dans la barre de contrôles. */
.stats-fs-range { min-width: 17rem; }
.stats-fs-range .dr-wrap { display: flex; align-items: center; gap: 0.6rem; }
.stats-fs-range .dual-range { flex: 1; min-width: 8rem; }
.stats-fs-range .dr-lbl {
    font-size: 0.8rem; font-weight: 600; color: var(--text);
    font-variant-numeric: tabular-nums; min-width: 2.6em; text-align: center;
}
.stats-fullscreen-head {
    display: flex; justify-content: space-between; align-items: flex-start;
    gap: 1rem;
    margin-bottom: 1rem;
    padding-bottom: 0.8rem;
    border-bottom: 1px solid var(--border);
    /* Sticks to the viewport top (real scroll is on window, cf. `main`
       overflow note); the negative margin lets the band cover `main`'s
       top padding so content doesn't peek above the header. */
    position: sticky; top: 0;
    background: var(--bg);
    padding-top: 1.1rem;
    margin-top: -1.1rem;
    z-index: 1;
}
.stats-fullscreen-head h2 { margin: 0 0 0.2rem 0; font-size: 1.2rem; }
.stats-fullscreen-head p { margin: 0; font-size: 0.85rem; }
.stats-fullscreen-close {
    /* Same footprint as `.stats-fs-bar-btn` (download / print) for a
       consistent action cluster on the right side of the bar. The icon
       is an SVG cross — perfectly centered by the flex parent, unlike
       the previous Unicode `×` which sat slightly above the geometric
       center because of its baseline metrics. */
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.85rem; height: 1.85rem;
    padding: 0;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    flex-shrink: 0;
    transition: background var(--duration-fast) var(--ease-out),
                color      var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}
.stats-fullscreen-close svg { width: 1rem; height: 1rem; }
.stats-fullscreen-close:hover {
    color: var(--text);
    border-color: var(--border-strong);
    background: var(--surface-2);
}
.stats-fullscreen-close:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 1px;
}

/* Wide blocks — line/bar avec beaucoup de points → span 2 colonnes. */
.stats-block-wide { grid-column: span 2; }
.stats-block-wide .stats-line-wrap { width: 100%; }
@media (max-width: 900px) {
    .stats-block-wide { grid-column: auto; }
}

/* Stats toolbar (header right) - one row of selects + segmented. */
.stats-toolbar {
    display: flex; gap: 0.5rem; flex-wrap: wrap;
    align-items: center;
}

/* Segmented control - group of links/buttons joined as a pill. Active
   state via class `.active` (server-rendered) or data-attribute (JS). */
.segmented {
    display: inline-flex;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 2px;
    gap: 0;
}
.segmented > a, .segmented > button {
    padding: 0.35rem 0.75rem;
    font-size: 0.78rem; font-weight: 500;
    color: var(--text-muted);
    text-decoration: none;
    border: 0; background: transparent;
    border-radius: calc(var(--radius-sm) - 2px);
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.segmented > a:hover, .segmented > button:hover {
    color: var(--text);
}
.segmented > a.active, .segmented > button.active {
    background: var(--surface);
    color: var(--primary);
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
    font-weight: 600;
}

.stats-block-canvas { flex: 1; display: flex; align-items: center; }

/* Panneau Info (option menu ⋯ → « Info ») — texte explicatif posé sous
   le header, au-dessus du canvas. Texte HTML construit serveur-side
   selon l'état courant des sélecteurs (vue, unité, cumul, dim…). */
.stats-block-info {
    margin: 0.4rem 0 0.6rem;
    padding: 0.6rem 0.8rem;
    background: var(--primary-light);
    border-left: 3px solid var(--primary);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 0.8rem;
    line-height: 1.5;
}
.stats-block-info p { margin: 0 0 0.5rem; }
.stats-block-info p:last-child { margin-bottom: 0; }
.stats-block-info strong { font-weight: 600; color: var(--primary); }
.stats-block-info em { font-style: italic; color: var(--text-muted); }
/* Bloc de récap des sélecteurs courants — un cran en-dessous du corps
   visuellement (italique, fond plus dilué) pour ne pas distraire. */
.stats-block-info .info-ctx {
    margin-top: 0.6rem;
    padding-top: 0.5rem;
    border-top: 1px dashed var(--border);
    color: var(--text-muted);
    font-size: 0.76rem;
}
.stats-block-info .info-ctx strong { color: var(--text-muted); font-weight: 600; }
/* Mise en garde (Σ ≠ Total, filtre actif…) — accent warning. */
.stats-block-info .info-warn {
    margin-top: 0.5rem;
    padding: 0.4rem 0.55rem;
    background: var(--warning-light);
    border-left: 2px solid var(--warning);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 0.76rem;
}
.stats-block-info .info-warn strong { color: var(--warning); }
/* When the canvas is in table view (gear → Vue tableau), it stacks the
   `.stats-table-wrap` + `.stats-table-footer` vertically instead of the
   default centred flex-row used for SVG charts. */
.stats-block-canvas:has(.stats-table-wrap) {
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
}

/* === Line chart (time series) === */
.stats-line-wrap { width: 100%; }
/* viewBox 600×140 (1100×140 pour .stats-block-wide, cf. renderLine) + meet +
   height:auto → la courbe garde son ratio (plus de déformation : points ronds,
   texte non écrasé). La hauteur suit la largeur via l'aspect intrinsèque du
   viewBox ; le viewBox plus large des blocs pleine largeur les garde compacts
   au lieu de ballonner à la hauteur du ratio 600:140. */
.stats-line-svg { display: block; width: 100%; height: auto; }
.stats-line-axis { stroke: var(--border-light); stroke-width: 1; }
.stats-line-grid { stroke: var(--border-light); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.6; }
.stats-line-tick {
    fill: var(--text-light);
    font-size: 9px;
    font-family: ui-monospace, monospace;
}
/* Inline message in the plot area when every series is toggled off
   (cf. renderLine). The grid stays so the user keeps frame context. */
.stats-line-empty {
    fill: var(--text-muted);
    font-size: 11px;
    font-style: italic;
}
.stats-line-path { fill: none; stroke-width: 2; }
.stats-line-dot { stroke: var(--surface); stroke-width: 1.5; }
/* Grand-total overlay line (dashed, theme text colour) — cf. renderLine */
.stats-line-total { stroke: var(--text); stroke-width: 2; stroke-dasharray: 5 3; }
.stats-line-dot-total { fill: var(--text); }
.stats-line-legend-total .stats-line-legend-dot.is-dashed {
    background: transparent;
    border: 1px dashed var(--text);
    box-shadow: none;
}
/* Static legend key — explains solid vs dashed convention when chips
   bundle multi-styled sub-series. Non-clickable, lives at the end of
   the legend after a thin separator. */
.stats-line-legend-sep {
    display: inline-block;
    width: 1px; height: 1rem;
    background: var(--border);
    margin: 0 0.35rem;
    vertical-align: middle;
}
.stats-line-legend-key {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.72rem;
    color: var(--text-muted);
    font-style: italic;
    user-select: none;
    cursor: default;
}
.stats-line-legend-keyline {
    width: 28px;
    height: 6px;
    color: var(--text-muted);
}
.stats-line-legend {
    display: flex; gap: 0.35rem 0.5rem; flex-wrap: wrap;
    margin-top: 0.8rem; padding-top: 0.6rem;
    border-top: 1px solid var(--border-light);
    font-size: 0.77rem;
}
/* Pilule = affordance « cliquable » (le petit oval) + état actif net :
   pilule pleine + halo coloré autour du point pour faire ressortir la
   série. État off = pilule transparente, texte atténué, point grisé.
   Calqué sur la légende du dashboard activité (.chart-legend). */
.stats-line-legend-item {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.22rem 0.6rem;
    background: var(--surface-2); border: 1px solid var(--border);
    border-radius: 999px;
    color: var(--text); font-weight: 500;
    cursor: pointer; user-select: none;
    transition: background 0.12s, border-color 0.12s, color 0.12s, opacity 0.12s;
}
.stats-line-legend-item:hover { border-color: var(--primary); background: var(--primary-light); }
.stats-line-legend-dot {
    width: 10px; height: 10px; border-radius: 50%;
    box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 22%, transparent);
    transition: box-shadow 0.12s, filter 0.12s;
}
.stats-line-legend-item.off {
    opacity: 0.62; font-weight: 400;
    color: var(--text-muted); background: transparent;
}
.stats-line-legend-item.off .stats-line-legend-dot { filter: grayscale(0.55); box-shadow: none; }

/* === Year range — dual slider widget ============================== */
.year-range {
    display: flex; align-items: center; gap: 0.6rem;
    font-size: 0.78rem; font-family: ui-monospace, monospace;
    color: var(--text-muted);
}
.year-range .yr-label-min,
.year-range .yr-label-max {
    min-width: 2.6rem; text-align: center;
    color: var(--text); font-weight: 600;
}
.yr-track {
    position: relative;
    width: 11rem; height: 28px;
    display: flex; align-items: center;
}
/* Grey rail in the background + indigo fill between the two thumbs
   (positioned by JS via inline style). */
.yr-track::before {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    height: 4px; transform: translateY(-50%);
    background: var(--surface-2);
    border-radius: 999px;
}
.yr-fill {
    position: absolute; top: 50%; height: 4px;
    transform: translateY(-50%);
    background: var(--primary);
    border-radius: 999px;
    pointer-events: none;
}
/* Both range inputs are stacked transparent - only their thumbs are
   visible. Pointer-events: none on the track masks the thumbs except
   in their immediate vicinity. */
.yr-track input[type="range"] {
    position: absolute; left: 0; right: 0; top: 0; bottom: 0;
    width: 100%; height: 100%;
    background: transparent;
    pointer-events: none;
    appearance: none; -webkit-appearance: none;
    margin: 0; padding: 0;
}
.yr-track input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--primary);
    border: 2px solid var(--surface);
    cursor: grab;
    pointer-events: auto;
    box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.yr-track input[type="range"]::-moz-range-thumb {
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--primary);
    border: 2px solid var(--surface);
    cursor: grab;
    pointer-events: auto;
}
.yr-track input[type="range"]::-webkit-slider-thumb:active { cursor: grabbing; }

/* === Radar / spider chart ========================================= */
.stats-radar-wrap {
    display: flex; flex-direction: column; align-items: center; gap: 0.7rem;
    width: 100%;
}
/* viewBox élargi à ~420×280 (LABEL_PAD horizontal pour les labels longs).
   Aspect ratio ~1.5:1 → le radar reste compact verticalement. max-width
   bumpé en proportion pour que le diamètre du polygone reste visuellement
   à peu près le même qu'avec l'ancien viewBox carré. */
.stats-radar-svg { display: block; width: 100%; max-width: 460px; height: auto; }
#radarChart .stats-radar-svg { max-width: 540px; }
.stats-radar-grid { fill: none; stroke: var(--border-light); stroke-width: 1; }
.stats-radar-axis { stroke: var(--border-light); stroke-width: 1; opacity: 0.5; }
.stats-radar-label {
    fill: var(--text-muted);
    font-size: 9px;
    /* text-anchor is set per-label inline by renderRadar based on the
       label's angle around the radar (start / middle / end) so long
       labels stay within the viewBox. Don't override here — CSS would
       win against the SVG attribute. */
    font-family: ui-monospace, monospace;
}
/* Per-axis value labels (settings → « Afficher les valeurs ») —
   un peu plus saturés que les axis labels pour ressortir, en
   tabular-nums pour aligner. text-anchor reste piloté inline. */
.stats-radar-value {
    fill: var(--text);
    font-size: 9.5px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    font-family: ui-monospace, monospace;
}
.stats-radar-poly {
    fill-opacity: 0.18;
    stroke-width: 1.6;
}
/* Total polygon — dashed reference overlay, no fill, theme text colour.
   Symmetric with `.stats-line-total` on the line chart. */
.stats-radar-poly-total {
    fill: none;
    stroke: var(--text);
    stroke-width: 2;
    stroke-dasharray: 5 3;
}

/* KPI - large figure + underlying label (already handled by subtitle in the head). */
.stats-block[data-chart="kpi"] .stats-block-canvas {
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
}
.stats-kpi-value {
    font-size: 2.4rem; font-weight: 700;
    color: var(--primary); line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* Bar - horizontal bars + label, value on the right. */
.stats-bar { width: 100%; display: flex; flex-direction: column; gap: 0.5rem; }
.stats-bar-row {
    display: grid;
    grid-template-columns: minmax(4rem, 0.6fr) 1fr auto;
    gap: 0.6rem; align-items: center;
    font-size: 0.82rem;
}
.stats-bar-label {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--text-muted);
}
.stats-bar-track {
    height: 8px; background: var(--surface-2);
    border-radius: 999px; overflow: hidden;
}
.stats-bar-fill { height: 100%; border-radius: 999px; }
.stats-bar-value {
    font-variant-numeric: tabular-nums;
    color: var(--text); font-weight: 600;
}

/* Donut - inline SVG, legend on the right. */
.stats-donut-wrap {
    display: flex; align-items: center; gap: 1rem; width: 100%;
}
.stats-donut-svg { flex-shrink: 0; }
.stats-donut-legend {
    display: flex; flex-direction: column; gap: 0.3rem;
    font-size: 0.78rem; flex: 1; min-width: 0;
}
.stats-donut-legend-item {
    display: flex; align-items: center; gap: 0.4rem;
}
.stats-donut-dot {
    width: 8px; height: 8px; border-radius: 50%;
    flex-shrink: 0;
}
.stats-donut-label {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--text-muted);
}
.stats-donut-value {
    margin-left: auto; font-variant-numeric: tabular-nums;
    color: var(--text); font-weight: 600;
    white-space: nowrap;
}
.stats-donut-pct { color: var(--text-muted); font-weight: 500; }
/* Donut agrandi (132px) : total + libellé au centre via <text> SVG. */
.stats-donut-center-val { fill: var(--text); font-size: 22px; font-weight: 700; }
.stats-donut-center-lbl { fill: var(--text-muted); font-size: 9px; text-transform: uppercase; letter-spacing: 0.04em; }

/* KPI : tendance (▲/▼ points) + ligne de contexte sous la valeur. */
.stats-kpi-trend { font-size: 0.8rem; font-weight: 600; margin-top: 0.3rem; }
.stats-kpi-trend.is-up { color: var(--success); }
.stats-kpi-trend.is-down { color: var(--danger); }
.stats-kpi-sub { font-size: 0.78rem; color: var(--text-muted); margin-top: 0.25rem; }

/* Bar : sous-libellé (ex. montant €, « 2 gagnés ») sous la valeur. */
.stats-bar-value { text-align: right; line-height: 1.2; }
.stats-bar-sub {
    display: block; font-size: 0.7rem; font-weight: 400; color: var(--text-muted);
}

/* Lien drill-down dans l'entête d'un bloc → /prospects?status=… */
.stats-block-link {
    font-size: 0.78rem; font-weight: 600; color: var(--primary);
    white-space: nowrap; flex-shrink: 0; align-self: flex-start;
}
.stats-block-link:hover { text-decoration: underline; }

/* Per-block segmented (e.g. Signature/Traitement/Les deux on the delay
   chart). Compact variant of `.segmented` for the top-right of a block
   header. Smaller font, tighter padding so it doesn't overshadow the
   block title.
   `margin-left: auto` pushes the segmented to the right edge of the
   header — without it, `.stats-block-head`'s `justify-content:
   space-between` would scatter the chips in the middle of the available
   space (because the gear-icon below sits as a 3rd flex item to the
   right of both text and segmented). With auto-margin, segmented +
   settings cluster tight to the right edge like a cohesive action zone. */
.stats-block-ctrl.segmented {
    align-self: flex-start;
    flex-shrink: 0;
    margin-left: auto;
}
.stats-block-ctrl.segmented button {
    padding: 0.22rem 0.55rem;
    font-size: 0.72rem;
}

/* Per-chart settings popover (gear / 3-dots menu in the block header).
   Toggles legend / values / yzero / smooth on line+radar charts.
   Uses a native <details>: summary = trigger button, content =
   absolutely-positioned popover below. */
.stats-block-settings {
    position: relative;
    align-self: flex-start;
    flex-shrink: 0;
}
.stats-block-settings-trigger {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.6rem; height: 1.6rem;
    padding: 0;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    list-style: none;
    transition: background var(--duration-fast) var(--ease-out),
                color      var(--duration-fast) var(--ease-out);
}
.stats-block-settings-trigger::-webkit-details-marker { display: none; }
.stats-block-settings-trigger svg { width: 1rem; height: 1rem; }
.stats-block-settings-trigger:hover { color: var(--text); background: var(--surface-2); }
.stats-block-settings[open] .stats-block-settings-trigger {
    background: var(--surface-2); color: var(--text);
}
.stats-block-settings-pop {
    position: absolute;
    top: calc(100% + 0.3rem); right: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: 0.4rem 0.5rem;
    min-width: 12.5rem;
    z-index: var(--z-popover);
    display: flex; flex-direction: column; gap: 0.15rem;
}
.stats-block-settings-pop label {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.32rem 0.4rem;
    border-radius: var(--radius-sm);
    font-size: 0.8rem;
    color: var(--text);
    cursor: pointer;
    user-select: none;
}
.stats-block-settings-pop label:hover { background: var(--surface-2); }
/* Button items inside the popover (e.g. "Télécharger en CSV" on the
   activity charts where there's no boolean toggle, just an action). */
.stats-block-settings-action {
    display: flex; align-items: center; gap: 0.5rem;
    width: 100%;
    padding: 0.32rem 0.4rem;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: 0.8rem;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}
.stats-block-settings-action:hover { background: var(--surface-2); }
.stats-block-settings-pop input[type="checkbox"] {
    width: 0.95rem; height: 0.95rem;
    margin: 0; cursor: pointer; accent-color: var(--primary);
}

/* Optional in-chart value labels (gear → "Afficher les valeurs"). Small
   muted text floats above each data point. */
.stats-line-value {
    fill: var(--text-muted);
    font-size: 9px;
    font-family: ui-monospace, monospace;
    pointer-events: none;
}

/* Separator inside the chart-settings popover. */
.stats-block-settings-sep {
    height: 1px; background: var(--border-light);
    border: 0; margin: 0.3rem -0.1rem;
}

/* Table view (gear → "Vue tableau"). Replaces the SVG with a compact
   HTML matrix of the same data + a "Télécharger en CSV" action at the
   bottom-right that exports verbatim what's on screen. */
.stats-table-wrap {
    overflow-x: auto;
    margin-bottom: 0.5rem;
}
.stats-table-mini {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
}
.stats-table-mini th,
.stats-table-mini td {
    padding: 0.32rem 0.55rem;
    text-align: right;
    border-bottom: 1px solid var(--border-light);
    white-space: nowrap;
}
.stats-table-mini thead th {
    font-weight: 600;
    color: var(--text-muted);
    text-transform: uppercase;
    font-size: 0.66rem;
    letter-spacing: 0.05em;
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.stats-table-mini tbody th {
    text-align: left;
    font-weight: 500;
    color: var(--text);
}
.stats-table-mini tbody tr:last-child th,
.stats-table-mini tbody tr:last-child td { border-bottom: 0; }
.stats-table-mini tr.stats-table-total {
    border-top: 1px solid var(--border);
}
.stats-table-mini tr.stats-table-total th,
.stats-table-mini tr.stats-table-total td {
    font-weight: 600;
    color: var(--text);
    border-top: 1px solid var(--border);
}
/* Bouton CSV en bas à droite — dans son propre footer flex pour
   rester aligné au bord droit sans float (qui s'empilait par-dessus
   le tableau quand celui-ci scrollait). */
.stats-table-footer {
    display: flex; justify-content: flex-end;
    margin-top: 0.5rem;
}
.stats-table-csv {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.32rem 0.6rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: 0.72rem; font-weight: 500;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                color      var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}
.stats-table-csv svg { width: 0.9rem; height: 0.9rem; }
.stats-table-csv:hover {
    color: var(--text);
    background: var(--surface-2);
    border-color: var(--border-strong);
}

/* Bandeau « filtré sur … » en tête d'une liste (drill-down depuis /stats). */
.filter-banner {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    padding: 0.6rem 0.9rem; margin-bottom: 1rem;
    background: var(--primary-light); border: 1px solid var(--border);
    border-radius: var(--radius); font-size: 0.85rem;
}
.filter-banner-clear { font-weight: 600; color: var(--primary); white-space: nowrap; }
.filter-banner-clear:hover { text-decoration: underline; }

/* === Responsive (single breakpoint at 1280) =================================
   Below 1280px (very common: 13" laptops, 1366×768 office monitors), the
   240px sidebar + 2.5rem horizontal main padding leaves ~1000px for content,
   which is too tight for our list tables. We reduce paddings rather than
   collapsing the sidebar to icons — the icons-only mode is a bigger UX
   shift, and the tables already scroll horizontally via `.table-wrap`.
   ============================================================================ */
@media (max-width: 1280px) {
    .sidebar { width: 196px; }
    main { padding: 0.85rem 1.1rem 1.2rem; --main-pad-x: 1.1rem; }
    .card { padding: 0.7rem 0.85rem; }
    th { padding: 0.3rem 0.5rem; }
    td { padding: 0.36rem 0.5rem; }
    th:first-child, td:first-child { min-width: 150px; }
}

/* ============================================================================
   Responsive (full mobile/tablet support)
   --------------------------------------------------------------------------
   Three breakpoints below 1280:
     1024 — tablet landscape: sidebar collapses to drawer, hamburger appears
      720 — tablet portrait : tables convert to "card list", detail-top mini-
            graph hidden, page-header CTA wraps below title
      480 — phone           : everything tightened further, search bars and
            inputs full-width, KPI strip in 2-col grid

   The drawer is purely state on <html data-sidebar-open>, toggled in app.js
   (setupSidebarBurger). Closes on overlay click, Escape, and after every
   navigation (the click on a sidebar nav link doesn't intercept — the new
   page just mounts without the attribute, which auto-closes it).
   ============================================================================ */

/* --- Burger + overlay (always in DOM, hidden on desktop) ----------------- */
.sidebar-burger {
    display: none;
    position: fixed;
    top: 0.55rem; left: 0.55rem;
    z-index: 60;
    width: 38px; height: 38px;
    align-items: center; justify-content: center;
    padding: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    cursor: pointer;
    box-shadow: var(--shadow-sm);
}
.sidebar-burger:hover { background: var(--surface-2); }
.sidebar-overlay {
    display: none;
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 40;
    opacity: 0;
    transition: opacity 0.18s;
}
[data-sidebar-open] .sidebar-overlay { opacity: 1; }

/* --- < 1024: drawer mode ------------------------------------------------- */
@media (max-width: 1023px) {
    .sidebar-burger { display: inline-flex; }
    .sidebar {
        position: fixed;
        top: 0; left: 0;
        /* `dvh` (dynamic viewport height) accounts for iOS Safari's
           collapsing URL bar and the on-screen keyboard, unlike `vh`
           which is fixed at the largest viewport. */
        height: 100dvh;
        /* Fluid drawer width — narrow enough on tiny phones to leave a
           generous overlay tap-zone, capped on larger devices to avoid
           the drawer feeling "too wide". */
        width: clamp(240px, 80vw, 320px);
        z-index: 50;
        transform: translateX(-100%);
        transition: transform 0.22s ease;
        box-shadow: var(--shadow-lg);
        /* Disable the icon-only auto-collapse in drawer mode — the @container
           query thinks the sidebar is narrow when it's actually fixed-positioned
           at 240px and otherwise correct. We force the labels back. */
        container-type: normal;
    }
    /* Below 360px (iPhone SE in portrait), give even more of the screen
       to the drawer so labels don't compress, while still leaving a
       tappable overlay strip. */
    @media (max-width: 360px) {
        .sidebar { width: 88vw; }
    }
    .sidebar .brand span:not(.logo),
    .sidebar nav a span,
    .sidebar .nav-label,
    .sidebar .ws-switcher-btn span,
    .sidebar-foot-label,
    .sidebar-user-name { display: inline !important; }
    .sidebar nav a { justify-content: flex-start !important; padding: 0.4rem 0.75rem !important; }
    .sidebar nav a span { display: inline-block !important; }
    [data-sidebar-open] .sidebar { transform: translateX(0); }
    [data-sidebar-open] .sidebar-overlay { display: block; }
    [data-sidebar-open] body { overflow: hidden; }
    /* Push main content to make room for the burger */
    main { padding-top: 3.2rem; }
    main.fullbleed { padding-top: 0; }
    /* Detail-top already stacks at 1100; fine here too */
}

/* --- < 900: tablet-portrait + phones, tables collapse to cards ---------- */
@media (max-width: 900px) {
    main { padding: 3.2rem 0.7rem 1rem; --main-pad-x: 0.7rem; }
    .page-header {
        flex-direction: column;
        align-items: stretch;
        gap: 0.6rem;
    }
    .page-header h1 { font-size: 1.05rem; }
    .page-header .btn { align-self: flex-start; }

    /* Tables → vertical cards. Each row stacks; each cell shows its column
       label (injected as `data-label` by hydrateRowLabels in app.js). The
       Excel filter chrome is hidden — phones don't need column-by-column
       sort/filter, the search bar above suffices. */
    .table-wrap { overflow-x: visible; margin: 0; padding: 0; }
    table, thead, tbody, tr, td { display: block; width: 100%; }
    thead { display: none; }
    tbody tr {
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--radius-sm);
        margin-bottom: 0.5rem;
        padding: 0.5rem 0.7rem;
    }
    tbody tr:nth-child(even) td { background: transparent; }
    tbody tr:hover td { background: transparent; }
    tbody tr[data-href]:hover td:first-child { box-shadow: none; }
    tbody tr[data-href] { box-shadow: none; }
    tbody tr[data-href]:active { background: var(--surface-2); }
    tbody td {
        padding: 0.25rem 0;
        border: 0;
        display: flex; align-items: center; justify-content: space-between;
        gap: 0.7rem;
        font-size: 0.85rem;
    }
    /* Show the column label on the left of each cell value */
    tbody td[data-label]::before {
        content: attr(data-label);
        font-size: 0.65rem; text-transform: uppercase;
        letter-spacing: 0.05em; color: var(--text-muted);
        font-weight: 600; flex-shrink: 0;
    }
    /* First cell (entity name) gets bigger emphasis, no label */
    tbody td:first-child {
        font-size: 0.95rem; font-weight: 600;
        margin-bottom: 0.3rem;
        padding-bottom: 0.4rem;
        border-bottom: 1px solid var(--border-light);
    }
    tbody td:first-child::before { display: none; }
    th:first-child, td:first-child { min-width: 0; }
    /* Action cells (delete buttons) align right, no label */
    tbody td:last-child { justify-content: flex-end; padding-top: 0.3rem; }
    tbody td:last-child::before { display: none; }
    /* Sur mobile, l'icône poubelle finale du `td:last-child` est rendue
       discrète + grossie par le tap-target 44px → ça crée ~50px d'espace
       quasi vide en bas de chaque card. Le swipe-to-delete (cf.
       `.swipeable` plus bas) couvre déjà l'action de suppression, donc on
       masque le bouton et on récupère la place. La classe `.action-only-delete`
       est posée par JS (app.js → markActionOnlyDeleteCells) car `:has` n'est
       pas supporté par tous les browsers cibles. */
    tbody td.action-only-delete { display: none; }

    /* Empty rows keep their full-width treatment */
    tbody tr:has(td.empty) { padding: 0; border: 0; background: transparent; }
    tbody td.empty { display: block; padding: 1.5rem 1rem !important; }
    tbody td.empty::before { display: none; }

    /* Detail page : hide the side mini-graph entirely on phone — it's
       cramped and unreadable. The fiche has the link to /connections for
       deep exploration. */
    .detail-top-graph { display: none; }
    .hero { flex-direction: column; align-items: flex-start; gap: 0.7rem; }
    .hero-actions { width: 100%; flex-direction: row; }
    .hero-actions.stacked { flex-direction: row; }
    .hero-actions.stacked > .btn,
    .hero-actions.stacked > form > .btn { width: auto; flex: 1; }

    /* Page-header search bar full-width with no extra margin */
    .search { padding: 0.2rem 0.5rem; }
    /* iOS auto-zooms text fields < 16px on focus. Bump every text input
       to 16px on mobile to suppress that — keeps the zoom locked. */
    input[type="text"],
    input[type="email"],
    input[type="password"],
    input[type="search"],
    input[type="number"],
    input[type="tel"],
    input[type="url"],
    input[type="date"],
    input[type="datetime-local"],
    select, textarea { font-size: 16px !important; }

    /* Forms: any grid collapses to 1 column. Catches both `.form-grid`
       and inline `style="grid-template-columns: 1fr 1fr"` on bare divs
       (used in persons/form.html, meetings/form.html, etc.). The
       `!important` beats inline non-important style declarations. */
    .form-grid,
    [style*="grid-template-columns"] { grid-template-columns: 1fr !important; }
    /* Override inline styles that prevent wrap on table cells. Used in
       prospects/list.html (notes cell) and similar. The card layout
       already constrains width — let text wrap naturally. */
    tbody td[style*="nowrap"],
    tbody td[style*="ellipsis"] {
        white-space: normal !important;
        max-width: none !important;
        overflow: visible !important;
        text-overflow: clip !important;
    }
    /* Stats cards : when in flex layout with the right-side category tag,
       the title gets squeezed into a 1-word column. Force the bento card
       header into block layout on mobile so title spans full width. */
    .stats-block-head {
        flex-direction: column !important;
        align-items: flex-start !important;
        gap: 0.4rem !important;
    }
    .stats-block-text { width: 100% !important; }
    .stats-block-modules { width: 100% !important; }
    /* Settings nav (admin pages) — base CSS uses `display: grid;
       grid-template-columns: 15rem 1fr` plus a `margin: -2rem -2.5rem`
       to bleed past the main padding. On mobile that negative margin
       pushes content offscreen left. We cancel both: stack to one column
       and zero the bleed margin. */
    .settings-page {
        display: block !important;
        margin: 0 !important;
        min-height: 0 !important;
    }
    .settings-nav {
        width: 100% !important;
        position: static !important;
        padding: 0.5rem 0 !important;
        border-right: 0 !important;
        border-bottom: 1px solid var(--border) !important;
    }
    .settings-main { padding: 0.5rem 0 !important; }

    /* Stats cards stack 1-col */
    .stats-grid, .stats-bento { grid-template-columns: 1fr !important; }

    /* Crumbs : ellide on long paths */
    .crumbs { font-size: 0.78rem; }
}

/* --- < 480: small phone tweaks ------------------------------------------- */
@media (max-width: 480px) {
    main { padding: 3.2rem 0.5rem 1rem; --main-pad-x: 0.5rem; }
    .card { padding: 0.6rem 0.7rem; border-radius: var(--radius-sm); }
    .crumbs { font-size: 0.72rem; }
    .page-header h1 { font-size: 0.98rem; }
    .ws-switcher-btn { font-size: 0.78rem; }
}

/* --- Graph dashboard mobile: simplified layout --------------------------
   On phones/small tablets the dashboard's panels (left filters, right
   exploration preview, bottom create-form, drawer handles, resize bars)
   make no sense — there's no room. We hide them and keep only what
   matters for browsing on a touch screen:
     - the canvas (full bleed, pinch-zoom + drag pan via D3)
     - a slim toolbar with search + galaxy (top row only)
     - the read-only banner
     - the picker (cold start)
   The legend overlay also gets shrunk into a bottom-right toggle.
   ------------------------------------------------------------------------ */
/* Legacy `@media (max-width: 900px) .graph-page .gp-*` block retiré le
   2026-05-11 — référençait #gpLeft / #gpBottom / .gp-toolbar-row-* / autres
   éléments du panneau historique disparu en mai 2026, et écrasait avec
   !important les positions de la toolbar single-row actuelle. Le responsif
   du dashboard graph vit désormais entièrement dans static/graph.css
   (paliers 900 / 600 / 420 px). */

/* --- Swipe-to-delete on mobile cards -----------------------------------
   When a `<tr>` is bound by setupSwipeToDelete in app.js, it gets
   `.swipeable` and a hidden `.swipe-reveal` child. CSS handles the
   layering: the reveal sits behind the row content (absolutely
   positioned, full height), painted red. The row slides left over it
   during the touch drag, and either snaps back or completes the slide
   off-screen → form.submit(). Desktop is unaffected — JS bails out
   above 900px and on non-touch devices. */
@media (max-width: 900px) {
    tbody tr.swipeable {
        position: relative;
        overflow: hidden;
        touch-action: pan-y;  /* allow vertical scroll, capture horizontal */
    }
    .swipe-reveal {
        position: absolute;
        top: 0; right: 0; bottom: 0;
        display: flex; align-items: center; justify-content: flex-end;
        gap: 0.4rem;
        padding: 0 1.1rem;
        background: var(--danger);
        color: var(--on-danger);
        font-weight: 600; font-size: 0.78rem;
        letter-spacing: 0.02em;
        opacity: 0;
        pointer-events: none;
        z-index: 0;
        border-radius: var(--radius-sm);
    }
    .swipe-reveal svg { color: var(--on-danger); }
}

/* --- Touch targets ≥ 44px (Apple HIG / Material guideline) -------------
   Triggered on any device that uses a coarse pointer (finger / stylus)
   regardless of viewport width — covers iPad Pro in landscape too. */
@media (pointer: coarse), (max-width: 900px) {
    .btn,
    button:not(.theme-toggle):not(.density-toggle),
    .sidebar nav a,
    .sidebar-burger,
    .delete-form button,
    .filter-pill,
    .empty-cta,
    .ws-switcher-btn,
    .sidebar-user-link,
    .sidebar-logout-btn { min-height: 44px; }
    /* Icon-only round buttons get min-width too so the tap circle is square */
    .delete-form button,
    .gp-toolbar button,
    .sidebar-burger,
    .sidebar-logout-btn,
    .fab { min-width: 44px; }
    /* Trash icon in cards: bigger hit area, padding > visual icon */
    .delete-form button svg,
    .delete-form button { padding: 0.55rem; }
    /* Spacing between adjacent tap targets in a row to prevent mistaps */
    tbody tr td:last-child { gap: 0.6rem; }
}

/* --- Subtitle desktop-only -------------------------------------------------
   The "clique sur en-tête, ▽ pour filtrer" hint references the table
   chrome that's `display: none` in card mode. Hide it on mobile to avoid
   confusing users about UI that doesn't exist. */
@media (max-width: 900px) {
    .subtitle-desktop-only { display: none; }
}

/* --- Safe-area for notched phones / home indicator -------------------------
   Anything fixed at the bottom of the viewport (FAB) needs to sit above the
   iPhone home indicator. We bake `env(safe-area-inset-bottom)` into the
   bottom offset directly. */
@supports (padding: env(safe-area-inset-bottom)) {
    .fab { bottom: calc(1rem + env(safe-area-inset-bottom)); }
}

/* --- Floating Action Button (mobile create CTA) ----------------------------
   Visible <720px on list pages where _fab_map matched (cf. base.html). The
   FAB carries the "+ Nouvelle X" intent on mobile so we can hide the
   page-header outlined CTA there — single source of action. The top CTA
   stays on tablets/desktop. */
.fab {
    display: none;
    position: fixed;
    bottom: 1rem;
    right: 1rem;
    width: 56px; height: 56px;
    align-items: center; justify-content: center;
    background: var(--primary); color: var(--on-primary);
    border-radius: 50%;
    box-shadow: 0 6px 20px -4px rgba(108, 122, 237, 0.5),
                0 4px 12px -2px rgba(0, 0, 0, 0.3);
    z-index: 35;
    text-decoration: none;
}
.fab:hover { background: var(--primary-hover); }
.fab:active { transform: scale(0.95); }
@media (max-width: 720px) {
    .fab { display: inline-flex; }
    .page-header .btn-primary { display: none; }
}

/* --- Confirm modal (custom replacement for native confirm()) ---------------
   Triggered by `<form data-confirm="...">` via static/app.js (setupConfirmForms).
   Overlay covers the viewport, the card centers, both fade in via the
   `.is-open` class. Tokens only — no hardcoded color. */
.modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9000;
    opacity: 0;
    transition: opacity 120ms ease;
    padding: 1rem;
}
.modal-overlay[hidden] { display: none; }
.modal-overlay.is-open { opacity: 1; }
.modal-card {
    background: var(--surface-elevated);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    width: 100%;
    max-width: 480px;
    padding: 1.4rem 1.5rem 1.2rem;
    transform: translateY(8px);
    transition: transform 120ms ease;
}
.modal-overlay.is-open .modal-card { transform: translateY(0); }
.modal-card .modal-icon {
    width: 44px; height: 44px;
    border-radius: 50%;
    background: var(--danger-light);
    color: var(--danger);
    display: inline-flex;
    align-items: center; justify-content: center;
    margin-bottom: 0.8rem;
}
.modal-card .modal-icon svg { width: 22px; height: 22px; }
.modal-card h3 {
    margin: 0 0 0.5rem;
    font-size: 1.05rem;
    font-weight: 600;
}
.modal-card .modal-body {
    margin: 0 0 1.2rem;
    color: var(--text-muted);
    line-height: 1.45;
    white-space: pre-line;
}
.modal-card .modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}

/* === Modal "form" (utilisé par l'éditeur de vue /persons) ==============
   Différent du `.modal-overlay`/`.modal-card` du confirm-modal : ici on a
   un panneau plus large avec header (titre + close), body scrollable, et
   footer (actions). Pattern Apple-like (Numbers / Pages settings). */
.modal {
    position: fixed; inset: 0;
    z-index: var(--z-modal);
    display: flex; align-items: center; justify-content: center;
    padding: 1rem;
}
.modal[hidden] { display: none; }
.modal-backdrop {
    position: absolute; inset: 0;
    background: rgba(0, 0, 0, 0.5);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}
.modal-panel {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    width: 100%;
    max-width: 520px;
    max-height: calc(100vh - 2rem);
    display: flex; flex-direction: column;
    overflow: hidden;
    animation: modal-pop var(--duration-base) var(--ease-out);
}
.modal-panel-md { max-width: 600px; }
@keyframes modal-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-header,
.modal-footer {
    padding: 0.85rem 1.2rem;
    display: flex; align-items: center;
}
.modal-header {
    justify-content: space-between;
    border-bottom: 1px solid var(--border-light);
}
.modal-header h2 {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    font-family: var(--font-sans);  /* override le serif du h2 par défaut — pas de hero ici */
}
.modal-close {
    background: transparent; border: 0;
    color: var(--text-muted);
    font-size: 1.4rem; line-height: 1;
    width: 1.8rem; height: 1.8rem;
    border-radius: 50%;
    cursor: pointer;
    box-shadow: none;
    padding: 0;
}
.modal-close:hover { background: var(--surface-2); color: var(--text); }
.modal-body {
    padding: 1rem 1.2rem;
    overflow-y: auto;
    /* `flex: 1 1 auto; min-height: 0` permet au body de rétrécir dans le
       parent et de prendre le relais via `overflow-y: auto`. Sans
       `min-height: 0`, l'élément flex hérite de `min-height: auto` (=
       hauteur de son contenu) et ignore l'overflow → le panel déborde
       du viewport en haut, le header sort hors écran. Bug typique flex
       + overflow. */
    flex: 1 1 auto;
    min-height: 0;
    display: flex; flex-direction: column;
    gap: 1rem;
}
.modal-footer {
    justify-content: flex-end;
    gap: 0.5rem;
    border-top: 1px solid var(--border-light);
    background: var(--surface-2);
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.modal .form-row {
    display: flex; flex-direction: column;
    gap: 0.35rem;
}
.modal .form-row > span,
.modal .form-row > legend {
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-muted);
}
.modal .form-row input[type="text"] {
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--text);
    font: inherit; font-size: 0.85rem;
}
.modal .form-row input[type="text"]:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.modal fieldset.form-row {
    border: 0; padding: 0; margin: 0;
}

/* Grille des cases à cocher de colonnes — 2 colonnes pour rester compact. */
.view-cols-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.3rem 1rem;
}
.view-col-row {
    display: flex; align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.2rem;
    border-radius: var(--radius);
    cursor: pointer;
    font-size: 0.85rem;
}
.view-col-row:hover { background: var(--surface-2); }
.view-col-row.is-gated {
    opacity: 0.55;
    cursor: not-allowed;
}
.view-col-row.is-gated input { cursor: not-allowed; }

/* Cellule tronquée (Notes, Note dernier RDV) */
.cell-truncate {
    max-width: 18rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* === Largeurs des colonnes sur /persons ================================
   Stratégie : `table-layout: auto` (défaut) + ellipsis + `max-width` par
   colonne. Le navigateur dimensionne au contenu mais chaque cellule est
   capée → les colonnes texte longues (notes, org, email) ne mangent pas
   tout l'espace. Pour les vues légères (5-8 colonnes) ça fit
   naturellement dans `.table-wrap` ; pour les vues à 15+ colonnes la
   somme dépasse la largeur disponible et `.table-wrap` montre un scroll
   horizontal — comportement acceptable quand l'utilisateur empile
   beaucoup de colonnes (l'éditeur de vue est l'outil de curation).

   Les cellules tronquent en ellipse pour ne pas pousser la colonne plus
   large que sa max-width. Le `<style>` du <head> masque les colonnes
   hors-vue (display:none), donc seules les visibles consomment de la
   largeur.

   Généralisé 2026-05-21 à tous les `td[data-col]` / `th[data-col]` (le
   data-col est porté uniquement par les tableaux du système de vues). Sans
   ça, les listes hors-persons avaient des cellules qui s'étiraient au
   contenu et le drag-and-drop de colonnes donnait une impression visuelle
   incohérente (colonnes qui sautent en largeur). Les cellules qui ont
   légitimement besoin de wrap (subject + résumé, notes + suivi…)
   surchargent localement via `.cell-last-contact`, `.cell-truncate`, ou
   `white-space: normal` sur le `<td>`. */
table[data-excel] td[data-col] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Pas d'`overflow: hidden` sur le `<th>` — ça clipperait le drop indicator
   `::before/::after` (positionné top/bottom: -3px + translateX: -50% pour
   déborder volontairement sur le voisin), et son glow. Le `<th>` n'a pas
   besoin d'ellipsis : son contenu est `<div class="th-content">` (flex)
   qui gère son propre layout, et le label est court par construction.
   Si jamais un label devenait très long, ajouter l'ellipsis sur
   `.th-label`, pas sur le `<th>` parent. */
table[data-excel] th[data-col] {
    white-space: nowrap;
}
/* Cellules multi-lignes : conservent l'autorisation de wrap (contiennent
   un `<br>` qui sépare 2 infos compactes : note + suivi, sujet + résumé,
   date + collabs). Le nowrap+ellipsis tronquerait la 2e ligne. */
#tableMeetings td[data-col="notes"],
#tableComms td[data-col="subject"] {
    white-space: normal;
}
/* Persons : la cellule « Dernier contact » a son override via la classe
   `.cell-last-contact` posée dans le template, plus haut dans ce fichier. */
/* Per-col max-width (caps de contenu). */
#tablePersons td[data-col="name"]               { max-width: 14rem; }
#tablePersons td[data-col="type"]               { max-width:  8rem; }
#tablePersons td[data-col="current_org"]        { max-width: 14rem; }
#tablePersons td[data-col="current_title"]      { max-width: 10rem; }
#tablePersons td[data-col="sector"]             { max-width: 11rem; }
#tablePersons td[data-col="lieu"]               { max-width:  9rem; }
#tablePersons td[data-col="country"]            { max-width:  8rem; }
#tablePersons td[data-col="visibility"]         { max-width:  7rem; }
#tablePersons td[data-col="prospect_stage"]     { max-width:  9rem; }
#tablePersons td[data-col="has_followup"]       { max-width:  6rem; }
#tablePersons td[data-col="email_pro"]          { max-width: 13rem; }
#tablePersons td[data-col="phone_pro"]          { max-width:  9rem; }
#tablePersons td[data-col="linkedin"]           { max-width:  4rem; }
#tablePersons td[data-col="notes"]              { max-width: 14rem; }
#tablePersons td[data-col="created_at"]         { max-width:  7rem; }
#tablePersons td[data-col="last_contact"]       { max-width: 12rem; }
#tablePersons td[data-col="last_contact_notes"] { max-width: 14rem; }
/* La cellule « Dernier contact » contient date + chips collabs en 2
   lignes — autorise le wrap interne malgré nowrap par défaut. */
#tablePersons td.cell-last-contact {
    white-space: normal;
}
/* `.lc-collabs` reste tronquée sur une ligne avec sa max-width existante. */


/* Drag-and-drop des colonnes (système de vues, toutes les listes). Le <th>
   entier devient draggable ; pendant le drag on tinte la source en muted
   et on affiche un trait vertical du côté où on va dropper. Pas de drag
   handle visible — le curseur `grab` au hover suffit pour signaler. */
.th-draggable {
    cursor: grab;
    position: relative;
}
.th-draggable:active { cursor: grabbing; }
.th-dragging { opacity: 0.4; }
/* Drop indicator : barre verticale or qui s'étend au-delà du <th> (haut +
   bas) pour rester lisible même quand l'en-tête est compact, posée
   au-dessus des voisins (z-index) avec un glow pour qu'elle ressorte
   sur un fond clair OU sombre. `transform: translateX` permet un offset
   visuel net sans pousser le layout. */
.th-drop-before::before,
.th-drop-after::after {
    content: "";
    position: absolute;
    top: -3px;
    bottom: -3px;
    width: 3px;
    background: var(--accent);
    border-radius: 2px;
    box-shadow:
        0 0 0 1px var(--bg),                /* halo neutre qui isole de la bordure du th voisin */
        0 0 6px 0 var(--accent),            /* glow proche */
        0 0 10px 0 var(--focus-ring);       /* glow lointain (or translucide) */
    pointer-events: none;
    z-index: 5;
}
.th-drop-before::before { left: 0;  transform: translateX(-50%); }
.th-drop-after::after   { right: 0; transform: translateX(50%); }
/* Le <th> source en cours de drag : on retire la barre éventuelle pour
   ne pas avoir 2 indicateurs visibles. */
.th-dragging::before,
.th-dragging::after { display: none; }

/* Séparateur dans les `.lt-pop` (Vue picker) */
.list-toolbar .lt-pop-divider {
    height: 1px;
    background: var(--border-light);
    margin: 0.3rem 0.4rem;
}

/* Item rouge dans un `.lt-pop` (action destructive) */
.list-toolbar .lt-pop-item-danger {
    color: var(--danger);
}
.list-toolbar .lt-pop-item-danger:hover {
    background: var(--danger-light);
    color: var(--danger);
}
.list-toolbar .lt-pop-item-danger svg { color: var(--danger); }

/* --- Detail prev/next nav (hydrated by setupDetailPrevNext) ----------------
   Host sits inside `.crumbs` and is pushed to the right via margin-left:auto
   so the prev/next pair shares the breadcrumbs row, saving vertical space. */
.crumbs .detail-nav-host { margin-left: auto; }
.crumbs .detail-nav-host:not([hidden]) { display: inline-flex; align-items: center; }
/* Segmented pill: [<] 12 / 47 [>]. Icon-only chevrons, integrated counter,
   single border, per-segment hover. Tokens only — fits both themes. */
.detail-nav {
    display: inline-flex;
    align-items: stretch;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    box-shadow: var(--shadow-sm);
    overflow: hidden;
    user-select: none;
    -webkit-user-select: none;
    height: 26px;
}
.detail-nav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    color: var(--text-muted);
    text-decoration: none;
    transition: background 120ms ease, color 120ms ease;
    cursor: pointer;
}
.detail-nav-btn:hover,
.detail-nav-btn:focus-visible { background: var(--surface-2); color: var(--text); outline: none; }
.detail-nav-btn:active { background: var(--border-light); }
.detail-nav-btn:visited { color: var(--text-muted); }
.detail-nav-btn.is-disabled { opacity: 0.3; pointer-events: none; cursor: default; }
.detail-nav-counter {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0 0.55rem;
    font-size: 0.74rem;
    color: var(--text-muted);
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.01em;
    white-space: nowrap;
}
.detail-nav-counter strong { color: var(--text); font-weight: 600; }
.detail-nav-sep { color: var(--text-light); font-weight: 400; }
@media (max-width: 720px) {
    .crumbs { flex-wrap: wrap; }
    .crumbs .detail-nav-host { margin-left: auto; }
}

/* --- List search polish (clear button + content-driven width) --------------
   Default narrow (~220px). Width is driven by JS via the `--search-w` CSS
   var: input grows with content length on input, snaps back to default
   when emptied/blurred. Capped at the parent's available width so it
   never overflows the page-header row. The browser's native ✕ on
   `<input type="search">` is hidden so we render only our own `.search-clear`
   button — having both creates the double-cross artifact. */
.search { position: relative; max-width: var(--search-w, 220px); transition: max-width 180ms ease; }
.search input[type="search"]::-webkit-search-cancel-button,
.search input[type="search"]::-webkit-search-decoration {
    -webkit-appearance: none; appearance: none; display: none;
}
.search-clear {
    flex-shrink: 0;
    width: 18px; height: 18px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--surface-2);
    border: 0;
    border-radius: 50%;
    color: var(--text-muted);
    cursor: pointer;
    padding: 0;
}
.search-clear svg { width: 10px; height: 10px; }
.search-clear:hover { background: var(--border); color: var(--text); }
.search-clear[hidden] { display: none; }
@media (max-width: 720px) {
    .search { max-width: 100%; width: 100%; }
}

/* =============================================================================
   SIGNATURE — helpers Private Banking
   -----------------------------------------------------------------------------
   Petits éléments décoratifs qui posent l'identité éditoriale du
   produit : filet or, frises, pattern de séparation.
   ============================================================================= */

/* Filet or fin (80px wide, 1px haut, 30% opacity) — séparateur visuel
   entre sections majeures d'une fiche client. Usage :
       <hr class="rule-gold">
   Centré ou aligné à gauche selon le contexte. */
.rule-gold {
    border: 0;
    height: 1px;
    width: 80px;
    background: var(--accent);
    opacity: 0.45;
    margin: var(--space-6) 0;
}
.rule-gold.center { margin-left: auto; margin-right: auto; }

/* Frise or pleine largeur — séparateur de section principale dans
   les hero / page-header. */
.rule-gold-full {
    border: 0;
    height: 1px;
    background: linear-gradient(to right, transparent, var(--accent), transparent);
    opacity: 0.5;
    margin: var(--space-5) 0;
}

/* Eyebrow label — petit suréclairage en or au-dessus d'un titre serif.
   Usage : <span class="eyebrow">Fiche client</span><h1>Mme Dupont</h1>
   Convention presse économique. */
.eyebrow {
    display: inline-block;
    font-family: var(--font-sans);
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--accent);
    margin-bottom: 0.4rem;
}

/* =============================================================================
   PRINT — Ctrl+P d'une fiche client = export présentable au RDV
   -----------------------------------------------------------------------------
   On masque toute la chrome (sidebar, toolbars, FAB, modales),
   on bascule en serif partout, on garde le papier ivoire chaud.
   Le banquier qui imprime une fiche pour son rendez-vous obtient
   un document, pas un screenshot d'app.
   ============================================================================= */
@media print {
    html, body {
        background: #FAF8F3 !important;
        color: #1A2332 !important;
        font-size: 11pt;
        line-height: 1.5;
    }
    /* Polices safe pour impression */
    h1, h2, h3, .hero-info h1, .page-header h1, .stat .value {
        font-family: "Source Serif 4", Georgia, serif !important;
        color: #0A2540 !important;
    }
    /* Masquer tout le chrome de navigation */
    .sidebar, .sidebar-burger, .sidebar-overlay, .sidebar-resize,
    .fab, .flash-stack, .modal-overlay, .license-banner,
    .actions-bar, .hero-actions, .add-menu, .delete-form,
    nav.crumbs, .search, .activity-toolbar, .mini-graph-card {
        display: none !important;
    }
    main { padding: 0 !important; max-width: 100% !important; }
    .app { display: block !important; min-height: 0 !important; }
    /* Cards : pas d'ombre, bordure fine pour démarcation papier */
    .card, .hero, .stat {
        background: #FFFFFF !important;
        border: 1px solid #E5DFD0 !important;
        box-shadow: none !important;
        break-inside: avoid;
        page-break-inside: avoid;
    }
    /* Tables : pas de hover, pas de zebra trop forte (papier) */
    tbody tr:hover td { background: transparent !important; }
    tbody tr:nth-child(even) td { background: #FAF8F3 !important; }
    th {
        background: #ECE7DB !important;
        color: #1A2332 !important;
        box-shadow: none !important;
        position: static !important;
    }
    a { color: #0A2540 !important; text-decoration: none !important; }
    /* Filet or imprimable */
    .rule-gold, .rule-gold-full { background: #B69756 !important; opacity: 0.6 !important; }
    /* Eviter de couper une row au milieu d'une page */
    tr, .tl-item, .stat { break-inside: avoid; }
}

/* =============================================================================
   Dossier canvas — composable widgets façon page Notion (2026-05-10)
   Visual rules :
   - Hero feels like a "fiche", not a tabular header.
   - Widgets blend with the page (no contrasted header band) — chrome
     reveals on widget hover (Notion pattern).
   - Empty states are short, muted, never towering.
   ============================================================================= */

/* ---- Hero ---- */
.dossier-hero {
    display: flex; gap: 1.5rem; align-items: flex-start; justify-content: space-between;
    margin-bottom: 1.5rem;
    padding-bottom: 1.1rem;
    border-bottom: 1px solid var(--border-light);
    position: relative;   /* anchor for the bottom-right `+` add-widget button */
}
.dossier-hero-main { flex: 1; min-width: 0; }
.dossier-hero-title {
    display: flex; align-items: center; gap: .55rem;
    margin: 0 0 .55rem 0;
    font-size: 1.6rem;
}
.dossier-hero-meta {
    display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
    font-size: .9rem; color: var(--text-muted);
}
.dossier-hero-person {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .2rem .55rem .2rem .25rem;
    background: var(--surface-2);
    border: 1px solid var(--border-light);
    border-radius: 999px;
    color: var(--text);
    text-decoration: none;
    font-weight: 500;
    transition: background-color 120ms ease, border-color 120ms ease;
}
.dossier-hero-person:hover {
    background: var(--primary-light);
    border-color: var(--primary);
    color: var(--text);
}
.dossier-hero-person .avatar.sm {
    width: 1.4rem; height: 1.4rem; font-size: .65rem;
}
.dossier-hero-dates { color: var(--text-muted); }
.dossier-hero-dates .sep { margin: 0 .35rem; opacity: .6; }
.dossier-hero-actions { display: flex; gap: .5rem; align-items: flex-start; flex-wrap: wrap; }

/* ============================================================================
   Dossier sheets — barre d'onglets façon classeur Excel.
   Onglets cliquables (la feuille active reçoit `.is-active`), drag horizontal
   pour reorder (mode édition uniquement), kébab par-onglet pour rename /
   save_as_template / delete, bouton « + » à droite qui ouvre le modal de
   création (cf. `.ds-newmodal`).
   ============================================================================ */
.dossier-sheets {
    display: flex; align-items: flex-end; gap: .25rem;
    margin: 1.25rem 0 0;
    padding: 0 .25rem;
    border-bottom: 1px solid var(--border);
    /* overflow:visible — sinon les popovers (kébab, add-widget) sont
       clipés et un scrollbar vertical parasite apparaît. Si la barre
       grossit au-delà du viewport (cas marginal), les onglets en trop
       wrap simplement vers le bas — acceptable jusqu'à preuve du contraire. */
    overflow: visible;
    flex-wrap: wrap;
}
.ds-tab {
    display: inline-flex; align-items: center; gap: .15rem;
    padding: 0 .15rem 0 .85rem;
    height: 34px;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    cursor: pointer;
    position: relative;
    margin-bottom: -1px;
    white-space: nowrap;
    transition: color var(--duration-fast) var(--ease-out);
}
/* Onglet actif : juste un trait or champagne en bas + texte plus dense.
   Pas de surface bg ni de contour boîte (l'utilisateur trouvait ça
   trop chargé). Le filet 2px overlap la border-bottom du parent. */
.ds-tab.is-active {
    color: var(--text);
}
.ds-tab.is-active::after {
    content: "";
    position: absolute;
    left: .5rem; right: .5rem; bottom: -1px;
    height: 2px;
    background: var(--accent);
    border-radius: 1px;
}
.ds-tab.is-dragging { opacity: .4; }
.ds-tab.is-drop-before::before,
.ds-tab.is-drop-after::after {
    content: ""; position: absolute; top: 4px; bottom: 4px;
    width: 2px; background: var(--accent);
    border-radius: 2px;
}
.ds-tab.is-drop-before::before { left: -2px; }
.ds-tab.is-drop-after::after  { right: -2px; }
.ds-tab-label {
    background: transparent; border: 0; color: inherit; cursor: pointer;
    padding: 0; font: inherit;
    max-width: 14rem; overflow: hidden; text-overflow: ellipsis;
    outline: none;
    box-shadow: none;
}
/* Defeat le global `button:hover` (style.css l.708) qui repeint en navy.
   On veut zéro changement visuel au hover sur les onglets — l'affordance
   « cliquable » est portée par le cursor:pointer. */
.ds-tab-label:hover,
.ds-tab-label:active {
    background: transparent;
    color: inherit;
    box-shadow: none;
}
.ds-tab-label:focus,
.ds-tab-label:focus-visible {
    outline: none;
    box-shadow: none;
}
.ds-tab-label.is-editing {
    /* Rename inline : signaler discrètement sans encadrer (l'utilisateur
       trouve les outline/bg de hover « bizarres »). Juste le caret + un
       fond très léger pour montrer qu'on tape. */
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    cursor: text;
}
.ds-tab-menu { position: relative; }
.ds-tab-menu > summary {
    list-style: none; cursor: pointer;
    width: 22px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--radius-sm);
    color: var(--text-light);
    /* Always visible (was opacity:0 → unreachable affordance). Faded
       a bit so the kebab doesn't compete with the tab name. */
    opacity: .6;
    transition: opacity var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.ds-tab-menu > summary::-webkit-details-marker { display: none; }
.ds-tab-menu > summary:hover,
.ds-tab-menu[open] > summary { opacity: 1; background: var(--surface-2); color: var(--text); }
.ds-tab-pop {
    /* Aligné au bord gauche de l'onglet — sinon le popover déborde
       à gauche du viewport (les onglets sont sur le bord gauche). */
    position: absolute; top: calc(100% + .35rem); left: 0;
    z-index: var(--z-dropdown);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: .3rem;
    min-width: 220px;
    display: flex; flex-direction: column;
}
/* Items du menu : tous au même format (left-align, full-width, hover bg).
   Pas de surface saturée, pas de mix de styles — menu déroulant standard. */
.ds-tab-pop .ds-tab-rename,
.ds-tab-pop .ds-tab-del,
.ds-tab-pop .ds-tab-tplform > summary {
    background: transparent; border: 0;
    text-align: left;
    padding: .45rem .65rem;
    border-radius: var(--radius-sm);
    color: var(--text); cursor: pointer; font: inherit;
    width: 100%;
    transition: background var(--duration-fast) var(--ease-out);
}
.ds-tab-pop .ds-tab-rename:hover,
.ds-tab-pop .ds-tab-tplform > summary:hover {
    background: var(--surface-2);
}
.ds-tab-pop .ds-tab-del { color: var(--danger); }
.ds-tab-pop .ds-tab-del:hover { background: var(--danger-light); }
.ds-tab-pop .ds-tab-delform { margin: 0; }

/* « Sauvegarder comme template » : <details> qui se déplie. Le summary
   se comporte comme un item du menu. Une fois ouvert, il révèle un
   panneau (surface-2) avec input + bouton de confirmation accent. */
.ds-tab-pop .ds-tab-tplform {
    display: flex; flex-direction: column;
}
/* Cache la flèche de divulgation native. Chrome/Firefox modernes la
   rendent via `display: list-item` (pas via le pseudo legacy) — il
   faut donc override le display vers `block`. */
.ds-tab-pop .ds-tab-tplform > summary {
    display: block;
    list-style: none;
}
.ds-tab-pop .ds-tab-tplform > summary::-webkit-details-marker,
.ds-tab-pop .ds-tab-tplform > summary::marker { display: none; content: ""; }
.ds-tab-pop .ds-tab-tplform[open] {
    margin: .15rem 0;
    background: var(--surface-2);
    border-radius: var(--radius-sm);
}
.ds-tab-pop .ds-tab-tplform[open] > summary {
    color: var(--text-muted);
    font-size: .85rem;
    padding: .35rem .65rem .15rem;
    background: transparent;
}
.ds-tab-pop .ds-tab-tplform-body {
    display: flex; flex-direction: column; gap: .35rem;
    padding: 0 .4rem .4rem;
}
.ds-tab-pop .ds-tab-tplform input {
    padding: .4rem .55rem; font-size: .9rem;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
}
.ds-tab-pop .ds-tab-tplform input:focus {
    outline: none; border-color: var(--accent);
}
.ds-tab-pop .ds-tab-tplform-submit {
    padding: .4rem .55rem;
    text-align: center;
    background: var(--accent);
    color: var(--on-accent);
    border: 0; border-radius: var(--radius-sm);
    font: inherit; font-weight: 500;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out);
}
.ds-tab-pop .ds-tab-tplform-submit:hover {
    background: var(--accent-hover);
}
.ds-tab-add {
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px;
    margin: 0 .25rem .25rem;
    background: transparent;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    font-size: 1.15rem; line-height: 1;
    font-weight: 400;
    padding: 0;
    transition: color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.ds-tab-add:hover {
    color: var(--accent); border-color: var(--accent);
    background: var(--accent-light);
}

/* Add-widget vit désormais dans la barre d'onglets, aligné à droite
   sur la même ligne que les feuilles. `margin-left: auto` le pousse
   à l'extrémité. `align-self: flex-end` aligne sur la baseline des
   onglets (qui sont en `align-items: flex-end`). */
.dossier-sheets .ds-add-widget {
    margin-left: auto;
    margin-bottom: .25rem;
    align-self: flex-end;
    position: relative;
}
.dossier-sheets .ds-add-widget .dw-add-trigger {
    padding: .25rem .55rem;
    font-size: .82rem;
    height: 28px;
}
.dossier-sheets .ds-add-widget .dw-add-pop {
    /* Aligner le popover sur le bord droit de la barre, pas sur le
       bouton (qui peut être un peu décalé selon la largeur des onglets). */
    right: 0;
}

/* ---- Modal : nouvelle feuille ---- */
.ds-newmodal {
    position: fixed; inset: 0; z-index: var(--z-modal);
    display: flex; align-items: center; justify-content: center;
}
.ds-newmodal[hidden] { display: none; }
.ds-newmodal-backdrop {
    position: absolute; inset: 0;
    background: rgba(10, 37, 64, .35);
    backdrop-filter: blur(4px);
}
.ds-newmodal-panel {
    position: relative;
    width: min(34rem, calc(100vw - 2rem));
    max-height: calc(100vh - 4rem);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    display: flex; flex-direction: column;
    overflow: hidden;
}
.ds-newmodal-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: .9rem 1.2rem;
    border-bottom: 1px solid var(--border-light);
}
.ds-newmodal-head h3 { margin: 0; font-size: 1.1rem; }
.ds-newmodal-close {
    background: transparent; border: 0; cursor: pointer;
    width: 28px; height: 28px; font-size: 1.3rem; line-height: 1;
    color: var(--text-muted);
    border-radius: var(--radius-sm);
}
.ds-newmodal-close:hover { background: var(--surface-2); color: var(--text); }
.ds-newmodal-body {
    padding: 1.2rem;
    display: flex; flex-direction: column; gap: 1rem;
    overflow-y: auto;
}
.ds-newmodal-field { display: flex; flex-direction: column; gap: .4rem; }
.ds-newmodal-field > span {
    font-size: .85rem; color: var(--text-muted);
    font-weight: 500;
}
.ds-newmodal-field input[type="text"] {
    padding: .55rem .7rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
}
.ds-newmodal-options {
    display: flex; flex-direction: column; gap: .4rem;
}
.ds-newmodal-option {
    display: flex; align-items: center; gap: .65rem;
    padding: .65rem .85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
    transition: border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.ds-newmodal-option:hover { border-color: var(--border-strong); }
.ds-newmodal-option.is-active {
    border-color: var(--accent);
    background: var(--accent-light);
}
.ds-newmodal-option input { margin: 0; }
.ds-newmodal-option-label { font-weight: 500; }
.ds-newmodal-option-sub { margin-left: auto; font-size: .85rem; }
.ds-newmodal-foot {
    display: flex; gap: .5rem; justify-content: flex-end;
    padding: .9rem 1.2rem;
    border-top: 1px solid var(--border-light);
}

/* ---- Toolbar : add widget ---- */
/* Add-widget : pill discret en haut du canvas de la feuille active, aligné
   avec la barre d'onglets pour rester accessible mais non-intrusif.
   Accent or au hover/open pour signaler la cible d'ajout. */
.dw-add-menu {
    position: relative;
    align-self: flex-end;
    margin-bottom: .25rem;
}
.dw-add-menu > summary { list-style: none; cursor: pointer; }
.dw-add-menu > summary::-webkit-details-marker { display: none; }
.dw-add-trigger {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .35rem .7rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: .9rem;
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.dw-add-trigger:hover,
.dw-add-menu[open] > .dw-add-trigger {
    color: var(--accent);
    border-color: var(--accent);
    background: var(--accent-light);
}

/* Popover — 2 colonnes côte à côte, filet or au milieu, headers serif. */
.dw-add-pop {
    position: absolute; right: 0; top: calc(100% + .5rem); z-index: 30;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: .85rem 1rem;
    display: grid;
    grid-template-columns: minmax(180px, 1fr) minmax(180px, 1fr);
    gap: 0 1.1rem;
    min-width: 420px;
    background-image: linear-gradient(to bottom,
        transparent 14%,
        var(--accent-light) 14%,
        var(--accent-light) 86%,
        transparent 86%);
    background-size: 1px 100%;
    background-position: center;
    background-repeat: no-repeat;
}
.dw-add-col { display: flex; flex-direction: column; }
.dw-add-group-label {
    display: block;
    padding: 0 .4rem .55rem;
    margin-bottom: .3rem;
    font-family: var(--font-display);
    font-size: .8rem;
    font-weight: 600;
    font-style: italic;
    letter-spacing: .015em;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-light);
}
.dw-add-item {
    display: flex; align-items: center; gap: .55rem;
    padding: .42rem .55rem;
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--text);
    text-align: left;
    cursor: pointer;
    font-size: .87rem;
    font-weight: 500;
    line-height: 1.2;
    transition: background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.dw-add-icon {
    flex: 0 0 auto;
    color: var(--text-light);
    transition: color var(--duration-fast) var(--ease-out);
}
.dw-add-label { flex: 1; min-width: 0; }
.dw-add-item:hover {
    background: var(--accent-light);
    color: var(--primary);
}
.dw-add-item:hover .dw-add-icon { color: var(--accent); }

@media (max-width: 540px) {
    .dw-add-pop {
        grid-template-columns: 1fr;
        gap: .8rem 0;
        min-width: 240px;
        background-image: linear-gradient(to right,
            transparent 14%,
            var(--accent-light) 14%,
            var(--accent-light) 86%,
            transparent 86%);
        background-size: 100% 1px;
    }
}

/* ---- Canvas : 3 zones (top full-width / 2-col band / bottom full-width) ---- */
.dw-canvas { display: flex; flex-direction: column; gap: .9rem; padding-top: 1rem; padding-bottom: 35vh; }
.dw-canvas[hidden] { display: none; }

/* Top / bottom bands stack widgets full-width. The 2-col band is always
   rendered (even when both cols are empty) — it's the structural
   backbone of the canvas and the obvious drop target for column widgets.
   Columns flow independently (masonry-style) so a short widget on one
   side lets the next widget slide up. */
.dw-zone {
    display: flex;
    flex-direction: column;
    gap: .9rem;
}
.dw-zone-cols {
    display: flex;
    flex-direction: row;
    gap: .9rem;
    align-items: flex-start;
}
.dw-col {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    gap: .9rem;
    min-width: 0;
}

/* Top / left / right empty zones are hidden at rest — they only materialize
   as drop targets during a drag (cf. .is-dragging block below). */
.dw-canvas .dw-zone-top.is-empty,
.dw-canvas .dw-col.is-empty { display: none; }

/* Bottom placeholder is the exception : permanently visible in edit
   mode as a "drop here for a full-width band" affordance. Subtle at
   rest ; fades out when the user hovers it so it gets out of the way. */
.dw-canvas:not(.is-editable) .dw-zone-bottom.is-empty { display: none; }
.dw-canvas.is-editable .dw-zone-bottom.is-empty {
    display: flex;
    min-height: 2.5rem;
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    align-items: center;
    justify-content: center;
    color: var(--text-light);
    font-size: .82rem;
    font-style: italic;
    transition: opacity var(--duration-fast) var(--ease-out);
}
.dw-canvas.is-editable .dw-zone-bottom.is-empty::before {
    content: "Déposer ici pour une bande pleine largeur en bas";
}
.dw-canvas.is-editable:not(.is-dragging) .dw-zone-bottom.is-empty:hover {
    opacity: 0;
}

/* During a drag, every empty zone matérialise comme drop target en bleu
   marine dimmed (cible potentielle, structurel). L'**indicateur d'active
   target** est un outline or champagne autour de la zone entière (cf.
   bloc `.is-drop-active` ci-dessous) — ça marche aussi bien sur une zone
   vide qu'une zone avec widgets, un seul feedback visuel par zone. */
.dw-canvas.is-editable.is-dragging .dw-zone.is-empty,
.dw-canvas.is-editable.is-dragging .dw-col.is-empty {
    display: flex;
    min-height: 3.5rem;
    border: 1px dashed var(--primary-pale);
    border-radius: var(--radius);
    align-items: center;
    justify-content: center;
    background: var(--primary-light);
    color: var(--text-muted);
    font-size: .82rem;
    font-style: italic;
}
.dw-canvas.is-editable.is-dragging .dw-zone.is-drop-active,
.dw-canvas.is-editable.is-dragging .dw-col.is-drop-active {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: var(--radius);
}

/* Insertion slot : ghost block or champagne inséré à la position exacte
   d'atterrissage du widget pendant le drag. Sa hauteur matche la card
   draggée (set inline par JS), il pousse les widgets voisins pour montrer
   « ça va atterrir ICI ». Non-interactif (pointer-events: none) pour ne
   pas absorber les events de drag. */
.dw-drop-slot {
    border: 2px dashed var(--accent);
    background: var(--accent-light);
    border-radius: var(--radius);
    box-sizing: border-box;
    pointer-events: none;
}

/* Quand le slot est inséré dans une zone vide (qui a déjà son placeholder
   bleu dashed + texte ::before « Colonne de gauche — vide » etc.), on
   neutralise le placeholder pour éviter le double affichage. Le slot or
   prend toute la place visuelle. */
.dw-canvas.is-editable.is-dragging .dw-zone.is-empty:has(.dw-drop-slot),
.dw-canvas.is-editable.is-dragging .dw-col.is-empty:has(.dw-drop-slot) {
    background: transparent;
    border-color: transparent;
    align-items: stretch;
    justify-content: flex-start;
}
.dw-canvas.is-editable.is-dragging .dw-zone.is-empty:has(.dw-drop-slot)::before,
.dw-canvas.is-editable.is-dragging .dw-col.is-empty:has(.dw-drop-slot)::before {
    display: none;
}

.dw-canvas.is-editable.is-dragging .dw-zone-top.is-empty::before {
    content: "Déposer ici pour une bande pleine largeur en haut";
}
.dw-canvas.is-editable.is-dragging .dw-col[data-zone="left"].is-empty::before {
    content: "Colonne de gauche — vide";
}
.dw-canvas.is-editable.is-dragging .dw-col[data-zone="right"].is-empty::before {
    content: "Colonne de droite — vide";
}

@media (max-width: 900px) {
    .dw-zone-cols { flex-direction: column; }
}

/* ---- Widget card : flat, blends with page ---- */
.dw-card {
    background: var(--surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius);
    display: flex; flex-direction: column;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}
.dw-card:hover { border-color: var(--border); }
.dw-card.is-dragging { opacity: .4; }
.dw-card.is-drop-target { outline: 2px dashed var(--primary); outline-offset: -2px; }

/* Header : transparent, no band, chrome only on hover.
   The whole header acts as the drag handle (cursor: grab while idle).
   Action buttons opt out via JS (closest('.dw-act') skips drag init). */
.dw-head {
    display: flex; align-items: center; gap: .35rem;
    padding: .65rem .85rem .35rem;
    border-bottom: 0;
    background: transparent;
    cursor: grab;
    user-select: none;
}
.dw-card:hover .dw-head { background: var(--surface-2); }
.dw-head:active { cursor: grabbing; }
.dw-card.is-dragging .dw-head { cursor: grabbing; }
.dw-title {
    font-size: .82rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    margin: 0;
    flex: 1; min-width: 0;
    border-radius: var(--radius-sm);
    transition: background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
/* Editable hint : text cursor only on canvases the user can modify, and
   subtle bg lift on hover so users know they can click to rename. */
.dw-canvas.is-editable .dw-title { cursor: text; }
.dw-canvas.is-editable .dw-card:hover .dw-title:not(.is-editing) {
    background: var(--surface-3);
    color: var(--text);
}
/* Editing state — looks like a focused input. */
.dw-title.is-editing {
    background: var(--surface);
    box-shadow: 0 0 0 2px var(--focus-ring);
    color: var(--text);
    outline: none;
    cursor: text;
}
.dw-actions {
    display: flex; gap: .1rem; flex-shrink: 0;
    opacity: 0;
    transition: opacity 120ms ease;
}
.dw-card:hover .dw-actions,
.dw-card:focus-within .dw-actions { opacity: 1; }
.dw-act {
    background: transparent; border: 0; cursor: pointer;
    width: 28px; height: 28px;
    border-radius: var(--radius-sm);
    color: var(--text);
    display: inline-flex; align-items: center; justify-content: center;
    transition: background-color 120ms ease, color 120ms ease;
}
.dw-act svg {
    width: 16px; height: 16px;
    min-width: 16px;        /* Flex children default to min-width: auto, which
                               collapses the SVG (no intrinsic width) to 0. */
    flex-shrink: 0;
}
.dw-act:hover { background: var(--surface-3); color: var(--text); }
.dw-act.dw-delete:hover { background: var(--danger-light); color: var(--danger); }

/* Flex column so all widgets get a uniform inter-block gap (chips→input,
   list→form, etc.) — without it, widgets that contain internal margins
   (.dw-persons margin-bottom, .dw-checklist margin-bottom) ended up
   visibly taller than minimalist widgets (.dw-list alone), making the
   column gap look uneven. */
.dw-body {
    padding: .35rem .9rem .55rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

/* ---- Notes widget ---- */
.dw-notes-input {
    width: 100%;
    min-height: 6rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    padding: .5rem .55rem;
    color: var(--text);
    font-family: var(--font-sans); font-size: .92rem;
    line-height: 1.55;
    resize: vertical;
}
.dw-notes-input:hover { border-color: var(--border-light); }
.dw-notes-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--focus-ring); }
.dw-notes-input::placeholder { color: var(--text-light); }
.dw-notes-readonly {
    color: var(--text);
    line-height: 1.55;
    font-size: .92rem;
    padding: .35rem .55rem;
    white-space: pre-wrap;
}
.dw-notes-status {
    font-size: .72rem; color: var(--text-muted);
    min-height: 1em; margin-top: .25rem;
    padding-left: .55rem;
}
.dw-notes-status.is-saving::before { content: "Enregistrement…"; color: var(--text-muted); }
.dw-notes-status.is-saved::before   { content: "✓ Enregistré"; color: var(--success); }
.dw-notes-status.is-error::before   { content: "Échec d'enregistrement"; color: var(--danger); }

/* ---- List widget (meetings, comms) ---- */
.dw-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .1rem; }
.dw-list-item a {
    display: grid;
    grid-template-columns: 6rem 1fr auto;
    gap: .7rem;
    padding: .45rem .55rem;
    border-radius: var(--radius-sm);
    color: var(--text);
    text-decoration: none;
    align-items: center;
    transition: background-color 100ms ease;
}
.dw-list-item a:hover { background: var(--surface-2); }
.dw-list-date { color: var(--text-muted); font-size: .82rem; font-variant-numeric: tabular-nums; }
.dw-list-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .9rem; }
.dw-list-meta { color: var(--text-muted); font-size: .78rem; }

@media (max-width: 700px) {
    .dw-list-item a { grid-template-columns: 1fr; }
    .dw-list-meta { display: none; }
}

/* ---- Persons widget ---- */
.dw-persons { display: flex; flex-wrap: wrap; gap: .4rem; margin: 0; }
.dw-person-chip {
    display: inline-flex; align-items: center; gap: .25rem;
    background: var(--surface-2);
    border: 1px solid var(--border-light);
    border-radius: 999px;
    padding: .15rem .45rem .15rem .15rem;
    font-size: .85rem;
}
.dw-person-chip a {
    display: inline-flex; align-items: center; gap: .35rem;
    color: var(--text);
    text-decoration: none;
    font-weight: 500;
}
.dw-person-chip a:hover { color: var(--primary); }
.dw-person-chip .avatar { width: 1.4rem; height: 1.4rem; font-size: .6rem; }
.dw-person-remove {
    background: transparent; border: 0; cursor: pointer;
    width: 18px; height: 18px; line-height: 1;
    color: var(--text-muted); border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: .9rem;
}
.dw-person-remove:hover { background: var(--danger-light); color: var(--danger); }

.dw-person-add { position: relative; }
.dw-person-search {
    width: 100%;
    padding: .4rem .6rem;
    background: var(--surface-2);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: .88rem;
}
.dw-person-search:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.dw-person-results {
    position: absolute; left: 0; right: 0; top: 100%;
    background: var(--surface-elevated);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    z-index: 20;
    max-height: 240px; overflow-y: auto;
    margin-top: .25rem;
}
.dw-person-result {
    padding: .45rem .6rem; cursor: pointer; font-size: .88rem;
    color: var(--text);
}
.dw-person-result:hover { background: var(--surface-2); }
.dw-person-result.is-empty { color: var(--text-muted); cursor: default; font-style: italic; }

/* ---- Mini-graph host ----
   Override the standalone .mini-graph-card (fixed 320px wide) so the
   graph fills the column width and gets an explicit height — without
   it the SVG collapses (flex chain has no anchor height). User can
   drag the bottom-right corner to resize ; height is persisted in
   localStorage by `wireMinigraphResize` in dossier-canvas.js, which
   also snaps to a neighbor card's bottom when within 20 px. */
.dw-minigraph-host {
    padding: 0;
    width: 100%;
    height: 320px;
    min-height: 180px;
    max-height: 900px;
    resize: vertical;
    overflow: hidden;
    position: relative;
}
.dw-minigraph-host .mini-graph-card {
    margin: 0; border: 0; padding: 0; background: transparent;
    width: 100%; height: 100%;
}
.dw-minigraph-host .mini-graph { width: 100%; height: 100%; }
.dw-minigraph-host .mini-graph-svg-box { flex: 1; min-height: 0; }

/* ---- Checklist widget ---- */
.dw-checklist { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: .1rem; }
.dw-check-item {
    display: flex; align-items: center; gap: .35rem;
    padding: .3rem .4rem;
    border-radius: var(--radius-sm);
}
.dw-check-item:hover { background: var(--surface-2); }
.dw-check-item label {
    flex: 1; display: flex; align-items: center; gap: .55rem;
    cursor: pointer; min-width: 0;
    font-size: .9rem;
}
.dw-check-text { color: var(--text); }
.dw-check-item.is-done .dw-check-text { color: var(--text-muted); text-decoration: line-through; }
.dw-check-empty { color: var(--text-muted); font-size: .85rem; padding: .35rem .4rem; font-style: italic; }
.dw-check-del {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: 0; cursor: pointer;
    width: 18px; height: 18px; border-radius: var(--radius-sm);
    color: var(--text-light); font-size: .8rem; line-height: 1;
    padding: 0;
    opacity: 0;
    transition: opacity var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.dw-check-item:hover .dw-check-del { opacity: 1; }
.dw-check-del:hover { background: var(--danger-light); color: var(--danger); }
.dw-check-add { display: flex; align-items: center; gap: .25rem; margin: .35rem 0; }
.dw-check-add input {
    flex: 1;
    padding: .4rem .55rem;
    background: var(--surface-2);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
    color: var(--text);
    font-size: .88rem;
    line-height: 1.2;
}
.dw-check-add input:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.dw-check-add button {
    flex: 0 0 auto;
    display: inline-flex; align-items: center; justify-content: center;
    width: 26px; height: 26px; padding: 0;
    background: transparent;
    border: 0;
    color: var(--text-muted);
    font-size: 1rem; line-height: 1;
    cursor: pointer;
    border-radius: var(--radius-sm);
    transition: background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.dw-check-add button:hover { background: var(--primary-light); color: var(--primary); }
.dw-check-progress { font-size: .78rem; color: var(--text-muted); padding-left: .4rem; }

/* ---- Grid widget : editable cell-based table + optional .xlsx import ----
 * Layout en édition :
 *   ┌─────────────────────┬───┐
 *   │ <table dans scroll> │ + │  ← .dw-grid-add-col (vertical, à droite)
 *   ├─────────────────────┴───┤
 *   │             +           │  ← .dw-grid-add-row (horizontal, en bas)
 *   └─────────────────────────┘
 * Le tableau a 2 gutters fins : une ligne `<thead>` au-dessus (poignées
 * de colonne) et une colonne `<th>` à gauche de chaque `<tr>` (poignées
 * de ligne). Le bouton `−` de chaque poignée n'apparaît qu'au hover. */
.dw-grid { display: flex; flex-direction: column; gap: 0; }
.dw-grid-row-frame { display: flex; align-items: stretch; gap: 0; }
.dw-grid-scroll {
    flex: 1; min-width: 0;
    overflow: auto;
    max-height: 460px;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
}
.dw-grid.is-editable .dw-grid-scroll {
    border-radius: var(--radius-sm) 0 0 var(--radius-sm);
    border-right: 0;
}
.dw-grid-table {
    width: 100%;
    /* fixed layout : largeurs de colonne stables au typing — pas de
     * jump de colonne à chaque caractère comme en `auto`. Le texte
     * wrappe dans la cellule (vertical growth) au lieu de pousser la
     * colonne (horizontal growth). */
    table-layout: fixed;
    border-collapse: collapse;
    font-size: .85rem;
    font-variant-numeric: tabular-nums;
}
/* Override les règles globales de tables (zebra, hover row, min-width
 * du 1er <td> à 180px, header sticky + gradient + box-shadow du <th>) —
 * la grille a son propre vocabulaire visuel. */
.dw-grid-table tbody tr:nth-child(even) td { background: transparent; }
.dw-grid-table tbody tr:hover td { background: inherit; }
.dw-grid-table tbody tr:last-child td { border-bottom: 1px solid var(--border-light); }
.dw-grid-table th:first-child,
.dw-grid-table td:first-child { min-width: 0; }
.dw-grid-table th {
    /* Reset complet des styles globaux <th> (cf. base table rules) */
    padding: 0;
    text-align: center;
    text-transform: none;
    letter-spacing: 0;
    font-size: .85rem;
    font-weight: 400;
    color: var(--text);
    background: var(--surface-2);
    box-shadow: none;
    border-radius: 0;
    border-bottom: 1px solid var(--border-light);
    border-right: 1px solid var(--border-light);
    position: static;
    z-index: auto;
    white-space: nowrap;
}
.dw-grid-table td {
    min-width: 6rem;
    padding: .35rem .55rem;
    border-bottom: 1px solid var(--border-light);
    border-right: 1px solid var(--border-light);
    color: var(--text);
    background: var(--surface);
    vertical-align: top;
}
.dw-grid-table td[contenteditable] {
    cursor: text;
    /* Wrapping prévisible quand le texte dépasse :
     *   - pre-wrap   : préserve les retours à la ligne explicites (Shift+Enter)
     *                  et wrappe à l'espace pour le reste
     *   - break-word : casse les mots sans espace (URLs, IDs longs) plutôt
     *                  qu'expanser la colonne — sinon table-layout: fixed
     *                  imposerait un overflow horizontal moche. */
    white-space: pre-wrap;
    overflow-wrap: break-word;
    word-break: break-word;
    transition: background var(--duration-fast) var(--ease-out);
}
.dw-grid-table td[contenteditable]:hover { background: var(--surface-2); }
.dw-grid-table td[contenteditable]:focus {
    outline: none;
    background: var(--surface);
    box-shadow: inset 0 0 0 2px var(--focus-ring);
}
.dw-grid-table tbody tr:first-child td[contenteditable] {
    background: var(--surface-2);
    font-weight: 600;
}
.dw-grid-table tbody tr:first-child td[contenteditable]:focus { background: var(--surface); }

/* Chrome row : poignées de colonne (en haut, fines) */
.dw-grid-col-handles th {
    height: 18px;
    vertical-align: middle;
}
/* Largeurs explicites des cellules de chrome — table-layout: fixed
 * a besoin de connaître la première rangée pour distribuer les
 * colonnes data. Sans largeur explicite, le corner prendrait 1/N
 * de la table. */
.dw-grid-corner,
.dw-grid-row-handle {
    width: 22px;
}
.dw-grid-row-handle {
    vertical-align: middle;
}

/* Boutons − (poignée ligne / colonne) : compacts au centre de la
 * cellule de poignée. Le bouton lui-même est petit, mais le clic est
 * délégué côté JS au `<th>` entier (cf. dossier-canvas.js) — n'importe
 * où dans la barre de chrome déclenche la suppression. Pas de cercle
 * ni de fond — c'est la mise en rouge de la ligne/colonne qui porte
 * le signal de destruction.
 *
 * `display: flex; margin: 0 auto` (vs `inline-flex` + text-align)
 * centre fiablement le bouton dans une <th> table-cell, indépendamment
 * du calcul anonymous-line-box des inline-level qui défaillait sur la
 * poignée de ligne.
 * Hover/focus/active explicités pour defeat le `button:hover` /
 * `button:focus` globaux qui repeindraient en navy via --primary*. */
.dw-grid-col-handle, .dw-grid-row-handle { cursor: pointer; }
.dw-grid-del-row, .dw-grid-del-col,
.dw-grid-del-row:hover, .dw-grid-del-col:hover,
.dw-grid-del-row:focus, .dw-grid-del-col:focus,
.dw-grid-del-row:active, .dw-grid-del-col:active {
    width: 14px; height: 14px;
    display: flex; align-items: center; justify-content: center;
    margin: 0 auto;
    background: transparent; border: 0; box-shadow: none;
    color: var(--danger);
    cursor: pointer;
    padding: 0;
    /* `pointer-events: none` : le clic traverse vers le <th> parent,
     * géré par la délégation JS. Évite la confusion de cibles entre
     * cellule et bouton. */
    pointer-events: none;
}
.dw-grid-del-row, .dw-grid-del-col {
    opacity: 0;
    transition: opacity var(--duration-fast) var(--ease-out);
}
.dw-grid-del-row::before, .dw-grid-del-col::before {
    content: "";
    display: block;
    width: 8px; height: 2px;
    background: currentColor;
    border-radius: 1px;
}
.dw-grid-table tbody tr:hover .dw-grid-del-row { opacity: 1; }
.dw-grid-col-handle:hover .dw-grid-del-col,
.dw-grid-col-handle:focus-within .dw-grid-del-col { opacity: 1; }

/* Hover sur une poignée → toute la ligne/colonne devient rouge pour
 * signaler la destruction imminente avant le clic.
 *   - Ligne : pur CSS via `:has()`, le tr contient la poignée hovered.
 *   - Colonne : `.is-col-doomed` posée par JS (les cells de la même
 *     colonne sont dans des <tr> différents, pas atteignables en CSS
 *     sans dupliquer une règle par index).
 * Préfixe `.dw-grid.is-editable` pour bumper la spécificité au-dessus
 * du reset zebra (`.dw-grid-table tbody tr:nth-child(even) td`,
 * spec 0,0,2,3) et du style « first row = header » (spec 0,0,3,3). */
.dw-grid.is-editable .dw-grid-table tbody tr:has(.dw-grid-row-handle:hover) > th,
.dw-grid.is-editable .dw-grid-table tbody tr:has(.dw-grid-row-handle:hover) > td,
.dw-grid.is-editable .dw-grid-table .dw-grid-col-handle.is-col-doomed,
.dw-grid.is-editable .dw-grid-table tbody tr td.is-col-doomed {
    background: var(--danger-light);
    transition: background var(--duration-fast) var(--ease-out);
}

/* Affordances « + » en bord de table : strip vertical à droite (ajoute
 * une colonne) et strip horizontal en bas (ajoute une ligne). Bandes
 * discrètes dashed — pas de glyphe `+` au repos, l'accent or au hover
 * suffit comme affordance. */
.dw-grid-add-col, .dw-grid-add-row {
    background: transparent;
    border: 1px dashed var(--border);
    cursor: pointer;
    padding: 0;
    /* Le texte « + » du template reste accessible pour les lecteurs
     * d'écran via aria-label mais visuellement masqué — la bande
     * dashed est l'affordance. */
    font-size: 0;
    color: transparent;
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}
.dw-grid-add-col {
    width: 20px;
    border-left: 0;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    align-self: stretch;
    /* Aligner la bande avec les lignes de données : ne couvre pas la
     * rangée des poignées de colonne (18px en haut), symétrique au
     * `margin-left: 22px` de la bande +row qui saute la poignée de
     * ligne. */
    margin-top: 18px;
}
.dw-grid-add-row {
    height: 20px;
    border-top: 0;
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    /* Aligner la bande avec les colonnes de données : ne couvre ni
     * la poignée de ligne (22px à gauche) ni la bande +col (20px à
     * droite) — l'utilisateur lit « ajouter une ligne ici, sous les
     * données ». */
    margin: 0 20px 0 22px;
}
.dw-grid-add-col:hover, .dw-grid-add-row:hover {
    background: var(--accent-light);
    border-color: var(--accent);
}

/* Toolbar restante (import seulement) */
.dw-grid-actions {
    display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
    font-size: .78rem;
    margin-top: .4rem;
}
.dw-grid-actions button {
    display: inline-flex; align-items: center; gap: .3rem;
    padding: .25rem .55rem;
    background: transparent;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    cursor: pointer;
    transition: color var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.dw-grid-actions button:hover {
    color: var(--accent); border-color: var(--accent);
    background: var(--accent-light);
}
.dw-grid-imported {
    font-size: .72rem;
    color: var(--text-muted);
    margin-left: auto;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    max-width: 14rem;
}

/* ---- Body : the muted "(empty state)" paragraph ---- */
.dw-body > p.muted {
    margin: .25rem 0;
    font-size: .85rem;
    color: var(--text-muted);
    padding: .25rem .55rem;
}

/* ---- Empty state on a fresh / pristine canvas ---- */
.dw-empty {
    border: 1px dashed var(--border);
    border-radius: var(--radius);
    padding: 2rem 1rem;
    text-align: center;
}

/* ---- Drag-and-drop visual cues ----
   Drop indicator : a single horizontal accent bar positioned by JS at
   the insertion edge inside the target zone (top / left col / right
   col / bottom). Spans the zone's width so it reads as "the widget
   will land here, across this whole drop target". */
.dw-canvas { position: relative; }
.dw-canvas.is-editable .dw-card { transition: border-color 120ms ease, opacity 80ms ease; }

/* ---- Status badge (clickable on the hero) ---- */
/* Status badge as a button — override the global `button` rule (l.626)
   that defaults all buttons to navy. The element keeps its `.tag` /
   `.tag.muted` visual via the class, so we only strip the UA chrome
   and the global button rules. */
.dossier-status-toggle.tag,
.dossier-status-toggle.tag.muted {
    cursor: pointer;
    border: 0;
    box-shadow: none;
    line-height: 1.4;
    transition: filter 120ms ease;
}
.dossier-status-toggle.tag {
    background: var(--primary-pale);
    color: var(--primary);
}
.dossier-status-toggle.tag.muted {
    background: var(--border-light);
    color: var(--text-muted);
}
.dossier-status-toggle.tag:hover {
    background: var(--primary-pale);
    color: var(--primary);
    filter: brightness(0.95);
    box-shadow: none;
}
.dossier-status-toggle.tag.muted:hover {
    background: var(--border-light);
    color: var(--text-muted);
    filter: brightness(0.95);
    box-shadow: none;
}
.dossier-status-toggle:focus { outline: none; }
.dossier-status-toggle.tag:focus-visible,
.dossier-status-toggle.tag.muted:focus-visible {
    box-shadow: 0 0 0 3px var(--focus-ring);
}

/* ============================================================================
 * .eml drag-and-drop on /communication/new
 * ========================================================================== */
.eml-dropzone {
    border: 2px dashed var(--border-strong);
    background: var(--surface-2);
    text-align: center;
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out);
}
.eml-dropzone p { margin: 0.35rem 0; }
.eml-dropzone.is-dragover {
    border-color: var(--primary);
    background: var(--primary-light);
}
.eml-dropzone .btn-link {
    background: none;
    border: 0;
    padding: 0;
    color: var(--primary);
    text-decoration: underline;
    cursor: pointer;
    font: inherit;
}
.eml-dropzone .btn-link:hover { color: var(--primary-hover); }

/* ============================================================================
 * Bulk-share — checkbox column, sticky bottom bar, share modal
 * ========================================================================== */
/* Checkbox column is opt-in : hidden by default, revealed when the user
 * enters selection mode via the ⋮ menu (cf. `window.__toggleSelectionMode`
 * in `static/app.js`, which toggles `body.is-selecting`).
 *
 * Specificity matters here: a global `th:first-child, td:first-child`
 * sets `min-width: 180px` to keep the first column comfortable. The
 * bulk-share checkbox lives in `:first-child` too, so we tag selectors
 * with the element + class to match that specificity and override via
 * cascade order. */
th.bs-head-th,
td.bs-row-td { display: none; }
/* The selectors below intentionally carry `table[data-excel] thead/tbody tr`
   so they out-specify the compact-density override
   (`body.is-compact table[data-excel] tbody tr td { padding: .45rem }`,
   specificity 0,2,4). Without that, compact mode re-imposes horizontal
   padding on this 2rem-wide cell and the 15px checkbox overflows into
   the next column. 2rem (32px) leaves a balanced ~8px gutter so the
   focus ring (3px) never clips. Centering = text-align + vertical-align. */
body.is-selecting table[data-excel] thead th.bs-head-th,
body.is-selecting table[data-excel] tbody tr td.bs-row-td {
    display: table-cell;
    width: 2rem;
    min-width: 2rem;
    max-width: 2rem;
    padding: 0;
    text-align: center;
    vertical-align: middle;
}
.bs-head-cb,
.bs-row-cb {
    /* Sizing comes from the global `input[type=checkbox]` rule (15px,
       appearance:none). Here we only neutralise margins and re-center. */
    margin: 0 auto;
    cursor: pointer;
    vertical-align: middle;
}
/* Actions menu (⋮) at the top of list pages. <details>/<summary> based,
 * same pattern as `.add-menu`. Opens a small popover with the share /
 * export / visibility shortcuts wired to the bulk-select selection. */
.page-header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}
.actions-menu {
    position: relative;
}
.actions-menu-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.45rem 0.6rem;
    list-style: none;
    cursor: pointer;
}
.actions-menu-btn::-webkit-details-marker { display: none; }
.actions-menu-btn svg { width: 18px; height: 18px; }
.actions-menu-count {
    font-size: 0.75rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.actions-menu-count:empty { display: none; }
.actions-menu[open] .actions-menu-btn { background: var(--surface-2); }
.actions-menu-items {
    position: absolute;
    top: calc(100% + 4px);
    right: 0;
    z-index: var(--z-popover);
    background: var(--surface-3);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: var(--space-1) 0;
    min-width: 12rem;
    display: flex;
    flex-direction: column;
}
.actions-menu-items button {
    background: none;
    border: 0;
    padding: 0.5rem 0.9rem;
    text-align: left;
    font: inherit;
    color: var(--text);
    cursor: pointer;
}
.actions-menu-items button:hover { background: var(--surface-2); }
.actions-menu-items button.actions-menu-clear {
    border-top: 1px solid var(--border-light);
    margin-top: var(--space-1);
    color: var(--text-muted);
    font-size: 0.85rem;
}
/* Selection-mode toggle: the menu first item morphs its label depending
 * on whether we're in selection mode. The action group below only
 * appears while in selection mode. */
.actions-menu-items .when-selecting { display: none; }
body.is-selecting .actions-menu-items .when-not-selecting { display: none; }
body.is-selecting .actions-menu-items .when-selecting { display: inline; }
.actions-menu-when-selecting { display: none; flex-direction: column; }
body.is-selecting .actions-menu-when-selecting {
    display: flex;
    border-top: 1px solid var(--border-light);
    margin-top: var(--space-1);
    padding-top: var(--space-1);
}
body.is-selecting .actions-menu-toggle { color: var(--accent); }
/* Count badge irrelevant outside selection mode (sticks at "" anyway,
 * just belt and suspenders). */
body:not(.is-selecting) .actions-menu-count { display: none; }

.modal-card-lg {
    max-width: 34rem;
    width: 92%;
    text-align: left;
    padding: 1.5rem 1.6rem 1.3rem;
}
.modal-card-lg h3 {
    text-align: left;
    margin: 0;
    font-size: 1.1rem;
}
.modal-card-lg h3 #shareModalCount { font-weight: 500; }
/* Full-bleed tab strip: the negative margin must match the card's
   horizontal padding (1.6rem) so the underline border spans edge to
   edge instead of stopping short. */
.share-tabs {
    display: flex;
    gap: 0.15rem;
    border-bottom: 1px solid var(--border);
    margin: 0.9rem -1.6rem var(--space-4);
    padding: 0 1.6rem;
}
.share-tab {
    background: none;
    border: 0;
    padding: 0.5rem 0.9rem;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.85rem;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color 0.15s, border-color 0.15s;
}
.share-tab:hover { color: var(--text); }
.share-tab.is-active {
    color: var(--primary);
    border-bottom-color: var(--primary);
    font-weight: 600;
}
.share-pane { display: none; text-align: left; }
.share-pane.is-active { display: block; }
.share-pane .row { margin-bottom: var(--space-4); }
.share-pane input[type="text"],
.share-pane input[type="email"],
.share-pane textarea {
    width: 100%;
}
.share-pane p.muted { font-size: 0.8rem; margin: 0.5rem 0 0; line-height: 1.4; }
.share-pane .modal-actions { margin-top: 1.3rem; }
/* Visibility radios. Without these resets the global
   `form input:not([type=checkbox])` rule stretches each radio to
   width:100% with input chrome (it rendered as a centred blob), and
   the global `form label` rule prints the option text as tiny
   uppercase grey. Turn each option into a readable, clickable card. */
.share-pane .row label:has(input[type="radio"]) {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin: 0 0 0.55rem;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: pointer;
    font-size: 0.88rem;
    font-weight: 400;
    color: var(--text);
    text-transform: none;
    letter-spacing: 0;
    transition: border-color 0.15s, background 0.15s;
}
.share-pane .row label:has(input[type="radio"]):hover { border-color: var(--primary); }
.share-pane .row label:has(input[type="radio"]:checked) {
    border-color: var(--primary);
    background: var(--primary-light);
}
.share-pane .row label strong { font-weight: 600; }
.share-pane input[type="radio"] {
    appearance: auto;
    -webkit-appearance: auto;
    width: 15px;
    height: 15px;
    min-width: 15px;
    margin: 0.08rem 0 0;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: none;
    flex-shrink: 0;
    accent-color: var(--primary);
    cursor: pointer;
}

/* ============================================================================
 * .list-toolbar — Excel-like single-row ribbon at the top of a list page.
 * Replaces the legacy .page-header for /persons (proto, May 2026). If the
 * pattern proves itself, extend to other lists.
 * ========================================================================== */
/* Pill + glass aesthetic borrowed from `.gp-toolbar` (graph dashboard).
 * Single-row rounded bar, semi-transparent surface + backdrop blur,
 * subtle border that crisps on hover/focus. Contents vertically centered
 * and the search field is a pill-within-pill that grows on focus. */
/* === Logique : la barre s'adapte à son contenu, pas l'inverse ===
   La toolbar est `width: fit-content` → sa largeur = somme des contenus
   + gap + padding. Quand le mode sélection ajoute 3 boutons, la barre
   GRANDIT pour les accueillir. Quand le popover Filtres ouvre, la barre
   ne bouge pas. Centrée par `margin-inline: auto`. Si jamais le contenu
   exceeds le viewport, `flex-wrap: wrap` permet une 2e ligne plutôt
   qu'un overflow horizontal.

   Le hover/focus ne change QUE le visuel (bg, border, shadow) — il ne
   touche pas aux dimensions. Les contenus ont des tailles stables. */
.list-toolbar {
    display: flex;
    align-items: center;
    /* Quand le contenu déborde, on wrap sur 2 lignes ; on centre chaque
       ligne pour éviter qu'une ligne soit pleine-largeur et l'autre
       hug-left (déséquilibré). `align-content: center` aligne les rangs
       verticalement quand il y a un peu d'air vertical. */
    flex-wrap: wrap;
    justify-content: center;
    align-content: center;
    /* row-gap réduit (le bar respire moins entre rangs qu'entre items) */
    gap: 0.35rem var(--space-3);
    padding: 0.45rem 1rem;
    /* `fit-content` = la bar prend la largeur de son contenu, cappé à
       100% du parent (le `<main>`). Centré horizontalement via
       margin-inline auto. */
    width: fit-content;
    max-width: 100%;
    margin: 0 auto var(--space-4);
    background: var(--surface);
    background: color-mix(in srgb, var(--surface) 60%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    backdrop-filter: blur(14px) saturate(150%);
    border: 1px solid var(--border-light);
    border-color: color-mix(in srgb, var(--border) 60%, transparent);
    /* Border-radius : pas un pure pill (999px) qui devient une stadium
       allongée bizarre quand la bar wrap sur 2 rangs. On utilise une
       valeur fixe douce qui marche pour 1 et 2 rangs — type bouton
       arrondi macOS. */
    border-radius: 1.25rem;
    box-shadow: var(--shadow-sm);
    /* Sticky scroll-aware : la barre suit le viewport en haut. JS toggle
       `.is-hidden` selon la direction du scroll (down → cache vers le
       haut, up → réapparaît). `top: 0.6rem` laisse un peu d'air entre
       la barre et le bord du viewport quand elle est ancrée.

       `backdrop-filter` crée déjà un stacking context — le `z-index`
       élevé garantit que la barre passe au-dessus du `.card` qui suit
       (sinon les popovers `.lt-pop` qui débordent vers le bas seraient
       cachés par les sticky `<th>` du tableau, et les clics passeraient
       à travers). */
    position: sticky;
    top: 0.6rem;
    z-index: var(--z-dropdown);
    transition: background var(--duration-base) var(--ease-out),
                border-color var(--duration-base) var(--ease-out),
                box-shadow var(--duration-base) var(--ease-out),
                transform var(--duration-base) var(--ease-out);
}
/* `.is-hidden` slide la toolbar hors-viewport vers le haut. 120% (au lieu
   de 100%) absorbe le margin-bottom + l'ombre projetée → pas de pixel
   résiduel visible. */
.list-toolbar.is-hidden {
    transform: translateY(-120%);
}
.list-toolbar:hover,
.list-toolbar:focus-within {
    background: color-mix(in srgb, var(--surface) 80%, transparent);
    border-color: var(--border);
    box-shadow: var(--shadow);
}
.list-toolbar .lt-title {
    font-family: var(--font-display);
    font-size: 0.95rem;
    font-weight: 600;
    margin: 0;
    color: var(--text);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    flex-shrink: 0;
    line-height: 1;
}
.list-toolbar .lt-count-inline {
    font-family: var(--font-sans);
    font-size: 0.7rem;
    color: var(--text-muted);
    font-weight: 500;
    background: color-mix(in srgb, var(--surface-2) 70%, transparent);
    padding: 0.1rem 0.5rem;
    border-radius: 999px;
    font-variant-numeric: tabular-nums;
}
.list-toolbar .lt-sep {
    width: 1px;
    height: 1.2rem;
    background: var(--border-light);
    flex-shrink: 0;
}
.list-toolbar .lt-group,
.list-toolbar .lt-when-selecting {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}
.list-toolbar .lt-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    /* Padding stable. Pas de changement au hover — la barre s'adapte au
       contenu, le contenu garde une taille stable. */
    padding: 0.4rem 0.7rem;
    background: transparent;
    border: 0;
    border-radius: 999px;
    color: var(--text-muted);
    font: inherit;
    font-size: 0.78rem;
    font-weight: 500;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out);
}
.list-toolbar .lt-btn:hover {
    background: var(--surface-2);
    color: var(--text);
}
.list-toolbar .lt-btn svg { width: 14px; height: 14px; }
/* Boutons icône-seule (label porté par le tooltip natif `title`) :
   compacts, quasi carrés, pour raccourcir la barre. */
.list-toolbar .lt-btn-icon { padding: 0.4rem 0.5rem; gap: 0.2rem; }
.list-toolbar .lt-btn-icon .lt-chev { width: 11px; height: 11px; opacity: 0.7; }
/* État actif d'un toggle (densité) — tinte primary, comme un contrôle
   enfoncé. */
.list-toolbar .lt-btn.is-active {
    background: var(--primary-light);
    color: var(--primary);
}
.list-toolbar .lt-btn.is-active:hover {
    background: var(--primary-light);
    color: var(--primary);
}
/* État « refinement actif » (filtre ou tri appliqué) — mis en avant en
   teinte accent (or, signature) + gras, distinct du toggle densité
   (primary). Pastille de gauche pour un repère immédiat dans la barre. */
/* Sélecteurs spécifiques : `.lt-menu[open] > summary` (0,3,1) écrasait
   un simple `.lt-btn.lt-btn-on` (0,3,0) → on cible explicitement les
   summary de menu (ouverts ET fermés) pour gagner la cascade, plus une
   variante générique pour les futurs boutons hors menu. */
.list-toolbar .lt-menu > summary.lt-btn.lt-btn-on,
.list-toolbar .lt-menu[open] > summary.lt-btn.lt-btn-on,
.list-toolbar .lt-btn.lt-btn-on,
.list-toolbar .lt-btn.lt-btn-on:hover {
    background: var(--accent-light);
    color: var(--accent-active);
    font-weight: 600;
}
.list-toolbar .lt-btn.lt-btn-on::before {
    content: "";
    width: 5px; height: 5px;
    border-radius: 50%;
    background: var(--accent);
    flex: 0 0 auto;
}
.list-toolbar .lt-btn.lt-btn-on .lt-btn-value {
    color: var(--accent-active);
    font-weight: 600;
}
.list-toolbar .lt-btn.lt-btn-on .lt-filter-count {
    background: var(--accent);
    color: var(--on-accent);
    min-width: 1.1rem;
    text-align: center;
    border-radius: 999px;
    padding: 0 0.3rem;
}
.list-toolbar .lt-btn-primary {
    background: var(--primary);
    color: var(--on-primary);
}
.list-toolbar .lt-btn-primary:hover {
    background: var(--primary-hover);
    color: var(--on-primary);
}
.list-toolbar .lt-spacer { flex: 1 1 0; min-width: 0; }
.list-toolbar .lt-subtitle {
    font-size: 0.7rem;
    color: var(--text-muted);
    flex-shrink: 0;
}
.list-toolbar .lt-selection-count {
    font-size: 0.72rem;
    color: var(--accent);
    font-weight: 600;
    margin-left: 0.25rem;
    font-variant-numeric: tabular-nums;
}
.list-toolbar .lt-selection-count:empty { display: none; }

/* Selection-mode toggle inside the toolbar.
   Anciennement, `.lt-when-selecting` était `display: none` au repos puis
   `display: inline-flex` en mode sélection — le switch de `display` n'est
   pas animable, donc les 3 boutons (Mail/CSV/Visibility) apparaissaient
   abruptement, faisant sauter la toolbar.
   Nouveau pattern : on garde `display: inline-flex` en permanence mais
   on collapse à `max-width: 0; opacity: 0; padding: 0` au repos, puis on
   anime vers leur taille naturelle en mode sélection. Le layout du
   parent flexbox se rééquilibre en douceur. */
.list-toolbar .when-selecting { display: none; }
body.is-selecting .list-toolbar .when-not-selecting { display: none; }
body.is-selecting .list-toolbar .when-selecting { display: inline; }
.list-toolbar .lt-when-selecting {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    max-width: 0;
    opacity: 0;
    overflow: hidden;
    padding-left: 0;
    margin-left: 0;
    transition: max-width var(--duration-base) var(--ease-out),
                opacity   var(--duration-base) var(--ease-out),
                padding-left var(--duration-base) var(--ease-out),
                margin-left  var(--duration-base) var(--ease-out);
}
body.is-selecting .list-toolbar .lt-when-selecting {
    max-width: 30rem;       /* large pour ne jamais clipper les boutons */
    opacity: 1;
    padding-left: 2px;
    margin-left: 2px;
}
body.is-selecting .list-toolbar .lt-btn:has(.when-selecting) { color: var(--accent); }
/* Le séparateur .lt-sep qui précède la sélection est techniquement
   toujours visible — c'est OK en mode normal (encadre le groupe
   Sélectionner). On garde tel quel pour minimiser le bruit visuel. */

/* Compact density toggle — squeezes everything around the list so more
 * rows fit on screen :
 * - Page padding shrinks (sidebar gap).
 * - The card around the list loses its decorative padding / border.
 * - The toolbar pulls closer to the table.
 * - Cell padding + font tightens.
 * - Avatars shrink. */
body.is-compact main { padding: 0.5rem 0.6rem 0.6rem; --main-pad-x: 0.6rem; }
body.is-compact .list-toolbar { margin-bottom: var(--space-2); padding: 0.25rem 0.6rem; }
body.is-compact .list-toolbar .lt-btn { padding: 0.25rem 0.55rem; font-size: 0.75rem; }
body.is-compact .list-toolbar .lt-title { font-size: 0.85rem; }
body.is-compact .card:has(table[data-excel]) {
    padding: 0;
    margin-bottom: 0;
    border: 0;
    box-shadow: none;
    background: transparent;
    border-radius: 0;
}
body.is-compact .card:has(table[data-excel]) .table-wrap { margin: 0; padding: 0; }
body.is-compact table[data-excel] tbody tr td,
body.is-compact table[data-excel] thead th {
    padding: 0.3rem 0.45rem;  /* horizontal aussi resserré (vs 0.75rem en standard) */
    font-size: 0.78rem;
}
body.is-compact table[data-excel] .avatar { width: 1.4rem; height: 1.4rem; font-size: 0.7rem; }
/* Cellule Nom : moins d'espace entre l'avatar et le nom en compact. */
body.is-compact #tablePersons .cell-person { gap: 0.35rem; }
/* Resserre les max-widths des colonnes en compact pour packer plus de
   contenu horizontalement. Garde les ratios relatifs (Nom/Org/Notes
   restent les plus larges) mais réduit globalement d'environ 25%. */
body.is-compact #tablePersons td[data-col="name"]               { max-width: 11rem; }
body.is-compact #tablePersons td[data-col="type"]               { max-width:  6.5rem; }
body.is-compact #tablePersons td[data-col="current_org"]        { max-width: 11rem; }
body.is-compact #tablePersons td[data-col="current_title"]      { max-width:  8rem; }
body.is-compact #tablePersons td[data-col="sector"]             { max-width:  8.5rem; }
body.is-compact #tablePersons td[data-col="lieu"]               { max-width:  7rem; }
body.is-compact #tablePersons td[data-col="country"]            { max-width:  6.5rem; }
body.is-compact #tablePersons td[data-col="visibility"]         { max-width:  6rem; }
body.is-compact #tablePersons td[data-col="prospect_stage"]     { max-width:  7rem; }
body.is-compact #tablePersons td[data-col="has_followup"]       { max-width:  5rem; }
body.is-compact #tablePersons td[data-col="email_pro"]          { max-width: 10rem; }
body.is-compact #tablePersons td[data-col="phone_pro"]          { max-width:  7.5rem; }
body.is-compact #tablePersons td[data-col="linkedin"]           { max-width:  3.5rem; }
body.is-compact #tablePersons td[data-col="notes"]              { max-width: 11rem; }
body.is-compact #tablePersons td[data-col="created_at"]         { max-width:  6rem; }
body.is-compact #tablePersons td[data-col="last_contact"]       { max-width:  9.5rem; }
body.is-compact #tablePersons td[data-col="last_contact_notes"] { max-width: 11rem; }
/* Le caption « Antoine Berger, Camille Sorel » sous la date — un cran
   plus petit + plus serré en compact. */
body.is-compact .cell-last-contact .lc-collabs {
    font-size: 0.66rem;
    max-width: 9rem;
}
/* Toolbar : items plus serrés, gap réduit, search rétractée. */
body.is-compact .list-toolbar { gap: var(--space-2); }
body.is-compact .list-toolbar .lt-search { flex: 0 1 12rem; }
body.is-compact .list-toolbar .lt-sep { margin: 0 0.1rem; }

/* Search bar embedded inside `.list-toolbar` — pill-within-pill aesthetic
 * mirroring `.gp-toolbar-search` on the graph dashboard. Compact at rest,
 * grows on focus, transparent bg that picks up surface tint on hover/focus.
 * Overrides the generic `.search` rules above via the parent selector. */
.list-toolbar .lt-search {
    /* Taille stable. Pas de `margin-left: auto` (la bar n'a plus d'espace
       libre à pousser — elle se dimensionne au contenu). Pas de transition
       sur les dimensions : seuls les états visuels (bg/border) animent
       au focus pour le feedback Spotlight-style. */
    flex: 0 0 18rem;
    margin: 0;
    padding: 0.35rem 0.85rem;
    border-radius: 999px;
    background: transparent;
    border: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
    box-shadow: none;
    transition: background var(--duration-fast) var(--ease-out),
                border-color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
}
.list-toolbar .lt-search:hover {
    background: color-mix(in srgb, var(--surface) 35%, transparent);
    border-color: color-mix(in srgb, var(--border) 70%, transparent);
}
.list-toolbar .lt-search:focus-within {
    background: color-mix(in srgb, var(--surface) 70%, transparent);
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.list-toolbar .lt-search svg { width: 14px; height: 14px; color: var(--text-muted); }
.list-toolbar .lt-search input {
    flex: 1; min-width: 0;
    background: transparent; border: 0; outline: none;
    font: inherit; font-size: 0.82rem;
    padding: 0;
}
.list-toolbar .lt-search input::placeholder { color: var(--text-muted); }

/* === Apple-style dropdowns & segmented controls in `.list-toolbar` ========
 * Glass panel with backdrop blur, soft shadow, fade-in. Mirrors the .gp-pop
 * popovers on the graph dashboard. */
.list-toolbar .lt-menu { position: relative; }
.list-toolbar .lt-menu > summary {
    list-style: none;
    cursor: pointer;
}
.list-toolbar .lt-menu > summary::-webkit-details-marker { display: none; }
.list-toolbar .lt-menu[open] > summary {
    background: var(--surface-2);
    color: var(--text);
}
.list-toolbar .lt-chev {
    width: 11px !important;
    height: 11px !important;
    opacity: 0.6;
    margin-left: 0.15rem;
    transition: transform var(--duration-fast) var(--ease-out);
}
.list-toolbar .lt-menu[open] .lt-chev { transform: rotate(180deg); }
.list-toolbar .lt-btn-label { color: var(--text-muted); }
.list-toolbar .lt-btn-value {
    color: var(--text);
    font-weight: 600;
    margin-left: 0.15rem;
}
.list-toolbar .lt-pop {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: var(--z-popover);
    min-width: 14rem;
    padding: 0.35rem;
    background: var(--surface);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    -webkit-backdrop-filter: blur(18px) saturate(160%);
    backdrop-filter: blur(18px) saturate(160%);
    border: 1px solid var(--border);
    border-color: color-mix(in srgb, var(--border) 80%, transparent);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    display: flex;
    flex-direction: column;
    gap: 1px;
    animation: lt-pop-in var(--duration-fast) var(--ease-out);
}
@keyframes lt-pop-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}
.list-toolbar .lt-pop-wide { min-width: 18rem; }
.list-toolbar .lt-pop-item {
    display: flex; align-items: center; gap: 0.55rem;
    padding: 0.5rem 0.7rem;
    background: transparent;
    border: 0;
    border-radius: var(--radius);
    color: var(--text);
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.list-toolbar .lt-pop-item:hover {
    background: var(--primary-light);
    color: var(--primary);
}
.list-toolbar .lt-pop-item svg {
    width: 16px; height: 16px;
    color: var(--text-muted);
    flex-shrink: 0;
}
.list-toolbar .lt-pop-item:hover svg { color: var(--primary); }
.list-toolbar .lt-pop-item.is-active {
    color: var(--primary);
    font-weight: 600;
}
.list-toolbar .lt-pop-item.is-active::after {
    content: "";
    margin-left: auto;
    width: 14px; height: 14px;
    background: currentColor;
    -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' d='m6 12 4 4 8-8'/></svg>") center/contain no-repeat;
            mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round' d='m6 12 4 4 8-8'/></svg>") center/contain no-repeat;
}
.list-toolbar .lt-pop-hint {
    margin-left: auto;
    color: var(--text-muted);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
}
.list-toolbar .lt-pop-section { padding: 0.4rem 0.5rem 0.2rem; }
.list-toolbar .lt-pop-section + .lt-pop-section { border-top: 1px solid var(--border-light); margin-top: 0.25rem; }
.list-toolbar .lt-pop-label {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-bottom: 0.4rem;
}
/* Apple-style segmented control. Inline-flex with a sliding active state. */
.list-toolbar .lt-segmented {
    display: inline-flex;
    width: 100%;
    background: var(--surface-2);
    border-radius: var(--radius);
    padding: 2px;
    gap: 2px;
}
.list-toolbar .lt-seg {
    flex: 1;
    background: transparent;
    border: 0;
    padding: 0.4rem 0.6rem;
    font: inherit;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--text-muted);
    border-radius: calc(var(--radius) - 2px);
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-out),
                color var(--duration-fast) var(--ease-out),
                box-shadow var(--duration-fast) var(--ease-out);
}
.list-toolbar .lt-seg:hover { color: var(--text); }
.list-toolbar .lt-seg.is-active {
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-sm);
}

/* === Filter popover (multi-critères, style Numbers) ====================
   Empilage de règles {criterion, op, value} en ET. Le popover hérite de
   `.lt-pop` (glass + animation) puis override pour gagner en largeur, et
   ajoute un head/foot avec les actions Effacer / Ajouter / Enregistrer. */
.list-toolbar .lt-pop-filter {
    min-width: 32rem;
    max-width: 92vw;
    padding: 0;
    gap: 0;
    /* On veut un rythme vertical clair head → rules → foot, pas le gap
       par défaut de 1 px du `.lt-pop` qui groupait des `.lt-pop-item`. */
}
/* Compteur (N) sur le bouton de la toolbar. Pastille discrète qui rappelle
   les counts dans les filter pills de la timeline (`.activity-toolbar`). */
.list-toolbar .lt-filter-count {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 1.1rem;
    padding: 0 0.35rem;
    background: var(--accent-light);
    color: var(--accent);
    border-radius: 999px;
    font-size: 0.7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
    margin-left: 0.25rem;
}
/* `.lt-filter-count` ci-dessus est `display:inline-flex` (spécificité
   0,2,0) → bat le `[hidden]{display:none}` UA (0,1,0), donc le JS qui pose
   l'attribut `hidden` quand le compteur vaut 0 n'avait aucun effet : persons
   laissait un « 0 », les listes macro un pill vide. On réaffirme le masquage. */
.list-toolbar .lt-filter-count[hidden] { display: none !important; }
.list-toolbar .lt-pop-filter .fp-head,
.list-toolbar .lt-pop-filter .fp-foot {
    display: flex; align-items: center; justify-content: space-between;
    padding: 0.55rem 0.75rem;
    font-size: 0.78rem;
}
.list-toolbar .lt-pop-filter .fp-head {
    border-bottom: 1px solid var(--border-light);
    font-weight: 600;
    color: var(--text);
}
.list-toolbar .lt-pop-filter .fp-foot {
    border-top: 1px solid var(--border-light);
    background: var(--surface-2);
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.list-toolbar .lt-pop-filter .fp-link {
    background: transparent; border: 0; padding: 0;
    color: var(--text-muted);
    font: inherit; font-size: 0.75rem;
    cursor: pointer;
}
.list-toolbar .lt-pop-filter .fp-link:hover { color: var(--primary); }
.list-toolbar .lt-pop-filter .fp-link[disabled] {
    color: var(--text-light);
    cursor: not-allowed;
}
.list-toolbar .lt-pop-filter .fp-add {
    display: inline-flex; align-items: center; gap: 0.4rem;
    background: transparent;
    border: 0;
    padding: 0.2rem 0.4rem;
    color: var(--primary);
    font: inherit; font-size: 0.8rem; font-weight: 600;
    border-radius: var(--radius);
    cursor: pointer;
}
.list-toolbar .lt-pop-filter .fp-add:hover { background: var(--primary-light); }
.list-toolbar .lt-pop-filter .fp-add svg { width: 14px; height: 14px; }
/* Filtres sauvegardés (segments) — bloc en pied du popover. */
/* Filtres sauvegardés — repos minimal : juste les noms cliquables. Les
   actions (crayon / ×) sont révélées au survol de la ligne pour ne pas
   surcharger ; l'enregistrement est replié derrière un seul lien. */
.list-toolbar .lt-pop-filter .fp-saved {
    border-top: 1px solid var(--border-light);
    padding: 0.3rem 0.4rem 0.4rem;
    display: flex; flex-direction: column;
}
/* Les sélecteurs .fp-saved-* (specificity 0,3,0) battent la règle UA
   [hidden]{display:none} (0,1,0) — il faut réaffirmer le masquage sinon
   les forms `hidden` resteraient affichés. */
.list-toolbar .lt-pop-filter .fp-saved[hidden],
.list-toolbar .lt-pop-filter .fp-saved-row[hidden],
.list-toolbar .lt-pop-filter .fp-saved-save[hidden],
.list-toolbar .lt-pop-filter .fp-saved-renameform[hidden],
.list-toolbar .lt-pop-filter .fp-saved-updateform[hidden],
.list-toolbar .lt-pop-filter .fp-saved-addbtn[hidden] { display: none !important; }
.list-toolbar .lt-pop-filter .fp-saved-list {
    display: flex; flex-direction: column;
}
.list-toolbar .lt-pop-filter .fp-saved-row {
    display: flex; align-items: center; gap: 0.2rem;
    border-radius: var(--radius-sm);
}
.list-toolbar .lt-pop-filter .fp-saved-row:hover { background: var(--surface-2); }
.list-toolbar .lt-pop-filter .fp-saved-apply {
    flex: 1; min-width: 0; text-align: left;
    background: transparent; border: 0;
    padding: 0.28rem 0.4rem;
    color: var(--text);
    font: inherit; font-size: 0.8rem;
    cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.list-toolbar .lt-pop-filter .fp-saved-apply:hover { color: var(--primary); }
/* Actions cachées au repos, révélées au survol / focus clavier. */
.list-toolbar .lt-pop-filter .fp-saved-actions {
    display: inline-flex; align-items: center; gap: 0.1rem;
    opacity: 0;
    transition: opacity var(--duration-fast) var(--ease-out);
}
.list-toolbar .lt-pop-filter .fp-saved-row:hover .fp-saved-actions,
.list-toolbar .lt-pop-filter .fp-saved-row:focus-within .fp-saved-actions {
    opacity: 1;
}
.list-toolbar .lt-pop-filter .fp-saved-ic {
    background: transparent; border: 0;
    padding: 0.25rem;
    color: var(--text-light);
    cursor: pointer;
    display: inline-flex;
    border-radius: var(--radius-sm);
}
.list-toolbar .lt-pop-filter .fp-saved-ic:hover { color: var(--primary); }
.list-toolbar .lt-pop-filter .fp-saved-ic-danger:hover { color: var(--danger); }
.list-toolbar .lt-pop-filter .fp-saved-ic svg { width: 12px; height: 12px; }
/* Form inline d'édition + form d'ajout : compacts, une seule ligne quand
   possible. */
/* Renommage en place : occupe exactement la place de la ligne (mêmes
   métriques), une seule rangée champ + ✓ + ×. */
.list-toolbar .lt-pop-filter .fp-saved-renameform {
    display: flex; align-items: center; gap: 0.2rem;
    padding: 0.1rem 0.1rem 0.1rem 0;
}
/* Bouton repli « + Enregistrer le filtre actuel » — discret, façon
   .fp-add ; muet quand aucun filtre complet n'est actif. */
.list-toolbar .lt-pop-filter .fp-saved-addbtn {
    display: inline-flex; align-items: center; gap: 0.4rem;
    align-self: flex-start;
    background: transparent; border: 0;
    padding: 0.3rem 0.4rem;
    color: var(--primary);
    font: inherit; font-size: 0.78rem; font-weight: 600;
    border-radius: var(--radius-sm);
    cursor: pointer;
}
.list-toolbar .lt-pop-filter .fp-saved-addbtn:hover { background: var(--primary-light); }
.list-toolbar .lt-pop-filter .fp-saved-addbtn:disabled {
    color: var(--text-light); cursor: not-allowed; background: transparent;
}
.list-toolbar .lt-pop-filter .fp-saved-addbtn svg { width: 13px; height: 13px; }
.list-toolbar .lt-pop-filter .fp-saved-save {
    display: flex; align-items: center; gap: 0.4rem;
    margin: 0.1rem 0 0;
    padding: 0.3rem 0.4rem;
}
.list-toolbar .lt-pop-filter .fp-saved-name {
    flex: 1; min-width: 0;
    padding: 0.28rem 0.45rem;
    background: var(--surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit; font-size: 0.78rem;
}
.list-toolbar .lt-pop-filter .fp-saved-name:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px var(--focus-ring);
}
.list-toolbar .lt-pop-filter .fp-saved-save .fp-link {
    font-weight: 600; white-space: nowrap;
}
.list-toolbar .lt-pop-filter .fp-rules {
    display: flex; flex-direction: column;
    gap: 0.4rem;
    padding: 0.6rem 0.75rem;
    max-height: 60vh;
    overflow-y: auto;
}
.list-toolbar .lt-pop-filter .fp-empty {
    padding: 0.5rem 0.25rem;
    color: var(--text-muted);
    font-size: 0.78rem;
    text-align: center;
    font-style: italic;
}
/* Une rangée de règle : [et] [critère] [opérateur] [valeur] [×]. Le
   label "et" est inséré devant à partir de la 2e règle. Les selects
   restent natifs pour le proto — pas la peine d'embarquer `single_select`
   ici tant que les listes sont courtes. */
.list-toolbar .lt-pop-filter .fp-row {
    display: flex; align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    padding: 0.4rem 0.5rem;
    background: var(--surface);
    border: 1px solid var(--border-light);
    border-radius: var(--radius);
}
/* "ET" separator between rules, on its own line. */
.list-toolbar .lt-pop-filter .fp-conn {
    display: flex; align-items: center; gap: 0.5rem;
    color: var(--text-light);
    font-size: 0.66rem; font-weight: 700;
    letter-spacing: 0.08em;
    margin: 0.1rem 0 0.1rem 0.2rem;
}
.list-toolbar .lt-pop-filter .fp-conn::after {
    content: ""; flex: 1;
    border-top: 1px solid var(--border-light);
}
.list-toolbar .lt-pop-filter .fp-row select,
.list-toolbar .lt-pop-filter .fp-row input,
.list-toolbar .lt-pop-filter .fp-value-num {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.3rem 0.5rem;
    font: inherit; font-size: 0.8rem;
    color: var(--text);
    cursor: pointer;
    /* Largeurs souples — chaque select prend ce qu'il faut. Le critère
       est un peu plus large parce que les libellés varient. */
    max-width: 12rem;
}
.list-toolbar .lt-pop-filter .fp-criterion { min-width: 8rem; flex: 1 1 8rem; }
.list-toolbar .lt-pop-filter .fp-op { min-width: 5rem; flex: 0 1 7rem; }
.list-toolbar .lt-pop-filter .fp-value { min-width: 6rem; flex: 1 1 7rem; }
.list-toolbar .lt-pop-filter .fp-value-text {
    min-width: 6rem; flex: 1 1 8rem; cursor: text;
}
.list-toolbar .lt-pop-filter .fp-value-num {
    width: 4.5rem;
    cursor: text;
}
.list-toolbar .lt-pop-filter .fp-unit {
    color: var(--text-muted);
    font-size: 0.78rem;
}
.list-toolbar .lt-pop-filter .fp-empty-values {
    color: var(--text-muted);
    font-size: 0.78rem;
    font-style: italic;
}
.list-toolbar .lt-pop-filter .fp-row select:focus,
.list-toolbar .lt-pop-filter .fp-row input:focus,
.list-toolbar .lt-pop-filter .fp-value-num:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--focus-ring);
}
.list-toolbar .lt-pop-filter .fp-remove {
    background: transparent;
    border: 0;
    border-radius: 999px;
    width: 1.5rem; height: 1.5rem;
    display: inline-flex; align-items: center; justify-content: center;
    color: var(--text-muted);
    cursor: pointer;
    margin-left: auto;
}
.list-toolbar .lt-pop-filter .fp-remove:hover {
    background: var(--danger-light);
    color: var(--danger);
}
.list-toolbar .lt-pop-filter .fp-remove svg { width: 12px; height: 12px; }

/* Cellule « Dernier contact » : date sur la 1re ligne + petit caption
   en-dessous listant le(s) collaborateur(s) attaché(s) à ce contact
   (le plus récent meeting OU communication, cf. CTE `last_contact` dans
   `routes/persons.py`). Caption muted pour ne pas voler le focus à la
   date, mais visible — c'est l'info "qui l'a vu en dernier" que le
   conseiller cherche au scan. */
.cell-last-contact {
    line-height: 1.3;
}
.cell-last-contact .lc-date {
    display: block;
}
.cell-last-contact .lc-collabs {
    display: block;
    font-size: 0.72rem;
    color: var(--text-muted);
    margin-top: 0.1rem;
    max-width: 14rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Liens vers la fiche du collaborateur : restent muted par défaut
   (cohérent avec le caption), mais gagnent la teinte de marque au hover
   pour signaler la cliquabilité. Pas d'underline permanent — déjà
   surchargés visuellement par la liste comma-separated. */
.cell-last-contact .lc-collabs a {
    color: inherit;
    text-decoration: none;
}
.cell-last-contact .lc-collabs a:hover {
    color: var(--primary);
    text-decoration: underline;
}

/* === Scanner une carte (module card_import) ============================ */
.scan-card { max-width: 34rem; }
.scan-section-label {
    font-size: 0.7rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.05em;
    color: var(--text-muted);
    margin: 0 0 0.6rem;
}
.scan-section-label + * { margin-bottom: 1.4rem; }
.scan-modes {
    display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem;
}
/* These components are <label> elements → cancel the global form-label
   uppercase/letter-spacing so their content reads normally. */
.scan-mode, .scan-drop {
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
}
.scan-mode { cursor: pointer; }
.scan-mode input { position: absolute; opacity: 0; pointer-events: none; }
.scan-mode-body {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: 0.15rem;
    padding: 0.85rem 0.95rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface);
    transition: border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.scan-mode-body svg { width: 22px; height: 22px; color: var(--text-muted);
    margin-bottom: 0.35rem; }
.scan-mode-title { font-weight: 600; font-size: 0.92rem; color: var(--text); }
.scan-mode-sub { font-size: 0.78rem; color: var(--text-muted); }
.scan-mode:hover .scan-mode-body { border-color: var(--border-strong); }
.scan-mode:has(input:checked) .scan-mode-body {
    border-color: var(--primary);
    background: var(--primary-pale);
}
.scan-mode:has(input:checked) .scan-mode-body svg,
.scan-mode:has(input:checked) .scan-mode-title { color: var(--primary); }
.scan-mode:has(input:focus-visible) .scan-mode-body {
    box-shadow: 0 0 0 3px var(--focus-ring);
}

.scan-drop {
    position: relative;
    display: flex; flex-direction: column; align-items: center;
    gap: 0.4rem; text-align: center;
    padding: 1.8rem 1rem;
    border: 1.5px dashed var(--border-strong);
    border-radius: var(--radius-lg);
    background: var(--surface-2);
    cursor: pointer;
    transition: border-color var(--duration-fast) var(--ease-out),
                background var(--duration-fast) var(--ease-out);
}
.scan-drop:hover { border-color: var(--primary); background: var(--primary-pale); }
.scan-drop input {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    opacity: 0; cursor: pointer;
}
.scan-drop-icon { width: 30px; height: 30px; color: var(--text-muted); }
.scan-drop-text { font-size: 0.9rem; color: var(--text); }
.scan-drop-text strong { color: var(--primary); }
.scan-drop-hint { font-size: 0.76rem; color: var(--text-muted); }
.scan-drop:has(input:focus-visible) {
    box-shadow: 0 0 0 3px var(--focus-ring);
    border-color: var(--primary);
}

.scan-privacy {
    display: flex; align-items: flex-start; gap: 0.5rem;
    margin: 1.2rem 0 0;
    font-size: 0.78rem; color: var(--text-muted);
}
.scan-privacy svg { width: 15px; height: 15px; flex-shrink: 0;
    margin-top: 1px; color: var(--text-muted); }
.scan-actions { margin-top: 1.4rem; }
@media (max-width: 560px) {
    .scan-modes { grid-template-columns: 1fr; }
}

/* === Command palette (Cmd/Ctrl+K — cf. static/command-palette.js) ======== */
/* Frosted overlay anchored top-center. Backdrop blur over the whole site,
   centered floating card with search + grouped results + keybinding hints.
   `html.cmdk-open` is set while open so the typing-finds autofocus in
   app.js doesn't fight for keystrokes. */
.cmdk-overlay {
    position: fixed; inset: 0;
    z-index: var(--z-modal);
    display: flex; align-items: flex-start; justify-content: center;
    padding: 12vh 1rem 4rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--duration-base) var(--ease-out);
}
.cmdk-overlay.is-open { opacity: 1; pointer-events: auto; }
.cmdk-overlay[hidden] { display: none; }
.cmdk-backdrop {
    position: absolute; inset: 0;
    background: rgba(10, 17, 25, 0.32);
    backdrop-filter: blur(6px) saturate(120%);
    -webkit-backdrop-filter: blur(6px) saturate(120%);
}
[data-theme="dark"] .cmdk-backdrop { background: rgba(0, 0, 0, 0.5); }
.cmdk-panel {
    position: relative;
    width: min(640px, 100%);
    max-height: calc(100vh - 18vh);
    display: flex; flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg), 0 0 0 1px rgba(10, 37, 64, 0.04);
    overflow: hidden;
    transform: translateY(-10px) scale(0.985);
    transition: transform var(--duration-base) var(--ease-out);
}
.cmdk-overlay.is-open .cmdk-panel { transform: translateY(0) scale(1); }

.cmdk-search {
    display: flex; align-items: center; gap: var(--space-3);
    padding: 0.85rem var(--space-4);
    border-bottom: 1px solid var(--border-light);
    background: var(--surface);
}
.cmdk-search > svg {
    width: 20px; height: 20px; flex-shrink: 0;
    color: var(--text-muted);
}
.cmdk-search input {
    flex: 1; min-width: 0;
    border: 0; outline: 0;
    background: transparent;
    font-family: var(--font-sans);
    font-size: 1.05rem;
    color: var(--text);
    padding: 0.15rem 0;
}
.cmdk-search input::placeholder { color: var(--text-light); }
.cmdk-search input::-webkit-search-cancel-button,
.cmdk-search input::-webkit-search-decoration { display: none; }

.cmdk-esc {
    display: inline-block;
    padding: 0.18rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-muted);
    font-family: var(--font-sans);
    font-size: 0.7rem;
    font-weight: 500;
    letter-spacing: 0.02em;
    flex-shrink: 0;
}

.cmdk-results {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-2) 0 var(--space-3);
    position: relative;
}
.cmdk-results.is-loading::after {
    content: "";
    display: block;
    width: 16px; height: 16px;
    margin: var(--space-3) auto 0;
    border: 2px solid var(--border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: cmdk-spin 0.7s linear infinite;
}
@keyframes cmdk-spin { to { transform: rotate(360deg); } }

.cmdk-empty {
    padding: var(--space-5) var(--space-4);
    text-align: center;
    color: var(--text-light);
    font-size: 0.85rem;
}
.cmdk-empty-err { color: var(--danger); }

.cmdk-group + .cmdk-group { margin-top: var(--space-2); }
.cmdk-group-label {
    padding: var(--space-2) var(--space-4) 0.15rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-size: 0.65rem;
    font-weight: 600;
    color: var(--accent);
}

.cmdk-item {
    width: 100%;
    display: grid;
    grid-template-columns: 1.4rem 1fr auto;
    gap: var(--space-3);
    align-items: center;
    padding: 0.55rem var(--space-4);
    background: transparent;
    border: 0;
    text-align: left;
    cursor: pointer;
    color: var(--text);
    font-family: inherit;
    font-size: 0.875rem;
    transition: background var(--duration-fast) var(--ease-out);
}
.cmdk-item:focus { outline: 0; }
.cmdk-item.is-active { background: var(--accent-light); }

.cmdk-item-icon {
    display: flex; align-items: center; justify-content: center;
    color: var(--text-muted);
    transition: color var(--duration-fast) var(--ease-out);
}
.cmdk-item-icon svg { width: 16px; height: 16px; }
.cmdk-item.is-active .cmdk-item-icon { color: var(--accent); }

.cmdk-item-body {
    display: flex; flex-direction: column; gap: 1px;
    min-width: 0;
}
.cmdk-item-title {
    font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmdk-item-sub {
    font-size: 0.75rem;
    color: var(--text-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cmdk-item-kind {
    font-size: 0.7rem;
    color: var(--text-light);
    text-transform: lowercase;
    font-variant: small-caps;
    flex-shrink: 0;
}

.cmdk-foot {
    display: flex; gap: var(--space-4);
    padding: 0.45rem var(--space-4);
    border-top: 1px solid var(--border-light);
    background: var(--surface-2);
    font-size: 0.72rem;
    color: var(--text-muted);
}
.cmdk-foot span { display: inline-flex; align-items: center; gap: 0.25rem; }
.cmdk-foot kbd {
    display: inline-block;
    padding: 0.05rem 0.35rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-muted);
    font-size: 0.7rem;
    font-family: var(--font-sans);
}

/* Lock the underlying page scroll while the palette is open. */
html.cmdk-open, html.cmdk-open body { overflow: hidden; }

@media (max-width: 560px) {
    .cmdk-overlay { padding: 6vh 0.5rem 1rem; }
    .cmdk-foot { font-size: 0.68rem; gap: var(--space-3); }
    .cmdk-item-kind { display: none; }
}
