/* Pokeloutre Grading — UI locale */

:root {
    --bg:#0f1115;
    --surface:#181b22;
    --surface-2:#20242c;
    --border:#2a2f39;
    /* Bordure spécifique boutons + selecteurs:plus claire que --border
       pour augmenter le contraste sur les surfaces sombres. À utiliser
       UNIQUEMENT sur les contrôles interactifs (boutons, inputs, selects).
       Ne pas remplacer --border global avec ça : sinon tous les traits
       fins (séparateurs cards, table rows, etc.) deviennent visibles. */
    --btn-border:#3d4452;
    --text:#e7eaef;
    --text-muted:#8a93a6;
    --primary:#ffcb05;          /* jaune Pokémon */
    --on-primary:#1d2430;       /* texte foncé lisible sur fond --primary */
    --ok:#3ccf6a;
    --warn:#ffa83a;
    --bad:#ff5d5d;
    --success:var(--ok);        /* alias sémantique — déjà référencé (bots, design-system) */
    --danger:var(--bad);        /* alias sémantique — déjà référencé (bots, design-system) */
    --info:#60a5fa;             /* bleu info (badges froids, catégories) */

    /* BL-045 (design system) — couleurs SÉMANTIQUES de statut métier.
       À réutiliser au lieu de hex épars (var(--ok), var(--warn)…) pour que tous
       les statuts parlent le même langage visuel dans toute l'app. */
    --planche:#93c5fd;          /* code planche / repère physique (bleu) */

    /* BL-046 — accents secondaires (identité visuelle du stock scellé) */
    --accent:#9b5fe6;           /* violet — cases / sélection scellé */
    --accent-2:#09b1ba;         /* cyan  — marché / valeur scellé */

    /* Couleurs sémantiques pour badges transporteurs (bot Expéditions).
       Chaque carrier a sa teinte fonctionnelle pour identification rapide. */
    --carrier-relay:#2563eb;        /* bleu — Mondial Relay (point relais) */
    --carrier-home:#374151;         /* gris fonctionnel — Domicile */
    --carrier-home-text:#cbd5e1;    /* texte clair sur badge home */

    /* Neutres absolus (overlays, ombres, texte sur fond coloré) */
    --white:#ffffff;
    --black:#000000;

    /* BL-045 — échelle typographique (à adopter progressivement). */
    --text-xs:11px;
    --text-sm:12px;
    --text-base:13.5px;
    --text-lg:15px;
    --text-md:16px;          /* intermédiaire lg/xl (drawer titres) */
    --text-xl:18px;
    --text-2xl:24px;

    --radius-xs:4px;            /* petits chips / badges */
    --radius-md:6px;            /* boutons, pilules small */
    --radius-sm:8px;            /* contrôles, cellules */
    --radius:12px;              /* cartes, blocs */
    --radius-pill:999px;        /* pilules / chips arrondis */
    --radius-full:50%;          /* cercles / pastilles */
    --shadow:0 4px 20px rgba(0, 0, 0, 0.35);

    /* Géométrie unifiée des contrôles interactifs.
       Toute la rangée boutons + inputs + selects partage cette hauteur
       pour un alignement vertical parfait. */
    --btn-height:36px;
    --btn-padding-x:var(--space-12);
    --btn-font-size:var(--text-sm);
    --btn-font-weight:600;

    /* ── Échelle d'espacement (BL-046) — 8 paliers, pour padding/margin/gap.
       Le nom = la valeur en px. Toutes les marges/paddings/gaps de l'app
       sont snappés sur ces paliers. */
    --space-2:2px;              /* micro-padding (badges pilule, ajustements) */
    --space-4:4px;
    --space-6:6px;
    --space-8:8px;
    --space-10:10px;
    --space-12:12px;
    --space-16:16px;
    --space-20:20px;
    --space-24:24px;
    --space-32:32px;

    /* ── Échelle z-index — empilement global tokenisé.
       Toute nouvelle couche DOIT prendre un de ces paliers plutôt
       qu'un nombre magique : dropdowns < topbar < drawers < modales
       < toasts < hovercards. Les barres flottantes de page (700-800)
       restent volontairement sous la topbar. */
    --z-dropdown:200;
    --z-topbar:900;
    --z-drawer:1000;
    --z-modal:1100;
    --z-toast:1200;
    --z-hovercard:1300;

    /* ── Layout global ─────────────────────────────────────────────
       Largeur + rythme du contenu, pilotés d'ici. */
    --content-max:none;             /* largeur max du contenu (none = pleine largeur, aligné au header) */
    --page-pad-x:var(--space-32);   /* marge latérale (identique au header) */
    --page-pad-y:var(--space-24);   /* marge haut/bas du contenu */
    /* Écart vertical uniforme entre sections. 12px -> 20px (07/2026,
       retour Adrien « ça paraît serré ») : l'app respire, une seule
       valeur pilote tout le rythme vertical. */
    --section-gap:var(--space-20);

    font-synthesis:none;
}

* {
    box-sizing:border-box;
}

html, body {
    margin:0;
    padding:0;
    background:var(--bg);
    color:var(--text);
    font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    -webkit-font-smoothing:antialiased;
}

.topbar {
    display:flex;
    align-items:center;
    justify-content:space-between;
    padding:var(--space-16) var(--space-32);
    border-bottom:1px solid var(--border);
    background:var(--surface);
    gap:var(--space-16);
    flex-wrap:wrap;
    /* Sticky:reste collée en haut quel que soit le scroll. --z-topbar
       passe sous drawers/modales (--z-drawer et +) mais au-dessus de
       tout le reste (barres flottantes 700-800, --z-dropdown). */
    position: sticky;
    top:0;
    z-index:var(--z-topbar);
    /* Subtle backdrop blur si le navigateur supporte — ajoute une touche
       moderne sans casser la lisibilité quand du contenu défile dessous.
       Pour les browsers sans support, le background opaque suffit. */
    backdrop-filter:blur(6px);
    -webkit-backdrop-filter:blur(6px);
    /* Léger box-shadow pour matérialiser la séparation quand on scrolle.
       Garde le border-bottom pour les browsers sans support box-shadow. */
    box-shadow:0 1px 0 var(--border);
}

.topbar-brand {
    display:flex;
    align-items:center;
    gap:var(--space-12);
    min-width:0;
}

.logo {
    font-weight:700;
    font-size:var(--text-base);
    letter-spacing:0.2px;
    white-space:nowrap;
    overflow:hidden;
    text-overflow:ellipsis;
}

/* Tag vert : dernier push déployé (auteur · date · heure) */
.topbar-push-tag {
    display:inline-flex;
    align-items:center;
    gap:var(--space-4);
    font-size:var(--text-xs);
    font-weight:600;
    line-height:1;
    color:var(--ok);
    background:color-mix(in srgb, var(--ok) 16%, transparent);
    border:1px solid color-mix(in srgb, var(--ok) 40%, transparent);
    padding:var(--space-2) var(--space-8);
    border-radius:var(--radius-pill);
    white-space:nowrap;
}

.topbar-nav {
    display:flex;
    gap:var(--space-6);
    align-items:center;
    flex-wrap:wrap;
}

