/* Symbio3D admin — système de design.
   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-*, .status-*, .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. */

:root {
  /* --- Primitives : gris chauds (teintés vers le jaune, moins fatigants que le gris bleu) */
  --gray-50:  #faf9f7;
  --gray-100: #f4f3f0;
  --gray-200: #e7e6e1;
  --gray-300: #d8d6cf;
  --gray-400: #b5b3aa;
  --gray-500: #8a8880;
  --gray-600: #6b6960;
  --gray-700: #4b4a43;
  --gray-800: #2b2a26;
  --gray-900: #1a1a18;
  --white:    #ffffff;

  /* --- Primitives : vert de marque.
     brand porte l'identité mais JAMAIS de texte (2,62:1 sur blanc).
     brand-deep est la seule variante qui porte du texte (5,25:1 sur blanc). */
  --brand:       #60b339;
  --brand-deep:  #3d7a26;
  --brand-light: #eaf5e4;

  /* --- Primitives : sémantiques métier */
  --red-fg:    #a33227;
  --red-bg:    #fdeceb;
  --amber-fg:  #8a6410;
  --amber-bg:  #fdf4e3;
  --blue-fg:   #24559e;
  --blue-bg:   #e9f0fd;
  --green-fg:  #22683a;
  --green-bg:  #e8f6ec;

  /* --- Couche sémantique : la seule que les composants utilisent */
  --bg:             var(--gray-50);
  --surface:        #ffffff;
  --surface-raised: var(--gray-100);
  --border:         #e7e6e1;
  --border-strong:  #8a8880;
  --text:           #1a1a18;
  --text-muted:     #6b6960;
  --accent:         #60b339;
  --accent-text:    #3d7a26;
  --btn-primary-bg: #3d7a26;
  --btn-primary-fg: #ffffff;
  --danger:         #a33227;
  --warning:        #8a6410;
  --info:           #24559e;
  --success:        #22683a;
  --focus:          #3d7a26;

  /* --- 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 */
  --r-sm: 6px; --r-md: 8px; --r-lg: 10px; --r-full: 999px;

  /* --- Ombres : deux niveaux, le registre s'appuie sur les bordures */
  --sh-1: 0 1px 2px rgba(26, 26, 24, .06);
  --sh-2: 0 8px 24px rgba(26, 26, 24, .14);

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

  /* --- Coquille */
  --sidebar-w: 240px;
  --rail-w: 64px;
}

/* Thème sombre : surfaces désaturées, jamais inversées.
   Le vert s'éclaircit pour rester lisible ; le bouton principal passe en
   fond vert + texte quasi noir (8,85:1). */
@media (prefers-color-scheme: dark) {
  :root {
    --bg:             #131311;
    --surface:        #1c1c1a;
    --surface-raised: #24241f;
    --border:         #33322d;
    /* #6e6c64 ne faisait que 2,96:1 sur --surface-raised (input[readonly]
       n'est pas exempte du seuil 3:1 des bordures de champ). Eclaircie
       a #7a7870 : 3,52:1 sur --surface-raised, 3,86:1 sur --surface. */
    --border-strong:  #7a7870;
    --text:           #f0efea;
    --text-muted:     #a3a196;
    --accent:         #7cc653;
    --accent-text:    #8fd465;
    --btn-primary-bg: #7cc653;
    --btn-primary-fg: #14140f;
    --danger:         #f0907f;
    --warning:        #e0b45f;
    --info:           #8fb4f0;
    --success:        #7fce9a;
    --focus:          #8fd465;

    --brand-light: #24301d;
    --red-bg:   #33211f; --red-fg:   #f0907f;
    --amber-bg: #322a1a; --amber-fg: #e0b45f;
    --blue-bg:  #1c2739; --blue-fg:  #8fb4f0;
    --green-bg: #1c2f22; --green-fg: #7fce9a;

    --sh-1: 0 1px 2px rgba(0, 0, 0, .4);
    --sh-2: 0 8px 24px rgba(0, 0, 0, .55);
  }
}

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

body.admin {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}

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

