/* GestLab — feuille de style unique.
 *
 * Écart assumé par rapport au plan, qui prévoyait Tailwind : celui-ci impose
 * soit une chaîne de compilation Node, soit un CDN à l'exécution. Pour un
 * développeur seul qui exploitera ce système une décennie sur un VPS, un
 * fichier CSS lisible et sans build vaut mieux.
 *
 * Ce que Tailwind apportait est désormais dans le navigateur, et c'est là que
 * cette feuille va le chercher : `@layer` ordonne la cascade, `light-dark()`
 * tient les deux thèmes en un seul jeu de jetons, `color-mix()` dérive les
 * survols, `oklch()` donne une palette qui passe à l'échelle. Rien à
 * installer, rien à recompiler au déploiement.
 *
 * Une réserve : `oklch()` ne sort pas d'ici. `print.css` reste en sRGB simple,
 * parce que les PDF archivés passeront peut-être par WeasyPrint et qu'un
 * document n'est pas un endroit où parier.
 */

@layer socle, composants, ecrans, utilitaires;

/* --- Polices ----------------------------------------------------------- *
 * IBM Plex, auto-hébergée : aucun CDN, aucune brèche CSP, fonctionne hors
 * ligne. Dessinée pour les systèmes techniques ; son mono appartient à la
 * même famille, ce qui compte dans un logiciel fait de références et de
 * montants. Sous-ensemble latin : il couvre les accents du français.
 * L'ancienne feuille déclarait « Inter » sans jamais la charger — les postes
 * du laboratoire retombaient sur Segoe UI.
 */
@font-face{font-family:"IBM Plex Sans";font-style:normal;font-weight:400;
  font-display:swap;src:url("../fonts/ibm-plex-sans-latin-400-normal.77bc02670657.woff2") format("woff2")}
@font-face{font-family:"IBM Plex Sans";font-style:normal;font-weight:500;
  font-display:swap;src:url("../fonts/ibm-plex-sans-latin-500-normal.f7016cd446d2.woff2") format("woff2")}
@font-face{font-family:"IBM Plex Sans";font-style:normal;font-weight:600;
  font-display:swap;src:url("../fonts/ibm-plex-sans-latin-600-normal.196e3cbc5fee.woff2") format("woff2")}
@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:400;
  font-display:swap;src:url("../fonts/ibm-plex-mono-latin-400-normal.79936b18df9f.woff2") format("woff2")}
@font-face{font-family:"IBM Plex Mono";font-style:normal;font-weight:600;
  font-display:swap;src:url("../fonts/ibm-plex-mono-latin-600-normal.5a2ce7798732.woff2") format("woff2")}

