/* =============================================================
   Composants — design system Stock cabinet dentaire
   -------------------------------------------------------------
   Charge _tokens.css et _base.css en amont.
   Chaque composant suit la même convention :
     - bloc .name
     - variantes .name--variant (modifier BEM)
     - éléments .name__part (BEM)
   Aucune valeur en dur : tous les styles dérivent des tokens.
   ============================================================= */


/* =============================================================
   .btn — bouton
   -------------------------------------------------------------
   API :
     <button class="btn">…</button>             (primary par défaut)
     <button class="btn btn--secondary">…</button>
     <button class="btn btn--ghost">…</button>
     <button class="btn btn--danger">…</button>
   Tailles :
     .btn--sm    (compact, pour barres d'outils de tableau)
     .btn        (par défaut)
     .btn--lg    (CTA principal d'un flux scan)
   États :
     :hover / :focus-visible / :disabled
   Notes :
     - radius-md (6px) — pas de pill.
     - poids 500 (medium), pas 700/800.
     - icône optionnelle : <svg width="16" height="16">… (16px stroke-1.5).
   ============================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: var(--brand-900);
  color: var(--neutral-0);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.12s ease, border-color 0.12s ease,
              color 0.12s ease, box-shadow 0.12s ease;
  cursor: pointer;
}

.btn:hover {
  background: var(--brand-800);
  text-decoration: none;
}

.btn:active {
  background: var(--brand-700);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.btn--secondary {
  background: var(--neutral-0);
  border-color: var(--neutral-300);
  color: var(--neutral-900);
}
.btn--secondary:hover {
  background: var(--neutral-50);
  border-color: var(--neutral-500);
}
.btn--secondary:active {
  background: var(--neutral-100);
}

.btn--ghost {
  background: transparent;
  color: var(--brand-800);
}
.btn--ghost:hover {
  background: var(--brand-50);
}
.btn--ghost:active {
  background: var(--brand-100);
}

.btn--danger {
  background: var(--danger);
  color: var(--neutral-0);
}
.btn--danger:hover {
  background: #8e2a2a;
}

.btn--sm {
  min-height: 32px;
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
}

.btn--lg {
  min-height: 56px;
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-base);
}

.btn--block {
  display: flex;
  width: 100%;
}

.btn__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
}


/* =============================================================
   .input, .select, .textarea, .checkbox — contrôles de formulaire
   -------------------------------------------------------------
   API :
     <label class="field">
       <span class="field__label">Étiquette</span>
       <input class="input" …>
       <span class="field__help">Texte d'aide</span>
     </label>
   Notes :
     - mêmes tokens border / radius / focus que les boutons.
     - :focus-visible pose un ring brand-100 + bordure brand-700.
     - placeholder en neutral-500 pour bon contraste mais discret.
   ============================================================= */
.field {
  display: grid;
  gap: var(--space-2);
}

.field__label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--neutral-700);
  line-height: var(--leading-tight);
}

.field__help {
  font-size: var(--text-xs);
  color: var(--neutral-500);
}

/* Variante réservée aux écrans tactiles : astuces propres au mobile
   (ex. OCR « Scanner du texte » d'iOS), inutiles au poste douchette. */
.field__help--touch {
  display: none;
}

@media (pointer: coarse) {
  .field__help--touch {
    display: block;
  }
}

.field__error {
  font-size: var(--text-xs);
  color: var(--danger);
}

.input,
.select,
.textarea {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  background: var(--neutral-0);
  color: var(--neutral-900);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.input:hover,
.select:hover,
.textarea:hover {
  border-color: var(--neutral-500);
}

.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
  outline: none;
  border-color: var(--brand-700);
  box-shadow: var(--focus-ring);
}

.input::placeholder,
.textarea::placeholder {
  color: var(--neutral-500);
}

.input:disabled,
.select:disabled,
.textarea:disabled {
  background: var(--neutral-50);
  color: var(--neutral-500);
  cursor: not-allowed;
}

.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--danger);
}
.input[aria-invalid="true"]:focus-visible,
.select[aria-invalid="true"]:focus-visible,
.textarea[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 3px var(--danger-bg);
}

.textarea {
  min-height: 96px;
  resize: vertical;
}

/* Select natif — chevron customisé via background-image SVG inline. */
.select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--space-8);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  background-size: 14px 14px;
}

/* Checkbox — on conserve le natif mais on calibre la teinte et la taille. */
.checkbox {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--brand-900);
  cursor: pointer;
}

.checkbox-row {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--neutral-700);
  cursor: pointer;
}


/* =============================================================
   .card — surface
   -------------------------------------------------------------
   API :
     <section class="card">
       <header class="card__header">
         <h2 class="card__title">Titre</h2>
         <p class="card__subtitle">Sous-titre</p>
       </header>
       <div class="card__body">…</div>
       <footer class="card__footer">…</footer>
     </section>
   Variantes :
     .card--flat     pas d'ombre (pour empilement dense)
     .card--muted    fond neutral-50 (pour zones secondaires)
   ============================================================= */
.card {
  background: var(--neutral-0);
  border: var(--border-subtle);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
}

.card--flat {
  box-shadow: none;
}

.card--muted {
  background: var(--neutral-50);
}

.card__header {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.card__title {
  margin: 0;
  font-size: var(--text-xl);
}

.card__subtitle {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--neutral-500);
}

.card__body > * + * {
  margin-top: var(--space-3);
}

.card__footer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: var(--border-subtle);
}


/* =============================================================
   .accordion — section repliable, sans JS (<details>/<summary>)
   -------------------------------------------------------------
   API :
     <details class="card accordion" open>
       <summary class="accordion__summary">
         <h2 class="card__title">Titre</h2>
         <span class="accordion__meta">100 plus récents</span>  (optionnel)
       </summary>
       <div class="accordion__body">… contenu …</div>
     </details>
   Notes :
     - 100 % natif : le clic / Entrée sur le <summary> ouvre-ferme la section.
       Le chevron est injecté en CSS et pivote via l'attribut [open].
     - L'état (ouvert/fermé) n'est PAS persisté : il repart de `open` au
       rechargement. Choix assumé pour rester sans JS ni stockage.
     - Réutilise .card pour le cadre et .card__title pour le titre.
   ============================================================= */
.accordion__summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  cursor: pointer;
  list-style: none;
}

.accordion__summary::-webkit-details-marker {
  display: none;
}

/* Le titre pousse méta + chevron sur la droite (robuste même sans méta). */
.accordion__summary > .card__title {
  margin-right: auto;
}

.accordion__summary::after {
  content: "";
  flex: none;
  width: 0.6em;
  height: 0.6em;
  border-right: 2px solid var(--neutral-500);
  border-bottom: 2px solid var(--neutral-500);
  transform: rotate(45deg);
  transform-origin: center;
  transition: transform 0.12s ease;
}

.accordion[open] > .accordion__summary::after {
  transform: rotate(-135deg);
}

.accordion__meta {
  font-size: var(--text-sm);
  color: var(--neutral-500);
}

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


/* =============================================================
   .table — tableau dense, lisible
   -------------------------------------------------------------
   API :
     <div class="table-wrap">
       <table class="table">
         <thead><tr><th>…</th></tr></thead>
         <tbody><tr><td>…</td></tr></tbody>
       </table>
     </div>
   Variantes :
     .table--striped     zebra subtile sur tr pairs
     .table--compact     padding réduit (audit / log)
   Notes :
     - en-tête sticky : le `.table-wrap` doit avoir une hauteur
       max ou un scroll vertical pour que `position: sticky`
       s'active sur th.
     - hover de ligne discret en brand-50.
   ============================================================= */
.table-wrap {
  overflow: auto;
  border: var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--neutral-0);
}

.table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--text-sm);
}

.table th,
.table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: top;
}

.table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--neutral-50);
  color: var(--neutral-500);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 1px solid var(--neutral-200);
  white-space: nowrap;
}

.table tbody td {
  border-bottom: 1px solid var(--neutral-100);
}

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

.table tbody tr:hover td {
  background: var(--brand-50);
}

.table--striped tbody tr:nth-child(even) td {
  background: var(--neutral-50);
}

.table--striped tbody tr:nth-child(even):hover td {
  background: var(--brand-50);
}

.table--compact th,
.table--compact td {
  padding: var(--space-2) var(--space-3);
}

.table__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.table__mono {
  font-family: var(--font-mono);
  font-size: 0.92em;
}