.mono { font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-size: .9em; }
.hint { color: var(--text-muted); font-size: .85rem; }
.msg { overflow-wrap: anywhere; }
.ok { color: var(--success); }

a { color: var(--accent-text); }

/* Focus visible partout — jamais supprimé. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* 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-strong); text-decoration: none;
}
.skip-link:focus { top: var(--sp-2); }

h1 { font-size: 1.5rem; font-weight: 650; letter-spacing: -.02em; margin: 0 0 var(--sp-4); }
h2 { font-size: 1.05rem; font-weight: 640; margin: 0 0 var(--sp-3); }
h3 { font-size: .95rem; font-weight: 640; 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 */
.adm-shell { display: flex; min-height: 100dvh; }

.adm-sidebar {
  width: var(--sidebar-w); flex: none;
  background: var(--surface); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100dvh; overflow-y: auto;
}
.adm-sidebar .logo {
  display: flex; align-items: center; gap: var(--sp-2);
  font-weight: 700; text-decoration: none; color: var(--text);
  padding: var(--sp-4); border-bottom: 1px solid var(--border);
}
/* Cible le span INTERIEUR (« admin »), pas .logo-text qui enveloppe désormais
   tout le texte : `.logo span` colorerait « Symbio3D » en vert lui aussi. */
.adm-sidebar .logo-text span { color: var(--accent-text); }

.adm-nav { padding: var(--sp-3) 0; flex: 1; }
.adm-nav-group { margin-bottom: var(--sp-2); }
.adm-nav-group > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-3) var(--sp-4) var(--sp-1);
  font-size: .68rem; text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-muted); font-weight: 700;
  border-left: 3px solid transparent;
}
.adm-nav-group > summary::-webkit-details-marker { display: none; }
.adm-nav-group[open] > summary {
  color: var(--text);
  background: var(--surface-raised);
  border-left-color: var(--accent);
}
.adm-nav-group .adm-group-chevron {
  transition: transform .2s ease;
}
.adm-nav-group[open] .adm-group-chevron {
  transform: rotate(180deg);
}
.adm-nav ul { list-style: none; margin: 0; padding: 0; }
.adm-nav a {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  color: var(--text); text-decoration: none; font-size: .9rem;
  border-left: 3px solid transparent;
}
.adm-nav a:hover { background: var(--surface-raised); }
.adm-nav a[aria-current="page"] {
  background: var(--brand-light); color: var(--accent-text);
  font-weight: 640; border-left-color: var(--accent);
}
.adm-nav .icon { color: var(--text-muted); }
.adm-nav a[aria-current="page"] .icon { color: var(--accent-text); }

/* Pastille de compteur (commandes à traiter). `margin-left: auto` la colle au
   bord droit du lien ; en mode rail, où le libellé est masqué, elle se retrouve
   simplement à côté de l'icône — c'est justement là qu'on veut la voir. */
.adm-nav-badge {
  margin-left: auto;
  color: var(--danger);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* 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 { margin: 0; }
.adm-nav-submenu > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  color: var(--text); font-size: .9rem;
  border-left: 3px solid transparent;
}
.adm-nav-submenu > summary::-webkit-details-marker { display: none; }
.adm-nav-submenu > summary:hover { background: var(--surface-raised); }
.adm-nav-submenu[open] > summary { background: var(--surface-raised); }
.adm-nav-submenu .adm-submenu-chevron {
  margin-left: auto;
  transition: transform .2s ease;
}
.adm-nav-submenu[open] .adm-submenu-chevron { transform: rotate(180deg); }
.adm-nav-submenu > ul {
  padding: 0 0 0 calc(var(--sp-4) + var(--sp-3));
  margin: 0;
}
.adm-nav-submenu > ul > li > a {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  color: var(--text); text-decoration: none; font-size: .88rem;
  border-left: none;
}
.adm-nav-submenu > ul > li > a:hover { background: var(--surface-raised); }
.adm-nav-submenu > ul > li > a[aria-current="page"] {
  background: var(--brand-light); color: var(--accent-text);
  font-weight: 640; border-radius: var(--r-sm);
}

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