@layer socle {

  :root {
    /* Les deux thèmes tiennent dans un seul jeu de jetons : `light-dark()`
     * lit `color-scheme`, et le réglage manuel ne fait que le forcer. */
    color-scheme: light dark;

    /* Neutres : légèrement biaisés vers le pétrole de l'accent. Un gris
     * parfaitement neutre se lit comme un gris par défaut. */
    --ink:         light-dark(oklch(0.24 0.018 220), oklch(0.93 0.010 220));
    --ink-soft:    light-dark(oklch(0.48 0.014 220), oklch(0.73 0.012 220));
    /* Le texte discret — en-têtes de colonne, aides, libellés de tuiles — est
     * du petit texte : il doit tenir 4,5:1, sur le papier comme sur le fond.
     * À 0,60 il n'en tenait que 3,6 sur le fond clair. `test_couleurs` le
     * vérifie, avec tous les couples que forment les accents. */
    --ink-faint:   light-dark(oklch(0.54 0.012 220), oklch(0.62 0.012 220));
    --line:        light-dark(oklch(0.91 0.006 220), oklch(0.31 0.012 220));
    --line-strong: light-dark(oklch(0.82 0.010 220), oklch(0.42 0.015 220));
    --paper:       light-dark(oklch(1    0     0  ), oklch(0.22 0.014 220));
    --ground:      light-dark(oklch(0.97 0.004 220), oklch(0.16 0.012 220));

    /* L'accent ne porte plus aucun état : il est réservé aux liens et aux
     * actions. L'ancienne feuille lui faisait porter aussi le badge « info »,
     * si bien qu'un lien et un statut avaient la même couleur. */
    --accent:      light-dark(oklch(0.47 0.085 218), oklch(0.78 0.090 210));
    --accent-soft: light-dark(oklch(0.96 0.018 218), oklch(0.29 0.040 218));
    --accent-ink:  light-dark(oklch(1 0 0), oklch(0.18 0.020 220));

    /* Les cinq états, à clarté perceptuelle égale : aucun ne crie plus fort
     * qu'un autre et leur contraste sur le fond est uniforme.
     *
     * Attention à ce que cela ne garantit PAS : à clarté égale, ils sont
     * indiscernables en niveaux de gris. Ce n'est pas la couleur qui porte
     * l'état — ce sont les mots des badges (« Conforme », « Non conforme »)
     * et, sur la paillasse, la position et la forme du marqueur de la jauge.
     * La couleur ne fait que redire. */
    --ok:        light-dark(oklch(0.50 0.11 150), oklch(0.78 0.13 150));
    --ok-soft:   light-dark(oklch(0.96 0.03 150), oklch(0.28 0.05 150));
    --warn:      light-dark(oklch(0.50 0.11  75), oklch(0.78 0.13  75));
    --warn-soft: light-dark(oklch(0.96 0.03  75), oklch(0.28 0.05  75));
    --bad:       light-dark(oklch(0.50 0.15  18), oklch(0.78 0.13  18));
    --bad-soft:  light-dark(oklch(0.96 0.03  18), oklch(0.28 0.05  18));
    --busy:      light-dark(oklch(0.50 0.13 295), oklch(0.78 0.11 295));
    --busy-soft: light-dark(oklch(0.96 0.03 295), oklch(0.28 0.05 295));

    /* Densité : la comptable parcourt 200 lignes de facture, le technicien
     * est debout, avec des gants, à 60 cm de l'écran. Deux métiers, une
     * application, un multiplicateur. */
    --t-scale: 1;
    --s-scale: 1;

    /* Taille du texte : un réglage à part, qui ne touche que les caractères.
     * Lié à la densité, il interdisait d'avoir des lignes serrées ET des
     * caractères plus gros — ce que demande justement une vue qui baisse. */
    --t-taille: 1;

    /* En rem, et non en px : le rem suit la taille de police choisie dans
     * Windows ou dans le navigateur, premier réglage d'un malvoyant, que des
     * pixels ignoraient. À 16 px — la valeur par défaut de tous les
     * navigateurs — chaque valeur tombe exactement sur l'ancienne. */
    --t-micro:   calc(0.6875rem  * var(--t-scale) * var(--t-taille));  /* 11 px */
    --t-small:   calc(0.75rem    * var(--t-scale) * var(--t-taille));  /* 12 px */
    --t-body:    calc(0.875rem   * var(--t-scale) * var(--t-taille));  /* 14 px */
    --t-lead:    calc(0.96875rem * var(--t-scale) * var(--t-taille));  /* 15,5 px */
    --t-h2:      calc(1.0625rem  * var(--t-scale) * var(--t-taille));  /* 17 px */
    --t-h1:      calc(1.375rem   * var(--t-scale) * var(--t-taille));  /* 22 px */
    --t-display: calc(1.875rem   * var(--t-scale) * var(--t-taille));  /* 30 px */

    /* L'espacement suit la police du navigateur, comme un zoom, mais pas le
     * réglage de taille du texte : c'est toute la différence entre les deux. */
    --s-1: calc(0.25rem  * var(--s-scale));  /*  4 px */
    --s-2: calc(0.5rem   * var(--s-scale));  /*  8 px */
    --s-3: calc(0.75rem  * var(--s-scale));  /* 12 px */
    --s-4: calc(1rem     * var(--s-scale));  /* 16 px */
    --s-5: calc(1.375rem * var(--s-scale));  /* 22 px */
    --s-6: calc(2rem     * var(--s-scale));  /* 32 px */

    --r-sm: 3px;
    --r-md: 5px;
    --r-lg: 8px;

    --shadow: 0 1px 2px light-dark(rgb(22 25 29/6%), rgb(0 0 0/40%)),
              0 1px 8px light-dark(rgb(22 25 29/4%), rgb(0 0 0/26%));
    --font: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  }

  :root[data-theme="light"] { color-scheme: light; }
  :root[data-theme="dark"]  { color-scheme: dark; }

  :root[data-density="compact"]     { --t-scale: 0.94; --s-scale: 0.85; }
  :root[data-density="confortable"] { --t-scale: 1.09; --s-scale: 1.20; }

  :root[data-texte="grande"]      { --t-taille: 1.15; }
  :root[data-texte="tres-grande"] { --t-taille: 1.30; }

  /* Accents au choix : une liste courte, chacun vérifié dans tous les couples
   * où il paraît — lien sur papier et sur fond, texte sur bouton, message
   * d'information —, dans les deux thèmes (`test_couleurs`). Jamais un
   * sélecteur libre : une couleur quelconque peut rendre un bouton illisible
   * ou le faire passer pour un état. Les teintes restent à plus de 35° des
   * quatre teintes d'état (150, 75, 18, 295), et la chroma au moins égale à
   * celle du pétrole : un lien ne se distingue de l'encre que par sa teinte. */
  :root[data-accent="bleu"] {
    --accent:      light-dark(oklch(0.47 0.12 258), oklch(0.78 0.10 258));
    --accent-soft: light-dark(oklch(0.96 0.020 258), oklch(0.29 0.050 258));
    --accent-ink:  light-dark(oklch(1 0 0), oklch(0.18 0.020 258));
  }
  :root[data-accent="prune"] {
    --accent:      light-dark(oklch(0.47 0.12 340), oklch(0.78 0.10 340));
    --accent-soft: light-dark(oklch(0.96 0.022 340), oklch(0.29 0.050 340));
    --accent-ink:  light-dark(oklch(1 0 0), oklch(0.18 0.020 340));
  }

  /* Contraste renforcé, demandé au système (macOS « Augmenter le
   * contraste », thèmes de contraste de Windows). Les textes secondaires et
   * les filets sont ceux qui manquent de contraste : on les rapproche de
   * l'encre. Les cinq états sont foncés du même pas, pour qu'aucun ne crie
   * plus fort qu'un autre — la règle qui les a taillés tient toujours. */
  @media (prefers-contrast: more) {
    :root {
      --ink-soft:    light-dark(oklch(0.34 0.014 220), oklch(0.88 0.010 220));
      --ink-faint:   light-dark(oklch(0.40 0.012 220), oklch(0.82 0.012 220));
      --line:        light-dark(oklch(0.62 0.010 220), oklch(0.56 0.015 220));
      --line-strong: light-dark(oklch(0.45 0.012 220), oklch(0.72 0.015 220));

      --ok:   light-dark(oklch(0.40 0.11 150), oklch(0.86 0.13 150));
      --warn: light-dark(oklch(0.40 0.11  75), oklch(0.86 0.13  75));
      --bad:  light-dark(oklch(0.40 0.15  18), oklch(0.86 0.13  18));
      --busy: light-dark(oklch(0.40 0.13 295), oklch(0.86 0.11 295));
    }
    /* Un lien ne se distingue plus par sa seule couleur. Le rail n'est pas
     * concerné : tout y est lien, et le souligner n'apprendrait rien. */
    .main a:not(.btn) { text-decoration: underline; }
  }

  * { box-sizing: border-box; }

  body {
    margin: 0;
    font-family: var(--font);
    font-size: var(--t-body);
    line-height: 1.5;
    color: var(--ink);
    background: var(--ground);
    -webkit-font-smoothing: antialiased;
  }

  a { color: var(--accent); text-decoration: none; }
  a:hover { text-decoration: underline; }

  /* L'ancienne feuille remplaçait l'anneau du navigateur par un bleu presque
   * blanc — 1,13:1 sur fond blanc, là où le WCAG en exige 3. Le focus clavier
   * était donc moins visible qu'avant toute feuille de style. */
  :focus-visible {
    outline: 2.5px solid var(--accent);
    outline-offset: 1px;
    border-radius: 2px;
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
    }
  }
}

