/* ══════════════════════════════════════════════════════════════════════════
   Arven — interface commune (ERP + Visionneur de documents)

   Habillée par le design system « Subtle Gradient » : les valeurs viennent
   TOUTES de `tokens.css`, jamais d'un code hexadécimal écrit ici. Trois règles
   du système décident de presque tout ce qui suit :

   1. Fond blanc, encre #111111. Le navy #0d182a appartient au LOGO — pas aux
      boutons, pas au texte, pas à la barre latérale.
   2. La hiérarchie se fait par la taille, deux graisses (600 / 400) et le
      blanc. Jamais par la couleur : quand tout est coloré, plus rien ne l'est.
   3. Pas d'effet au survol au-delà du strict nécessaire. L'action primaire
      s'assombrit à l'appui, les liens se soulignent.

   ⚠️ AUCUNE CLASSE N'A ÉTÉ RENOMMÉE. Les noms sont un contrat avec `app.js`
   et les gabarits : `app.js` fabrique lui-même des `.toast`, des
   `.switcher-item`, des `.achat-item` et bascule `.is-syncing`,
   `.col-commande--urgent`, `.kpi-tile--zero`. Renommer casserait la page EN
   SILENCE — pas d'erreur, juste un bouton qui ne fait plus rien.

   ⚠️ FICHIER PARTAGÉ avec arven-doc-viewer. Les blocs « Espace de travail » et
   « Visionneur » ne servent pas à l'ERP mais doivent rester valides là-bas ;
   ce qui n'existe que dans l'ERP va dans `comptes.css` ou `connexion.css`.
   ══════════════════════════════════════════════════════════════════════════ */

/* ⚠️ PLUS AUCUNE FONTE WEB. La feuille commençait par un `@import` vers
   fonts.googleapis.com qui chargeait Inter, Manrope, Montserrat et JetBrains
   Mono — quatre familles, une requête bloquante vers un tiers, sur un outil
   interne. Le design system emploie délibérément les mots-clés système : la
   police est celle de l'appareil (Segoe UI Variable, SF Pro…), donc déjà
   chargée, et l'interface se met à ressembler au système qui l'affiche. */

:root { color-scheme: only light; }

/* ─────────────────────────── Socle ─────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }
html, body { height: 100%; }

body {
    font-family: var(--font-sans);
    background: var(--color-surface-soft);
    color: var(--color-text-body);
    font-size: var(--body-sm-size);
    line-height: var(--body-sm-leading);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow: hidden;
}

a { color: inherit; text-decoration: none; }

h1, h2, h3, h4, h5, h6 {
    color: var(--color-ink);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--title-lg-tracking);
}

/* ⚠️ LES INTITULÉS EN CAPITALES ONT DISPARU. `.brand-label`,
   `.nav-section-title`, les en-têtes de tableau et les libellés de tuile
   étaient en Montserrat majuscule avec 0,18 em d'approche. Le design system
   est explicite : « casse normale partout — titres, boutons, entrées de
   navigation, pastilles ; pas de Title Case, pas de CAPITALES ». Un intitulé
   se distingue par sa taille et sa couleur, pas en criant. */
.brand-label,
.nav-section-title,
.notion-table th {
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    letter-spacing: 0;
    text-transform: none;
    color: var(--color-text-muted);
}

.mono { font-family: var(--font-mono); }

::selection { background: var(--color-ink); color: var(--color-on-primary); }

/* Une seule bague de focus dans tout le système : 2px d'encre, décalée de 2px.
   Jamais colorée — le focus dit « c'est ici », pas « attention ». */
:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 2px; }

/* L'attribut `hidden` doit toujours gagner. Le navigateur lui applique bien
   `display: none`, mais n'importe quelle règle de classe posant un `display`
   (nos `.btn { display: inline-flex }`) l'emporte par spécificité — et un
   bouton « caché » réapparaissait, vide, sur la page des réglages. */
[hidden] { display: none !important; }

/* Contenu réservé aux lecteurs d'écran : l'en-tête de la colonne « masquer »
   n'a pas de libellé visible, mais la colonne doit rester nommée. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ═══════════════════════════ COQUILLE ═══════════════════════════ */
.app-layout {
    display: flex;
    height: 100vh;
    overflow: hidden;
}

/* ─────────────────────────── Barre latérale ───────────────────────────
   ⚠️ ELLE ÉTAIT NAVY, ELLE EST BLANCHE. C'est le changement le plus visible de
   toute la refonte, et il n'est pas décoratif : le design system réserve le
   navy au logo et aux surfaces qui portent la marque, et construit tout le
   reste sur un fond blanc. Une colonne sombre de 244px sur toute la hauteur,
   présente à chaque écran, aurait été la seule chose qui empêche l'outil de
   ressembler à ce design.

   Ce qu'on y gagne, en plus de la cohérence : la marque redevient visible.
   Le logo était un carré BLANC OPAQUE de 24px posé sur du navy — une tuile.
   Il est maintenant détouré, en navy, sur blanc : on voit la montagne. */
.sidebar {
    width: var(--sidebar-width);
    flex-shrink: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--color-canvas);
    color: var(--color-text-body);
    border-right: var(--border-hairline);
    z-index: 100;
}

.sidebar-logo {
    padding: 12px 12px 8px;
    flex-shrink: 0;
    position: relative;   /* ancre le menu déroulant */
    z-index: 300;         /* le menu passe au-dessus du contenu principal */
}

/* La marque fait 165 × 76 : deux pics, plus large que haute. En la forçant
   dans un carré de 24px elle ne remplissait qu'un tiers de sa boîte. */
.logo-mark {
    width: 30px;
    height: 20px;
    flex-shrink: 0;
    display: block;
    object-fit: contain;
    object-position: left center;
}

/* ─────────── Sélecteur d'outils Arven ─────────── */
.app-switcher-trigger {
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: 8px;
    border: 1px solid transparent;
    background: none;
    border-radius: var(--radius-md);
    color: inherit;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: var(--transition-press);
}
.app-switcher-trigger:hover,
.app-switcher-trigger[aria-expanded="true"] { background: var(--color-surface-soft); }
.app-switcher-trigger:focus { outline: none; }
.app-switcher-trigger:focus-visible { outline: 2px solid var(--color-ink); outline-offset: -2px; }