.adm-topbar {
  display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-5);
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: var(--z-sticky);
}
.adm-crumb { font-size: .8rem; color: var(--text-muted); }
.adm-crumb a { color: inherit; }

/* Recherche : styles posés dès maintenant, champ ajouté au lot 4 avec sa route.
   C'est .adm-account qui porte margin-left:auto tant que la recherche n'est pas là. */
.adm-search { display: flex; align-items: center; }
.adm-search input {
  width: 240px; padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  background: var(--bg); color: var(--text); font-size: .88rem;
}
.adm-search kbd {
  font: inherit; font-size: .7rem; color: var(--text-muted);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 1px 5px; margin-left: calc(-1 * var(--sp-12)); pointer-events: none;
}

.adm-account { position: relative; margin-left: auto; }
.adm-account > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2); border-radius: var(--r-md);
}
.adm-account > summary::-webkit-details-marker { display: none; }
.adm-account > summary:hover { background: var(--surface-raised); }
.adm-account-menu {
  position: absolute; right: 0; top: calc(100% + var(--sp-1)); z-index: var(--z-drawer);
  min-width: 200px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: var(--sh-2); padding: var(--sp-1);
}
.adm-account-menu a, .adm-account-menu button {
  display: flex; align-items: center; gap: var(--sp-2); width: 100%;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--r-sm);
  color: var(--text); text-decoration: none; font: inherit; font-size: .88rem;
  background: none; border: 0; cursor: pointer; text-align: left;
}
.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) 0; }
.adm-account-menu .is-danger { color: var(--danger); }

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

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

/* --- Rail d'icônes entre 1024 et 1280 px
   Piloté par la seule media query, sans classe ni JavaScript : la largeur est
   la seule condition, et le rail doit fonctionner script désactivé comme le
   reste de la coquille. (Ces règles étaient auparavant préfixées par
   `.adm-shell.is-rail`, une classe que rien ne posait — le rail n'a donc
   jamais fonctionné jusqu'au 21/07/2026.) */
@media (max-width: 1280px) and (min-width: 1024px) {
  .adm-sidebar { width: var(--rail-w); }
  .adm-sidebar .logo { justify-content: center; padding: var(--sp-4) 0; }
  /* Pas de recherche en mode rail : les libellés y sont masqués, un champ de
     texte de 60 px serait inutilisable. */
  .adm-nav-search { display: none; }
  .adm-nav a { justify-content: center; padding: var(--sp-3); }
  /* 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-3) 0 0; }
  .adm-nav-group > ul,
  .adm-nav-group:not([open]) > ul {
    display: block;
  }

  /* 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 : popup icône + label au survol/ouverture, car la sidebar
     est trop étroite pour montrer les libellés des enfants. */
  .adm-nav-submenu-item { position: relative; }
  .adm-nav-submenu[open] > ul {
    position: absolute; left: calc(100% - var(--sp-2)); top: calc(-1 * var(--sp-1));
    z-index: var(--z-drawer);
    min-width: 180px; padding: var(--sp-1);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r-md); box-shadow: var(--sh-2);
  }
  .adm-nav-submenu[open] > ul .adm-nav-label {
    position: static; width: auto; height: auto;
    clip-path: none; overflow: visible; white-space: nowrap;
  }
}