@layer composants {

  :root {
    /* Le rail garde son fond sombre dans les deux thèmes — c'est la chrome,
     * elle ne suit pas le document — mais il se détache du fond en mode
     * sombre, sans quoi la limite entre menu et contenu disparaît. */
    --rail:        light-dark(oklch(0.29 0.055 255), oklch(0.24 0.048 255));
    --rail-ink:    oklch(0.84 0.010 220);
    --rail-vif:    oklch(1 0 0);
    --rail-actif:  light-dark(oklch(0.36 0.065 255), oklch(0.31 0.058 255));
    --rail-ligne:  light-dark(oklch(0.39 0.055 255), oklch(0.34 0.048 255));
    /* Le texte discret du rail. `--ink-faint` est taillé pour le papier : sur
     * ce fond sombre, il ne tenait que 3,6:1. */
    --rail-discret: oklch(0.74 0.012 220);
  }

  /* --- Lien d'évitement -------------------------------------------------- *
   * Première tabulation de la page : sans lui, le clavier traverse six
   * entrées de menu avant d'atteindre le contenu, à chaque écran. */
  .saut {
    position: absolute; left: var(--s-2); top: -100px; z-index: 50;
    padding: var(--s-2) var(--s-3);
    background: var(--paper); color: var(--ink);
    border: 1px solid var(--accent); border-radius: var(--r-sm);
    font-size: var(--t-small); font-weight: 500;
  }
  .saut:focus { top: var(--s-2); text-decoration: none; }

  /* --- Ossature ---------------------------------------------------------- */

  /* Le rail ne porte que du texte : sa largeur suit donc le texte — densité
   * comprise —, sans quoi ses boutons se font rogner dès que les caractères
   * grossissent. 14,5 rem = 232 px au réglage par défaut. */
  .shell {
    display: grid;
    grid-template-columns: calc(14.5rem * var(--t-scale) * var(--t-taille)) 1fr;
    min-height: 100vh;
  }
  /* Les deux colonnes, et pas seulement le contenu : un enfant de grille vaut
   * `min-width: auto` et refuse de descendre sous la largeur de son contenu.
   * Le rail, qui porte une navigation plus large que l'écran en étroit,
   * étirait sinon la grille entière — donc la page — à 701 px sur un
   * téléphone de 375. */
  .shell > * { min-width: 0; }

  /* Le rail reste sous les yeux. Sans cela il s'étire à la hauteur de la page
   * — 2 865 px sur l'écran d'administration — et les réglages et la
   * déconnexion, ancrés en bas par `margin-top: auto`, se retrouvent à près de
   * deux mille pixels du regard. `align-self: start` empêche la cellule de
   * grille de l'étirer ; `overflow-y: auto` lui laisse son propre défilement
   * si la fenêtre est plus courte que son contenu. */
  .sidebar {
    background: var(--rail);
    color: var(--rail-ink);
    padding: var(--s-4) 0;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
  }

  .sidebar .brand {
    padding: 0 var(--s-5) var(--s-4);
    font-size: var(--t-lead);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--rail-vif);
    border-bottom: 1px solid var(--rail-ligne);
    margin-bottom: var(--s-3);
  }
  /* Pastille claire : le rail est sombre dans les deux thèmes, et un logo à
     l'encre foncée y disparaîtrait. Le fond blanc ne présume rien du fichier
     importé — il va aussi bien à un logo sombre qu'à un logo en couleur. */
  .sidebar .brand-logo {
    display: block;
    height: 34px;
    width: auto;
    max-width: 100%;
    margin-bottom: var(--s-3);
    padding: 5px 7px;
    background: #fff;
    border-radius: 5px;
    box-sizing: content-box;
  }
  .sidebar .brand small {
    display: block;
    font-size: var(--t-micro);
    font-weight: 400;
    color: var(--rail-discret);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    margin-top: 3px;
  }

  /* Le périmètre décide de ce que le compte voit : un technicien rattaché à
   * « MI » ne verra jamais la physico-chimie, et un compte sans unité ne voit
   * rien du tout. C'est une information de chrome, pas une note de bas de
   * page. */
  .perimetre {
    margin: calc(-1 * var(--s-2)) var(--s-5) var(--s-3);
    padding: 3px var(--s-2);
    font-size: var(--t-micro);
    letter-spacing: 0.04em;
    color: var(--rail-ink);
    background: var(--rail-actif);
    border-radius: var(--r-sm);
    display: inline-block;
    align-self: flex-start;
  }

  .sidebar nav a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-2);
    padding: var(--s-2) var(--s-5);
    color: var(--rail-ink);
    font-size: var(--t-body);
    border-left: 3px solid transparent;
  }
  .sidebar nav a:hover { background: var(--rail-actif); color: var(--rail-vif); text-decoration: none; }
  .sidebar nav a.active {
    background: var(--rail-actif);
    color: var(--rail-vif);
    border-left-color: var(--accent);
    font-weight: 500;
  }
  .sidebar nav a:focus-visible { outline-offset: -3px; }

  /* Le compte d'une file d'attente : la journée du laboratoire est pilotée
   * par les files, autant les porter dans le menu. */
  .file {
    font-family: var(--mono);
    font-size: var(--t-micro);
    font-variant-numeric: tabular-nums;
    padding: 1px 6px;
    border-radius: 9px;
    background: var(--rail-ligne);
    color: var(--rail-vif);
  }
  .sidebar nav a.active .file { background: var(--accent); color: var(--accent-ink); }

  .sidebar .who {
    margin-top: auto;
    padding: var(--s-3) var(--s-5) 0;
    border-top: 1px solid var(--rail-ligne);
    font-size: var(--t-small);
    color: var(--rail-discret);
  }
  .sidebar .who strong { display: block; color: var(--rail-ink); font-weight: 500; }

  /* `min-width: 0` n'est pas cosmétique : un enfant de grille vaut par défaut
   * `min-width: auto`, donc il refuse de descendre sous la largeur de son
   * contenu. Sans cela, la grille de la paillasse (min-width 1264 px) pousse
   * la fenêtre elle-même à 1290 px sur un téléphone, et c'est la page entière
   * qui défile latéralement au lieu du seul tableau. */
  .main { padding: var(--s-5) var(--s-6) var(--s-6); max-width: 1400px; min-width: 0; }

  /* --- Titres ------------------------------------------------------------ */

  .page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s-5);
    margin-bottom: var(--s-5);
    flex-wrap: wrap;
  }
  h1 { font-size: var(--t-h1); font-weight: 600; margin: 0 0 2px; letter-spacing: -0.01em; }
  .page-head p { margin: 0; color: var(--ink-soft); font-size: var(--t-small); }
  h2 { font-size: var(--t-h2); font-weight: 600; margin: 0 0 var(--s-3); }

  /* --- Cartes ------------------------------------------------------------ */

  .card {
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: var(--shadow);
    margin-bottom: var(--s-4);
  }
  .card > header {
    padding: var(--s-3) var(--s-4);
    border-bottom: 1px solid var(--line);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-3);
    flex-wrap: wrap;
  }
  .card > header h2 { margin: 0; }
  .card-body { padding: var(--s-4); }
  .card-body.tight { padding: 0; }

  /* --- Tableaux ---------------------------------------------------------- */

  table { width: 100%; border-collapse: collapse; }
  thead th {
    text-align: left;
    font-size: var(--t-micro);
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-faint);
    padding: var(--s-2) var(--s-3);
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
  }
  tbody td { padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--line); vertical-align: middle; }
  tbody tr:last-child td { border-bottom: 0; }
  tbody tr:hover { background: color-mix(in oklab, var(--ground) 60%, var(--paper)); }
  td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
  td.ref { font-family: var(--mono); font-size: var(--t-small); }
  /* `position: relative` en fait le bloc conteneur des textes `.sr-only`, qui
   * sont en position absolue : sans lui, ils échappent au défilement du cadre
   * et élargissent la page elle-même. La paillasse faisait ainsi défiler toute
   * la fenêtre dès que la police du navigateur dépassait 16 px. */
  .table-scroll { overflow-x: auto; max-width: 100%; position: relative; }

  .empty { padding: var(--s-6) var(--s-4); text-align: center; color: var(--ink-faint); font-size: var(--t-small); }

  /* --- Badges ------------------------------------------------------------ *
   * Le mot porte l'état ; la couleur ne fait que le redire. C'est ce qui
   * les garde lisibles à l'impression en noir et blanc. */

  .badge {
    display: inline-block;
    padding: 2px var(--s-2);
    border-radius: 20px;
    font-size: var(--t-micro);
    font-weight: 500;
    line-height: 1.6;
    white-space: nowrap;
  }
  .badge.neutral { background: var(--ground); color: var(--ink-soft); border: 1px solid var(--line-strong); }
  .badge.info    { background: var(--busy-soft); color: var(--busy); }
  .badge.ok      { background: var(--ok-soft);   color: var(--ok); }
  .badge.warn    { background: var(--warn-soft); color: var(--warn); }
  .badge.bad     { background: var(--bad-soft);  color: var(--bad); }

  /* --- Formulaires ------------------------------------------------------- */

  label { display: block; font-size: var(--t-small); font-weight: 500; color: var(--ink-soft); margin-bottom: var(--s-1); }

  input[type="text"], input[type="date"], input[type="datetime-local"],
  input[type="number"], input[type="password"], input[type="email"],
  input[type="search"], select, textarea {
    width: 100%;
    padding: 7px 10px;
    font: inherit;
    font-size: var(--t-body);
    color: var(--ink);
    background: var(--paper);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-sm);
  }
  input[readonly] { background: var(--ground); color: var(--ink-soft); }
  textarea { min-height: 70px; resize: vertical; }

  .grid { display: grid; gap: var(--s-3); }
  .grid.two { grid-template-columns: repeat(2, 1fr); }
  .grid.three { grid-template-columns: repeat(3, 1fr); }
  .grid.four { grid-template-columns: repeat(4, 1fr); }

  .field-error { color: var(--bad); font-size: var(--t-small); margin-top: 3px; }
  .help { color: var(--ink-faint); font-size: var(--t-micro); margin-top: 3px; }

  /* Choix d'un client parmi des milliers : les réponses de la recherche, une
     par ligne, chacune un bouton radio du formulaire. */
  .choix-client { margin-top: var(--s-2); }
  .choix-client label.choix {
    display: flex; gap: var(--s-2); align-items: baseline;
    margin: 0; padding: 3px 0;
    font-size: var(--t-body); font-weight: 400; color: var(--ink);
    cursor: pointer;
  }
  .choix-client input[type="radio"] { flex: none; accent-color: var(--accent); }
  .choix-client .lieu { color: var(--ink-faint); }
  /* Chemin d'un fichier resté sur le partage réseau : à recopier tel quel
     dans l'explorateur, donc en chasse fixe et sélectionnable en entier. */
  .help.mono { font-family: var(--mono); user-select: all; word-break: break-all; }

  /* --- Boutons ----------------------------------------------------------- */

  .btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px var(--s-3);
    font: inherit;
    font-size: var(--t-body);
    font-weight: 500;
    border-radius: var(--r-sm);
    border: 1px solid var(--line-strong);
    background: var(--paper);
    color: var(--ink);
    cursor: pointer;
    white-space: nowrap;
  }
  .btn:hover { background: var(--ground); text-decoration: none; }
  .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
  .btn.primary:hover { background: color-mix(in oklab, var(--accent) 86%, black); }
  .btn.danger { color: var(--bad); border-color: color-mix(in oklab, var(--bad) 40%, var(--line-strong)); }
  .btn.danger:hover { background: var(--bad-soft); }
  .btn.small { padding: var(--s-1) var(--s-2); font-size: var(--t-small); }
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }

  .actions { display: flex; gap: var(--s-2); flex-wrap: wrap; align-items: center; }

  /* --- Filtres ----------------------------------------------------------- */

  .filters {
    display: flex;
    gap: var(--s-3);
    align-items: flex-end;
    flex-wrap: wrap;
    padding: var(--s-3) var(--s-4);
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    margin-bottom: var(--s-4);
  }
  .filters .field { min-width: 170px; }
  .filters .field.wide { flex: 1; min-width: 240px; }

  /* --- Messages ---------------------------------------------------------- */

  .message {
    padding: var(--s-3) var(--s-3);
    border-radius: var(--r-md);
    margin-bottom: var(--s-3);
    font-size: var(--t-body);
    border: 1px solid;
  }
  .message.success { background: var(--ok-soft);   border-color: color-mix(in oklab, var(--ok) 35%, transparent);   color: var(--ok); }
  .message.error   { background: var(--bad-soft);  border-color: color-mix(in oklab, var(--bad) 35%, transparent);  color: var(--bad); }
  .message.warning { background: var(--warn-soft); border-color: color-mix(in oklab, var(--warn) 35%, transparent); color: var(--warn); }
  .message.info    { background: var(--accent-soft); border-color: color-mix(in oklab, var(--accent) 35%, transparent); color: var(--accent); }

  /* --- Détails ----------------------------------------------------------- */

  dl.facts { display: grid; grid-template-columns: auto 1fr; gap: 7px var(--s-5); margin: 0; }
  dl.facts dt { color: var(--ink-faint); font-size: var(--t-small); }
  dl.facts dd { margin: 0; font-size: var(--t-body); }

  /* --- Contraste renforcé : le rail -------------------------------------- *
   * Le texte discret du rail monte à l'encre du rail elle-même. */
  @media (prefers-contrast: more) {
    .sidebar .brand small, .sidebar .who { color: var(--rail-ink); }
  }

  /* --- Couleurs forcées --------------------------------------------------- *
   * Les thèmes de contraste de Windows remplacent toutes les couleurs par
   * celles du système et effacent les fonds. Tout ce qui ne se voyait que
   * par un fond disparaît : il faut le redire par un trait, ou par les
   * couleurs système posées explicitement (`forced-color-adjust: none`). */
  @media (forced-colors: active) {
    /* La bordure transparente qui réserve la place de l'indicateur actif
     * serait peinte à la couleur des liens : toutes les entrées auraient
     * l'air actives. Le rail pose donc lui-même ses couleurs système. */
    .sidebar nav a {
      forced-color-adjust: none;
      background: Canvas; color: LinkText; border-left-color: Canvas;
    }
    .sidebar nav a:hover { background: Canvas; color: LinkText; text-decoration: underline; }
    .sidebar nav a.active { background: Highlight; color: HighlightText; border-left-color: Highlight; }
    /* Sorti du forçage, l'anneau de focus garderait l'accent : invisible sur
     * le fond Highlight de l'entrée active. */
    .sidebar nav a:focus-visible { outline-color: CanvasText; }
    .sidebar nav a.active:focus-visible { outline-color: HighlightText; }
    .sidebar nav a .file { forced-color-adjust: auto; }
    .sidebar nav a.active .file { forced-color-adjust: none; background: HighlightText; color: Highlight; }
    .badge, .file, .perimetre { border: 1px solid; }
    /* La pastille blanche du logo existe pour un logo à l'encre foncée : la
     * laisser effacer, c'est le rendre invisible sur un thème sombre. */
    .sidebar .brand-logo { forced-color-adjust: none; }
  }
}