/* =============================================================
   .badge — pastille de statut courte
   -------------------------------------------------------------
   API :
     <span class="badge">Neutre</span>
     <span class="badge badge--success">En stock</span>
     <span class="badge badge--warning">Bientôt périmé</span>
     <span class="badge badge--danger">Rupture</span>
     <span class="badge badge--info">Info</span>
   Notes :
     - radius-sm (4px) — pas de pill.
     - poids medium (pas 800) pour ton éditorial.
   ============================================================= */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  background: var(--neutral-100);
  color: var(--neutral-700);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1.5;
  white-space: nowrap;
}

.badge--success {
  background: var(--success-bg);
  color: var(--success);
}

.badge--warning {
  background: var(--warning-bg);
  color: var(--warning);
}

.badge--danger {
  background: var(--danger-bg);
  color: var(--danger);
}

.badge--info {
  background: var(--brand-100);
  color: var(--brand-900);
}

.badge--dot::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: currentColor;
}

/* Compat — anciens noms de classe mono-dash (.badge-success, etc.)
   utilisés par les pages legacy. À supprimer en Phase 3 quand toutes
   les pages auront migré vers .badge--success / .badge--warning / … */
.badge-success { background: var(--success-bg);  color: var(--success); }
.badge-warning { background: var(--warning-bg);  color: var(--warning); }
.badge-danger  { background: var(--danger-bg);   color: var(--danger); }
.badge-info    { background: var(--brand-100);   color: var(--brand-900); }
.badge-muted   { background: var(--neutral-100); color: var(--neutral-700); }


/* =============================================================
   .alert — message d'état inline
   -------------------------------------------------------------
   API :
     <div class="alert alert--info" role="status">
       <strong>Titre.</strong> Message complémentaire.
     </div>
   Variantes : info, success, warning, danger.
   Notes :
     - bordure gauche colorée + fond très clair → calme.
     - role="status" pour info/success, role="alert" pour danger.
   ============================================================= */
.alert {
  display: flex;
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--brand-50);
  color: var(--neutral-900);
  border-radius: var(--radius-md);
  border-left: 3px solid var(--brand-700);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.alert--info {
  background: var(--brand-50);
  border-left-color: var(--brand-700);
}

.alert--success {
  background: var(--success-bg);
  border-left-color: var(--success);
}

.alert--warning {
  background: var(--warning-bg);
  border-left-color: var(--warning);
}

.alert--danger {
  background: var(--danger-bg);
  border-left-color: var(--danger);
}

.alert__title {
  display: block;
  font-weight: var(--weight-semibold);
  margin-bottom: 2px;
}

.alert__list {
  margin: 0;
  padding-left: var(--space-5);
}

.alert__list > li + li {
  margin-top: var(--space-1);
}


/* =============================================================
   .nav — barre de navigation (header avec wordmark)
   -------------------------------------------------------------
   API :
     <header class="nav">
       <a class="nav__brand brand" href="/">
         <svg class="brand__logo" …>…</svg>  <!-- wordmark « le Stock » entier, inliné via brand_logo_svg() -->
       </a>
       <nav class="nav__links">
         <a class="nav__link is-active" href="…">Accueil</a>
         <a class="nav__link" href="…">…</a>
       </nav>
       <div class="nav__end">
         <span class="nav__user">Nom utilisateur</span>
         <a class="nav__action" href="/logout.php">Déconnexion</a>
       </div>
     </header>
   Notes :
     - fond brand-900 (signature).
     - wordmark préservé tel quel (cahier des charges §2 + §7).
     - safe-area iOS gérée via env(safe-area-inset-*).
   ============================================================= */
.nav {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: calc(env(safe-area-inset-top) + var(--space-3))
           max(var(--space-4), env(safe-area-inset-right))
           var(--space-3)
           max(var(--space-4), env(safe-area-inset-left));
  min-height: calc(64px + env(safe-area-inset-top));
  background: var(--brand-900);
  color: var(--neutral-0);
  transition: transform 0.18s ease;
}

/* Clavier mobile ouvert : la navbar s'escamote (app.js, visualViewport) pour
   ne jamais recouvrir le champ que WebKit vient de panner dans la vue. La
   bande libérée est peinte par le fond brand-900 du body (_base.css). */
.nav--keyboard-hidden {
  transform: translateY(-100%);
}

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

/* Hamburger — visible mobile/tablette, caché ≥ 1024px. */
.nav__menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  flex: 0 0 auto;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-md);
  color: var(--neutral-0);
  cursor: pointer;
  transition: background-color 0.12s ease;
}

.nav__menu-toggle:hover {
  background: rgba(255, 255, 255, 0.14);
}

.nav__brand {
  color: var(--neutral-0);
  text-decoration: none;
  flex: 0 0 auto;
}

.nav__brand:hover {
  text-decoration: none;
}

/* Wordmark « le Stock » — logo unique entièrement vectorisé (chaque lettre
   est un <path>, « le »/« tock » = Helvetica Neue Light, « s » = Snell
   Roundhand), inliné en dur dans le HTML par brand_logo_svg() (lib/layout.php).
   Rendu identique partout : aucune police requise, aucun fichier d'asset à
   servir → robuste au déploiement (le « s » ne disparaît plus quand le symlink
   public/assets n'est pas suivi sur OVH). fill=currentColor hérite du blanc. */
.brand {
  display: inline-flex;
  align-items: center;
  color: var(--neutral-0);
  text-decoration: none;
}

/* Hauteur + largeur EXPLICITES (32 × 129px, ratio du viewBox 134.66/33.42).
   width n'est PAS `auto` : Safari et anciens Firefox effondrent la largeur d'un
   <svg> inliné en width:auto quand seul le viewBox donne le ratio → le wordmark
   disparaîtrait. Les mêmes dimensions sont posées en attributs sur le <svg>
   (filet de sécurité si le CSS ne charge pas). currentColor → tracé blanc. */
.brand__logo {
  display: block;
  height: 32px;
  width: 129px;
  color: var(--neutral-0);
}

.nav__links {
  display: none;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--space-1);
  margin-left: var(--space-5);
}

.nav__link {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: rgba(255, 255, 255, 0.78);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.nav__link:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--neutral-0);
  text-decoration: none;
}

.nav__link.is-active {
  background: rgba(255, 255, 255, 0.14);
  color: var(--neutral-0);
}

.nav__end {
  display: none;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
  font-size: var(--text-sm);
  color: rgba(255, 255, 255, 0.78);
}

.nav__user {
  font-size: var(--text-sm);
  color: rgba(255, 255, 255, 0.85);
}

.nav__action {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-3);
  border: 1px solid rgba(255, 255, 255, 0.32);
  border-radius: var(--radius-md);
  color: var(--neutral-0);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  background: rgba(255, 255, 255, 0.06);
  transition: background-color 0.12s ease;
}

.nav__action:hover {
  background: rgba(255, 255, 255, 0.14);
  text-decoration: none;
}

@media (min-width: 1024px) {
  .nav__menu-toggle { display: none; }
  .nav__links { display: inline-flex; }
  .nav__end   { display: inline-flex; }
}

/* Mobile/tablette (< 1024px) : le wordmark est centré horizontalement dans le
   header pendant que le hamburger reste calé à gauche. On sort le lien du flux
   (absolute) et on le centre via left:50% + translateX. top/bottom ancrent la
   boîte sur la zone de CONTENU (padding safe-area exclu) → le logo reste aligné
   verticalement avec le hamburger et insensible au décalage du notch iOS.
   La boîte reste à la largeur du logo (pas de right:0) pour ne pas recouvrir le
   hamburger et bloquer son clic. .brand a déjà align-items:center → centrage V. */
@media (max-width: 1023.98px) {
  .nav__brand {
    position: absolute;
    left: 50%;
    top: calc(env(safe-area-inset-top) + var(--space-3));
    bottom: var(--space-3);
    transform: translateX(-50%);
  }
}


/* =============================================================
   .nav-drawer — menu coulissant (mobile + tablette)
   -------------------------------------------------------------
   API :
     <aside class="nav-drawer" id="primary-nav" data-nav-drawer hidden>
       <div class="nav-drawer__backdrop" data-nav-close></div>
       <nav class="nav-drawer__panel" aria-label="Menu complet">
         <header class="nav-drawer__head">
           <span class="nav-drawer__eyebrow">Menu</span>
           <button class="nav-drawer__close" data-nav-close>…</button>
         </header>
         <ul class="nav-drawer__list">
           <li><a class="nav-drawer__link is-active" href="…">…</a></li>
         </ul>
         <footer class="nav-drawer__foot">…</footer>
       </nav>
     </aside>
   Notes :
     - le toggle est piloté par assets/js/app.js via data-nav-toggle
       et data-nav-close ; aucun JS spécifique au composant.
     - le panel utilise brand-900 (rappel signature) sur surface
       blanche derrière ; jamais blanc-sur-blanc.
   ============================================================= */