/* --- 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; }
  .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);
    box-shadow: var(--sh-2); height: 100dvh;
    /* Le tiroir ouvert place .adm-sidebar en (0,0) : sans décalage, son
       premier enfant .logo se retrouve juste sous .adm-drawer-toggle, qui
       est au-dessus (z-index +1) et donc intercepte le clic/tap.
       Calcul : le bouton est à top: var(--sp-3) (12px) et fait 44px de
       haut (min-height, en border-box donc bordure incluse) → son bord bas
       est à 12 + 44 = 56px du haut de l'écran. On réserve cette hauteur en
       haut de la sidebar, plus --sp-2 (8px) de respiration visuelle,
       soit 64px, pour que le logo commence toujours sous le bouton. */
    padding-top: calc(var(--sp-3) + 44px + var(--sp-2));
  }
  /* .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); }
  .adm-search input { width: 100%; }
  .adm-search kbd { display: none; }
}

/* ============================================================ widgets */
.widget {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-5); margin-bottom: var(--sp-5);
}
.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); }
/* 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); }

/* 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: none; padding: 0;
  border-radius: var(--r-lg); box-shadow: 0 8px 24px rgba(0,0,0,.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-lg); margin-bottom: var(--sp-2);
}
.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);
}
.carrier-list li:has(input:checked) { border-color: var(--accent); background: var(--surface-raised); }
.carrier-list label.checkbox { margin: 0; }

/* ============================================================ formulaires */
label { display: block; margin: var(--sp-3) 0 var(--sp-1); font-weight: 600; font-size: .88rem; }
label.checkbox { font-weight: 400; display: flex; align-items: center; gap: var(--sp-2); }

input[type=text], input[type=email], input[type=password], input[type=number],
input[type=date], input[type=url], select, textarea {
  width: 100%; padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  font-size: 1rem; font-family: inherit;
  background: var(--surface); color: var(--text);
  min-height: 44px; /* cible tactile */
}
select { 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; }

/* 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-3) var(--sp-2); }
.adm-nav-search input { width: 100%; font-size: .85rem; }
.adm-nav .nav-hide { display: none; }

/* Journal de la fiche commande, replié par défaut : le titre sert de bouton,
   la mention bascule déplier/replier toute seule. */
.log-fold > summary { cursor: pointer; list-style: none; display: flex; align-items: baseline; gap: var(--sp-3); }
.log-fold > summary::-webkit-details-marker { display: none; }
.log-fold > summary h2 { margin: 0; }
.log-fold-toggle { color: var(--text-muted); font-size: .84rem; }
.log-fold[open] .log-fold-toggle { display: none; }

.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 */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  border: 1px solid var(--border-strong); border-radius: var(--r-md);
  cursor: pointer; padding: var(--sp-2) var(--sp-4);
  font: inherit; font-size: .94rem; font-weight: 550;
  background: var(--surface); color: var(--text);
  min-height: 44px; text-decoration: none;
  transition: background .15s ease-out;
}
.btn:hover { background: var(--surface-raised); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

.btn-primary {
  background: var(--btn-primary-bg); color: var(--btn-primary-fg);
  border-color: var(--btn-primary-bg); font-weight: 640;
}
.btn-primary:hover { background: var(--btn-primary-bg); filter: brightness(1.08); }

.btn-secondary { background: var(--surface); color: var(--text); }
.btn-ghost { background: none; border-color: transparent; }
.btn-ghost:hover { background: var(--surface-raised); }
.btn-danger { color: var(--danger); border-color: var(--danger); background: var(--surface); }
.btn-danger:hover { background: var(--red-bg); }

.btn-small { padding: var(--sp-1) var(--sp-3); font-size: .85rem; min-height: 36px; }

/* État de chargement : le JS pose aria-busy, le CSS fait le reste. */
.btn[aria-busy="true"] { pointer-events: none; opacity: .7; }
.btn[aria-busy="true"] .icon { animation: adm-spin .8s linear infinite; }
@keyframes adm-spin { to { transform: rotate(360deg); } }

/* ============================================================ messages */
.alert {
  padding: var(--sp-3) var(--sp-4); border-radius: var(--r-md);
  margin: var(--sp-3) 0; border: 1px solid transparent;
  display: flex; align-items: flex-start; gap: var(--sp-2);
}
.alert-error   { background: var(--red-bg);   color: var(--red-fg);   border-color: var(--red-fg); }
.alert-success { background: var(--green-bg); color: var(--green-fg); border-color: var(--green-fg); }
.alert-warning { background: var(--amber-bg); color: var(--amber-fg); border-color: var(--amber-fg); }
.alert-info    { background: var(--blue-bg);  color: var(--blue-fg);  border-color: var(--blue-fg); }

.badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.5em; border-radius: var(--r-full);
  font-size: .75rem; font-weight: 650; padding: .1em .5em;
  background: var(--surface-raised); color: var(--text);
}
.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: .18em .6em; border-radius: var(--r-sm);
  font-size: .78rem; font-weight: 640; 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 { 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 */
.table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.table th {
  text-align: left; padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border);
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-muted); font-weight: 650;
}
.table td {
  text-align: left; padding: var(--sp-3);
  border-bottom: 1px solid var(--border); vertical-align: middle;
}
.table tbody tr:hover { background: var(--surface-raised); }
.table .msg { max-width: 420px; overflow-wrap: anywhere; }