@layer ecrans {

  /* --- Paillasse : barre de scan ---------------------------------------- *
   * Un scanner de laboratoire est un clavier : il tape vite et termine par
   * Entrée. Ce champ garde le focus tant qu'on ne saisit pas, et le reprend
   * sur Échap. Il est délibérément plus grand que le reste : c'est le point
   * d'entrée de la séance, pas un filtre parmi d'autres. */
  .scan {
    display: flex;
    gap: var(--s-3);
    align-items: center;
    padding: var(--s-3) var(--s-4);
    background: var(--paper);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--r-md);
    margin-bottom: var(--s-3);
    box-shadow: var(--shadow);
  }
  .scan label {
    margin: 0;
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent);
    white-space: nowrap;
  }
  .scan input {
    flex: 1;
    font-family: var(--mono);
    font-size: var(--t-lead);
    padding: var(--s-2) var(--s-3);
  }
  .scan .help { margin: 0; white-space: nowrap; }

  /* Le champ ne cède pas le premier. Le rappel des raccourcis tient sur une
   * ligne insécable : avec de gros caractères, il réduisait le champ à
   * 131 px sur un écran de 1 024. Sous 18 rem (288 px par défaut), c'est le
   * rappel qui passe à la ligne. Au-dessus, rien ne bouge. */
  .scan { flex-wrap: wrap; }
  .scan input { min-width: min(100%, calc(18rem * var(--t-taille))); }

  /* Les files de la séance, au-dessus de la grille. */
  .files {
    display: flex;
    gap: var(--s-4);
    flex-wrap: wrap;
    margin-bottom: var(--s-3);
    font-size: var(--t-small);
    color: var(--ink-soft);
  }
  .files b { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }

  /* --- Paillasse : la grille -------------------------------------------- *
   * Largeurs figées : une ligne bascule entre le mode saisie (un champ large)
   * et le mode consulté (valeur + conformité). Sans cela, la table change de
   * largeur à chaque enregistrement et la colonne d'action sort de l'écran.
   *
   * La colonne d'état passe de 100 à 148 px : « Validé technique » mesure
   * 108 px et débordait sur la colonne voisine, sur 12 lignes de 25.
   *
   * Ces largeurs sont mesurées sur du texte : elles grandissent donc avec
   * lui, en rem × taille du texte. Au réglage par défaut, 1 rem = 16 px et
   * chaque valeur retombe sur son ancien compte de pixels. */
  .result-table { table-layout: fixed; min-width: calc(79rem * var(--t-taille)); }  /* 1264 px */
  .result-table th:nth-child(1), .result-table td:nth-child(1) { width: calc(6rem * var(--t-taille)); }
  .result-table th:nth-child(3), .result-table td:nth-child(3) { width: calc(3rem * var(--t-taille)); }
  .result-table th:nth-child(4) { width: calc(11.875rem * var(--t-taille)); }  /* valeur, ou saisie */
  .result-table th:nth-child(5) { width: calc(10.75rem * var(--t-taille)); }   /* la piste de limites */
  .result-table th:nth-child(6) { width: calc(7.375rem * var(--t-taille)); }   /* conformité */
  .result-table th:nth-child(7), .result-table td:nth-child(7) { width: calc(9.25rem * var(--t-taille)); }
  .result-table th:nth-child(8), .result-table td:nth-child(8) { width: calc(7.875rem * var(--t-taille)); }
  .result-table th:nth-child(9), .result-table td:nth-child(9) { width: calc(10.5rem * var(--t-taille)); }

  .result-row td { padding: var(--s-1) var(--s-3); }
  .result-row input[type="text"], .result-row select { padding: 5px 8px; font-size: var(--t-small); }

  /* La ligne bascule entre saisie et consultation : les champs se partagent
   * l'espace disponible plutôt que d'imposer des largeurs fixes, sinon le
   * bouton sort de la cellule dès que la fenêtre se réduit. */
  .result-row .entry { display: flex; gap: 6px; align-items: center; }
  .result-row .op { flex: 0 0 calc(3.375rem * var(--t-taille)); }  /* 54 px */
  .result-row .val { flex: 1 1 60px; min-width: 0; }
  .result-row .unit { flex: 0 0 calc(2.625rem * var(--t-taille)); color: var(--ink-faint); font-size: var(--t-small); }  /* 42 px */
  .result-row .entry .btn { flex: 0 0 auto; }
  .row-actions { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
  .row-actions form { margin: 0; }
  .result-row.saved { animation: flash 1.1s ease-out; }
  @keyframes flash { from { background: var(--ok-soft); } to { background: transparent; } }

  /* La ligne visée par le parcours clavier, après échange htmx. */
  .result-row:focus-within { background: var(--accent-soft); }

  /* La règle des quatre yeux, énoncée plutôt que subie : l'ancienne interface
   * se contentait de retirer le bouton, ce qui se lit comme une panne. */
  .quatre-yeux {
    font-size: var(--t-micro);
    color: var(--ink-faint);
    font-style: italic;
    white-space: normal;
    line-height: 1.35;
  }

  .htmx-request .btn { opacity: 0.6; pointer-events: none; }

  /* --- La jauge de limites ---------------------------------------------- *
   * Le seuil n'était écrit nulle part : l'écran disait « 250 UFC/g » et
   * « Non conforme », jamais « ≤ 10 ». La jauge le remet à l'écran.
   *
   * Piste logarithmique, du seuil ÷ 100 au seuil × 10. Tous les seuils du
   * laboratoire sont des seuils hauts seuls et les valeurs couvrent plusieurs
   * décades : sur une piste linéaire, tout ce qui dépasse se colle au bord
   * droit, indiscernable. Avec ces bornes l'encoche tombe toujours à 66,7 %,
   * quel que soit le paramètre — l'œil apprend une seule position.
   *
   * La FORME porte l'état, pas la couleur : disque sous le seuil, losange
   * au-dessus, chevron au-delà de l'échelle. La couleur ne fait que redire. */
  .jauge { position: relative; display: block; width: 100%; max-width: 150px; height: 16px; }
  .jauge i { position: absolute; }
  .jauge .piste { left: 0; right: 0; top: 6px; height: 4px; border-radius: 2px; background: var(--line-strong); }
  .jauge .hors  { left: var(--seuil); right: 0; top: 6px; height: 4px;
                  border-radius: 0 2px 2px 0; background: var(--bad); opacity: 0.30; }
  .jauge .encoche { left: var(--seuil); top: 0; bottom: 0; width: 2px; margin-left: -1px;
                    background: var(--ink-soft); border-radius: 1px; }
  .jauge .marque { left: var(--pos); top: 8px; }
  .jauge .marque::before {
    content: ""; position: absolute; width: 11px; height: 11px;
    margin: -5.5px 0 0 -5.5px; border-radius: 50%;
    background: var(--ok); box-shadow: 0 0 0 2.5px var(--paper);
  }
  .jauge[data-hors="1"] .marque::before {
    background: var(--bad); border-radius: 1px;
    width: 10px; height: 10px; margin: -5px 0 0 -5px; transform: rotate(45deg);
  }
  /* Au-delà de l'échelle par le haut : la valeur sort de la piste. */
  .jauge[data-butee="haut"] .marque::before {
    clip-path: polygon(0 0, 100% 50%, 0 100%); border-radius: 0; transform: none;
    width: 9px; height: 12px; margin: -6px 0 0 -3px; box-shadow: none;
  }
  /* Par le bas, ce n'est pas une alerte : la valeur est simplement très en
   * dessous du seuil, ce qui est le meilleur cas. Le disque conforme est
   * conservé, précédé d'un « ‹ » qui dit que l'échelle s'arrête là. */
  .jauge[data-butee="bas"] .marque::after {
    content: "‹"; position: absolute; left: -11px; top: -9px;
    font-size: 13px; line-height: 1; color: var(--ink-faint); font-family: var(--mono);
  }
  .seuil-txt {
    display: block; font-family: var(--mono); font-size: var(--t-micro);
    color: var(--ink-faint); font-variant-numeric: tabular-nums;
    white-space: nowrap; margin-top: 1px;
  }

  /* En couleurs forcées, piste, encoche et marqueur sont des fonds : la jauge
   * disparaîtrait entière, et avec elle la forme qui porte la conformité.
   * Elle garde donc ses fonds, redits en couleurs système. */
  @media (forced-colors: active) {
    .jauge { forced-color-adjust: none; }
    /* Deux teintes système franches plutôt qu'une transparence : au-delà du
     * seuil, la piste change de couleur, comme elle rougit hors de ce mode. */
    .jauge .piste { background: GrayText; }
    .jauge .hors { background: CanvasText; opacity: 1; }
    .jauge .encoche { background: CanvasText; }
    .jauge .marque::before { background: CanvasText; box-shadow: 0 0 0 2.5px Canvas; }
    .jauge[data-hors="1"] .marque::before { background: CanvasText; }
    .jauge[data-butee="bas"] .marque::after { color: CanvasText; }
  }

  /* --- Réglages d'affichage --------------------------------------------- */
  .reglages { display: flex; gap: var(--s-3); align-items: center; flex-wrap: wrap; }
  .seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: var(--r-sm); overflow: hidden; }
  .seg button {
    font: inherit; font-size: var(--t-small); padding: 4px 10px;
    background: var(--paper); color: var(--ink-soft); border: 0; cursor: pointer; white-space: nowrap;
  }
  .seg button + button { border-left: 1px solid var(--line-strong); }
  .seg button[aria-pressed="true"] { background: var(--accent); color: var(--accent-ink); }
  .seg button:focus-visible { outline-offset: -3px; }
  /* Le bouton actif ne se distingue que par son fond, que les couleurs
   * forcées effacent : le choix en cours deviendrait invisible. */
  @media (forced-colors: active) {
    .seg button[aria-pressed="true"] {
      forced-color-adjust: none; background: Highlight; color: HighlightText;
    }
    .seg button[aria-pressed="true"]:focus-visible { outline-color: HighlightText; }
  }

  /* Dans le rail, 188 px pour trois boutons au réglage par défaut — le rail
   * s'élargit avec le texte. Plutôt que de disputer les pixels au
   * rembourrage — le troisième se faisait rogner en silence par
   * l'`overflow: hidden` du groupe — les trois se partagent la largeur à
   * parts égales. Les groupes s'alignent, ce qui se lit mieux. */
  .sidebar .seg { display: flex; width: 100%; }
  .sidebar .seg button {
    flex: 1 1 0;
    min-width: 0;
    padding: 4px 2px;
    font-size: var(--t-micro);
    text-align: center;
  }

  /* --- Préférences ------------------------------------------------------- */

  /* Dans le rail, sous le nom : discret, mais toujours là — sur écran étroit,
   * c'est le seul accès aux réglages d'affichage. */
  .lien-preferences { display: inline-block; color: var(--rail-ink); font-size: var(--t-small); }
  .lien-preferences:hover { color: var(--rail-vif); }
  .lien-preferences[aria-current="page"] { color: var(--rail-vif); font-weight: 500; }

  .reglage-ligne { display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap; }
  .reglage-intitule {
    flex: 0 0 calc(10rem * var(--t-taille));
    font-size: var(--t-small); font-weight: 500; color: var(--ink-soft);
  }
  .sous-titre { font-size: var(--t-body); font-weight: 600; margin: var(--s-4) 0 var(--s-1); }
  fieldset.colonnes { border: 1px solid var(--line); border-radius: var(--r-sm); padding: var(--s-2) var(--s-3); margin: 0; }
  fieldset.colonnes legend { font-size: var(--t-small); font-weight: 600; padding: 0 var(--s-1); }
  label.case {
    display: flex; align-items: center; gap: var(--s-2);
    font-weight: 400; color: var(--ink); margin: var(--s-1) 0;
  }

  /* --- Connexion --------------------------------------------------------- */

  .login-shell { display: grid; place-items: center; min-height: 100vh; padding: var(--s-5); }
  /* En rem : avec une police système agrandie, une carte en pixels écrasait
   * ses propres champs. 21,25 et 27,5 rem = 340 et 440 px par défaut. */
  .login-card { width: 100%; max-width: 21.25rem; }
  .login-card.large { max-width: 27.5rem; }
  .login-card .brand { text-align: center; margin-bottom: var(--s-5); }
  .login-card .brand strong { font-size: var(--t-h1); letter-spacing: -0.01em; }
  .login-card .brand span {
    display: block;
    font-size: var(--t-micro);
    color: var(--ink-faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-top: 3px;
  }

  /* --- Étroit ------------------------------------------------------------ *
   * L'ancienne feuille passait simplement `.shell` à une colonne : la barre
   * sombre s'empilait au-dessus du contenu, soit ~300 px de navigation avant
   * le premier champ. Le rail devient ici une bande horizontale défilante,
   * et le contenu commence tout de suite. */
  @media (max-width: 900px) {
    .grid.two, .grid.three, .grid.four { grid-template-columns: 1fr; }

    .shell { grid-template-columns: 1fr; }
    .sidebar {
      position: sticky; top: 0; z-index: 20;
      flex-direction: row; align-items: center; gap: var(--s-3);
      padding: var(--s-2) var(--s-3);
      /* Bande horizontale : ni hauteur d'écran, ni défilement vertical. */
      height: auto;
      align-self: stretch;
      overflow-y: visible;
      overflow-x: auto;
    }
    .sidebar .brand {
      padding: 0 var(--s-3) 0 0; margin: 0; border-bottom: 0;
      border-right: 1px solid var(--rail-ligne); white-space: nowrap;
      font-size: var(--t-body);
    }
    .sidebar .brand small { display: none; }
    /* En bande horizontale, le logo se met à côté du nom et non au-dessus :
       empilé, il doublerait la hauteur de la barre. */
    .sidebar .brand {
      display: flex; align-items: center; gap: var(--s-2);
    }
    .sidebar .brand-logo {
      height: 22px; margin-bottom: 0; padding: 3px 4px;
    }
    .sidebar nav { display: flex; gap: 2px; }
    .sidebar nav a {
      padding: var(--s-1) var(--s-3); white-space: nowrap;
      border-left: 0; border-bottom: 3px solid transparent; border-radius: var(--r-sm);
    }
    .sidebar nav a.active { border-left: 0; border-bottom-color: var(--accent); }
    /* Le rail passe sur deux lignes plutôt que de tout mettre dans une seule
     * bande à faire défiler : avec six entrées, l'identité et la déconnexion,
     * le contenu fait 1 217 px pour un cadre de 375, et le bouton de
     * déconnexion se trouve à 691 px du bord — atteignable, mais après avoir
     * fait défiler tout le menu. Identité et déconnexion restent donc à
     * l'écran, et seules les entrées de navigation défilent. */
    .sidebar { flex-wrap: wrap; overflow-x: visible; }
    /* `position: relative` : le bandeau devient le bloc conteneur des
     * « , 3 en attente » lus par les lecteurs d'écran, en position absolue.
     * Sans lui, ils échappaient au défilement et élargissaient la page — à
     * 465 px sur un téléphone de 375. */
    .sidebar nav {
      order: 3; flex-basis: 100%; min-width: 0; overflow-x: auto;
      margin-top: var(--s-2); padding-bottom: 2px; position: relative;
    }
    .sidebar .who {
      order: 2; margin: 0 0 0 auto; padding: 0;
      border-top: 0; display: flex; align-items: center;
      gap: var(--s-2); white-space: nowrap;
    }
    .sidebar .who strong { display: inline; }
    .sidebar .who .lien-preferences { margin: 0; }
    .sidebar .who form { margin: 0 !important; }

    /* Pas de place ici pour trois rangées de boutons. Les réglages restent
     * joignables par le lien « Préférences », toujours visible : c'est ce qui
     * compte pour qui zoome à 200 % sur un portable, et passe sous ce seuil. */
    .sidebar .reglages { display: none; }

    /* Le périmètre porte des marges taillées pour un rail vertical — dont une
     * négative, qui le décollait vers le haut une fois la bande horizontale.
     * Ici il n'est qu'une pastille de plus sur la première ligne. */
    .perimetre { margin: 0; align-self: center; }

    .main { padding: var(--s-4) var(--s-3) var(--s-6); }
    .page-head { align-items: flex-start; }

    /* Le champ de scan est le premier outil de l'écran : il ne doit jamais
     * être le premier à céder. Le rappel des raccourcis, lui, ne sert à rien
     * sur un appareil sans touche Entrée ni Échap — et il tenait sur une
     * seule ligne insécable, ce qui écrasait le champ à une trentaine de
     * pixels. */
    .scan { flex-wrap: wrap; }
    .scan .help { display: none; }
    .scan input { flex: 1 1 60%; min-width: 0; }
  }
}