.switcher-labels {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;         /* autorise l'ellipse */
    line-height: 1.3;
}
.switcher-org {
    font-size: 11px;
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.switcher-tool {
    font-size: var(--title-sm-size);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--title-sm-tracking);
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.logo-chevron {
    margin-left: 2px;
    color: var(--color-text-muted-soft);
    display: inline-flex;
    flex-shrink: 0;
    transition: transform var(--duration-base) var(--ease-standard);
}
.app-switcher-trigger[aria-expanded="true"] .logo-chevron { transform: rotate(180deg); }

/* Le menu déroulant. Carte blanche, filet, rayon de carte, l'ombre douce du
   système — les deux seules ombres autorisées sont `sm` et `md`. */
.app-switcher-menu {
    position: absolute;
    top: calc(100% + 4px);
    left: 10px;
    width: 268px;
    padding: 6px;
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    display: flex;
    flex-direction: column;
    gap: 2px;
    z-index: 300;
    animation: switcher-in var(--duration-fast) var(--ease-standard);
}
.app-switcher-menu[hidden] { display: none; }

@keyframes switcher-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.switcher-menu-label {
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    color: var(--color-text-muted);
    padding: 6px 8px 4px;
}

.switcher-item {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    padding: 8px;
    border: none;              /* reset <button> */
    background: none;
    border-radius: var(--radius-md);
    color: var(--color-ink);
    font: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
    transition: var(--transition-press);
}
.switcher-item:hover { background: var(--color-surface-soft); text-decoration: none; }
.switcher-item:focus { outline: none; }
.switcher-item:focus-visible { outline: 2px solid var(--color-ink); outline-offset: -2px; }
.switcher-item--current,
.switcher-item--current:hover { background: var(--color-surface-card); cursor: default; }
.switcher-item--soon { cursor: default; opacity: 0.6; }
.switcher-item--soon:hover { background: none; }

/* L'unique surface sombre de l'ERP, et elle est minuscule : la pastille de
   l'outil. Encre, pas navy — c'est un élément d'interface, pas le logo. */
.switcher-item-icon {
    width: var(--icon-button-size);
    height: var(--icon-button-size);
    border-radius: var(--radius-md);
    background: var(--color-primary);
    color: var(--color-on-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.switcher-item--soon .switcher-item-icon {
    background: var(--color-surface-card);
    color: var(--color-text-muted);
}
.switcher-item-icon svg { width: 16px; height: 16px; }

.switcher-item-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    line-height: 1.35;
}
.switcher-item-name {
    font-size: var(--body-sm-size);
    font-weight: var(--weight-medium);
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.switcher-item-desc {
    font-size: var(--caption-size);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.switcher-item-trail {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    color: var(--color-text-muted);
}
/* Pastille « bientôt ». En casse normale : le design system n'admet les
   capitales nulle part dans le produit. */
.switcher-badge {
    font-size: 11px;
    font-weight: var(--weight-medium);
    color: var(--ink-warning);
    background: var(--tint-warning);
    padding: 3px 8px;
    border-radius: var(--radius-pill);
    white-space: nowrap;
}

/* ═══════════════ Menu d'actions (composant partagé) ═══════════════
   Le tiroir `⋮` des lignes du tableau des utilisateurs, ET le menu du pied de
   la barre latérale. Ici et non dans `comptes.css` : le pied est sur toutes les
   pages, et une feuille absente ne provoque aucune erreur — elle laisse
   simplement un menu sans style, ouvert et empilé au milieu de l'écran.

   Le placement est dans `app.js` ; tout le reste (ouvrir, fermer au clic
   extérieur, Échap, un seul menu à la fois, rendre le focus) vient de
   l'attribut `popover`. */

/* ⚠️ `.btn--icone` EST DÉFINI PLUS BAS, AVEC LES AUTRES VARIANTES DE BOUTON, et
   ce n'est pas un rangement : il DOIT venir après `.btn`. Il pose `padding: 0`
   à la même spécificité que le `padding: 0 20px` de `.btn` — à égalité, c'est
   le dernier écrit qui l'emporte. Écrit ici, il perdait : le bouton gardait ses
   40 px de rembourrage pour 32 px de large, et son icône était comprimée à
   2 px. Un rond de trois points devenait un trait.

   La règle vivait dans `comptes.css`, chargé après `global.css`, ce qui la
   faisait gagner par accident. Le déplacement du composant l'a révélé. */

/* ─────────── L'état par défaut EST le repli ───────────
   Ce qui suit décrit ce que voit un navigateur qui ne connaît pas `popover` :
   pas de menu du tout, les entrées en clair là où elles se trouvent, et le
   déclencheur `⋮` masqué. Tout reste atteignable — c'est seulement moins bien
   rangé. Le bloc `@supports` plus bas est L'AMÉLIORATION.

   Écrit dans ce sens et pas dans l'autre : si le repli était l'exception, il
   faudrait penser à le tester, donc il ne le serait jamais. Là, c'est le menu
   qui est l'exception, et il ne s'active que là où le navigateur sait le fermer
   au clic extérieur et à la touche Échap. */
.menu-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: flex-end;
    white-space: normal;
}
.menu-actions form { display: block; }
.menu-filet { display: none; }
.btn--icone[popovertarget] { display: none; }

.menu-item {
    height: var(--control-height-mini);
    padding: 0 14px;
    border: var(--border-hairline);
    border-radius: var(--radius-sm);
    background: var(--color-canvas);
    color: var(--color-ink);
    font: inherit;
    text-decoration: none;
    cursor: pointer;
    transition: var(--transition-press);
}
.menu-item--danger { color: var(--color-error); }

/* Le menu du pied : une colonne, pas une rangée — la barre latérale fait
   232 px, trois boutons côte à côte n'y tiennent pas. Et le chevron du
   déclencheur disparaît, puisqu'il n'y a rien à déplier. */
.menu-actions--pied { flex-direction: column; margin-top: 4px; }
.sidebar-footer-chevron { display: none; }
/* Sans menu, c'est l'avatar qui reprend son rôle d'origine — ouvrir le choix de
   photo. `app.js` rebranche le geste ; l'entrée du menu ferait double emploi. */
[data-action="photo"] { display: none; }

/* ─────────── Le menu, là où le navigateur sait le tenir ───────────
   `@supports selector(:popover-open)` et non un test de l'attribut : c'est la
   prise en charge du PSEUDO-SÉLECTEUR qu'il faut vérifier, puisque tout le
   basculement ouvert/fermé repose dessus. Un navigateur qui ignore
   `:popover-open` invalide silencieusement chaque règle qui l'emploie. */
@supports selector(:popover-open) {

    .btn--icone[popovertarget] { display: inline-flex; }
    .sidebar-footer-chevron { display: inline-flex; }
    [data-action="photo"] { display: flex; }

    /* ⚠️ MASQUAGE EXPLICITE, ET IL N'EST PAS FACULTATIF. La feuille du
       navigateur ferme les popovers avec `[popover]:not(:popover-open) {
       display: none }` — mais le `display: flex` du repli ci-dessus est une
       règle d'auteur, et une règle d'auteur passe devant la feuille du
       navigateur. Sans cette ligne, TOUS les menus de la page s'afficheraient
       en même temps, ouverts, empilés là où ils sont écrits. */
    .menu-actions:not(:popover-open) { display: none; }

    /* ⚠️ `inset: auto` ET `margin: 0` DÉFONT LA FEUILLE DU NAVIGATEUR, CE N'EST
       PAS DU STYLE. Elle pose sur tout `[popover]` :
           position: fixed; inset: 0; margin: auto;
       soit un centrage au milieu de l'écran, comme une boîte de dialogue. Sans
       ces deux lignes, le menu s'ouvrirait en plein centre quoi que `app.js`
       écrive dans `top` et `left` : `inset: 0` fixe les quatre côtés à la fois,
       donc `top` ne décide plus de rien.

       `position: fixed` est gardé volontairement — dans la « top layer » les
       coordonnées sont celles de la fenêtre, ce qui rend le placement
       directement lisible depuis `getBoundingClientRect()`. */
    .menu-actions {
        position: fixed;
        inset: auto;
        margin: 0;
        min-width: 208px;
        padding: 6px;
        border: var(--border-hairline);
        border-radius: var(--radius-lg);
        background: var(--color-canvas);
        box-shadow: var(--shadow-md);
    }
    .menu-actions:popover-open {
        display: flex;
        flex-direction: column;
        gap: 2px;
        animation: switcher-in var(--duration-fast) var(--ease-standard);
    }
    .menu-actions--pied { margin-top: 0; }

    /* Le même article que l'app-switcher : ce sont les seuls menus de l'ERP, et
       deux menus qui ne se ressemblent pas se remarquent. Le survol teinte le
       fond — l'une des rares familles où le système l'autorise, parce qu'un menu
       qui n'indique pas la ligne visée se clique de travers. */
    .menu-item {
        display: flex;
        align-items: center;
        gap: 10px;              /* l'écart du `.nav-item`, pour les entrées à icône */
        width: 100%;
        height: auto;
        min-height: var(--control-height-mini);
        padding: 0 8px;
        border: none;
        background: none;
        border-radius: var(--radius-md);
        text-align: left;
    }
    .menu-item:hover { background: var(--color-surface-soft); text-decoration: none; }
    .menu-item:focus { outline: none; }
    .menu-item:focus-visible { outline: 2px solid var(--color-ink); outline-offset: -2px; }
    .menu-item--danger:hover { background: var(--tint-error); }

    .menu-filet {
        display: block;
        height: 1px;
        margin: 4px 2px;
        background: var(--color-hairline);
    }
}

/* Entrées réservées au téléphone. Déclarées APRÈS le bloc `@supports`, qui pose
   `.menu-item { display: flex }` à la même spécificité : à égalité, c'est le
   dernier écrit qui gagne. Le média sous 800 px les rallume. */
.menu-item--mobile { display: none; }

/* L'avatar de la barre du haut : second déclencheur du menu du compte, allumé
   sous 800 px seulement, quand la barre latérale — et donc le premier — n'est
   plus là. */
.topbar-avatar {
    display: none;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    border-radius: var(--radius-full);
    flex-shrink: 0;
}
.topbar-avatar:focus { outline: none; }
.topbar-avatar:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 2px; }

/* ═══════════════ Champs de date ═══════════════
   ⚠️ ILS N'AVAIENT AUCUN STYLE DE BASE. Un `input[type="date"]` non habillé
   prend la police du système et non celle du produit, une hauteur qui n'est pas
   celle des autres champs, et l'icône de calendrier du navigateur — noire,
   pleine, étrangère au trait de tous les autres pictogrammes de l'application.
   Trois champs côte à côte dans le même formulaire ne se ressemblaient plus dès
   que l'un d'eux portait une date.

   ⚠️ UN MASQUE, PAS UNE IMAGE COLORÉE. Une couleur ne peut pas s'écrire dans
   une URI de données : elle y serait figée en hexadécimal, contre la règle du
   système qui veut des tokens partout. Le masque ne porte que la FORME ; c'est
   `background-color` qui la peint, et il lit `--color-text-muted`. Le jour où
   cette teinte change, l'icône suit.

   ⚠️ `::-webkit-calendar-picker-indicator` N'EXISTE QUE DANS CHROMIUM ET SAFARI.
   Firefox garde la sienne — c'est acceptable : la boîte, elle, est habillée
   partout, et c'est ce qui se voit d'abord. */
input[type="date"] {
    height: var(--control-height);
    padding: 0 var(--input-padding-x);
    border: 1px solid var(--color-hairline);
    border-radius: var(--radius-md);
    background: var(--color-canvas);
    color: var(--color-ink);
    font-family: var(--font-sans);
    font-size: var(--body-sm-size);
    transition: var(--transition-press);
}
input[type="date"]:focus {
    outline: none;
    border-color: var(--color-ink);
    border-width: 1.5px;
    /* Compense l'épaississement du filet : les chiffres ne bougent pas d'un
       demi-pixel au moment où l'on clique dedans. */
    padding: 0 13.5px;
}

/* La zone du calendrier. Élargie au-delà de l'icône : viser seize pixels pour
   ouvrir un sélecteur de date est un geste de précision inutile. */
input[type="date"]::-webkit-calendar-picker-indicator {
    width: 16px;
    height: 16px;
    padding: 0;
    margin-left: var(--space-xs);
    cursor: pointer;
    opacity: 1;
    background-color: var(--color-text-muted);
    transition: background-color var(--duration-fast) var(--ease-standard);
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}
input[type="date"]:hover::-webkit-calendar-picker-indicator,
input[type="date"]:focus::-webkit-calendar-picker-indicator {
    background-color: var(--color-ink);
}

/* Les chiffres eux-mêmes. Sans ça, Chromium les rend dans la police du système
   d'exploitation : « 29/07/2026 » ne ressemblait à aucun autre texte de la page. */
input[type="date"]::-webkit-datetime-edit { font-family: var(--font-sans); }
/* ⚠️ `jj/mm/aaaa` EST UN GABARIT, PAS UNE VALEUR. En encre pleine il se lit
   comme une date saisie ; en gris il se lit comme ce qu'il est — un champ vide.
   C'est la même règle que les zéros éteints du tableau de stock. */
input[type="date"]:not(:focus)::-webkit-datetime-edit-year-field:not([aria-valuenow]),
input[type="date"]:not(:focus)::-webkit-datetime-edit-month-field:not([aria-valuenow]),
input[type="date"]:not(:focus)::-webkit-datetime-edit-day-field:not([aria-valuenow]) {
    color: var(--color-text-muted);
}

/* ─────────── Navigation ─────────── */
.sidebar-nav {
    flex: 1;
    padding: 4px 12px 12px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    overflow-y: auto;
}

.sidebar-nav--utils { flex: 0; padding-bottom: 2px; }

.nav-section-title {
    padding: 16px 8px 6px;
    flex-shrink: 0;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border-radius: var(--radius-md);
    color: var(--color-text-body);
    text-decoration: none;
    font-size: var(--nav-link-size);
    font-weight: var(--nav-link-weight);
    line-height: var(--nav-link-leading);
    transition: var(--transition-press);
    cursor: pointer;
    border: none;
    background: none;
    width: 100%;
    text-align: left;
    font-family: inherit;
}

/* ⚠️ LES DEUX SÉLECTEURS, ET C'EST VOLONTAIRE. `.nav-icon` n'a jamais été
   stylée seule : toutes ses règles étaient sous `.nav-item`. Les entrées du
   menu du pied portent la même icône sans être des `.nav-item` — sans cette
   ligne, leurs SVG retombaient à leur taille naturelle, désalignés et de la
   mauvaise couleur. */
.nav-item .nav-icon,
.menu-item .nav-icon {
    width: 16px;
    height: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    color: var(--color-text-muted);
    transition: color var(--duration-fast) var(--ease-standard);
}
.nav-item .nav-icon svg,
.menu-item .nav-icon svg { width: 16px; height: 16px; }
.menu-item:hover .nav-icon { color: var(--color-ink); }
.menu-item--danger .nav-icon,
.menu-item--danger:hover .nav-icon { color: currentColor; }

.nav-item:hover { background: var(--color-surface-soft); color: var(--color-ink); text-decoration: none; }
.nav-item:hover .nav-icon { color: var(--color-ink); }

/* L'entrée courante : le gris de carte plein et l'encre. Pas de liseré
   coloré, pas de fond de marque — la sélection se lit au contraste, comme
   tout le reste du système. */
.nav-item.active,
.nav-item.filter-btn.is-active {
    background: var(--color-surface-card);
    color: var(--color-ink);
    font-weight: var(--weight-semibold);
}
.nav-item.active .nav-icon,
.nav-item.filter-btn.is-active .nav-icon { color: var(--color-ink); }

.nav-count {
    margin-left: auto;
    font-size: var(--caption-size);
    color: var(--color-text-muted-soft);
    font-variant-numeric: tabular-nums;
}
.nav-item:hover .nav-count,
.nav-item.active .nav-count { color: var(--color-text-muted); }

.sidebar-footer {
    padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
    border-top: var(--border-hairline-soft);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.sidebar-footer-user {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border-radius: var(--radius-md);
    color: inherit;
    text-decoration: none;
    transition: var(--transition-press);
    /* C'est un <button> : on neutralise l'habillage natif, le bloc doit rester
       visuellement identique tout en étant cliquable sur toute sa largeur. */
    width: 100%;
    background: none;
    border: 0;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.sidebar-footer-user:hover { background: var(--color-surface-soft); }

/* Le chevron pousse le nom à occuper la place restante, sinon il se colle à
   lui. Même rotation que celui de l'app-switcher : deux déclencheurs de menu
   dans la même barre ne devraient pas se comporter différemment. */
.sidebar-footer-user .sidebar-user-name { flex: 1; min-width: 0; }
.sidebar-footer-chevron {
    align-items: center;
    flex-shrink: 0;
    color: var(--color-text-muted);
    transition: transform var(--duration-fast) var(--ease-standard);
}
.sidebar-footer-user[aria-expanded="true"] .sidebar-footer-chevron { transform: rotate(180deg); }

/* Les avatars sont ronds et pleins dans tout le système. Fond encre : c'est
   la seule couleur de remplissage que le design autorise hors pastille. */
.sidebar-avatar {
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: var(--color-primary);
    color: var(--color-on-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: var(--weight-semibold);
    flex-shrink: 0;
    overflow: hidden;
    position: relative;
}

/* La photo se superpose aux initiales et garde exactement la même empreinte.
   Si le fichier n'existe pas, app.js la retire et les initiales réapparaissent. */
.sidebar-avatar-photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.sidebar-user-name {
    font-size: var(--body-sm-size);
    font-weight: var(--weight-medium);
    color: var(--color-ink);
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ═══════════════════════════ BARRE HAUTE ═══════════════════════════ */
.main-wrapper {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    height: 100vh;
}

.topbar {
    background: var(--color-canvas);
    color: var(--color-ink);
    height: var(--topbar-height);
    border-bottom: var(--border-hairline);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 var(--space-lg);
    flex-shrink: 0;
    gap: var(--space-sm);
}

.topbar-title {
    font-size: var(--body-sm-size);
    font-weight: var(--weight-medium);
    color: var(--color-text-muted);
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    min-width: 0;
}

.topbar-title .topbar-sep { color: var(--color-surface-strong); font-weight: var(--weight-regular); }

/* Le point ne s'affiche que s'il sépare deux choses. L'accueil de l'outil ne
   surcharge pas le second niveau : sans cette règle, le fil se terminait par un
   « Stock · » suspendu.

   `:has()` parce qu'aucun sélecteur ne remonte au frère PRÉCÉDENT — c'est le
   parent qu'on interroge sur le contenu de son dernier enfant. Là où il manque,
   il reste un point orphelin sur une seule page : le prix est proportionné. */
.topbar-title:has(.topbar-current:empty) .topbar-sep { display: none; }

/* L'écran où l'on est, en encre : c'est le seul des deux qui répond à une
   question. Le nom de l'outil est un repère, il passe en gris. */
.topbar-title .topbar-current {
    color: var(--color-ink);
    font-weight: var(--weight-semibold);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.topbar-actions {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
}

/* Champ de recherche : blanc + filet, comme tous les champs du système. Il
   était sur fond gris, ce qui le faisait lire comme une pastille inerte. */
.topbar-search {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    height: 34px;
    padding: 0 var(--space-sm);
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-md);
    width: 264px;
    transition: var(--transition-press);
}

/* Le focus épaissit le filet jusqu'à l'encre — le seul changement d'état que
   le système documente pour un champ. Le décalage de 0,5px est compensé pour
   que le contenu ne bouge pas. */
.topbar-search:focus-within {
    border-color: var(--color-ink);
    border-width: 1.5px;
    padding: 0 11.5px;
}

.topbar-search input {
    flex: 1;
    min-width: 0;
    border: none;
    background: none;
    outline: none;
    font-family: inherit;
    font-size: var(--body-sm-size);
    color: var(--color-ink);
}

.topbar-search input::placeholder { color: var(--color-text-muted-soft); }

.topbar-search .topbar-search-icon { color: var(--color-text-muted); display: inline-flex; }

.topbar-search .topbar-search-kbd {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted-soft);
    border: var(--border-hairline);
    border-radius: var(--radius-xs);
    padding: 1px 5px;
    line-height: 1.3;
}

/* Bouton icône : cercle de 36px, un glyphe centré. */
.topbar-icon-btn {
    width: var(--icon-button-size);
    height: var(--icon-button-size);
    border-radius: var(--radius-full);
    border: 1px solid transparent;
    background: none;
    color: var(--color-text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: var(--transition-press);
    text-decoration: none;
}

.topbar-icon-btn:hover { background: var(--color-surface-soft); color: var(--color-ink); }

.topbar-search.is-searching .topbar-search-icon { animation: spin 0.9s linear infinite; }

@keyframes spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* ═══════════════════ CONTRÔLES ═══════════════════
   Le vocabulaire de boutons du design system, et il n'y en a que trois :
   — primaire : encre pleine, blanc dessus. S'assombrit à l'APPUI, jamais au
     survol. C'est la seule action qui a le droit d'être remplie ;
   — secondaire : blanc, filet, encre. Le fond passe au gris le plus clair à
     l'appui ;
   — texte : ni fond ni filet.
   Une couleur d'accent ne touche JAMAIS un bouton — pas de bouton bleu, pas
   de bouton rouge de suppression. C'est la place du geste qui dit sa gravité,
   pas sa couleur. */
/* ⚠️ UNE SEULE HAUTEUR DE BOUTON DANS TOUT LE PROJET — 32 px. Il y en a eu
   deux : `.btn` à 40 pour l'action d'une carte, `.btn--mini` à 32 pour les
   commandes d'une ligne dense. Chacune se défendait ; les voir voisiner sur un
   même écran, non. Le rayon et le rembourrage suivent l'ancien `--mini`, qui
   était le cran documenté par le design system. */
.btn {
    height: var(--button-height);
    padding: 0 14px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-hairline);
    background: var(--color-canvas);
    color: var(--color-ink);
    font-family: var(--font-sans);
    font-size: var(--caption-size);
    font-weight: var(--button-weight);
    line-height: var(--button-leading);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition: var(--transition-press);
}
.btn:hover { text-decoration: none; }
.btn:active { background: var(--color-surface-soft); }
.btn[disabled], .btn:disabled {
    background: transparent;
    color: var(--color-text-muted);
    cursor: not-allowed;
}

.btn--primary {
    background: var(--color-primary);
    color: var(--color-on-primary);
    border-color: var(--color-primary);
}
.btn--primary:active { background: var(--color-primary-active); border-color: var(--color-primary-active); }
.btn--primary[disabled], .btn--primary:disabled {
    background: var(--color-primary-disabled);
    border-color: var(--color-primary-disabled);
    color: var(--color-text-muted);
}

/* ⚠️ `.btn--mini` A DISPARU, ET SON ABSENCE EST LE SUJET. Elle ne décrivait
   plus rien : `.btn` porte désormais exactement ses valeurs. La garder en
   alias vide aurait laissé croire, à la lecture d'un gabarit, qu'un bouton
   diffère de son voisin — et invité à la réintroduire. Elle a été retirée des
   onze endroits qui la portaient, dans le même geste. */

/* La suppression est le SEUL endroit où le design system laisse une couleur
   toucher un bouton — et encore : le texte seulement, jamais le fond. Un bouton
   rouge plein attirerait l'œil vers l'action qu'on veut rendre délibérée. */
.btn--danger { color: var(--color-error); }
.btn--danger:active { background: var(--tint-error); }

/* Bouton carré : la hauteur d'un bouton, la même largeur, aucun rembourrage
   horizontal. Sans quoi le rembourrage de `.btn` étire un rond de 16 px en
   pilule — ou pire, ne lui laisse aucune place et l'écrase à 2 px, ce qui est
   arrivé tant que cette règle était écrite avant `.btn`.

   ⚠️ SA PLACE EST ICI, APRÈS `.btn`. Les deux posent `padding` à la même
   spécificité : seul l'ordre les départage. Déplacer ce bloc plus haut le
   casserait sans qu'aucune erreur ne le signale — le bouton garderait sa
   taille, seule l'icône disparaîtrait. */
.btn--icone {
    width: var(--button-height);
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.btn--icone svg { width: 16px; height: 16px; flex: none; }

.btn--sync.is-syncing .nav-icon { animation: rot 0.9s linear infinite; }
.btn--sync.is-syncing { pointer-events: none; opacity: 0.7; }
@keyframes rot { to { transform: rotate(360deg); } }

/* Champs de saisie : 40px, blanc, filet. Le focus épaissit le filet jusqu'à
   l'encre. Aucune bague colorée nulle part. */
.list-toolbar select,
.list-toolbar button,
.reglages-ligne select,
.reglages-ligne input[type="number"],
.reglages-outils select,
.reglages-outils input[type="number"],
.fiche-ligne input,
.achat-champ input,
.tri-mobile select {
    height: var(--control-height);
    padding: 0 var(--input-padding-x);
    border: 1px solid var(--color-hairline);
    border-radius: var(--radius-md);
    background: var(--color-canvas);
    color: var(--color-ink);
    font-family: inherit;
    font-size: var(--body-sm-size);
    transition: var(--transition-press);
}
/* ⚠️ LA LISTE D'UNE RANGÉE D'ACTIONS SUIT LES BOUTONS, PAS LES CHAMPS. Dans
   `--large`, le `<select>` n'est pas une valeur qu'on règle et qu'on relit : il
   choisit ce que le bouton d'à côté va lancer, et les deux se lisent comme un
   seul geste. À 40 px contre 32, la liste dépassait ses boutons de quatre
   pixels en haut et en bas — l'écart se voyait d'autant plus qu'ils se
   touchent.

   ⚠️ LES AUTRES `.reglages-outils` NE SONT PAS CONCERNÉS, et c'est délibéré :
   l'heure du résumé ou le seuil d'alerte sont des VALEURS, alignées sur les
   champs de saisie du reste du projet. Ils n'ont pas de bouton à côté d'eux. */
.reglages-outils--large select { height: var(--button-height); }

.reglages-ligne select:focus,
.reglages-ligne input[type="number"]:focus,
.reglages-outils select:focus,
.reglages-outils input[type="number"]:focus,
.fiche-ligne input:focus,
.achat-champ input:focus,
.tri-mobile select:focus {
    outline: none;
    border-color: var(--color-ink);
    border-width: 1.5px;
}

/* ═══════════════ ESPACE DE TRAVAIL — Visionneur de documents ═══════════════
   Ce bloc ne sert pas à l'ERP : il habille arven-doc-viewer, qui partage cette
   feuille. Il est maintenu à jour pour que la refonte ne casse pas l'autre
   outil le jour où on la lui déploie. */
.workspace {
    flex: 1;
    display: flex;
    min-height: 0;
    overflow: hidden;
}

.list-panel {
    width: var(--list-width);
    border-right: var(--border-hairline);
    display: flex;
    flex-direction: column;
    background: var(--color-canvas);
    min-width: 0;
    flex-shrink: 0;
}

.list-header {
    padding: var(--space-lg) var(--space-lg) var(--space-sm);
    display: flex;
    flex-direction: column;
    gap: var(--space-xxs);
}

.notion-breadcrumb {
    font-size: var(--caption-size);
    color: var(--color-text-muted);
    display: flex;
    align-items: center;
    gap: 6px;
}
.notion-breadcrumb span { color: var(--color-surface-strong); }

.notion-h1-sm {
    font-size: var(--title-lg-size);
    font-weight: var(--title-lg-weight);
    line-height: var(--title-lg-leading);
    letter-spacing: var(--title-lg-tracking);
    color: var(--color-ink);
    margin: 0;
}

.list-meta { font-size: var(--caption-size); color: var(--color-text-muted); }

.list-toolbar {
    padding: var(--space-sm) var(--space-md);
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    border-bottom: var(--border-hairline);
}
.list-toolbar select,
.list-toolbar button { height: 32px; border-radius: var(--radius-sm); cursor: pointer; }
.list-toolbar button.is-active {
    background: var(--color-primary);
    color: var(--color-on-primary);
    border-color: var(--color-primary);
}
.list-toolbar .spacer { flex: 1; }

.list-scroll { flex: 1; overflow-y: auto; padding-bottom: var(--space-lg); }

.doc-row {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-md);
    border-bottom: var(--border-hairline-soft);
    cursor: pointer;
    transition: var(--transition-press);
    text-decoration: none;
    color: inherit;
}
.doc-row:hover { background: var(--color-surface-soft); text-decoration: none; }
.doc-row.active { background: var(--color-surface-card); }

.doc-icon {
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    background: var(--color-surface-card);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--color-text-muted);
    flex-shrink: 0;
}
/* Les pastels du système, réservés justement à ce genre de catégorie. */
.doc-icon--pdf   { background: color-mix(in srgb, var(--color-error) 12%, var(--color-canvas));         color: var(--ink-error); }
.doc-icon--image { background: color-mix(in srgb, var(--color-badge-emerald) 16%, var(--color-canvas)); color: var(--ink-success); }
.doc-icon--text  { background: color-mix(in srgb, var(--color-brand-accent) 12%, var(--color-canvas));  color: color-mix(in srgb, var(--color-brand-accent) 45%, #000); }
.doc-icon--video { background: color-mix(in srgb, var(--color-badge-violet) 12%, var(--color-canvas));  color: color-mix(in srgb, var(--color-badge-violet) 45%, #000); }
.doc-icon--audio { background: color-mix(in srgb, var(--color-badge-orange) 16%, var(--color-canvas));  color: color-mix(in srgb, var(--color-badge-orange) 45%, #000); }
.doc-icon--other { background: var(--color-surface-card); color: var(--color-text-muted); }

.doc-body { min-width: 0; }
.doc-name {
    font-size: var(--body-sm-size);
    font-weight: var(--weight-medium);
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
}
.doc-meta {
    font-size: var(--caption-size);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    margin-top: 2px;
    display: flex;
    gap: 6px;
    align-items: center;
    white-space: nowrap;
    overflow: hidden;
    min-width: 0;
}
.doc-meta .folder { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }
.doc-meta .dot { color: var(--color-surface-strong); flex-shrink: 0; }
.doc-meta .ext { font-family: var(--font-mono); font-size: 11px; flex-shrink: 0; }

.doc-right { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
.doc-size { font-size: var(--caption-size); color: var(--color-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.doc-date { font-size: var(--caption-size); color: var(--color-text-muted); white-space: nowrap; }

.doc-row .match-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: var(--weight-medium);
    color: var(--color-ink);
    background: var(--color-surface-card);
    padding: 2px 8px;
    border-radius: var(--radius-pill);
    flex-shrink: 0;
}

.empty-state { padding: 64px var(--space-lg); text-align: center; color: var(--color-text-muted); }
.empty-state-icon { font-size: 28px; margin-bottom: var(--space-sm); opacity: 0.5; }
.empty-state-title {
    font-size: var(--title-sm-size);
    font-weight: var(--weight-semibold);
    color: var(--color-ink);
    margin-bottom: var(--space-xxs);
}
.empty-state-text { font-size: var(--body-sm-size); }
.empty-state code {
    font-family: var(--font-mono);
    font-size: var(--caption-size);
    background: var(--color-surface-card);
    padding: 2px 6px;
    border-radius: var(--radius-xs);
    color: var(--color-ink);
}

.viewer-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; background: var(--color-surface-soft); }

.viewer-toolbar {
    height: 52px;
    padding: 0 var(--space-md);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    background: var(--color-canvas);
    border-bottom: var(--border-hairline);
    flex-shrink: 0;
}
.viewer-title { display: flex; align-items: center; gap: 10px; min-width: 0; flex: 1; }
.viewer-title .doc-icon { width: 26px; height: 26px; }
.viewer-title-text {
    font-size: var(--title-sm-size);
    font-weight: var(--weight-semibold);
    color: var(--color-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.viewer-title-meta { font-size: var(--caption-size); color: var(--color-text-muted); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.viewer-actions { display: flex; align-items: center; gap: 6px; }

.viewer-body { flex: 1; overflow: auto; background: var(--color-surface-soft); display: flex; min-height: 0; }
.viewer-empty { margin: auto; text-align: center; color: var(--color-text-muted); padding: 40px; }
.viewer-empty-icon {
    width: 56px;
    height: 56px;
    border-radius: var(--radius-lg);
    background: var(--color-surface-card);
    color: var(--color-text-muted);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: var(--space-md);
}
.viewer-empty-title { font-weight: var(--weight-semibold); color: var(--color-ink); font-size: var(--title-md-size); margin-bottom: var(--space-xxs); }
.viewer-empty-text { font-size: var(--body-sm-size); }

.viewer-pdf { width: 100%; height: 100%; border: none; background: var(--color-surface-soft); }
.viewer-image-wrap { margin: auto; padding: var(--space-xl); text-align: center; width: 100%; }
.viewer-image-wrap img {
    max-width: 100%;
    max-height: calc(100vh - 200px);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    background: var(--color-canvas);
}
.viewer-text-wrap { width: 100%; padding: var(--space-xl); overflow: auto; }
.viewer-text-wrap pre {
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-lg);
    padding: var(--space-lg);
    font-family: var(--font-mono);
    font-size: var(--code-size);
    line-height: var(--code-leading);
    color: var(--color-ink);
    white-space: pre-wrap;
    word-break: break-word;
    max-width: 920px;
    margin: 0 auto;
}
.viewer-media-wrap { margin: auto; padding: var(--space-xl); text-align: center; width: 100%; }
.viewer-media-wrap video,
.viewer-media-wrap audio { max-width: 100%; width: 720px; border-radius: var(--radius-lg); box-shadow: var(--shadow-md); }

/* Barres de défilement discrètes. */
.list-scroll::-webkit-scrollbar,
.viewer-body::-webkit-scrollbar,
.viewer-text-wrap::-webkit-scrollbar,
.stock-main::-webkit-scrollbar { width: 10px; height: 10px; }

.list-scroll::-webkit-scrollbar-thumb,
.viewer-body::-webkit-scrollbar-thumb,
.viewer-text-wrap::-webkit-scrollbar-thumb,
.stock-main::-webkit-scrollbar-thumb {
    background: var(--color-surface-strong);
    border-radius: 5px;
    border: 2px solid transparent;
    background-clip: padding-box;
}
.list-scroll::-webkit-scrollbar-thumb:hover,
.viewer-body::-webkit-scrollbar-thumb:hover,
.viewer-text-wrap::-webkit-scrollbar-thumb:hover,
.stock-main::-webkit-scrollbar-thumb:hover {
    background: var(--color-text-muted-soft);
    background-clip: padding-box;
}

/* ═══════════════════════════ ÉCRANS DE L'ERP ═══════════════════════════ */
.stock-main {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-lg) var(--space-lg) 40px;
    background: var(--color-surface-soft);
}

/* ─────────── Tuiles de chiffres ───────────
   Cartes blanches à filet : le design system fait de ce choix une question de
   SENS — carte grise pour une affirmation, carte blanche à filet pour « voici
   le produit ». Un chiffre mesuré sur le vrai stock est un fait, pas une
   affirmation : il va sur du blanc. */
.kpi-row {
    display: grid;
    /* 150px : la cinquième tuile (« À commander ») restait seule, orpheline,
       sur une deuxième rangée dès que le contenu descendait sous ~900px.
       Deux par ligne restent lisibles sur téléphone. */
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--space-md);
    margin-bottom: var(--space-lg);
}
.kpi-tile {
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-lg);
    padding: var(--card-padding-compact);
}
.kpi-label {
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    line-height: var(--caption-leading);
    color: var(--color-text-muted);
}
/* Le chiffre porte tout le poids de la tuile. Graisse 600 et non 700 : le
   design system est explicite — « pour plus d'emphase, augmenter la TAILLE,
   pas la graisse ». */
.kpi-value {
    font-size: var(--display-sm-size);
    font-weight: var(--display-sm-weight);
    line-height: var(--display-sm-leading);
    letter-spacing: var(--display-sm-tracking);
    color: var(--color-ink);
    margin-top: 6px;
    font-variant-numeric: tabular-nums;
}
.kpi-tile--alert .kpi-value { color: var(--color-error); }
.kpi-tile--alert.kpi-tile--zero .kpi-value { color: var(--color-ink); }
.kpi-note { font-size: var(--caption-size); font-weight: var(--weight-regular); color: var(--color-text-muted); margin-top: 6px; line-height: 1.4; }
/* Un montant n'est pas un compteur : « 1 247,80 € » à 28px déborde de la
   tuile dès qu'on passe le millier. */
.kpi-value--texte { font-size: var(--title-lg-size); letter-spacing: var(--title-lg-tracking); }

/* ─────────── Panneaux ─────────── */
.stock-panel {
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-lg);
    overflow: hidden;
}
.stock-panel-head {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-lg);
    border-bottom: var(--border-hairline);
}
.stock-panel-title {
    font-size: var(--title-md-size);
    font-weight: var(--title-md-weight);
    line-height: var(--title-md-leading);
    color: var(--color-ink);
}
.stock-sync-meta { font-size: var(--caption-size); font-weight: var(--weight-regular); color: var(--color-text-muted); }
.stock-sync-meta .sync-err, .sync-err { color: var(--color-error); }

/* ─────────── Tableau du stock ─────────── */
.stock-table-wrap { overflow-x: auto; }
.stock-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--body-sm-size);
}
/* ⚠️ LA GOUTTIÈRE EST À 12 px, PAS AUX 16 px DU RESTE DU SYSTÈME. Onze
   colonnes, dont aucune n'est superflue : à 16 px de chaque côté le tableau
   réclamait 1258 px pour un panneau qui en offre 976 sur un portable de
   1280 — il fallait le faire défiler de côté pour atteindre « État », soit
   exactement ce qu'on vient chercher le matin. Le pas de 4 px du système
   autorise 12 ; c'est la densité d'une ligne de données, pas d'une carte. */
.stock-table th {
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    color: var(--color-text-muted);
    text-align: left;
    padding: 10px var(--space-sm);
    border-bottom: var(--border-hairline);
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}
/* Les colonnes de chiffres sont encore plus serrées : leur contenu fait deux
   ou trois caractères, c'est l'intitulé qui fixait leur largeur. */
.stock-table th.num, .stock-table td.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    padding-left: 10px;
    padding-right: 10px;
}
.stock-table th .arrow { opacity: 0.7; }
.stock-table td {
    padding: var(--space-sm);
    border-bottom: var(--border-hairline-soft);
    vertical-align: middle;
    color: var(--color-text-body);
}
.stock-table tbody tr:hover { background: var(--color-surface-soft); }
.stock-table tbody tr:last-child td { border-bottom: none; }
.stock-table .sku { font-family: var(--font-mono); font-size: var(--caption-size); color: var(--color-text-muted); white-space: nowrap; }
/* 300 px et non 380 : le nom est de toute façon coupé à l'ellipse, et ce sont
   la vignette et le SKU qui identifient la ligne. Quatre-vingts pixels rendus
   aux colonnes qu'on vient vraiment lire. */
.stock-table .pname {
    max-width: 300px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--color-ink);
    font-weight: var(--weight-medium);
}
.stock-table .muted { color: var(--color-text-muted); }

/* ─────────── Le poids d'un chiffre dit son importance ───────────
   ⚠️ `.stock-table td` EN TÊTE DU SÉLECTEUR, pour la raison déjà documentée
   plus bas à propos de `.col-action` : la cellule porte AUSSI `.num`, et
   `.stock-table td.num` est plus spécifique qu'une classe seule. Écrites
   `.num--muet` tout court, ces règles seraient purement et simplement
   ignorées — sans erreur, évidemment.

   « Dispo » est le chiffre qu'on vient lire : c'est le seul qui décide s'il
   reste quelque chose à vendre. Il porte donc un demi-cran de graisse, pas une
   couleur — le système veut la taille avant la graisse et la graisse avant la
   couleur, et la couleur est déjà prise par l'état. */
.stock-table td.col-dispo { font-weight: var(--weight-medium); }

/* Zéro réservé, zéro en route, zéro vendu : il ne se passe rien, et ça ne
   mérite pas de retenir l'œil. Sur une page où quatre lignes sur six sont en
   rupture, ces zéros-là formaient l'essentiel de ce qu'on voyait. */
.stock-table td.num--muet { color: var(--color-text-muted); }

/* Zéro disponible, en revanche, est la seule chose de la page qui appelle une
   décision. C'est le deuxième et dernier endroit du tableau où la couleur
   touche autre chose qu'une pastille d'état. */
.stock-table td.num--rupture {
    color: var(--color-error);
    font-weight: var(--weight-semibold);
}

/* Vignette produit — première colonne. Le tableau était une liste de SKU
   alphanumériques : « ARV-260708-003 » ne dit rien, la photo se reconnaît
   d'un coup d'œil. */
.stock-table th.col-vignette,
.stock-table td.col-vignette { width: 1%; padding-right: 0; cursor: default; }
.vignette {
    display: block;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-md);
    object-fit: contain;
    /* Fond blanc : beaucoup de photos produit sont détourées sur blanc, elles
       disparaîtraient sur une case transparente. */
    background: var(--color-canvas);
    border: var(--border-hairline);
}
/* Repli quand Amazon n'a pas de visuel : l'initiale, pour garder l'alignement
   des lignes plutôt qu'un trou. */
.vignette--vide {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--color-surface-card);
    color: var(--color-text-muted);
    font-weight: var(--weight-semibold);
    font-size: var(--body-sm-size);
}

/* Bouton œil de fin de ligne : discret pour ne pas concurrencer les données,
   mais TOUJOURS visible — un bouton qui n'apparaît qu'au survol est
   introuvable pour qui ne le cherche pas, et n'existe pas du tout sur mobile. */
.stock-table th:last-child { cursor: default; width: 1%; }
/* ⚠️ `nowrap` OBLIGATOIRE. La colonne est en `width: 1%`, donc au plus étroit :
   les deux boutons (crayon, œil) s'empilaient l'un sous l'autre et faisaient
   grandir toutes les lignes du tableau. Ils tiennent sur une ligne. */
/* ⚠️ `.stock-table td` EN TÊTE DU SÉLECTEUR, ET C'EST INDISPENSABLE : la cellule
   porte AUSSI `.num`, dont la règle `.stock-table td.num` est plus spécifique.
   Écrit `.col-action` tout court, le rembourrage réduit était purement et
   simplement ignoré — sans erreur, évidemment. */
.col-action { white-space: nowrap; }
.stock-table td.col-action {
    /* Gouttière réduite : cette colonne ne porte pas de donnée, seulement deux
       boutons. Chaque pixel qu'elle rend va aux colonnes qu'on vient lire. */
    padding-left: 2px;
    padding-right: 6px;
}
.row-hide {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 5px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--color-text-muted-soft);
    cursor: pointer;
    transition: var(--transition-press);
}
.stock-table tbody tr:hover .row-hide,
.row-hide:hover,
.row-hide:focus-visible { color: var(--color-ink); }
.row-hide:hover { background: var(--color-surface-card); }
.row-hide[disabled] { color: var(--color-surface-strong); cursor: default; }
.row-fiche { margin-right: 2px; }

.stock-hidden { border-top: var(--border-hairline); padding: 0 var(--space-md) var(--space-xxs); }
.stock-hidden > summary {
    font-size: var(--caption-size);
    color: var(--color-text-muted);
    padding: var(--space-sm) 0;
    cursor: pointer;
    user-select: none;
}
.stock-hidden > summary:hover { color: var(--color-ink); }
/* Les lignes masquées sont volontairement ternes : on les consulte pour les
   réactiver, pas pour les lire. */
.stock-table--hidden { opacity: 0.7; margin-bottom: var(--space-xs); }
.stock-table--hidden .row-hide { color: var(--color-text-muted); }

/* ─────────── Pastilles ───────────
   ⚠️ ELLES ÉTAIENT EN CAPITALES MONTSERRAT à 9px avec 0,08 em d'approche.
   Le design system emploie la casse normale partout, y compris sur les
   pastilles, et les compose en 13px. À 9px capitales, « rupture » se lisait
   comme une étiquette de gabarit plutôt que comme un état du stock. */
/* ⚠️ UN GROUPE DE PASTILLES A SON CONTENEUR — même leçon que `.compte-corps`.
   Deux `.badge` voisins n'ont entre eux que l'espace typographique du HTML, et
   PLUS RIEN du tout dès qu'ils passent à la ligne : ils se touchent alors bord
   à bord. Mesuré à −47px sur la colonne « État » de l'écran Utilisateurs.

   La marge ne va pas sur la pastille : elle décalerait de travers les
   trois-quarts des cas, où il n'y en a qu'une (colonne État du stock, en-tête
   de panneau, état d'un achat). C'est le groupe qui espace, jamais l'élément. */
.badges {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.badge {
    display: inline-flex;
    align-items: center;
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    line-height: 1.3;
    padding: 3px 10px;
    border-radius: var(--radius-pill);
    white-space: nowrap;
}
.badge--ok      { color: var(--ink-success); background: var(--tint-success); }
.badge--low     { color: var(--ink-warning); background: var(--tint-warning); }
.badge--out     { color: var(--ink-error);   background: var(--tint-error); }
.badge--unknown { color: var(--color-text-muted); background: var(--color-surface-card); }
/* « À commander » est un ORDRE DE MISSION, pas un symptôme : encre pleine,
   comme l'action primaire, là où « faible » et « rupture » sont des états
   subis. C'est l'inversion qui porte le signal — le système fait exactement
   pareil avec sa formule tarifaire mise en avant. */
.badge--order   { color: var(--color-on-primary); background: var(--color-primary); }

/* Colonne « à commander ». Sobre par défaut : la plupart des lignes affichent
   « dans 26 j » ou « — », et un rouge partout ne signalerait plus rien. */
.col-commande { color: var(--color-text-muted); white-space: nowrap; }
.col-commande--urgent { color: var(--color-error); font-weight: var(--weight-semibold); }

.setup-banner {
    background: var(--tint-warning);
    border: 1px solid var(--line-warning);
    color: var(--ink-warning);
    border-radius: var(--radius-lg);
    padding: var(--space-md) var(--space-lg);
    font-size: var(--body-sm-size);
    margin-bottom: var(--space-lg);
    line-height: 1.5;
}
.stock-empty { padding: 64px var(--space-lg); text-align: center; color: var(--color-text-muted); }
.stock-empty h3 {
    color: var(--color-ink);
    font-size: var(--title-md-size);
    line-height: var(--title-md-leading);
    letter-spacing: 0;
    margin-bottom: 6px;
}

/* ─────────────────────────── Fiche produit ─────────────────────────── */
.fiche-champs { display: flex; flex-direction: column; gap: var(--space-sm); margin: var(--space-xs) 0 var(--space-md); }
.fiche-ligne {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px var(--space-sm);
    font-size: var(--body-sm-size);
}
.fiche-libelle {
    min-width: 140px;
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    color: var(--color-ink);
}
.fiche-ligne input { flex: 1; min-width: 0; }
.fiche-ligne input:disabled { background: var(--color-surface-soft); color: var(--color-text-muted); }
.fiche-note { flex: 1 1 100%; padding-left: 152px; font-size: var(--caption-size); font-weight: var(--weight-regular); color: var(--color-text-muted); }
.fiche-calcul {
    font-size: var(--caption-size);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    line-height: 1.5;
    min-height: 20px;      /* réserve la place : sinon la modale sautille */
}

/* Le délai constaté sur les commandes réellement reçues. Discret : c'est une
   suggestion, pas une correction — il peut vouloir garder sa marge. */
.fiche-mesure {
    border: 1px dashed var(--color-hairline);
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-text-muted);
    font: inherit;
    font-size: var(--caption-size);
    padding: 3px 9px;
    cursor: pointer;
    transition: var(--transition-press);
}
.fiche-mesure:hover { color: var(--color-ink); border-color: var(--color-text-muted); }

/* ─────────────────────────── Achats ─────────────────────────── */
.fiche-achats { margin-top: var(--space-lg); padding-top: var(--space-md); border-top: var(--border-hairline); }
.fiche-section { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px var(--space-sm); margin-bottom: var(--space-sm); }
.fiche-section-titre {
    margin: 0;
    font-size: var(--title-sm-size);
    font-weight: var(--weight-semibold);
    letter-spacing: 0;
    color: var(--color-ink);
}
.fiche-note--inline { flex: 0 1 auto; padding-left: 0; }
.fiche-vide { font-size: var(--caption-size); color: var(--color-text-muted); line-height: 1.5; margin-bottom: var(--space-sm); }

.achat-liste { list-style: none; margin: 0 0 var(--space-sm); padding: 0; }
.achat-item {
    padding: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-xs);
    border: var(--border-hairline);
    border-radius: var(--radius-md);
    cursor: pointer;
    line-height: 1.45;
    transition: var(--transition-press);
}
.achat-item:hover { background: var(--color-surface-soft); }
.achat-haut { display: flex; align-items: center; gap: var(--space-xs); font-size: var(--body-sm-size); }
.achat-titre { font-weight: var(--weight-semibold); color: var(--color-ink); }
/* Pousse le total à droite : c'est le chiffre qu'on cherche du regard. */
.achat-total { margin-left: auto; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.achat-meta, .achat-bilan { font-size: var(--caption-size); font-weight: var(--weight-regular); color: var(--color-text-muted); }
.achat-etat { font-size: 11px; padding: 2px 8px; }
.achat-etat--en-route    { color: var(--ink-warning); background: var(--tint-warning); }
.achat-etat--reçu        { color: var(--color-ink);   background: var(--color-surface-card); }
.achat-etat--chez-Amazon { color: var(--ink-success); background: var(--tint-success); }

/* L'avancée du lot vers Amazon. Toujours sobre : « il en reste deux à
   envoyer » est un état de fait, pas un problème — et une ligne rouge sur
   chaque commande fournisseur incomplète ne signalerait plus rien. */
.achat-reception { font-size: var(--caption-size); font-weight: var(--weight-regular); color: var(--color-text-muted); margin-top: 2px; }

/* Le litige, lui, est rare et demande un geste : des unités déclarées à
   Amazon qu'Amazon n'a pas scannées. D'où le rouge, et d'où sa place à part —
   il ne parle pas d'un lot en particulier mais de ce qu'on doit réclamer. */
.achat-litige {
    margin: 0 0 var(--space-xs);
    padding: var(--space-sm) var(--space-md);
    border-radius: var(--radius-md);
    background: var(--tint-error);
    border: 1px solid var(--line-error);
    color: var(--ink-error);
    font-size: var(--caption-size);
    line-height: 1.5;
    font-weight: var(--weight-medium);
}

.achat-form {
    padding: var(--space-md);
    margin-bottom: var(--space-sm);
    border: var(--border-hairline);
    border-radius: var(--radius-lg);
    background: var(--color-surface-soft);
}
.achat-grille { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-sm); }
.achat-champ--large { grid-column: span 3; }
.achat-champ { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.achat-champ span { font-size: var(--caption-size); font-weight: var(--caption-weight); color: var(--color-ink); }
.achat-champ input { width: 100%; min-width: 0; }  /* sans ça un input en grille refuse de rétrécir */

/* Unités payées mais pas encore chez Amazon. Volontairement discret : c'est
   un rappel pour ne pas recommander deux fois, pas une alerte de plus. */
.approche {
    margin-left: 6px;
    padding: 1px 7px;
    border-radius: var(--radius-pill);
    background: var(--tint-warning);
    color: var(--ink-warning);
    font-size: 11px;
    font-weight: var(--weight-medium);
    white-space: nowrap;
}

/* ═══════════════════ RÉGLAGES ═══════════════════
   ⚠️ DEUX DISPOSITIONS QUI NE FONT PAS DOUBLE EMPLOI.
   `.reglages-groupe` / `.reglages-ligne` : on SAISIT des champs — libellé à
   gauche, valeur à droite, une par ligne. Servent aux trois pages de
   `comptes/` (compte, 2FA, codes de secours).
   `.reglages-rangee` / `.reglages-tete` : on BASCULE des interrupteurs et on
   lit un état. Sert à la page Réglages.
   Les fondre donnerait une forme moyenne, mauvaise pour les deux. */
.reglages-groupe {
    border: none;
    border-top: var(--border-hairline);
    padding: var(--space-lg) 0 var(--space-xs);
    margin: 0;
}
.reglages-groupe:first-of-type { border-top: none; padding-top: var(--space-md); }
/* ⚠️ `float` SUR UN `<legend>`, ET CE N'EST PAS UN BRICOLAGE. Par défaut le
   navigateur pose la légende À CHEVAL sur la bordure du `fieldset` : elle y
   perce un trou et la ligne repart à sa droite. Avec l'ancienne étiquette de
   10px en capitales, ça passait pour un style. Avec un vrai titre de 16px, le
   filet traversait le texte à mi-hauteur — on lisait « Photo de profil » barré.

   `float: left; width: 100%` est la façon reconnue de rendre une légende
   normale : elle redescend sous la bordure, qui redevient un trait plein. Le
   `clear` sur ce qui suit est la contrepartie obligatoire du flottement. */
.reglages-groupe > legend {
    float: left;
    width: 100%;
    font-size: var(--title-sm-size);
    font-weight: var(--weight-semibold);
    line-height: var(--title-sm-leading);
    letter-spacing: 0;
    text-transform: none;
    color: var(--color-ink);
    padding: 0;
    margin-bottom: var(--space-xs);
}
.reglages-groupe > legend ~ * { clear: both; }
.reglages-aide {
    font-size: var(--body-sm-size);
    color: var(--color-text-muted);
    margin: var(--space-xs) 0 var(--space-md);
    max-width: 62ch;
    line-height: 1.5;
}
.reglages-ligne {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    font-size: var(--body-sm-size);
    margin-bottom: var(--space-md);
}
.reglages-libelle {
    min-width: 160px;
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    color: var(--color-ink);
}
.reglages-ligne input[type="number"],
.reglages-outils input[type="number"] { width: 88px; }

.reglages-form { padding: 0 var(--space-lg) var(--space-lg); }

.reglages-bloc { padding-bottom: var(--space-xxs); }
/* Le titre de section : un vrai titre, pas une étiquette en capitales. */
.reglages-titre {
    font-size: var(--title-sm-size);
    font-weight: var(--weight-semibold);
    letter-spacing: 0;
    text-transform: none;
    color: var(--color-ink);
    margin: var(--space-lg) 0 var(--space-xxs);
}

.reglages-rangee { padding: var(--space-md) 0; border-bottom: var(--border-hairline-soft); }
.reglages-bloc:last-of-type .reglages-rangee:last-child { border-bottom: none; }

/* ⚠️ LE NOM ET SA COMMANDE SUR LA MÊME LIGNE. Essayé d'abord en deux colonnes
   — description à gauche, commandes à droite : sur 375px la colonne de droite
   ne tenait plus et retombait SOUS le paragraphe, alignée à gauche.
   L'interrupteur se retrouvait au milieu du texte. Ici il est toujours à
   droite du titre, à la même place sur tous les écrans. */
.reglages-tete { display: flex; align-items: center; gap: var(--space-md); min-height: 24px; }
.reglages-nom {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--body-sm-size);
    font-weight: var(--weight-semibold);
    color: var(--color-ink);
    line-height: 1.4;
}
label.reglages-nom { cursor: pointer; }
.reglages-sous {
    font-size: var(--body-sm-size);
    color: var(--color-text-muted);
    line-height: 1.5;
    margin-top: var(--space-xxs);
    /* Une mesure bornée : au-delà, l'œil perd la ligne suivante en revenant à
       gauche. */
    max-width: 62ch;
}