.nav-drawer {
  position: fixed;
  inset: 0;
  /* z-index 100 : le menu ouvert recouvre TOUT, y compris la barre de scan
     fixe de l'accueil (.scan-bar, 40) et le panneau notif (50). Sinon la barre
     SORTIE / ENTRÉE se peint par-dessus le pied du drawer et rogne le nom
     d'utilisateur (visible surtout en admin, liste plus longue). */
  z-index: 100;
}

.nav-drawer[hidden] {
  display: none;
}

.nav-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(17, 24, 39, 0.55);
  cursor: pointer;
  animation: nav-drawer-fade 0.15s ease;
}

.nav-drawer__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(320px, 86vw);
  height: 100%;
  height: 100dvh;
  padding: calc(env(safe-area-inset-top) + var(--space-5))
           max(var(--space-5), env(safe-area-inset-left))
           calc(env(safe-area-inset-bottom) + var(--space-5))
           max(var(--space-5), env(safe-area-inset-left));
  background: var(--brand-900);
  color: var(--neutral-0);
  box-shadow: var(--shadow-lg);
  animation: nav-drawer-slide 0.18s ease;
  overflow-y: auto;
}

.nav-drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.nav-drawer__eyebrow {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.7);
}

.nav-drawer__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-md);
  color: var(--neutral-0);
  cursor: pointer;
}

.nav-drawer__close:hover {
  background: rgba(255, 255, 255, 0.14);
}

.nav-drawer__list {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  gap: 2px;
  flex: 1;
}

.nav-drawer__link {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  color: rgba(255, 255, 255, 0.82);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.nav-drawer__link:hover {
  background: rgba(255, 255, 255, 0.08);
  color: var(--neutral-0);
  text-decoration: none;
}

.nav-drawer__link.is-active {
  background: rgba(255, 255, 255, 0.16);
  color: var(--neutral-0);
}

.nav-drawer__foot {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  display: grid;
  gap: var(--space-2);
}

.nav-drawer__user {
  font-size: var(--text-sm);
  color: rgba(255, 255, 255, 0.7);
}

.nav-drawer__logout {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-3);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.06);
  color: var(--neutral-0);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

@keyframes nav-drawer-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes nav-drawer-slide {
  from { transform: translateX(-12px); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

/* Scroll lock pendant que le drawer est ouvert (toggle JS dans app.js).
   Le body ne défile jamais (app shell) : c'est .shell qu'on fige. */
body.is-nav-open .shell {
  overflow: hidden;
}

/* Sur desktop, le drawer ne doit jamais s'afficher même si JS
   l'a laissé ouvert pendant un resize. */
@media (min-width: 1024px) {
  .nav-drawer { display: none !important; }
}


/* =============================================================
   .shell / .container / .page-header — primitives de layout
   -------------------------------------------------------------
   API :
     <main class="shell">
       <div class="shell__body">
         <div class="container">
           <header class="page-header">
             <h1 class="page-header__title">Titre</h1>
             [actions optionnelles]
           </header>
           …sections…
         </div>
       </div>
     </main>
   Notes :
     - shell = LE conteneur de scroll de l'app (le body ne défile pas,
       cf. _base.css). Son fond en deux bandes ne bouge pas pendant le
       rebond élastique : l'étirement révèle du bleu en haut (continuité
       de la navbar) et du neutre en bas.
     - shell__body = bandeau plein large (fond neutre + paddings).
     - container = max-width 1200, stack vertical avec gap.
     - page-header = ligne titre + actions, marge inférieure réduite
       (le gap du container fait le reste).
   ============================================================= */
.shell {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Jamais de chaînage du geste vers le document (le rebond local reste). */
  overscroll-behavior: contain;
  background: linear-gradient(to bottom, var(--brand-900) 50%, var(--neutral-50) 50%);
}

.shell__body {
  /* +1px : .shell est TOUJOURS défilable, même quand le contenu tient dans
     la vue (Entrée/Sortie). Sinon iOS juge le conteneur non scrollable et
     fait rebondir le document entier — navbar comprise. */
  min-height: calc(100% + 1px);
  background: var(--neutral-50);
  padding: var(--space-6) var(--space-4) var(--space-12);
}

@media (min-width: 1024px) {
  .shell__body {
    padding: var(--space-8) var(--space-6) var(--space-12);
  }
}

.container {
  width: min(1200px, 100%);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-2);
}

.page-header__title {
  margin: 0;
}

.page-header__actions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}


/* =============================================================
   .field-grid — grille 2 colonnes pour formulaires et filtres
   -------------------------------------------------------------
   API :
     <form class="field-grid">
       <label class="field">…</label>
       <label class="field">…</label>
       <div class="field-grid__full">…élément pleine largeur…</div>
     </form>
   Notes :
     - 2 colonnes ≥ 1024px, 1 colonne en dessous.
     - field-grid__full → s'étend sur toutes les colonnes.
   ============================================================= */
.field-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}

.field-grid__full {
  grid-column: 1 / -1;
}

@media (max-width: 1023.98px) {
  .field-grid {
    grid-template-columns: 1fr;
  }
}


/* =============================================================
   .actions-row — rangée de boutons inline (filtres, fiche, etc.)
   -------------------------------------------------------------
   API :
     <div class="actions-row">
       <button class="btn">Action principale</button>
       <a class="btn btn--secondary">Action secondaire</a>
     </div>
   Notes :
     - inline-flex sur desktop, column pleine largeur sous 640px.
   ============================================================= */
.actions-row {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

@media (max-width: 639.98px) {
  .actions-row {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }
  .actions-row > .btn,
  .actions-row > a.btn,
  .actions-row > button {
    width: 100%;
  }
}


/* =============================================================
   .search-bar — input + bouton rechercher en ligne
   -------------------------------------------------------------
   API :
     <form class="search-bar">
       <input class="input" type="search" placeholder="…">
       <button class="btn">Rechercher</button>
     </form>
   ============================================================= */
.search-bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-2);
}

@media (max-width: 639.98px) {
  .search-bar {
    grid-template-columns: 1fr;
  }
  .search-bar .btn {
    width: 100%;
  }
}


/* =============================================================
   .kpi-grid — rangée d'indicateurs chiffrés auto-fit
   -------------------------------------------------------------
   API :
     <div class="kpi-grid">
       <div class="kpi">…</div>
       <div class="kpi">…</div>
     </div>
   ============================================================= */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-3);
}


/* =============================================================
   .ref-list — liste de sous-formulaires séparés par filets
   -------------------------------------------------------------
   API :
     <div class="ref-list">
       <form class="ref-list__item field-grid" method="post">…</form>
       <form class="ref-list__item field-grid" method="post">…</form>
     </div>
   Notes :
     - séparation discrète 1px neutral-200 entre items.
     - pas de border autour (le parent .card encapsule déjà).
   ============================================================= */
.ref-list {
  display: grid;
}

.ref-list__item {
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--neutral-200);
}

.ref-list__item:first-child {
  padding-top: 0;
}

.ref-list__item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}


/* =============================================================
   .kv-list — liste clé/valeur (fiches read-only)
   -------------------------------------------------------------
   API :
     <dl class="kv-list">
       <div class="kv-list__item">
         <dt class="kv-list__key">Numéro de lot</dt>
         <dd class="kv-list__value">LOT-A23B-2026</dd>
       </div>
     </dl>
   Notes :
     - 2 colonnes ≥ 640px, 1 colonne en dessous.
     - libellés en eyebrow-style (xs neutral-500 uppercase).
   ============================================================= */
.kv-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
  margin: 0;
}

.kv-list__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.kv-list__key {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--neutral-500);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.kv-list__value {
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--neutral-900);
  line-height: var(--leading-normal);
}

@media (max-width: 639.98px) {
  .kv-list {
    grid-template-columns: 1fr;
  }
}

/* Variante empilée (1 colonne quelle que soit la taille) — utile
   pour une fiche dense ou quand la kv-list est imbriquée dans
   une colonne déjà étroite. */
.kv-list--stack {
  grid-template-columns: 1fr;
}


/* =============================================================
   .back-link — lien retour discret en haut de fiche
   -------------------------------------------------------------
   API :
     <a class="back-link" href="/lots.php">
       <svg>arrow-left</svg>
       Tous les lots
     </a>
   ============================================================= */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--neutral-500);
  text-decoration: none;
  transition: color 0.12s ease;
}