@layer utilitaires {
  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  .tile-label {
    font-size: var(--t-micro); text-transform: uppercase;
    letter-spacing: 0.05em; color: var(--ink-faint);
  }
  .tile-value {
    font-size: var(--t-display); font-weight: 600; margin-top: var(--s-1);
    font-variant-numeric: tabular-nums; line-height: 1.1;
  }
  .montant-fort { font-size: var(--t-display); font-weight: 600; font-variant-numeric: tabular-nums; }
  .montant-fort .devise { font-size: var(--t-lead); color: var(--ink-faint); font-weight: 400; }
  .nowrap { white-space: nowrap; }
  .pile-serree > * + * { margin-top: var(--s-2); }

  /* Espacement. Une petite échelle assumée : ces valeurs revenaient 40 fois
   * en attribut `style`, chacune légèrement différente de sa voisine. */
  .m-0  { margin: 0; }
  .mt-1 { margin-top: var(--s-1); }
  .mt-2 { margin-top: var(--s-2); }
  .mt-3 { margin-top: var(--s-3); }
  .mt-4 { margin-top: var(--s-4); }
  .mb-3 { margin-bottom: var(--s-3); }
  .mb-4 { margin-bottom: var(--s-4); }
  .mb-5 { margin-bottom: var(--s-5); }

  .centre { text-align: center; }
  .reference-client { font-weight: 400; color: var(--ink-faint); }
  .texte-ok { color: var(--ok); }
  .total-ttc { font-size: var(--t-h2); }
  .pleine-largeur { grid-column: 1 / -1; }

  /* Une rangée de champs qui se termine par un bouton : le motif de tous les
   * formulaires en ligne de l'application. */
  /* Liste de gestes : un bouton et son libellé par ligne. L'écran des devis
   * l'emploie pour les rapprochements clients, où chaque entrée est un
   * formulaire à elle seule. */
  .empile { list-style: none; margin: var(--s-2) 0; padding: 0; }
  .empile > li { margin-bottom: var(--s-2); }
  .empile > li > form { display: flex; gap: var(--s-2); align-items: center; }
  /* Ce que l'œil cherche prend la place ; les commandes gardent la leur. */
  .empile .pousse { flex: 1 1 auto; min-width: 0; }
  .empile .court-large { flex: 0 0 14rem; }

  .pagination { display: flex; gap: var(--s-3); align-items: center;
    justify-content: center; margin-top: var(--s-4); font-size: var(--t-small); }

  .rangee { display: flex; gap: var(--s-3); align-items: flex-end; flex-wrap: wrap; }
  .rangee.serree { gap: 6px; align-items: center; }
  .rangee .souple { flex: 1; min-width: 240px; }
  .rangee .souple.etroit { min-width: 200px; }

  /* Saisies numériques courtes : un nombre d'exemplaires n'a pas besoin de la
   * largeur d'un nom de client. */
  .saisie-courte  { width: 70px; }
  .saisie-moyenne { width: 140px; }
  .largeur-auto   { width: auto; }

  /* Un bloc d'action au pied d'une carte, détaché de ce qui précède. */
  .bloc-pied { border-top: 1px solid var(--line); background: var(--ground); }
  .bloc-separe { border-top: 1px solid var(--line); }

  .bouton-pleine-largeur { width: 100%; justify-content: center; }
  .col-case { width: 40px; }

  /* Une liste longue qui ne doit pas pousser la page : elle défile chez elle. */
  .boite-defilante { overflow-y: auto; max-height: 320px; }
  .boite-defilante.courte { max-height: 220px; }

  /* Colonnes de tableau à largeur tenue, quand l'intitulé doit rester lisible
   * et la valeur prendre le reste. */
  .col-intitule { width: 220px; }
  .col-etroite  { width: 120px; }
}

/* --- Impression -------------------------------------------------------- *
 * Hors couches, et en valeurs sRGB simples : ce bloc doit rester lisible par
 * un moteur PDF qui ne connaîtrait ni `oklch()` ni `@layer`. */
@media print {
  .sidebar, .filters, .actions, .btn, .scan, .saut, .reglages { display: none !important; }
  .shell { grid-template-columns: 1fr; }
  .main { padding: 0; max-width: none; }
  .card { border: 0; box-shadow: none; break-inside: avoid; }
  body { background: #fff; color: #000; font-size: 11pt; }
}