/* Les commandes : une seule ligne, jamais coupée. C'est la colonne qu'on
   balaie du regard pour savoir ce qui est activé. */
.reglages-outils { flex: none; display: flex; align-items: center; gap: var(--space-xs); }
/* Les rangées à plusieurs boutons prennent leur propre ligne : trois boutons
   à droite d'un titre écrasent celui-ci dès que la fenêtre rétrécit. */
.reglages-outils--large { margin-top: var(--space-sm); flex-wrap: wrap; }
.reglages-mini { font-size: var(--caption-size); color: var(--color-text-muted); white-space: nowrap; }
.reglages-note { color: var(--color-text-muted); font-size: var(--caption-size); font-weight: var(--weight-regular); }
/* Même règle que `.reglages-alerte` ci-dessous : deux marges, jamais une. */
.reglages-etat { font-size: var(--body-sm-size); color: var(--color-text-muted); margin: var(--space-xs) 0; line-height: 1.5; }
.reglages-pied { display: flex; align-items: center; gap: var(--space-sm); flex-wrap: wrap; margin: var(--space-sm) 0 0; }

/* L'explication longue, repliée. Le résumé annonce CE QU'IL CONTIENT plutôt
   qu'un « Pourquoi » générique : on doit pouvoir décider de ne pas l'ouvrir. */
