/* Mon affichage Tempo admin - système de design.
   Langage visuel de shadcn/ui v4 (thème « neutral », police Geist) et
   consignes d'interface de Vercel, repris de l'administration de l'ERP
   (refonte du 26/09/2026, même anatomie des composants, docs/admin.md).
   Couleurs de la marque en dur (le site n'a qu'une marque) : Signal Green
   #60b339 (bouton principal, accent), bouton secondaire noir #141414,
   entrée active du menu #eaf5e4 - les mêmes que l'ERP en production.
   Trois couches : primitives → sémantiques → composants.
   La plupart des composants ne référencent que la couche sémantique, ce qui
   rend le thème sombre peu coûteux ; quelques exceptions assumées
   (.alert-*, .pastille-*, .btn-danger:hover) référencent directement des
   primitives sémantiques métier (--red-bg, --green-fg, etc.) plutôt que de
   dupliquer un jeu de tokens par variante. Contrastes vérifiés par
   tests/css_contract.php (les tokens de couleur restent des #rrggbb
   littéraux, que le test lit). */

/* --- Polices : Geist Sans et Geist Mono (SIL OFL 1.1, fonts/OFL.txt),
   variables, sous-ensemble latin + latin étendu + ponctuation, servies
   depuis le domaine (CSP default-src 'self') et préchargées par les layouts. */
@font-face {
  font-family: "Geist";
  src: url("../fonts/geist-sans-1.7.2.woff2") format("woff2");
  font-weight: 100 900; font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/geist-mono-1.7.2.woff2") format("woff2");
  font-weight: 100 900; font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: light dark;

  /* --- Primitives : gris neutres de shadcn (oklch → hex). */
  --gray-50:  #fafafa;
  --gray-100: #f5f5f5;
  --gray-200: #e5e5e5;
  --gray-300: #d4d4d4;
  --gray-400: #a1a1a1;
  --gray-500: #8a8a8a;
  --gray-600: #6b6b6b;
  --gray-700: #404040;
  --gray-800: #262626;
  --gray-900: #171717;
  --gray-950: #0a0a0a;
  --white:    #ffffff;

  /* --- Primitives : vert de marque (Signal Green). brand porte l'identité
     mais JAMAIS de texte (2,62:1 sur blanc) ; brand-deep porte le texte vert
     sur blanc ; brand-light la teinte d'un élément actif. Valeurs dérivées
     comme dans l'ERP (partials/marque.php) pour le même rendu. */
  --brand:       #60b339;
  --brand-deep:  #35621f;
  --brand-light: #e9f4e3;

  /* --- Primitives : sémantiques métier (pastilles, alertes). */
  --red-fg:    #b42318;
  --red-bg:    #fef3f2;
  --amber-fg:  #93520b;
  --amber-bg:  #fffaeb;
  --blue-fg:   #1d4ed8;
  --blue-bg:   #eff6ff;
  --green-fg:  #15803d;
  --green-bg:  #f0fdf4;
  /* Bandeau « mode développeur » : rouge foncé sous du texte blanc, dans
     les DEUX thèmes (--red-fg s'éclaircit en sombre). */
  --bandeau-rouge: #b42318;

  /* --- Couche sémantique : la seule que les composants utilisent.
     Valeurs littérales (le contrat CSS lit les #rrggbb). */
  --bg:             #fafafa;
  --surface:        #ffffff;
  --surface-raised: #f5f5f5;
  --border:         #e5e5e5;
  /* Bord du bouton « outline » (shadcn « input ») : décoratif, le libellé
     identifie le bouton. */
  --border-input:   #d4d4d4;
  /* Bord des CHAMPS : 3:1 au moins sur surface ET surface surélevée
     (WCAG 1.4.11) - écart voulu avec le #e5e5e5 de shadcn. */
  --border-strong:  #8a8a8a;
  --text:           #0a0a0a;
  --text-muted:     #6b6b6b;
  --accent:         #60b339;
  --accent-text:    #35621f;
  --btn-primary-bg: #60b339;
  --btn-primary-fg: #1a1a18;
  --danger:         #b42318;
  --warning:        #93520b;
  --info:           #1d4ed8;
  --success:        #15803d;
  --focus:          #35621f;
  /* Halo de l'anneau (shadcn ring-[3px] ring/50) : suit la marque. */
  --ring-halo: color-mix(in srgb, var(--focus) 28%, transparent);

  /* --- Espacement, base 4 px */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px; --sp-16: 64px;

  /* --- Rayons (shadcn --radius 10px : sm = -4, md = -2, lg, xl = +4).
     Un enfant n'a jamais un rayon supérieur à son parent. */
  --r-sm: 6px; --r-md: 8px; --r-lg: 10px; --r-xl: 14px; --r-full: 999px;

  /* --- Ombres à deux couches (lumière ambiante + directe, Vercel).
     xs : champs et boutons bordés ; 1 : cartes ; 2 : menus ; 3 : dialogues. */
  --sh-xs: 0 1px 2px rgba(0, 0, 0, .05);
  --sh-1: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px -1px rgba(0, 0, 0, .06);
  --sh-2: 0 4px 6px -1px rgba(0, 0, 0, .08), 0 2px 4px -2px rgba(0, 0, 0, .08);
  --sh-3: 0 10px 15px -3px rgba(0, 0, 0, .1), 0 4px 6px -4px rgba(0, 0, 0, .1);

  /* --- Mouvement : opacity et transform seulement, propriétés toujours listées. */
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --t-rapide: 120ms;
  --t-moyen: 180ms;

  /* --- Typographie */
  --font-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* --- Plans */
  --z-base: 0; --z-sticky: 20; --z-drawer: 40; --z-dialog: 100;

  /* --- Surlignage du menu : vert pâle de la marque. */
  --nav-actif-bg: #eaf5e4;
  --nav-actif-fg: #35621f;

  /* --- Bouton secondaire : fond noir plein (Annuler, Actions, Modifier…),
     comme l'ERP en production. */
  --btn-secondary-bg: #141414;
  --btn-secondary-fg: #ffffff;
  --btn-secondary-border: #141414;
  --btn-secondary-hover-bg: #3a3a3a;

  /* --- Coquille */
  --sidebar-w: 256px;  /* SIDEBAR_WIDTH de shadcn (16rem) */
  --rail-w: 56px;      /* mode icônes : entrées de 40 px + 8 px de marge */
  --topbar-h: 56px;
  /* Tiroir mobile (Sheet) : SIDEBAR_WIDTH_MOBILE de shadcn, 85 % au plus. */
  --drawer-w: min(85vw, 18rem);

  /* Chevron des listes (Select de shadcn) : lucide chevron-down, trait
     --text-muted du thème (redéfini en sombre). */
  --chevron-select: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6b6b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}

/* Thème sombre shadcn : fond presque noir, cartes #171717, jamais inversé.
   Le vert s'éclaircit (#98ce7e) pour rester lisible, sous du texte quasi
   noir ; le secondaire noir passe en gris clair (comme l'ERP). */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:             #0a0a0a;
    --surface:        #171717;
    --surface-raised: #262626;
    --border:         #2e2e2e;
    --border-input:   #404040;
    /* 3,53:1 sur --surface-raised (input[readonly]), 4,2:1 sur --surface. */
    --border-strong:  #7a7a7a;
    --text:           #fafafa;
    --text-muted:     #a1a1a1;
    --accent:         #98ce7e;
    --accent-text:    #98ce7e;
    --btn-primary-bg: #98ce7e;
    --btn-primary-fg: #1a1a18;
    --danger:         #f97066;
    --warning:        #fdb022;
    --info:           #84adff;
    --success:        #4ade80;
    --focus:          #98ce7e;

    --brand-deep:  #98ce7e;
    --brand-light: #2b3d21;
    --red-bg:   #2d1714; --red-fg:   #f97066;
    --amber-bg: #2b200e; --amber-fg: #fdb022;
    --blue-bg:  #111c33; --blue-fg:  #84adff;
    --green-bg: #0f2418; --green-fg: #4ade80;

    --sh-xs: 0 1px 2px rgba(0, 0, 0, .5);
    --sh-1: 0 1px 3px rgba(0, 0, 0, .5), 0 1px 2px -1px rgba(0, 0, 0, .5);
    --sh-2: 0 4px 6px -1px rgba(0, 0, 0, .55), 0 2px 4px -2px rgba(0, 0, 0, .55);
    --sh-3: 0 10px 15px -3px rgba(0, 0, 0, .6), 0 4px 6px -4px rgba(0, 0, 0, .6);
    --chevron-select: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a1a1a1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    --nav-actif-bg: #2b3d21;
    --nav-actif-fg: #98ce7e;
    --btn-secondary-bg: #8a8a8a;
    --btn-secondary-fg: #1a1a18;
    --btn-secondary-border: #8a8a8a;
    --btn-secondary-hover-bg: #9d9d9d;
  }
}

/* ============================================================ base */
*, *::before, *::after { box-sizing: border-box; }

body.admin {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Chiffres alignés partout où il y a des colonnes de valeurs. */
.table, .mono, .kpi-value, input[type=number], .totaux, .pagination { font-variant-numeric: tabular-nums; }

.mono { font-family: var(--font-mono); font-size: .9em; }
.hint { color: var(--text-muted); font-size: .8125rem; }
.msg { overflow-wrap: anywhere; }
.ok { color: var(--success); }
p { text-wrap: pretty; }

a { color: var(--accent-text); text-underline-offset: 3px; }

/* Contrôles au doigt : pas de zoom au double tap, pas de flash gris. */
a, button, input, select, textarea, summary, label {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Focus visible partout - jamais supprimé. L'anneau suit la marque. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Une ancre ne se cache pas sous la barre du haut collante. */
[id] { scroll-margin-top: calc(var(--topbar-h) + var(--sp-4)); }

::selection { background: color-mix(in srgb, var(--focus) 22%, transparent); }

/* Lien d'évitement. */
.skip-link {
  position: absolute; left: var(--sp-2); top: -100px; z-index: var(--z-dialog);
  background: var(--surface); color: var(--text);
  padding: var(--sp-2) var(--sp-4); border-radius: var(--r-md);
  border: 1px solid var(--border); box-shadow: var(--sh-2); text-decoration: none;
}
.skip-link:focus { top: var(--sp-2); }

h1, h2, h3 { text-wrap: balance; color: var(--text); }
h1 { font-size: 1.5rem; font-weight: 600; letter-spacing: -.025em; line-height: 1.2; margin: 0 0 var(--sp-4); }
h2 { font-size: 1rem; font-weight: 600; letter-spacing: -.01em; line-height: 1.3; margin: 0 0 var(--sp-3); }
h3 { font-size: .875rem; font-weight: 600; line-height: 1.4; margin: 0 0 var(--sp-2); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ============================================================ icônes */
.icon { width: 1.25em; height: 1.25em; fill: none; stroke: currentColor;
        stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
        vertical-align: -.22em; flex: none; }
.icon-sm { width: 1em; height: 1em; }

/* ============================================================ coquille
   Anatomie de la Sidebar de shadcn/ui (variante « sidebar », repli
   « icônes » ; docs/07 §1 bis) : en-tête de marque, contenu défilant,
   pied du compte, rail sur le bord. Entrées de 32 px arrondies et inscrites
   (marge de 8 px), entrée active sur fond plein (surlignage d'instance),
   titres de groupe discrets en casse normale. */
.adm-shell { display: flex; min-height: 100dvh; }

.adm-sidebar {
  width: var(--sidebar-w); flex: none;
  background: var(--bg); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100dvh;
  z-index: calc(var(--z-sticky) + 1);
}
/* SidebarHeader : 56 px, aligné sur la barre du haut. */
.adm-sidebar-entete { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-2); flex: none; }
.adm-sidebar .logo {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 40px; padding: 4px var(--sp-2); border-radius: var(--r-md);
  text-decoration: none; color: var(--text);
  transition: background-color var(--t-rapide) ease-out;
}
.adm-sidebar .logo:hover { background: var(--surface-raised); }
/* Carré de marque (team switcher de shadcn) : l'initiale de l'instance sur
   la couleur du bouton principal (paire de contraste garantie). Masqué quand
   un logo existe, sauf en mode icônes. */
.logo-marque {
  display: grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: var(--r-lg);
  background: var(--btn-primary-bg); color: var(--btn-primary-fg);
  font-size: .875rem; font-weight: 600; line-height: 1;
}
.adm-sidebar .logo.a-logo .logo-marque { display: none; }
.adm-sidebar .logo img { max-height: 28px; max-width: 120px; width: auto; height: auto; flex: none; }
.logo-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.logo-nom { font-size: .875rem; font-weight: 600; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.logo-sous { font-size: .75rem; font-weight: 400; color: var(--text-muted); }

/* SidebarContent : seule zone qui défile ; en-tête et pied restent en place. */
.adm-sidebar-contenu {
  flex: 1; min-height: 0; overflow-y: auto;
  overscroll-behavior: contain; scrollbar-width: thin;
}
.adm-nav { padding: 0 0 var(--sp-4); }
.adm-nav-group { margin-bottom: var(--sp-1); }
.adm-nav-group > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between;
  min-height: 32px; margin: 0 var(--sp-2); padding: 0 var(--sp-2);
  font-size: .75rem; font-weight: 500;
  color: var(--text-muted); border-radius: var(--r-md);
  transition: color var(--t-rapide) ease-out, background-color var(--t-rapide) ease-out;
}
.adm-nav-group > summary::-webkit-details-marker { display: none; }
.adm-nav-group > summary:hover { color: var(--text); }
.adm-nav-group[open] > summary { color: var(--text-muted); }
/* Groupe épinglé (Accueil, Favoris) : jamais replié, titre non cliquable. */
.adm-nav-group.est-epingle > summary { cursor: default; }
.adm-nav-group.est-epingle > summary:hover { color: var(--text-muted); }
/* Chevron à droite, quart de tour à l'ouverture (convention shadcn). */
.adm-nav-group .adm-group-chevron {
  transition: transform var(--t-moyen) var(--ease-out);
}
.adm-nav-group[open] .adm-group-chevron {
  transform: rotate(90deg);
}
.adm-nav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.adm-nav a {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 32px; margin: 0 var(--sp-2); padding: 6px var(--sp-2);
  color: var(--text); text-decoration: none; font-size: .875rem;
  border-radius: var(--r-md);
  transition: background-color var(--t-rapide) ease-out, color var(--t-rapide) ease-out;
}
.adm-nav a:hover { background: var(--surface-raised); }
.adm-nav a[aria-current="page"] {
  background: var(--nav-actif-bg); color: var(--nav-actif-fg);
  font-weight: 500;
}
.adm-nav .icon { color: var(--text-muted); width: 16px; height: 16px; }
.adm-nav a:hover .icon { color: var(--text); }
.adm-nav a[aria-current="page"] .icon { color: var(--nav-actif-fg); }
/* 44 px au doigt : voir le bloc (pointer: coarse) après le rail. */

/* Pastille de compteur (envois en échec, erreurs). `margin-left: auto` la colle
   au bord droit du lien ; en mode icônes, où le libellé est masqué, elle se
   pose sur le coin de l'icône - c'est justement là qu'on veut la voir. */
.adm-nav-badge {
  margin-left: auto;
  color: var(--danger);
  font-size: .75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* Puce rouge d'un titre de groupe ou de sous-menu replié : au moins une de
   ses entrées porte une pastille (sidebar.php, calcul serveur). Le texte
   « éléments en attente » est en .sr-only à l'intérieur. Déplié, les
   pastilles se voient : la puce s'efface. forced-colors : couleur système. */
.adm-nav-puce {
  display: inline-block; flex: none;
  width: 8px; height: 8px; margin-left: var(--sp-2);
  border-radius: 50%; background: var(--danger);
  vertical-align: middle;
}
.adm-nav-group[open] > summary .adm-nav-puce,
.adm-nav-submenu[open] > summary .adm-nav-puce { display: none; }
@media (forced-colors: active) {
  .adm-nav-puce { background: CanvasText; forced-color-adjust: none; }
}

/* Texte réservé aux lecteurs d'écran : retiré de l'affichage, PAS de l'arbre
   d'accessibilité (ni display:none ni visibility:hidden, qui l'en sortiraient). */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* --- Sous-menus de navigation (dépliables sans JS) */
.adm-nav-submenu-item { position: relative; }
.adm-nav-submenu { margin: 0; }
.adm-nav-submenu > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 32px; margin: 0 var(--sp-2); padding: 6px var(--sp-2);
  color: var(--text); font-size: .875rem; border-radius: var(--r-md);
  transition: background-color var(--t-rapide) ease-out;
}
.adm-nav-submenu > summary::-webkit-details-marker { display: none; }
.adm-nav-submenu > summary:hover { background: var(--surface-raised); }
.adm-nav-submenu .adm-submenu-chevron {
  margin-left: auto;
  transition: transform var(--t-moyen) var(--ease-out);
}
.adm-nav-submenu[open] .adm-submenu-chevron { transform: rotate(90deg); }
/* Sous-entrées sur un trait vertical, comme SidebarMenuSub. */
.adm-nav-submenu > ul {
  margin: 1px 0 0 calc(var(--sp-2) + var(--sp-4)); padding: 0 0 0 var(--sp-2);
  border-left: 1px solid var(--border);
}
.adm-nav-submenu > ul > li > a {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: 28px; margin: 0 var(--sp-2) 0 0; padding: 4px var(--sp-2);
  color: var(--text); text-decoration: none; font-size: .8125rem;
}
.adm-nav-submenu > ul > li > a:hover { background: var(--surface-raised); }
.adm-nav-submenu > ul > li > a[aria-current="page"] {
  background: var(--nav-actif-bg); color: var(--nav-actif-fg);
  font-weight: 500; border-radius: var(--r-md);
}

/* SidebarFooter / NavUser : le compte de l'admin, menu ouvert au-dessus. */
.adm-sidebar-pied { flex: none; padding: var(--sp-2); }
.adm-account { position: relative; }
.adm-account > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--sp-2);
  min-height: 48px; padding: var(--sp-2); border-radius: var(--r-md);
  transition: background-color var(--t-rapide) ease-out;
}
.adm-account > summary::-webkit-details-marker { display: none; }
.adm-account > summary:hover, .adm-account[open] > summary { background: var(--surface-raised); }
.adm-avatar {
  display: grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: var(--r-lg);
  background: var(--surface-raised); border: 1px solid var(--border);
  font-size: .8125rem; font-weight: 600; line-height: 1; color: var(--text);
}
.adm-compte-texte { display: flex; flex-direction: column; min-width: 0; flex: 1; line-height: 1.25; }
.adm-compte-nom { font-size: .875rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adm-compte-email { font-size: .75rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adm-compte-chevrons { flex: none; margin-left: auto; color: var(--text-muted); }
.adm-account-menu {
  position: absolute; left: 0; right: 0; bottom: calc(100% + var(--sp-1)); z-index: var(--z-drawer);
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--sh-2); padding: var(--sp-1);
  transform-origin: bottom center; overscroll-behavior: contain;
}
.adm-account[open] > .adm-account-menu { animation: adm-menu-in var(--t-rapide) var(--ease-out); }
.adm-account-menu a, .adm-account-menu button {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  min-height: 32px; padding: 6px var(--sp-2); border-radius: var(--r-sm);
  color: var(--text); text-decoration: none; font: inherit; font-size: .875rem;
  background: none; border: 0; cursor: pointer; text-align: left;
}
.adm-account-menu .icon { width: 16px; height: 16px; color: var(--text-muted); }
.adm-account-menu a:hover, .adm-account-menu button:hover { background: var(--surface-raised); }
/* Déconnexion nettement détachée du reste. */
.adm-account-sep { border: 0; border-top: 1px solid var(--border); margin: var(--sp-1) calc(-1 * var(--sp-1)); }
.adm-account-menu .is-danger { color: var(--danger); }

/* SidebarRail : bord cliquable (16 px, trait de 2 px au survol). */
.adm-sidebar-rail {
  position: absolute; top: 0; bottom: 0; right: -8px; width: 16px;
  padding: 0; border: 0; background: transparent; cursor: w-resize; z-index: 2;
}
.adm-sidebar-rail::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%; width: 2px;
  transform: translateX(-50%); background: transparent;
  transition: background-color var(--t-rapide) ease-out;
}
.adm-sidebar-rail:hover::after { background: var(--border-strong); }
.adm-sidebar-rail[hidden] { display: none; }