.nav-link {
    color:var(--text-muted);
    text-decoration:none;
    font-size:var(--text-base);
    padding:var(--space-6) var(--space-16);
    border-radius:var(--radius-sm);
    transition:color 120ms ease, background 120ms ease;
    white-space:nowrap;
}

.nav-link:hover {
    color:var(--text);
    background:var(--surface-2);
}

.nav-link.is-active {
    color:var(--black);
    background:var(--primary);
    font-weight:600;
}

/* ---- Dropdown "Bots" dans la topbar (DEBT-016) ---- */
.nav-dropdown {
    position:relative;
    display:inline-block;
}

.nav-dropdown-trigger {
    background:transparent;
    border:none;
    cursor:pointer;
    font-family:inherit;
}

.nav-dropdown-menu {
    position:absolute;
    /* Pas de gap top → le menu touche le bas du trigger. Sinon la souris
       traverse une zone vide en passant trigger → menu et:hover du parent
       est perdu → menu se ferme. Le visuel garde l'espacement grâce au
       padding-top interne (8px). */
    top: 100%;
    left:0;
    min-width:180px;
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius-sm);
    box-shadow:0 8px 24px color-mix(in srgb, var(--black) 40%, transparent);
    padding:var(--space-8) var(--space-4) var(--space-4);
    display:none;
    z-index:var(--z-dropdown);
}

/* Pont invisible au-dessus du menu : élargit la zone hover de 6px pour
   absorber un léger décalage souris au passage trigger → menu sans laisser
   un trou qui ferme le menu. */
.nav-dropdown-menu::before {
    content:"";
    position:absolute;
    top:-6px;
    left:0;
    right:0;
    height:6px;
    /* Couleur transparente:invisible mais capte le hover. */
    background: transparent;
}

/* Ouverture : hover (desktop), focus clavier, ou data-open=true (clic). */
.nav-dropdown:hover .nav-dropdown-menu,
.nav-dropdown:focus-within .nav-dropdown-menu,
.nav-dropdown[data-open="true"] .nav-dropdown-menu {
    display:block;
}

.nav-dropdown-item {
    display:block;
    color:var(--text-muted);
    text-decoration:none;
    font-size:var(--text-base);
    padding:var(--space-8) var(--space-12);
    border-radius:var(--radius-sm);
    white-space:nowrap;
}

.nav-dropdown-item:hover {
    color:var(--text);
    background:var(--surface-2);
}

.nav-dropdown-item.is-active {
    color:var(--black);
    background:var(--primary);
    font-weight:600;
}

/* En mode mobile (topbar repliée) : le menu déroulant s'affiche inline */
@media (max-width: 760px) {
    .nav-dropdown-menu {
        position:static;
        display:block;
        box-shadow:none;
        border:none;
        padding-left:var(--space-16);
        min-width:0;
    }
}

/* Logout : un petit bouton de fin de topbar */
.topbar-logout-form {
    display:inline;
    margin-left:var(--space-8);
}

.nav-link-logout {
    background:transparent;
    border:1px solid var(--border);
    cursor:pointer;
    font-family:inherit;
    font-size:var(--text-base);
    padding:var(--space-6) var(--space-12);
}

.nav-link-logout:hover {
    color:var(--bad);
    border-color:var(--bad);
    background:color-mix(in srgb, var(--bad) 6%, transparent);
}

/* Burger (visible uniquement en mobile) */
.topbar-burger {
    display:none;
    flex-direction:column;
    justify-content:center;
    align-items:center;
    gap:var(--space-4);
    width:36px;
    height:36px;
    padding:0;
    background:transparent;
    border:1px solid var(--border);
    border-radius:var(--radius-sm);
    cursor:pointer;
}

.topbar-burger span {
    display:block;
    width:18px;
    height:2px;
    background:var(--text);
    border-radius:var(--radius-xs);
    transition:transform 150ms ease, opacity 150ms ease;
}

.topbar.is-open .topbar-burger span:nth-child(1) {
    transform:translateY(6px) rotate(45deg);
}
.topbar.is-open .topbar-burger span:nth-child(2) {
    opacity:0;
}
.topbar.is-open .topbar-burger span:nth-child(3) {
    transform:translateY(-6px) rotate(-45deg);
}

/* ---------- Responsive < 760px : nav repliable -------------------------- */
@media (max-width: 760px) {
    .topbar {
        padding:var(--space-12) var(--space-16);
        flex-wrap:nowrap;
        justify-content:space-between;
    }
    .topbar-brand {
        flex:1 1 auto;
        justify-content:space-between;
    }
    .topbar-burger {
        display:flex;
    }
    .topbar-nav {
        display:none;
        order:3;
        flex-basis:100%;
        flex-direction:column;
        align-items:stretch;
        gap:var(--space-4);
        margin-top:var(--space-12);
        padding-top:var(--space-12);
        border-top:1px solid var(--border);
    }
    .topbar.is-open {
        flex-wrap:wrap;
    }
    .topbar.is-open .topbar-nav {
        display:flex;
    }
    .topbar-nav .nav-link {
        padding:var(--space-10) var(--space-12);
        font-size:var(--text-lg);
        border-radius:var(--radius-sm);
    }
    .topbar-logout-form {
        margin-left:0;
        margin-top:var(--space-4);
    }
    .topbar-logout-form .nav-link-logout {
        width:100%;
        text-align:left;
        padding:var(--space-10) var(--space-12);
    }
    .logo {
        font-size:var(--text-lg);
    }
}

.muted {
    color:var(--text-muted);
}

.small {
    font-size:var(--text-sm);
}

.container {
    max-width:var(--content-max);
    margin-inline:auto;            /* centre les conteneurs cappés (ex. settings 980px) */
    padding:var(--page-pad-y) var(--page-pad-x);
    /* Rythme vertical uniforme:un seul gap entre toutes les sections de
       premier niveau, plutôt que des marges éparses. */
    display: flex;
    flex-direction:column;
    gap:var(--section-gap);
}
/* Les marges verticales propres aux sections de 1er niveau sont neutralisées :
   c'est le gap du conteneur qui gère l'espacement. !important pour gagner
   sur les marges des CSS de page (chargées après) — sinon double espacement. */
.container > * { margin-top:0 !important; margin-bottom:0 !important; }
/* Les overlays (drawers/modales) en position fixe/absolue sortent du flux :
   pas de gap fantôme. */
.container > [class*="drawer"],
.container > [class*="modal"],
.container > aside { margin:0; }

/* ============================================================
   GRILLE DE PAGE CANONIQUE (2e passe DS — Lot A)
   Toute page suit le même squelette :
     <main class="container">                    ← style.css (layout global)
       <section class="page-header">
         <div>
           <h1>🗂 Titre</h1>
           <p class="muted small">Sous-titre.</p>
         </div>
         <div class="header-actions">…boutons…</div>
       </section>
       <section class="panel">…</section>        ← blocs surface
   ============================================================ */