.reglages-plus { margin: var(--space-xs) 0; }
.reglages-plus > summary {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--caption-size);
    color: var(--color-text-muted);
    cursor: pointer;
    user-select: none;
    list-style: none;
}
.reglages-plus > summary::-webkit-details-marker { display: none; }
.reglages-plus > summary::before { content: "▸"; font-size: 10px; }
.reglages-plus[open] > summary::before { content: "▾"; }
.reglages-plus > summary:hover { color: var(--color-ink); }
.reglages-plus p {
    font-size: var(--body-sm-size);
    color: var(--color-text-muted);
    line-height: 1.55;
    margin-top: var(--space-xs);
    max-width: 62ch;
}
.reglages-plus code, .reglages-alerte code {
    font-family: var(--font-mono);
    font-size: var(--caption-size);
    background: var(--color-surface-card);
    border-radius: var(--radius-xs);
    padding: 1px 5px;
}

/* ⚠️ LES DEUX MARGES, PAS SEULEMENT CELLE DU HAUT. Écrire `margin-top` seul est
   la faute que ce fichier a répétée : ça écarte de ce qui précède, et ça tient
   jusqu'au jour où quelque chose SUIT — l'élément suivant se retrouve alors
   collé au bandeau. C'est exactement ce qui est arrivé à l'avertissement
   « aucun serveur de courrier » posé au-dessus du premier champ du formulaire
   d'invitation.
   Les marges verticales fusionnent entre blocs voisins : en déclarer une de
   chaque côté ne double jamais l'espacement, c'est le plus grand des deux qui
   l'emporte. Il n'y a donc aucune raison de n'en écrire qu'une. */