.back-link:hover,
.back-link:focus-visible {
  color: var(--brand-800);
  text-decoration: none;
}


/* =============================================================
   .input--lg — variante large (input de scan)
   -------------------------------------------------------------
   API : <input class="input input--lg" type="search" …>
   Notes : utilisée pour les champs centraux d'un flux scan
   (entree.php, sortie.php, ajuster.php) où la cible tactile
   doit être grande et la valeur très lisible.
   ============================================================= */
.input--lg {
  min-height: 60px;
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-xl);
}


/* =============================================================
   .input--sm — variante compacte (formulaires inline de tableau)
   -------------------------------------------------------------
   API : <input class="input input--sm" …>
   Notes : champ court pour une action inline dans une ligne de
   tableau (admin.php — poser un PIN / mot de passe directement).
   ============================================================= */
.input--sm {
  min-height: 36px;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
}


/* =============================================================
   .inline-set — champ court + bouton sur une même ligne
   -------------------------------------------------------------
   API :
     <form class="inline-set">
       <input class="input input--sm" …>
       <button class="btn btn--sm">…</button>
     </form>
   Notes : aligne un contrôle compact et son bouton d'action ;
   repasse à la ligne sur écran étroit. Marge nulle (intégré en
   cellule de tableau).
   ============================================================= */
.inline-set {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin: 0;
}


/* =============================================================
   .qty-stepper — − [ valeur ] + (entrée scan)
   -------------------------------------------------------------
   API :
     <div class="qty-stepper">
       <button class="qty-stepper__btn" type="button" data-qty-minus>−</button>
       <input class="input" type="number" data-qty value="1">
       <button class="qty-stepper__btn" type="button" data-qty-plus>+</button>
     </div>
   Notes :
     - boutons 44×44, glyph en brand-800.
     - input centré, font medium, tabular-nums.
     - redéfinit le .qty-stepper d'app.css (qui sera supprimé en
       Phase 3 après migration finale).
   ============================================================= */
.qty-stepper {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) 44px;
  gap: var(--space-2);
  align-items: center;
}

.qty-stepper__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  background: var(--neutral-0);
  color: var(--brand-800);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-md);
  font-size: var(--text-xl);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.qty-stepper__btn:hover {
  background: var(--brand-50);
  border-color: var(--neutral-500);
}

.qty-stepper > input {
  text-align: center;
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  min-height: 44px;
}


/* =============================================================
   .login-shell / .login-card — écran de connexion
   -------------------------------------------------------------
   API :
     <div class="login-shell">
       <section class="card login-card">
         <header class="login-card__header">…</header>
         <form class="login-form">…</form>
       </section>
     </div>
   Notes :
     - centré verticalement dans le shell, marge de respiration
       suffisante pour iPhone safe-area.
     - le PIN est traité comme un input "console" (mono, large,
       espacé) — sans devenir un kiosque pour gosse.
   ============================================================= */
.login-shell {
  display: grid;
  place-items: center;
  min-height: calc(100dvh - 64px - var(--space-12) - var(--space-6));
  padding: var(--space-4) 0;
}

.login-card {
  width: min(440px, 100%);
}

.login-card__header {
  margin-bottom: var(--space-5);
}

.login-card__title {
  margin: var(--space-2) 0 var(--space-2);
}

.login-card__intro {
  margin: 0;
  color: var(--neutral-500);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.login-form__pin {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  letter-spacing: 0.3em;
  min-height: 56px;
}

.login-form__actions {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.login-form__submit {
  flex: 1 1 auto;
  justify-content: center;
}

/* Bouton icône passkey : carré, révélé par JS quand le PIN a une clé d'accès. */
.login-form__passkey {
  flex: 0 0 auto;
  width: var(--tap-min);
  padding-inline: 0;
  justify-content: center;
}

.login-form__passkey[hidden] {
  display: none;
}

.login-form__hint {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--neutral-500);
  line-height: var(--leading-normal);
}


/* =============================================================
   .watchlist — liste compacte "À surveiller" (accueil)
   -------------------------------------------------------------
   API :
     <ul class="watchlist">
       <li class="watchlist__item">
         <span class="badge badge--warning">Bientôt périmé</span>
         <span class="watchlist__detail">Lot X — péremption …</span>
         <a class="btn btn--ghost btn--sm" href="…">Voir →</a>
       </li>
     </ul>
   Notes :
     - 3 colonnes (badge / détail / action) desktop, empilé mobile.
     - séparateur 1px neutral-100 entre items, pas autour.
     - pagination 100 % CSS via .watchlist__counts : boutons radio 10/25/50/Tout
       (cachés) qui masquent les items au-delà du palier coché (:has + nth-child).
   ============================================================= */
.watchlist {
  list-style: none;
  margin: 0;
  padding: 0;
}

.watchlist__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--neutral-100);
}

.watchlist__item:first-child {
  padding-top: 0;
}

.watchlist__item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.watchlist__detail {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--neutral-700);
  line-height: var(--leading-normal);
}

.watchlist-empty {
  margin: 0;
  padding: var(--space-2) 0;
  color: var(--neutral-500);
  font-size: var(--text-sm);
}

/* --- pagination « Afficher 10 / 25 / 50 / Tout » ---------------------- */

/* base : au-delà de 10 items, les suivants sont masqués */
.watchlist__item:nth-child(n+11) {
  display: none;
}

/* palier coché → révèle tout, puis re-masque au-delà du seuil */
.card:has(#wl-25:checked) .watchlist__item {
  display: grid;
}
.card:has(#wl-25:checked) .watchlist__item:nth-child(n+26) {
  display: none;
}
.card:has(#wl-50:checked) .watchlist__item {
  display: grid;
}
.card:has(#wl-50:checked) .watchlist__item:nth-child(n+51) {
  display: none;
}
.card:has(#wl-all:checked) .watchlist__item {
  display: grid;
}

.watchlist__counts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  margin-top: var(--space-4);
}

.watchlist__counts-legend {
  margin-right: var(--space-1);
  font-size: var(--text-xs);
  color: var(--neutral-500);
}

/* radio masqué visuellement mais focusable au clavier */
.watchlist__count-input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.watchlist__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 32px;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-md);
  background: var(--neutral-0);
  color: var(--neutral-700);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease,
              color 0.12s ease;
}

.watchlist__count:hover {
  border-color: var(--neutral-500);
  background: var(--neutral-50);
}

.watchlist__count-input:checked + .watchlist__count {
  background: var(--brand-900);
  border-color: var(--brand-900);
  color: var(--neutral-0);
}

.watchlist__count-input:focus-visible + .watchlist__count {
  outline: 2px solid var(--brand-700);
  outline-offset: 2px;
}

@media (max-width: 639.98px) {
  .watchlist__item {
    grid-template-columns: 1fr;
    gap: var(--space-2);
  }
  .watchlist__item .btn {
    justify-self: start;
  }
}


/* =============================================================
   .home-tail — lien discret sous l'accueil (réglages, etc.)
   ============================================================= */
.home-tail {
  display: flex;
  justify-content: center;
  padding: var(--space-2) 0;
}

.home-tail__link {
  font-size: var(--text-sm);
  color: var(--neutral-500);
  text-decoration: none;
}

.home-tail__link:hover {
  color: var(--brand-800);
  text-decoration: underline;
}


/* =============================================================
   .kpi — indicateur chiffré (chiffre + label + delta)
   -------------------------------------------------------------
   API :
     <div class="kpi">
       <span class="kpi__label">Lignes</span>
       <strong class="kpi__value">128</strong>
       <span class="kpi__delta kpi__delta--up">+12</span>
     </div>
   Variantes delta :
     .kpi__delta--up        positive (vert)
     .kpi__delta--down      négative (rouge)
     .kpi__delta--neutral   neutre (gris)
   Notes :
     - tabular-nums pour alignement des chiffres en colonne.
   ============================================================= */
.kpi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-5);
  background: var(--neutral-0);
  border: var(--border-subtle);
  border-radius: var(--radius-lg);
}

.kpi__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--neutral-500);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.kpi__value {
  font-size: var(--text-3xl);
  font-weight: var(--weight-semibold);
  line-height: 1;
  color: var(--neutral-900);
  font-variant-numeric: tabular-nums;
  margin-top: var(--space-2);
}

.kpi__unit {
  font-size: var(--text-base);
  font-weight: var(--weight-regular);
  color: var(--neutral-500);
  margin-left: var(--space-1);
}

.kpi__delta {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  margin-top: var(--space-1);
  font-variant-numeric: tabular-nums;
}