/* En-tête de page : titre 24px/700 + sous-titre muted + actions à droite. */
.page-header {
    display:flex;
    justify-content:space-between;
    align-items:flex-end;
    gap:var(--space-24);
    margin:var(--section-gap) 0;
    /* Respiration supplémentaire sous le titre de page (les margins des
       enfants du .container sont neutralisées, le padding passe). */
    padding-bottom:var(--space-4);
    flex-wrap:wrap;
}

.page-header h1 {
    margin:0 0 var(--space-6) 0;
    font-size:var(--text-2xl);
    font-weight:700;
}

/* Bloc d'actions de l'en-tête. CANONIQUE : .header-actions.
   .page-header-actions reste un alias temporaire (les 4 défs locales —
   listings/inventory_sealed/bot_match_psa/wallet — ont été fusionnées ici). */
.header-actions,
.page-header-actions {
    display:flex;
    gap:var(--space-10);
    align-items:center;
    flex-wrap:wrap;
}

/* En-tête de section INTERNE (h2 + sous-titre à gauche, compteur/
   actions à droite, à l'intérieur d'une section — le rythme
   --section-gap ne s'y applique pas). Ce wrapper n'était défini nulle
   part -> marge 0, le contenu collait à la toolbar suivante (retour
   Adrien 07/2026). NB : .listing-projects-head a son propre layout en
   bloc (components.css), ne pas l'inclure ici. */
.listing-section-head {
    display:flex;
    justify-content:space-between;
    align-items:flex-end;
    gap:var(--space-16);
    flex-wrap:wrap;
    margin-bottom:var(--space-16);
}

/* ============================================================
   PANEL CANONIQUE (.panel) — bloc surface générique.
   Promotion du .card-block historique (projects.css) : surface,
   bordure fine, rayon 12, padding 16/24. `.card-block` reste un
   ALIAS le temps de finir la migration (listing_project /
   envoi_psa — markup consommé par des JS fragiles).
   Markup :
     <section class="panel">
       <h3 class="panel-title">Titre de section</h3>
       …
     </section>
   Variante .panel-flush : padding 0 (wrapper de tableau bord à bord).
   ============================================================ */
.panel,
.card-block {
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius);
    padding:var(--space-16) var(--space-24);
    margin-bottom:var(--section-gap);
}
.panel-flush { padding:0; overflow:hidden; }
/* [hidden] doit gagner même si une CSS de page pose un display explicite. */
.panel[hidden],
.card-block[hidden] { display:none !important; }

.panel-title,
.panel h3,
.card-block h3 {
    margin:0 0 var(--space-16) 0;
    font-size:var(--text-base);
    font-weight:700;
    text-transform:uppercase;
    letter-spacing:0.5px;
    color:var(--text-muted);
}

.upload-section {
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius);
    padding:var(--space-24);
    box-shadow:var(--shadow);
}

.form {
    display:grid;
    gap:var(--space-16);
}

.dropzone {
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:var(--space-12);
    border:2px dashed var(--border);
    border-radius:var(--radius);
    padding:var(--space-32) var(--space-24);
    cursor:pointer;
    transition:border-color 120ms ease, background 120ms ease;
    text-align:center;
    background:var(--surface-2);
}

.dropzone:hover,
.dropzone.is-dragover {
    border-color:var(--primary);
    background:color-mix(in srgb, var(--primary) 5%, transparent);
}

.dropzone-icon {
    font-size:var(--text-2xl);
}

.dropzone-text strong {
    display:block;
    font-size:var(--text-lg);
    margin-bottom:var(--space-4);
}

.dropzone-text small {
    color:var(--text-muted);
}

.dropzone-file {
    font-size:var(--text-base);
    color:var(--primary);
    font-weight:600;
    margin-top:var(--space-6);
    word-break:break-all;
}

.controls {
    display:flex;
    flex-wrap:wrap;
    gap:var(--space-16);
}

.field {
    display:flex;
    flex-direction:column;
    gap:var(--space-6);
    font-size:var(--text-base);
    color:var(--text-muted);
}

.field select {
    background:var(--surface-2);
    color:var(--text);
    border:1px solid var(--border);
    border-radius:var(--radius-sm);
    padding:var(--space-8) var(--space-12);
    font-size:var(--text-base);
    min-width:120px;
}

.field.checkbox {
    flex-direction:row;
    align-items:center;
    gap:var(--space-8);
    color:var(--text);
    font-size:var(--text-base);
}

.field.checkbox input {
    accent-color:var(--primary);
    width:16px;
    height:16px;
}

/* ---------- Boutons unifiés (primary + secondary) ---------- */
/* Yellow var(--primary) = référence visuelle. Les boutons partagent la même
   géométrie (padding, taille, radius) pour que la rangée soit cohérente.
   .btn-primary : aplat jaune (CTA fort, ex: "Terminer le projet").
   .btn-secondary : transparent + bordure subtile, hover en jaune accent
   (style Stripe/Linear, cohérent avec le PDF). Marche sur <button> ET <a>. */

.btn-primary,
.btn-secondary,
.btn-danger {
    display:inline-flex;
    align-items:center;
    justify-content:center;
    gap:var(--space-6);
    border-radius:var(--radius-sm);
    height:var(--btn-height);
    padding:0 var(--btn-padding-x);
    font-size:var(--btn-font-size);
    font-weight:var(--btn-font-weight);
    line-height:1;
    cursor:pointer;
    text-decoration:none;
    font-family:inherit;
    box-sizing:border-box;
    transition:filter 120ms ease, background 120ms ease,
                border-color 120ms ease, color 120ms ease, transform 120ms ease;
}

.btn-primary {
    background:var(--primary);
    color:var(--black);
    border:1px solid var(--primary);
    width:fit-content;
}

.btn-primary:hover:not(:disabled) {
    filter:brightness(1.08);
}

.btn-primary:active:not(:disabled) {
    transform:translateY(1px);
}

.btn-primary:disabled,
.btn-secondary:disabled,
.btn-danger:disabled {
    opacity:0.4;
    cursor:not-allowed;
}

.btn-secondary {
    background:transparent;
    color:var(--text);
    border:1px solid var(--btn-border);
}

.btn-secondary:hover:not(:disabled) {
    border-color:var(--primary);
    color:var(--primary);
}

.btn-secondary:active:not(:disabled) {
    transform:translateY(1px);
}

/* BL-045 (design system) — teintes sémantiques pour boutons secondaires.
   Vocabulaire partagé (accent/warn/danger/success) au lieu de styles inline
   bouton par bouton. S'appuie sur les tokens globaux. */