.reglages-alerte {
    font-size: var(--body-sm-size);
    background: var(--tint-warning);
    border: 1px solid var(--line-warning);
    color: var(--ink-warning);
    border-radius: var(--radius-md);
    padding: 10px var(--space-sm);
    margin: var(--space-sm) 0;
    max-width: 62ch;
    line-height: 1.5;
}

/* L'interrupteur. `appearance: none` sur une VRAIE case à cocher : le champ
   reste un `input[type=checkbox]`, donc le formulaire, `app.js` et la page
   sans JavaScript continuent de fonctionner à l'identique. Seul le dessin
   change — un état binaire se lit d'un coup d'œil, une case cochée demande de
   regarder. Encre à l'état actif, comme toute action primaire. */
.interrupteur {
    appearance: none;
    -webkit-appearance: none;
    flex: none;
    position: relative;
    width: 42px;
    height: 24px;
    border-radius: var(--radius-pill);
    background: var(--color-surface-strong);
    cursor: pointer;
    transition: background-color var(--duration-fast) var(--ease-standard);
}
.interrupteur::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: var(--radius-full);
    background: var(--color-canvas);
    box-shadow: var(--shadow-sm);
    transition: transform var(--duration-fast) var(--ease-standard);
}
.interrupteur:checked { background: var(--color-primary); }
.interrupteur:checked::after { transform: translateX(18px); }
.interrupteur:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 2px; }
/* ⚠️ `appearance: none` efface le rendu natif — donc AUSSI en contraste forcé,
   où le système ne peut plus rien dessiner et l'interrupteur disparaîtrait. On
   redonne une bordure et un remplissage tirés de la palette système. */
@media (forced-colors: active) {
    .interrupteur { border: 1px solid ButtonText; background: ButtonFace; }
    .interrupteur::after { background: ButtonText; }
    .interrupteur:checked { background: Highlight; }
}
@media (prefers-reduced-motion: reduce) {
    .interrupteur, .interrupteur::after { transition: none; }
}

#rattrapageJauge { width: 100%; max-width: 320px; height: 6px; margin-top: var(--space-xs); accent-color: var(--color-primary); }