/* Infobulle du mode icônes (posée par admin.js, position fixe hors du
   conteneur qui défile). */
.adm-infobulle {
  position: fixed; z-index: var(--z-dialog); transform: translateY(-50%);
  padding: 4px var(--sp-2); border-radius: var(--r-md);
  background: var(--text); color: var(--bg);
  font-size: .75rem; font-weight: 500; line-height: 1.4; white-space: nowrap;
  box-shadow: var(--sh-2); pointer-events: none;
  animation: adm-fondu var(--t-rapide) ease-out;
}
.adm-infobulle[hidden] { display: none; }

.adm-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }

/* Barre du haut (site header) : 56 px, bouton de repli, séparateur, fil
   d'Ariane ; fond de page translucide et flouté quand le navigateur sait
   le faire. */
.adm-topbar {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: var(--topbar-h); padding: var(--sp-2) var(--sp-6);
  background: var(--bg); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: var(--z-sticky);
}
@supports (backdrop-filter: blur(8px)) {
  .adm-topbar { background: color-mix(in srgb, var(--bg) 82%, transparent); backdrop-filter: blur(8px); }
}
.adm-sidebar-bascule { min-height: 28px; width: 28px; padding: 0; margin-left: calc(-1 * var(--sp-2)); box-shadow: none; }
.adm-sidebar-bascule .icon { width: 16px; height: 16px; }
.adm-sidebar-bascule[hidden] { display: none; }
.adm-topbar-sep { display: block; flex: none; width: 1px; height: 16px; background: var(--border); margin: 0 var(--sp-1); }
.adm-topbar-sep[hidden] { display: none; }
.adm-crumb { margin: 0; font-size: .875rem; color: var(--text-muted); }
.adm-crumb a { color: inherit; text-decoration: none; border-radius: var(--r-sm); }
.adm-crumb a:hover { color: var(--text); }

.adm-main { padding: var(--sp-6) var(--sp-6) var(--sp-10); max-width: 1400px; width: 100%; margin: 0 auto; }

/* Bouton du tiroir : masqué sur grand écran. */
.adm-drawer-toggle { display: none; }

/* --- Mode icônes (collapsible="icon"), deux déclencheurs :
   1. sans choix de l'admin, entre 1024 et 1280 px : le rail automatique,
      piloté par la seule media query, sans JavaScript (la coquille doit
      fonctionner script désactivé) ;
   2. barre repliée par l'admin (body[data-sidebar="collapsed"], cookie lu
      par layout.php), sur tout écran de bureau.
   Les deux blocs portent EXACTEMENT les mêmes règles (contrôlé par
   tests/css_contract.php) : seul le préfixe change. */
@media (max-width: 1280px) and (min-width: 1024px) {
  body.admin:not([data-sidebar="expanded"]) {
    .adm-sidebar { width: var(--rail-w); }
    .adm-sidebar .logo { justify-content: center; padding: 4px; }
    .adm-sidebar .logo.a-logo img { display: none; }
    .adm-sidebar .logo.a-logo .logo-marque { display: grid; }
    /* Pas de recherche en mode icônes : les libellés y sont masqués, un
       champ de texte de 40 px serait inutilisable. */
    .adm-nav-search { display: none; }
    .adm-nav a, .adm-nav-submenu > summary { justify-content: center; position: relative; margin: 0 var(--sp-2); padding: var(--sp-2); min-height: 40px; }
    .adm-nav-submenu .adm-submenu-chevron { display: none; }
    .adm-nav-badge { position: absolute; top: 2px; right: 0; margin: 0; font-size: .625rem; line-height: 1; }
    /* Le titre est masqué mais son conteneur garderait son padding horizontal :
       il ne reste qu'une respiration verticale entre les groupes. */
    .adm-nav-group { padding: var(--sp-2) 0 0; margin-bottom: 0; }
    .adm-nav-group + .adm-nav-group { border-top: 1px solid var(--border); margin: var(--sp-2) var(--sp-3) 0; }
    .adm-nav-group + .adm-nav-group > ul { margin: 0 calc(-1 * var(--sp-3)); }
    .adm-nav-group > summary { min-height: 0; margin: 0; padding: 0; }
    /* Toutes les entrées restent affichées (demande de Marc du 23/09),
       groupe replié compris : le contenu d'un <details> fermé est rendu. */
    .adm-nav-group:not([open])::details-content { content-visibility: visible; display: contents; }
    .adm-nav-group > ul,
    .adm-nav-group:not([open]) > ul {
      display: flex;
    }
    /* Masquage VISUEL, jamais display:none. Le nom accessible d'un lien de
       navigation vient uniquement de .adm-nav-label - l'icône SVG est
       aria-hidden - donc le retirer de l'arbre d'accessibilité laisserait
       quinze liens anonymes. Même raisonnement pour .adm-nav-group-title,
       référencé par l'aria-labelledby de chaque <ul>. */
    .logo-text,
    .adm-nav-label,
    .adm-nav-group-title {
      position: absolute; width: 1px; height: 1px;
      overflow: hidden; clip-path: inset(50%); white-space: nowrap;
    }
    /* Sous-menu : ses entrées (sans icône) ne tiennent pas dans la barre
       étroite, et une popup serait rognée par la zone qui défile. L'icône
       du parent porte l'état actif ; le clic redéplie la barre et ouvre le
       sous-menu (admin.js, comme shadcn). */
    .adm-nav-submenu > ul { display: none; }
    .adm-nav-submenu:has(a[aria-current="page"]) > summary { background: var(--nav-actif-bg); color: var(--nav-actif-fg); }
    .adm-nav-submenu:has(a[aria-current="page"]) > summary .icon { color: var(--nav-actif-fg); }
    /* Compte : l'avatar seul, menu ouvert à droite de la barre. */
    .adm-account > summary { justify-content: center; min-height: 40px; padding: 4px; }
    .adm-compte-chevrons { display: none; }
    .adm-account-menu { left: calc(100% + var(--sp-2)); right: auto; bottom: 0; min-width: 240px; transform-origin: bottom left; }
    .adm-sidebar-rail { cursor: e-resize; }
  }
}
@media (min-width: 1024px) {
  body.admin[data-sidebar="collapsed"] {
    .adm-sidebar { width: var(--rail-w); }
    .adm-sidebar .logo { justify-content: center; padding: 4px; }
    .adm-sidebar .logo.a-logo img { display: none; }
    .adm-sidebar .logo.a-logo .logo-marque { display: grid; }
    /* Pas de recherche en mode icônes : les libellés y sont masqués, un
       champ de texte de 40 px serait inutilisable. */
    .adm-nav-search { display: none; }
    .adm-nav a, .adm-nav-submenu > summary { justify-content: center; position: relative; margin: 0 var(--sp-2); padding: var(--sp-2); min-height: 40px; }
    .adm-nav-submenu .adm-submenu-chevron { display: none; }
    .adm-nav-badge { position: absolute; top: 2px; right: 0; margin: 0; font-size: .625rem; line-height: 1; }
    /* Le titre est masqué mais son conteneur garderait son padding horizontal :
       il ne reste qu'une respiration verticale entre les groupes. */
    .adm-nav-group { padding: var(--sp-2) 0 0; margin-bottom: 0; }
    .adm-nav-group + .adm-nav-group { border-top: 1px solid var(--border); margin: var(--sp-2) var(--sp-3) 0; }
    .adm-nav-group + .adm-nav-group > ul { margin: 0 calc(-1 * var(--sp-3)); }
    .adm-nav-group > summary { min-height: 0; margin: 0; padding: 0; }
    /* Toutes les entrées restent affichées (demande de Marc du 23/09),
       groupe replié compris : le contenu d'un <details> fermé est rendu. */
    .adm-nav-group:not([open])::details-content { content-visibility: visible; display: contents; }
    .adm-nav-group > ul,
    .adm-nav-group:not([open]) > ul {
      display: flex;
    }
    /* Masquage VISUEL, jamais display:none. Le nom accessible d'un lien de
       navigation vient uniquement de .adm-nav-label - l'icône SVG est
       aria-hidden - donc le retirer de l'arbre d'accessibilité laisserait
       quinze liens anonymes. Même raisonnement pour .adm-nav-group-title,
       référencé par l'aria-labelledby de chaque <ul>. */
    .logo-text,
    .adm-nav-label,
    .adm-nav-group-title {
      position: absolute; width: 1px; height: 1px;
      overflow: hidden; clip-path: inset(50%); white-space: nowrap;
    }
    /* Sous-menu : ses entrées (sans icône) ne tiennent pas dans la barre
       étroite, et une popup serait rognée par la zone qui défile. L'icône
       du parent porte l'état actif ; le clic redéplie la barre et ouvre le
       sous-menu (admin.js, comme shadcn). */
    .adm-nav-submenu > ul { display: none; }
    .adm-nav-submenu:has(a[aria-current="page"]) > summary { background: var(--nav-actif-bg); color: var(--nav-actif-fg); }
    .adm-nav-submenu:has(a[aria-current="page"]) > summary .icon { color: var(--nav-actif-fg); }
    /* Compte : l'avatar seul, menu ouvert à droite de la barre. */
    .adm-account > summary { justify-content: center; min-height: 40px; padding: 4px; }
    .adm-compte-chevrons { display: none; }
    .adm-account-menu { left: calc(100% + var(--sp-2)); right: auto; bottom: 0; min-width: 240px; transform-origin: bottom left; }
    .adm-sidebar-rail { cursor: e-resize; }
  }
}

/* Au doigt : 44 px pour toutes les entrées du menu, sous-entrées et mode
   icônes compris. Placé APRÈS les règles des sous-menus et des deux blocs du
   mode icônes, qui le battraient sinon (même spécificité ou plus forte). */
@media (pointer: coarse) {
  .adm-nav a, .adm-nav-group > summary, .adm-nav-submenu > summary,
  .adm-nav-submenu > ul > li > a { min-height: 44px; }
  /* Mode icônes : même poids que ses deux blocs (0,3,2), placé après eux. */
  body.admin:is(:not([data-sidebar="expanded"]), [data-sidebar="collapsed"]) :is(.adm-nav a, .adm-nav-submenu > summary) { min-height: 44px; }
}

/* --- Tiroir sous 1024 px, construit sur <details> : fonctionne sans JS.
   Le <summary class="adm-drawer-toggle"> est l'enfant direct de <details
   class="adm-drawer"> (contrainte HTML : voir layout.php), donc il précède
   .adm-shell dans le flux plutôt que de vivre dans .adm-topbar. On le
   repositionne ici en survol fixe, au même endroit visuel que la barre du
   haut, et on réserve la place correspondante dans .adm-topbar pour que son
   contenu (fil d'Ariane) ne passe pas dessous. */
@media (max-width: 1023px) {
  .adm-shell { display: block; }
  .adm-drawer-toggle {
    display: flex; align-items: center; justify-content: center;
    list-style: none; cursor: pointer;
    position: fixed; top: var(--sp-3); left: var(--sp-3);
    z-index: calc(var(--z-drawer) + 1);
    min-height: 44px; min-width: 44px; padding: var(--sp-2); border-radius: var(--r-md);
    background: var(--surface); border: 1px solid var(--border); box-shadow: var(--sh-1);
  }
  .adm-drawer-toggle::-webkit-details-marker { display: none; }
  /* Sheet de shadcn (côté gauche) : panneau de 18rem au plus, entrée
     glissée, voile en fondu ; ouvert, le bouton devient la croix en haut à
     droite du panneau. */
  .adm-drawer-toggle .icone-fermer, .adm-drawer[open] .adm-drawer-toggle .icone-ouvrir { display: none; }
  .adm-drawer[open] .adm-drawer-toggle .icone-fermer { display: inline; }
  .adm-drawer[open] > .adm-drawer-toggle {
    top: var(--sp-2); left: calc(var(--drawer-w) - 44px - var(--sp-2));
    background: transparent; border-color: transparent; box-shadow: none;
  }
  .adm-drawer[open] ~ .adm-shell .adm-sidebar { animation: adm-sheet-in 300ms var(--ease-out); }
  .adm-drawer[open] .adm-drawer-scrim { animation: adm-fondu 200ms ease-out; }
  /* Le tiroir remplace le repli : pas de rail ni de bouton de repli. */
  .adm-sidebar-rail, .adm-sidebar-bascule, .adm-topbar-sep { display: none; }
  .adm-topbar { padding-left: calc(44px + var(--sp-3) + var(--sp-2)); }
  .adm-sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: var(--z-drawer);
    width: var(--drawer-w);
    box-shadow: var(--sh-3); height: 100dvh;
  }
  /* La croix se pose en haut à droite du panneau, à côté de la marque :
     l'en-tête lui laisse sa place (44 px + marge). */
  .adm-sidebar-entete { padding-right: calc(44px + var(--sp-3)); }
  /* .adm-shell est un frere de <details class="adm-drawer">, pas son enfant
     (voir layout.php) : le selecteur combinateur-frere (~) est donc
     obligatoire ici, un simple descendant ne matcherait jamais. */
  .adm-drawer:not([open]) ~ .adm-shell .adm-sidebar { display: none; }
  .adm-drawer[open] .adm-drawer-scrim {
    position: fixed; inset: 0; z-index: calc(var(--z-drawer) - 1);
    background: rgba(0, 0, 0, .5);
  }
  .adm-main { padding: var(--sp-4); }
}