.kpi__delta--up      { color: var(--success); }
.kpi__delta--down    { color: var(--danger); }
.kpi__delta--neutral { color: var(--neutral-500); }

/* Variantes accueil : valeur teintée (à n'appliquer que si le compteur > 0,
   un zéro rouge crierait pour rien) + rangée serrée de 3 compteurs. */
.kpi--danger  .kpi__value { color: var(--danger); }
.kpi--warning .kpi__value { color: var(--warning); }

.kpi-grid--trio {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Libellé avec pictogramme (compteurs de l'accueil) */
.kpi__label--icon {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.kpi__label--icon svg {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

@media (max-width: 639.98px) {
  .kpi-grid--trio {
    gap: var(--space-2);
  }

  .kpi-grid--trio .kpi {
    padding: var(--space-3);
  }

  .kpi-grid--trio .kpi__value {
    font-size: var(--text-2xl);
  }
}


/* =============================================================
   .modal — dialogue centré
   -------------------------------------------------------------
   API :
     <div class="modal" data-modal hidden role="dialog"
          aria-modal="true" aria-labelledby="modal-title">
       <div class="modal__backdrop" data-modal-close></div>
       <div class="modal__panel">
         <header class="modal__header">
           <h3 id="modal-title" class="modal__title">Titre</h3>
           <button class="modal__close" data-modal-close
                   aria-label="Fermer">×</button>
         </header>
         <div class="modal__body">…</div>
         <footer class="modal__actions">
           <button class="btn btn--secondary">Annuler</button>
           <button class="btn">Confirmer</button>
         </footer>
       </div>
     </div>
   Notes :
     - le panel scrolle si le contenu dépasse la fenêtre.
     - action principale toujours en dernier dans le HTML
       (pas de column-reverse mobile — placement clavier respecté).
   ============================================================= */
.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--space-4);
}

.modal[hidden] {
  display: none;
}

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(17, 24, 39, 0.5);
  cursor: pointer;
  animation: modal-fade 0.15s ease;
}

.modal__panel {
  position: relative;
  width: min(480px, 100%);
  max-height: calc(100dvh - var(--space-8));
  overflow-y: auto;
  background: var(--neutral-0);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
  animation: modal-pop 0.15s ease;
}

.modal__header {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.modal__title {
  margin: 0;
  font-size: var(--text-xl);
}

.modal__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: -6px -6px 0 0;
  border-radius: var(--radius-md);
  color: var(--neutral-500);
  font-size: 22px;
  line-height: 1;
}

.modal__close:hover {
  background: var(--neutral-100);
  color: var(--neutral-900);
}

.modal__body > * + * {
  margin-top: var(--space-3);
}

.modal__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-6);
}

@keyframes modal-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

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


/* =============================================================
   .cta-card — call-to-action grand format (accueil)
   -------------------------------------------------------------
   API :
     <a class="cta-card" href="/entree.php">
       <span class="cta-card__icon" aria-hidden="true">
         <svg width="22" height="22" stroke="currentColor"
              stroke-width="1.5" …>…</svg>
       </span>
       <h2 class="cta-card__title">Entrée</h2>
       <p class="cta-card__description">…</p>
       <span class="cta-card__action">
         Démarrer
         <svg width="14" height="14" stroke="currentColor"
              stroke-width="1.5" …>…</svg>
       </span>
     </a>
   Layout :
     <div class="cta-pair">…deux .cta-card…</div>
     <div class="cta-pair__tertiary">…lien ghost optionnel…</div>
   Notes :
     - surface neutral-0, jamais brand-900 en aplat (cahier §1).
     - icône dans bloc brand-100 / brand-900, 40×40.
     - hover : bordure neutral-300 + shadow-sm. Aucune translation.
   ============================================================= */
.cta-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}

@media (max-width: 639.98px) {
  .cta-pair {
    grid-template-columns: 1fr;
  }

  .cta-card__icon {
    align-self: center;
  }
}

.cta-pair__tertiary {
  display: flex;
  justify-content: center;
  margin-top: var(--space-4);
}

.cta-card {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: var(--space-2);
  min-height: 200px;
  padding: var(--space-8);
  background: var(--neutral-0);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-xl);
  color: var(--neutral-900);
  text-decoration: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.cta-card:hover,
.cta-card:focus-visible {
  border-color: var(--neutral-300);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: var(--neutral-900);
  outline: none;
}

.cta-card:hover .cta-card__action,
.cta-card:focus-visible .cta-card__action {
  color: var(--brand-700);
}

.cta-card:hover .cta-card__action svg,
.cta-card:focus-visible .cta-card__action svg {
  transform: translateX(2px);
}

.cta-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: var(--brand-100);
  color: var(--brand-900);
  margin-bottom: var(--space-4);
}

.cta-card__title {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

.cta-card__description {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--neutral-500);
  line-height: var(--leading-normal);
  max-width: 32ch;
}

.cta-card__action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: auto;
  padding-top: var(--space-6);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--brand-800);
  transition: color 0.15s ease;
}

.cta-card__action svg {
  transition: transform 0.15s ease;
}


/* =============================================================
   .scan-bar — actions de scan de l'accueil (mobile-first)
   -------------------------------------------------------------
   API :
     <nav class="scan-bar" aria-label="Scanner un produit">
       <a class="scan-bar__btn scan-bar__btn--primary" href="/sortie.php?cam=1">
         <svg class="scan-bar__icon" …viseur-sortie…/>
         <span class="scan-bar__txt">
           <span class="scan-bar__label">Sortie</span>
           <span class="scan-bar__sub">Scanner · FEFO</span>
         </span>
       </a>
       <a class="scan-bar__btn" href="/entree.php?cam=1">…Entrée</a>
     </nav>
     <div class="scan-bar__spacer" aria-hidden="true"></div>
   Notes :
     - polarité codifiée : Sortie = --primary (plein, dégradé brand),
       Entrée = défaut (contour brand-50/100). Même règle sur les
       pages de flux (.flow-header, .scan-camera-btn--*) et le viseur.
     - < 1024px : barre fixée en bas de l'écran (zone du pouce), le
       contenu défile dessous ; le spacer — dernier enfant du
       .container — réserve la place en fin de page.
     - ≥ 1024px : rangée statique en tête de contenu (souris /
       douchette), le spacer se replie.
     - Les liens portent ?cam=1 : ouverture automatique du viseur,
       sur écran tactile uniquement (voir sortie.js / entree.js).
     - z-index 40 : au-dessus du contenu, sous le menu de notifications
       (.notif-panel, 50), le drawer (100), les toasts (200) et le
       viseur caméra (300).
   ============================================================= */
.scan-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4)
           max(var(--space-3), env(safe-area-inset-bottom));
  background: rgba(255, 255, 255, 0.94);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-top: var(--border-subtle);
}

.scan-bar__btn {
  flex: 1 1 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 58px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--brand-100);
  border-radius: var(--radius-xl);
  background: var(--brand-50);
  color: var(--brand-800);
  text-decoration: none;
  transition: background-color 0.12s ease, box-shadow 0.15s ease,
              transform 0.1s ease;
}

.scan-bar__btn:hover {
  background: var(--brand-100);
  color: var(--brand-800);
  text-decoration: none;
}

.scan-bar__btn:active {
  transform: translateY(1px);
}

/* Reflet inset : littéral alpha assumé (pas de token blanc translucide),
   même précédent que les rgba() du bloc .scan-camera. */