.reglages-actions { border-top: var(--border-hairline); padding-top: var(--space-md); margin-top: var(--space-xs); }

/* ═══════════════════ HISTORIQUE DES VENTES ═══════════════════
   Une liste et non un tableau : chaque vente porte un nom long, un numéro de
   commande de 19 caractères et une pastille d'état. En colonnes, tout finit
   tronqué — et le numéro tronqué est précisément celui qu'on venait chercher. */

/* Les onglets de période : le groupe de pastilles du design system — un rail
   gris, la pastille active en blanc avec l'ombre légère. */
.periodes {
    display: flex;
    gap: var(--space-xxs);
    flex-shrink: 0;
    padding: var(--pill-group-padding);
    background: var(--color-surface-card);
    border-radius: var(--radius-pill);
}
.periode {
    padding: var(--tab-padding-y) var(--tab-padding-x);
    border-radius: var(--radius-pill);
    font-size: var(--caption-size);
    font-weight: var(--caption-weight);
    color: var(--color-text-muted);
    text-decoration: none;
    white-space: nowrap;
    transition: var(--transition-press);
}
.periode:hover { color: var(--color-ink); text-decoration: none; }
.periode--active,
.periode--active:hover {
    background: var(--color-canvas);
    color: var(--color-ink);
    box-shadow: var(--shadow-sm);
}

.vjour-tete {
    display: flex;
    align-items: baseline;
    gap: var(--space-xs);
    padding: 10px var(--space-lg);
    /* Le gris le plus clair du système : ces bandeaux reviennent tous les
       trois ou quatre lignes, un fond plein découperait la page en tranches. */
    background: var(--color-surface-soft);
    border-bottom: var(--border-hairline-soft);
}
.vjour + .vjour .vjour-tete { border-top: var(--border-hairline-soft); }
.vjour-libelle { font-size: var(--title-sm-size); font-weight: var(--weight-semibold); color: var(--color-ink); }
.vjour-total { font-size: var(--caption-size); color: var(--color-text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* Deux mentions discrètes sur la ligne du jour, même poids visuel : l'écart
   entre les deux sources (n'apparaît QUE s'il y en a un) et l'absence de
   détail sur les journées antérieures au suivi. */
.vjour-ecart,
.vjour-note {
    font-size: var(--caption-size);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-pill);
    padding: 1px 10px;
    white-space: nowrap;
    cursor: help;
}
.vjour-note { font-style: italic; }

.vente-liste { list-style: none; }
.vente {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-sm) var(--space-lg);
    border-bottom: var(--border-hairline-soft);
}
.vente:last-child { border-bottom: none; }
.vente:hover { background: var(--color-surface-soft); }
.vente-vignette .vignette { width: 44px; height: 44px; }
.vente-corps { min-width: 0; }
.vente-article { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.vente-nom {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: var(--weight-medium);
    color: var(--color-ink);
}
.vente-qte { color: var(--color-text-muted); flex-shrink: 0; }
.vente-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px var(--space-xs);
    margin-top: 2px;
    font-size: var(--caption-size);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
}
/* L'heure est ce qui distingue deux ventes du même produit dans la journée :
   elle est en tête de la ligne, et lisible sans être criarde. */
.vente-heure { color: var(--color-ink); font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
.vente-num {
    font-size: var(--caption-size);
    color: var(--color-text-muted);
    text-decoration: none;
    border-bottom: 1px dotted var(--color-text-muted-soft);
}
.vente-num:hover { color: var(--color-ink); border-bottom-color: var(--color-ink); text-decoration: none; }
.vente-montant { font-weight: var(--weight-semibold); color: var(--color-ink); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* ⚠️ ESTIMÉ, PAS ENCAISSÉ — et ça doit se voir SANS survoler l'infobulle.
   Amazon ne donne aucun montant tant que le paiement n'est pas vérifié ; on
   affiche alors le prix de l'annonce, qui donne l'ordre de grandeur. Il perd
   la graisse et le noir des montants réels : dans une colonne de chiffres
   alignés, c'est le contraste qui dit lequel fait foi. Le « ≈ » du gabarit
   reste lisible en niveaux de gris, et donc pour qui distingue mal les
   couleurs. */
.vente-montant--estime { font-weight: var(--weight-regular); color: var(--color-text-muted); }
/* Une commande annulée reste visible : elle a déclenché une notification, et
   ne plus la trouver ferait douter de sa propre mémoire. Elle est simplement
   barrée — c'est ce qu'annonce aussi sa pastille. */
.vente--annulee { opacity: 0.6; }
.vente--annulee .vente-nom,
.vente--annulee .vente-montant { text-decoration: line-through; }
/* La vente désignée par la notification (/ventes?c=…) : le fond la retrouve
   d'un coup d'œil, sans le rouge d'une alerte — ce n'est pas un problème. */
.vente--ciblee { background: var(--color-surface-card); }
.ventes-pied {
    padding: var(--space-md) var(--space-lg);
    border-top: var(--border-hairline);
    font-size: var(--caption-size);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    line-height: 1.5;
}
/* Ce que fait la boucle de fond, dit à voix haute : sans ça, on ne sait pas
   si l'écran est à jour ou simplement figé. */
.ventes-entretien { margin-top: var(--space-xs); padding-top: var(--space-xs); border-top: var(--border-hairline-soft); }
.ventes-entretien.sync-err { color: var(--color-error); }

/* Le bouton qui tourne, collé au titre du panneau. Discret au repos : c'est
   un secours, l'entretien se fait tout seul. */
.ventes-refresh {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: 1px solid transparent;
    border-radius: var(--radius-full);
    background: none;
    color: var(--color-text-muted);
    cursor: pointer;
    transition: var(--transition-press);
}
.ventes-refresh:hover { background: var(--color-surface-soft); color: var(--color-ink); }
.ventes-refresh[disabled] { cursor: default; opacity: 0.6; }
/* Mêmes images-clés que la barre du téléphone : une seule définition pour
   tout ce qui tourne dans l'interface. */
.ventes-refresh.tourne svg { animation: mobilebar-tourne 1s linear infinite; }

/* ═══════════════════ NOTIFICATIONS ═══════════════════
   ⚠️ UN SEUL ENDROIT POUR TOUTES LES NOTIFICATIONS, présentes et à venir.
   Avant, chaque écran inventait la sienne : un bandeau inséré en haut du
   contenu ici, un `<span>` gris dans un en-tête de panneau là, un texte sous
   un bouton ailleurs. Trois formes pour la même chose, dont deux passaient
   inaperçues — et un bandeau inséré dans le flux DÉCALE la page au moment
   précis où l'on vient de cliquer quelque part.

   Ici, c'est une couche fixe : elle ne déplace rien, elle est vue au même
   endroit quoi qu'on fasse, et une nouvelle vue n'a rien à concevoir —
   `messages.success(request, "…")` côté serveur, `arvenToast("…")` côté
   navigateur. */
.toasts {
    position: fixed;
    /* Au-dessus de la barre de navigation du téléphone, qui est à 40. */
    z-index: 60;
    right: var(--space-lg);
    /* ⚠️ EN HAUT, SOUS LA BARRE — et non en bas à droite, où la première
       version les avait mis. Sur un écran large, ce coin est à cinquante
       centimètres du bouton qu'on vient de cliquer : le message s'affichait
       vraiment, et personne ne le voyait. */
    top: calc(var(--topbar-height) + var(--space-md));
    width: min(380px, calc(100vw - 40px));
    display: flex;
    /* `column-reverse` avec un ancrage EN HAUT : le dernier arrivé se place
       juste sous la barre, toujours au même pixel, et les précédents
       descendent. Sans ça, un nouveau message apparaîtrait plus bas à chaque
       fois — donc jamais là où on l'attend. */
    flex-direction: column-reverse;
    gap: var(--space-xs);
    /* ⚠️ La couche ne doit JAMAIS intercepter un clic destiné à la page —
       chaque toast le réactive pour lui-même. */
    pointer-events: none;
}
.toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: var(--space-sm) 10px var(--space-sm) var(--space-md);
    border-radius: var(--radius-lg);
    border: var(--border-hairline);
    background: var(--color-canvas);
    box-shadow: var(--shadow-md);
    font-size: var(--body-sm-size);
    line-height: 1.45;
    color: var(--color-ink);
    animation: toast-entree var(--duration-base) var(--ease-standard);
}
/* Entrée par le haut, puisqu'ils arrivent du haut. */
@keyframes toast-entree { from { opacity: 0; transform: translateY(-8px); } }
.toast--sortant { animation: toast-sortie var(--duration-base) var(--ease-standard) forwards; }
@keyframes toast-sortie { to { opacity: 0; transform: translateY(-6px); } }

/* Les tags de niveau viennent de `django.contrib.messages` : les reprendre
   tels quels évite une table de correspondance entre le serveur et la
   feuille. */
.toast--success { background: var(--tint-success); border-color: var(--line-success); color: var(--ink-success); }
.toast--error   { background: var(--tint-error);   border-color: var(--line-error);   color: var(--ink-error); }
.toast--warning { background: var(--tint-warning); border-color: var(--line-warning); color: var(--ink-warning); }

.toast-texte { flex: 1; min-width: 0; }
.toast-fermer {
    flex: none;
    width: 24px;
    height: 24px;
    margin: -2px 0 0;
    padding: 0;
    border: none;
    background: none;
    color: inherit;
    opacity: 0.5;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    border-radius: var(--radius-full);
}
.toast-fermer:hover { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
    .toast, .toast--sortant { animation: none; }
}

/* ═══════════════════ PAGES D'ERREUR ═══════════════════
   400, 404, 405, 500. Elles ne chargent QUE cette feuille et `tokens.css` — ni
   `comptes.css`, ni `app.js`, ni la coquille de l'application. Une page
   d'erreur doit pouvoir s'afficher quand le reste est cassé : moins elle dépend
   de choses, mieux elle tient.

   ⚠️ `height: 100vh; overflow: auto` ET NON une hauteur automatique : `body`
   porte `overflow: hidden` pour la coquille de l'application, et une page
   d'erreur un peu haute deviendrait sinon impossible à faire défiler sur un
   téléphone — le bouton de sortie hors d'atteinte. */
.erreur-page {
    height: 100vh;
    overflow: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-lg);
    background: var(--color-surface-soft);
}

.erreur-carte {
    width: 100%;
    max-width: 480px;
    padding: var(--space-xl);
    background: var(--color-canvas);
    border: var(--border-hairline);
    border-radius: var(--radius-lg);
    text-align: center;
}

/* La marque seule, pas le lockup : à cette taille le nom serait illisible, et
   l'adresse dans la barre dit déjà de qui vient la page. */
.erreur-marque {
    width: 44px;
    height: auto;
    margin: 0 auto var(--space-lg);
    display: block;
    opacity: 0.85;
}

.erreur-titre {
    margin: 0 0 var(--space-sm);
    font-size: var(--display-sm-size);
    font-weight: var(--display-sm-weight);
    line-height: var(--display-sm-leading);
    letter-spacing: var(--display-sm-tracking);
    color: var(--color-ink);
}

.erreur-texte {
    margin: 0;
    font-size: var(--body-sm-size);
    line-height: 1.55;
    color: var(--color-text-muted);
}
.erreur-texte--second { margin-top: var(--space-sm); }

.erreur-action { margin-top: var(--space-lg); width: 100%; }

/* Le code en dernier et en petit : inutile à qui lit la page, indispensable à
   qui vient dire « j'ai eu une erreur » sans savoir laquelle. */
.erreur-code {
    margin: var(--space-md) 0 0;
    font-family: var(--font-mono);
    font-size: var(--caption-size);
    color: var(--color-text-muted-soft);
}

@media (max-width: 480px) {
    .erreur-carte { padding: var(--space-lg); }
    .erreur-titre { font-size: var(--title-lg-size); letter-spacing: var(--title-lg-tracking); }
}

/* ═══════════════════ FENÊTRE MODALE ═══════════════════ */
.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-lg);
    /* Le voile est tiré de la surface sombre du système, pas du navy. */
    background: color-mix(in srgb, var(--color-surface-dark) 55%, transparent);
}
.modal-overlay[hidden] { display: none; }

.modal {
    width: 100%;
    max-width: 400px;
    padding: var(--card-padding-compact);
    background: var(--color-canvas);
    border-radius: var(--radius-xl);
    box-shadow: var(--shadow-md);
}

/* La fiche produit porte la liste des achats : elle déborde de la hauteur de
   l'écran dès trois commandes. C'est la modale qui défile, pas la page
   derrière — sinon on perd le bouton « Enregistrer » sans savoir où. */
.modal--large {
    max-width: 560px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
}

.modal-title {
    margin: 0;
    font-size: var(--title-lg-size);
    font-weight: var(--title-lg-weight);
    line-height: var(--title-lg-leading);
    letter-spacing: var(--title-lg-tracking);
    color: var(--color-ink);
}
.modal-sub { margin: 6px 0 var(--space-lg); font-size: var(--body-sm-size); color: var(--color-text-muted); }

.modal-actions { display: flex; align-items: center; gap: var(--space-xs); margin-top: var(--space-lg); flex-wrap: wrap; }
.modal-spacer { flex: 1; }
.modal-actions--serrees { margin-top: var(--space-sm); }
.modal-btn--large { width: 100%; }