/* ============================================================ widgets */
/* Card de shadcn : rayon xl, ombre sm (comme les blocs de l'ERP). */
.widget {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-xl); padding: var(--sp-6); margin-bottom: var(--sp-4);
  box-shadow: var(--sh-1);
}
@media (max-width: 767px) {
  .widget { padding: var(--sp-4); }
}
/* Un bouton qui suit directement un champ ne s'y colle pas. */
form > input + .btn, form > select + .btn, form > textarea + .btn { margin-top: var(--sp-3); }
.widget-head { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); }
.widget-notice { border-left: 3px solid var(--warning); }
/* Employée par dashboard.php sans avoir jamais été définie (orpheline
   détectée par le test du contrat en Task 1). */
.widget-health { border-left: 3px solid var(--info); }
.widget textarea { width: 100%; border: 1px solid var(--border-strong);
                   border-radius: var(--r-md); padding: var(--sp-2); background: var(--surface); color: var(--text); }
.widget hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-4) 0; }
/* Formulaires-boutons empilés (fiche commande : étiquette, e-mail de suivi…) :
   sans marge, les boutons se collent — un <p> voisin en a une, eux non. */
.form-action { margin: 0 0 var(--sp-3); }
/* Deux formulaires-boutons côte à côte (écran Santé : « vu » + « purger »). */
.form-action-row { display: flex; gap: var(--sp-3); margin: 0 0 var(--sp-3); flex-wrap: wrap; }
/* Soupape « Corriger le statut » : repliée et discrète — c'est une réparation
   de données, pas le levier principal. */
.statut-correction { margin-top: var(--sp-4); }
.statut-correction summary { cursor: pointer; }
.statut-correction form { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }

/* Remboursement : dépliant de la fiche commande. Replié par défaut comme la
   correction de statut — c'est un geste rare, qui engage de l'argent, il ne
   doit pas être à un clic de distraction. Le formulaire est en COLONNE (et non
   en ligne comme statut-correction) : trois modes, une liste de pièces et un
   motif ne tiennent pas sur une ligne. */
.remboursement { margin-top: var(--sp-4); }
.remboursement summary { cursor: pointer; }
.remboursement form { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-2); }
/* Annuler avec bon d'achat (fiche commande, 28/08/2026) : même dépliant que
   Rembourser — l'argent du client reste ici, le geste mérite le même écrin. */
.bon-achat-emission { margin-top: var(--sp-4); }
.bon-achat-emission summary { cursor: pointer; }
.bon-achat-emission form { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-2); }

/* Émettre un bon, paiement reçu dans l'ERP (bon_nouveau.php, 06/10/2026) :
   le client choisi en carte, le montant suffixé « € TTC » (le TTC se lit
   dans le champ, pas seulement dans le libellé), les trois régimes en liste,
   un récapitulatif encadré tenu à jour par admin.js. Tokens seuls : les deux
   thèmes suivent. */
.bon-client {
  display: flex; justify-content: space-between; align-items: flex-start; gap: var(--sp-4); flex-wrap: wrap;
  padding: var(--sp-4); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: var(--r-md); background: var(--surface-raised);
}
.bon-client-liens { display: flex; gap: var(--sp-4); font-size: .875rem; }
/* Bouton d'en-tête à côté d'un long paragraphe : il garde sa ligne. */
.bon-emettre { flex: none; white-space: nowrap; }
.bon-numero { white-space: nowrap; }
.bon-form { max-width: 720px; }
.bon-montant { display: flex; align-items: center; gap: var(--sp-2); max-width: 260px; }
.bon-montant input { text-align: right; font-family: var(--font-mono); font-size: 1rem; }
.bon-montant span { font-weight: 600; white-space: nowrap; }
.bon-legende { margin: var(--sp-5) 0 var(--sp-2); font-weight: 500; font-size: .875rem; }
.bon-regimes { display: grid; gap: var(--sp-2); }
.bon-regimes label.radio {
  align-items: flex-start; margin: 0; padding: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--r-md); cursor: pointer;
}
.bon-regimes label.radio:has(input:checked) { border-color: var(--accent); background: var(--surface-raised); }
.bon-regimes input[type=radio] { margin-top: 3px; }
.bon-recap {
  margin: var(--sp-5) 0 var(--sp-4); padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md); background: var(--surface-raised); border: 1px dashed var(--border-strong);
}

/* Choisir un compte client (08/10/2026) : la liste déroulante sous les
   « Emails autorisés » d'un code et sous le champ client d'un bon (admin.js,
   « Choisir un compte client »). Le panneau est posé par le script. Une
   adresse sans compte est dite en rouge, sans bloquer. Tokens seuls. */
/* Le panneau suit le champ dans le document : sans `top`, il se pose juste
   sous lui, par-dessus l'avertissement et l'aide qui suivent. */
.comptes { position: relative; }
.comptes-panneau {
  position: absolute; z-index: 30; left: 0; margin-top: var(--sp-1);
  width: min(36rem, 100%); max-height: 22rem; overflow: auto; padding: var(--sp-2);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--sh-3);
}
.comptes-panneau[hidden] { display: none; }
.comptes-recherche { width: 100%; margin-bottom: var(--sp-2); }
.comptes-liste { list-style: none; margin: 0; padding: 0; }
.comptes-candidat {
  display: block; width: 100%; padding: var(--sp-2) var(--sp-3); border: 0; border-radius: var(--r-sm);
  background: none; color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
.comptes-candidat:hover, .comptes-candidat.is-actif { background: var(--surface-raised); }
.comptes-candidat:disabled { opacity: .55; cursor: not-allowed; }
.comptes-detail { display: block; font-size: .8125rem; color: var(--text-muted); }
.comptes-badge {
  display: inline-block; margin-left: var(--sp-2); padding: 0 var(--sp-2); border-radius: var(--r-full);
  font-size: .75rem; background: var(--surface-raised); color: var(--text-muted);
}
.comptes-badge-alerte { background: var(--red-bg); color: var(--red-fg); }
.comptes-etat { margin: var(--sp-2) 0 0; font-size: .8125rem; color: var(--text-muted); }
.comptes-etat:empty { display: none; }
.comptes-alerte {
  margin: var(--sp-2) 0 0; padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md);
  background: var(--red-bg); color: var(--red-fg); font-size: .875rem;
}
.comptes-alerte[hidden] { display: none; }

/* Grille tarifaire (Matériaux → Tarifs, 08/10/2026) : une croix par ligne,
   les erreurs du serveur en liste en tête de la fenêtre. */
.grille-saisie td { vertical-align: middle; }
.grille-retirer { min-width: 2rem; font-size: 1rem; line-height: 1; }
.grille-erreurs { margin: 0; padding-left: 1.2em; }

/* Étiquette de bouton radio : même dessin que label.checkbox (ligne 600), pour
   que les deux familles de choix se ressemblent dans un même formulaire. */
label.radio { font-weight: 400; display: flex; align-items: center; gap: var(--sp-2); }

/* Les pièces remboursables, en retrait sous leur bouton radio : le retrait dit
   que la liste APPARTIENT à ce choix-là et pas aux deux autres. */
.refund-lines {
  margin: 0 0 var(--sp-2) var(--sp-4);
  padding-left: var(--sp-3);
  border-left: 2px solid var(--border);
}

/* Avoirs émis sur une commande. */
.credit-notes { list-style: none; padding: 0; margin: 0 0 var(--sp-3); }
.credit-notes li {
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--border);
}
.credit-notes li:last-child { border-bottom: 0; }

/* Templates d'e-mails : matrice de couverture et aperçu comparatif. */
.couverture td { vertical-align: middle; }
.couverture-case {
  display: inline-block; padding: 2px var(--sp-2);
  border-radius: var(--r-full); font-size: .8rem; text-decoration: none;
  border: 1px solid var(--border);
}
.couverture-case.est-complet   { background: var(--green-bg); color: var(--success); border-color: var(--success); }
/* « Modèle intégré » n'est PAS un manque : ni rouge, ni ambre. C'est l'état
   normal d'un type que personne n'a eu besoin de retoucher. */
.couverture-case.est-incomplet { background: var(--bg); color: var(--text-muted); }
.couverture-case:hover { border-color: var(--accent); }

/* Édition des textes d'un e-mail : la langue source reste sous les yeux. */
.textes-emails td { vertical-align: top; }
.textes-emails textarea { width: 100%; resize: vertical; }
.texte-source { color: var(--text-muted); font-size: .9rem; max-width: 32em; }
/* Un texte vide sortirait chez le client sous la forme de sa clé : il doit se
   voir dans le formulaire, pas seulement dans la matrice. */
.texte-manquant { border-color: var(--warning); }

.apercu-dialog {
  width: 92%; max-width: 760px; border: 1px solid var(--border); padding: 0;
  border-radius: var(--r-lg); box-shadow: var(--sh-3);
}
/* La comparaison a besoin de place : deux volets côte à côte tiennent mal
   dans 760 px. Le dialogue s'élargit quand il en porte plusieurs. */
.apercu-dialog:has(.apercu-cote-a-cote) { max-width: 1200px; }
.apercu-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: var(--sp-3) var(--sp-4); background: #141414; color: #fff;
}
.apercu-fermer {
  background: transparent; border: none; color: #fff;
  cursor: pointer; font-size: 24px; line-height: 1;
}
.apercu-panes { display: block; }
.apercu-cote-a-cote {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--sp-4); padding: var(--sp-4);
}
.apercu-volet-tete {
  font-weight: 600; margin: 0 0 var(--sp-2); padding: var(--sp-3) var(--sp-4) 0;
}
.apercu-cote-a-cote .apercu-volet-tete { padding: 0; }
.apercu-cadre { width: 100%; height: 520px; border: 1px solid var(--border); background: #fff; }

/* Liste repliée (Assistance, Signalements).
   Une ligne par entrée : numéro, extrait, e-mail, état. Le détail s'ouvre au
   clic — `<details>` natif, donc rien à charger et Ctrl+F ouvre la bonne ligne
   tout seul. */
.ligne-repliee {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-xl); margin-bottom: var(--sp-2); box-shadow: var(--sh-xs);
}
.ligne-repliee > summary {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  cursor: pointer; list-style: none;
}
/* Le marqueur par défaut diffère selon le navigateur : on pose le nôtre. */
.ligne-repliee > summary::-webkit-details-marker { display: none; }
.ligne-repliee > summary::before {
  content: '▸'; color: var(--text-muted); flex: none;
  transition: transform .15s;
}
.ligne-repliee[open] > summary::before { transform: rotate(90deg); }
.ligne-repliee > summary:hover { background: var(--bg); }
/* Le focus clavier doit se voir : la ligne EST le bouton. */
.ligne-repliee > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.ligne-num { font-variant-numeric: tabular-nums; color: var(--text-muted); flex: none; }
/* L'extrait prend la place restante et se coupe : c'est lui qui doit céder
   quand la fenêtre rétrécit, pas l'e-mail ni l'état. */
.ligne-extrait {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ligne-email { flex: none; color: var(--text-muted); font-size: .85rem; }
.ligne-a-traiter { border-left: 3px solid var(--warning); }
.ligne-detail {
  padding: 0 var(--sp-4) var(--sp-4);
  border-top: 1px solid var(--border);
  padding-top: var(--sp-4);
}
/* Sous 700 px, l'e-mail passe à la ligne plutôt que d'écraser l'extrait. */
@media (max-width: 700px) {
  .ligne-repliee > summary { flex-wrap: wrap; }
  .ligne-extrait { flex-basis: 100%; order: 3; }
}

/* Réponse au client (Assistance, Signalements).
   Le formulaire est SÉPARÉ de celui du statut — un e-mail ne se rattrape pas —
   d'où le filet qui le détache visiblement de la barre de filtres au-dessus. */
.reponse-form {
  border-top: 1px solid var(--border);
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
}
.reponse-form label { display: block; margin-bottom: var(--sp-2); font-weight: 600; }
/* Langue du destinataire, toujours visible : c'est ce qu'on doit savoir AVANT
   d'écrire. Une pastille qui n'apparaît que parfois ne se remarque pas quand
   elle apparaît. */
.pastille-langue {
  display: inline-block; margin-left: var(--sp-2);
  padding: 2px var(--sp-2); border-radius: var(--r-full);
  background: var(--bg); border: 1px solid var(--border);
  color: var(--text-muted); font-size: .8rem; font-weight: 400;
}
.reponse-form textarea { resize: vertical; }
.reponse-actions {
  display: flex; align-items: center; gap: var(--sp-4);
  margin-top: var(--sp-3); flex-wrap: wrap;
}
.reponse-actions label { margin: 0; font-weight: 400; }
/* Ce qui est DÉJÀ parti : liseré vert, comme une réponse acquise. */
.reponse-envoyee {
  border-left: 3px solid var(--success);
  padding-left: var(--sp-4);
  margin: var(--sp-4) 0;
}
.reponse-envoyee-head {
  color: var(--text-muted); font-size: .85rem; margin: 0 0 var(--sp-2);
}
.widget img { border-radius: var(--r-sm); }

.two-cols { display: grid; grid-template-columns: 1.6fr 1fr; gap: var(--sp-5); align-items: start; }
/* `min-width: 0` n'est pas cosmétique : une colonne de grille vaut par défaut
   `min-width: auto`, c'est-à-dire qu'elle REFUSE de descendre sous la largeur
   de son contenu. Un tableau large à l'intérieur poussait donc la colonne, la
   colonne poussait la grille, et la fiche matériau débordait de l'écran —
   le conteneur défilant du tableau ne pouvait rien y faire. */
.two-cols > * { min-width: 0; }
@media (max-width: 1000px) { .two-cols { grid-template-columns: 1fr; } }

/* Liste des transporteurs Packlink (cases a cocher) : grille sans puces, une
   pilule par transporteur. */
.carrier-list {
  list-style: none; margin: var(--sp-3) 0 var(--sp-5); padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--sp-2);
}
.carrier-list li {
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: var(--sp-2) var(--sp-3); background: var(--surface);
}
/* Le surlignage ne suit que la case du TRANSPORTEUR (label direct), pas
   celles de ses sous-possibilités. */
.carrier-list > li:has(> label.checkbox > input:checked) { border-color: var(--accent); background: var(--surface-raised); }
.carrier-list label.checkbox { margin: 0; }
/* Sous-possibilités d'un transporteur (collecte, remise) : une case par
   variante, en retrait sous la case du transporteur. */
.carrier-variants {
  list-style: none; margin: var(--sp-2) 0 0; padding: var(--sp-2) 0 0 var(--sp-4);
  border-top: 1px solid var(--border); font-size: .88rem;
}
.carrier-variants li { border: 0; padding: var(--sp-1) 0; background: transparent; }

/* ============================================================ formulaires
   Anatomie shadcn Label / Input / Select / Textarea : 36 px, rayon md, ombre
   xs, 14 px ; focus = bord de l'anneau + halo de 3 px. Écart voulu : le bord
   des champs reste à 3:1 (--border-strong, WCAG 1.4.11). */
label { display: block; margin: var(--sp-3) 0 var(--sp-2); font-weight: 500; font-size: .875rem; line-height: 1.25; }
label.checkbox { font-weight: 400; display: flex; align-items: center; gap: var(--sp-2); cursor: pointer; }

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=url], input[type=search], input[type=tel],
input[type=time], input[type=datetime-local], select, textarea {
  width: 100%; padding: 6px var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  font-size: .875rem; line-height: 1.5; font-family: inherit;
  background: var(--surface); color: var(--text);
  min-height: 36px; box-shadow: var(--sh-xs);
  transition: border-color var(--t-rapide) ease-out, box-shadow var(--t-rapide) ease-out;
}
/* Anatomie shadcn Textarea : 64 px au moins, hauteur qui suit le contenu
   (field-sizing, repli : hauteur fixe redimensionnable), bornée à 60 % de
   l'écran. */
textarea { min-height: 64px; max-height: 60vh; padding: var(--sp-2) var(--sp-3); resize: vertical; field-sizing: content; }
input::placeholder, textarea::placeholder { color: var(--text-muted); opacity: 1; }
/* Cases, boutons radio et champs fichier gardent l'anneau plein de
   :focus-visible (le bord n'a pas d'effet sur une case native). */