/* --- 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; }
.table th[aria-sort="ascending"]  a, .table th[aria-sort="descending"] a { color: var(--accent-text); }

/* Champs en ligne dans les tableaux. */
.table input[type=text], .table input[type=number], .table select, .table textarea {
  padding: var(--sp-1) var(--sp-2); font-size: .85rem; min-height: 36px;
}
.table.mono td, .table.mono th { font-size: .82rem; }

/* 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: 650; font-size: .78rem;
    color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em;
  }
  .table-cards td:empty { display: none; }
}

/* ============================================================ pagination */
.pagination { margin: var(--sp-4) 0; display: flex; gap: var(--sp-1); flex-wrap: wrap; }
.pagination a, .pagination span {
  padding: var(--sp-2) var(--sp-3); border: 1px solid var(--border);
  border-radius: var(--r-md); text-decoration: none; color: var(--text);
  min-width: 44px; min-height: 44px; display: inline-flex;
  align-items: center; justify-content: center;
}
.pagination a:hover { background: var(--surface-raised); }
.pagination a.active, .pagination [aria-current="page"] {
  background: var(--btn-primary-bg); color: var(--btn-primary-fg); border-color: var(--btn-primary-bg);
}
.pagination .ellipsis { border-color: transparent; }

/* ============================================================ é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 */
dialog {
  border: 1px solid var(--border); border-radius: var(--r-lg);
  background: var(--surface); color: var(--text);
  padding: var(--sp-5); max-width: 460px; width: calc(100% - var(--sp-8));
  box-shadow: var(--sh-2);
}
dialog::backdrop { background: rgba(0, 0, 0, .5); }
dialog h2 { margin-top: 0; }
.dialog-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; margin-top: var(--sp-5); }

/* ============================================================ 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-lg);
  padding: var(--sp-8); width: 100%; max-width: 400px; box-shadow: var(--sh-1);
}
.auth-box h1 { font-size: 1.25rem; }
.auth-box h1 span { color: var(--accent-text); }
.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);
  font-size: 1.05rem; letter-spacing: .05em; font-family: ui-monospace, monospace;
}
.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); }
@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, SFMono-Regular, 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, SFMono-Regular, 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; }

/* ============================================================ 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%; }
.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);
}

/* 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; }
.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: .9rem; text-transform: uppercase; letter-spacing: .05em; 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; }
.order-summary-file { flex: none; color: var(--accent-text); font-weight: 650; text-decoration: underline; }
.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: .72rem; text-transform: uppercase; letter-spacing: .04em; }
.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 sur une seule ligne : trop serré ici, où une cellule peut porter une
   miniature, un champ de note éditable ou une liste de matériaux. On empile
   le libellé au-dessus de la valeur à la place. 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-materials,
  .table-orders.table-cards .col-thumb,
  .table-orders.table-cards .col-country,
  .table-orders.table-cards .col-processed,
  .table-orders.table-cards .col-note,
  .table-orders.table-cards .col-detail {
    width: auto;
  }
  .table-orders.table-cards td {
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-1);
  }
  .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;
  }
}

/* ============================================================ 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; }

/* ------------------- 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;
}
/* 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 ». */
[data-autosave].autosave-ok { background: #dcf2d0; transition: background 1.2s ease; }
[data-autosave].autosave-err { background: #f6d4d4; }

/* ============================================================ 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; }