/* Mêmes boutons que partout ailleurs — le système n'en a que trois, et une
   modale n'est pas une raison d'en inventer un quatrième.

   ⚠️ CETTE RÈGLE RECOPIE `.btn` LIGNE POUR LIGNE, et c'est un doublon assumé
   qu'on garde faute de mieux : `.modal-btn--large` (pleine largeur) et
   `.modal-spacer` sont propres aux modales, et fusionner les deux familles
   demanderait de reprendre les quatre modales du projet. Ce qui compte ici,
   c'est qu'elle SUIVE `.btn` au pixel — d'où le même jeton de hauteur. */
.modal-btn {
    height: var(--button-height);
    padding: 0 14px;
    border: 1px solid var(--color-hairline);
    border-radius: var(--radius-sm);
    background: var(--color-canvas);
    color: var(--color-ink);
    font-family: var(--font-sans);
    font-size: var(--caption-size);
    font-weight: var(--button-weight);
    line-height: var(--button-leading);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-xs);
    cursor: pointer;
    transition: var(--transition-press);
}
.modal-btn:active:not(:disabled) { background: var(--color-surface-soft); }
.modal-btn:disabled { background: transparent; color: var(--color-text-muted); cursor: not-allowed; }

.modal-btn--primary { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-on-primary); }
.modal-btn--primary:active:not(:disabled) { background: var(--color-primary-active); border-color: var(--color-primary-active); }
.modal-btn--primary:disabled { background: var(--color-primary-disabled); border-color: var(--color-primary-disabled); color: var(--color-text-muted); }

/* La suppression est le SEUL endroit où le système laisse une couleur toucher
   un bouton, et encore : seulement le texte, jamais le fond. Un bouton rouge
   plein attire l'œil vers l'action qu'on veut justement rendre délibérée. */
.modal-btn--danger { color: var(--color-error); }
.modal-btn--danger:active:not(:disabled) { background: var(--tint-error); }

/* ─────────────────── Cadrage de la photo de profil ─────────────────── */
.avatar-editor { display: flex; flex-direction: column; align-items: center; gap: var(--space-sm); }

.avatar-stage {
    position: relative;
    width: 256px;
    height: 256px;
    max-width: 100%;
    overflow: hidden;
    border-radius: var(--radius-lg);
    background: var(--color-surface-card);
    cursor: grab;
    /* Le glissement sert à recadrer : on empêche le navigateur de
       l'interpréter comme un défilement de page sur écran tactile. */
    touch-action: none;
}
.avatar-stage.is-dragging { cursor: grabbing; }
.avatar-stage canvas { display: block; width: 100%; height: 100%; }

/* Voile percé d'un disque : montre exactement le cadrage final, la pastille
   étant ronde. L'ombre démesurée assombrit tout ce qui est hors du cercle. */
.avatar-stage-mask {
    position: absolute;
    inset: 0;
    pointer-events: none;
    border-radius: var(--radius-full);
    box-shadow: 0 0 0 9999px rgba(255, 255, 255, 0.76);
}

.avatar-hint { margin: 0; font-size: var(--caption-size); color: var(--color-text-muted); text-align: center; }
.avatar-zoom { display: flex; align-items: center; gap: 10px; width: 100%; font-size: var(--caption-size); color: var(--color-text-muted); }
.avatar-zoom input { flex: 1; accent-color: var(--color-primary); }

/* ═══════════════════ BARRE DE NAVIGATION DU TÉLÉPHONE ═══════════════════
   Placée en FIN de fichier volontairement : le bloc responsive du milieu est
   suivi de plusieurs centaines de lignes de règles de base, et à spécificité
   égale c'est la dernière déclarée qui gagne — le padding du contenu était
   silencieusement écrasé.

   Sous 800px, la barre latérale disparaît. Sans ce remplacement, les réglages
   sont inatteignables depuis l'app posée sur l'écran d'accueil, qui n'a pas
   de barre d'adresse. En bas plutôt qu'en haut : c'est là qu'arrive le pouce. */
.mobilebar { display: none; }
.tri-mobile { display: none; }
.switcher-mobile { display: none; }

/* ─────────────────────────── Adaptatif ─────────────────────────── */
/* ⚠️ LA COLONNE DU NOM REND DE LA PLACE AVANT QUE LE TABLEAU NE DÉBORDE.
   Onze colonnes réclament ~1070px ; un portable de 1280 n'en offre que 988 au
   panneau, et c'est « État » — la colonne qu'on vient lire le matin — qui se
   retrouvait coupée derrière une barre de défilement horizontale.

   Le nom est le seul poste compressible : il est déjà tronqué à l'ellipse, et
   ce sont la vignette et le SKU qui identifient la ligne. Il cède donc en
   premier, et seulement quand la fenêtre l'exige. */
@media (max-width: 1400px) {
    .stock-table .pname { max-width: 210px; }
}

@media (max-width: 1180px) {
    :root { --list-width: 380px; }
    .topbar-search { width: 200px; }
}

@media (max-width: 980px) {
    :root { --list-width: 320px; }
    .topbar-search { width: 150px; }
}