.btn-secondary.tint-accent  { border-color:color-mix(in srgb, var(--primary) 45%, transparent); color:var(--primary); }
.btn-secondary.tint-warn    { border-color:color-mix(in srgb, var(--warn) 45%, transparent);    color:var(--warn); }
.btn-secondary.tint-danger  { border-color:color-mix(in srgb, var(--bad) 50%, transparent);     color:var(--bad); }
.btn-secondary.tint-success { border-color:color-mix(in srgb, var(--ok) 45%, transparent);      color:var(--ok); }
.btn-secondary.tint-accent:hover:not(:disabled) {
    background:color-mix(in srgb, var(--primary) 12%, transparent);
    border-color:var(--primary); color:var(--primary);
}
.btn-secondary.tint-warn:hover:not(:disabled) {
    background:color-mix(in srgb, var(--warn) 12%, transparent);
    border-color:var(--warn); color:var(--warn);
}
.btn-secondary.tint-danger:hover:not(:disabled) {
    background:color-mix(in srgb, var(--bad) 12%, transparent);
    border-color:var(--bad); color:var(--bad);
}
.btn-secondary.tint-success:hover:not(:disabled) {
    background:color-mix(in srgb, var(--ok) 12%, transparent);
    border-color:var(--ok); color:var(--ok);
}

.btn-danger {
    background:transparent;
    color:var(--bad);
    border:1px solid var(--bad);
}

.btn-danger:hover:not(:disabled) {
    background:color-mix(in srgb, var(--bad) 10%, transparent);
}

.btn-danger:active:not(:disabled) {
    transform:translateY(1px);
}

/* Variante compacte — bouton dense pour rangées de cartes / cellules de
   tableau (ex: « Détail » eBay/Vinted/GCC). S'ajoute à une base
   .btn-primary / .btn-secondary / .btn-danger :
     <button class="btn-secondary btn-compact">Détail</button>
   Ne réduit QUE la géométrie (hauteur 26px, padding réduit, typo --text-xs,
   radius-xs) ; les couleurs restent celles de la base ou du différentiel
   de page (ex: hover cyan --accent-2 des boutons marché). */
.btn-compact {
    height:26px;
    padding:0 var(--space-8);
    font-size:var(--text-xs);
    border-radius:var(--radius-xs);
}

/* L'attribut [hidden] doit gagner sur le display:inline-flex des boutons
   unifiés (sinon un bouton masqué via `hidden` resterait visible dès que
   son conteneur est affiché — la règle UA [hidden]{display:none} perd
   contre toute déclaration display auteur). */
.btn-primary[hidden],
.btn-secondary[hidden],
.btn-danger[hidden],
.btn-icon[hidden] { display:none; }

/* Bouton icône carré (utilisé par rotation reset, etc.) */
.btn-icon {
    display:inline-flex;
    align-items:center;
    justify-content:center;
    background:var(--surface-2);
    color:var(--text);
    border:1px solid var(--btn-border);
    border-radius:var(--radius-sm);
    width:var(--btn-height);
    height:var(--btn-height);
    padding:0;
    cursor:pointer;
    font-size:var(--text-base);
    font-family:inherit;
    transition:background 120ms ease, border-color 120ms ease, color 120ms ease;
}

.btn-icon:hover:not(:disabled) {
    background:var(--surface);
    border-color:var(--primary);
    color:var(--primary);
}

/* Inputs + selects + textareas : même hauteur que les boutons. Ne s'applique
   qu'aux éléments "normaux" (pas la dropzone ni les inputs custom). */
input[type="text"],
input[type="search"],
input[type="email"],
input[type="password"],
input[type="number"],
select {
    height:var(--btn-height);
    font-size:var(--btn-font-size);
    line-height:1.2;
    box-sizing:border-box;
}

/* Textarea : pas de hauteur fixe (multi-ligne) mais même padding/font */
textarea {
    font-size:var(--btn-font-size);
    box-sizing:border-box;
}

.status {
    margin-top:var(--space-24);
    padding:var(--space-16) var(--space-16);
    border-radius:var(--radius-sm);
    background:var(--surface);
    border:1px solid var(--border);
    font-size:var(--text-base);
}

.status.is-error {
    border-color:var(--bad);
    color:var(--bad);
}

.status.is-loading {
    border-color:var(--primary);
}

.results {
    margin-top:var(--space-32);
}

.results h2 {
    margin:0 0 var(--space-16) 0;
    font-size:var(--text-xl);
    font-weight:600;
}

.cards-grid {
    display:grid;
    grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));
    gap:var(--space-16);
}

.card-result {
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius);
    overflow:hidden;
    display:flex;
    flex-direction:column;
}

.card-result img {
    width:100%;
    height:auto;
    display:block;
    background:var(--black);
}

.card-result-body {
    padding:var(--space-16);
    display:flex;
    flex-direction:column;
    gap:var(--space-8);
}

.card-result-header {
    display:flex;
    align-items:baseline;
    justify-content:space-between;
}

.card-title {
    font-weight:600;
    font-size:var(--text-base);
}

.psa-badge {
    font-size:var(--text-sm);
    font-weight:700;
    padding:var(--space-4) var(--space-8);
    border-radius:var(--radius-pill);
    background:var(--surface-2);
    border:1px solid var(--border);
}

.psa-badge.ok    { background:color-mix(in srgb, var(--ok) 15%, transparent); color:var(--ok);   border-color:color-mix(in srgb, var(--ok) 40%, transparent); }
.psa-badge.warn  { background:color-mix(in srgb, var(--warn) 15%, transparent); color:var(--warn); border-color:color-mix(in srgb, var(--warn) 40%, transparent); }
.psa-badge.bad   { background:color-mix(in srgb, var(--bad) 15%, transparent);  color:var(--bad);  border-color:color-mix(in srgb, var(--bad) 40%, transparent); }

.metric-row {
    display:flex;
    justify-content:space-between;
    font-size:var(--text-base);
    color:var(--text-muted);
}

.metric-row strong {
    color:var(--text);
    font-weight:600;
}

.bottombar {
    text-align:center;
    padding:var(--space-16);
    border-top:1px solid var(--border);
    margin-top:var(--space-32);
}

/* ============================================================
   BL-046 (design system) — SEGMENTED CONTROL CANONIQUE
   Style « onglets soulignés » (accent jaune). À utiliser pour
   toutes les bascules de vue / onglets de page (les variantes
   historiques ont été migrées — Lot 1, 2e passe DS).
   Markup :
     <nav class="seg">
       <button class="seg-tab is-active">📦 Label <span class="seg-badge">12</span></button>
       <button class="seg-tab">🗂 Autre</button>
     </nav>
   ============================================================ */
/* Conteneur (barre d'onglets soulignée). Ne wrappe PAS : quand les
   onglets débordent (mobile, beaucoup d'onglets), la barre scrolle
   horizontalement — le soulignement reste sur une seule ligne. */
.seg {
    display:flex;
    gap:var(--space-4);
    border-bottom:1px solid var(--border);
    flex-wrap:nowrap;
    overflow-x:auto;
    margin:0 0 var(--section-gap);
}
/* Onglet */
.seg-tab {
    display:inline-flex;
    align-items:center;
    gap:var(--space-8);
    background:none;
    border:none;
    border-bottom:2px solid transparent;
    margin-bottom:-1px;
    padding:var(--space-12) var(--space-16);
    color:var(--text-muted);
    font-family:inherit;
    font-size:var(--text-base);
    font-weight:700;
    line-height:1;
    white-space:nowrap;
    cursor:pointer;
    transition:color 120ms ease, border-color 120ms ease;
}
.seg-tab:hover { color:var(--text); }
/* Onglet désactivé (fonctionnalité indisponible / chargement) */
.seg-tab:disabled,
.seg-tab[disabled] {
    opacity:0.45;
    cursor:not-allowed;
    pointer-events:none;
}
.seg-tab.is-active {
    color:var(--primary);
    border-bottom-color:var(--primary);
    background:none;
}
/* Panneau associé à un onglet — masqué via [hidden] */
.seg-pane[hidden] { display:none; }
.seg-badge {
    font-size:var(--text-xs);
    font-weight:700;
    background:color-mix(in srgb, var(--primary) 15%, transparent);
    color:var(--primary);
    border-radius:var(--radius-pill);
    padding:var(--space-4) var(--space-8);
    line-height:1.5;
}
.seg-tab:not(.is-active) .seg-badge {
    background:var(--surface-2);
    color:var(--text-muted);
}