.scan-bar__btn--primary {
  background: linear-gradient(180deg, var(--brand-900), var(--brand-800));
  border-color: transparent;
  color: var(--neutral-0);
  box-shadow: 0 4px 14px rgba(var(--brand-shadow-rgb), 0.35),
              inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.scan-bar__btn--primary:hover {
  background: linear-gradient(180deg, var(--brand-800), var(--brand-700));
  color: var(--neutral-0);
}

.scan-bar__btn--primary:active {
  box-shadow: 0 2px 6px rgba(var(--brand-shadow-rgb), 0.3),
              inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

.scan-bar__icon {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
}

.scan-bar__txt {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
}

.scan-bar__label {
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

.scan-bar__sub {
  font-size: var(--text-xs);
  opacity: 0.72;
  letter-spacing: 0.01em;
}

.scan-bar__spacer {
  height: calc(58px + var(--space-6) + env(safe-area-inset-bottom));
}

@media (min-width: 1024px) {
  .scan-bar {
    position: static;
    z-index: auto;
    max-width: 640px;
    padding: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-top: 0;
  }

  .scan-bar__spacer {
    display: none;
  }
}


/* =============================================================
   .activity — fil des derniers mouvements (accueil)
   -------------------------------------------------------------
   API :
     <ul class="activity">
       <li class="activity__item">
         <span class="activity__time">14:32</span>
         <span class="activity__dir activity__dir--out" aria-hidden="true">↑</span>
         <span class="activity__label">Produit
           <span class="activity__meta">−1 · Marie</span>
         </span>
       </li>
     </ul>
   Notes :
     - --out (sortie) en brand, --in (entrée) en success : le sens
       est aussi porté par le signe −/+ du .activity__meta.
   ============================================================= */
.activity {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.activity__item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--neutral-700);
}

.activity__time {
  flex-shrink: 0;
  min-width: 5ch;
  font-size: var(--text-xs);
  color: var(--neutral-500);
  font-variant-numeric: tabular-nums;
}

.activity__dir {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
}

.activity__dir svg {
  width: 12px;
  height: 12px;
}

.activity__dir--out {
  background: var(--brand-100);
  color: var(--brand-800);
}

.activity__dir--in {
  background: var(--success-bg);
  color: var(--success);
}

.activity__label {
  flex: 1 1 auto;
  min-width: 0;
}

.activity__meta {
  color: var(--neutral-500);
}


/* =============================================================
   .flow-header — en-tête identitaire des pages de flux
   -------------------------------------------------------------
   API :
     <header class="flow-header">
       <span class="flow-header__icon flow-header__icon--filled" aria-hidden="true">
         <svg …viseur-sortie…/>
       </span>
       <div>
         <h1 class="flow-header__title">Sortie</h1>
         <p class="flow-header__sub">1 scan = −1 unité · FEFO · annulable 30 min</p>
       </div>
       <div class="flow-header__actions">…bouton optionnel…</div>
     </header>
   Notes :
     - polarité : --filled (dégradé brand) pour Sortie, défaut
       (pastille brand-100) pour Entrée — même règle que .scan-bar.
     - remplace page-header + paragraphe d'aide sur entree/sortie ;
       le sous-titre absorbe la règle métier.
     - __actions (Catalogue : « Nouveau produit ») se cale à droite
       et passe à la ligne sur écran étroit.
   ============================================================= */
.flow-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.flow-header__actions {
  margin-left: auto;
}

.flow-header__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-lg);
  background: var(--brand-100);
  color: var(--brand-900);
}

.flow-header__icon svg {
  width: 24px;
  height: 24px;
}

.flow-header__icon--filled {
  background: linear-gradient(180deg, var(--brand-900), var(--brand-800));
  color: var(--neutral-0);
  box-shadow: 0 3px 10px rgba(var(--brand-shadow-rgb), 0.3);
}

.flow-header__title {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
}

.flow-header__sub {
  margin: 2px 0 0;
  font-size: var(--text-sm);
  color: var(--neutral-500);
}


/* =============================================================
   Tables — variante .table--cards (flip cards sous 1024px)
   -------------------------------------------------------------
   API : <table class="table table--cards">
     <td data-label="…">      paire label/valeur en mobile
     <td data-cell="title">   titre de carte
     <td data-cell="actions"> bandeau d'actions pied de carte
     <td data-cell="empty">   ligne vide colspan
   ============================================================= */
@media (max-width: 1023.98px) {
  .table--cards,
  .table--cards thead,
  .table--cards tbody,
  .table--cards tr,
  .table--cards th,
  .table--cards td {
    display: block;
    width: auto;
  }

  .table--cards thead {
    display: none;
  }

  .table-wrap:has(.table--cards) {
    overflow-x: visible;
    border: 0;
    background: transparent;
    border-radius: 0;
  }

  .table--cards tbody tr {
    margin-bottom: var(--space-2);
    padding: var(--space-4);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-lg);
    background: var(--neutral-0);
    box-shadow: var(--shadow-sm);
  }

  .table--cards tbody tr:last-child {
    margin-bottom: 0;
  }

  .table--cards tbody tr:hover td {
    background: transparent;
  }

  .table--cards tbody td {
    padding: var(--space-2) 0;
    border: 0;
    text-align: left;
  }

  .table--cards tbody td[data-label] {
    display: grid;
    grid-template-columns: minmax(35%, auto) 1fr;
    gap: var(--space-3);
    align-items: baseline;
    text-align: right;
  }

  .table--cards tbody td[data-label]::before {
    content: attr(data-label);
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--neutral-500);
    text-align: left;
  }

  .table--cards tbody td[data-cell="title"] {
    padding: 0 0 var(--space-2);
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--neutral-200);
    font-size: var(--text-base);
    font-weight: var(--weight-semibold);
  }

  .table--cards tbody td[data-cell="title"] .text-muted {
    font-size: var(--text-sm);
    font-weight: var(--weight-regular);
  }

  .table--cards tbody td[data-cell="actions"] {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-3);
    padding-top: var(--space-3);
    border-top: 1px solid var(--neutral-200);
  }

  .table--cards tbody td[data-cell="actions"] .btn,
  .table--cards tbody td[data-cell="actions"] a.btn {
    flex: 1 1 100%;
    min-width: 0;
  }

  .table--cards tbody td[data-cell="empty"] {
    text-align: center;
    color: var(--neutral-500);
    padding: var(--space-5) var(--space-3);
  }
}


/* =============================================================
   .grid — utilitaires de grille 2/3/4 colonnes
   -------------------------------------------------------------
   Note : on conserve la syntaxe .grid.two/.three/.four
   utilisée par les pages historiques (product.php, security.php)
   ainsi que la forme BEM .grid--two/--three/--four pour les
   futures pages.
   ============================================================= */
.grid {
  display: grid;
  gap: var(--space-4);
}

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

.grid.three,
.grid--three {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.grid.four,
.grid--four {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 700px) {
  .grid.two, .grid--two,
  .grid.three, .grid--three,
  .grid.four, .grid--four {
    grid-template-columns: 1fr;
  }
}

@media (min-width: 700.01px) and (max-width: 1023.98px) {
  .grid.three, .grid--three,
  .grid.four, .grid--four {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}


/* =============================================================
   .filter-input — recherche au-dessus d'un tableau
   ============================================================= */
.filter-input {
  max-width: 320px;
  margin-bottom: var(--space-3);
}

@media (max-width: 1023.98px) {
  .filter-input {
    max-width: 100%;
  }
}


/* =============================================================
   Flux scan — résultats de recherche (entree/sortie/ajuster)
   -------------------------------------------------------------
   Note : .search-result-item et .is-active sont pilotés par
   assets/js/scan_chain.js — ne pas renommer.
   ============================================================= */
.search-results {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-2);
  margin-top: var(--space-3);
  background: var(--neutral-0);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.search-result-item {
  display: grid;
  grid-template-columns: minmax(110px, auto) 1fr auto;
  align-items: center;
  gap: var(--space-3);
  min-height: 56px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--neutral-900);
  text-align: left;
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.search-result-item:hover {
  background: var(--neutral-50);
}

.search-result-item.is-active {
  border-color: var(--brand-700);
  background: var(--brand-50);
}

.search-result-item__ref {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--brand-800);
}

.search-result-item__name {
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

.search-result-item__stock {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--neutral-500);
}

.search-empty {
  padding: var(--space-4);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--neutral-500);
}


/* =============================================================
   Flux Entrée — pile d'items en attente de validation
   -------------------------------------------------------------
   Note : structure générée dynamiquement par entree.js.
   ============================================================= */
.pile-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-2);
}

.pile-item {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  background: var(--neutral-0);
  font-size: var(--text-sm);
}

.pile-item__meta {
  font-size: var(--text-xs);
  color: var(--neutral-500);
}

.pile-item__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  padding: 0;
  background: var(--neutral-0);
  color: var(--danger);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-md);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.pile-item__remove:hover {
  background: var(--danger-bg);
  border-color: var(--danger);
}


/* =============================================================
   Flux Sortie — liste des décréments enregistrés
   -------------------------------------------------------------
   Note : structure générée dynamiquement par sortie.js.
   ============================================================= */
.sortie-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: var(--space-1);
}

/* Lot/péremption en seconde ligne grise sous le nom : l'heure et le
   bouton d'annulation occupent les deux rangées. */
.sortie-line {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  background: var(--neutral-0);
  font-size: var(--text-sm);
}

.sortie-line__time {
  grid-row: 1 / 3;
}

.sortie-line__name,
.sortie-line__meta {
  grid-column: 2;
}

.sortie-line__cancel,
.sortie-line__cancelled {
  grid-column: 3;
  grid-row: 1 / 3;
}