input:not([type=checkbox]):not([type=radio]):not([type=file]):focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid transparent; outline-offset: 0;
  border-color: var(--focus); box-shadow: 0 0 0 3px var(--ring-halo);
}
/* Qualifié par l'élément : bat la bordure des champs (input[type=text]…). */
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 {
  border-color: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 25%, transparent);
}
/* Thème sombre de Windows : le <select> et ses options ne suivent pas la page
   sans couleurs explicites (consigne Vercel). */
select {
  cursor: pointer; background-color: var(--surface); color: var(--text);
}
select option { background-color: var(--surface); color: var(--text); }
/* Anatomie shadcn Select : chevron discret (16 px, texte secondaire) au
   lieu de la flèche du système. SVG en data: (CSP img-src 'self' data:),
   une couleur par thème (--chevron-select). Liste à choix multiples et
   liste déroulée (size) gardent le rendu natif. */
select:not([multiple]):not([size]) {
  appearance: none; -webkit-appearance: none;
  background-image: var(--chevron-select); background-repeat: no-repeat;
  background-position: right 10px center; background-size: 16px 16px;
  padding-right: var(--sp-8);
}
/* Liste ouverte façon shadcn (SelectContent / SelectItem) là où le
   navigateur sait la dessiner (select personnalisable) ; ailleurs, la liste
   native du système reste. */
@supports (appearance: base-select) {
  select:not([multiple]):not([size]),
  select:not([multiple]):not([size])::picker(select) { appearance: base-select; }
  select:not([multiple]):not([size])::picker-icon { display: none; }
  select:not([multiple]):not([size])::picker(select) {
    margin-block: var(--sp-1); padding: var(--sp-1);
    background: var(--surface); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--r-md);
    box-shadow: var(--sh-2); max-height: min(24rem, 60vh);
  }
  select:not([multiple]):not([size]) option {
    display: flex; align-items: center; gap: var(--sp-2);
    min-height: 32px; padding: 6px var(--sp-2); border-radius: var(--r-sm);
    font-size: .875rem;
  }
  select:not([multiple]):not([size]) option:hover,
  select:not([multiple]):not([size]) option:focus-visible { background: var(--surface-raised); outline: none; }
  select:not([multiple]):not([size]) option:checked { font-weight: 500; }
  select:not([multiple]):not([size]) option::checkmark { order: 1; margin-left: auto; color: var(--text-muted); }
}
@media (forced-colors: active) {
  select:not([multiple]):not([size]) { appearance: auto; background-image: none; padding-right: var(--sp-3); }
}
input[type=checkbox], input[type=radio] {
  width: 16px; height: 16px; margin: 0; flex: none;
  accent-color: var(--btn-primary-bg); cursor: pointer;
}
input[type=file] { font: inherit; font-size: .875rem; color: var(--text-muted); }
input[type=file]::file-selector-button {
  font: inherit; font-weight: 500; margin-right: var(--sp-3);
  padding: 6px var(--sp-3); border-radius: var(--r-md);
  border: 1px solid var(--border-input); background: var(--surface); color: var(--text);
  box-shadow: var(--sh-xs); cursor: pointer;
}
input:disabled, select:disabled, textarea:disabled {
  opacity: .5; cursor: not-allowed; background: var(--surface-raised);
}
input[readonly] { background: var(--surface-raised); border-style: dashed; }

/* Employée par settings.php sans avoir jamais été définie (orpheline
   détectée par le test du contrat en Task 1). */
.form-row { display: flex; gap: var(--sp-3); align-items: flex-end; flex-wrap: wrap; }
.form-row > * { flex: 1 1 200px; }

/* Quantité > 1 sur les lignes d'articles d'une commande : PASTILLE pleine
   « × N » — le simple texte rouge ne se voyait pas assez (retour Marc,
   30/07/2026), et une commande multi-exemplaires se rate facilement à la
   préparation. Texte en --surface : blanc sur le rouge sombre du thème
   clair, sombre sur le saumon du thème sombre — contraste tenu des deux
   côtés sans second token. */
.qte-multiple {
  display: inline-block;
  background: var(--danger);
  color: var(--surface);
  font-weight: 700;
  font-size: .95rem;
  padding: .1em .55em;
  border-radius: 999px;
  white-space: nowrap;
}

/* Aide-mémoire du cycle de vie d'une commande (fiche, bloc Actions),
   replié par défaut sous les boutons. */
.statut-aide { margin-top: var(--sp-3); }
.statut-aide > summary { cursor: pointer; }
.statut-aide-corps { font-size: .84rem; margin-top: var(--sp-2); }
.statut-aide-corps ul { padding-left: 1.2em; margin: var(--sp-2) 0; }
.statut-aide-corps li { margin-bottom: var(--sp-1); }

/* Dépôt de fichiers du simulateur : le champ fichier prend la place, le
   bouton reste à sa taille. */
.sim-depot input[type="file"] { flex: 1 1 260px; }
.sim-depot .btn { flex: 0 0 auto; }

/* Pas d'ombre au repos ; au focus, le halo reste (sinon seul un bord
   pointillé de 1 px signalerait le focus). */
input[readonly]:not(:focus-visible) { box-shadow: none; }
/* Recherche dans le menu (sidebar). Le champ est révélé par admin.js ;
   pendant une recherche, .nav-hide masque groupes et entrées sans résultat. */
.adm-nav-search { padding: 0 var(--sp-2) var(--sp-2); }
.adm-nav-search input { width: 100%; font-size: .8125rem; min-height: 32px; padding: 4px var(--sp-3); background: var(--surface); }
.adm-nav .nav-hide { display: none; }

/* Journal et parcours de la fiche commande : deux boutons verts, leurs
   panneaux dessous, repliés par admin.js. Le bouton ouvert se distingue par
   un liseré noir (texte des boutons), sans quitter le vert de la charte. */
.log-onglets { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.log-onglets .btn[aria-expanded="true"] { box-shadow: inset 0 0 0 2px var(--btn-primary-fg); }
.log-panneau { margin-top: var(--sp-3); }

.field-error { color: var(--danger); font-size: .84rem; margin: var(--sp-1) 0 0; }
.field-hint  { color: var(--text-muted); font-size: .84rem; margin: var(--sp-1) 0 0; }
.required-mark { color: var(--danger); }
.optional { color: var(--text-muted); font-size: .84rem; font-weight: 400; }

.filters {
  display: flex; gap: var(--sp-3); align-items: center;
  margin: var(--sp-4) 0; flex-wrap: wrap;
}
/* Filtres + encadré de définition de la vue courante : côte à côte tant que la
   place le permet, l'encadré passe dessous sur petit écran. */
.filters-row { display: flex; gap: var(--sp-4); align-items: flex-start; flex-wrap: wrap; }
.filters-row .filters { margin-bottom: 0; }
.filtre-def {
  flex: 1 1 260px; max-width: 480px; margin: var(--sp-4) 0;
  padding: var(--sp-2) var(--sp-3); font-size: .84rem; color: var(--text-muted);
  border: 1px solid var(--border); border-left: 3px solid var(--info);
  border-radius: var(--r-sm); background: var(--surface);
}
.filters input[type=text] { max-width: 260px; }
.filters input[name="q"] { max-width: 420px; flex: 1 1 220px; }
.filters input[type=number] { width: auto; max-width: 120px; }
.filters select { width: auto; }
.filters label { margin: 0; }
/* Groupe de radios exclusives dans une barre de filtres : elles restent
   ensemble quand la barre passe à la ligne, sinon « Uniquement hors stock » se
   retrouve seul sous les autres et ne se lit plus comme un choix parmi trois. */
.filtre-radios {
  display: inline-flex; align-items: center;
  gap: var(--sp-3); flex-wrap: wrap;
}

/* ============================================================ boutons
   Anatomie shadcn Button. Couleur de marque par défaut sur TOUS les boutons ;
   `.btn-primary` est un alias explicite, `.btn-secondary` (« outline »)
   l'échappatoire pour les actions de second plan (« Annuler » à côté d'un
   « Enregistrer »). Survol : contraste ACCRU (consigne Vercel), jamais un
   éclaircissement. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  border: 1px solid var(--btn-primary-bg); border-radius: var(--r-md);
  cursor: pointer; padding: 6px var(--sp-4);
  font: inherit; font-size: .875rem; font-weight: 500; line-height: 1.5;
  background: var(--btn-primary-bg); color: var(--btn-primary-fg);
  min-height: 36px; text-decoration: none; box-shadow: var(--sh-xs);
  transition: background-color var(--t-rapide) ease-out, border-color var(--t-rapide) ease-out, color var(--t-rapide) ease-out;
}
.btn .icon { width: 16px; height: 16px; }
.btn:hover { background: color-mix(in srgb, var(--btn-primary-bg) 86%, var(--text)); }
.btn:active { transform: translateY(.5px); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

.btn-primary { /* alias explicite : identique au bouton par défaut */ }

.btn-secondary {
  background: var(--btn-secondary-bg); color: var(--btn-secondary-fg);
  border-color: var(--btn-secondary-border); font-weight: 500;
}
.btn-secondary:hover { background: var(--btn-secondary-hover-bg); filter: none; }
.btn-ghost { background: none; border-color: transparent; color: var(--text); box-shadow: none; }
.btn-ghost:hover { background: var(--surface-raised); filter: none; }
.btn-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--border-input)); background: var(--surface); font-weight: 500; }
.btn-danger:hover { background: var(--red-bg); border-color: var(--danger); filter: none; }

.btn-small { padding: 4px var(--sp-3); gap: 6px; font-size: .8125rem; min-height: 32px; }

/* Au doigt (téléphone, tablette) : 44 px de cible (consigne Vercel). La
   densité 32-36 px reste celle de la souris. */
@media (pointer: coarse) {
  .btn,
  .btn-small,
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=url], input[type=search], input[type=tel],
  input[type=time], input[type=datetime-local], select { min-height: 44px; }
}

/* État de chargement : le JS pose aria-busy, le CSS fait le reste. L'icône ne
   tourne qu'après 150 ms : une réponse rapide ne fait pas clignoter. */
.btn[aria-busy="true"] { pointer-events: none; opacity: .7; }
.btn[aria-busy="true"] .icon { animation: adm-spin .8s linear infinite; animation-delay: 150ms; }
@keyframes adm-spin { to { transform: rotate(360deg); } }

/* ============================================================ messages
   Anatomie shadcn Alert : rayon lg, 14 px, bord teinté de la couleur du
   message (consigne Vercel : bordures teintées vers la même teinte). */
.alert {
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-lg);
  margin: var(--sp-3) 0; border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  display: flex; align-items: flex-start; gap: var(--sp-3);
  font-size: .875rem; line-height: 1.5;
}
.alert > .icon { margin-top: 2px; width: 16px; height: 16px; }
.alert-error   { background: var(--red-bg);   color: var(--red-fg); }
/* Un message vide posé par script reste caché : `display` battrait l'attribut hidden. */
.alert[hidden] { display: none; }
.alert-success { background: var(--green-bg); color: var(--green-fg); }
.alert-fermer {
  margin-left: auto; border: 0; background: none; color: inherit; cursor: pointer;
  font-size: 1.1rem; line-height: 1; padding: 0; min-width: 24px; min-height: 24px;
  border-radius: var(--r-sm); opacity: .75;
  transition: opacity var(--t-rapide) ease-out, background-color var(--t-rapide) ease-out;
}
.alert-fermer:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); }
.alert-warning { background: var(--amber-bg); color: var(--amber-fg); }
.alert-info    { background: var(--blue-bg);  color: var(--blue-fg); }

/* Anatomie shadcn Badge « secondary » : compteurs. */
.badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; border-radius: var(--r-full);
  font-size: .75rem; font-weight: 500; line-height: 1; padding: 0 6px;
  background: var(--surface-raised); color: var(--text);
  border: 1px solid var(--border); font-variant-numeric: tabular-nums;
}
.badge-alert { background: var(--danger); color: var(--surface); }

/* ============================================================ statuts */
/* La couleur ne porte jamais seule l'information : icône + libellé. */
.status {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-2); border-radius: var(--r-sm);
  font-size: .75rem; font-weight: 500; background: var(--surface-raised); color: var(--text);
  vertical-align: middle; white-space: nowrap;
}
.status-paid, .status-completed { background: var(--green-bg); color: var(--green-fg); }
.status-processing, .status-shipped, .status-available { background: var(--blue-bg); color: var(--blue-fg); }
.status-pending { background: var(--amber-bg); color: var(--amber-fg); }
.status-cancelled, .status-failed, .status-refunded { background: var(--red-bg); color: var(--red-fg); }

/* ============================================================ tableaux
   Anatomie shadcn Table : en-têtes en casse normale, poids 500, texte
   secondaire, 40 px ; lignes séparées d'un filet, survol discret. */
.table { width: 100%; border-collapse: collapse; font-size: .875rem; }
.table th {
  text-align: left; padding: var(--sp-2) var(--sp-3); height: 40px;
  border-bottom: 1px solid var(--border);
  font-size: .8125rem; font-weight: 500; color: var(--text-muted);
  white-space: nowrap;
}
.table td {
  text-align: left; padding: var(--sp-3);
  border-bottom: 1px solid var(--border); vertical-align: middle;
}
.table tbody tr { transition: background-color var(--t-rapide) ease-out; }
.table tbody tr:hover { background: color-mix(in srgb, var(--surface-raised) 65%, transparent); }
.table .msg { max-width: 420px; overflow-wrap: anywhere; }
/* Avis à modérer : le commentaire entier, ses retours à la ligne gardés (texte
   échappé, le CSS seul met en forme). */
.table .msg-avis { display: block; white-space: pre-line; }

/* --- Journal des erreurs (/sante) : répartir la largeur selon l'utilité.
   En largeur automatique, l'URL gagnait toute la place et écrasait le message.
   Une URL est UN SEUL MOT de 150 caractères : sans césure possible, le
   navigateur n'a pas le choix, il élargit la colonne — et le message, lui,
   sait se replier, donc c'est lui qui payait. Or c'est le message qu'on lit.

   D'où la largeur fixe : les proportions sont décidées ici et ne dépendent plus
   du contenu. Priorité au message, puis à l'URL, le reste au strict nécessaire.
   La césure « anywhere » redevient obligatoire pour les deux colonnes
   monospace, sinon leur mot unique déborderait de la cellule au lieu de s'y
   plier. Et la largeur minimale fait défiler le tableau sur petit écran plutôt
   que de réduire les sept colonnes à l'illisible. */
.table-sante { table-layout: fixed; min-width: 980px; }
.table-sante th:nth-child(1), .table-sante td:nth-child(1) { width: 6%; }   /* Niveau */
.table-sante th:nth-child(2), .table-sante td:nth-child(2) { width: 33%; }  /* Message */
.table-sante th:nth-child(3), .table-sante td:nth-child(3) { width: 14%; }  /* Fichier */
.table-sante th:nth-child(4), .table-sante td:nth-child(4) { width: 22%; }  /* URL */
.table-sante th:nth-child(5), .table-sante td:nth-child(5) { width: 5%; }   /* Occurrences */
.table-sante th:nth-child(6), .table-sante td:nth-child(6) { width: 10%; }  /* Première */
.table-sante th:nth-child(7), .table-sante td:nth-child(7) { width: 10%; }  /* Dernière */
.table-sante .msg { max-width: none; }
.table-sante td { overflow-wrap: anywhere; vertical-align: top; }

/* --- Journal d'un visiteur (/audit/…) : même raisonnement.
   « Détail » porte tout ce qui compte — matière, couleur et prix pour un
   calcul, phrase d'incident pour la visionneuse. C'est la seule colonne qu'on
   vienne lire ; le chemin de page, lui, est un mot insécable qui prendrait
   toute la largeur si on le laissait faire. */
.table-audit { table-layout: fixed; min-width: 900px; }
.table-audit th:nth-child(1), .table-audit td:nth-child(1) { width: 12%; }  /* Quand */
.table-audit th:nth-child(2), .table-audit td:nth-child(2) { width: 13%; }  /* Événement */
.table-audit th:nth-child(3), .table-audit td:nth-child(3) { width: 45%; }  /* Détail */
.table-audit th:nth-child(4), .table-audit td:nth-child(4) { width: 24%; }  /* Page */
.table-audit th:nth-child(5), .table-audit td:nth-child(5) { width: 6%; }   /* Langue */
.table-audit .msg { max-width: none; }
.table-audit td { overflow-wrap: anywhere; vertical-align: top; }