/* ============================================================
   BL-047 — TUILE DE STAT UNIFIÉE (.stat)
   Une seule source de vérité ; les 8 variantes historiques y sont
   rattachées. Les différences par page sont conservées plus bas.
   Markup : <div class="stat"><span class="stat-label">…</span>
            <strong class="stat-value">…</strong>
            <span class="stat-sub">…</span></div>
   ============================================================ */
.stat {
    display:flex;
    flex-direction:column;
    gap:var(--space-4);
    padding:var(--space-16);
    background:var(--surface-2);
    border:1px solid var(--border);
    border-left:3px solid var(--primary);
    border-radius:var(--radius-sm);
}
.stat-label {
    font-size:var(--text-xs);
    text-transform:uppercase;
    letter-spacing:0.5px;
    color:var(--text-muted);
}
.stat-value {
    font-size:var(--text-2xl);
    font-weight:700;
    color:var(--text);
    line-height:1.1;
    font-variant-numeric:tabular-nums;
}
.stat-sub {
    font-size:var(--text-xs);
    color:var(--text-muted);
}
/* Variante dense (pages bots / studio) : padding réduit + valeur plus
   petite. S'ajoute à la base : <div class="stat stat-compact">. */
.stat-compact { padding:var(--space-12) var(--space-16); }
.stat-compact .stat-value { font-size:var(--text-xl); line-height:1.15; }
/* ============================================================
   BL-047 — CHIP unifié (.chip) : petite pastille arrondie.
   Seul alias restant : .bucket-chip (markup généré par buckets.js
   et consommé par listing_project.js / psa_project.js — fragiles,
   on ne renomme pas tant que ces JS ne sont pas restructurés).
   ============================================================ */
.chip,
.bucket-chip {
    display:inline-flex;
    align-items:center;
    gap:var(--space-4);
    padding:var(--space-4) var(--space-8);
    border-radius:var(--radius-pill);
    font-size:var(--text-xs);
    font-weight:600;
    line-height:1.25;
    white-space:nowrap;
    border:1px solid var(--text-muted);
    color:var(--text-muted);
    background:color-mix(in srgb, var(--text-muted) 12%, transparent);
    transition:background 0.12s, border-color 0.12s, transform 0.08s;
}

/* ============================================================
   Phase 2 DS — CONTRÔLES DE FORMULAIRE CANONIQUES
   .ds-search · .ds-select · label.chip (radio/checkbox-chip) · .switch
   Remplacent les ~10 variantes de recherche (.clients-search,
   .vig-search, .envoi-search…), les 4 copies de *-filter-radio
   (vinted/gcc/ebay/envoi_psa_sent) et les toggles on/off épars.
   ============================================================ */

/* ---- Champ de recherche canonique (.ds-search) ----
   Convention : l'icône 🔍 se met en tête du placeholder (pas de
   wrapper). Fluide : s'étire dans une toolbar flex et se cappe à
   460px ; override par page uniquement pour un vrai réglage local. */
.ds-search {
    flex:1 1 280px;
    width:100%;
    min-width:220px;
    max-width:460px;
    height:var(--btn-height);
    padding:0 var(--space-12);
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius-sm);
    color:var(--text);
    font-size:var(--text-base);
    font-family:inherit;
    box-sizing:border-box;
    transition:border-color 0.15s;
}
.ds-search:focus {
    outline:none;
    border-color:var(--primary);
}
.ds-search::placeholder {
    color:var(--text-muted);
    opacity:0.75;
}
/* Alias legacy — encore utilisé par listing_project.html (phase
   ultérieure). À supprimer quand cette page sera migrée. */
.clients-search {
    flex:1 1 280px;
    height:var(--btn-height);
    padding:0 var(--space-12);
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius-sm);
    color:var(--text);
    font-size:var(--text-base);
}
.clients-search:focus { outline:none; border-color:var(--primary); }

/* ---- Select / contrôle de filtre canonique (.ds-select) ----
   Même géométrie que .ds-search mais dimensionné à son contenu.
   Marche sur <select>, <input list=…> (combobox) et <button>
   déclencheur de menu. */
.ds-select {
    height:var(--btn-height);
    padding:0 var(--space-10);
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius-sm);
    color:var(--text);
    font-size:var(--text-base);
    font-family:inherit;
    cursor:pointer;
    box-sizing:border-box;
    transition:border-color 0.15s;
}
.ds-select:focus {
    outline:none;
    border-color:var(--primary);
}

/* ---- Radio-chip / checkbox-chip : le .chip devient un contrôle ----
   Markup :
     <label class="chip"><input type="radio" name="…" checked> Libellé</label>
     <label class="chip"><input type="checkbox"> Option</label>
   L'input est masqué visuellement mais reste focusable (a11y
   clavier) ; l'état :checked allume la pilule en jaune primaire. */
label.chip {
    position:relative;
    cursor:pointer;
    user-select:none;
    /* Un poil plus grand que le chip statique : c'est un contrôle. */
    padding:var(--space-6) var(--space-12);
    font-size:var(--text-sm);
}
label.chip input[type="radio"],
label.chip input[type="checkbox"] {
    position:absolute;
    opacity:0;
    width:1px;
    height:1px;
    margin:0;
    pointer-events:none;
}
label.chip:hover {
    border-color:var(--text);
    color:var(--text);
}
label.chip:has(input:checked) {
    background:color-mix(in srgb, var(--primary) 15%, transparent);
    border-color:color-mix(in srgb, var(--primary) 55%, transparent);
    color:var(--primary);
}
label.chip:has(input:focus-visible) {
    outline:2px solid var(--primary);
    outline-offset:2px;
}

/* ---- Interrupteur on/off canonique (.switch) ----
   Markup :
     <label class="switch">
       <input type="checkbox">
       <span class="switch-slider" aria-hidden="true"></span>
       <span>Libellé</span>
     </label> */