.sortie-line__cancel svg {
  width: 18px;
  height: 18px;
}

.sortie-line.is-cancelled {
  background: var(--neutral-50);
  color: var(--neutral-500);
}

.sortie-line__time {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--neutral-500);
}

.sortie-line__name {
  font-weight: var(--weight-medium);
}

.sortie-line__meta {
  font-size: var(--text-xs);
  color: var(--neutral-500);
}

.sortie-line__cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  padding: 0;
  background: var(--neutral-0);
  color: var(--brand-800);
  border: 1px solid var(--neutral-300);
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  cursor: pointer;
  transition: background-color 0.12s ease, border-color 0.12s ease;
}

.sortie-line__cancel:hover {
  background: var(--brand-50);
  border-color: var(--neutral-500);
}

.sortie-line__cancelled {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--neutral-500);
}


/* =============================================================
   Flux Ajuster — lignes lot + delta + motif radio cards
   -------------------------------------------------------------
   Note : .is-active sur les motifs ET .is-negative/-positive/-zero
   sur les deltas sont pilotés par assets/js/ajuster.js.
   ============================================================= */
.adjust-lot-list {
  display: grid;
  gap: var(--space-2);
}

.adjust-lot-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  background: var(--neutral-0);
}

.adjust-lot-row__head {
  display: grid;
  gap: 2px;
}

.adjust-lot-row__meta {
  font-size: var(--text-xs);
  color: var(--neutral-500);
}

.adjust-lot-row__delta {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  min-width: 64px;
  text-align: right;
}

.adjust-lot-row__delta.is-negative { color: var(--danger); }
.adjust-lot-row__delta.is-positive { color: var(--success); }
.adjust-lot-row__delta.is-zero    { color: var(--neutral-500); }

.adjust-lot-row input[type="number"] {
  width: 96px;
  text-align: center;
  font-weight: var(--weight-semibold);
}

.adjust-motif-list {
  display: grid;
  gap: var(--space-2);
}

.adjust-motif-list label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--neutral-200);
  border-radius: var(--radius-md);
  background: var(--neutral-0);
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--neutral-700);
  transition: border-color 0.12s ease, background-color 0.12s ease, color 0.12s ease;
}

.adjust-motif-list label:hover {
  border-color: var(--neutral-500);
}

.adjust-motif-list label.is-active {
  border-color: var(--brand-700);
  background: var(--brand-50);
  color: var(--brand-900);
}

/* Aide sous le bouton d'enregistrement, masquée par ajuster.js dès
   qu'une quantité diffère du système (le bouton devient actif). */
.adjust-hint {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--neutral-500);
}


/* =============================================================
   Toasts — notifications éphémères (assets/js/toast.js)
   -------------------------------------------------------------
   Bottom-centered sur mobile, top-right sur desktop.
   ============================================================= */
.toast-container {
  position: fixed;
  left: 0;
  right: 0;
  bottom: max(var(--space-4), env(safe-area-inset-bottom));
  display: grid;
  gap: var(--space-2);
  justify-content: center;
  pointer-events: none;
  z-index: 200;
  padding: 0 var(--space-3);
}

.toast {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: min(420px, 90vw);
  max-width: 96vw;
  padding: var(--space-3) var(--space-4);
  background: var(--neutral-900);
  color: var(--neutral-0);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  animation: toast-in 0.18s ease;
}

.toast.is-leaving {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

.toast--success { background: var(--success); }
.toast--error   { background: var(--danger); }
.toast--info    { background: var(--brand-900); }

.toast__message {
  flex: 1;
}

.toast__action {
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.5);
  color: var(--neutral-0);
  min-height: 32px;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

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

@keyframes toast-in-top {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (min-width: 1024px) {
  .toast-container {
    top: max(var(--space-4), env(safe-area-inset-top));
    right: max(var(--space-5), env(safe-area-inset-right));
    bottom: auto;
    left: auto;
    padding: 0;
    justify-content: end;
  }

  .toast {
    min-width: 280px;
    max-width: 420px;
    animation: toast-in-top 0.18s ease;
  }
}


/* =============================================================
   Print
   ============================================================= */
@media print {
  .nav,
  .nav-drawer,
  .back-link,
  .btn,
  button {
    display: none !important;
  }

  /* Défiger l'app shell : sans ça, seule la première page s'imprime. */
  body {
    display: block;
    height: auto;
    overflow: visible;
  }

  .shell {
    overflow: visible;
    background: var(--neutral-0);
  }

  .shell__body {
    padding: 0;
    background: var(--neutral-0);
  }

  .card {
    box-shadow: none;
    border-color: var(--neutral-300);
  }
}


/* =============================================================
   Utilitaires partagés (en bonus — discrets et nommés)
   ------------------------------------------------------------- */
.text-muted   { color: var(--neutral-500); }
.text-strong  { color: var(--neutral-900); font-weight: var(--weight-semibold); }
.text-mono    { font-family: var(--font-mono); }
.text-right   { text-align: right; }
.text-success { color: var(--success); font-weight: var(--weight-semibold); }
.text-danger  { color: var(--danger); font-weight: var(--weight-semibold); }
.nowrap       { white-space: nowrap; }

.eyebrow {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--neutral-500);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}


/* =============================================================
   .scan-camera — scan code-barres via l'appareil photo
   -------------------------------------------------------------
   API :
     <div class="scan-input-row">
       <input class="input" …>
       <button class="btn scan-camera-btn scan-camera-btn--soft"
               data-scan-camera data-scan-label="Inventaire">…icône…</button>
     </div>
   Viseur plein écran construit dynamiquement par
   assets/js/scan_camera.js (.scan-camera--full .scan-camera__*) —
   ne pas renommer les classes. Deux usages, même overlay :
   scanCamera() « un scan » (recherches) et scanCameraSession()
   (flux Entrée/Sortie, viseur persistant).
   Notes : la caméra exige HTTPS (ou localhost) + Permissions-Policy
   camera=(self) (lib/bootstrap.php).
   ============================================================= */
.scan-input-row {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
}

.scan-input-row .input {
  flex: 1 1 auto;
  min-width: 0;
}

.scan-camera-btn {
  flex: 0 0 auto;
  padding-inline: var(--space-3);
}

.scan-camera-btn__icon {
  width: 22px;
  height: 22px;
}

/* Variantes de flux (polarité, cf. .scan-bar) : à combiner avec .btn nu
   (sans --secondary). Sortie = plein, Entrée = contour. */
.scan-camera-btn--filled {
  background: linear-gradient(180deg, var(--brand-900), var(--brand-800));
  border-color: transparent;
  color: var(--neutral-0);
  box-shadow: 0 3px 10px rgba(var(--brand-shadow-rgb), 0.3);
}

.scan-camera-btn--filled:hover {
  background: linear-gradient(180deg, var(--brand-800), var(--brand-700));
}

.scan-camera-btn--soft {
  background: var(--brand-50);
  border-color: var(--brand-100);
  color: var(--brand-800);
}

.scan-camera-btn--soft:hover {
  background: var(--brand-100);
  color: var(--brand-800);
}

.scan-camera-btn--soft:active {
  background: var(--brand-100);
  color: var(--brand-800);
}

.scan-camera {
  position: fixed;
  /* inset:0 : le bas est ancré au viewport RÉEL.
     (L'essai height:100dvh débordait sous l'écran en PWA : la barre d'état
     opaque décale la webview vers le bas mais dvh mesure l'écran entier —
     Terminer était rogné d'exactement cette hauteur.) */
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: stretch;
}

.scan-camera__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}

.scan-camera__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  margin: -4px -4px 0 0;
  border-radius: var(--radius-md);
  color: var(--neutral-500);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.scan-camera__close:hover {
  background: var(--neutral-100);
  color: var(--neutral-900);
}

.scan-camera__viewport {
  position: relative;
  aspect-ratio: 4 / 3;
  background: #000;
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.scan-camera__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.scan-camera__frame {
  position: absolute;
  inset: 22% 12%;
  border: 2px solid rgba(255, 255, 255, 0.9);
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.25);
  pointer-events: none;
}

.scan-camera__hint {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm);
  color: var(--neutral-500);
  text-align: center;
}

/* --- Plein écran (.scan-camera--full, toujours posé par le JS) ---
   Construit par scanCameraSession() (assets/js/scan_camera.js) ; le mode
   « un scan » (scanCamera) réutilise le même overlay. En session, le viseur
   reste ouvert entre les produits ; la confirmation s'affiche DANS le
   viseur (.scan-camera__result) car les toasts (z-index 200) passent sous
   l'overlay (300). Fond sombre : les alphas blancs/couleur sont des
   littéraux assumés. */