/* Marquage « nouveau / non traité » : barre latérale + fond, pas la couleur seule. */
.table tr.is-new { background: var(--amber-bg); box-shadow: inset 3px 0 0 var(--warning); }
/* Tableau des commandes : même vert que l'élément actif du menu. */
.table-orders .order-row.is-new {
  background: var(--brand-light);
  box-shadow: inset 3px 0 0 var(--accent);
}
.table-orders .order-row.is-new:hover { background: var(--brand-light); filter: brightness(.96); }
/* Lignes en erreur / avertissement (audit visiteur, santé) : la LIGNE
   entière se voit — la seule première cellule colorée passait inaperçue
   (demande Marc, 30/07/2026). Fond teinté DÉRIVÉ des tokens (color-mix) :
   rouge doux en thème clair, rouge sombre en thème sombre, sans second jeu
   de couleurs à tenir ; le liseré gauche porte le niveau même pour un œil
   qui ne distingue pas les teintes. */
.table tr.level-critical td:first-child { font-weight: 700; }
.table tr.level-error,
.table tr.level-critical {
  background: color-mix(in srgb, var(--danger) 12%, var(--surface));
  box-shadow: inset 3px 0 0 var(--danger);
}
.table tr.level-error   td:first-child,
.table tr.level-critical td:first-child { color: var(--danger); }
.table tr.level-warning {
  background: color-mix(in srgb, var(--warning) 16%, var(--surface));
  box-shadow: inset 3px 0 0 var(--warning);
}
.table tr.level-warning td:first-child { color: var(--warning); }

/* Tri de colonne (liens serveur). */
.table th a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; border-radius: var(--r-sm); }
.table th a:hover { color: var(--text); }
.table th[aria-sort="ascending"]  a, .table th[aria-sort="descending"] a { color: var(--text); }

/* Champs en ligne dans les tableaux : 32 px (densité), 44 px au doigt. */
.table input[type=text], .table input[type=number], .table select, .table textarea {
  padding: var(--sp-1) var(--sp-2); font-size: .8125rem; min-height: 32px;
}
.table select:not([multiple]):not([size]) { padding-right: var(--sp-6); background-position: right 6px center; }
/* Au doigt (téléphone, tablette), les compacts remontent à la cible tactile
   de 44 px : le Stock se consulte au téléphone tous les jours, et 36 px y
   fait rater le champ voisin. La densité 36 px reste celle de la souris. */
@media (pointer: coarse) {
  .table input[type=text], .table input[type=number], .table select, .table textarea {
    min-height: 44px;
  }
}
.table.mono td, .table.mono th { font-size: .8125rem; }

/* iOS agrandit la page sur une saisie de moins de 16 px (consigne Vercel).
   Placé APRÈS les champs compacts (recherche du menu, éditeur de lignes),
   qu'il doit battre. */
@media (max-width: 767px) {
  input[type=text], input[type=email], input[type=password], input[type=number],
  input[type=date], input[type=url], input[type=search], input[type=tel],
  input[type=time], input[type=datetime-local], select, textarea,
  .adm-nav-search input,
  .table input[type=text], .table input[type=number], .table select, .table textarea { font-size: 16px; }
}

/* Défilement horizontal de secours pour les tableaux larges non convertis. */
.table-scroll { overflow-x: auto; }

/* --- Conversion en cartes sous 768 px.
   Opt-in par .table-cards : chaque <td> porte un data-label.
   Les tableaux non convertis retombent sur .table-scroll. */
@media (max-width: 767px) {
  .table-cards thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .table-cards tr {
    display: block; margin-bottom: var(--sp-3);
    border: 1px solid var(--border); border-radius: var(--r-md);
    background: var(--surface); padding: var(--sp-2);
  }
  .table-cards td { display: flex; justify-content: space-between; gap: var(--sp-3); border: 0; padding: var(--sp-2); }
  .table-cards td::before {
    content: attr(data-label); font-weight: 500; font-size: .75rem;
    color: var(--text-muted);
  }
  .table-cards td:empty { display: none; }
  /* Cartes mobiles des listes (Card de shadcn) : rayon xl, ombre sm. */
  .table-cards tr { border-radius: var(--r-xl); box-shadow: var(--sh-1); }
}

/* --- FILET GLOBAL de débordement horizontal (03/08/2026, constat Marc).
   Le commentaire ci-dessus promettait que « les tableaux non convertis
   retombent sur .table-scroll » — mais cette retombée n'existait que si le
   gabarit pensait à poser le conteneur, et 26 gabarits sur 37 ne le
   faisaient pas : tableau de bord (commandes du jour), /catalogue,
   /materiaux/photos, clients, factures, promos, SEO, stats… Sur téléphone, un
   tableau à six colonnes élargissait la PAGE ENTIÈRE, et le contenu partait
   sous le bord de l'écran.

   Corrigé ici plutôt que dans chaque gabarit : la règle est vraie pour tout
   tableau, y compris ceux qu'on écrira demain — c'est ce qui évite que le
   défaut revienne. `display: block` fait du tableau son propre conteneur de
   défilement ; le navigateur reconstruit une boîte de tableau anonyme autour
   des lignes, la mise en page interne est préservée.

   Exclus : les tableaux convertis en cartes (.table-cards), qui n'ont plus de
   largeur à faire défiler, et ceux déjà enveloppés (.table-scroll), dont le
   conteneur s'en charge. */
@media (max-width: 900px) {
  .table:not(.table-cards) { display: block; overflow-x: auto; max-width: 100%; }
  .table-scroll > .table:not(.table-cards) { display: table; overflow-x: visible; }

  /* Une référence longue sans espace (jeton, sha256, URL de suivi) élargit une
     cellule autant qu'un tableau entier : elle doit pouvoir se couper. */
  .table .mono, .table .msg { overflow-wrap: anywhere; }

  /* Les barres de filtres et d'actions passent à la ligne au lieu de pousser
     la page — même cause, même effet que les tableaux. */
  .filters, .filters-row { flex-wrap: wrap; }
}

/* Champ LEURRE du garde anti-robot (Core\AntiBot), posé sur l'écran de
   connexion depuis le 03/08/2026. Hors écran plutôt que `display: none` :
   certains robots ignorent les champs masqués par `display`, et un lecteur
   d'écran l'annoncerait — d'où aussi `aria-hidden` côté gabarit.
   Même définition que dans site.css ; elle manquait ici, et sans elle le
   champ « website » s'affichait en clair sur le formulaire d'administration. */
.form-piege { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }

/* LA CAUSE RACINE du débordement, et elle n'est pas dans les tableaux.
   `.adm-shell` est un conteneur flex ; un élément flex a `min-width: auto` par
   défaut, ce qui l'empêche de rétrécir SOUS la largeur intrinsèque de son
   contenu. Un tableau large poussait donc `.adm-main`, qui poussait
   `.adm-shell`, qui débordait la page — et le défilement horizontal emportait
   la barre latérale et l'en-tête avec lui.

   `min-width: 0` autorise l'élément à rétrécir ; le contenu large gère alors
   lui-même son défilement (règle des tableaux ci-dessus).

   On ne pose SURTOUT PAS `overflow-x: hidden` sur `html`/`body` pour cela :
   un ancêtre au débordement non visible devient le conteneur de défilement des
   éléments `position: sticky`, ce qui casserait la barre latérale collante
   (l.180) et l'en-tête collant (l.284). Le remède serait pire que le mal. */
.adm-main { min-width: 0; }

/* ============================================================ pagination
   Anatomie shadcn Pagination : liens « ghost » de 36 px, page courante en
   « outline » (bord + ombre xs). */
.pagination { margin: var(--sp-4) 0; display: flex; gap: var(--sp-1); flex-wrap: wrap; justify-content: center; }
.pagination a, .pagination span {
  padding: 0 var(--sp-3); border: 1px solid transparent;
  border-radius: var(--r-md); text-decoration: none; color: var(--text);
  font-size: .875rem; font-weight: 500;
  min-width: 36px; min-height: 36px; display: inline-flex;
  align-items: center; justify-content: center;
  transition: background-color var(--t-rapide) ease-out;
}
.pagination a:hover { background: var(--surface-raised); }
.pagination a.active, .pagination [aria-current="page"] {
  background: var(--surface); color: var(--text); border-color: var(--border-strong); box-shadow: var(--sh-xs);
}
.pagination .ellipsis { border-color: transparent; color: var(--text-muted); }
@media (pointer: coarse) {
  .pagination a, .pagination span { min-width: 44px; min-height: 44px; }
}

/* ============================================================ états vides */
.empty-state {
  text-align: center; padding: var(--sp-10) var(--sp-4); color: var(--text-muted);
}
.empty-state .icon { width: 2.5rem; height: 2.5rem; color: var(--border-strong); margin-bottom: var(--sp-3); }
.empty-state p { margin: 0 0 var(--sp-4); }

/* ============================================================ dialogues */
/* Anatomie shadcn Dialog : rayon lg, p-6, ombre lg, voile noir 50 %,
   apparition fondu + zoom 96 % (opacity / transform seulement). */
dialog {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); color: var(--text);
  padding: var(--sp-6); max-width: 460px; width: calc(100% - var(--sp-8));
  max-height: calc(100dvh - var(--sp-8)); overflow-y: auto;
  box-shadow: var(--sh-3); overscroll-behavior: contain;
}
dialog[open] { animation: adm-dialog-in var(--t-moyen) var(--ease-out); }
dialog::backdrop { background: rgba(0, 0, 0, .5); }
dialog[open]::backdrop { animation: adm-fondu var(--t-moyen) ease-out; }
dialog h2 { margin-top: 0; font-size: 1.125rem; font-weight: 600; letter-spacing: -.01em; line-height: 1.3; }
.dialog-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; margin-top: var(--sp-6); flex-wrap: wrap; }
@keyframes adm-dialog-in { from { opacity: 0; transform: translateY(4px) scale(.96); } }
@keyframes adm-menu-in { from { opacity: 0; transform: translateY(-4px) scale(.96); } }
@keyframes adm-fondu { from { opacity: 0; } }

/* ============================================================ authentification */
.adm-auth { min-height: 100dvh; display: flex; align-items: center; justify-content: center; padding: var(--sp-4); }
.auth-box {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-xl);
  padding: var(--sp-8); width: 100%; max-width: 400px; box-shadow: var(--sh-1);
}
.auth-box h1 { font-size: 1.25rem; letter-spacing: -.02em; }
.auth-box h1 span { color: var(--text-muted); font-weight: 500; }
.auth-box .btn-primary { width: 100%; margin-top: var(--sp-5); }

.totp-steps li { margin-bottom: var(--sp-3); }
.totp-secret {
  display: inline-block; background: var(--surface-raised);
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-md); border: 1px solid var(--border);
  font-size: 1rem; letter-spacing: .05em; font-family: var(--font-mono);
}
.totp-confirm input { max-width: 180px; display: inline-block; margin-right: var(--sp-3); }
.totp-confirm .btn { width: auto; margin-top: 0; }

/* ============================================================ langues & stats */
.i18n-row {
  display: grid; grid-template-columns: 220px 1fr auto; gap: var(--sp-4);
  align-items: start; padding: var(--sp-3) 0; border-bottom: 1px solid var(--border);
}
.i18n-key { margin: 0; font-size: .82rem; overflow-wrap: anywhere; }
.i18n-values { display: grid; gap: var(--sp-1); }
.i18n-values label { margin: 0; font-size: .82rem; }
.i18n-values textarea { min-height: 44px; }
@media (max-width: 800px) { .i18n-row { grid-template-columns: 1fr; } }

/* Textes des pages importées : source à gauche, traduction à droite.
   Deux colonnes égales et non le 220px de .i18n-row : ici la colonne de gauche
   porte des paragraphes entiers (les CGV, la politique de confidentialité), pas
   une clé technique. */
.page-text-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4);
  align-items: start; padding: var(--sp-3) 0; border-bottom: 1px solid var(--border);
}
.page-text-row .i18n-key { grid-column: 1 / -1; }
.page-text-source { margin: 0; font-size: .88rem; color: var(--text-muted); white-space: pre-wrap; }
.page-text-row textarea { min-height: 44px; }
@media (max-width: 800px) { .page-text-row { grid-template-columns: 1fr; } }

.funnel-row { display: grid; grid-template-columns: 180px 1fr 120px; gap: var(--sp-3); align-items: center; margin: var(--sp-1) 0; }
.funnel-label { font-size: .88rem; }
.funnel-bar { background: var(--surface-raised); border-radius: var(--r-sm); height: 14px; overflow: hidden; display: block; }
.funnel-bar span { display: block; height: 100%; background: var(--accent); }
/* Statistiques matériaux : une 4e colonne, le stock restant en kg, alignée à droite. */
.funnel-row--stock { grid-template-columns: 180px 1fr 120px 100px; }
.funnel-stock { text-align: right; color: var(--text-muted); }
@media (max-width: 700px) { .funnel-row { grid-template-columns: 1fr; } }

/* ============================================================ catalogue */
.color-swatch { border: 1px solid var(--border-strong); }

/* ============================================================ CMS
   Fondu depuis admin-cms.css, supprimé en Task 5.
   Mêmes sélecteurs, valeurs passées aux tokens. */
/* Les règles `.cms-block*` et `.cms-field-hint` sont parties le 28/07/2026 :
   elles habillaient l'écran d'édition des pages CMS, mécanisme retiré par la
   migration 037 et dont la dernière trace en base a disparu avec la 058. */
.cms-json {
  width: 100%; font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .85rem; border: 1px solid var(--border-strong); border-radius: var(--r-md);
  padding: var(--sp-2); white-space: pre; resize: vertical;
  background: var(--surface); color: var(--text);
}
/* Plusieurs gabarits (cms_menus.php notamment) posent plusieurs <form> freres
   dans un meme <td> — un <form> ne peut pas englober <tr>/<td>, donc chaque
   action a son propre formulaire — sans quoi ils s'empilent verticalement
   comme des blocs.
   Le selecteur `.cms-block-actions` qui l'accompagnait est parti avec l'ecran
   des pages CMS ; celui-ci reste, il sert les tableaux. */
.table td form { display: inline; }

.cms-media-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--sp-4);
}
.cms-media-item {
  border: 1px solid var(--border); border-radius: var(--r-sm); padding: var(--sp-3);
  background: var(--surface);
}
.cms-media-item img {
  width: 100%; height: 130px; object-fit: contain; background: var(--surface-raised);
  border-radius: var(--r-sm); margin-bottom: var(--sp-1);
}
.cms-media-url input {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: .78rem;
  width: 100%; padding: var(--sp-1) var(--sp-2); border: 1px solid var(--border-strong);
  border-radius: var(--r-sm); background: var(--bg); color: var(--text); min-height: 36px;
}
.cms-media-item label { font-size: .82rem; margin: var(--sp-2) 0 var(--sp-1); }
.cms-media-item input[type=text] { padding: var(--sp-1) var(--sp-2); font-size: .85rem; }
.cms-media-item form, .cms-media-item details { margin-top: var(--sp-2); }
.cms-media-item summary { cursor: pointer; font-size: .85rem; }
.cms-media-item input[type=file] { max-width: 100%; font-size: .82rem; }

/* ============================================================ tableau des commandes */

/* Tableau large : on garde l'essentiel visible sans défilement sur écran
   d'atelier (≥ 1280 px), le reste défile horizontalement. */
.table-orders { table-layout: fixed; min-width: 1200px; }
.table-orders th,
.table-orders td { vertical-align: middle; }

/* Largeurs de colonnes définies une seule fois ici, le gabarit n'a pas à
   inventer de styles inline. */
.table-orders .col-number  { width: 8%; }
.table-orders .col-name    { width: 15%; }
.table-orders .col-total   { width: 9%; }
/* Commande sans TVA : mention sous le montant, rouge et gras, pays + régime en
   petit (15/09/2026). */
.table-orders .col-total .tva-zero { display: block; color: var(--red-fg); font-weight: 700; font-size: .78rem; line-height: 1.25; margin-top: 2px; white-space: nowrap; }
.table-orders .col-total .tva-zero small { font-weight: 600; font-size: .9em; }
.table-orders .col-qty     { width: 6%; }
.table-orders .col-materials { width: 14%; }
.table-orders .col-detail  { width: 6%; }
.table-orders .col-thumb   { width: 6%; }
.table-orders .col-country { width: 5%; }
.table-orders .col-status  { width: 7%; }
.table-orders .col-processed { width: 5%; }
.table-orders .col-note    { width: 16%; }

/* Liste de matériaux : une seule ligne, ellipsis si trop long. */
.order-materials {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: .85rem;
}