.switch {
    position:relative;
    display:inline-flex;
    align-items:center;
    gap:var(--space-8);
    cursor:pointer;
    user-select:none;
    font-size:var(--text-sm);
    color:var(--text);
}
.switch input {
    position:absolute;
    opacity:0;
    width:1px;
    height:1px;
    margin:0;
    pointer-events:none;
}
.switch-slider {
    flex-shrink:0;
    width:38px;
    height:22px;             /* ≈ --btn-height × 0.6 */
    box-sizing:border-box;
    border-radius:var(--radius-pill);
    background:var(--surface-2);
    border:1px solid var(--btn-border);
    position:relative;
    transition:background 0.15s, border-color 0.15s;
}
.switch-slider::before {
    content:"";
    position:absolute;
    top:2px;
    left:2px;
    width:16px;
    height:16px;
    border-radius:var(--radius-full);
    background:var(--text-muted);
    transition:transform 0.15s ease, background 0.15s;
}
.switch:hover .switch-slider { border-color:var(--text-muted); }
.switch input:checked ~ .switch-slider {
    background:color-mix(in srgb, var(--primary) 25%, transparent);
    border-color:var(--primary);
}
.switch input:checked ~ .switch-slider::before {
    transform:translateX(16px);
    background:var(--primary);
}
.switch input:focus-visible ~ .switch-slider {
    outline:2px solid var(--primary);
    outline-offset:2px;
}

/* ============================================================
   BL-047 — TOOLBAR unifiée (.toolbar) : barre de filtres/actions.
   Sous-éléments canoniques :
     .toolbar-filters — groupe de filtres (flex, wrap)
     .toolbar-actions — bloc d'actions poussé à droite
   ============================================================ */
.toolbar {
    display:flex;
    flex-wrap:wrap;
    align-items:center;
    gap:var(--space-12);
    margin:0 0 var(--section-gap);
}
.toolbar-filters {
    display:flex;
    flex-wrap:wrap;
    align-items:center;
    gap:var(--space-8);
}
.toolbar-actions {
    margin-left:auto;
    display:flex;
    align-items:center;
    gap:var(--space-8);
}

/* ============================================================
   BL-047 — DRAWER unifié (.drawer) : panneau latéral droite.
   Composant CANONIQUE piloté par drawer.js (module partagé) :
     - [hidden]  = présence dans le rendu (display:none)
     - .is-open  = état visuel animé (le JS retire [hidden], force un
       reflow puis pose .is-open → la transition joue à l'ouverture
       ET à la fermeture, avant de reposer [hidden] sur transitionend).
   Largeur pilotée par --drawer-w (override inline ou par variante).
   Markup canonique :
     <aside class="drawer" id="mon-drawer" hidden aria-hidden="true">
       <div class="drawer-overlay" data-drawer-close></div>
       <div class="drawer-panel" role="dialog" aria-modal="true">
         <header class="drawer-head">
           <h2 class="drawer-title">Titre</h2>
           <button class="drawer-close" data-drawer-close aria-label="Fermer">✕</button>
         </header>
         <div class="drawer-body">…</div>
         <footer class="drawer-foot">…</footer>
       </div>
     </aside>
   Ouverture déclarative : <button data-drawer-open="#mon-drawer">.
   Les ALIAS legacy (.envoi-edit-drawer, .ipsa-drawer,
   .card-detail-drawer, .psa-cust-drawer) restent branchés sur
   :not([hidden]) le temps que leurs pages migrent vers drawer.js.
   ============================================================ */
.drawer,
.envoi-edit-drawer, .ipsa-drawer, .card-detail-drawer,
.psa-cust-drawer {
    position:fixed;
    inset:0;
    z-index:var(--z-drawer);
    pointer-events:none;          /* seuls overlay + panel captent les clics */
}
.drawer[hidden],
.envoi-edit-drawer[hidden], .ipsa-drawer[hidden],
.card-detail-drawer[hidden], .psa-cust-drawer[hidden] { display:none !important; }

/* Voile */
.drawer-overlay, .drawer-backdrop,
.envoi-edit-drawer-overlay, .ipsa-drawer-overlay,
.card-detail-backdrop, .psa-cust-overlay {
    position:absolute;
    inset:0;
    background:color-mix(in srgb, var(--black) 55%, transparent);
    backdrop-filter:blur(2px);
    opacity:0;
    transition:opacity 200ms ease;
    pointer-events:auto;
}
.drawer.is-open .drawer-overlay,
.drawer.is-open .drawer-backdrop,
.envoi-edit-drawer:not([hidden]) .envoi-edit-drawer-overlay,
.ipsa-drawer:not([hidden]) .ipsa-drawer-overlay,
.card-detail-drawer:not([hidden]) .card-detail-backdrop,
.psa-cust-drawer:not([hidden]) .psa-cust-overlay { opacity:1; }

/* Panneau */
.drawer-panel,
.envoi-edit-drawer-panel, .ipsa-drawer-panel,
.card-detail-panel, .psa-cust-panel {
    position:absolute;
    top:0; right:0; bottom:0;
    width:var(--drawer-w, min(480px, 96vw));
    background:var(--surface);
    border-left:1px solid var(--border);
    display:flex;
    flex-direction:column;
    box-shadow:-8px 0 32px color-mix(in srgb, var(--black) 50%, transparent);
    transform:translateX(100%);
    transition:transform 220ms ease-out;
    pointer-events:auto;
}
.drawer.is-open .drawer-panel,
.envoi-edit-drawer:not([hidden]) .envoi-edit-drawer-panel,
.ipsa-drawer:not([hidden]) .ipsa-drawer-panel,
.card-detail-drawer:not([hidden]) .card-detail-panel,
.psa-cust-drawer:not([hidden]) .psa-cust-panel { transform:none; }

/* Largeurs NOMMÉES — à poser sur la racine .drawer (la custom prop
   hérite jusqu'au .drawer-panel). Les panels legacy partagent les
   mêmes valeurs (plus de min() en dur par alias). */
.drawer-sm, .psa-cust-panel    { --drawer-w:min(440px, 92vw); }
.drawer-md, .card-detail-panel { --drawer-w:min(620px, 96vw); }
.drawer-lg, .ipsa-drawer-panel { --drawer-w:min(880px, 96vw); }

/* En-tête : titre + bouton fermer (+ éventuel sous-titre).
   Avec sous-titre, envelopper titre + sous-titre :
     <header class="drawer-head">
       <div class="drawer-titles">
         <h2 class="drawer-title">Titre</h2>
         <p class="drawer-subtitle">Contexte secondaire</p>
       </div>
       <button class="drawer-close" …>✕</button>
     </header> */
.drawer-head {
    display:flex;
    justify-content:space-between;
    align-items:center;
    gap:var(--space-12);
    padding:var(--space-16) var(--space-16);
    border-bottom:1px solid var(--border);
    flex-shrink:0;
}
.drawer-title {
    margin:0;
    font-size:var(--text-md);
    color:var(--text);
    flex:1;
    /* Truncate si le titre est très long */
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
}
/* Wrapper titre + sous-titre (le titre ne flexe plus lui-même) */
.drawer-titles {
    flex:1 1 auto;
    min-width:0;
}
.drawer-titles .drawer-title { flex:none; }
.drawer-subtitle {
    margin:var(--space-2) 0 0;
    font-size:var(--text-sm);
    color:var(--text-muted);
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
}