.scan-camera--full .scan-camera__dialog {
  position: relative;
  overflow: hidden;
  width: 100%;
  background: var(--neutral-900);
  display: flex;
  flex-direction: column;
  /* Bas : plancher space-10 même si env() vaut 0 (mode barre d'état opaque —
     l'indicateur home fait ~34px), sinon safe-area + marge. */
  padding: 0 0 max(calc(env(safe-area-inset-bottom) + var(--space-3)), var(--space-10));
}

/* Caméra « transparente » : le flux vidéo tapisse tout l'écran (absolu, sous
   les contrôles) et tout le reste flotte par-dessus en surfaces translucides —
   plus aucun bandeau web. La barre d'état système au-dessus reste peinte par
   iOS (theme-color), hors de portée du web. */
.scan-camera--full .scan-camera__viewport {
  position: absolute;
  inset: 0;
  border-radius: 0;
  aspect-ratio: auto;
}

.scan-camera--full .scan-camera__header,
.scan-camera--full .scan-camera__hint,
.scan-camera--full .scan-camera__result,
.scan-camera--full .scan-camera__count,
.scan-camera--full .scan-camera__done {
  /* Étage explicite au-dessus de la vidéo : à z-index auto, l'ordre du
     document gagne et la vidéo (absolue, déclarée APRÈS l'en-tête)
     recouvrait chip / torche / ✕. */
  position: relative;
  z-index: 1;
  margin-left: max(var(--space-4), env(safe-area-inset-left));
  margin-right: max(var(--space-4), env(safe-area-inset-right));
}

/* En-tête flottant : un simple scrim de lisibilité qui se fond dans le flux. */
.scan-camera--full .scan-camera__header {
  margin: 0;
  padding: max(var(--space-3), env(safe-area-inset-top))
           max(var(--space-4), env(safe-area-inset-right))
           var(--space-6)
           max(var(--space-4), env(safe-area-inset-left));
  background: linear-gradient(to bottom, rgba(17, 24, 39, 0.55), rgba(17, 24, 39, 0));
}

.scan-camera__mode {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  min-height: 32px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(219, 238, 250, 0.35);
  background: rgba(219, 238, 250, 0.14);
  color: var(--brand-100);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.scan-camera__mode svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.scan-camera__torch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  margin-left: auto;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: var(--radius-md);
  background: none;
  color: rgba(255, 255, 255, 0.85);
  cursor: pointer;
}

.scan-camera__torch[aria-pressed="true"] {
  background: rgba(255, 255, 255, 0.9);
  color: var(--neutral-900);
}

.scan-camera--full .scan-camera__close {
  margin: 0;
  color: rgba(255, 255, 255, 0.8);
}

.scan-camera--full .scan-camera__close:hover {
  background: rgba(255, 255, 255, 0.12);
  color: var(--neutral-0);
}

.scan-camera--full .scan-camera__hint {
  margin-top: auto;   /* cale le groupe aide / chip / Terminer en bas */
  align-self: center;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: rgba(17, 24, 39, 0.45);
  color: rgba(255, 255, 255, 0.85);
}

.scan-camera__result {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 56px;
  margin-top: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-lg);
  background: rgba(255, 255, 255, 0.1);
  color: var(--neutral-0);
  font-size: var(--text-sm);
}

.scan-camera__result--error {
  background: rgba(168, 50, 50, 0.4);   /* --danger à 40 % sur fond sombre */
  border-color: rgba(251, 237, 237, 0.3);
}

.scan-camera__result-ok {
  flex-shrink: 0;
  color: #7fd0a5;                        /* --success éclairci pour fond sombre */
  font-weight: var(--weight-semibold);
}

.scan-camera__result-text {
  flex: 1 1 auto;
  min-width: 0;
}

.scan-camera__result-title {
  display: block;
  font-weight: var(--weight-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.scan-camera__result-meta {
  display: block;
  font-size: var(--text-xs);
  color: rgba(255, 255, 255, 0.72);
}

.scan-camera__result-undo {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.14);
  color: var(--neutral-0);
  font-size: var(--text-lg);
  cursor: pointer;
}

.scan-camera__result-undo:disabled {
  opacity: 0.5;
  cursor: default;
}

.scan-camera__result-undo svg {
  width: 18px;
  height: 18px;
}

.scan-camera__count {
  margin: var(--space-2) 0 0;
  font-size: var(--text-xs);
  color: rgba(255, 255, 255, 0.6);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.scan-camera__done {
  min-height: 48px;
  margin-top: var(--space-3);
  border: 0;
  border-radius: var(--radius-lg);
  background: var(--neutral-0);
  color: var(--neutral-900);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

/* =============================================================
   .nav__bell / .notif-panel — cloche de notifications (header)
   -------------------------------------------------------------
   API :
     <button class="nav__bell" data-notif-toggle hidden>
       <svg …/> <span class="nav__bell-dot" data-notif-dot hidden></span>
     </button>
     <div class="notif-panel" id="notif-panel" data-notif-panel hidden> … </div>
   Notes :
     - La cloche est calée en haut à droite (mobile ET desktop) via
       margin-left:auto ; elle reste `hidden` tant que notifications.js n'a pas
       confirmé le support (Service Worker + Push + clé VAPID).
     - Le panneau est un popover fixe ancré sous le header (z-index 50 :
       au-dessus de la barre de scan de l'accueil, cf. .scan-bar).
     - Hauteur bornée (max-height + overflow) pour ne jamais passer sous la
       barre SORTIE / ENTRÉE : le trop-plein défile dans le panneau.
     - assets/js/notifications.js pilote l'ouverture et les préférences.
   ============================================================= */
.nav__bell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  width: var(--tap-min);
  height: var(--tap-min);
  flex: 0 0 auto;
  margin-left: auto;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: var(--radius-md);
  color: var(--neutral-0);
  cursor: pointer;
  transition: background-color 0.12s ease;
}

.nav__bell:hover {
  background: rgba(255, 255, 255, 0.14);
}

.nav__bell-dot {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-pill);
  background: var(--danger);
  border: 2px solid var(--brand-900);
}

/* Quand la cloche est présente, .nav__end n'a plus besoin de pousser :
   la cloche a déjà consommé l'espace libre. Sur desktop elle reste à gauche
   du nom/déconnexion (cluster droit). */
.nav__bell + .nav__end {
  margin-left: var(--space-2);
}

.notif-panel {
  /* Ancrage haut-droite et réserve basse exprimés en variables locales pour
     éviter la duplication dans le calc() de max-height. */
  --notif-top: calc(env(safe-area-inset-top) + 64px + var(--space-2));
  --scan-bar-reserve: calc(58px + var(--space-6) + env(safe-area-inset-bottom));
  position: fixed;
  /* z-index 50 : au-dessus de la barre de scan fixe (.scan-bar, 40) pour ne
     plus être rogné par SORTIE / ENTRÉE ; reste sous le drawer (100). */
  z-index: 50;
  top: var(--notif-top);
  right: max(var(--space-4), env(safe-area-inset-right));
  width: min(300px, calc(100vw - 2 * var(--space-4)));
  /* Bornage vertical : le menu s'arrête au-dessus de la barre de scan (même
     empreinte que .scan-bar__spacer) ; au-delà, il défile en interne plutôt
     que d'être coupé. La réserve s'applique sur toutes les pages (la cloche
     est globale) — sans effet visible ailleurs, le menu tenant sur quelques
     lignes. */
  max-height: calc(100dvh - var(--notif-top) - var(--scan-bar-reserve)
                   - var(--space-4));
  overflow-y: auto;
  padding: var(--space-4);
  background: var(--neutral-0);
  border: var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  color: var(--neutral-900);
}

.notif-panel[hidden] {
  display: none;
}

.notif-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-3);
}

.notif-panel__title {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
}

.notif-panel__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-8);
  margin: calc(-1 * var(--space-2)) calc(-1 * var(--space-2)) 0 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-md);
  color: var(--neutral-500);
  cursor: pointer;
}

.notif-panel__close:hover {
  background: var(--neutral-100);
  color: var(--neutral-900);
}

.notif-panel__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--text-sm);
  cursor: pointer;
}

.notif-panel__row input {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  accent-color: var(--brand-900);
  cursor: pointer;
}

.notif-panel__main {
  font-weight: var(--weight-medium);
  font-size: var(--text-base);
}

.notif-panel__types {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: var(--border-subtle);
}

.notif-panel__types[hidden] {
  display: none;
}

.notif-panel__hint {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--neutral-500);
}