/* Miniature : carré fixe, fond neutre si l'image est transparente. */
.table-orders .col-thumb img {
  width: 48px;
  height: 48px;
  object-fit: contain;
  border-radius: var(--r-sm);
  background: var(--surface-raised);
}

/* Une commande peut porter plusieurs pièces, donc plusieurs miniatures
   (demande Florian, 01/08/2026) : orders_list.php les rend toutes dans
   .order-thumbs, une par pièce. Desktop garde le comportement d'avant (une
   seule visible, la première) ; le mode mobile plus bas les affiche toutes
   côte à côte. */
.order-thumbs { display: flex; align-items: center; gap: var(--sp-1); }
.order-thumbs > :not(:first-child) { display: none; }
/* Portee par chaque piece (<img> ou <button class="v3d-trigger">) : ne
   sert qu'a la cibler ci-dessus et en mobile plus bas, la taille vient de
   .table-orders .col-thumb img (element+ascendant, s'applique deja aux deux
   cas grace a la structure du gabarit). */
.order-thumb { flex: none; }

/* Formulaire de note interne dans la liste : champ + bouton compact. */
.order-note-form {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.order-note-form input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--sp-1) var(--sp-2);
  font-size: .85rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text);
}
.order-note-form input[type="text"]::placeholder { color: var(--text-muted); }
.order-note-form .btn-small {
  flex: none;
  padding: var(--sp-1) var(--sp-2);
  line-height: 1;
}

/* Cellule vide de miniature pour garder l'alignement vertical constant. */
.order-no-thumb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-sm);
  background: var(--surface-raised);
  color: var(--text-muted);
  font-size: .85rem;
}

/* ============================================================ détail inline de la liste des commandes */

/* Ligne cliquable : elle ressemble à une ligne de tableau normale mais change
   de curseur au survol pour indiquer l'ouverture. */
.order-row { cursor: pointer; }
.order-row:hover { background: var(--brand-light); }
/* Ligne dépliée : on garde la couleur de survol pour montrer visuellement
   que la ligne de détail appartient à cette commande. */
.order-row.is-expanded { background: var(--brand-light); }
.order-row.is-new.is-expanded { background: var(--brand-light); filter: brightness(.96); }

/* La ligne de détail est masquée par défaut. Le tableau garde son
   table-layout: fixed malgré le <tr> supplémentaire grâce au <td colspan>. */
.order-detail-row { display: table-row; }
.order-detail-row[hidden] { display: none; }

/* Cellule unique du détail : elle occupe toute la largeur. */
.order-detail-cell {
  padding: 0;
  border-bottom: 1px solid var(--border);
  background: var(--surface-raised);
}

/* Conteneur animé de la ligne de détail. On anime la hauteur et l'opacité. */
.order-detail-pane {
  overflow: hidden;
  height: 0;
  opacity: 0;
  padding: 0 var(--sp-5);
  transition: height .25s ease-out, opacity .25s ease-out, padding .25s ease-out;
  box-sizing: border-box;
}

/* Quand la ligne est ouverte, on déplie le contenu. La hauteur est pilotée
   par JavaScript (scrollHeight), mais on ajoute une classe pour le padding. */
.order-detail-row.is-open .order-detail-pane {
  padding-top: var(--sp-5);
  padding-bottom: var(--sp-5);
}

/* Contenu du résumé de commande. */
.order-summary { display: block; }
.order-summary-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); margin-bottom: var(--sp-4);
}
.order-summary-head h3 { margin: 0; font-size: 1.1rem; }

/* Numéro et e-mail copiables d'un clic dans le titre du résumé. Le bouton se
   fond dans le <h3> (même fonte, même graisse) : seul le soulignement
   pointillé dit « cliquable », et la coche confirme la copie sans rien
   déplacer autour. */
.copier {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; font-weight: inherit; color: inherit; cursor: pointer;
  text-decoration: underline dotted var(--text-muted);
  text-underline-offset: 3px;
}
.copier:hover { text-decoration-color: var(--accent); }
.copier.is-copied::after { content: ' ✓'; color: var(--success); }
.order-summary-actions { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* Trois colonnes de sections. */
.order-summary-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-4);
}

.order-summary-section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--sp-4);
  min-height: 120px;
}
.order-summary-section h4 { margin: 0 0 var(--sp-2); font-size: .8125rem; font-weight: 500; color: var(--text-muted); }
.order-summary-items { display: grid; gap: var(--sp-3); }
.order-summary-item { padding-top: var(--sp-3); border-top: 1px solid var(--border); }
.order-summary-item:first-child { padding-top: 0; border-top: 0; }
.order-summary-item-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3); }
.order-summary-item-head strong { overflow-wrap: anywhere; }
/* Vignette de la pièce à côté de son nom, section Configuration (demande
   Florian, 01/08/2026) : même carré neutre que la miniature de la liste. */
.order-summary-item-title { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
.order-summary-thumb {
  flex: none;
  width: 40px;
  height: 40px;
  object-fit: contain;
  border-radius: var(--r-sm);
  background: var(--surface-raised);
}
.order-summary-file { flex: none; color: var(--accent-text); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
.order-summary-item-label { display: block; margin-bottom: var(--sp-2); overflow-wrap: anywhere; }
.order-summary-data { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-2) var(--sp-3); margin: var(--sp-3) 0 0; }
.order-summary-data > div { min-width: 0; }
.order-summary-data dt { color: var(--text-muted); font-size: .75rem; font-weight: 500; }
.order-summary-data dd { margin: 2px 0 0; overflow-wrap: anywhere; }
.order-summary-data-wide { grid-column: 1 / -1; }
.order-summary-totals { margin-top: 0; }
.order-summary-totals > div { padding-bottom: var(--sp-2); border-bottom: 1px solid var(--border); }
.order-summary-totals > div:last-child { border-bottom: 0; padding-bottom: 0; }

@media (max-width: 900px) {
  .order-summary-grid { grid-template-columns: 1fr; }
}

@media (max-width: 420px) {
  .order-summary-data { grid-template-columns: 1fr; }
  .order-summary-data-wide { grid-column: auto; }
}

/* ============================================================ liste des commandes : cartes mobiles
   .table-cards générique (plus haut) met le libellé à gauche et la valeur à
   droite, une cellule par ligne : onze cellules, onze lignes, la carte faisait
   ~400 px et l'écran d'un téléphone n'en montrait qu'une et demie (constat
   Florian, 05/09/2026). Ici la carte est une grille de CINQ rangées qui garde
   les onze informations : les libellés ne sont conservés que là où la valeur
   seule serait ambiguë (Qté, Pays, Traité) — un numéro, un nom, un prix, une
   pastille de statut, une miniature, un champ « Note… » et un bouton « Détail »
   se reconnaissent sans. Sélecteurs à deux classes minimum exprès : ils
   doivent l'emporter sur les règles génériques .table-cards (une classe) et
   sur .table-orders .col-* (largeurs en %), quel que soit l'ordre dans le
   fichier. */
@media (max-width: 767px) {
  .table-orders.table-cards {
    table-layout: auto;
    min-width: 0;
    width: 100%;
  }
  .table-orders.table-cards .col-number,
  .table-orders.table-cards .col-name,

  .table-orders.table-cards .col-total,
  .table-orders.table-cards .col-qty,
  .table-orders.table-cards .col-materials,
  .table-orders.table-cards .col-thumb,
  .table-orders.table-cards .col-country,
  .table-orders.table-cards .col-status,
  .table-orders.table-cards .col-processed,
  .table-orders.table-cards .col-note,
  .table-orders.table-cards .col-detail {
    width: auto;
  }
  .table-orders .order-materials,
  .table-orders [data-autosave-zone],
  .table-orders [data-autosave-zone] input {
    width: 100%;
  }
  /* Cellule de détail dépliée : bloc pleine largeur, pas de libellé généré
     (elle n'a pas de data-label — la valeur attr() serait vide de toute façon). */
  .table-orders .order-detail-cell {
    display: block;
    padding: 0;
  }
  .table-orders .order-detail-cell::before {
    content: none;
  }

  /* La carte (05/09/2026, remplace les deux colonnes du 01/08) : cinq rangées
     nommées, l'essentiel à gauche, ce qui qualifie à droite —

         #C260830-9E27                    [Payée]
         Jean Dupont                     PAYS FR
         42,00 €      QTÉ 3            TRAITÉ ✓
         PLA blanc, PETG noir…
         [img] [Note……………………………]  [Détail]

     Seule .order-row est une grille — .order-detail-row garde le
     display:block de la règle générique .table-cards tr, elle n'est pas une
     carte de commande, et son contenu n'est pas touché ici.

     Quatre colonnes, pour que la note garde de la largeur quel que soit le
     contenu des autres cellules :
     - la première, en fit-content(45 %), est large comme le total et la ou
       les miniatures, jamais plus de 45 % — une commande à trois pièces (+ le
       « + ») ne doit pas écraser le champ de note, au-delà les vignettes
       passent à la ligne. Pas minmax(auto, 45 %) : la phase « maximize
       tracks » de la grille étire une telle colonne jusqu'à son plafond avant
       de servir les colonnes 1fr, et la note tombait à 94 px sur toutes les
       cartes (mesuré le 05/09/2026) ;
     - la dernière, auto, est large comme le bouton Détail, le pays et Traité ;
     - le numéro, le statut et la note s'étalent sur deux colonnes dont une
       flexible : la grille ne les compte pas pour dimensionner les colonnes
       de bord. Sans cela, « En attente de paiement » ou un numéro de commande
       fixait à lui seul une colonne de bord et la note passait sous 100 px. */
  .table-orders.table-cards tr.order-row {
    display: grid;
    grid-template-columns: fit-content(45%) 1fr 1fr auto;
    grid-template-areas:
      "number    number    status    status"
      "name      name      name      country"
      "total     qty       qty       processed"
      "materials materials materials materials"
      "thumb     note      note      detail";
    gap: var(--sp-1) var(--sp-2);
    align-items: center;
    padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-2);
  }
  .table-orders.table-cards .col-number    { grid-area: number; }
  .table-orders.table-cards .col-status    { grid-area: status; justify-self: end; }
  .table-orders.table-cards .col-name      { grid-area: name; font-weight: 600; }
  .table-orders.table-cards .col-country   { grid-area: country; justify-self: end; }
  .table-orders.table-cards .col-total     { grid-area: total; }
  .table-orders.table-cards .col-qty       { grid-area: qty; }
  .table-orders.table-cards .col-processed { grid-area: processed; justify-self: end; }
  .table-orders.table-cards .col-materials { grid-area: materials; }
  .table-orders.table-cards .col-thumb     { grid-area: thumb; }
  .table-orders.table-cards .col-note      { grid-area: note; }
  .table-orders.table-cards .col-detail    { grid-area: detail; justify-self: end; }

  /* Les cellules : plus de padding propre (la grille espace déjà), libellé et
     valeur sur une même ligne. Le libellé généré est retiré partout sauf sur
     les trois cellules dont la valeur seule ne dit pas ce qu'elle est. */
  .table-orders.table-cards tr.order-row > td {
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: var(--sp-1);
    padding: 0;
    min-width: 0;
  }
  .table-orders.table-cards tr.order-row > td::before { content: none; }
  /* Même préfixe de sélecteur que la règle juste au-dessus : à spécificité
     égale, c'est l'ordre qui tranche, et ces trois-là viennent après. */
  .table-orders.table-cards tr.order-row > .col-qty::before,
  .table-orders.table-cards tr.order-row > .col-country::before,
  .table-orders.table-cards tr.order-row > .col-processed::before {
    content: attr(data-label);
    font-weight: 500; font-size: .75rem; line-height: 1;
    color: var(--text-muted);
  }
  .table-orders.table-cards .col-number a { font-weight: 600; }
  .table-orders.table-cards .col-materials { font-size: .85rem; }

  /* Plusieurs pièces : miniatures côte à côte (au lieu d'une seule) —
     colonne étant plus étroite qu'une ligne de tableau pleine largeur, elles
     rétrécissent un peu et passent à la ligne suivante s'il y en a beaucoup. */
  .table-orders.table-cards .order-thumbs { flex-wrap: wrap; justify-content: flex-start; }
  .table-orders.table-cards .order-thumbs > * { display: inline-flex; }
  .table-orders.table-cards .col-thumb img { width: 40px; height: 40px; }

  /* Au-delà de 3 pièces, un gros + remplace le reste plutôt que de faire
     grossir la carte indéfiniment (demande Florian, 01/08/2026). :has() vérifie
     la présence d'une 4e vignette dans le DOM, peu importe qu'elle soit masquée. */
  .table-orders.table-cards .order-thumbs > :nth-child(n+4) { display: none; }
  .table-orders.table-cards .order-thumbs:has(> :nth-child(4))::after {
    content: '+';
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; font-size: 1.4rem; font-weight: 700; line-height: 1;
    color: var(--text-muted); border: 1px dashed var(--border-strong); border-radius: var(--r-md);
  }
}

/* ============================================================ cibles tactiles
   36 px suffit à la souris (bureau), mais il faut au moins 44 px pour un doigt
   sur la tablette d'atelier. Même point de rupture que le tiroir.

   Ce bloc DOIT rester en fin de fichier : une media query n'ajoute aucune
   spécificité, donc à sélecteur identique c'est l'ordre source qui tranche.
   Placé plus haut, il était silencieusement écrasé par les règles à 36 px
   définies plus bas, et la hausse tactile ne s'appliquait nulle part. */
@media (max-width: 1023px) {
  .btn-small,
  .table input[type=text], .table input[type=number], .table select, .table textarea,
  .cms-media-url input {
    min-height: 44px;
  }
}

/* --- Catalogue : stock, photos ---------------------------------------------
   Trois classes introduites par les écrans Matériaux (stock, photos). Elles
   sont ici et non dans un fichier à part : le contrat CSS de l'administration
   ne lit que admin*.css, et une classe employée sans être définie y échoue. */

/* Pastille de couleur, à côté du nom d'une couleur du catalogue. La bordure
   assure que le blanc et les teintes très claires restent visibles sur fond
   clair — sans elle, « Blanc » n'affiche rien du tout. */
.swatch {
  display: inline-block;
  width: .85em;
  height: .85em;
  border-radius: 50%;
  border: 1px solid var(--border);
  vertical-align: -1px;
  margin-right: .35em;
}

/* Vignette d'aperçu dans les tableaux de photos. Hauteur bornée pour que la
   ligne ne s'étire pas selon l'image envoyée. */
.thumb {
  max-width: 90px;
  max-height: 60px;
  width: auto;
  height: auto;
  display: block;
  border-radius: var(--r-sm);
}

/* Ligne de tableau à surveiller (stock au niveau du seuil). Fond teinté ET
   bordure gauche : la couleur seule ne suffit pas à porter une information. */
.table tr.is-warning > td {
  background: var(--amber-bg);
}
.table tr.is-warning > td:first-child {
  box-shadow: inset 3px 0 0 var(--warning);
}

/* Pastille de légende, même teinte que les lignes qu'elle explique. */
.legende-bas {
  display: inline-block;
  width: .9em;
  height: .9em;
  vertical-align: -.1em;
  margin-right: var(--sp-1);
  background: var(--amber-bg);
  border-left: 3px solid var(--warning);
  border-radius: var(--r-sm);
}

/* Mention à surveiller, en ligne dans un tableau ou sur un bouton : « aucun
   profil tarifaire », « 1/3 langue remplie ». Pastille plutôt que couleur de
   texte seule — sur un tiret ou un compteur, la couleur seule ne se voit pas. */
span.is-warning {
  display: inline-block;
  background: var(--amber-bg);
  color: var(--warning);
  border-radius: var(--r-sm);
  padding: 0 var(--sp-2);
  font-size: .82rem;
  font-weight: 600;
}

/* ----------------------------------- simulateur : choix des fichiers 3D */
/* Une <option> ne peut porter ni image ni mise en forme : la liste à cocher
   remplace le <select multiple>, pour montrer la miniature et les dimensions. */
.sim-files {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 26rem;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}
.sim-files li + li { border-top: 1px solid var(--border); }
.sim-file {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  cursor: pointer;
  margin: 0;
}
.sim-file:hover { background: var(--surface-raised); }
.sim-file img {
  width: 48px; height: 48px;
  object-fit: contain;
  background: var(--surface-raised);
  border-radius: var(--r-sm);
  flex-shrink: 0;
}
.sim-file-nothumb {
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-raised);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: .72rem; font-weight: 600;
  flex-shrink: 0;
}
.sim-file-info { display: flex; flex-direction: column; min-width: 0; }
.sim-file-name { overflow-wrap: anywhere; }
.sim-file-meta { font-size: .8rem; color: var(--text-muted); }
.sim-file-meta em { font-style: italic; }
/* Miniature dans le tableau de résultats (23/09/2026) : la même image que la
   liste des fichiers, un cran plus petite pour ne pas étirer les lignes. */