/* Corps : seule zone scrollable du panneau */
.drawer-body {
    flex:1 1 auto;
    min-height:0;
    overflow-y:auto;
    padding:var(--space-16);
    display:flex;
    flex-direction:column;
    gap:var(--space-16);
}
/* Variante sans padding : les sections internes gèrent leur propre
   rythme (ex. wallet : summary / actions / tabs bord à bord). */
.drawer-body.is-flush { padding:0; gap:0; }

/* Pied : actions alignées à droite */
.drawer-foot {
    display:flex;
    align-items:center;
    justify-content:flex-end;
    gap:var(--space-8);
    padding:var(--space-12) var(--space-16);
    border-top:1px solid var(--border);
    flex-shrink:0;
}

/* Bouton fermer */
.drawer-close,
.envoi-edit-drawer-close, .ipsa-drawer-close, .card-detail-close {
    display:inline-flex;
    align-items:center;
    justify-content:center;
    background:transparent;
    border:1px solid var(--border);
    color:var(--text-muted);
    width:32px; height:32px;
    border-radius:var(--radius-sm);
    font-size:var(--text-xl);
    line-height:1;
    cursor:pointer;
    flex-shrink:0;
}
.drawer-close:hover,
.envoi-edit-drawer-close:hover, .ipsa-drawer-close:hover, .card-detail-close:hover {
    background:color-mix(in srgb, var(--white) 5%, transparent);
    color:var(--white);
}

/* ============================================================
   MODALE CANONIQUE (.modal) — overlay centré + boîte.
   Largeur pilotée par --modal-w (override via .modal-wide ou une
   classe locale). Masquage via [hidden].
   Markup :
     <div class="modal" hidden>
       <div class="modal-overlay"></div>
       <div class="modal-content">
         <header class="modal-head"><h2>Titre</h2>
           <button class="modal-close">✕</button></header>
         <div class="modal-body">…</div>
         <footer class="modal-foot">…</footer>
       </div>
     </div>
   ============================================================ */
.modal {
    position:fixed;
    inset:0;
    /* --z-modal (>--z-drawer) : une modale ouverte depuis un drawer
       (ex. np-modal depuis le drawer Import d'un envoi PSA) passe DEVANT. */
    z-index:var(--z-modal);
    display:flex;
    align-items:center;
    justify-content:center;
    padding:var(--space-24);
}
.modal[hidden] { display:none !important; }

.modal-overlay {
    position:absolute;
    inset:0;
    background:color-mix(in srgb, var(--black) 60%, transparent);
    backdrop-filter:blur(2px);
}

.modal-content {
    position:relative;
    z-index:1;
    background:var(--surface);
    border:1px solid var(--border);
    border-radius:var(--radius);
    box-shadow:0 24px 60px color-mix(in srgb, var(--black) 55%, transparent);
    width:100%;
    max-width:var(--modal-w, min(720px, 96vw));
    max-height:92vh;
    overflow:hidden;
    display:flex;
    flex-direction:column;
}
.modal-wide { --modal-w:min(1180px, 96vw); }

.modal-head {
    display:flex;
    align-items:center;
    justify-content:space-between;
    padding:var(--space-16);
    border-bottom:1px solid var(--border);
}
.modal-head h2, .modal-head h3 {
    margin:0;
    font-size:var(--text-lg);
    color:var(--text);
}

.modal-close {
    display:inline-flex;
    align-items:center;
    justify-content:center;
    background:transparent;
    border:none;
    color:var(--text-muted);
    font-size:var(--text-xl);
    line-height:1;
    cursor:pointer;
    padding:var(--space-4) var(--space-8);
    flex-shrink:0;
}
.modal-close:hover { color:var(--text); }

.modal-body {
    padding:var(--space-16);
    overflow-y:auto;
}

.modal-foot {
    display:flex;
    justify-content:flex-end;
    gap:var(--space-10);
    padding:var(--space-12) var(--space-16);
    border-top:1px solid var(--border);
}

/* ============================================================
   TABLEAU-GRILLE CANONIQUE (.ds-table) — listing type tableur.
   Les colonnes sont pilotées par la custom prop --cols posée sur
   le conteneur (ou une classe/id local) ; head et rows la
   consomment. Groupes optionnels (.ds-table-group) pour les
   sections « POUR <client> » avec en-tête replable.
   Markup :
     <div class="ds-table" style="--cols:…">
       <div class="ds-table-head"><span>Col</span>…</div>
       <div class="ds-table-group">
         <div class="ds-table-grouphead">
           <span class="ds-table-groupname">…</span></div>
         <div class="ds-table-row"><span>…</span>…</div>
       </div>
     </div>
   ============================================================ */
.ds-table {
    width:100%;
}
.ds-table-head {
    display:grid;
    grid-template-columns:var(--cols);
    position:sticky;
    top:0;
    z-index:3;
    background:var(--surface);
    border-bottom:1px solid var(--border);
}
.ds-table-head > span {
    padding:var(--space-8) var(--space-8);
    font-size:var(--text-xs);
    text-transform:uppercase;
    letter-spacing:0.4px;
    color:var(--text-muted);
    font-weight:700;
    white-space:nowrap;
    overflow:hidden;
}
.ds-table-group.is-hidden { display:none; }
.ds-table-group.is-dragging { opacity:0.45; }
.ds-table-grouphead {
    display:flex;
    align-items:center;
    gap:var(--space-8);
    padding:var(--space-6) var(--space-8);
    background:var(--surface-2);
    border-top:1px solid var(--border);
    border-bottom:1px solid var(--border);
}
.ds-table-group:first-of-type .ds-table-grouphead { border-top:none; }
.ds-table-groupname {
    flex:1 1 auto;
    min-width:0;
    font-size:var(--text-xs);
    letter-spacing:0.4px;
    color:var(--text-muted);
    text-transform:uppercase;
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
}
.ds-table-groupname strong { color:var(--text); font-size:var(--text-base); }
.ds-table-row {
    display:grid;
    grid-template-columns:var(--cols);
    align-items:stretch;
    border-bottom:1px solid var(--border);
}
.ds-table-row:last-child { border-bottom:none; }
.ds-table-row.is-hidden { display:none; }
.ds-table-row.is-dragging { opacity:0.4; }
.ds-table-row:hover { background:color-mix(in srgb, var(--white) 2%, transparent); }

/* Cellule numérique : alignée à droite + chiffres tabulaires (les
   colonnes de montants/quantités s'empilent proprement). Marche dans
   les .ds-table (spans) ET les <table> classiques (td/th).
   :is() donne une spécificité 0,2,1 — assez pour battre les resets
   locaux type `.ma-table th { text-align:left }`, tout en restant
   surchargeable par un différentiel de page (fichier chargé après). */
:is(table, .ds-table) :is(th, td, span).is-num {
    text-align:right;
    font-variant-numeric:tabular-nums;
}

/* Densité confortable : cellules 10×12 au lieu du 8×8 par défaut.
   À poser sur le conteneur : <div class="ds-table ds-table-comfort">. */
.ds-table-comfort .ds-table-head > span,
.ds-table-comfort .ds-table-row > span {
    padding:var(--space-10) var(--space-12);
}