@media (max-width: 800px) {
    .sidebar { display: none; }
    .workspace { flex-direction: column; }
    .list-panel { width: 100%; height: 45%; border-right: none; border-bottom: var(--border-hairline); }
    .viewer-panel { flex: 1; }
    .topbar-search { display: none; }

    .topbar-avatar { display: flex; }
    .menu-item--mobile { display: flex; }
    .menu-filet.menu-item--mobile { display: block; }

    /* La barre passe en blanc elle aussi : elle prolonge la coquille plutôt
       que d'y coller une bande sombre isolée, seule survivante du navy. */
    .mobilebar {
        display: flex;
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 40;
        background: var(--color-canvas);
        border-top: var(--border-hairline);
        /* Apple prévoit 49 pt de contenu + 34 pt de zone de sécurité = 83 pt.
           On reste EN DESSOUS, volontairement : dans une app native cette
           bande de 34 pt est translucide et le contenu défile dessous. */
        padding-bottom: max(env(safe-area-inset-bottom, 0px) - 20px, 6px);
        padding-top: 2px;
    }
    .mobilebar-item {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 3px;
        /* 44px = exactement la cible tactile minimale d'Apple. */
        min-height: 44px;
        padding: 4px 2px;
        border: none;
        background: none;
        color: var(--color-text-muted);
        font-family: inherit;
        font-size: 10px;
        /* ⚠️ À GARDER MÊME MAINTENANT QUE LA BARRE N'EST QUE DES LIENS. Elle a
           porté un `<button>` — « Synchroniser » — et il était désaligné des
           quatre autres : un bouton ne reçoit pas la hauteur de ligne du
           document, le navigateur lui pose `normal`, soit 12 px là où les liens
           en font 13,5, et son icône descendait de 0,8 px. Assez peu pour ne pas
           se nommer, assez pour se voir en bout de rangée. La ligne ne coûte
           rien et rattrape d'avance le prochain bouton qui passera par ici.
           Même raison que le `font-family: inherit` juste au-dessus. */
        line-height: inherit;
        font-weight: var(--weight-medium);
        text-decoration: none;
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }
    /* ⚠️ SANS `nowrap`, UN MOT TROP LONG SE REPLIE SUR DEUX LIGNES et fait
       grandir TOUTE la barre — pas seulement sa colonne. « Synchroniser » et ses
       52 px à 350 px de large tenaient le record, pour une colonne de 70 ;
       « Réglages », qui a pris sa place, est plus court. La marge est donc plus
       confortable qu'avant, mais c'est toujours le mot le plus long qui la
       mangera le jour où une entrée s'ajoutera. */
    .mobilebar-item span { white-space: nowrap; }
    .mobilebar-item--actif { color: var(--color-ink); }
    .mobilebar-item:active { background: var(--color-surface-soft); }

    /* Le contenu doit pouvoir défiler AU-DESSUS de la barre, sinon la
       dernière ligne du tableau reste cachée dessous.

       Par un espaceur et NON par `padding-bottom` : Safari n'honore pas
       toujours la marge basse d'un conteneur défilant en fin de course, et le
       bas du tableau restait inaccessible sous les onglets. */
    .stock-main { padding: var(--space-md) var(--space-sm) 0; }
    .stock-main::after {
        content: '';
        display: block;
        height: calc(96px + env(safe-area-inset-bottom, 0px));
        flex-shrink: 0;
    }

    /* Contrepartie de `viewport-fit=cover` : le contenu remonte sous la barre
       d'état. Le fond blanc de l'en-tête s'étend jusqu'en haut de l'écran —
       ce qui est l'aspect natif attendu — mais son contenu redescend. */
    .topbar {
        height: calc(var(--topbar-height) + env(safe-area-inset-top, 0px));
        padding-top: env(safe-area-inset-top, 0px);
        padding-left: var(--space-md);
        padding-right: var(--space-md);
    }

    /* Sélecteur d'outils du téléphone : c'est la barre latérale qui le porte
       sur grand écran, et elle a disparu. */
    .switcher-mobile { display: block; position: relative; }
    .switcher-mobile-trigger {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: var(--icon-button-size);
        height: var(--icon-button-size);
        border: var(--border-hairline);
        border-radius: var(--radius-full);
        background: var(--color-canvas);
        color: var(--color-ink);
        cursor: pointer;
        -webkit-tap-highlight-color: transparent;
    }
    .switcher-mobile-trigger:active { background: var(--color-surface-soft); }
    /* Le menu est déplacé ici : il doit s'ouvrir sous le bouton, aligné à
       droite, et ne jamais dépasser de l'écran sur un téléphone étroit. */
    .switcher-mobile .app-switcher-menu {
        left: auto;
        right: 0;
        top: calc(100% + 6px);
        width: max-content;
        max-width: calc(100vw - 24px);
    }

    /* Pleine largeur, et toujours en haut : en bas, le message se glisserait
       derrière la barre de navigation, à l'endroit exact où le pouce clique. */
    .toasts { left: var(--space-sm); right: var(--space-sm); width: auto; }

    .vente, .vjour-tete, .ventes-pied { padding-left: var(--space-sm); padding-right: var(--space-sm); }
    /* Le nom sur deux lignes plutôt que tronqué net : « POUXIT - Flash -
       Lotion Spray A… » ne dit pas de quel format il s'agit, et c'est
       justement ce qui distingue deux ventes voisines dans la liste. */
    .vente-nom {
        white-space: normal;
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        overflow: hidden;
        line-height: 1.3;
    }

    /* Les tuiles reviennent à la densité d'un téléphone. À 24px de rembourrage
       — la valeur « carte » du système — les cinq tuiles occupaient 451px avant
       la première ligne de stock, soit plus de la moitié d'un écran d'iPhone
       pour cinq chiffres qu'on lit en une seconde. */
    .kpi-row { gap: var(--space-sm); margin-bottom: var(--space-md); }
    .kpi-tile { padding: var(--space-md); }
    .kpi-value { font-size: var(--title-lg-size); letter-spacing: var(--title-lg-tracking); margin-top: 2px; }

    .stock-panel-head { flex-wrap: wrap; row-gap: var(--space-xs); padding: var(--space-sm) var(--space-md); }

    /* Prend sa propre ligne, sur toute la largeur : c'est un contrôle qu'on
       cherche, pas une décoration à caser dans un coin. */
    .tri-mobile {
        display: flex;
        align-items: center;
        gap: var(--space-xs);
        flex: 1 1 100%;
        font-size: var(--caption-size);
        color: var(--color-text-muted);
    }
    .tri-mobile-libelle { font-weight: var(--caption-weight); text-transform: none; letter-spacing: 0; }
    .tri-mobile select { flex: 1; min-width: 0; }

    /* ─────── Le tableau devient une pile de cartes ───────
       Dix colonnes ne rentrent pas dans 393px, et aucune n'est superflue : le
       tableau débordait donc horizontalement, on ne voyait que « Produit » et
       il fallait faire défiler de côté pour atteindre l'état — soit exactement
       ce qu'on vient chercher le matin.

       Empilé en cartes, mais SANS toucher au DOM : un <tr> reste un <tr>,
       seul son `display` change. La recherche masque des <tr>, le tri les
       réordonne, le bouton œil en supprime un — les trois continuent de
       fonctionner sans une ligne de JavaScript en plus. */
    .stock-table-wrap { overflow-x: visible; }

    .stock-table--principal,
    .stock-table--principal tbody,
    .stock-table--principal td { display: block; }
    .stock-table--principal thead { display: none; }

    /* La grille de la carte : quatre colonnes égales, une par chiffre.
       La vignette et le bouton œil en sont SORTIS, positionnés en absolu dans
       les marges — tant qu'ils occupaient une piste chacun, ils confisquaient
       78px sur toute la hauteur de la carte. */
    .stock-table--principal tbody tr {
        position: relative;
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        align-items: center;
        column-gap: var(--space-xs);
        padding: var(--space-md) var(--space-sm);
        border-bottom: var(--border-hairline-soft);
    }
    .stock-table--principal tbody tr:last-child { border-bottom: none; }
    .stock-table--principal td { padding: 0; border-bottom: none; }

    /* `td` explicite pour repasser devant `.stock-table td.col-vignette`, qui
       impose `width: 1%` : sans lui la case tombe à 0,4px et l'image déborde
       de sa propre cellule. */
    .stock-table--principal td.col-vignette { position: absolute; left: var(--space-sm); top: var(--space-md); width: auto; }
    .stock-table--principal .vignette { width: 44px; height: 44px; }

    /* 56 = 44 de vignette + 12 de respiration. Les trois éléments qui longent
       l'image la contournent ; tout le reste de la carte part du bord. */
    .stock-table--principal .pname,
    .stock-table--principal .sku,
    .stock-table--principal .col-etat { padding-left: 56px; }

    .stock-table--principal .pname {
        grid-column: 1 / -1;
        grid-row: 1;
        padding-right: 30px;   /* la place du bouton œil, lui aussi hors flux */
        max-width: none;
        white-space: normal;
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        line-clamp: 2;
        overflow: hidden;
        font-weight: var(--weight-semibold);
        line-height: 1.3;
    }
    .stock-table--principal .sku { grid-column: 1 / -1; grid-row: 2; margin-top: 2px; }

    .stock-table--principal .col-etat { grid-column: 1 / 3; grid-row: 3; margin-top: 10px; }
    .stock-table--principal .col-couverture { grid-column: 3 / -1; grid-row: 3; margin-top: 10px; text-align: right; }

    /* Le même cycle que sur grand écran : arrive → en stock → part → parti. */
    .stock-table--principal .col-inbound { grid-column: 1; grid-row: 4; }
    .stock-table--principal .col-dispo   { grid-column: 2; grid-row: 4; }
    .stock-table--principal .col-reserve { grid-column: 3; grid-row: 4; }
    .stock-table--principal .col-ventes  { grid-column: 4; grid-row: 4; }

    /* Le point de commande prend TOUTE la largeur, sur sa propre ligne :
       c'est la seule information de la carte qui demande un geste
       aujourd'hui. Masqué quand il n'y a rien à dire — une ligne « — » pleine
       largeur sur chaque carte serait du bruit. */
    .stock-table--principal .col-commande { grid-column: 1 / -1; grid-row: 5; text-align: left; }
    .stock-table--principal .col-commande:not(.col-commande--urgent) { display: none; }

    /* Les chiffres reprennent l'intitulé de leur colonne : sans en-tête de
       tableau, « 12 · 2 · 40 · 18 » ne veut plus rien dire. */
    .stock-table--principal tbody td[data-label] {
        text-align: left;
        margin-top: var(--space-sm);
        font-weight: var(--weight-semibold);
        color: var(--color-ink);
    }
    /* ⚠️ RÉPÉTÉ ICI, ET CE N'EST PAS UNE REDONDANCE. La règle juste au-dessus
       pose `color` et `font-weight` avec une spécificité de (0,2,2) — plus forte
       que le `.stock-table td.num--muet` de la version bureau, et plus loin dans
       la feuille. Sans ces deux lignes, le téléphone rendait TOUS les chiffres
       en encre pleine et demi-gras : ni zéro éteint, ni rupture rouge, c'est-à-
       dire exactement ce que ce travail cherchait à corriger, annulé sur la
       moitié des écrans. Le sélecteur reprend donc `[data-label]` pour passer
       devant. */
    .stock-table--principal tbody td[data-label].num--muet {
        color: var(--color-text-muted);
        font-weight: var(--weight-regular);
    }
    .stock-table--principal tbody td[data-label].num--rupture { color: var(--color-error); }
    .stock-table--principal tbody td[data-label]::before {
        content: attr(data-label);
        display: block;
        font-size: 11px;
        font-weight: var(--weight-medium);
        letter-spacing: 0;
        text-transform: none;
        color: var(--color-text-muted);
        /* « Ventes 30 j » tombe au pixel près sur la largeur de sa colonne :
           sans ceci il se replie dès que le rendu de la police varie d'un
           cheveu. Il empiète alors sur la marge droite, qui est vide. */
        white-space: nowrap;
    }
    /* La couverture partage sa ligne avec la pastille d'état : son intitulé
       passe devant la valeur au lieu de la surmonter. `td[data-label]` est
       répété À DESSEIN — à spécificité inférieure ces déclarations étaient
       purement et simplement ignorées. */
    .stock-table--principal tbody td[data-label].col-couverture { text-align: right; margin-top: 10px; }
    .stock-table--principal tbody td[data-label].col-couverture::before { display: inline; margin-right: 6px; }

    /* Le bouton œil, deuxième élément hors flux : épinglé en haut à droite de
       la carte, il ne consomme plus de piste. */
    .stock-table--principal .col-action {
        position: absolute;
        top: var(--space-xs);
        right: 6px;
        display: flex;
        flex-direction: column;
        gap: 2px;
    }
    .stock-table--principal .row-hide { padding: var(--space-xs); }

    /* Le libellé « À commander » est déjà porté par la valeur elle-même
       (« en retard de 12 j ») : le répéter au-dessus ferait doublon. */
    .stock-table--principal tbody td[data-label].col-commande::before { content: none; }
    .stock-table--principal tbody td[data-label].col-commande { margin-top: 10px; }

    /* Modale de fiche : les libellés en colonne fixe serrent trop les champs. */
    .fiche-libelle { min-width: 0; flex: 1 1 100%; }
    .fiche-note { padding-left: 0; }

    /* Liste des SKU masqués : mêmes cartes, en plus petit et sans les
       chiffres. On l'ouvre pour réafficher un SKU, pas pour l'analyser. */
    .stock-table--hidden,
    .stock-table--hidden tbody,
    .stock-table--hidden td { display: block; }
    .stock-table--hidden tbody tr {
        display: grid;
        grid-template-columns: 32px minmax(0, 1fr) auto auto;
        align-items: center;
        column-gap: var(--space-xs);
        padding: var(--space-xs) 0;
    }
    .stock-table--hidden td { padding: 0; border-bottom: none; }
    .stock-table--hidden td.col-vignette { grid-column: 1; grid-row: 1 / 3; width: auto; }
    .stock-table--hidden .vignette { width: 32px; height: 32px; }
    .stock-table--hidden .pname { grid-column: 2; grid-row: 1; max-width: none; }
    .stock-table--hidden .sku { grid-column: 2; grid-row: 2; }
    .stock-table--hidden .col-dispo { grid-column: 3; grid-row: 1 / 3; white-space: nowrap; font-size: var(--caption-size); }
    .stock-table--hidden .col-action { grid-column: 4; grid-row: 1 / 3; }

    /* ─────────── Les comptes en cartes ───────────
       ⚠️ CE TABLEAU N'AVAIT PAS DE VERSION TÉLÉPHONE, et le repli commun ne
       pouvait pas le sauver : `.stock-table-wrap` passe en `overflow-x: visible`
       quelques lignes plus haut, parce que les tableaux de stock deviennent des
       cartes ici et n'ont plus rien à faire glisser. Celui des comptes, lui,
       restait une vraie table de 531 px dans un cadre de 354 — donc débordante,
       ET privée du défilement qui aurait permis d'aller voir le reste. Son
       bouton `⋮` tombait à 110 px hors de l'écran.

       La carte : le nom sur la première ligne avec les actions à sa droite, le
       rôle et l'état sur la seconde. */
    .stock-table--comptes,
    .stock-table--comptes tbody,
    .stock-table--comptes td { display: block; }
    .stock-table--comptes thead { display: none; }

    /* ⚠️ QUATRE COLONNES DONT UNE VIDE, et c'est elle qui fait le travail. Avec
       trois colonnes, le rôle occupait la première et l'état la deuxième : la
       largeur du rôle décidait donc de l'abscisse de l'état, qui sautait de 95
       à 133 px d'une carte à l'autre selon qu'on lise « Membre » ou
       « Administrateur ». Deux colonnes ragged au lieu d'une file de pastilles.

       La troisième colonne, en `1fr` et sans contenu, absorbe le mou : les deux
       pastilles se suivent à écart constant, et les actions restent collées à
       droite. */
    /* ⚠️ LE REMBOURRAGE HORIZONTAL N'EST PAS FACULTATIF : les cellules passent à
       `padding: 0` juste en dessous, donc plus aucune gouttière ne vient d'elles.
       Sans lui, le contenu des cartes court d'un bord à l'autre du panneau —
       « c'est toi » finissait à 1 px du filet, ce qui se lit comme du texte
       coupé même quand rien ne dépasse.

       `--space-md` et non `--space-sm` : c'est le rembourrage que
       `.stock-panel-head` prend sous 800 px. Le nom d'un compte et le titre
       « Utilisateurs » partagent ainsi le même bord gauche. */
    .stock-table--comptes tbody tr {
        display: grid;
        grid-template-columns: auto auto minmax(0, 1fr) auto;
        align-items: center;
        column-gap: var(--space-sm);
        row-gap: 8px;
        padding: var(--space-sm) var(--space-md);
        border-bottom: var(--border-hairline-soft);
    }
    .stock-table--comptes tbody tr:last-child { border-bottom: none; }
    .stock-table--comptes td { padding: 0; border-bottom: none; }

    .stock-table--comptes .col-personne { grid-column: 1 / 4; grid-row: 1; min-width: 0; }
    /* ⚠️ LE RETRAIT ALIGNE LA SECONDE LIGNE SOUS LE NOM, pas sous l'avatar. Le
       nom commence après la photo ; sans ce décalage, les pastilles repartaient
       du bord gauche de la carte et pendaient 48 px à gauche du bloc de texte
       auquel elles se rapportent. Calculé sur les mêmes tokens que
       `.gestion-personne` — sa photo et son écart — pour qu'un changement de
       taille d'avatar entraîne l'alignement avec lui. */
    .stock-table--comptes .col-role {
        grid-column: 1;
        grid-row: 2;
        padding-left: calc(var(--avatar-size) + var(--space-sm));
    }
    .stock-table--comptes .col-etat { grid-column: 2; grid-row: 2; }
    /* Les actions tiennent la hauteur des deux lignes et se calent en haut :
       le `⋮` doit être au niveau du nom, pas flotter au milieu de la carte. */
    .stock-table--comptes .gestion-actions {
        grid-column: 4;
        grid-row: 1 / 3;
        align-self: start;
        text-align: right;
    }
    /* ⚠️ COUPÉ À L'ELLIPSE PLUTÔT QUE REPLIÉ. Une adresse longue passait à la
       ligne et faisait une carte de 118 px au milieu de cartes de 99 : la liste
       se lit d'un coup d'œil, des hauteurs inégales la hachent. L'identité est
       en tête de la ligne — c'est la fin de l'adresse qu'on peut perdre. */
    .stock-table--comptes .gestion-nom,
    .stock-table--comptes .gestion-meta {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    /* Fiches de `comptes/` : le libellé en colonne étroite serre trop le
       champ à côté, et la note explicative doit passer à la ligne suivante —
       sans quoi elle comprime le libellé jusqu'à le couper en deux. */
    .reglages-libelle { min-width: 0; white-space: nowrap; }
    .reglages-ligne { flex-wrap: wrap; }
    .reglages-ligne .reglages-note { flex: 1 1 100%; }

    .reglages-form { padding: 0 var(--space-md) var(--space-lg); }
    .reglages-rangee { padding: var(--space-md) 0; }
    /* Le titre peut passer sur deux lignes ; la commande, elle, reste calée
       en haut à droite plutôt que centrée sur un titre devenu haut. */
    .reglages-tete { align-items: flex-start; gap: var(--space-sm); }
    .reglages-outils { padding-top: 1px; }

    /* Une cible ratée au doigt ne produit RIEN de visible — on croit avoir
       coché. Apple demande 44px : le `::before` étend la zone sensible
       au-delà du dessin, et le titre reste cliquable puisqu'il lui est
       associé par `for`. */
    .interrupteur { width: 52px; height: 30px; -webkit-tap-highlight-color: transparent; }
    .interrupteur::after { width: 26px; height: 26px; }
    .interrupteur:checked::after { transform: translateX(22px); }
    .interrupteur::before { content: ""; position: absolute; inset: -7px -4px; }
}

/* iPhone SE / mini : à cinq entrées, chacune ne dispose plus que d'une
   soixantaine de pixels. Un point de moins suffit à faire tenir le plus long
   des libellés sans le tronquer. */
@media (max-width: 380px) {
    .mobilebar-item { font-size: 9px; }
}

@keyframes mobilebar-tourne { to { transform: rotate(360deg); } }

/* Très petits écrans — iPhone SE, et surtout n'importe quel iPhone dont on a
   activé le zoom d'affichage : il annonce alors 320px au lieu de 393.
   En dessous de ~322px, quatre colonnes de chiffres ne rentrent plus, même la
   vignette et le bouton sortis du flux. On passe en 2 × 2. */
@media (max-width: 350px) {
    /* Deux par deux, mais le cycle est conservé : on lit le haut avant le bas
       comme on lisait la gauche avant la droite. */
    .stock-table--principal .col-inbound { grid-column: 1 / 3;  grid-row: 4; }
    .stock-table--principal .col-dispo   { grid-column: 3 / -1; grid-row: 4; }
    .stock-table--principal .col-reserve { grid-column: 1 / 3;  grid-row: 5; }
    .stock-table--principal .col-ventes  { grid-column: 3 / -1; grid-row: 5; }
}

/* La grille d'achat à trois colonnes ne tient pas dans une modale de 345px :
   un champ de date réclame ~110px à lui seul, et Safari refuse de le
   comprimer. Deux colonnes, et les champs de texte prennent la largeur. */
@media (max-width: 560px) {
    .achat-grille { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .achat-champ--large { grid-column: span 2; }
    /* Les boutons du formulaire d'achat sont trois : à cette largeur ils se
       chevauchent, « Supprimer » finit sous « Enregistrer ». */
    .achat-form .modal-actions { flex-wrap: wrap; gap: 6px; }
    .achat-form .modal-spacer { display: none; }
    .achat-form .modal-btn { flex: 1 1 auto; }
}