.sim-thumb {
  display: block;
  width: 40px; height: 40px;
  object-fit: contain;
  background: var(--surface-raised);
  border-radius: var(--r-sm);
}
.sim-thumb-vide {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-muted);
  font-size: .7rem; font-weight: 600;
}
/* Envoi vers l'ERP replié (23/09/2026) : admin.js pose .is-erp-ferme sur le
   formulaire ; la colonne des cases, son pied et le bouton d'envoi
   disparaissent, la miniature reste. `.btn` impose display:inline-flex, qui
   l'emporterait sur l'attribut hidden des boutons révélés par le script. */
.is-erp-ferme .sim-erp-partie { display: none; }
[data-sim-erp] .btn[hidden] { display: none; }

/* ------------------- bloc d'explication d'un réglage (surcoût plateau…) */
/* Un réglage à quatre paramètres sans explication ne se règle pas : on le
   laisse tel quel, ou on le change au hasard. */
.explication {
  background: var(--surface-raised);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  padding: var(--sp-4) var(--sp-5);
  margin-bottom: var(--sp-5);
  font-size: .9rem;
}
.explication p:first-child { margin-top: 0; }
.explication p:last-child { margin-bottom: 0; }
.explication code { font-size: .86rem; overflow-wrap: anywhere; }
.explication .params { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-4); margin: var(--sp-4) 0; }
.explication .params dt { font-weight: 600; white-space: nowrap; }
.explication .params dd { margin: 0; }
@media (max-width: 640px) {
  .explication .params { grid-template-columns: 1fr; gap: var(--sp-1); }
  .explication .params dd { margin-bottom: var(--sp-2); }
}

/* Deuxième ligne d'un en-tête de tableau : l'ordre des sous-colonnes.
   « L/H/R » ne se devinait pas, et un title ne se découvre pas tout seul. */
.th-sub { font-weight: 400; font-size: .78rem; color: var(--text-muted); }
.table th abbr { text-decoration: none; border-bottom: 1px dotted var(--border-strong); cursor: help; }

/* ------------------------------------------------- champs multilingues */
/* La valeur de la langue source reste lisible à côté du bouton qui ouvre la
   modale : on voit ce qu'on va traduire sans avoir à l'ouvrir. */
.i18n-field {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
/* L'aperçu ne doit JAMAIS pousser la mise en page. Un commentaire matériau de
   plusieurs lignes étirait la fiche entière et débordait de l'écran : `flex: 1`
   seul ne suffit pas, un élément flex refuse de passer sous sa taille de
   contenu tant qu'on ne lui donne pas `min-width: 0` ET une largeur maximale.

   Deux lignes affichées, coupées proprement au-delà : les retours à la ligne
   d'un commentaire long sont ainsi visibles, sans que la case devienne un
   pavé. */
.i18n-current {
  flex: 1 1 0;
  min-width: 0;
  max-width: 100%;
  padding: var(--sp-2) var(--sp-3);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-height: 1.45;
}
.i18n-current em { color: var(--text-muted); }
.i18n-count { margin-left: var(--sp-1); }

/* Sans JavaScript la modale reste dépliée dans la page (attribut `open` posé
   par le gabarit) : elle doit alors ressembler à un bloc du formulaire, pas à
   une fenêtre flottante. */
/* Modale contenant un tableau (paliers tarifaires) : la largeur par défaut de
   460 px y écraserait les colonnes. */
dialog.dialog-large { max-width: 640px; }

/* Repli sans JavaScript : la modale reste dépliée dans la page (attribut `open`
   posé par le gabarit) et doit alors ressembler à un bloc du formulaire.

   `:not(.js-dialog)` n'est pas décoratif. admin.js pose cette classe sur les
   modales qu'il prend en charge ; sans elle, la règle s'appliquait AUSSI à
   l'état modal — showModal() repose l'attribut `open` — et la fenêtre
   s'étalait sur toute la largeur de l'écran. */
dialog.i18n-dialog[open]:not(.js-dialog) {
  position: static;
  max-width: none;
  width: auto;
  box-shadow: none;
  margin: 0 0 var(--sp-4);
}

/* Modale réellement modale : largeur lisible, et de la hauteur pour les
   tableaux de paliers sans étirer la fenêtre au-delà de l'écran. */
dialog.i18n-dialog.js-dialog {
  max-width: 34rem;
  max-height: 85vh;
  overflow-y: auto;
}
dialog.dialog-large.js-dialog { max-width: 44rem; }
@media (max-width: 700px) {
  .i18n-field { flex-wrap: wrap; }
}

/* --------------------------------------------------- couleur (hex + palette) */
/* La pastille n'est plus décorative : c'est le sélecteur natif. On lui retire
   son habillage de champ pour qu'elle redevienne une simple pastille, et on la
   colle au code hexadécimal pour que l'ensemble se lise comme un seul champ. */
.hex-field {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.hex-field .hex-pick {
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  flex: 0 0 auto;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: none;
  cursor: pointer;
  overflow: hidden;
}
/* Chrome et Firefox dessinent chacun leur cadre à l'intérieur du champ ; sans
   ces deux règles, la pastille ronde garde un carré blanc au centre. */
.hex-field .hex-pick::-webkit-color-swatch-wrapper { padding: 0; }
.hex-field .hex-pick::-webkit-color-swatch { border: none; border-radius: 50%; }
.hex-field .hex-pick::-moz-color-swatch { border: none; border-radius: 50%; }
.hex-field .hex-code {
  width: 6.5em;
  text-transform: uppercase;
}

/* Écran Réglages : un encadré par sujet, chacun avec son bouton.
   Tout tenait auparavant dans un seul encadré, sept titres empilés sous un
   unique « Enregistrer » — on ne savait pas ce qu'il enregistrait, et le champ
   qu'on venait de modifier pouvait être six écrans plus haut.
   Les blocs sont bornés en largeur, pas la colonne : sur un grand écran, un
   champ de trois caractères étiré sur 1400 px se lit mal. */
.reglages-bloc {
  max-width: 620px;
}
/* Sommaire en haut de page (revue Florian, 02/08/2026) : six encadrés à
   dérouler sans aucun repère, on ne sait pas ce qu'il y a plus bas. Simples
   liens d'ancrage, pas de position sticky — sur cette page précise les
   encadrés sont courts, le lien « retour en haut » du navigateur suffit. */
.reglages-sommaire {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin-bottom: var(--sp-5); max-width: 620px;
}
.reglages-sommaire a {
  padding: var(--sp-1) var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r-full); font-size: .85rem; text-decoration: none;
}
.reglages-sommaire a:hover, .reglages-sommaire a:focus-visible {
  background: var(--surface-raised); border-color: var(--border-strong);
}
/* Le bouton se détache du dernier champ par un filet : sans lui, il flotte et
   on ne voit pas où le bloc s'arrête. */
.reglages-enregistrer {
  width: auto;
  margin-top: var(--sp-4);
  padding-top: var(--sp-2);
}
.reglages-bloc > .reglages-enregistrer {
  display: block;
  margin-top: var(--sp-5);
}

/* Autosauvegarde (stock, notes de commande) : vert = enregistré, rouge =
   refusé — dont le conflit « modifié par quelqu'un d'autre entre-temps ».
   En TOKENS, pas en littéraux : les anciens pastels clairs en dur rendaient
   le texte (--text, clair en sombre) illisible en mode sombre — le retour
   visuel disparaissait précisément là où on le guettait. */
[data-autosave].autosave-ok { background: var(--green-bg); transition: background 1.2s ease; }
[data-autosave].autosave-err { background: var(--red-bg); }

/* ============================================================ TVA : taux par pays */
.tax-rate-row {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  margin-bottom: var(--sp-2);
}
.tax-rate-row .tax-rate-remove { flex: none; }

/* ============================================== Assistant de cotation (fiche commande)
   Commande payée sans offre transporteur (migrée, grille de secours) :
   l'admin vérifie le colis, cote, choisit l'offre — et le point de retrait
   si l'offre l'exige (demande Marc, 01/08/2026, née de la #17890). */
.cotation-admin {
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  border: 1px dashed var(--border);
  border-radius: var(--r-md);
}
/* <summary> du bloc repliable : ouvert d'office sans offre, replié en mode
   « Recoter » quand une offre est déjà enregistrée. */
.cotation-admin-titre { margin: 0 0 var(--sp-1); font-size: .95rem; font-weight: 600; cursor: pointer; }
.cotation-admin-colis {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  align-items: end;
  margin: var(--sp-2) 0;
}
.cotation-admin-colis label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: .8rem;
  color: var(--text-muted);
}
.cotation-admin-colis input { width: 90px; }
.cotation-offre,
.cotation-point {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  padding: var(--sp-1) 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
}
.cotation-offre span:first-of-type,
.cotation-point span { flex: 1; }
.cotation-offre img { flex: none; }
[data-cotation-form] { margin-top: var(--sp-2); }
[data-cotation-points] { margin: var(--sp-2) 0; }

/* --- Audit : le parcours raconté ------------------------------------------
   La table brute reste la source ; ce bloc la rend lisible. L'indentation et
   la ligne verticale donnent le sens de lecture, les silences sont en retrait
   et grisés (ils se survolent, ils ne se lisent pas), et seules les ruptures
   et les moments décisifs prennent de la couleur. */
.recit-seance { margin-bottom: var(--sp-4); }
.recit-seance-titre { font-size: .95rem; margin: 0 0 var(--sp-2); }
.recit-etapes {
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--sp-3);
  border-left: 2px solid var(--border);
}
.recit-etape {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-1) 0;
}
.recit-heure { color: var(--text-muted); font-size: .8rem; flex: none; }
.recit-libelle { font-weight: 600; }
.recit-detail { color: var(--text-muted); font-size: .85rem; }
.recit-attente {
  padding: 2px 0 2px var(--sp-2);
  color: var(--text-muted);
  font-size: .8rem;
  font-style: italic;
}
.recit-cle .recit-libelle { color: var(--accent); }
.recit-rupture .recit-libelle { color: var(--danger); }

/* Vignettes des pièces d'un panier sur la page Audit (bloc Aboutissement) :
   petites images cliquables en ligne à côté de « Panier #… — N pièces ». */
.audit-cart-thumbs {
  display: inline-flex;
  gap: 4px;
  vertical-align: middle;
  margin-left: 8px;
}
.audit-cart-thumbs a { display: inline-block; line-height: 0; }
.audit-cart-thumbs img {
  width: 48px;
  height: 48px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
}
.audit-cart-thumbs a:hover img { border-color: var(--accent); }

/* Encadré d'explication de l'écran Redirections : du texte, lisible, sans
   concurrencer le tableau qui suit. */
.redirects-help p { margin: 0 0 8px; max-width: 90ch; }
.redirects-help p:last-child { margin-bottom: 0; }

/* ============================================================ tableau de bord :
   cartes de synthèse (anatomie shadcn Card « stat »). Chiffres en
   tabular-nums, contraste par les tokens (la marque n'apparaît qu'en décor). */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--sp-4);
  margin: 0 0 var(--sp-8);
}
.stat-card {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-1);
  color: var(--text);
  text-decoration: none;
  transition: border-color var(--t-rapide) ease-out, box-shadow var(--t-rapide) ease-out;
}
.stat-card:hover { border-color: var(--border-strong); box-shadow: var(--sh-2); }
.stat-card:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--r-xl); }
.stat-card-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--r-lg);
  background: var(--brand-light);
  color: var(--brand-deep);
  border: 1px solid color-mix(in srgb, var(--brand-deep) 12%, transparent);
}
.stat-card-icon svg { width: 20px; height: 20px; }
.stat-card-body { min-width: 0; }
.stat-card-label {
  display: block;
  margin: 0 0 var(--sp-1);
  font-size: .8125rem;
  font-weight: 500;
  color: var(--text-muted);
}
.stat-card-value {
  display: block;
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -.025em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.stat-card-sub {
  display: block;
  margin-top: var(--sp-1);
  font-size: .8125rem;
  color: var(--text-muted);
}

/* Vignette de pièce dans les listes (Paniers, Fichiers chargés).
   NOM DISTINCT de `.thumb` (ligne 1392) : cette règle-ci force un carré de
   44 px rogné au centre, l'autre borne à 90×60 en gardant les proportions.
   Les avoir toutes deux appelées `.thumb` faisait gagner la dernière chargée,
   et les photos de matériaux (`materials_photos.php`) se retrouvaient
   rétrécies et rognées sans que personne l'ait demandé. */
.thumb-piece {
  display: block;
  width: 44px;
  height: 44px;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-raised);
}
.thumb-vide {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  font-size: .6rem;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* Fiche commande — mode intervention (décision Marc, 05/08/2026).
   Les gestes sensibles portent `data-sensible` dans le gabarit ; la classe
   `adm-sensibles-masques` est posée sur <body> PAR admin.js au chargement,
   et retirée/reposée par le bouton `data-toggle-sensible`. Sans JavaScript,
   la classe n'existe jamais : tout reste visible, aucune protection ne
   repose sur cette règle — les routes et leurs verrous restent les gardes. */
body.adm-sensibles-masques [data-sensible] {
  display: none;
}

/* Parcours guidé (Contenu → Parcours guidé, 07/09/2026) : un vrai tableau par
   question, ouvert en dessous par la flèche de la réponse (navigation = simulation). */
.guide-level { margin-bottom: var(--sp-4); }
.guide-level-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.guide-level-badge { font-size: .75rem; font-weight: 500; color: var(--text-muted); white-space: nowrap; }
.guide-level-title .guide-txt { font-size: 1.05rem; font-weight: 600; }
.guide-hidden-form { display: none; }
.guide-table th, .guide-table td { vertical-align: middle; }
.guide-table td select, .guide-table td input[type=text] { width: 100%; min-width: 7em; }
.guide-table td.guide-ordre { white-space: nowrap; text-align: center; }
.guide-move { padding: 2px 6px; font-size: 0.8rem; line-height: 1; }
.guide-move + .guide-move { margin-left: 2px; }
.guide-table input.autosave-ok, .guide-table select.autosave-ok { background: var(--green-bg); transition: background 1.2s ease; }
.guide-table input.autosave-err, .guide-table select.autosave-err { background: var(--red-bg); }
.guide-row-error td { background: var(--red-bg); color: var(--red-fg, #8a1c1c); font-size: 0.9rem; padding: 4px 10px; }
.guide-table tr.is-open td { background: var(--surface-raised); }
.guide-table tr.guide-row-new td { background: var(--surface-raised); border-top: 2px solid var(--border-strong); }
.guide-txt { display: block; width: 100%; max-width: 22em; text-align: left; background: none; border: 0; padding: 2px 0; color: var(--text); font: inherit; cursor: pointer; text-decoration: underline dotted var(--border-strong); text-underline-offset: 3px; white-space: normal; }
.guide-txt:hover { color: var(--accent, var(--success)); }
.guide-txt em { color: var(--text-muted); }
.guide-qualite { white-space: nowrap; }
.guide-qualite input { width: 4.2em !important; min-width: 0 !important; margin-right: 3px; }
.guide-check { margin-top: var(--sp-1); font-size: .82rem; white-space: nowrap; }
.guide-table tr.autosave-ok td, .guide-level-head.autosave-ok { background: var(--green-bg); transition: background 1.2s ease; }
.guide-table tr.autosave-err td, .guide-level-head.autosave-err { background: var(--red-bg); }
.guide-actions { white-space: nowrap; }
.guide-actions .btn { margin-right: 4px; }
.guide-arrow { text-align: center; }
.guide-open { font-size: 1rem; line-height: 1; padding: 4px 10px; }

/* ================================================================
   COMPLÉMENTS SHADCN (repris de l'ERP, 26/09/2026) : Sheet (animation
   du tiroir) et Tooltip général, posé par admin.js.
   ================================================================ */
@keyframes adm-sheet-in { from { transform: translateX(-100%); } }

/* --- Tooltip : fond de la couleur du texte, 12 px, flèche ; au-dessus de
   l'élément (dessous s'il n'y a pas la place), à droite dans la barre
   repliée. Position fixe calculée par admin.js. */
.adm-infobulle { padding: 6px var(--sp-3); }
.adm-infobulle[data-cote="haut"], .adm-infobulle[data-cote="bas"] {
  max-width: 20rem; white-space: normal; text-wrap: balance; text-align: center;
}
.adm-infobulle[data-cote="haut"] { transform: translate(-50%, calc(-100% - 8px)); }
.adm-infobulle[data-cote="bas"] { transform: translate(-50%, 8px); }
.adm-infobulle::after {
  content: ""; position: absolute; width: 8px; height: 8px;
  background: inherit; border-radius: 2px; transform: rotate(45deg);
}
.adm-infobulle[data-cote="haut"]::after { left: calc(50% - 4px); bottom: -3px; }
.adm-infobulle[data-cote="bas"]::after { left: calc(50% - 4px); top: -3px; }
.adm-infobulle[data-cote="droite"]::after, .adm-infobulle:not([data-cote])::after { left: -3px; top: calc(50% - 4px); }

/* ============================================================ mails de contact@ */
/* Carte de l'accueil (30/09/2026, revue le soir en composants shadcn) et écran
   Système → Emails → Boîte contact@. Trois Card clients côte à côte (une par
   ligne sous 1 100 px), puis la Card « Fournisseurs et administratif » en
   lignes. Avatar à initiales, Badge pour le type, la priorité et l'attente. */
.mails-synchro { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; }
.mails-synchro form { margin: 0; }
.mails-synchro .btn .icon { width: 1em; height: 1em; margin-right: var(--sp-1); }

/* Card de colonne : même langage que .stat-card (bordure, ombre --sh-1, rayon xl). */
.mails-cols { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); margin-top: var(--sp-4); }
.mails-col, .mails-bande {
  display: flex; flex-direction: column; gap: var(--sp-3); min-width: 0;
  padding: var(--sp-4); background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-xl); box-shadow: var(--sh-1);
}
.mails-bande { margin-top: var(--sp-4); }
.mails-col-head { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--sp-3); }
.mails-col-icone {
  display: grid; place-items: center; width: 36px; height: 36px; border-radius: var(--r-lg);
  background: var(--surface-raised); color: var(--text-muted); border: 1px solid var(--border);
}
.mails-col-icone .icon { width: 18px; height: 18px; }
.mails-col-traiter .mails-col-icone { background: var(--blue-bg); color: var(--blue-fg); border-color: color-mix(in srgb, var(--blue-fg) 15%, transparent); }
.mails-col-sans_reponse .mails-col-icone { background: var(--red-bg); color: var(--red-fg); border-color: color-mix(in srgb, var(--red-fg) 15%, transparent); }
.mails-col-relancer .mails-col-icone { background: var(--amber-bg); color: var(--amber-fg); border-color: color-mix(in srgb, var(--amber-fg) 15%, transparent); }
.mails-col-titre { min-width: 0; }
.mails-col-titre h3 { margin: 0; font-size: .9375rem; font-weight: 600; letter-spacing: -.01em; }
.mails-regle { margin: 2px 0 0; font-size: .75rem; line-height: 1.35; color: var(--text-muted); }
.mails-compte { font-size: 1.5rem; font-weight: 600; line-height: 1; letter-spacing: -.025em; font-variant-numeric: tabular-nums; }
.mails-col-sans_reponse .mails-compte { color: var(--red-fg); }
.mails-vide { display: flex; align-items: center; gap: var(--sp-2); margin: 0; font-size: .8125rem; color: var(--text-muted); }
.mails-vide .icon { width: 1em; height: 1em; color: var(--green-fg); }