/* ============================================================
   ACCORDÉON CANONIQUE (.acc) — section dépliable.
   L'état se pilote via aria-expanded sur .acc-head et [hidden]
   sur .acc-body (comportement JS par page).
   Markup :
     <section class="acc">
       <button class="acc-head" aria-expanded="true">
         <span class="acc-caret">▾</span>
         <span class="acc-title">Titre</span>
         <span class="acc-count">12</span>
       </button>
       <div class="acc-body">…</div>
     </section>
   ============================================================ */
.acc {
    border:1px solid var(--border);
    border-radius:var(--radius);
    background:var(--surface);
    overflow:hidden;
}
.acc-head {
    display:flex;
    align-items:center;
    gap:var(--space-10);
    width:100%;
    padding:var(--space-12) var(--space-16);
    background:var(--surface-2);
    border:none;
    border-bottom:1px solid var(--border);
    color:var(--text);
    cursor:pointer;
    font-family:inherit;
    text-align:left;
}
.acc-head[aria-expanded="false"] { border-bottom:none; }
.acc-caret { color:var(--text-muted); font-size:var(--text-sm); width:14px; }
.acc-title { font-size:var(--text-base); font-weight:700; flex:1 1 auto; }
.acc-count {
    min-width:24px;
    text-align:center;
    font-size:var(--text-sm);
    font-weight:700;
    color:var(--primary);
    background:color-mix(in srgb, var(--primary) 14%, transparent);
    border-radius:var(--radius-pill);
    padding:var(--space-4) var(--space-10);
    font-variant-numeric:tabular-nums;
}
.acc-body[hidden] { display:none; }
.acc-empty { padding:var(--space-24) var(--space-16); text-align:center; }

/* ============================================================
   2e passe DS — Lot B : COMPOSANTS CANONIQUES
   .empty · .spinner · .progress · .btn-*.is-loading · .field-error
   Helpers JS associés dans ui_utils.js (UI.confirm, UI.btnLoading,
   UI.emptyHtml). Vitrine : /design-system.
   ============================================================ */

/* ---- État vide (.empty) — picto + message + CTA optionnel ----
   Markup (ou via UI.emptyHtml({icon, text, ctaLabel, ctaAttr})) :
     <div class="empty">
       <div class="empty-icon">🃏</div>
       <p class="empty-text">Aucune carte pour le moment.</p>
       <button class="btn-primary">+ Ajouter</button>   ← optionnel
     </div> */
.empty {
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:var(--space-12);
    text-align:center;
    padding:var(--space-32) var(--space-24);
    color:var(--text-muted);
}
/* display:flex écraserait la règle UA [hidden] (même piège que .btn-*) */
.empty[hidden] { display:none !important; }
.empty-icon {
    font-size:var(--text-2xl);
    line-height:1;
    opacity:0.6;
}
.empty-text {
    margin:0;
    font-size:var(--text-base);
}

/* ---- Spinner canonique (.spinner) ----
   Taille via --spinner-size (défaut 18px), couleur via currentColor
   (poser `color:` sur le spinner ou un parent pour teinter). */
.spinner {
    display:inline-block;
    flex-shrink:0;
    width:var(--spinner-size, 18px);
    height:var(--spinner-size, 18px);
    border:2px solid color-mix(in srgb, currentColor 25%, transparent);
    border-top-color:currentColor;
    border-radius:var(--radius-full);
    animation:ds-spin 0.8s linear infinite;
}
@keyframes ds-spin { to { transform:rotate(360deg); } }

/* ---- Barre de progression canonique (.progress) ----
   Hauteur via --progress-h (défaut 6px). Trois modes :
     - déterminé : JS pose width:% sur .progress-bar ;
     - .is-sweep : reflet balayant PAR-DESSUS la fill (activité pendant
       qu'un % réel progresse lentement) ;
     - .is-indeterminate : segment glissant, pas de % connu. */
.progress {
    position:relative;
    height:var(--progress-h, 6px);
    background:var(--surface-2);
    border-radius:var(--radius-pill);
    overflow:hidden;
}
.progress-bar {
    height:100%;
    width:0%;
    background:var(--primary);
    border-radius:var(--radius-pill);
    transition:width 280ms ease;
}
.progress.is-sweep::after {
    content:"";
    position:absolute;
    top:0;
    left:-30%;
    height:100%;
    width:30%;
    background:linear-gradient(
        90deg,
        transparent,
        color-mix(in srgb, var(--white) 22%, transparent),
        transparent
    );
    animation:ds-progress-slide 1.4s linear infinite;
}
.progress.is-indeterminate .progress-bar {
    position:absolute;
    width:30%;
    transition:none;
    animation:ds-progress-slide 1.4s ease-in-out infinite;
}
@keyframes ds-progress-slide {
    from { left:-30%; }
    to   { left:100%; }
}

/* ---- Bouton en chargement (.is-loading) ----
   S'ajoute à .btn-primary/-secondary/-danger/-icon : le texte passe
   transparent (la géométrie ne bouge pas) et un spinner centré tourne
   par-dessus. Préférer le helper UI.btnLoading(btn, on) qui gère
   aussi disabled + restauration. */
.btn-primary.is-loading,
.btn-secondary.is-loading,
.btn-danger.is-loading,
.btn-icon.is-loading {
    position:relative;
    color:transparent !important;
    pointer-events:none;
}
.btn-primary.is-loading::after,
.btn-secondary.is-loading::after,
.btn-danger.is-loading::after,
.btn-icon.is-loading::after {
    content:"";
    position:absolute;
    inset:0;
    margin:auto;
    width:14px;
    height:14px;
    border:2px solid color-mix(in srgb, var(--text) 30%, transparent);
    border-top-color:var(--text);
    border-radius:var(--radius-full);
    animation:ds-spin 0.8s linear infinite;
}
/* Le spinner suit la couleur « logique » du bouton (le texte est
   transparent, currentColor est donc inutilisable ici). */
.btn-primary.is-loading::after {
    border-color:color-mix(in srgb, var(--black) 30%, transparent);
    border-top-color:var(--black);
}
.btn-danger.is-loading::after {
    border-color:color-mix(in srgb, var(--bad) 30%, transparent);
    border-top-color:var(--bad);
}

/* ---- Erreur de champ (.field-error) + champ invalide ----
   Markup :
     <label class="field">
       <span class="field-required">Nom</span>
       <input type="text" id="x" aria-describedby="x-error">
     </label>
     <p class="field-error" id="x-error" hidden>Le nom est obligatoire.</p>
   JS : input.classList.add("is-invalid") + aria-invalid="true" +
   afficher le .field-error ; nettoyer sur `input`. */
.field-error {
    margin:var(--space-4) 0 0;
    font-size:var(--text-xs);
    color:var(--bad);
}
.field-error[hidden] { display:none; }
input.is-invalid,
select.is-invalid,
textarea.is-invalid,
input[aria-invalid="true"],
select[aria-invalid="true"],
textarea[aria-invalid="true"] {
    border-color:var(--bad) !important;
}
/* Marqueur unique de champ requis : astérisque rouge après le libellé. */
.field-required::after {
    content:" *";
    color:var(--bad);
    font-weight:700;
}