/* Élément de liste : ghost (fond au survol), séparé par un trait, sans boîte dans la boîte. */
.mails-fils, .mails-lignes { list-style: none; margin: 0; padding: 0; display: grid; }
.mails-fils > li + li, .mails-lignes > li + li { border-top: 1px solid var(--border); }
.mails-fil {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--sp-3); align-items: start;
  margin: 0 calc(-1 * var(--sp-2)); padding: var(--sp-3) var(--sp-2); border-radius: var(--r-md);
  color: var(--text); text-decoration: none;
  transition: background-color var(--t-rapide) ease-out;
}
.mails-fil:hover { background: var(--surface-raised); }
.mails-fil:focus-visible, .mails-ligne:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.mails-fil-corps { display: grid; gap: 2px; min-width: 0; }
.mails-fil-top { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: var(--sp-2); font-size: .75rem; color: var(--text-muted); }
.mails-de { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.mails-sujet { min-width: 0; font-size: .8125rem; font-weight: 600; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mails-fil.is-unread .mails-sujet::before, .mails-ligne.is-unread .mails-sujet::before {
  content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 6px; border-radius: 50%;
  background: var(--info); vertical-align: middle;
}
/* Résumé ou extrait : deux lignes au plus, le reste est dans Gmail. */
.mails-extrait, .mails-resume {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  font-size: .75rem; line-height: 1.45; overflow-wrap: anywhere;
}
.mails-extrait { color: var(--text-muted); }
.mails-resume { color: var(--text); }

/* Avatar (shadcn AvatarFallback) : initiales, teinte stable par correspondant. */
.mails-avatar {
  display: grid; place-items: center; flex: none; width: 32px; height: 32px; border-radius: var(--r-full);
  font-size: .75rem; font-weight: 600; letter-spacing: .02em; user-select: none;
}
.mails-avatar.is-sm { width: 24px; height: 24px; font-size: .625rem; }
.mails-av-0 { background: var(--nav-actif-bg); color: var(--nav-actif-fg); }
.mails-av-1 { background: var(--blue-bg); color: var(--blue-fg); }
.mails-av-2 { background: var(--amber-bg); color: var(--amber-fg); }
.mails-av-3 { background: var(--surface-raised); color: var(--text-muted); }

/* Badge (shadcn) : outline pour le type, destructive atténué pour la priorité. */
.mails-reperes { display: flex; flex-wrap: wrap; gap: var(--sp-1); margin-top: 2px; }
.mails-badge {
  display: inline-flex; align-items: center; gap: 4px; width: fit-content; padding: 1px 8px;
  border: 1px solid var(--border); border-radius: var(--r-full);
  font-size: .6875rem; font-weight: 500; line-height: 1.5; white-space: nowrap; color: var(--text);
}
.mails-badge .icon { width: 11px; height: 11px; }
.mails-badge.is-urgent { background: var(--red-bg); color: var(--red-fg); border-color: transparent; }
.mails-badge.is-demande_devis { background: var(--blue-bg); color: var(--blue-fg); border-color: transparent; }
.mails-badge.is-commande { background: var(--green-bg); color: var(--green-fg); border-color: transparent; }
.mails-badge.is-reclamation { background: var(--amber-bg); color: var(--amber-fg); border-color: transparent; }

/* Âge : Badge secondaire ; ambre au-delà du seuil de la colonne, rouge au double. */
.mails-age {
  display: inline-flex; align-items: center; gap: 3px; flex: none; padding: 0 6px; border-radius: var(--r-full);
  font-family: var(--font-mono); font-size: .6875rem; line-height: 1.6; font-variant-numeric: tabular-nums;
  color: var(--text-muted); white-space: nowrap;
}
.mails-age .icon { width: 10px; height: 10px; }
.mails-age.is-longue { background: var(--amber-bg); color: var(--amber-fg); font-weight: 600; }
.mails-age.is-critique { background: var(--red-bg); color: var(--red-fg); font-weight: 600; }

/* Ligne : avatar, expéditeur, sujet, âge. */
.mails-ligne {
  display: grid; grid-template-columns: auto minmax(0, 13rem) minmax(0, 1fr) auto; align-items: center; gap: var(--sp-3);
  margin: 0 calc(-1 * var(--sp-2)); padding: var(--sp-2); border-radius: var(--r-md);
  color: var(--text); text-decoration: none; font-size: .8125rem;
  transition: background-color var(--t-rapide) ease-out;
}
.mails-ligne:hover { background: var(--surface-raised); }
.mails-ligne .mails-de { color: var(--text-muted); font-weight: 400; }
.mails-ligne .mails-sujet { font-weight: 500; }
.mails-ligne.is-unread .mails-sujet { font-weight: 600; }
.mails-col .mails-ligne { grid-template-columns: auto minmax(0, 6.5rem) minmax(0, 1fr) auto; gap: var(--sp-2); }

/* Actions d'une conversation (« Déplacer… », « Traité ») : Button outline
   taille xs, révélées au survol ou au clavier (focus-within), toujours
   visibles sur écran tactile. Hors du lien : un formulaire ne s'imbrique pas
   dans un <a>. */
.mails-fils > li, .mails-lignes > li { position: relative; }
.mails-traite, .mails-deplacer { margin: 0; }
/* Groupe d'actions d'une conversation : « Déplacer… » + « Traité ». */
.mails-actions-fil {
  position: absolute; right: 0; bottom: var(--sp-3); display: flex; align-items: center; gap: var(--sp-1);
  opacity: 0; transition: opacity var(--t-rapide) ease-out;
}
.mails-lignes .mails-actions-fil { bottom: auto; top: 50%; transform: translateY(-50%); }
.mails-fils > li:hover .mails-actions-fil, .mails-fils > li:focus-within .mails-actions-fil,
.mails-lignes > li:hover .mails-actions-fil, .mails-lignes > li:focus-within .mails-actions-fil { opacity: 1; }
@media (hover: none) { .mails-actions-fil { opacity: 1; } }
.mails-deplacer { display: flex; gap: var(--sp-1); }
/* Select shadcn, taille xs. */
.mails-deplacer-choix {
  height: 26px; min-height: 0; width: auto; max-width: 11rem; padding: 0 var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--sh-xs);
  color: var(--text-muted); font: inherit; font-size: .75rem; font-weight: 500; cursor: pointer;
}
.mails-deplacer-choix:hover { color: var(--text); }
.mails-deplacer-choix:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Avec JavaScript, le choix part seul : pas besoin de « OK ». */
.mails-js .mails-deplacer-ok { display: none; }
/* Glisser-déposer : la carte saisie s'estompe, la colonne visée s'encadre. */
.mails-js [draggable="true"] { cursor: grab; }
.mails-js [draggable="true"].is-glisse { opacity: .45; }
.mails-col.is-cible, .mails-bande.is-cible { outline: 2px dashed var(--focus); outline-offset: 2px; background: var(--surface-raised); }
.mails-traite-btn {
  display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 8px;
  border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); box-shadow: var(--sh-xs);
  color: var(--text-muted); font: inherit; font-size: .75rem; font-weight: 500; cursor: pointer;
  transition: background-color var(--t-rapide) ease-out, color var(--t-rapide) ease-out;
}
.mails-traite-btn .icon { width: 12px; height: 12px; }
.mails-traite-btn:hover { background: var(--surface-raised); color: var(--text); }
.mails-traite-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Liste « Marquées traitées » : bouton « Remettre » toujours visible, en bout de ligne. */
.mails-traitees { margin-top: var(--sp-3); }
.mails-traitees .mails-lignes > li { display: flex; align-items: center; gap: var(--sp-2); }
.mails-traitees .mails-ligne { flex: 1; min-width: 0; margin-right: 0; }
.mails-traitees .mails-traite { flex: none; }
/* Sortie sur place (admin.js) : fondu, puis la ligne est retirée. */
.mails-fils > li.is-sortie, .mails-lignes > li.is-sortie { opacity: 0; transition: opacity 150ms ease-out; }
/* Toast (shadcn Sonner) : en bas à droite, au-dessus de tout, sans décaler la page. */
.mails-toast {
  position: fixed; right: var(--sp-6); bottom: var(--sp-6); z-index: 1000;
  display: flex; align-items: center; gap: var(--sp-3); max-width: min(420px, calc(100vw - 2 * var(--sp-6)));
  padding: var(--sp-3) var(--sp-4); background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-lg); box-shadow: var(--sh-2);
  font-size: .8125rem; opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity var(--t-rapide) ease-out, transform var(--t-rapide) ease-out;
}
.mails-toast.is-visible { opacity: 1; transform: none; pointer-events: auto; }
.mails-toast .mails-traite-btn { flex: none; }
@media (prefers-reduced-motion: reduce) {
  .mails-actions-fil, .mails-traite-btn, .mails-fils > li.is-sortie, .mails-lignes > li.is-sortie, .mails-toast { transition: none; }
}

/* « N autres » : Collapsible, bouton ghost pleine largeur, chevron qui tourne. */
.mails-plus { margin-top: auto; }
.mails-plus summary {
  display: flex; align-items: center; justify-content: center; gap: var(--sp-1);
  padding: var(--sp-2); border-radius: var(--r-md); list-style: none; cursor: pointer;
  font-size: .8125rem; font-weight: 500; color: var(--text-muted);
  transition: background-color var(--t-rapide) ease-out, color var(--t-rapide) ease-out;
}
.mails-plus summary::-webkit-details-marker { display: none; }
.mails-plus summary:hover { background: var(--surface-raised); color: var(--text); }
.mails-plus summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.mails-plus summary .icon { width: 1em; height: 1em; transition: transform var(--t-rapide) ease-out; }
.mails-plus[open] summary .icon { transform: rotate(180deg); }
.mails-plus[open] summary { margin-bottom: var(--sp-1); }
@media (prefers-reduced-motion: reduce) {
  .mails-plus summary .icon, .mails-fil, .mails-ligne, .mails-plus summary { transition: none; }
}

.mails-pied { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-3) 0 0; font-size: .75rem; color: var(--text-muted); }
.mails-pied .icon { flex: none; }

/* Écran Boîte contact@ */
.mails-apercu { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 60vh; overflow: auto; padding: var(--sp-3); background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--r-md); font-size: .8125rem; line-height: 1.5; }
.mails-etat { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-1) var(--sp-4); margin: var(--sp-4) 0; }
.mails-etat dt { color: var(--text-muted); }
.mails-etat dd { margin: 0; }
.mails-erreur { color: var(--danger); }
.mails-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
.mails-actions form { margin: 0; }
.mails-etapes { margin: 0; padding-left: 1.25rem; display: grid; gap: var(--sp-2); }
.mails-uri { display: block; margin-top: var(--sp-1); overflow-wrap: anywhere; }

@media (max-width: 1100px) {
  .mails-cols { grid-template-columns: 1fr; }
}
@media (max-width: 700px) {
  .mails-ligne, .mails-col .mails-ligne { grid-template-columns: auto minmax(0, 1fr) auto; row-gap: 0; }
  .mails-ligne .mails-avatar { grid-row: span 2; }
  .mails-ligne .mails-de { grid-column: 2 / -1; }
  .mails-ligne .mails-sujet { grid-column: 2; }
}

/* Partage de fichiers (App\Partage, 30/09/2026) : fiche et zone d'envoi.
   La zone est celle du site (depot.js) : mêmes classes .pj-*, stylées ici avec
   les jetons de l'administration (les feuilles publiques ne sont pas chargées). */
.partage-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; margin: var(--sp-3) 0; }
.partage-actions form { margin: 0; }
.partage-etat { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-1) var(--sp-4); margin: 0 0 var(--sp-4); }
.partage-etat dt { color: var(--text-muted); }
.partage-etat dd { margin: 0; overflow-wrap: anywhere; }
.partage-lien { width: 100%; max-width: 760px; font-size: .8rem; }
.pj { margin: var(--sp-2) 0 var(--sp-3); padding: var(--sp-3); border: 1px dashed var(--border); border-radius: var(--r-md); }
.pj.is-survol { border-color: var(--focus); background: var(--surface-raised); }
.pj-liste { list-style: none; margin: var(--sp-2) 0 0; padding: 0; }
.pj-item { display: grid; grid-template-columns: 1fr auto; gap: 2px var(--sp-3); margin: 0 0 var(--sp-2); padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); }
.pj-tete { display: flex; flex-wrap: wrap; gap: 2px var(--sp-2); min-width: 0; }
.pj-nom { font-weight: 600; overflow-wrap: anywhere; }
.pj-taille, .pj-info { color: var(--text-muted); font-size: .85rem; }
.pj-info { grid-column: 1 / 2; }
.pj-actions { grid-column: 2 / 3; grid-row: 1 / 3; align-self: start; display: flex; gap: var(--sp-1); }
.pj-item progress { grid-column: 1 / 2; width: 100%; height: 6px; }
.pj-ok .pj-info { color: var(--green-fg); font-weight: 600; }
.pj-refus, .pj-echec { border-color: var(--danger); }
.pj-refus .pj-info, .pj-echec .pj-info { color: var(--danger); }
.pj-bouton { height: 28px; padding: 0 var(--sp-2); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); color: var(--text); font: inherit; font-size: .8rem; cursor: pointer; }
.pj-bouton:hover { background: var(--surface-raised); }
.pj-etat { color: var(--danger); font-weight: 600; margin: var(--sp-2) 0 0; }
