/* =====================================================================
   Sando v3 — "Bento Editorial Dark-First"
   Inspired by Linear · Vercel · Arc · Raycast
   ===================================================================== */

/* ---------- 1. Tokens (dark-first) ---------- */
:root {
  /* === Brand & accents === */
  --brand: #0E5C63;       /* Petrole - couleur principale Diasy Labs */
  --brand-soft: #6FB3A8;  /* Sauge - variante claire pour hover / chips actifs */
  --brand-dim: #084046;   /* Petrole fonce - variante pressed / box-shadow dim */
  --brand-glow: rgba(14, 92, 99, 0.45);
  --focus-ring: var(--brand-soft); /* FE-5 : anneau de focus clavier - Sauge (~7:1 sur fond sombre) */

  /* === Accent Diasy Labs (Ambre) === */
  --accent: #E8A33D;          /* Ambre - CTA secondaires + highlights + signature */
  --accent-soft: #F2C078;     /* Ambre clair - hover / badge actif */
  --accent-dim: #B8801D;      /* Ambre fonce - pressed */
  --accent-glow: rgba(232, 163, 61, 0.45);
  --accent-ink: #0B2B2E;      /* Encre - texte OBLIGATOIRE sur fond Ambre (jamais blanc) */

  --emerald: #10b981;
  --emerald-soft: #34d399;
  --emerald-glow: rgba(16,185,129,0.40);

  --violet: #a855f7;
  --pink: #ec4899;
  --coral: #fb7185;
  --amber: #f59e0b;
  --cyan: #06b6d4;
  --sky: #0ea5e9;

  --success: #10b981;
  --warning: #f59e0b;
  --danger:  #f43f5e;
  --info:    #06b6d4;

  /* === Surfaces (dark default) === */
  --bg: #0a0b14;
  --bg-elevated: #11131f;
  --bg-surface: #161827;
  --bg-card: #181b2c;
  --bg-card-hover: #1e2238;
  --bg-glass: rgba(22,24,39,0.7);
  --bg-overlay: rgba(5,6,12,0.78);

  --line: rgba(255,255,255,0.06);
  --line-strong: rgba(255,255,255,0.10);
  --line-glow: rgba(14, 92, 99, 0.18);

  --text: #e8eaf3;
  --text-2: #a3a9bf;
  --text-3: #6b7088;
  --text-faint: #4b5063;
  --text-inverse: #0a0b14;

  /* === Gradients === */
  --grad-brand: linear-gradient(135deg, #0E5C63 0%, #6FB3A8 100%);
  --grad-emerald: linear-gradient(135deg, #10b981 0%, #06b6d4 100%);
  --grad-sunset: linear-gradient(135deg, #f43f5e 0%, #fb923c 100%);
  --grad-aurora: linear-gradient(135deg, #0E5C63 0%, #6FB3A8 50%, #E8A33D 100%);
  --grad-night: linear-gradient(180deg, rgba(14, 92, 99, 0.10), rgba(111, 179, 168, 0.04) 60%, transparent);
  --grad-mesh: radial-gradient(at 20% 0%, rgba(14, 92, 99, 0.18) 0%, transparent 50%),
               radial-gradient(at 80% 0%, rgba(232, 163, 61, 0.10) 0%, transparent 50%),
               radial-gradient(at 50% 100%, rgba(111, 179, 168, 0.10) 0%, transparent 50%);

  /* === Typography === */
  --font-display: 'Sora', 'Inter', system-ui, sans-serif;
  --font-sans: 'Inter', system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;

  --fs-2xs: 11px;
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-md: 14px;
  --fs-base: 15px;
  --fs-lg: 16px;
  --fs-xl: 18px;
  --fs-2xl: 22px;
  --fs-3xl: 28px;
  --fs-4xl: 36px;
  --fs-5xl: 48px;
  --fs-6xl: 64px;
  --fs-display: 80px;

  /* === Radii === */
  --r-1: 4px;
  --r-2: 8px;
  --r-3: 12px;
  --r-4: 16px;
  --r-5: 20px;
  --r-6: 24px;
  --r-7: 28px;
  --r-full: 9999px;

  /* === Spacing === */
  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
  --s-5: 20px; --s-6: 24px; --s-7: 28px; --s-8: 32px;
  --s-10: 40px; --s-12: 48px; --s-16: 64px; --s-20: 80px;

  /* === Shadows (dark-tuned) === */
  --shadow-1: 0 1px 2px rgba(0,0,0,0.30), 0 1px 1px rgba(0,0,0,0.20);
  --shadow-2: 0 4px 12px rgba(0,0,0,0.35), 0 2px 4px rgba(0,0,0,0.25);
  --shadow-3: 0 12px 32px rgba(0,0,0,0.45), 0 6px 12px rgba(0,0,0,0.30);
  --shadow-4: 0 24px 48px rgba(0,0,0,0.55), 0 8px 16px rgba(0,0,0,0.30);
  --shadow-glow: 0 0 0 4px rgba(14, 92, 99,0.22);
  --shadow-inset: inset 0 1px 0 rgba(255,255,255,0.04);
  --shadow-pop: 0 0 60px rgba(14, 92, 99,0.20);

  /* === Layout === */
  --sidebar-w: 244px;
  --sidebar-collapsed-w: 64px;
  --topbar-h: 60px;
  --container: 1400px;

  /* === Motion === */
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --t-fast: 120ms;
  --t-md: 220ms;
  --t-slow: 380ms;
}

/* ---------- 2. Light theme override ---------- */
[data-theme="light"] {
  --bg: #fafaff;
  --bg-elevated: #ffffff;
  --bg-surface: #ffffff;
  --bg-card: #ffffff;
  --bg-card-hover: #f5f6fc;
  --bg-glass: rgba(255,255,255,0.72);
  --bg-overlay: rgba(15,17,28,0.30);

  --line: rgba(15,17,28,0.06);
  --line-strong: rgba(15,17,28,0.10);
  --line-glow: rgba(14, 92, 99,0.20);
  --focus-ring: var(--brand); /* FE-5 : anneau de focus clavier - Petrole (contraste OK sur fond clair) */

  --text: #0f111c;
  --text-2: #4c5169;
  --text-3: #777c95;
  --text-faint: #a3a9bf;

  --shadow-1: 0 1px 2px rgba(15,17,28,0.04), 0 1px 1px rgba(15,17,28,0.02);
  --shadow-2: 0 4px 12px rgba(15,17,28,0.06), 0 2px 4px rgba(15,17,28,0.04);
  --shadow-3: 0 12px 32px rgba(15,17,28,0.10), 0 6px 12px rgba(15,17,28,0.06);
  --shadow-4: 0 24px 48px rgba(15,17,28,0.14), 0 8px 16px rgba(15,17,28,0.08);
  --shadow-pop: 0 0 60px rgba(14, 92, 99,0.10);
  --grad-night: linear-gradient(180deg, rgba(14, 92, 99,0.06), rgba(168,85,247,0.02) 60%, transparent);
}

/* ---------- 3. Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* L attribut `hidden` doit cacher, quoi qu il arrive.
   La feuille par defaut du navigateur ne pose que `display: none` a
   specificite 0 : n importe quel composant qui declare son propre `display`
   (`.alert { display: flex }`, une grille, un flex utilitaire…) le bat sans
   le vouloir, et l element reste visible alors que le JS le croit masque.
   Meme parti que le reboot de Bootstrap 4, qui porte la meme regle. */
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.5;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: 'cv02','cv03','cv04','cv11';
}
::selection { background: var(--brand); color: #fff; }

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; transition: color var(--t-fast); }
button { font-family: inherit; cursor: pointer; }
input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
hr { border: 0; border-top: 1px solid var(--line); margin: var(--s-4) 0; }

h1, h2, h3, h4, h5, h6 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--text);
}
h1 { font-size: var(--fs-5xl); letter-spacing: -0.03em; }
h2 { font-size: var(--fs-4xl); letter-spacing: -0.025em; }
h3 { font-size: var(--fs-3xl); }
h4 { font-size: var(--fs-2xl); }
h5 { font-size: var(--fs-xl); }
h6 { font-size: var(--fs-lg); }

::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: var(--r-full); border: 3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--text-3); }

/* FE-5 : focus clavier visible sur les elements interactifs du shell v3
   (WCAG 2.4.7 Focus Visible + 1.4.11 Non-text Contrast).
   :focus-visible ne se declenche pas au clic souris, seulement au clavier. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
.sidebar-link:focus-visible,
.icon-btn:focus-visible,
.topbar-cmd:focus-visible,
.tile:focus-visible,
.chip:focus-visible,
.tab:focus-visible,
.pm:focus-visible,
.reaction:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* ---------- 4. Shell layout ---------- */
.app { display: flex; min-height: 100vh; }
.sidebar {
  width: var(--sidebar-w);
  background: var(--bg-elevated);
  border-right: 1px solid var(--line);
  position: fixed; inset: 0 auto 0 0;
  display: flex; flex-direction: column;
  z-index: 50;
  transition: width var(--t-md) var(--ease-out);
}
.main {
  flex: 1; min-width: 0;
  margin-left: var(--sidebar-w);
  display: flex; flex-direction: column;
  transition: margin-left var(--t-md) var(--ease-out);
}

/* Mode reduit (bouton menu) — DESKTOP UNIQUEMENT.
   Sous 980px la sidebar devient un tiroir plein format : `.collapsed` n'y a
   plus de sens, d'ou le cadrage en min-width plutot qu'une neutralisation
   regle par regle dans le bloc mobile (qui aurait ecrase les `display` des
   composants au lieu de simplement ne pas s'appliquer). */
@media (min-width: 981px) {
  .sidebar.collapsed { width: var(--sidebar-collapsed-w); }
  .sidebar.collapsed .sidebar-link span,
  .sidebar.collapsed .sidebar-section-title,
  .sidebar.collapsed .sidebar-brand-text,
  .sidebar.collapsed .sidebar-user-meta,
  .sidebar.collapsed .sidebar-link .badge { display: none; }
  .sidebar.collapsed .sidebar-link { justify-content: center; padding: 10px 0; }
  .sidebar.collapsed .sidebar-brand { justify-content: center; padding: 18px 0; }
  .sidebar.collapsed ~ .main { margin-left: var(--sidebar-collapsed-w); }
}

/* Sidebar en tiroir (drawer) sur mobile / tablette.
   La sidebar reste `position: fixed` mais sort de l'ecran par la gauche ; le
   contenu reprend toute la largeur. Le bouton menu de la topbar bascule
   `.mobile-open` et affiche le voile `.sidebar-backdrop` (cf. footer_v3.php). */
@media (max-width: 980px) {
  .sidebar {
    transform: translateX(-100%);
    transition: transform var(--t-md) var(--ease-out);
    z-index: 60;
    box-shadow: var(--shadow-3);
  }
  .sidebar.mobile-open { transform: translateX(0); }
  .main { margin-left: 0; }

  .sidebar-backdrop {
    position: fixed; inset: 0;
    background: var(--bg-overlay);
    opacity: 0; visibility: hidden;
    transition: opacity var(--t-md) var(--ease-out), visibility var(--t-md) var(--ease-out);
    z-index: 55;
  }
  .sidebar-backdrop.show { opacity: 1; visibility: visible; }
}

.topbar {
  position: sticky; top: 0; z-index: 40;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: var(--s-3);
  padding: 0 var(--s-5);
  background: var(--bg-glass);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
}
.content { flex: 1; padding: var(--s-6); max-width: var(--container); width: 100%; }

/* Sidebar brand */
.sidebar-brand {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 18px var(--s-5) 16px;
  border-bottom: 1px solid var(--line);
}
.brand-mark {
  width: 32px; height: 32px;
  border-radius: var(--r-2);
  background: var(--grad-brand);
  display: grid; place-items: center;
  color: #fff; font-weight: 700; font-size: 13px;
  box-shadow: 0 4px 16px rgba(14,92,99,0.45), inset 0 1px 0 rgba(255,255,255,0.30);
  font-family: var(--font-display);
  flex-shrink: 0;
}
/* Marque Sando : l'icone SVG porte deja sa tuile Petrole + ses coins arrondis.
   On neutralise donc le fond du .brand-mark ; le halo (box-shadow) est conserve,
   il est de la meme teinte que la tuile. */
.brand-mark-logo { background: none; }
.brand-mark-logo img,
.brand-mark-logo svg { display: block; width: 32px; height: 32px; border-radius: var(--r-2); }
.sidebar-brand-text {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.01em;
}
.sidebar-brand-text small {
  display: block; font-size: 11px; color: var(--text-3); font-weight: 500; margin-top: 1px;
}

/* Sidebar nav */
.sidebar-scroll { flex: 1; overflow-y: auto; padding: var(--s-3); }
.sidebar-section { margin-bottom: var(--s-4); }
.sidebar-section-title {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.10em;
  color: var(--text-3);
  padding: var(--s-3) var(--s-3) var(--s-2);
}
.sidebar-link {
  display: flex; align-items: center; gap: var(--s-3);
  padding: 8px 10px;
  margin-bottom: 1px;
  border-radius: var(--r-2);
  color: var(--text-2);
  font-size: 13.5px;
  font-weight: 500;
  position: relative;
  transition: background var(--t-fast), color var(--t-fast), transform var(--t-fast);
}
.sidebar-link:hover { background: rgba(255,255,255,0.04); color: var(--text); }
[data-theme="light"] .sidebar-link:hover { background: rgba(15,17,28,0.04); }
.sidebar-link.active {
  background: linear-gradient(90deg, rgba(14,92,99,0.18), rgba(14,92,99,0.06));
  color: var(--text);
}
.sidebar-link.active::before {
  content: ""; position: absolute; left: -3px; top: 8px; bottom: 8px; width: 3px;
  background: var(--brand); border-radius: 0 var(--r-1) var(--r-1) 0;
  box-shadow: 0 0 8px var(--brand-glow);
}
.sidebar-link .icon { width: 16px; height: 16px; flex-shrink: 0; opacity: 0.85; }
.sidebar-link.active .icon { opacity: 1; color: var(--brand-soft); }
.sidebar-link .badge { margin-left: auto; }

/* Sous-entree de menu (ex. un magasin sous « Mes magasins ») : legerement en
   retrait et plus discrete, pour se lire comme une precision de l'entree
   au-dessus et non comme une rubrique de plus. */
.sidebar-link-sub {
  padding-left: 30px;
  font-size: var(--fs-xs);
  color: var(--text-3);
}
.sidebar-link-sub .icon { width: 13px; height: 13px; }
.sidebar-link-sub.active { color: var(--text); }
/* En mode reduit la sidebar n'affiche que les icones : un retrait n'aurait plus
   de sens et decalerait l'icone hors de son axe. */
@media (min-width: 981px) {
  .sidebar.collapsed .sidebar-link-sub { padding-left: 0; }
}

/* Sidebar footer / user */
.sidebar-footer { padding: var(--s-3); border-top: 1px solid var(--line); }
.sidebar-user {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-2);
  border-radius: var(--r-2);
  cursor: pointer;
  transition: background var(--t-fast);
}
.sidebar-user:hover { background: rgba(255,255,255,0.04); }
.sidebar-user-meta { min-width: 0; flex: 1; }
.sidebar-user-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar-user-role { font-size: 11px; color: var(--text-3); }

/* Topbar */
.topbar-cmd {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 12px;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--text-3);
  font-size: 13px;
  cursor: pointer;
  transition: all var(--t-fast);
  min-width: 280px;
}
.topbar-cmd:hover { border-color: var(--line-strong); color: var(--text-2); background: var(--bg-card-hover); }
.topbar-cmd .kbd {
  margin-left: auto;
  padding: 1px 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: 11px;
  font-family: var(--font-mono);
  color: var(--text-3);
}

.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; }

/* Topbar compacte sous 640px.
   Mesure avant : a 375px la topbar poussait le document a 491px de large,
   soit ~130px de defilement horizontal sur LES 26 PAGES du shell v3. La cause
   est le `min-width: 280px` de .topbar-cmd, qui depasse a lui seul la largeur
   utile d'un telephone une fois deduits les 2 boutons de gauche et les
   3 actions de droite.
   Aucune action n'est retiree : menu, accueil, recherche, theme et
   notifications restent tous atteignables. */
@media (max-width: 640px) {
  .topbar { padding: 0 var(--s-3); gap: var(--s-2); }

  /* La recherche se reduit a son icone. Le libelle et le raccourci clavier
     (« Ctrl K ») n'ont de toute facon aucun sens au doigt. */
  .topbar-cmd { min-width: 0; flex: 0 0 auto; padding: 7px 9px; }
  .topbar-cmd > span { display: none; }

  /* La pastille de role est CONSERVEE : une fois la recherche repliee, elle
     tient meme dans le pire cas mesure — « Admin / Patron », 94px de large a
     320px — sans reintroduire le moindre defilement. Inutile donc de masquer
     une information que l'utilisateur regarde pour savoir ou il se trouve.
     A savoir si l'on change d'avis : `.role-pill` est stylee par le <style>
     injecte au runtime par `assets/libs/js/v3/app-layout.js` (l. 157), donc
     APRES cette feuille — a specificite egale il gagne, et une media query
     n'ajoute aucune specificite. Il faudrait ecrire `.topbar .role-pill`. */

  .topbar-actions { gap: 2px; }
}

.icon-btn {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-2);
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-2);
  position: relative;
  transition: all var(--t-fast);
}
.icon-btn:hover { background: var(--bg-card); color: var(--text); border-color: var(--line); }
.icon-btn.dot::after {
  content: ""; position: absolute; top: 7px; right: 7px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 0 2px var(--bg-elevated), 0 0 8px var(--danger);
}

/* ---------- 5. Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px 14px;
  border-radius: var(--r-2);
  border: 1px solid transparent;
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  transition: all var(--t-fast);
  white-space: nowrap;
  font-family: inherit;
  background: transparent;
  color: var(--text);
}
/* FE-5 : le glow seul etait trop discret au clavier - on garde le glow ET l'anneau */
.btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; box-shadow: var(--shadow-glow); }

.btn-primary {
  background: var(--brand);
  color: #fff;
  border-color: rgba(255,255,255,0.10);
  box-shadow: 0 4px 12px rgba(14,92,99,0.30), inset 0 1px 0 rgba(255,255,255,0.20);
}
.btn-primary:hover { background: var(--brand-dim); transform: translateY(-1px); box-shadow: 0 6px 18px rgba(14,92,99,0.45); }

.btn-grad {
  background: var(--grad-brand);
  color: #fff;
  border: 0;
  box-shadow: 0 4px 14px rgba(168,85,247,0.30), inset 0 1px 0 rgba(255,255,255,0.20);
  position: relative;
  overflow: hidden;
}
.btn-grad::before {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(255,255,255,0.20), transparent);
  opacity: 0; transition: opacity var(--t-fast);
}
.btn-grad:hover::before { opacity: 1; }

.btn-secondary {
  background: var(--bg-card);
  border-color: var(--line);
  color: var(--text);
}
.btn-secondary:hover { background: var(--bg-card-hover); border-color: var(--line-strong); }

.btn-ghost { color: var(--text-2); }
.btn-ghost:hover { background: var(--bg-card); color: var(--text); }

.btn-success { background: var(--success); color: #fff; border: 0; box-shadow: 0 4px 12px rgba(16,185,129,0.30); }
.btn-success:hover { filter: brightness(1.05); transform: translateY(-1px); }
.btn-danger { background: var(--danger); color: #fff; border: 0; }
.btn-danger:hover { filter: brightness(1.05); }

.btn-sm { padding: 6px 10px; font-size: var(--fs-sm); }
.btn-xs { padding: 4px 8px; font-size: var(--fs-xs); border-radius: var(--r-1); }
.btn-lg { padding: 11px 18px; font-size: var(--fs-base); }
.btn-xl { padding: 14px 22px; font-size: var(--fs-lg); border-radius: var(--r-3); }
.btn-block { width: 100%; }

/* ---------- 6. Forms ---------- */
.field { display: flex; flex-direction: column; gap: 6px; }
.label { font-size: var(--fs-sm); font-weight: 500; color: var(--text-2); }
.label .req { color: var(--danger); margin-left: 2px; }
.input {
  display: block; width: 100%;
  padding: 9px 12px;
  border-radius: var(--r-2);
  border: 1px solid var(--line-strong);
  background: var(--bg-card);
  color: var(--text);
  font-size: var(--fs-md);
  transition: all var(--t-fast);
}
.input:focus { outline: none; border-color: var(--brand); box-shadow: var(--shadow-glow); background: var(--bg-elevated); }
.input::placeholder { color: var(--text-faint); }
.input-help { font-size: var(--fs-xs); color: var(--text-3); }
select.input {
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a3a9bf' stroke-width='2'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 32px;
}
textarea.input { resize: vertical; min-height: 80px; }

.input-group { display: flex; }
.input-group .input { border-radius: 0; }
.input-group > :first-child { border-radius: var(--r-2) 0 0 var(--r-2); }
.input-group > :last-child  { border-radius: 0 var(--r-2) var(--r-2) 0; }
.input-group-text {
  display: grid; place-items: center;
  padding: 0 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--line-strong);
  color: var(--text-3); font-size: var(--fs-sm);
}

.checkbox, .radio { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; font-size: var(--fs-md); }
.checkbox input, .radio input { width: 16px; height: 16px; accent-color: var(--brand); }

.switch { position: relative; display: inline-block; width: 38px; height: 22px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch-slider { position: absolute; inset: 0; background: var(--line-strong); border-radius: var(--r-full); transition: var(--t-fast); }
.switch-slider::before { content: ""; position: absolute; left: 3px; bottom: 3px; width: 16px; height: 16px; background: #fff; border-radius: 50%; transition: var(--t-md) var(--ease-spring); box-shadow: 0 2px 4px rgba(0,0,0,0.3); }
.switch input:checked + .switch-slider { background: var(--brand); box-shadow: 0 0 12px var(--brand-glow); }
.switch input:checked + .switch-slider::before { transform: translateX(16px); }

/* ---------- 7. Cards & Bento ---------- */
.card {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-4);
  box-shadow: var(--shadow-1), var(--shadow-inset);
  transition: all var(--t-md) var(--ease-out);
}
.card:hover { border-color: var(--line-strong); }
.card-head { padding: var(--s-5); display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); border-bottom: 1px solid var(--line); }
.card-head h5 { font-size: var(--fs-lg); font-weight: 600; }
.card-head .sub { font-size: var(--fs-sm); color: var(--text-3); margin-top: 2px; }
.card-body { padding: var(--s-5); }
.card-foot { padding: var(--s-4) var(--s-5); border-top: 1px solid var(--line); display: flex; justify-content: space-between; align-items: center; gap: var(--s-3); }

/* Glass card */
.glass {
  background: var(--bg-glass);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-4);
}

/* Bento grid */
.bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 16px;
  grid-auto-rows: minmax(112px, auto);
}
.bento > .item { grid-column: span 4; }
.bento .span-2 { grid-column: span 2; }
.bento .span-3 { grid-column: span 3; }
.bento .span-4 { grid-column: span 4; }
.bento .span-5 { grid-column: span 5; }
.bento .span-6 { grid-column: span 6; }
.bento .span-7 { grid-column: span 7; }
.bento .span-8 { grid-column: span 8; }
.bento .span-9 { grid-column: span 9; }
.bento .span-10 { grid-column: span 10; }
.bento .span-12 { grid-column: span 12; }
.bento .row-2 { grid-row: span 2; }
.bento .row-3 { grid-row: span 3; }
/* Effondrement de la grille bento sur mobile.
   ⚠️ Le bloc precedent ne remappait que .span-3/4/6/8/9. Une tuile en
   `.span-12` — ou 10, 7, 5, 2 — gardait donc son span desktop dans une grille
   passee a 6 colonnes, ce qui CREE des colonnes implicites et ecrase les
   colonnes explicites a zero.
   Mesure a 400px sur le dashboard, avant :
     grid-template-columns = 0px 0px 0px 0px 0px 0px 27px 27px 27px 27px 27px 27px
   soit 6 colonnes explicites a 0 plus 6 implicites a 27px, et 5 tuiles sur 13
   reduites a 80px de large. Sur fournisseur, une tuile tombait a 0px.
   Le `!important` est necessaire ici : ces regles doivent battre les `.span-N`
   desktop quel que soit l'ordre des declarations dans le fichier. */
@media (max-width: 980px) {
  .bento { grid-template-columns: repeat(2, 1fr); }
  .bento > .item,
  .bento .span-2,  .bento .span-3,  .bento .span-4,  .bento .span-5,
  .bento .span-6,  .bento .span-7,  .bento .span-8,  .bento .span-9,
  .bento .span-10, .bento .span-11, .bento .span-12 { grid-column: span 2 !important; }
  /* Les hauteurs imposees n'ont plus de sens une fois la grille a 2 colonnes :
     elles ouvraient des trous verticaux. */
  .bento .row-2, .bento .row-3 { grid-row: auto !important; }
}

/* Sous 640px, une seule colonne : a cette largeur, deux colonnes ne laissent
   que ~145px par tuile, ou un montant en GNF tombe mot par mot. */
@media (max-width: 640px) {
  .bento { grid-template-columns: 1fr !important; }
  /* La liste des `.span-N` est repetee ici a dessein : `.bento .span-12`
     (0,2,0) bat `.bento > *` (0,1,0), donc sans elle les tuiles gardaient le
     `span 2` du bloc 980px et creaient une 2e colonne IMPLICITE a 0px dans une
     grille declaree a une seule colonne. A specificite egale, c'est ce bloc-ci
     qui gagne puisqu'il vient apres. */
  .bento > *,
  .bento .span-2,  .bento .span-3,  .bento .span-4,  .bento .span-5,
  .bento .span-6,  .bento .span-7,  .bento .span-8,  .bento .span-9,
  .bento .span-10, .bento .span-11, .bento .span-12 { grid-column: 1 / -1 !important; }
}

/* Indispensable pour que le contenu d'une tuile puisse retrecir : sans
   `min-width: 0`, un element de grille refuse de descendre sous sa largeur
   min-content et fait deborder la tuile. `.tile` le pose deja pour lui-meme ;
   on l'etend a tout enfant direct de .bento, car toutes les tuiles ne sont pas
   des `.tile`. */
.bento > * { min-width: 0; }

/* Tile (bento element) */
/* ⚠️⚠️ UI-3 — LA TUILE EST SON PROPRE REFERENTIEL DE TAILLE.
   Toutes les tailles de KPI se disaient en `vw` (le VIEWPORT) alors que la boite
   qui doit les contenir est la TUILE. Or la largeur d'une tuile ne suit PAS le
   viewport : elle chute d'un coup quand la grille bento change de nombre de
   colonnes. Mesure du 2026-08-30 sur le hero du tableau de bord —
       980 px de fenetre : 890 px utiles dans la tuile
      1000 px de fenetre : 304 px utiles       (-586 px d'un seul palier)
   pendant que la police, elle, continuait de grossir. Aucune valeur de `vw` ne
   peut etre juste des deux cotes de cette falaise : c'est pour cela que le
   reglage avait ete refait tuile par tuile (UI-1, LANDING-1 bis) sans jamais
   fermer le sujet.
   ⭐ `container-type: inline-size` fait de la tuile l'unite de reference : 1 cqw
   vaut 1 % de SA largeur utile. La taille suit alors la boite a TOUTE largeur,
   y compris aux paliers intermediaires ou les mesures ponctuelles ne passaient
   pas. ⚠️ Ne pose AUCUNE contention de peinture : la tuile clippe deja par son
   `overflow: hidden`, rien n'est ajoute a ce titre. */
.tile { container-type: inline-size; }

.tile {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-4);
  padding: var(--s-5);
  position: relative;
  overflow: hidden;
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-1), var(--shadow-inset);
  transition: all var(--t-md) var(--ease-out);
  min-width: 0;
}
/* Prevent long unbreakable content (logs, mono text) from forcing column expansion */
.tile .mono, .tile pre, .tile code, .tile .log-row { word-break: break-word; overflow-wrap: anywhere; }
.tile .tw { max-width: 100%; }
.tile:hover { transform: translateY(-2px); border-color: var(--line-strong); box-shadow: var(--shadow-3); }
.tile-grad {
  background: linear-gradient(135deg, rgba(14,92,99,0.12), rgba(168,85,247,0.06));
  border-color: rgba(14,92,99,0.25);
}
.tile-emerald { background: linear-gradient(135deg, rgba(16,185,129,0.12), rgba(6,182,212,0.06)); border-color: rgba(16,185,129,0.25); }
.tile-sunset { background: linear-gradient(135deg, rgba(244,63,94,0.10), rgba(251,146,60,0.06)); border-color: rgba(244,63,94,0.25); }
.tile-aurora {
  background:
    radial-gradient(120% 100% at 0% 0%, rgba(14,92,99,0.18), transparent 60%),
    radial-gradient(120% 100% at 100% 100%, rgba(236,72,153,0.14), transparent 60%),
    var(--bg-card);
  border-color: rgba(168,85,247,0.30);
}
.tile-deco::after {
  content: ""; position: absolute; top: -40%; right: -20%;
  width: 240px; height: 240px;
  background: radial-gradient(circle, rgba(14,92,99,0.30), transparent 70%);
  filter: blur(20px);
  pointer-events: none;
}

/* Tuile hote d un popover (liste de combobox, menu contextuel).
   `.tile` clippe son contenu (overflow: hidden), ce qui rogne toute liste
   deroulante en position absolue des qu elle depasse le bas de la tuile.
   Ce modificateur leve le clip pour CETTE tuile seulement — jamais
   globalement : le clip protege les `.tile-deco::after` et le debordement
   des contenus larges partout ailleurs.
   A n appliquer qu a une tuile SANS decor deborde : `.tile-head` n a pas de
   fond (juste une bordure basse), il n y a donc aucun angle a rogner ici. */
.tile-popover { overflow: visible; }

/* Tile structured slots (head / body / foot) — utilisés par les views v3
   pour avoir un header séparé du body dans un tile bento.
   Marges négatives pour étendre head/foot jusqu'aux bords du tile (qui a padding s-5). */
.tile-head {
  margin: calc(-1 * var(--s-5)) calc(-1 * var(--s-5)) var(--s-4);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.tile-title { font-size: var(--fs-md); font-weight: 600; color: var(--text); margin: 0; }
.tile-actions { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.tile-body { padding: 0; }
.tile-foot {
  margin: var(--s-4) calc(-1 * var(--s-5)) calc(-1 * var(--s-5));
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
}
/* Quand un tile contient une table tw pleine largeur (pas de body wrapper),
   on neutralise le padding du tile pour que la table touche les bords. */
.tile.tile-flush { padding: 0; }
.tile.tile-flush .tile-head { margin: 0; }
.tile.tile-flush .tile-foot { margin: 0; }

/* Magasin card — utilisée par views/magasin.view.php (Admin) pour afficher
   chaque magasin avec cover, body KPI/rows, footer actions. */
.mag-card {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}
.mag-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-2);
  border-color: var(--line-strong, var(--line));
}
.mag-card.is-flagship { border-color: rgba(168, 85, 247, 0.30); }
.mag-card.is-warning  { border-color: rgba(244, 63, 94, 0.30); background: rgba(244, 63, 94, 0.04); }
.mag-card.is-closed   { opacity: 0.78; }

.mag-cover {
  position: relative;
  min-height: 84px;
  padding: var(--s-4) var(--s-5);
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s-1);
}
.mag-cover .mag-cover-title { font-family: var(--font-display); font-weight: 700; font-size: 20px; line-height: 1.1; color: #fff; }
.mag-cover .mag-cover-meta  { font-size: var(--fs-xs); color: rgba(255,255,255,0.85); }

.mag-body { padding: var(--s-4) var(--s-5); display: flex; flex-direction: column; gap: var(--s-3); }
.mag-body .mag-mini-kpis {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.mag-body .mag-mini-kpis .kpi-label { font-size: 10px; }
/* ⚠️ LE REFERENTIEL EST LA CELLULE, PAS LA CARTE : `.mag-mini-kpis` est une
   grille de QUATRE colonnes, chaque valeur ne dispose donc que du quart de la
   carte. Prendre la carte pour unite surestimerait la place d'un facteur 4 et
   la valeur deborderait toujours. */
.mag-body .mag-mini-kpis > div { container-type: inline-size; }
.mag-body .mag-mini-kpis .mini-val  { font-family: var(--font-display); font-weight: 700; font-size: 18px; color: var(--text); }
/* UI-3 : « 183,5M GNF » debordait de 34 px a 360 px — une valeur en pixels fixes
   ne sait pas retrecir. Plancher a 12 px : en dessous, un montant n'est plus
   lisible sur un telephone. */
.mag-body .mag-mini-kpis .mini-val {
  --car: 10;
  font-size: clamp(12px, calc(100cqw / (var(--car) * 0.63)), 18px);
}

.mag-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding: 6px 0;
  border-bottom: 1px dashed var(--line);
  font-size: var(--fs-sm);
}
.mag-row:last-child { border-bottom: 0; }
.mag-row .lbl { color: var(--text-3); font-weight: 500; }
.mag-row .val { color: var(--text); text-align: right; display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; justify-content: flex-end; }

.mag-foot {
  padding: var(--s-3) var(--s-5);
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  flex-wrap: wrap;
  background: var(--bg-elevated);
}

.substock {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--r-1);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  font-size: 11px;
  color: var(--text-2);
}

/* KPI display */
.kpi {
  display: flex; flex-direction: column; gap: var(--s-3);
  height: 100%;
}
.kpi-label { font-size: var(--fs-xs); color: var(--text-3); font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; display: flex; align-items: center; gap: 6px; }
.kpi-icon-mini { width: 14px; height: 14px; opacity: 0.7; }
.kpi-value {
  font-family: var(--font-display);
  /* Le plancher etait a 28px : trop haut pour un telephone, un montant a 8
     chiffres debordait de sa tuile. Il descend a 24px.
     ⚠️ La part variable reste a `3vw` et NON a 5vw : avec 5vw, le plafond de
     44px est atteint des 880px, alors qu'avec 3vw il ne l'est qu'a 1467px. Sur
     la fiche client, la tuile KPI fait 232px a 1280px — soit 190px utiles pour
     le montant — et « 7 536 500 » y demande 217px a 44px : la taille maximale
     tenable y est de 38.5px, exactement ce que rend `3vw` a cette largeur.
     Monter la part variable faisait donc GROSSIR les KPI en desktop et les
     tronquait. Seul le plancher devait bouger. */
  /* ⭐ UI-3 : la part variable se dit en cqw, et elle est divisee par la
     LONGUEUR du contenu (`--car`, publie par footer_v3.php — un FAIT sur le
     contenu, pas une taille : la doctrine UI-1 tient). Consequence voulue : une
     valeur COURTE atteint le plafond exactement comme avant, seule une valeur
     longue retrecit, et seulement de ce qu'il faut.
     ⚠️ 0.63 = la chasse d'un chiffre a chasse fixe (0,5718 mesure) + ~10 % de
     garde pour le repli de fonte et la marge de `.unit`.
     ⚠️ LES DEUX DECLARATIONS SONT VOULUES : un navigateur sans container
     queries ignore la seconde et garde la premiere — il retrouve exactement le
     comportement d'avant UI-3 au lieu de perdre toute taille. Meme prudence que
     le refus de `:has()` pour les Android anciens du parc guineen. */
  --car: 11;
  font-size: clamp(24px, 3vw, 44px);
  /* ⚠️ PLANCHER A 20px DANS CETTE DECLARATION, ET 24px DANS LE REPLI — ce n'est
     pas une incoherence. Le plancher de 24px avait ete pose pour le TELEPHONE,
     ou la tuile occupe toute la largeur : la, `cqw` donne une taille large et le
     plancher ne mord jamais. Il ne mord qu'en BUREAU, dans une tuile `span-3`
     d'une grille a 12 colonnes — mesure du 2026-08-30, `venteListe` a 1024 px :
     169 px de tuile, soit 129 px utiles, ou « 442 527 475 » demande 151 px a
     24px. Sur un ecran de bureau, 20px se lit sans peine ; un montant coupe,
     non. */
  font-size: clamp(20px, calc(100cqw / (var(--car) * 0.63)), 44px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  margin-top: auto;
  font-variant-numeric: tabular-nums; /* chiffres a chasse fixe : evite le tremblement du count-up */
  /* ⚠️⚠️ UI-3 REINTRODUIT `overflow-wrap` ICI, ET LE MOTIF A CHANGE.
     La note precedente disait vrai de son epoque : avec des tailles calees sur
     le VIEWPORT, la valeur debordait souvent, la coupure se declenchait souvent,
     et elle tombait au milieu des chiffres. Depuis que la taille se cale sur la
     TUILE, le debordement ne survient plus qu'a une condition : le plancher est
     atteint ET la tuile est plus etroite que le montant a cette taille.
     ⭐ Dans CE cas precis, l'alternative n'est pas « couper proprement » contre
     « passer a la ligne » — c'est « afficher un nombre FAUX » contre « afficher
     le nombre juste sur deux lignes ». « 442 527 4… » se lit comme un montant
     plus petit qu'il n'est, dans un logiciel qui tient une comptabilite ; un
     montant sur deux lignes est laid et exact. Une tuile peut etre plus haute,
     un chiffre ne peut pas etre faux.
     ⚠️⚠️ MAIS PAS `overflow-wrap: anywhere` — MESURE, PUIS CORRIGE. Laisse
     libre, la rupture retient la DERNIERE occasion qui tient dans la ligne,
     donc un caractere quelconque : « 6 492 25 / 5 » sur l'ecran de comptabilite
     a 1024 px. Exact, et lisible de travers. Les seules cesures acceptables sont
     celles des GROUPES de milliers ; elles sont offertes au navigateur par un
     `<wbr>` pose apres chaque separateur (includes/footer_v3.php), et le mode
     permissif est justement ce qui les ECRASERAIT.
     ⚠️ C'est un DERNIER recours, pas le mecanisme : le balayage 360->1440 px
     verifie ou il se declenche. */
  overflow-wrap: normal;
}
.kpi-value .unit { font-size: 0.5em; font-weight: 500; color: var(--text-3); margin-left: 4px; }

/* ── UI-1 : la taille d'un KPI se dit dans la FEUILLE, jamais dans l'attribut ──
   ⚠️⚠️ 22 `style="font-size:…"` en ligne vivaient sur des `.kpi-value` dans 7
   vues. Un style en ligne écrase la feuille : la règle `.kpi-value` ne pouvait
   plus rien pour ces tuiles, et le `clamp()` calibré pour elles ne s'appliquait
   pas. Résultat mesuré le 2026-08-29, sur 60 cas (5 vues × 4 largeurs × 3
   valeurs) : **23 cas fautifs**, dont des coupes en montant RÉEL sur
   `comptabilite` (3 à 1280 px, 5 à 1024 px) et sur `gestion_mag`.

   ⭐ `.kpi-value--compact` est la traduction en feuille de ce que ces attributs
   voulaient dire : « cette tuile est étroite, le nombre doit y tenir ». La
   différence est qu'ici la taille est FLUIDE — elle descend quand la fenêtre
   rétrécit, ce qu'une valeur fixe en pixels ne sait pas faire, et c'est
   précisément aux largeurs intermédiaires (1024–1280 px) que les tuiles
   `span-2` et `span-3` coupaient.

   ⚠️ Le plancher est à 15 px : en dessous, un montant devient illisible sur un
   téléphone, et mieux vaut alors une tuile plus haute qu'un chiffre qu'on ne
   peut pas lire. Le plafond à 21 px reste sous les 22–24 px des anciens
   attributs : c'est voulu, ce sont eux qui coupaient. */
/* (regle deplacee plus bas : voir .tile .kpi-value--compact) */

/* ── LANDING-1 bis : une tuile a badge RESERVE la place de son badge ────────
   ⚠️⚠️ LE BADGE EST EN `position:absolute` EN HAUT A DROITE, donc HORS FLUX :
   il ne pousse rien, il RECOUVRE. Le montant passait dessous et se retrouvait
   coupe — un chiffre tronque sur un ecran de rapport, c'est une valeur fausse
   pour qui la lit.
   ⭐ Mesure du 2026-08-29 (chevauchement du texte sous le badge, montant le plus
   long que le format GNF puisse produire — 999 999 999 999) :
       1280 px : CA valide 157 px · Panier moyen 157 px · Meilleur mois 71 px
       1024 px : CA valide 158 px · Panier moyen 158 px · Meilleur mois 135 px
        768 px et 390 px : aucun (la grille passe pleine largeur, la place existe)
   Le defaut est donc celui de la TUILE, pas d'une valeur particuliere : il
   apparait aux largeurs ou la tuile est etroite ET le badge present.
   ⚠️ La classe se pose sur la TUILE et non sur le badge, volontairement : cela
   evite `:has()`, dont le support manque encore sur les Android anciens que le
   parc guineen fait durer.
   ⚠️ 56 px = 14 (droite) + 38 (badge) + 4 de garde. */
.tile-ic-tr .kpi-label {
  /* Le label occupe la BANDE du badge : 38 px de haut, et sa largeur s'arrete
     avant lui. C'est ce qui pousse la valeur SOUS le badge — la seule facon que
     le chevauchement soit impossible quelle que soit la longueur du montant.
     Reduire la police a la place n'aurait fait que reculer le probleme d'un
     chiffre. */
  min-height: 38px;
  padding-right: 56px;
  align-items: flex-start;
}
.tile-ic-tr .kpi-value {
  /* La valeur recupere TOUTE la largeur de la tuile : elle n'est plus sous le
     badge, donc elle n'a plus a lui reserver quoi que ce soit. */
  padding-right: 0;
  /* ⭐ SECOND DEFAUT, revele par la correction du premier : une fois le badge
     ecarte, le montant debordait encore de la TUILE. Le `clamp()` general de
     `.kpi-value` a ete calibre sur « 7 536 500 » — SEPT chiffres ; huit ne
     tiennent pas dans une tuile `span-3`.
     Mesure (chevauchement en px, montant reel « 20 284 650 ») :
         1280 px : 22 px hors tuile · 1024 px : 43 px · 768 et 390 px : aucun.
     ⚠️ On ne touche pas au `clamp()` general : il vaut pour toutes les tuiles
     KPI du produit, dont beaucoup n'ont pas de badge et affichent des valeurs
     courtes. La reduction est cantonnee aux tuiles a badge. */
  font-size: clamp(20px, 2.2vw, 30px);
  /* UI-3 : meme bascule en cqw. Le plafond de 30px de LANDING-1 bis est
     conserve — il vaut pour les valeurs courtes, qui l'atteignent toujours. */
  font-size: clamp(20px, calc(100cqw / (var(--car) * 0.63)), 30px);
}

/* ⚠️⚠️ SPECIFICITE ET ORDRE — la mesure a tranche, pas l'intuition.
   Ecrite `.kpi-value--compact` (0,1,0) et placee AVANT, cette regle perdait
   contre `.tile-ic-tr .kpi-value` (0,2,0) de LANDING-1 bis : les tuiles a badge
   recevaient 28,16 px au lieu de 21, soit PLUS que les 22 px de l'attribut
   qu'on venait de retirer — une regression mesuree (caisse 1280 px : 0 coupe
   avant, 4 apres). Une classe utilitaire doit battre les regles CONTEXTUELLES :
   d'ou `.tile .kpi-value--compact` (0,2,0), placee APRES elles. */
.tile .kpi-value--compact { font-size: clamp(15px, 1.45vw, 21px); }
.tile .kpi-value--compact { font-size: clamp(15px, calc(100cqw / (var(--car) * 0.63)), 21px); }
.kpi-trend { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-xs); font-weight: 600; padding: 3px 8px; border-radius: var(--r-full); }
/* Aplat + encre foncee, et non une teinte a 14 % d'opacite : sur le fond blanc
   du theme clair, `--emerald-soft` sur `rgba(16,185,129,0.14)` retombait a
   ~1.5:1 — un ecart de tendance illisible. L'aplat donne ~6.7:1 (vert) et
   ~4.6:1 (corail), et la MEME valeur dans les deux themes : ni `--emerald`, ni
   `--coral`, ni `--accent-ink` n'est redefini en clair, donc aucune surcharge
   de theme a maintenir. */
.kpi-trend.up { color: var(--accent-ink); background: var(--emerald); }
.kpi-trend.down { color: var(--accent-ink); background: var(--coral); }

/* ---------- 8. Tables ---------- */
/* ⚠⚠ `contain: paint` N'EST PAS DECORATIF : sans lui, la PAGE defile
   horizontalement alors que le tableau est pourtant correctement clippe.

   Le defaut est contre-intuitif et il a coute une passe entiere a diagnostiquer.
   `overflow-x: auto` fait bien son travail VISUELLEMENT — mesure sur ?page=tarifs
   a 390 px : `.tw` affiche 348 px sur 734 px de contenu, aucun element ne depasse
   le viewport, et un balayage des ~900 elements de la page ne remonte AUCUN
   coupable. Mais la largeur intrinseque du tableau (min-width: 720px) continue
   d'alimenter la region de DEBORDEMENT DEFILABLE de l'element racine :
   `documentElement.scrollWidth` valait 694 px pour un viewport de 390. Resultat,
   le navigateur laissait defiler la page de 300 px dans du VIDE — rien a voir,
   rien a lire, juste la mise en page qui part.

   Prouve par elimination : masquer `.tw` faisait tomber `scrollWidth` de 694 a
   390. Et par essai comparatif : `width:100%`, `max-width:100%`, `min-width:0`
   sur la table et `display:block` sur la table ne changent RIEN (694) ; seules
   les trois formes de `contain` ramenent a 390 en laissant le tableau defiler.

   `paint` plutot que `layout` ou `content` : c'est la plus etroite des trois qui
   corrige, et elle ne fait que declarer ce que `overflow: auto` impose deja —
   rien ne se peint hors de cette boite.

   ⚠ NE PAS remplacer par `overflow-x: hidden` sur `body` ou `.lp` : ca masque le
   symptome, casse le defilement ancre, et peut couper du contenu reel.

   ⚠ `contain` fait de l'element un bloc conteneur pour ses descendants
   `position: fixed`. Verifie : aucune des 37 vues portant `.tw` n'en contient.
   Le `position: sticky` qui vit ICI (colonne d'actions figee a droite en mobile,
   en-tete de tableau) est INTACT — mesure au banc d'essai, colonne collee a
   -12 px du bord avant comme apres 106 px de defilement horizontal, en-tete a 0
   apres 200 px de defilement vertical, chiffres identiques avec et sans.

   ⚠ Toute modification ici doit etre reportee dans
   `public/v3-mockups/assets/css/design-system-v3.css` (CLAUDE.md §7). */
.tw { overflow-x: auto; border-radius: var(--r-3); contain: paint; }

/* ---------- Colonne d'action des tables de donnees ----------
   Classe utilitaire `.col-actions`, a poser sur le <th> ET sur les <td>
   correspondants. Generique a dessein : elle couvre les tables existantes et
   les futures, sans regle au cas par cas.

   Deux problemes qu'elle resout :

   1. Cible tactile. Les boutons de table etaient a 24px de haut ; la regle des
      44px n'avait ete posee que dans le tunnel de vente. (Sous 768px, plus bas.)
   2. Accessibilite sans defilement. La colonne d'action est en DERNIERE
      position : atteindre « Encaisser » demandait de faire glisser toute la
      table vers la gauche. Elle est desormais figee a droite, donc toujours
      visible pendant que le reste defile dessous.

   ⚠️⚠️ UI-3 — LE COLLAGE SORT DU `@media (max-width: 768px)`, ET C'EST LA
   CORRECTION DE FOND. La regle avait ete ecrite en supposant que le defaut
   etait « telephone ». Il ne l'est pas : mesure du 2026-08-30, colonne d'action
   COUPEE alors que le conteneur defile pourtant bien —

       gestion_mag  56 px hors vue a 1100 px · 132 px a 1024 px
       stock       121 px a 1100 px · 197 px a 1024 px
       client      134 px a 1100 px · 210 px a 1024 px
       venteListe   77 px a 1024 px

   toutes largeurs ou le media query ne s'appliquait pas. Le bouton « Ajuster »
   de gestion_mag s'affichait litteralement coupe en deux, sans barre de
   defilement visible pour indiquer qu'il restait quelque chose a droite.
   « Un tableau large defile dans son conteneur » etait donc TENU au sens
   mecanique et FAUX au sens de l'utilisateur : il voyait un bouton tronque et
   n'avait aucune raison de deviner qu'il fallait faire glisser la table.

   ⭐ Figee a TOUTE largeur, la colonne est toujours entierement visible et
   cliquable, quel que soit le defilement horizontal. Le desktop change, et
   c'est le but — c'est la ou le defaut se voyait.

   `position: sticky` fonctionne ici parce que le conteneur defilant est `.tw`
   et que la cellule est son descendant : le collage se fait par rapport a lui,
   pas au viewport. Le fond doit etre OPAQUE (sinon le contenu qui defile se
   voit au travers) et le separateur marque la limite. */
.col-actions { text-align: right; }

.tw table.t th.col-actions,
.tw table.t td.col-actions {
    position: sticky;
    right: 0;
    z-index: 2;
    /* Opaque, sinon les colonnes qui defilent apparaissent sous les boutons. */
  background: var(--bg-card);
  box-shadow: -8px 0 12px -8px rgba(0, 0, 0, 0.45);
  border-left: 1px solid var(--line-strong);
  white-space: nowrap;
}
/* L'en-tete a son propre fond dans le composant table : on le reprend, sinon
   la cellule figee laisserait voir les en-tetes defiler dessous. */
.tw table.t thead th.col-actions {
  background: var(--bg-elevated);
  z-index: 3;
}
/* Les lignes zebrees / survolees repeignent la cellule : la colonne figee
   doit suivre, faute de quoi elle trancherait sur sa ligne. */
.tw table.t tbody tr:hover td.col-actions { background: var(--bg-card-hover); }

/* ⭐ UI-3 — LE DEFILEMENT D'UN TABLEAU DOIT SE VOIR.
   Chrome et Safari masquent la barre de defilement d'un conteneur tant qu'on ne
   le fait pas glisser : un tableau plus large que sa tuile n'annonce RIEN. C'est
   ce silence qui a fait conclure « la page ne defile pas » sur gestion_mag, et
   c'est la moitie manquante de la regle « un tableau large defile dans son
   conteneur » — il doit aussi DIRE qu'il defile.
   ⚠️ Une barre fine et discrete, pas la barre systeme : elle vit a l'interieur
   du conteneur et ne doit pas concurrencer le contenu. */
.tw { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
.tw::-webkit-scrollbar { height: 8px; }
.tw::-webkit-scrollbar-track { background: transparent; }
.tw::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: var(--r-full);
}
.tw:hover::-webkit-scrollbar-thumb { background: var(--text-3); }

/* Cible tactile des controles d'action dans les tables de donnees.
   La regle des 44px n'avait ete posee que dans le tunnel de vente : partout
   ailleurs les boutons de table restaient a 24-34px de haut.
   Independant de `.col-actions` a dessein — ceci couvre TOUS les controles de
   table, y compris ceux d'une colonne non marquee. Desktop inchange. */
@media (max-width: 768px) {
  .tw table.t td .btn,
  .tw table.t td a.btn,
  .tw table.t td button:not(.btn-link),
  .tw table.t td .icon-btn {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .tw table.t td .icon-btn { min-width: 44px; }
  /* Deux actions cote a cote restent separees : sans cet ecart, l'agrandissement
     des cibles les colle et provoque des taps rates. */
  .tw table.t td .btn + .btn,
  .tw table.t td .icon-btn + .icon-btn { margin-left: 6px; }
}
.t {
  width: 100%; border-collapse: collapse;
  font-size: var(--fs-md);
}
.t th, .t td { padding: 10px 14px; text-align: left; vertical-align: middle; }
.t thead th {
  font-size: var(--fs-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-3);
  border-bottom: 1px solid var(--line);
  background: var(--bg-elevated);
  white-space: nowrap;
  position: sticky; top: 0;
}
.t tbody tr { transition: background var(--t-fast); }
.t tbody tr:hover { background: rgba(255,255,255,0.02); }
[data-theme="light"] .t tbody tr:hover { background: rgba(15,17,28,0.02); }
.t tbody td { border-bottom: 1px solid var(--line); }

/* ---------- 9. Badges ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px;
  font-size: var(--fs-xs); font-weight: 500;
  border-radius: var(--r-full);
  background: var(--bg-elevated);
  color: var(--text-2);
  line-height: 1.4;
  white-space: nowrap;
  border: 1px solid var(--line);
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 6px currentColor; }
.badge-brand   { background: rgba(14,92,99,0.14); color: var(--brand-soft); border-color: rgba(14,92,99,0.30); }
.badge-success { background: rgba(16,185,129,0.14); color: var(--emerald-soft); border-color: rgba(16,185,129,0.30); }
.badge-warning { background: rgba(245,158,11,0.14); color: var(--amber); border-color: rgba(245,158,11,0.30); }
.badge-danger  { background: rgba(244,63,94,0.14); color: var(--coral); border-color: rgba(244,63,94,0.30); }
.badge-info    { background: rgba(6,182,212,0.14); color: var(--cyan); border-color: rgba(6,182,212,0.30); }
.badge-violet  { background: rgba(168,85,247,0.14); color: var(--violet); border-color: rgba(168,85,247,0.30); }

/* ---------- 10. Avatars ---------- */
.avatar {
  width: 32px; height: 32px;
  border-radius: 50%;
  display: inline-grid; place-items: center;
  font-weight: 600;
  font-size: var(--fs-xs);
  color: #fff;
  flex-shrink: 0;
  overflow: hidden;
  background: var(--grad-brand);
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.avatar.sm { width: 24px; height: 24px; font-size: 10px; }
.avatar.lg { width: 48px; height: 48px; font-size: 16px; }
.avatar.xl { width: 64px; height: 64px; font-size: 22px; }
.avatar.online::after { content: ""; position: absolute; bottom: -1px; right: -1px; width: 10px; height: 10px; background: var(--emerald); border: 2px solid var(--bg-elevated); border-radius: 50%; }
.avatar-stack .avatar { border: 2px solid var(--bg-card); margin-left: -8px; }
.avatar-stack .avatar:first-child { margin-left: 0; }

/* Variants */
.av-violet { background: linear-gradient(135deg,#8b5cf6,#ec4899); }
.av-emerald { background: linear-gradient(135deg,#10b981,#06b6d4); }
.av-sunset { background: linear-gradient(135deg,#f59e0b,#f43f5e); }
.av-cyan { background: linear-gradient(135deg,#06b6d4,#3b82f6); }

/* ---------- 11. Toasts & Alerts ---------- */
.alert {
  display: flex; gap: 12px;
  padding: 12px 14px;
  border-radius: var(--r-3);
  border: 1px solid var(--line);
  background: var(--bg-card);
  font-size: var(--fs-md);
  margin-bottom: var(--s-4);
  position: relative;
}
/* ⚠ `display: flex` ci-dessus GAGNE contre l attribut `hidden` du HTML, dont la
   feuille par defaut du navigateur ne pose que `display: none` — a specificite
   0. Consequence observee sur l ecran Appro : `<div class="alert alert-danger"
   data-appro-error hidden>` s affichait en permanence, en barre rouge VIDE, et
   le JS qui ne fait que basculer `hidden` n y pouvait rien.
   La regle vaut pour TOUTE alerte pilotee par `hidden` — c est le contrat
   normal de l attribut, il ne doit pas dependre du composant. `!important` est
   ici justifie : il faut battre toute variante de `display` posee plus bas. */
.alert[hidden] { display: none !important; }
/* Plus de bande d accent a gauche (l ancien `.alert::before` de 3 px) : la
   couleur passe par la BORDURE COMPLETE et le fond teinte, qui entourent le
   message au lieu de le tamponner sur un cote. `overflow: hidden` a ete retire
   avec elle — il n existait que pour clipper cette bande sur les angles
   arrondis, et il rognait au passage tout contenu debordant (anneau de focus
   d un bouton place dans l alerte). */
.alert-success { background: rgba(16,185,129,0.06); border-color: rgba(16,185,129,0.35); }
.alert-warning { background: rgba(245,158,11,0.06); border-color: rgba(245,158,11,0.35); }
.alert-danger  { background: rgba(244,63,94,0.06); border-color: rgba(244,63,94,0.35); }
.alert-info    { background: rgba(6,182,212,0.06); border-color: rgba(6,182,212,0.35); }

/* Toast container */
.toasts { position: fixed; bottom: 24px; right: 24px; z-index: 200; display: flex; flex-direction: column; gap: 10px; max-width: 380px; }
.toast {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px;
  background: var(--bg-elevated);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-3);
  min-width: 280px;
  animation: toast-in var(--t-md) var(--ease-spring);
}
@keyframes toast-in {
  from { transform: translateX(20px) scale(0.95); opacity: 0; }
  to { transform: translateX(0) scale(1); opacity: 1; }
}

/* ---------- 12. Modal & Drawer ---------- */
.backdrop {
  position: fixed; inset: 0;
  background: var(--bg-overlay);
  backdrop-filter: blur(8px);
  display: grid; place-items: center;
  z-index: 100;
  padding: var(--s-4);
  animation: fade var(--t-md) var(--ease-out);
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.modal {
  background: var(--bg-elevated);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-5);
  box-shadow: var(--shadow-4);
  width: 100%; max-width: 520px;
  overflow: hidden;
  animation: pop var(--t-slow) var(--ease-spring);
}
.modal.lg { max-width: 720px; }
.modal.xl { max-width: 960px; }
@keyframes pop {
  from { transform: translateY(20px) scale(0.96); opacity: 0; }
  to { transform: translateY(0) scale(1); opacity: 1; }
}
.modal-head { padding: var(--s-5); display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); }
.modal-body { padding: var(--s-5); max-height: 70vh; overflow-y: auto; }
.modal-foot { padding: var(--s-4) var(--s-5); border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: var(--s-2); background: var(--bg-card); }

/* Drawer (right slide-in) */
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 100%; max-width: 480px;
  background: var(--bg-elevated);
  border-left: 1px solid var(--line);
  z-index: 90;
  display: flex; flex-direction: column;
  animation: slide-in var(--t-md) var(--ease-out);
}
@keyframes slide-in { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

/* ---------- 13. Command palette (Ctrl+K) ---------- */
.cmdk {
  position: fixed; top: 18vh; left: 50%; transform: translateX(-50%);
  width: 100%; max-width: 580px;
  background: var(--bg-elevated);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-4);
  box-shadow: var(--shadow-4);
  z-index: 110;
  overflow: hidden;
  animation: pop var(--t-md) var(--ease-spring);
}
.cmdk-input {
  width: 100%; border: 0; outline: 0;
  padding: 16px 20px;
  background: transparent;
  color: var(--text);
  font-size: var(--fs-lg);
  border-bottom: 1px solid var(--line);
}
.cmdk-list { max-height: 360px; overflow-y: auto; padding: 6px; }
.cmdk-section-title { font-size: 11px; text-transform: uppercase; color: var(--text-3); font-weight: 600; letter-spacing: 0.06em; padding: 10px 12px 6px; }
.cmdk-item {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border-radius: var(--r-2);
  cursor: pointer;
  font-size: var(--fs-md);
  color: var(--text);
  transition: background var(--t-fast);
}
.cmdk-item:hover, .cmdk-item.active { background: rgba(14,92,99,0.10); color: var(--text); }
.cmdk-item .ic { width: 18px; height: 18px; color: var(--text-3); }
.cmdk-item .kbd { margin-left: auto; padding: 1px 5px; background: var(--bg-card); border: 1px solid var(--line); border-radius: 4px; font-size: 10px; font-family: var(--font-mono); color: var(--text-3); }

/* ---------- 14. Tabs / Pills / Chips ---------- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: var(--s-5); padding: 0 var(--s-1); }
.tab {
  padding: 10px 14px; font-size: var(--fs-md);
  color: var(--text-2);
  background: transparent; border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  font-weight: 500;
  transition: color var(--t-fast), border var(--t-fast);
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--brand-soft); border-bottom-color: var(--brand); }

/* Barre d'onglets defilante — a TOUTES les largeurs.
   `.tabs` est un flex en `nowrap` sans overflow : les 10 onglets de la
   comptabilite s'alignaient jusqu'a 1046px et faisaient defiler LA PAGE.
   Mesures avant : +660px de defilement horizontal du body sur comptabilite a
   400px, +588px sur rapport, et encore +24px a 1280px (1022px d'onglets pour
   974 disponibles). Le defilement doit rester DANS la barre.
   ⚠️ Volontairement HORS media query, contrairement au brief qui la cadrait
   sous 768px : le debordement existe aussi en desktop des que le module a
   beaucoup d'onglets. `overflow-x: auto` ne se voit pas quand tout tient.
   `overflow-y: hidden` est explicite : des qu'un axe passe en `auto`, l'autre
   quitte `visible`, et le `margin-bottom: -1px` de l'onglet actif — qui fait
   mordre son liseré sur la bordure du conteneur — ouvrait une barre de
   defilement verticale parasite. Le `padding-bottom` de 1px rend au liseré la
   place que ce clipping lui prenait. */
/* ⚠️⚠️ UI-3 — LE DEFILEMENT RESTE, MAIS SEULEMENT LA OU IL EST UN GESTE.
   La decision precedente — « le defilement doit rester DANS la barre », hors
   media query — etait juste sur son motif (le debordement existe aussi en
   desktop) et incomplete sur sa consequence. Mesure du 2026-08-30, `rapport`,
   dix onglets :
       1440 px : « Caisse & journal » a 38 px hors vue, « Approvisionnement » a 224 px
       1100 px : les deux, a 378 et 564 px
   Deux entrees de navigation invisibles sur un ecran de bureau qui a toute la
   place voulue, et aucune barre de defilement pour dire qu'elles existent :
   3 px de pouce, en overlay, invisibles tant qu'on ne fait pas defiler quelque
   chose qu'on ne sait pas defilable.
   ⭐ Un balayage horizontal est un geste NATUREL sur un ecran tactile et une
   manoeuvre obscure a la souris. La barre passe donc a la ligne en bureau — dix
   onglets sur deux rangees, tous visibles — et garde le defilement sous 768 px,
   ou il est l'idiome attendu et ou passer a la ligne couterait sept rangees. */
.tabs {
  flex-wrap: wrap;
  padding-bottom: 1px;
}

@media (max-width: 768px) {
  .tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
  }
}
.tabs::-webkit-scrollbar { height: 3px; }
.tabs::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: var(--r-full); }
.tab {
  flex: 0 0 auto;
  white-space: nowrap;
}

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px;
  border-radius: var(--r-full);
  background: var(--bg-card);
  border: 1px solid var(--line);
  font-size: 13px; font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  transition: all var(--t-fast);
}
.chip:hover { border-color: var(--line-strong); color: var(--text); }
.chip.active { background: rgba(14,92,99,0.14); border-color: rgba(14,92,99,0.40); color: var(--brand-soft); }

/* Segment control */
.segment {
  display: inline-flex;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  padding: 3px;
  gap: 1px;
}
.segment .seg {
  padding: 5px 10px;
  font-size: var(--fs-sm);
  color: var(--text-2);
  border-radius: var(--r-1);
  cursor: pointer;
  background: transparent;
  border: 0;
  font-weight: 500;
  transition: all var(--t-fast);
}
.segment .seg:hover { color: var(--text); }
.segment .seg.active { background: var(--bg-elevated); color: var(--text); box-shadow: var(--shadow-1); }

/* ---------- 15. Page header ---------- */
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  flex-wrap: wrap; gap: var(--s-4);
  margin-bottom: var(--s-6);
}
.page-eyebrow { font-size: 12px; font-weight: 600; color: var(--brand-soft); text-transform: uppercase; letter-spacing: 0.08em; }
.page-title { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -0.025em; margin: 6px 0 4px; font-family: var(--font-display); }
.page-sub { color: var(--text-3); font-size: var(--fs-base); }
.crumbs { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-3); }
.crumbs .sep { opacity: 0.5; }
.crumbs a { color: var(--text-3); }
.crumbs a:hover { color: var(--text); }

/* ---------- 16. Pagination ---------- */
.pager { display: flex; align-items: center; gap: 4px; }
.pager .p {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--r-1);
  background: var(--bg-card);
  border: 1px solid var(--line);
  color: var(--text-2);
  font-size: 13px;
  cursor: pointer;
  transition: all var(--t-fast);
}
.pager .p:hover { border-color: var(--line-strong); color: var(--text); }
.pager .p.active { background: var(--brand); color: #fff; border-color: var(--brand); }

/* ---------- 17. Progress ---------- */
.progress { width: 100%; height: 6px; background: var(--line); border-radius: var(--r-full); overflow: hidden; }
.progress > .fill { height: 100%; background: var(--grad-brand); border-radius: var(--r-full); transition: width var(--t-slow) var(--ease-out); }

.ring { position: relative; width: 90px; height: 90px; }
.ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.ring .label { position: absolute; inset: 0; display: grid; place-items: center; font-weight: 700; font-family: var(--font-display); font-size: 18px; }

/* ---------- 18. Empty state ---------- */
.empty { text-align: center; padding: var(--s-12) var(--s-4); color: var(--text-3); }
.empty-ic {
  width: 72px; height: 72px;
  margin: 0 auto var(--s-4);
  border-radius: var(--r-4);
  background: var(--grad-night);
  border: 1px solid var(--line);
  display: grid; place-items: center;
  color: var(--text-3);
}

/* ---------- 19. Utility ---------- */
.flex { display: flex; }
.fcol { flex-direction: column; }
.f1 { flex: 1; }
.fwrap { flex-wrap: wrap; }
.aic { align-items: center; }
.ais { align-items: flex-start; }
.aie { align-items: flex-end; }
.jcb { justify-content: space-between; }
.jce { justify-content: flex-end; }
.jcc { justify-content: center; }
.gap-1 { gap: 4px; } .gap-2 { gap: 8px; } .gap-3 { gap: 12px; } .gap-4 { gap: 16px; } .gap-5 { gap: 20px; } .gap-6 { gap: 24px; }
.mt-1 { margin-top: 4px; } .mt-2 { margin-top: 8px; } .mt-3 { margin-top: 12px; } .mt-4 { margin-top: 16px; } .mt-5 { margin-top: 20px; } .mt-6 { margin-top: 24px; } .mt-8 { margin-top: 32px; }
.mb-1 { margin-bottom: 4px; } .mb-2 { margin-bottom: 8px; } .mb-3 { margin-bottom: 12px; } .mb-4 { margin-bottom: 16px; } .mb-5 { margin-bottom: 20px; } .mb-6 { margin-bottom: 24px; }
.text-xs { font-size: var(--fs-xs); }
.text-sm { font-size: var(--fs-sm); }
.text-md { font-size: var(--fs-md); }
.text-lg { font-size: var(--fs-lg); }
/* `.text-display` sert surtout aux grands chiffres (hero CA du dashboard...) :
   chasse fixe pour que le count-up ne fasse pas trembler la mise en page. La
   taille reste posee au cas par cas par les vues, en `clamp()`.
   Pas d'`overflow-wrap: anywhere` : il couperait le nombre au milieu des
   chiffres (CLAUDE.md §6). */
.text-display {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
}

/* ═══ UI-3 · B.3 — LE CHIFFRE D'AFFAIRES NE PEUT PLUS ETRE ROGNE ═══════════
   ⚠️⚠️ LA CAUSE N'EST PAS « l'ecran est etroit » — 390 px etait propre et
   1024 px coupait. Elle est que la police se disait en `vw` (le VIEWPORT) alors
   que la boite qui doit la contenir est la TUILE. Mesure du 2026-08-30, hero du
   tableau de bord, valeur reelle « 442 020 875 » :

       largeur   place utile dans la tuile   police rendue par 7vw
        980 px            890 px                   68,6 px      ✅
       1000 px            304 px                   70,0 px      ❌  -114 px

   A 1000 px la grille bento repasse a 12 colonnes : la tuile `span-6` perd 586
   px d'un coup, et `7vw` continue de grossir. Aucune valeur de `vw` ne peut
   etre juste des deux cotes de cette falaise — c'est pour cela que le reglage
   avait ete refait tuile par tuile, sans jamais fermer le sujet.

   ⭐ LA TUILE DEVIENT SON PROPRE REFERENTIEL DE TAILLE (`container-type`), et la
   police se dit en `cqw` : 1 cqw = 1 % de la largeur UTILE DE LA TUILE. Le
   rapport « largeur du texte / taille de police » est une constante de la
   fonte — mesure a 6,29 pour 11 caracteres, soit 0,5718 par caractere, stable
   sur les 55 largeurs. La taille qui remplit exactement la tuile est donc
   toujours la meme fraction de sa largeur, quelle que soit la fenetre.

   ⭐ LE NOMBRE DE CARACTERES EST LA SEULE INCONNUE, et c'est le JS qui l'ecrit
   (`--car`, pose par le compteur d'`app-layout.js`). ⚠️ Cela ne rouvre PAS ce
   qu'UI-1 a ferme : le JS publie un FAIT (« ce nombre fait 11 caracteres »), il
   ne pose aucune taille. La taille reste ici, dans la feuille, et elle seule.
   Sans JS, `--car: 11` sert de repli.

   ⚠️ 0.61 et non 0.5718 : ~6 % de garde, pour la variation de chasse entre le
   glyphe de la fonte d'affichage et son repli systeme.

   ⚠️ LES DEUX DECLARATIONS SONT VOULUES. Un navigateur qui ignore `cqw` rejette
   la seconde et garde la premiere : la tuile retombe sur le comportement
   d'avant UI-3 au lieu de perdre toute taille. Meme prudence que le refus de
   `:has()` — le parc guineen fait durer des Android anciens.

   ⚠️ Aucune abreviation ici, et c'est un choix : c'est LE chiffre du produit,
   il se lit en entier. L'abreviation reste reservee aux tuiles etroites, ou la
   valeur exacte demeure atteignable dans l'infobulle (`app_gnf_abrege`). */
/* ═══ UI-3 — `.tile-figure` : LA GRANDE VALEUR D'UNE TUILE, EN FEUILLE ═════
   ⚠️⚠️ MEME CLASSE DE DEFAUT QU'UI-1, SUR UN AUTRE SELECTEUR. UI-1 avait sorti
   22 `style="font-size:…"` des `.kpi-value` ; les grandes valeurs ecrites en
   `.text-display` n'etaient pas dans son perimetre et gardaient une taille
   FIXE EN PIXELS — qui ne sait pas retrecir. Mesure du 2026-08-30,
   `comptabilite`, resultat net « 64 371 700 » a `font-size:48px` :
       1000 px : 56 px hors tuile · 1024 px : 48 px · 1100 px : 23 px
   Une valeur monetaire fausse sur l'ecran de comptabilite, aux largeurs de
   bureau les plus courantes.

   ⭐ Trois tailles nommees, et AUCUNE taille dans l'attribut : le plafond se
   choisit par une classe (`--lg` / `--md` / `--sm`), la taille effective est
   calculee par la feuille contre la tuile. La doctrine d'UI-1 est tenue a la
   lettre — l'auteur de la vue declare une INTENTION de hierarchie, jamais une
   taille en pixels. */
.tile-figure {
  --car: 11;
  --plafond: 28px;
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  line-height: 1;
  font-size: var(--plafond);                                     /* repli */
  font-size: clamp(14px, calc(100cqw / (var(--car) * 0.63)), var(--plafond));
  /* La cesure ne tombe qu'entre deux groupes de milliers : cf. `.kpi-value`. */
  overflow-wrap: normal;
}
.tile-figure--lg { --plafond: 48px; letter-spacing: -0.04em; }
.tile-figure--md { --plafond: 28px; }
.tile-figure--sm { --plafond: 18px; }

.tile-aurora { container-type: inline-size; }
/* ⚠️ `[data-count]` AUTANT QUE `[data-kpi]` : la page de demonstration du
   design system porte la meme tuile hero avec le seul `data-count`, et elle a
   garde sa taille en dur jusqu'a ce que le cliquet V7 la denonce. Une regle qui
   ne couvre qu'un des deux attributs laisse une copie du defaut en vie. */
.tile-aurora .text-display[data-kpi],
.tile-aurora .text-display[data-count] {
  --car: 11;
  font-size: clamp(26px, 7vw, 72px);                              /* repli */
  font-size: clamp(26px, calc(100cqw / (var(--car) * 0.61)), 72px);
}
.t-mute { color: var(--text-3); }
.t-2 { color: var(--text-2); }
.t-success { color: var(--emerald-soft); }
.t-danger { color: var(--coral); }
.t-warn { color: var(--amber); }
.t-brand { color: var(--brand-soft); }
.fw-500 { font-weight: 500; }
.fw-600 { font-weight: 600; }
.fw-700 { font-weight: 700; }
.tc { text-align: center; }
.tr { text-align: right; }
.mono { font-family: var(--font-mono); }
/* Chiffres tabulaires : alignement vertical des montants GNF et quantites en colonne.
   Sans effet sur le texte non numerique, donc sans risque sur les colonnes mixtes. */
.tnum, .t .tr, .t td.mono, .t th.mono { font-variant-numeric: tabular-nums; }
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.divider { height: 1px; background: var(--line); margin: var(--s-4) 0; }

.scroll-box { overflow-y: auto; max-height: 100%; }

/* Animations utility */
.fade-up { animation: fade-up var(--t-slow) var(--ease-out) both; }
@keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.fade-in { animation: fade var(--t-md) var(--ease-out) both; }
.stagger > * { animation: fade-up var(--t-slow) var(--ease-out) both; }
.stagger > *:nth-child(1) { animation-delay: 0ms; }
.stagger > *:nth-child(2) { animation-delay: 50ms; }
.stagger > *:nth-child(3) { animation-delay: 100ms; }
.stagger > *:nth-child(4) { animation-delay: 150ms; }
.stagger > *:nth-child(5) { animation-delay: 200ms; }
.stagger > *:nth-child(6) { animation-delay: 250ms; }
.stagger > *:nth-child(7) { animation-delay: 300ms; }
.stagger > *:nth-child(8) { animation-delay: 350ms; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/* Pulse dot (for live status) */
.pulse-dot { position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--emerald); display: inline-block; }
.pulse-dot::before { content: ""; position: absolute; inset: 0; border-radius: 50%; background: inherit; animation: pulse 2s var(--ease-out) infinite; }
@keyframes pulse { 0% { transform: scale(1); opacity: 0.7; } 100% { transform: scale(2.5); opacity: 0; } }

/* Spark line tooltip */
.spark { width: 100%; height: 40px; }

/* Dropzone */
.dropzone {
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-3);
  padding: var(--s-6);
  text-align: center;
  background: var(--bg-card);
  transition: all var(--t-fast);
  cursor: pointer;
}
.dropzone:hover { border-color: var(--brand); background: rgba(14,92,99,0.04); }

/* File chip */
.file-chip {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  font-size: var(--fs-sm);
}
.file-chip .ext {
  width: 28px; height: 28px;
  border-radius: var(--r-1);
  background: var(--grad-brand);
  color: #fff; font-weight: 700; font-size: 9px;
  display: grid; place-items: center;
  text-transform: uppercase;
}
.file-chip .ext.pdf { background: linear-gradient(135deg,#f43f5e,#fb7185); }
.file-chip .ext.xlsx { background: linear-gradient(135deg,#10b981,#34d399); }
.file-chip .ext.docx { background: linear-gradient(135deg,#0ea5e9,#3b82f6); }
.file-chip .ext.img { background: linear-gradient(135deg,#a855f7,#ec4899); }

/* Conversation/message bubble */
.bubble {
  padding: 8px 12px;
  border-radius: var(--r-3);
  background: var(--bg-card);
  font-size: var(--fs-md);
  max-width: 70%;
  line-height: 1.5;
  word-break: break-word;
}
.bubble.me { background: var(--brand); color: #fff; border-bottom-right-radius: 4px; margin-left: auto; }
.bubble.them { border-bottom-left-radius: 4px; }

/* ---------- 20. Misc ---------- */
.kbd-key {
  padding: 1px 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: 4px;
  font-size: 11px;
  font-family: var(--font-mono);
  color: var(--text-2);
}

/* ---------- 21. Modals v3 ---------- */
/* Mini systeme de modals introduit Phase 2 superadmin. Pattern :
   <div class="v3-modal" id="modalXxx" aria-hidden="true">
     <div class="v3-modal-backdrop" data-v3-action="close-modal" data-target="#modalXxx"></div>
     <div class="v3-modal-dialog">
       <form ...>
         <header class="v3-modal-head"> ... </header>
         <div class="v3-modal-body"> ... </div>
         <footer class="v3-modal-foot"> ... </footer>
       </form>
     </div>
   </div>
   Ouvert via window.openModal(elt|id) ou via [data-v3-action="open-modal"].
*/
.v3-modal {
  position: fixed; inset: 0;
  display: none;
  z-index: 100;
  padding: var(--s-4);
}
.v3-modal.open {
  display: grid;
  place-items: center;
  animation: v3ModalFadeIn .15s ease-out;
}
.v3-modal-backdrop {
  position: absolute; inset: 0;
  background: rgba(0, 0, 0, 0.60);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  cursor: pointer;
}
.v3-modal-dialog {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-3);
  width: min(560px, 100%);
  max-height: calc(100vh - 2 * var(--s-4));
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-3);
  animation: v3ModalSlideUp .20s ease-out;
  overflow: hidden;
}
.v3-modal-dialog.wide { width: min(720px, 100%); }
.v3-modal-head,
.v3-modal-foot {
  padding: var(--s-3) var(--s-4);
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-shrink: 0;
}
.v3-modal-head {
  border-bottom: 1px solid var(--line);
  justify-content: space-between;
}
.v3-modal-foot {
  border-top: 1px solid var(--line);
  justify-content: flex-end;
}
.v3-modal-body {
  padding: var(--s-4);
  overflow-y: auto;
  /* CRITIQUE, meme piege que `.cart-list` : sans `min-height: 0`, un flex item
     refuse de descendre sous la hauteur de son contenu. `overflow-y: auto` ne
     declenchait donc jamais, et des que le contenu depassait la place dispo le
     dialog (max-height + overflow: hidden) CLIPPAIT le pied — les boutons de
     validation devenaient inatteignables, typiquement sur un telephone en
     paysage ou clavier ouvert. Mesure a 375x418 : pied hors ecran, body non
     defilant. `.retour-echange-modal` posait deja ce min-height pour son seul
     cas ; on generalise. */
  min-height: 0;
  flex: 1;
}
.v3-modal-title {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  font-family: var(--font-display);
  color: var(--text);
}

/* La plupart des modales enveloppent head/body/foot dans un <form> — il faut
   bien que les champs et le bouton de validation soient dans le meme
   formulaire. Le form devient alors le SEUL enfant flex du dialog : sans ces
   regles il conserve la hauteur de son contenu, deborde du dialog plafonne par
   max-height, et se fait clipper par son `overflow: hidden`. Le pied — donc les
   boutons — sort de l'ecran, sans que rien ne defile.
   Mesure a 375x418 : form de 531px dans un dialog de 375px, pied hors ecran.
   `.retour-echange-modal` reglait deja son cas particulier ; on generalise. */
.v3-modal-dialog > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
body.v3-modal-open { overflow: hidden; }

/* Modales quasi plein ecran sous 768px.
   Mesure avant : le `width: min(560px, 100%)` combine au padding de 16px du
   `.v3-modal` ne laissait que 86% de la largeur a 320px (274px sur 318). On
   passe a 95vw et un padding de 8px, soit ~30px de contenu regagnes la ou
   l'ecran est le plus contraint.
   ⚠️ On NE met PAS `overflow-y: auto` sur le dialog, contrairement a ce que
   demandait le brief : ce serait une regression. Le dialog est un flex column
   en `overflow: hidden` dont SEUL `.v3-modal-body` defile — c'est ce qui garde
   l'en-tete et le pied (donc les boutons de validation) toujours visibles,
   comportement deja acquis et explicitement recherche par les modales compta
   et « Retour / Echange ». Faire defiler le dialog entier ferait sortir les
   boutons de l'ecran.
   Les modales qui fixent deja leur propre largeur gardent la main, leur
   selecteur etant plus specifique : `.v3-confirm-modal` (92vw) et
   `.compta-modal-ecriture` (95vw, cf. plus bas).
   Pas de contrepartie dans `public/v3-mockups/` : le systeme de modales v3 a
   ete introduit en Phase 2 superadmin, APRES les maquettes, qui ne portent
   aucun `.v3-modal` — ni markup ni style. */
@media (max-width: 768px) {
  .v3-modal { padding: var(--s-2); }
  .v3-modal-dialog {
    width: 95vw;
    max-height: 90vh;
  }
}

@keyframes v3ModalFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes v3ModalSlideUp {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}

/* Form helpers utilisables dans .v3-modal-body et hors modals */
.v3-form-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}
.v3-form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.v3-form-field label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.v3-form-field .input { width: 100%; }
.v3-form-help {
  font-size: 12px;
  color: var(--text-3);
}
.v3-form-help.info {
  background: rgba(14, 92, 99, 0.06);
  border: 1px solid rgba(14, 92, 99, 0.20);
  color: var(--text-2);
  padding: 10px 12px;
  border-radius: var(--r-2);
}

/* ---------- 22. Print styles ---------- */
/* Pour imprimer / Save as PDF : masque la chrome layout (sidebar, topbar,
   actions, tabs, panels inactifs), force fond blanc + texte noir,
   ajoute un header d'impression. Utilisé par window.print() depuis
   les boutons "Imprimer" / "Exporter PDF" des views v3. */

.print-only { display: none; }

@media print {
    /* Reset thème pour économie d'encre */
    :root, [data-theme="dark"], [data-theme="light"] {
        --bg: #fff !important;
        --bg-card: #fff !important;
        --bg-elevated: #f9fafb !important;
        --text: #111 !important;
        --text-2: #333 !important;
        --text-3: #555 !important;
        --line: #ddd !important;
    }
    body { background: #fff !important; color: #111 !important; }

    /* Masque la chrome de l'app */
    .sidebar, .topbar, [data-rapport-tabs], .tabs,
    .page-head-actions, [data-flash-alert], .alert-flash,
    [data-tab-panel]:not([data-tab-panel="overview"]),
    .v3-modal, .v3-modal-backdrop, footer.app-footer { display: none !important; }

    .app, .main, .content { display: block !important; margin: 0 !important; padding: 0 !important; }

    /* Print header (visible uniquement à l'impression) */
    .print-only { display: block !important; }
    .print-header {
        border-bottom: 2px solid #4c1d95;
        padding-bottom: 8px;
        margin-bottom: 16px;
    }
    .print-header .brand {
        font-family: var(--font-display, 'Sora', sans-serif);
        font-weight: 800;
        font-size: 18px;
        color: #4c1d95;
    }
    .print-header .meta {
        font-size: 11px;
        color: #666;
        margin-top: 4px;
    }
    .print-header h1 {
        font-size: 22px;
        font-weight: 700;
        margin: 6px 0 0;
        color: #111;
    }

    /* Tiles : fond blanc + bordure légère, pas de shadow, pas de transform */
    .tile, .bento {
        background: #fff !important;
        box-shadow: none !important;
        transform: none !important;
        page-break-inside: avoid;
    }
    .tile {
        border: 1px solid #ddd !important;
        padding: 12px !important;
        margin-bottom: 8px;
    }
    .tile-grad, .tile-aurora, .tile-emerald, .tile-sunset {
        background: #fff !important;
    }
    .tile::before, .tile-aurora::before, .tile-grad::before { display: none !important; }

    /* Bento → grid simple sur 4 colonnes */
    .bento { gap: 6px !important; margin-bottom: 8px !important; }

    /* KPI : ramener gradient text en violet solide */
    .kpi-value[style*="grad-aurora"],
    .kpi-value[style*="background-clip"] {
        background: none !important;
        -webkit-text-fill-color: #4c1d95 !important;
        color: #4c1d95 !important;
        font-weight: 800;
    }
    .kpi-label { color: #555 !important; }
    .kpi-sub, .kpi-trend { color: #555 !important; background: transparent !important; }
    .kpi-trend.up { color: #047857 !important; }
    .kpi-trend.down { color: #b91c1c !important; }

    /* Tile icon box (KPI corner) → fond gris clair */
    .tile > div[style*="position:absolute"] {
        background: #f3f4f6 !important;
        border: 1px solid #e5e7eb !important;
    }

    /* Tables */
    table.t { width: 100% !important; }
    table.t th { background: #f3f4f6 !important; color: #111 !important; border-bottom: 1px solid #ddd !important; }
    table.t td { border-bottom: 1px solid #eee !important; padding: 4px 6px !important; }

    /* Progress bars : bordure simple, fill gris */
    .progress { background: #eee !important; border: 1px solid #ddd; }
    .progress .fill { background: #4c1d95 !important; }

    /* Charts SVG : keep but make text darker */
    svg text { fill: #333 !important; }

    /* Page settings */
    @page {
        size: A4;
        margin: 1.5cm 1cm 2cm 1cm;
    }

    /* Footer fixe : date + numéro de page */
    body::after {
        content: "Sando · Document généré automatiquement";
        position: fixed;
        bottom: 8mm;
        left: 1cm;
        right: 1cm;
        text-align: center;
        font-size: 9px;
        color: #888;
        border-top: 1px solid #ddd;
        padding-top: 4px;
    }

    /* Évite les coupures dans une ligne KPI */
    .bento > .tile { page-break-inside: avoid; }
    h1, h2, h3 { page-break-after: avoid; }
}

/* ---------- 23. PDF export mode ---------- */
/* Activé via JS [data-pdf-mode="1"] sur <html> juste avant html2pdf().save().
   Mirror des règles @media print pour que la capture html2canvas voie
   le même rendu que l'aperçu d'impression. Retiré après génération. */
[data-pdf-mode] .sidebar,
[data-pdf-mode] .topbar,
[data-pdf-mode] [data-rapport-tabs],
[data-pdf-mode] .page-head-actions,
[data-pdf-mode] [data-tab-panel]:not([data-tab-panel="overview"]),
[data-pdf-mode] [data-flash-alert],
[data-pdf-mode] .alert-flash { display: none !important; }

[data-pdf-mode] .main { margin-left: 0 !important; }
[data-pdf-mode] .content { padding: 16px !important; }

[data-pdf-mode] body,
[data-pdf-mode] .tile,
[data-pdf-mode] .bento { background: #fff !important; color: #111 !important; }

[data-pdf-mode] .tile {
    border: 1px solid #ddd !important;
    box-shadow: none !important;
    transform: none !important;
    background: #fff !important;
}
[data-pdf-mode] .tile-grad,
[data-pdf-mode] .tile-aurora,
[data-pdf-mode] .tile-emerald,
[data-pdf-mode] .tile-sunset { background: #fff !important; }
[data-pdf-mode] .tile::before,
[data-pdf-mode] .tile-aurora::before { display: none !important; }

[data-pdf-mode] .print-only { display: block !important; }
[data-pdf-mode] .print-header {
    border-bottom: 2px solid #4c1d95;
    padding-bottom: 8px;
    margin-bottom: 16px;
}
[data-pdf-mode] .print-header .brand {
    font-family: var(--font-display);
    font-weight: 800;
    font-size: 18px;
    color: #4c1d95;
}
[data-pdf-mode] .print-header .meta { font-size: 11px; color: #666; margin-top: 4px; }
[data-pdf-mode] .print-header h1 { font-size: 22px; font-weight: 700; margin: 6px 0 0; color: #111; }

[data-pdf-mode] .kpi-label { color: #555 !important; }
[data-pdf-mode] .kpi-value[style*="grad-aurora"],
[data-pdf-mode] .kpi-value[style*="background-clip"] {
    background: none !important;
    -webkit-text-fill-color: #4c1d95 !important;
    color: #4c1d95 !important;
}
[data-pdf-mode] .kpi-sub,
[data-pdf-mode] .kpi-trend { color: #555 !important; background: transparent !important; }
[data-pdf-mode] .kpi-trend.up { color: #047857 !important; }
[data-pdf-mode] .kpi-trend.down { color: #b91c1c !important; }

[data-pdf-mode] .tile > div[style*="position:absolute"] {
    background: #f3f4f6 !important;
    border: 1px solid #e5e7eb !important;
}

[data-pdf-mode] table.t th { background: #f3f4f6 !important; color: #111 !important; border-bottom: 1px solid #ddd !important; }
[data-pdf-mode] table.t td { border-bottom: 1px solid #eee !important; padding: 4px 6px !important; }

[data-pdf-mode] .progress { background: #eee !important; border: 1px solid #ddd; }
[data-pdf-mode] .progress .fill { background: #4c1d95 !important; }

[data-pdf-mode] svg text { fill: #333 !important; }

[data-pdf-mode] .crumbs,
[data-pdf-mode] .page-sub { color: #555 !important; }
[data-pdf-mode] .page-title { color: #111 !important; }

[data-pdf-mode] .badge { background: #f3f4f6 !important; color: #111 !important; border: 1px solid #ddd; }
[data-pdf-mode] .badge-brand { background: #dcefec !important; color: #084046 !important; }
[data-pdf-mode] .badge-warning { background: #fef3c7 !important; color: #92400e !important; }

/* ---------- 24. Caisse POS (vente_gerant) ---------- */
/* Layout 2 colonnes : grille articles a gauche, ticket sticky a droite.
   Inspire du mockup public/v3-mockups/caisse.html. Sous 1200px les 2
   colonnes s'empilent verticalement et la cart perd son sticky.
   IMPORTANT : on laisse la PAGE entiere scroller (pas de scroll interne
   dans .pgrid ni dans .cart) -- sinon la moitie des articles est cachee
   et le bouton "Encaisser & valider" devient inaccessible. */
.pos-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--s-4);
  align-items: start;
}
.pos-left {
  display: flex;
  flex-direction: column;
  min-width: 0;
  gap: var(--s-3);
}
@media (max-width: 1200px) {
  .pos-grid { grid-template-columns: 1fr; }
}

/* Catalogue d'articles : grille cliquable, chaque tuile = .ptile */
.cat-pills {
  display: flex;
  gap: 6px;
  padding: 4px 0 4px;
  overflow-x: auto;
  flex-wrap: wrap;
}
.cat-pills .chip { font-size: var(--fs-xs); padding: 4px 10px; }

.pgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 10px;
  padding: 2px;
}
.ptile {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: 12px;
  cursor: pointer;
  transition: transform var(--t-fast), border-color var(--t-fast), box-shadow var(--t-fast);
  text-align: center;
  position: relative;
  overflow: hidden;
  font: inherit;
  color: inherit;
}
/* Raccourci de recouvrement (« Encaisser »), pose dans la tuile Creances du
   dashboard et sur les lignes en creance de gestion_mag. */
.creance-cta {
  margin-top: var(--s-2);
  align-self: flex-start;
  white-space: nowrap;
}
@media (max-width: 640px) {
  /* Dans la tuile du dashboard, l'action prend toute la largeur et reste
     tactile. La variante en ligne de tableau (gestion_mag) garde sa taille :
     elle vit dans une cellule, pas dans une colonne flex. */
  .tile-body .creance-cta {
    width: 100%;
    min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
}

/* Carte « Configuration » du dashboard patron (checklist de demarrage).
   Hors `.bento` a dessein : elle precede la grille et occupe toute la largeur
   sans dependre des `.span-N`. */
.onboarding-card {
  margin-bottom: var(--s-4);
  gap: var(--s-4);
  border-color: var(--line-strong);
}
.onboarding-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.onboarding-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.onboarding-step {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 10px 12px;
  /* `--line-strong` et non `--line` : en theme clair `--bg-elevated` vaut blanc,
     exactement comme le `--bg-card` de la carte qui les contient. Sans une
     bordure franche, les etapes se fondent dans la carte et la liste n'a plus
     aucune structure lisible. En sombre les deux fonds different deja, la
     bordure ne fait qu'y renforcer la separation. */
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
}
.onboarding-check {
  width: 24px; height: 24px;
  flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: var(--r-full);
  border: 1px solid var(--line-strong);
  color: var(--accent-ink);
  font-size: 13px; font-weight: 700;
  background: transparent;
}
/* Aplat + encre : meme choix que la pastille du bandeau de vente, pour rester
   lisible dans les deux themes sans surcharge (une teinte a faible opacite
   retombe sous 2:1 sur fond blanc). */
.onboarding-step.is-done .onboarding-check {
  background: var(--emerald);
  border-color: var(--emerald);
}
.onboarding-txt { flex: 1 1 auto; min-width: 0; }
.onboarding-label { font-weight: 600; font-size: var(--fs-sm); color: var(--text); }
.onboarding-step.is-done .onboarding-label { color: var(--text-2); }
.onboarding-cta, .onboarding-fait { flex-shrink: 0; }

@media (max-width: 640px) {
  /* A 360px, libelle + bouton ne tiennent pas sur une ligne : l'action passe
     dessous, pleine largeur et tactile. */
  .onboarding-step { flex-wrap: wrap; }
  .onboarding-txt { flex: 1 1 60%; }
  .onboarding-cta {
    flex: 1 0 100%;
    min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
}

/* Etat vide du catalogue de la caisse : le magasin du gerant n'a aucun stock. */
.pos-catalogue-vide {
  text-align: center;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-7) var(--s-5);
}
.pos-catalogue-vide .pos-cv-ic {
  font-size: 34px;
  line-height: 1;
  margin-bottom: var(--s-2);
}
.pos-catalogue-vide p {
  margin: 0;
  max-width: 46ch;
  line-height: 1.5;
}

/* Badge « A approvisionner » de la liste stock. C'est un lien deguise en badge :
   il mene a l'ecran d'approvisionnement, seul endroit qui resout le probleme
   qu'il signale. `display: inline-flex` et non `inline` pour que la puce et le
   texte restent alignes quand le libelle passe sur deux lignes a 360px. */
.stock-a-appro {
  display: inline-flex;
  margin-top: 4px;
  max-width: 100%;
  text-align: left;
  line-height: 1.25;
  cursor: pointer;
  transition: filter var(--t-fast);
  /* Aplat plutot que la teinte a 14 % de `.badge-warning` : sur fond blanc,
     l'ambre sur ambre translucide tombe a ~2:1. Ce badge est LE signal qui
     explique pourquoi la caisse reste vide — il doit se lire. Surcharge locale
     uniquement : `.badge-warning` n'est pas touche ailleurs dans l'app. */
  background: var(--amber);
  color: var(--accent-ink);
  border-color: var(--amber);
}
.stock-a-appro:hover { filter: brightness(1.12); }

/* Bandeau de confirmation d'encaissement (caisse gerant).
   Le gerant reste sur sa caisse apres avoir encaisse : ce bandeau lui rend le
   montant, lui donne l'impression du ticket et amorce la vente suivante.
   Il ne s'auto-efface pas — le gerant doit pouvoir lire, annoncer, imprimer. */
.pos-flash {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
  padding: var(--s-4) var(--s-5);
  padding-right: 44px; /* place du bouton de fermeture */
  margin-bottom: var(--s-4);
  background: var(--bg-card);
  border: 1px solid var(--emerald);
  border-left: 3px solid var(--emerald);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-2);
}
.pos-flash-main { display: flex; align-items: center; gap: var(--s-3); min-width: 0; }
/* Pastille en aplat, et non en teinte a 16 % d'opacite comme `.kpi-trend.up` :
   sur fond blanc, `--emerald-soft` sur `rgba(16,185,129,0.16)` retombe a ~1.5:1
   de contraste, illisible. L'aplat `--emerald` + encre `--accent-ink` donne
   ~6.7:1, et la meme valeur dans les DEUX themes (aucun des deux tokens n'est
   redefini en clair) — donc aucune surcharge de theme a maintenir. */
.pos-flash-ic {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-full);
  background: var(--emerald);
  color: var(--accent-ink);
  font-size: 17px; font-weight: 700;
  flex-shrink: 0;
}
.pos-flash-txt { min-width: 0; }
.pos-flash-title {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-3);
  font-weight: 600;
}
.pos-flash-amount {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(20px, 5vw, 26px);
  color: var(--text);
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.pos-flash-actions {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  flex-wrap: wrap;
  margin-left: auto;
}
.pos-flash-close {
  position: absolute;
  top: 8px; right: 8px;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-2);
  color: var(--text-3);
  font-size: 20px; line-height: 1;
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
.pos-flash-close:hover { color: var(--text); background: var(--bg-elevated); }

@media (max-width: 640px) {
  /* A 360px, montant et actions ne tiennent pas cote a cote : le bandeau
     s'empile, et les actions passent en pleine largeur pour rester tactiles. */
  .pos-flash { gap: var(--s-3); padding: var(--s-4); padding-right: 44px; }
  .pos-flash-actions { margin-left: 0; width: 100%; }
  .pos-flash-actions > form,
  .pos-flash-actions > form > .btn,
  .pos-flash-actions > .btn { flex: 1 1 auto; }
  .pos-flash-actions .btn { min-height: 44px; }
  .pos-flash-close { width: 44px; height: 44px; top: 4px; right: 4px; }
  /* Le lien vers l'historique reste secondaire : pleine largeur, sous les deux
     actions principales. */
  .pos-flash-link { flex: 1 0 100%; }
}

/* Enveloppe de tuile : depuis l'ajout en un tap, la tuile porte un second
   bouton (« ⋯ » = quantite/prix personnalises). Un <button> ne pouvant pas en
   contenir un autre, les deux sont freres dans ce wrapper, qui devient
   l'element de grille de `.pgrid`. */
.ptile-wrap {
  position: relative;
  display: flex;
  min-width: 0;
}
.ptile-wrap > .ptile {
  flex: 1 1 auto;
  width: 100%;
}
.ptile-more {
  position: absolute;
  top: 6px; right: 6px;
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  color: var(--text-3);
  font: inherit;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast), background var(--t-fast);
}
.ptile-more:hover { color: var(--text); border-color: var(--brand); }

/* Retour visuel immediat du tap : le POST recharge la page, mais sur un
   telephone lent l'aller-retour se voit. */
.ptile.is-adding {
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-glow);
  opacity: 0.7;
}

.ptile:hover {
  transform: translateY(-2px);
  border-color: var(--brand);
  box-shadow: var(--shadow-2);
}
.ptile.is-rupture {
  opacity: 0.55;
  cursor: not-allowed;
}
.ptile.is-rupture:hover {
  transform: none;
  border-color: var(--line);
  box-shadow: var(--shadow-1);
}
.ptile .ic {
  width: 48px; height: 48px;
  margin: 0 auto 8px;
  border-radius: var(--r-3);
  background: var(--bg-elevated);
  display: grid; place-items: center;
  font-size: 24px;
}
.ptile .name {
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--text);
  min-height: 32px;
  line-height: 1.25;
  word-break: break-word;
}
.ptile .price {
  font-size: var(--fs-sm);
  font-weight: 600;
  margin-top: 4px;
  background: var(--grad-aurora);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.ptile .stock-mini {
  font-size: var(--fs-2xs);
  color: var(--text-3);
  margin-top: 2px;
}
.ptile .stock-mini.is-low { color: var(--amber); }
.ptile .stock-mini.is-out { color: var(--coral); }
.ptile .pchip-loc {
  position: absolute;
  top: 6px; left: 6px;
  font-size: var(--fs-2xs);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  color: var(--text-2);
  padding: 1px 6px;
  border-radius: var(--r-1);
}

/* Cart / ticket panier -- Layout "POS pro" (Option A validee par PO)
   La cart est dimensionnee a la hauteur du viewport ET decoupee en 3
   zones flex strictes : head fixe + list scrollable + foot fixe.
   IMPORTANT : "height" (pas "max-height") -- sinon flex-grow sur cart-list
   ne s'applique pas et la liste d'articles s'ecrase a 0 (basis: 0).
   Avec height fixe, cart-list prend tout l'espace dispo entre head et foot. */
.cart {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-4);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: sticky;
  top: calc(var(--topbar-h) + var(--s-3));
  height: calc(100vh - var(--topbar-h) - var(--s-6));
}
.cart-head {
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  flex-shrink: 0; /* la tete ne se compresse jamais */
}
.cart-list {
  padding: 6px 16px;
  flex: 1 1 0;       /* prend tout l'espace dispo entre head et foot */
  min-height: 0;     /* CRITIQUE : permet a overflow-y de fonctionner dans un flex column */
  overflow-y: auto;  /* le scroll se fait UNIQUEMENT dans la liste d'articles */
}

/* ⚠️ CE BLOC DOIT RESTER APRES `.cart` ET `.cart-list`.
   Il etait place AVANT `.cart-list` : a specificite egale (0,1,0), c'est la
   derniere regle du fichier qui gagne, media query ou pas. Le `flex: 0 1 auto`
   ci-dessous etait donc ecrase par le `flex: 1 1 0` de la regle de base, et
   sous 1200px la liste d'articles s'ecrasait a la hauteur de son padding
   (12px) : le gerant ne voyait plus AUCUNE des lignes qu'il venait d'ajouter.
   Exactement le scenario que decrit le commentaire de `.cart` — la parade
   avait ete ecrite, mais posee au mauvais endroit. */
@media (max-width: 1200px) {
  /* Sur petit ecran, layout 1 colonne : la cart suit le flux normal
     et n'est plus contrainte (le scroll natif de la page prend le relais). */
  .cart {
    position: static;
    height: auto;
  }
  .cart-list {
    flex: 0 1 auto;
    max-height: 50vh;
  }
}
.cart-list .empty-mini {
  text-align: center;
  padding: var(--s-6) var(--s-3);
  color: var(--text-3);
  font-size: var(--fs-sm);
}
.ci {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}
.ci:last-child { border-bottom: 0; }
.ci-thumb {
  width: 36px; height: 36px;
  background: rgba(14,92,99,0.15);
  border-radius: var(--r-2);
  display: grid;
  place-items: center;
  font-size: 18px;
  flex-shrink: 0;
}
.ci-body { flex: 1; min-width: 0; }
.ci-name {
  font-weight: 500;
  font-size: var(--fs-sm);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ci-meta { color: var(--text-3); font-size: var(--fs-xs); }
/* Controle de quantite en ligne : − / valeur / +. Un POST dedie par bouton
   (`maj_qte_article` + `delta`), donc aucun calcul cote client. */
.ci-qty-ctl {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}
.ci-step {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  color: var(--text-2);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: color var(--t-fast), border-color var(--t-fast);
}
.ci-step:hover { color: var(--text); border-color: var(--brand); }

.ci-qty {
  font-weight: 600;
  font-size: var(--fs-sm);
  color: var(--text);
  min-width: 40px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.ci-total {
  font-weight: 600;
  font-size: var(--fs-sm);
  min-width: 70px;
  text-align: right;
  color: var(--text);
}
.ci-remove {
  background: transparent;
  border: 0;
  color: var(--text-3);
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: var(--r-1);
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
  font-size: 16px;
}
.ci-remove:hover { color: var(--coral); background: rgba(244,63,94,0.08); }

.cart-foot {
  padding: 16px;
  border-top: 1px solid var(--line);
  background: var(--bg-elevated);
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex-shrink: 0; /* totaux + modes paiement + boutons toujours visibles */
}
.cart-foot .total-line {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--fs-sm);
}
.cart-foot .total-line.total-final {
  padding-top: 10px;
  border-top: 1px dashed var(--line-strong);
  margin-top: 4px;
}
.cart-foot .total-final .lbl { font-weight: 600; color: var(--text); }
.cart-foot .total-final .val {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 24px;
  background: var(--grad-aurora);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* Modes de paiement -- selecteur de chips horizontales (Option 1 validee PO).
   Layout flex inline avec scroll horizontal si besoin (ex: 6+ modes actifs).
   Hauteur compacte (~32px) pour preserver la place verticale du cart-foot,
   sinon les boutons Creance/Proforma debordent sur petit viewport. */
.pm-grid {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-top: 8px;
  overflow-x: auto;
  padding: 2px 0 6px; /* place pour la scrollbar discrete */
  scrollbar-width: thin;
}
.pm-grid::-webkit-scrollbar { height: 4px; }
.pm-grid::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: var(--r-full); }

.pm {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  cursor: pointer;
  transition: all var(--t-fast);
  font: inherit;
  color: inherit;
  white-space: nowrap;
  flex-shrink: 0;
}
.pm:hover { border-color: var(--brand); }
.pm.active {
  background: rgba(14,92,99,0.14);
  border-color: var(--brand);
  box-shadow: 0 0 0 2px rgba(14,92,99,0.10);
}
.pm .ic { font-size: 16px; line-height: 1; }
.pm .lbl {
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text-2);
}
.pm.active .lbl { color: var(--text); }

/* Recherche barre (variante du topbar-cmd pour usage in-page) */
.pos-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  color: var(--text-2);
  font-size: var(--fs-md);
}
.pos-search input {
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--text);
  flex: 1;
  font-size: var(--fs-md);
}
/* FE-5b : le champ pose outline:0 et le conteneur n'a pas de :focus-within,
   donc aucun retour de focus clavier sur la caisse. On rend l'anneau au champ.
   Offset positif (et non negatif) : l'input n'a pas de padding propre, un anneau
   en retrait couperait le texte ; +2px reste largement dans les 10px de padding
   du conteneur, donc l'anneau ne deborde pas de la barre. */
.pos-search input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.pos-search input::placeholder { color: var(--text-3); }
.pos-search .kbd {
  padding: 1px 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: var(--fs-2xs);
  font-family: var(--font-mono);
  color: var(--text-3);
}


/* ---------- 25. Cart panier -- refonte UX (Phase 2B suite) ---------- */
/* Barre compacte au-dessus du Total : compteur d'articles + raccourcis
   inline (Remise, Options, Validite). Remplace les selects Type/Reference
   qui prenaient ~85px et le bouton Validite qui prenait ~36px. */
.cart-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: var(--fs-xs);
  color: var(--text-3);
  gap: 8px;
  flex-wrap: wrap;
}
.cart-bar-count {
  font-weight: 500;
  color: var(--text-2);
}
.cart-bar-actions {
  display: inline-flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: center;
}
.cart-bar-actions .btn-link {
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: var(--fs-xs);
  color: var(--brand-soft);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: color var(--t-fast);
}
.cart-bar-actions .btn-link:hover { color: var(--brand); }
.cart-bar-actions .btn-link.is-set {
  color: var(--text-2);
  font-weight: 500;
}

/* Total HERO : grand chiffre central, l'info la plus importante du POS.
   Encadre par 2 bordures pointillees pour creer une zone visuelle distincte. */
.cart-total-hero {
  text-align: center;
  padding: 12px 0 14px;
  border-top: 1px dashed var(--line-strong);
  border-bottom: 1px dashed var(--line-strong);
  margin: 8px 0 12px;
}
.cart-total-hero .lbl {
  font-size: var(--fs-2xs);
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 4px;
}
.cart-total-hero .val {
  font-family: var(--font-display);
  font-weight: 700;
  /* Taille fixe a l'origine. C'est LE chiffre que le gerant montre au client :
     il ne doit jamais etre tronque, quel que soit le telephone. */
  font-size: clamp(24px, 6vw, 28px);
  background: var(--grad-aurora);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
  /* Pas d'`overflow-wrap: anywhere` : c'est le montant que le gerant montre au
     client, il ne doit jamais se couper au milieu des chiffres (CLAUDE.md §6).
     Le clamp ci-dessus suffit. */
}
.cart-total-hero .tva {
  font-size: var(--fs-xs);
  color: var(--text-3);
  margin-top: 6px;
}

/* CTA principal (Encaisser & valider) en pleine largeur */
.cart-cta {
  width: 100%;
  padding: 12px;
  margin-bottom: 8px;
}

/* Actions secondaires (Creance + Proforma) sur 1 ligne 50/50 */
.cart-actions-secondary {
  display: flex;
  gap: 6px;
}
.cart-actions-secondary .btn {
  flex: 1;
  padding: 8px 6px;
}

/* Variantes compactes du bouton croix, utilisees dans .cart-head.
   Elles remplacent des width/height poses en style inline : une regle de
   classe se laisse surcharger par la media query tactile, pas un inline. */
.ci-remove.is-xs { width: 20px; height: 20px; font-size: 14px; vertical-align: middle; }
.ci-remove.is-sm { width: 28px; height: 28px; }

/* ---------- 25 bis. POS mobile — barre panier fixe + ergonomie tactile ----------
   Le gerant vend sur son telephone, face au client. Sous 1200px le layout POS
   passe en 1 colonne : le ticket tombe SOUS le catalogue, donc le total et
   « Encaisser & valider » sortent de l'ecran des qu'on ajoute des articles.
   Une barre fixe en bas restitue en permanence les 3 informations qui comptent :
   combien d'articles, combien ca fait, et le bouton pour encaisser. */

.pos-mobile-bar { display: none; }

@media (max-width: 900px) {
  .pos-mobile-bar {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* > topbar (40), < voile de la sidebar (55) et modales (100) : le tiroir
       et les modales doivent recouvrir la barre, le contenu de page non. */
    z-index: 45;
    padding: 10px var(--s-4);
    /* Barre d'accueil / encoche iOS : sans ca le bouton Valider passe dessous. */
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    background: var(--bg-glass);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-top: 1px solid var(--line-strong);
    box-shadow: var(--shadow-3);
  }

  /* La barre est en `fixed` : elle ne reserve pas sa place dans le flux. On
     rend au bas de page la hauteur qu'elle recouvre, sinon les boutons
     Creance / Proforma du ticket restent sous la barre, intouchables. */
  .pos-grid { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0px)); }

  .pos-mobile-bar-summary {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    min-height: 44px;
    padding: 4px 6px;
    background: transparent;
    border: 0;
    border-radius: var(--r-2);
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
  }
  .pos-mobile-bar-summary .pmb-count {
    font-size: var(--fs-2xs);
    color: var(--text-3);
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }
  /* Pas de degrade clippe ici (contrairement a .cart-total-hero .val) : le
     texte transparent se lit mal sur un fond translucide et floute. */
  .pos-mobile-bar-summary .pmb-total {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: var(--fs-xl);
    line-height: 1.1;
    color: var(--text);
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .pos-mobile-bar-cta {
    flex: 0 0 auto;
    min-height: 48px;
    padding: 12px 20px;
  }
}

/* Cibles tactiles : 44px minimum sous 768px (WCAG 2.5.5 / iOS HIG).
   Portee volontairement limitee au tunnel de vente (`.pos-grid`, `.pos-mobile-bar`
   et les modales) : `.chip`, `.btn-sm` ou `.icon-btn` servent partout dans
   l'app, et ce chantier ne couvre que la caisse. */
@media (max-width: 768px) {
  .pos-grid .pgrid { gap: 12px; }
  .pos-grid .ptile { min-height: 44px; padding: 14px 12px; }

  .pos-grid .cat-pills { gap: 8px; }
  .pos-grid .cat-pills .chip { min-height: 44px; padding: 0 14px; font-size: var(--fs-sm); }

  .pos-grid .ci { padding: 12px 0; gap: 8px; flex-wrap: wrap; }
  .pos-grid .ci-remove,
  .pos-grid .ci-remove.is-xs,
  .pos-grid .ci-remove.is-sm { width: 44px; height: 44px; font-size: 20px; }

  /* Le bouton « ⋯ » et les pas de quantite sont les deux nouvelles cibles du
     tunnel en un tap : ils doivent etre atteignables au pouce comme le reste. */
  .pos-grid .ptile-more { width: 44px; height: 44px; font-size: 18px; }
  .pos-grid .ci-step { width: 44px; height: 44px; font-size: 20px; }
  /* Sur une ligne de panier a 360px, thumb + nom + (− qte +) + total + croix ne
     tiennent pas cote a cote. On casse en DEUX rangees :
       rangee 1 : vignette + nom/prix unitaire
       rangee 2 : (− qte +)          total   croix
     Le saut est pose par un pseudo-element a `flex-basis: 100%`, place entre
     les deux groupes par `order`. Mettre `flex: 1 0 100%` sur `.ci-body`
     donnerait TROIS rangees, la vignette se retrouvant seule sur la premiere —
     mesure : 158px de hauteur de ligne au lieu de 106px. */
  .pos-grid .ci-body { flex: 1 1 auto; }
  .pos-grid .ci::after {
    content: "";
    flex-basis: 100%;
    height: 0;
    order: 0;
  }
  .pos-grid .ci-qty-ctl { order: 1; }
  .pos-grid .ci-total   { order: 2; margin-left: auto; }
  /* `:not(.ci-qty-ctl)` est indispensable : le controle de quantite EST lui
     aussi un <form>, et a specificite egale il aurait herite de cet order. */
  .pos-grid .ci > form:not(.ci-qty-ctl) { order: 3; }

  .pos-grid .cart-head .btn-xs,
  .pos-grid .cart-head .btn-sm { min-height: 44px; padding: 0 12px; font-size: var(--fs-sm); }

  .pos-grid .cart-bar-actions { gap: 4px; }
  .pos-grid .cart-bar-actions .btn-link {
    min-height: 44px;
    padding: 0 10px;
    font-size: var(--fs-sm);
  }

  .pos-grid .pm { min-height: 44px; padding: 8px 14px; }
  .pos-grid .pm .lbl { font-size: var(--fs-sm); }

  .pos-grid .cart-cta { min-height: 52px; }
  .pos-grid .cart-actions-secondary { gap: 10px; }
  .pos-grid .cart-actions-secondary .btn { min-height: 44px; }

  .v3-modal .v3-modal-head .icon-btn { width: 44px; height: 44px; }
  .v3-modal .v3-modal-foot .btn { min-height: 44px; }
}

/* Tres petits telephones. Avec minmax(140px) le catalogue tient 2 colonnes
   jusqu'a ~344px de viewport (140x2 + 12 de gap + 48 de padding .content + 4
   de padding .pgrid) ; en dessous — un iPhone SE 1re generation fait 320px —
   il bascule sur UNE colonne etiree, ou chaque article occupe un ecran.
   120px ramene le seuil a ~304px. Mesure : 2 colonnes de 130px a 320px. */
@media (max-width: 380px) {
  .pgrid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 8px;
  }
}

/* Anti-zoom iOS. Safari zoome sur tout champ dont la font-size est < 16px au
   moment du focus — et ne dezoome jamais ensuite : le gerant se retrouve avec
   une caisse a 150 % en pleine vente. 16px est le seuil exact. Regle globale :
   le probleme n'a rien de propre au POS, il touche tous les formulaires. */
@media (max-width: 768px) {
  .input,
  select.input,
  textarea.input,
  .pos-search input,
  .v3-modal input,
  .v3-modal select,
  .v3-modal textarea { font-size: 16px; }
}

/* ---------- 26. Mailbox (mailbox + superadmin_mailbox) ---------- */
/* Layout 3 colonnes Gmail-like : sidebar dossiers + liste + detail.
   Inspire du mockup public/v3-mockups/mailbox.html. Phase 3.1. */

.mail-shell {
  display: grid;
  grid-template-columns: 240px 380px 1fr;
  gap: var(--s-3);
  height: calc(100vh - var(--topbar-h) - 130px);
  min-height: 560px;
}
@media (max-width: 1200px) {
  .mail-shell { grid-template-columns: 200px 320px 1fr; }
}
@media (max-width: 980px) {
  .mail-shell { grid-template-columns: 1fr; height: auto; }
}

.mail-sidebar {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: 14px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mail-folder {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: var(--r-2);
  color: var(--text-2);
  font-size: var(--fs-md);
  cursor: pointer;
  transition: all var(--t-fast);
  font-weight: 500;
  text-decoration: none;
}
.mail-folder:hover { background: rgba(14,92,99,0.06); color: var(--text); }
.mail-folder.active {
  background: rgba(14,92,99,0.14);
  color: var(--brand-soft);
}
.mail-folder .count {
  margin-left: auto;
  font-size: var(--fs-2xs);
  color: var(--text-3);
  font-weight: 600;
  padding: 1px 7px;
  border-radius: var(--r-full);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
}
.mail-folder.active .count {
  color: var(--brand-soft);
  background: rgba(14,92,99,0.10);
  border-color: rgba(14,92,99,0.30);
}

.mail-list {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.mail-list-head {
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

.mail-bulkbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-elevated);
  flex-shrink: 0;
}

.mail-item {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  transition: background var(--t-fast);
  position: relative;
}
.mail-item:hover { background: rgba(14,92,99,0.04); }
.mail-item.active { background: rgba(14,92,99,0.10); }
.mail-item.active::before {
  content: "";
  position: absolute;
  left: 0; top: 8px; bottom: 8px;
  width: 3px;
  background: var(--brand);
  border-radius: 0 3px 3px 0;
}
.mail-item.unread .mail-from,
.mail-item.unread .mail-subj { font-weight: 700; color: var(--text); }
.mail-item .mail-from { font-size: var(--fs-sm); color: var(--text); font-weight: 600; }
.mail-item .mail-time {
  font-size: var(--fs-2xs);
  color: var(--text-3);
  margin-left: auto;
  flex-shrink: 0;
  white-space: nowrap;
}
.mail-item .mail-subj {
  font-size: var(--fs-sm);
  color: var(--text);
  margin-top: 2px;
}
.mail-item .mail-prev {
  font-size: var(--fs-xs);
  color: var(--text-3);
  margin-top: 4px;
  line-height: 1.4;
  max-height: 2.8em;
  overflow: hidden;
}
.mail-item .mail-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  flex-wrap: wrap;
}

.mail-detail {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.mail-detail .mail-list-head { background: var(--bg-elevated); }
.mail-body-text p { margin: 0 0 12px 0; }
.mail-body-text ul, .mail-body-text ol { padding-left: 20px; margin: 0 0 12px 0; }
.mail-body-text a { color: var(--brand-soft); text-decoration: underline; }
.mail-body-text a:hover { color: var(--brand); }

/* ---------- 27. Comptabilite (Phase 3.4) ---------- */
/* Plan OHADA + journal partie double + factures fournisseurs + saisie rapide.
   Reutilise massivement .tile/.bento/.tabs/.tw/.badge/.btn-* du DS v3. */
.journal-row-annulee td {
  text-decoration: line-through;
  color: var(--text-3);
  opacity: 0.6;
}
.fac-card.retard {
  border-color: rgba(244, 63, 94, 0.35) !important;
  background: rgba(244, 63, 94, 0.06);
}
.compte-numero {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-weight: 600;
}
.tva-banner {
  padding: 10px 14px;
  border-radius: var(--r-2);
  background: rgba(245, 158, 11, 0.08);
  border: 1px solid rgba(245, 158, 11, 0.30);
  color: var(--text);
  font-size: var(--fs-sm);
  margin-bottom: 16px;
}
/* Taille FIXE a l'origine : c'est elle qui coupait « 2 018 036 » a 320px sur la
   comptabilite (217px de place pour 222px de texte). Meme traitement que
   `.kpi-value`, avec un plafond plus haut puisque c'est un KPI de tete. */
.kpi-hero .kpi-value { font-size: clamp(26px, 6vw, 48px); }
.compta-saisie-card {
  cursor: pointer;
  padding: 14px;
  text-align: center;
  border-radius: var(--r-2);
  border: 1px solid var(--line);
  background: var(--bg-elevated);
  transition: background var(--t-fast), border-color var(--t-fast);
}
.compta-saisie-card:hover {
  border-color: var(--brand);
  background: rgba(14, 92, 99, 0.05);
}
.compta-equilibre {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-weight: 600;
}
.compta-equilibre.ok { color: var(--emerald-soft); }
.compta-equilibre.ko { color: var(--coral); }

/* Compta v3 commit 2 — additions */
.btn-disabled {
  opacity: 0.45;
  cursor: not-allowed !important;
  pointer-events: none;
}
.journal-row.statut-annulee td {
  text-decoration: line-through;
  color: var(--text-3);
  opacity: 0.6;
}
.compta-line-row {
  display: grid;
  grid-template-columns: minmax(180px, 2fr) minmax(140px, 1.5fr) 120px 120px auto;
  gap: 8px;
  margin-bottom: 6px;
  align-items: center;
}
.compta-line-row .input { width: 100%; }

/* Compta v3 commit 2 fix — modal detail (centrage + largeur) + modal
 * ecriture (footer sticky meme avec 5+ lignes via flex column).
 *
 * Le DS expose deja .v3-modal-head/body/foot (flex-shrink:0 / flex:1)
 * mais il faut que ces classes soient enfants directs de
 * .v3-modal-dialog en flex column. Quand on englobe head/body/foot dans
 * un <form>, on doit propager le flex au form. */
.v3-modal-dialog > form {
  display: flex;
  flex-direction: column;
  width: 100%;
}

/* Modal "Detail ecriture" : plus large pour afficher la table 4 colonnes
 * avec montants longs sans tronquer. */
.v3-modal.compta-modal-detail .v3-modal-dialog {
  width: min(900px, 92vw);
  min-width: min(640px, 92vw);
}

/* Modal "Nouvelle ecriture manuelle" : max-height bornee, body scrollable,
 * footer toujours visible (flex-shrink:0 sur .v3-modal-foot + sticky). */
.v3-modal.compta-modal-ecriture .v3-modal-dialog {
  width: min(820px, 95vw);
  max-height: 90vh;
}
.compta-modal-ecriture .v3-modal-body {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.compta-modal-ecriture .compta-ecr-lignes {
  max-height: 50vh;
  overflow-y: auto;
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.compta-modal-ecriture .v3-modal-foot {
  position: sticky;
  bottom: 0;
  background: var(--bg-card);
  border-top: 1px solid var(--line);
}

/* R.4c — Modal "Retour / Echange" (venteListe) : le <form> enveloppe
 * head/body/foot, il faut qu'il remplisse la hauteur du dialog pour que
 * .v3-modal-body scrolle et que le footer (boutons) reste toujours
 * visible, meme en mode Echange (contenu long : article A + article B +
 * differentiel). Scope strict a .retour-echange-modal : zero impact sur
 * les autres modales. */
.retour-echange-modal .v3-modal-dialog {
  max-height: 90vh;
}
.retour-echange-modal .v3-modal-dialog > form {
  flex: 1 1 auto;
  min-height: 0;
  max-height: 100%;
}
.retour-echange-modal .v3-modal-body {
  /* Hauteur max explicite = scroll garanti (independant de la resolution
   * flex du parent). Reserve ~140px pour le header + le footer sticky
   * dans un dialog contraint a 90vh. */
  min-height: 0;
  max-height: calc(90vh - 140px);
  overflow-y: auto;
}
.retour-echange-modal .v3-modal-foot {
  position: sticky;
  bottom: 0;
  background: var(--bg-card);
}

/* Modal v3 generique de confirmation (remplace window.confirm). */
.v3-modal.v3-confirm-modal .v3-modal-dialog {
  width: min(440px, 92vw);
}
.v3-modal.v3-confirm-modal .v3-modal-body p {
  margin: 0;
  color: var(--text-2);
  line-height: 1.5;
}

/* Compta v3 commit 3 — Depenses + Tresorerie + Factures.
 * Cards categories saisie rapide, mouvements treso colores selon sens,
 * cards factures groupees par statut. */
.compta-cat-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px;
  padding: 16px 10px;
  text-align: center;
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast), transform var(--t-fast);
  font: inherit;
  color: var(--text);
}
.compta-cat-card:hover {
  border-color: var(--brand);
  background: rgba(14, 92, 99, 0.06);
  transform: translateY(-2px);
}
.compta-cat-icon  { font-size: 28px; line-height: 1; }
.compta-cat-label { font-weight: 600; font-size: var(--fs-sm); }
.compta-cat-meta  { font-size: var(--fs-2xs); color: var(--text-3); font-family: 'JetBrains Mono', ui-monospace, monospace; }

/* Mouvements de tresorerie : couleur du fond selon sens (subtil). */
.mvt-row.mvt-sens-in  td:first-child { box-shadow: inset 3px 0 0 var(--emerald); }
.mvt-row.mvt-sens-out td:first-child { box-shadow: inset 3px 0 0 var(--coral); }

/* Factures fournisseurs : variantes statut. */
.fac-card.fac-payee {
  opacity: 0.6;
  filter: grayscale(0.3);
}
.fac-card.fac-annulee {
  opacity: 0.45;
  text-decoration: line-through;
}
.fac-card { transition: transform var(--t-fast), border-color var(--t-fast); }
.fac-card:hover:not(.fac-payee):not(.fac-annulee) {
  border-color: var(--brand);
  transform: translateY(-1px);
}

/* Compta v3 commit 5 — rapprochement bancaire. */
.rappr-row.rappr-ok td {
  background: rgba(16, 185, 129, 0.08);
  color: var(--text-2);
}
.rappr-row.rappr-ok td:first-child {
  box-shadow: inset 3px 0 0 var(--emerald);
}
.rappr-row td input[type="checkbox"]:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
/* Responsive mobile : forcer scroll horizontal sur les tables internes
 * du rapprochement (small screens). Le wrapper .tw a deja overflow-x,
 * on s assure que les colonnes ne se compriment pas trop. */
@media (max-width: 768px) {
  .compta-modal-ecriture .v3-modal-dialog,
  .compta-modal-detail .v3-modal-dialog {
    width: 95vw !important;
    min-width: 0 !important;
  }
  .compta-line-row {
    grid-template-columns: 1fr 1fr 1fr 1fr auto !important;
    gap: 4px !important;
  }
  /* La regle `.bento[style*="grid-template-columns:1fr 1fr"]` vivait ici. Elle
     n'a jamais rien matche : le markup ecrit `grid-template-columns: 1fr 1fr`
     AVEC une espace apres les deux-points. Remplacee par la regle generique de
     la section « Formulaires sur mobile », en pied de fichier. */
}

/* ---------- 30. Formulaires sur mobile ---------- */
/* Sous 640px, un formulaire doit s'empiler et se laisser remplir au pouce.
   Mesures avant, a 375px : les champs de `profile` et de la modale « Nouveau
   client » restaient sur 2 colonnes (130px et 155px de large), et les boutons
   d'action de `profile` tombaient a 36px de haut, alignes a droite. */
@media (max-width: 640px) {
  /* Grilles de formulaire dont les colonnes sont posees en style inline
     (`<div class="bento" style="grid-template-columns: 1fr 1fr">`). On cible la
     propriete seule, ce qui couvre toutes les graphies — c'est l'espace apres
     les deux-points qui rendait morte la regle precedente.
     `!important` est inevitable ici : on surcharge un style inline. */
  .bento[style*="grid-template-columns"] { grid-template-columns: 1fr !important; }
  /* Indispensable en complement : un enfant qui porte `grid-column: span 2`
     recree une 2e colonne IMPLICITE, et la grille repasse a 2 colonnes malgre
     le `1fr !important` ci-dessus. Mesure du symptome : « 22.4px 237.6px » au
     lieu d'une colonne pleine. Sur une grille a une colonne, `auto` fait
     occuper toute la largeur — c'est le comportement voulu pour ces champs. */
  .bento[style*="grid-template-columns"] > * { grid-column: auto !important; }

  /* Barres d'actions : les boutons se partagent toute la largeur et ne
     descendent jamais sous 44px. Deux motifs couverts — le pied de modale, et
     le conteneur `.flex.jce` (aligne a droite) utilise dans les formulaires
     de page. */
  .v3-modal-foot { flex-wrap: wrap; }
  .v3-modal-foot .btn,
  form .flex.jce > .btn {
    flex: 1 1 auto;
    min-height: 44px;
  }
}

/* ---------- 28. Approvisionnement v2 — pieces jointes (Phase 3.3) ---------- */
.file-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  transition: border-color var(--t-fast);
}
.file-row:hover { border-color: var(--line-strong); }
.file-thumb {
  width: 40px; height: 48px; border-radius: 6px;
  display: grid; place-items: center;
  color: #fff; font-weight: 700; font-size: 10px;
  flex-shrink: 0;
  box-shadow: var(--shadow-1);
}
.file-thumb.pdf  { background: linear-gradient(135deg, #f43f5e, #fb7185); }
.file-thumb.xlsx { background: linear-gradient(135deg, #10b981, #34d399); }
.file-thumb.docx { background: linear-gradient(135deg, #0ea5e9, #3b82f6); }
.file-thumb.img  { background: linear-gradient(135deg, #a855f7, #ec4899); }

.appro-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
  padding: 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}

/* Ligne historique selectionnee (mode ?appro=<id>) */
tr.is-selected td {
  background: rgba(14, 92, 99, 0.08);
  border-left: 3px solid var(--brand);
}

/* =====================================================================
   Section 29 — Approvisionnement v3
   Saisie d une livraison (lignes multiples), badges statut, workflow steps.

   L ancien bloc .appro-mode-toggle (4 radios « mode de livraison ») a ete
   retire avec la refonte de la saisie : il n existe plus qu un ecran.
   ===================================================================== */

/* --- Lignes de la livraison : grille alignee sur son en-tete --- */
.appro-lines-head,
.appro-line {
  display: grid;
  grid-template-columns:
    minmax(170px, 2fr)     /* article  */
    minmax(130px, 1.2fr)   /* magasin  */
    88px                   /* quantite */
    minmax(96px, 1fr)      /* unite (Lot 3a) */
    120px                  /* PA       */
    120px                  /* total    */
    56px                   /* MAJ PA   */
    40px                   /* prix vente */
    40px;                  /* retirer  */
  gap: var(--s-2);
  align-items: start;
}
.appro-lines-head {
  padding: 0 var(--s-2) var(--s-2);
  font-size: var(--fs-xs);
  color: var(--text-2);
  font-weight: 600;
}
.appro-lines {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.appro-line {
  padding: var(--s-2);
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
}
.appro-line:focus-within {
  border-color: var(--line-strong);
}
.appro-cell { min-width: 0; }
.appro-cell .input { width: 100%; }
/* Libelle par cellule : redondant avec l en-tete sur grand ecran, il ne
   reapparait qu une fois la grille repliee (voir media queries). */
.appro-cell-label {
  display: none;
  font-size: var(--fs-2xs);
  color: var(--text-3);
  margin-bottom: 2px;
}
.appro-cell-subtotal,
.appro-cell-check,
.appro-cell-btn {
  display: flex;
  align-items: center;
  min-height: 38px;
}
.appro-cell-subtotal { justify-content: flex-end; }
.appro-cell-check,
.appro-cell-btn { justify-content: center; }
.appro-cell-check input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}
.appro-line-hint {
  margin-top: 3px;
  line-height: 1.35;
}
/* Panneau replie « reviser le prix de vente » : pleine largeur sous la ligne. */
.appro-line-extra {
  grid-column: 1 / -1;
  margin-top: var(--s-2);
  padding-top: var(--s-2);
  border-top: 1px dashed var(--line-strong);
}
.appro-line-extra[hidden] { display: none; }
[data-appro-line-toggle].is-active {
  background: var(--brand-dim);
  color: var(--text);
}

/* --- Combobox article (catalogue en memoire, pas de <select> clone) --- */
.appro-combo { position: relative; }
.appro-combo-list {
  position: absolute;
  /* Au-dessus des tuiles suivantes (recapitulatif, historique), qui viennent
     apres dans le DOM. Suppose `.tile-popover` sur la tuile hote, sans quoi
     la liste serait rognee avant meme d etre empilee. */
  z-index: 60;
  top: calc(100% + 2px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--bg-card);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-3);
}
.appro-combo-list[hidden] { display: none; }
.appro-combo-item {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 8px;
  border-radius: var(--r-1);
  cursor: pointer;
  font-size: var(--fs-sm);
}
.appro-combo-item:hover,
.appro-combo-item.is-active {
  background: var(--bg-card-hover);
}
.appro-combo-item.is-active { box-shadow: inset 2px 0 0 var(--brand-soft); }
.appro-combo-nom { color: var(--text); font-weight: 500; }
.appro-combo-empty { padding: 8px; }

/* --- Modal « Choisir plusieurs » --- */
.appro-pick-list {
  max-height: 46vh;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  padding: 4px;
}
.appro-pick-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 7px 8px;
  border-radius: var(--r-1);
  cursor: pointer;
}
.appro-pick-row:hover { background: var(--bg-card-hover); }
.appro-pick-row input[type="checkbox"] {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  cursor: pointer;
}
.appro-pick-txt { min-width: 0; }

/* --- Barre d actions de la saisie --- */
.appro-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
  justify-content: space-between;
  margin-top: var(--s-4);
}

/* Grille repliee : les libelles par cellule prennent le relais de l en-tete,
   qui disparait. Aucun scroll horizontal (cf. checklist de revue UI §A).

   La grille passe en 12 colonnes et chaque cellule declare sa portee : les
   champs numeriques gardent ainsi une largeur reelle (>= 3/12) au lieu des
   pistes fixes de 40 px qui tronquaient un prix a 6 chiffres. */
@media (max-width: 1100px) {
  .appro-lines-head { display: none; }
  .appro-line { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .appro-cell-label { display: block; }

  .appro-cell-article  { grid-column: span 12; }
  .appro-cell-magasin  { grid-column: span 6; }
  /* Lot 3a — Qté et Unité vont ENSEMBLE : « 10 » ne veut rien dire sans
     « cartons » a cote. Elles partagent donc la meme rangee que le PA. */
  .appro-cell-qte      { grid-column: span 2; }
  .appro-cell-unite    { grid-column: span 2; }
  .appro-cell-pa       { grid-column: span 2; }
  .appro-cell-subtotal { grid-column: span 6; justify-content: flex-start; gap: var(--s-2); }
  /* row-reverse : la case passe AVANT son libelle, qui n est plus masque. */
  .appro-cell-check    {
    grid-column: span 4;
    flex-direction: row-reverse;
    justify-content: flex-end;
    gap: var(--s-2);
  }
  .appro-cell-btn      { grid-column: span 1; }
  /* Les libelles de cellule s alignent avec leur champ, pas au-dessus du vide. */
  .appro-cell-subtotal .appro-cell-label,
  .appro-cell-check .appro-cell-label { margin-bottom: 0; }
}
@media (max-width: 640px) {
  .appro-cell-magasin  { grid-column: span 12; }
  /* Au doigt, Qté et Unité restent cote a cote sur une rangee, le PA prend la
     suivante en pleine largeur : un prix a 6 chiffres ne tient pas en demi. */
  .appro-cell-qte      { grid-column: span 6; }
  .appro-cell-unite    { grid-column: span 6; }
  .appro-cell-pa       { grid-column: span 12; }
  .appro-cell-subtotal { grid-column: span 12; }
  .appro-cell-check    { grid-column: span 8; }
  .appro-cell-btn      { grid-column: span 2; }

  .appro-actions { flex-direction: column; align-items: stretch; }
  .appro-actions .btn { width: 100%; }
}

/* Badge statut workflow (sera utilise par les commits 2/4 sur la liste) */
.appro-status-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.appro-status-badge.brouillon      { background: rgba(160,160,160,.15); color: var(--text-2); }
.appro-status-badge.a_receptionner { background: rgba(245,158,11,.15);  color: #f59e0b; }
.appro-status-badge.recu           { background: rgba(16,185,129,.15);  color: var(--emerald-soft, #34d399); }
.appro-status-badge.cloture        { background: rgba(14,92,99,.15);  color: var(--brand); }
.appro-status-badge.legacy         { background: rgba(120,120,160,.10); color: var(--text-3); }

/* Workflow steps visuels (utilise par la fiche detail en commit 2) */
.appro-workflow-steps {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 12px;
  background: var(--bg-elevated);
  border-radius: var(--r-2);
}
.appro-workflow-steps .step-row {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 8px 0;
}
.appro-workflow-steps .step-bullet {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 700;
  border: 2px solid var(--line);
  color: var(--text-3);
  background: var(--bg-card);
}
.appro-workflow-steps .step-bullet.done {
  background: var(--emerald, #10b981);
  border-color: var(--emerald, #10b981);
  color: #fff;
}
.appro-workflow-steps .step-bullet.current {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  box-shadow: 0 0 0 4px rgba(14,92,99,0.30);
}
.appro-workflow-steps .step-label {
  flex: 1;
  font-size: 13px;
  color: var(--text);
}
.appro-workflow-steps .step-label.muted {
  color: var(--text-3);
}

/* =====================================================================
   Section 30 — Chat inter-magasins (Phase 3.2)
===================================================================== */

/* Palette avatar — extension des couleurs existantes (av-violet etc.) */
.av-rose  { background: linear-gradient(135deg,#f43f5e,#fb7185); color: #fff; }
.av-amber { background: linear-gradient(135deg,#f59e0b,#fbbf24); color: #fff; }
.av-mute  { background: var(--bg-elevated); color: var(--text-3); }

/* Layout global 3 colonnes */
.chat-shell {
  display: grid;
  grid-template-columns: 240px 1fr 280px;
  gap: 12px;
  height: calc(100vh - var(--topbar-h, 56px) - 96px);
  min-height: 540px;
}
@media (max-width: 1100px) {
  .chat-shell { grid-template-columns: 220px 1fr; }
  .chat-people { display: none; }
}
@media (max-width: 800px) {
  .chat-shell { grid-template-columns: 1fr; height: auto; min-height: 0; }
  .chat-channels { display: none; }
}

/* Sidebar canaux */
.chat-channels {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: 14px;
}
.channel-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--r-2);
  color: var(--text-2);
  font-size: var(--fs-md);
  cursor: pointer;
  transition: background var(--t-fast), color var(--t-fast);
  margin-bottom: 1px;
  text-decoration: none;
}
.channel-item:hover {
  background: rgba(14, 92, 99, 0.08);
  color: var(--text);
}
.channel-item.active {
  background: rgba(14, 92, 99, 0.14);
  color: var(--brand-soft);
}
.channel-item .hash {
  color: var(--text-3);
  font-weight: 700;
}
.channel-item .unread {
  margin-left: auto;
  padding: 1px 7px;
  background: var(--brand);
  color: #fff;
  border-radius: var(--r-full);
  font-size: 10px;
  font-weight: 600;
}

/* Main feed */
.chat-main {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 480px;
}
.chat-head {
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.chat-feed {
  flex: 1;
  overflow-y: auto;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.chat-input-zone {
  padding: 14px 16px;
  border-top: 1px solid var(--line);
  background: var(--bg-card);
}

/* Day divider */
.day-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-3);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 16px 0 8px;
}
.day-divider::before, .day-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* Bloc message */
.msg-block {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.msg-block .msg-body { flex: 1; min-width: 0; }
.msg-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 2px;
  flex-wrap: wrap;
}
.msg-author {
  font-weight: 600;
  font-size: 13px;
  color: var(--text);
}
.msg-time { font-size: 11px; color: var(--text-3); }
.msg-content {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
  word-wrap: break-word;
  overflow-wrap: break-word;
}
.msg-content a {
  color: var(--brand-soft);
  text-decoration: underline;
}
.msg-content code {
  font-family: var(--font-mono, ui-monospace, Menlo, Consolas, monospace);
  padding: 1px 6px;
  background: var(--bg-elevated);
  border-radius: 4px;
  font-size: 12px;
}
.msg-content strong { font-weight: 700; }
.msg-content em { font-style: italic; }

/* Message continued (meme auteur, dans 5 min) — preparation commit 2 */
.msg-block.continued { padding-left: 42px; }
.msg-block.continued .msg-time-hover {
  font-size: 10px;
  color: var(--text-3);
  width: 32px;
  opacity: 0;
  transition: opacity var(--t-fast);
}
.msg-block.continued:hover .msg-time-hover { opacity: 1; }

/* PJ (commit 3) */
.msg-attach {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  margin-top: 6px;
  max-width: 360px;
}

/* Reactions (commit 3) */
.reactions {
  display: flex;
  gap: 4px;
  margin-top: 6px;
  flex-wrap: wrap;
}
.reaction {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-full);
  font-size: 11px;
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.reaction:hover { border-color: var(--brand); }
.reaction.me {
  background: rgba(14, 92, 99, 0.14);
  border-color: var(--brand);
  color: var(--brand-soft);
}

/* Typing indicator (commit 4) */
.typing {
  display: inline-flex;
  gap: 3px;
}
.typing span {
  width: 5px;
  height: 5px;
  background: var(--text-3);
  border-radius: 50%;
  animation: chatTyping 1.2s infinite;
}
.typing span:nth-child(2) { animation-delay: 0.2s; }
.typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes chatTyping {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-3px); }
}

/* Panneau equipe (droite) */
.chat-people {
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: 14px;
  overflow-y: auto;
}
.person-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 6px;
  border-radius: var(--r-2);
  cursor: pointer;
  transition: background var(--t-fast);
}
.person-row:hover { background: rgba(14, 92, 99, 0.08); }

/* Statut presence (dot) */
.avatar.has-status { position: relative; }
.avatar .status-dot {
  position: absolute;
  bottom: -1px;
  right: -1px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid var(--bg-card);
}

/* Avatar systeme (messages auto Sando) */
.avatar-system {
  background: linear-gradient(135deg, #1e293b, #374151);
  color: var(--text-3);
}

/* Input bar */
.input-bar {
  display: flex;
  align-items: center;
  padding: 4px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  transition: border-color var(--t-fast);
}
.input-bar:focus-within { border-color: var(--brand); }
.input-bar input,
.input-bar textarea {
  flex: 1;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
}

/* Message systeme — card jaune au lieu du msg-content classique */
.msg-block.msg-system .msg-author { color: var(--text-3); }
.msg-system-card {
  padding: 10px 12px;
  background: var(--bg-elevated);
  border-left: 3px solid var(--amber);
  border-radius: var(--r-2);
  font-size: 13px;
  color: var(--text);
}

/* Modale DM (overlay) */
.chat-dm-modal[hidden] { display: none !important; }
.chat-canal-modal[hidden] { display: none !important; }
.chat-invite-modal[hidden] { display: none !important; }

/* Mentions @ — Phase 3.2 commit 2 */
.msg-mention {
  display: inline-block;
  padding: 0 4px;
  background: rgba(14, 92, 99, 0.18);
  color: var(--brand-soft);
  border-radius: 3px;
  font-weight: 500;
  cursor: pointer;
}
.msg-mention.me {
  background: rgba(245, 158, 11, 0.20);
  color: var(--amber);
}
.msg-block.mentioned-me {
  border-left: 2px solid var(--amber);
  padding-left: 8px;
  margin-left: -10px;
}

/* Dropdown autocomplete mentions */
.mention-dropdown[hidden] { display: none !important; }
.mention-dropdown .mention-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: var(--r-2);
  cursor: pointer;
  font-size: 13px;
}
.mention-dropdown .mention-item:hover,
.mention-dropdown .mention-item.active {
  background: rgba(14, 92, 99, 0.14);
  color: var(--brand-soft);
}

/* =====================================================================
   Section 30b — Chat Phase 3.2 commit 3 (PJ + Reactions + Recherche + Epingles)
===================================================================== */

/* Drag & drop dropzone (sur .chat-feed) */
.chat-dropzone.drag-over {
  outline: 2px dashed var(--brand);
  outline-offset: -8px;
  background: rgba(14, 92, 99, 0.06);
}

/* Actions hover sur message (pin) */
.msg-block {
  position: relative;
}
.msg-actions {
  display: none;
  position: absolute;
  top: 6px;
  right: 6px;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  padding: 2px;
  gap: 2px;
}
.msg-block:hover .msg-actions {
  display: flex;
}
.msg-action-btn {
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 4px 8px;
  font-size: 14px;
  border-radius: var(--r-2);
  color: var(--text-2);
}
.msg-action-btn:hover {
  background: var(--bg-elevated);
  color: var(--text);
}

/* Reaction add (+) button — fix UX task #103 : toujours visible + accentue au hover */
.reaction.reaction-add {
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  color: var(--text-2);
  cursor: pointer;
  font-size: 13px;
  padding: 3px 9px;
  transition: all var(--t-fast);
}
.reaction.reaction-add::before {
  content: '😊 ';
  font-size: 13px;
}
.msg-block:hover .reaction.reaction-add {
  background: var(--bg-card);
  border-color: var(--line-strong);
  color: var(--text);
}
.reaction.reaction-add:hover {
  background: rgba(14, 92, 99, 0.10);
  border-color: var(--brand);
  color: var(--brand-soft);
  transform: scale(1.05);
}

/* Emoji picker (overlay) */
.emoji-picker[hidden] { display: none !important; }
.emoji-picker {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  padding: 6px;
  background: var(--bg-card);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
  min-width: 200px;
}
.emoji-pick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  font-size: 18px;
  cursor: pointer;
  border-radius: var(--r-2);
  user-select: none;
}
.emoji-pick:hover {
  background: rgba(14, 92, 99, 0.14);
}

/* Bandeau epingle haut canal */
.pinned-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  background: rgba(245, 158, 11, 0.08);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  color: var(--text);
}
.pinned-banner a {
  color: var(--brand-soft);
  text-decoration: none;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

/* Carte epingle (panneau droit) */
.pinned-card {
  padding: 10px;
  background: var(--bg-elevated);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  margin-bottom: 8px;
  font-size: 12px;
}

/* Panneau resultats recherche */
.search-results-panel[hidden] { display: none !important; }
.search-results-panel {
  border-bottom: 1px solid var(--line);
  background: var(--bg-card);
  max-height: 340px;
  overflow-y: auto;
}
.search-result-card:hover {
  background: var(--bg-elevated);
}
.search-highlight {
  background: rgba(245, 158, 11, 0.30);
  padding: 0 2px;
  border-radius: 2px;
  color: inherit;
}

/* Preview PJ avant envoi (input zone) */
.chat-pj-preview {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.pj-preview-chip { /* style inline mais classe utile pour selecteurs futurs */
  position: relative;
}

/* Fallback file-thumb pour extensions non listees (csv, txt, etc.) — les
   variantes .pdf/.img/.xlsx/.docx sont definies plus haut dans v3.css. */
.file-thumb.file { background: linear-gradient(135deg,#475569,#64748b); color: #fff; }

/* ---------- 31. Cibles tactiles — plancher de 44px sous 768px ----------
   WCAG 2.5.5 / iOS HIG / Material : une cible actionnee au doigt mesure au
   moins 44px sur les DEUX axes. La regle n'avait ete posee que dans le tunnel
   de vente (§25 bis) et sur les controles des tables de donnees (§8) ; partout
   ailleurs l'app tournait entre 15 et 40px.

   Mesures avant, hit-test reel a 360px (Chrome headless, 21 ecrans admin +
   gerant) : 204 `.icon-btn` a 34x34, 84 `.btn-sm` a 30px de haut, 92 champs a
   32-40px, 36 onglets a 41-43px, 36 chips a 31,5px, les segments a 29,5px, le
   fil d'Ariane a 18px, les cases a cocher de la boite mail a 16x16, le bouton
   « Envoyer » du chat a 20x28.

   DEUX TECHNIQUES, choisies selon la densite du voisinage :

   (a) AGRANDISSEMENT REEL (`min-height` / `min-width`) quand l'element est une
       boite dans un flux. Le layout lui rend la place, donc deux cibles
       voisines ne peuvent PAS se recouvrir : c'est la seule technique sure des
       qu'une rangee peut passer a la ligne (`fwrap`) ou qu'une liste s'empile.
       Employee pour : boutons, champs, chips, onglets, segments, pagination,
       liens de navigation, cases a cocher.

   (b) EXTENSION DE LA ZONE (pseudo-element transparent, `position: absolute` a
       insets negatifs) quand grossir la boite deplacerait le texte autour. Le
       visuel ne bouge pas d'un pixel, seule la zone cliquable deborde.
       Employee pour : le fil d'Ariane et les badges cliquables, poses DANS une
       ligne de texte, et l'interrupteur (dont la pastille est un dessin fixe).

   ⚠️ Pourquoi (b) n'est pas generalise, alors que le brief le privilegie :
   il ne convient qu'aux elements ISOLES de leurs semblables. Deux chips
   etendues de 6px de part et d'autre dans une rangee `fwrap` a `gap: 8px` se
   recouvriraient des le passage a la ligne, et le tap partirait sur la mauvaise
   cible — exactement ce que la consigne « eviter les chevauchements » interdit.
   Les trois cas (b) retenus ci-dessous sont, eux, seuls sur leur ligne.

   Desktop > 768px STRICTEMENT inchange : tout vit dans la media query.

   UNE SEULE EXCEPTION au plancher de 44px dans toute cette section : les
   reactions du chat, plafonnees a 32px — motif detaille en 31.7. */
@media (max-width: 768px) {

  /* --- 31.1 Boutons — technique (a) ---------------------------------------
     Couvre d'un coup `.btn-xs` (24px), `.btn-sm` (30px), `.btn` (37px) et
     `.btn-lg` (40px), toutes couleurs confondues. `min-*` ne retrecit jamais :
     les boutons deja a 44px (tunnel de vente, cellules d'action de table, pied
     de modale) sont inchanges, la regle est un plancher et non une taille.
     `min-width` est indispensable et pas seulement confortable : la pagination
     de l'historique des ventes rend des pastilles « 1 » de 27px de large, et le
     « Envoyer » du chat tombe a 20px une fois comprime par son voisin. */
  .btn {
    min-height: 44px;
    min-width: 44px;
  }

  /* Corollaire du `min-width` ci-dessus : une grappe de pagination compte
     jusqu'a 7 pastilles. A 44px elles totalisent 332px pour 296px utiles a
     320px de viewport, donc un debordement horizontal — le defaut meme que ce
     chantier doit eviter. `.flex.gap-1` est le conteneur de ces grappes (3
     vues) ; l'autoriser a passer a la ligne ne change rien tant que tout tient,
     et evite le debordement quand ce n'est plus le cas. */
  .flex.gap-1 { flex-wrap: wrap; }

  /* --- 31.2 Champs de formulaire — technique (a) --------------------------
     Un champ se saisit au doigt avant de se lire : sa hauteur est une cible.
     Les `select` compacts de la boite mail (27px) et de la pagination (34px)
     portent leur padding en style inline — `min-height` n'entre pas en
     concurrence avec `padding`, la regle passe donc sans `!important`.
     Complement de la regle anti-zoom iOS (§ « Anti-zoom iOS ») : celle-ci fixe
     la police a 16px, celle-la la hauteur a 44px. */
  .input,
  select.input,
  textarea.input,
  .input-group > .input,
  .input-group-text,
  .chat-search input,
  .pos-search input { min-height: 44px; }

  /* --- 31.3 Boutons-icones — technique (a) --------------------------------
     Le fond d'un `.icon-btn` est transparent au repos : l'agrandir ne se voit
     pas, seule la surface de survol grandit. C'est le meilleur des deux mondes
     et la raison pour laquelle (a) l'emporte ici malgre la densite de la
     topbar. Les deux `.icon-btn` du chat portent `width/height: 32px` en style
     INLINE : `min-width` / `min-height` contraignent la valeur utilisee quelle
     que soit la specificite, la regle passe donc quand meme. */
  .icon-btn,
  .topbar-cmd,
  .emoji-pick,
  .msg-action-btn {
    min-width: 44px;
    min-height: 44px;
  }
  /* Le bouton d'envoi du chat est le seul `.btn` d'une rangee `nowrap` : sans
     ce `flex-shrink`, son voisin `flex: 1` le comprimait sous son `min-width`. */
  .input-bar .btn { flex-shrink: 0; }
  /* La saisie du chat et la recherche de canal n'ont pas la classe `.input`
     (styles inline) : leur hauteur vient du seul padding, soit 29 et 33px.
     C'est le CHAMP qui doit faire 44px, pas la barre qui l'entoure : taper
     dans les 4px de padding de `.input-bar` ne donne pas le focus. La barre
     passe donc a 52px, ce qui est sa hauteur naturelle sur telephone. */
  .input-bar input,
  .input-bar textarea { min-height: 44px; }

  /* --- 31.4 Chips, onglets, segments, pagination — technique (a) ----------
     Tous sont des elements de rangees `fwrap` : (b) les ferait se recouvrir
     des la 2e ligne (cf. l'encadre en tete de section).
     `.tab` n'etait qu'a 1px du compte (43px) et `.tabs` defile deja
     horizontalement : le passage a 44px ne change rien a l'encombrement.
     `display: inline-flex` est necessaire sur `.tab` et `.seg`, qui sont rendus
     tantot en `<a>`/`<span>` : sans lui `min-height` n'aurait aucun effet hors
     d'un conteneur flex. */
  .chip,
  .tab,
  .segment .seg {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .pager .p { min-width: 44px; min-height: 44px; }

  /* --- 31.5 Listes de navigation — technique (a) --------------------------
     Elements empiles : l'agrandissement pousse le suivant vers le bas, aucun
     recouvrement possible. La sidebar est hors-ecran a cette largeur, donc la
     hauteur gagnee ne coute rien a la page. */
  .sidebar-link,
  .mail-folder,
  .channel-item,
  .mail-item { min-height: 44px; }

  /* --- 31.6 Cases a cocher et boutons radio — technique (a) ---------------
     Le `<input>` de 16x16 est la plus petite cible de toute l'app (boite mail,
     selection multiple). Le label l'englobe : c'est lui qu'on porte a 44px, et
     l'input passe a 20px pour rester visible au centre d'une zone trois fois
     plus grande.
     `min-width` compte autant que `min-height` ici : la case de selection d'un
     message n'a AUCUN texte, son label se reduisait donc a la largeur de la
     case (27px). Sans effet sur les labels qui portent un libelle, deja plus
     larges. */
  .checkbox,
  .radio { min-height: 44px; min-width: 44px; }
  /* `flex-shrink: 0` : le label etant un `inline-flex`, la case se laissait
     comprimer a 13px par le libelle voisin dans les rangees serrees. */
  .checkbox input,
  .radio input { width: 20px; height: 20px; flex-shrink: 0; }

  /* --- 31.7 Reactions du chat — 32px, SEULE EXCEPTION au plancher ---------
     ⚠️ La seule cible de l'app qui ne monte pas a 44px, et c'est delibere.
     Une reaction est un controle SECONDAIRE et DENSE : plusieurs pastilles
     emoji sous un meme message, dans un fil qu'on lit d'abord. A 44px elles
     pesaient autant que les boutons d'action de la page et faisaient du fil
     une succession de gros boutons — le rapport visuel entre le message et sa
     decoration s'inversait. 32px les garde tapables et discretes.

     Ce que ca respecte quand meme : le minimum de 24x24 de WCAG 2.5.8 (AA)
     est depasse de 33 %. Seul le 44x44 de la 2.5.5 (AAA) n'est pas atteint,
     sur ce composant-la uniquement.

     Pourquoi PAS la technique (b), qui aurait donne 44px sans grossir la
     pastille : `.reactions` est une rangee `flex-wrap: wrap` a `gap: 4px`.
     Deux zones etendues de ~12px de part et d'autre se recouvriraient des le
     passage a la ligne, et le tap partirait sur la mauvaise reaction — le
     defaut que l'encadre en tete de section interdit explicitement.
     Le bouton d'ajout `.reaction-add` suit la meme regle : il vit dans la
     meme rangee et doit rester aligne sur ses voisines. */
  .reaction { min-height: 32px; min-width: 32px; }

  /* --- 31.8 Fil d'Ariane — technique (b) ----------------------------------
     18px de haut, pose juste au-dessus du titre de page. L'agrandir a 44px
     repousserait le titre de 26px sur l'ecran ou la place verticale est la plus
     rare. Le pseudo-element etend la zone sans rien deplacer.
     Pas de recouvrement lateral : deux miettes sont separees par un `.sep` et
     deux `gap` de 6px, soit ~16px pour 4+4px d'extension. */
  .crumbs a { position: relative; }
  .crumbs a::before {
    content: "";
    position: absolute;
    top: -13px; right: -4px; bottom: -13px; left: -4px;
  }

  /* --- 31.9 Badges cliquables — technique (b) -----------------------------
     Un `<a class="badge">` (« Ce produit n'a encore aucun stock… » sur la fiche
     stock) fait 23px de haut et vit au fil du texte : le grossir casserait la
     ligne. Seul le lien est vise — un `.badge` de statut n'est pas cliquable et
     ne doit pas capter de tap. */
  a.badge,
  button.badge { position: relative; }
  a.badge::before,
  button.badge::before {
    content: "";
    position: absolute;
    top: -11px; right: 0; bottom: -11px; left: 0;
  }

  /* --- 31.10 Interrupteur — technique (b) ---------------------------------
     `.switch` est un dessin de 38x22 dont les proportions font le composant :
     le porter a 44px de haut le deformerait. Le pseudo-element passe SOUS la
     pastille (`.switch-slider`, peinte apres lui) et etend la zone du <label>,
     qui bascule l'input. */
  .switch { position: relative; }
  .switch::before {
    content: "";
    position: absolute;
    top: -11px; right: -3px; bottom: -11px; left: -3px;
  }
}

/* Topbar sous 400px — arbitrage assume.
   Avec les `.icon-btn` a 44px, la topbar demande 24 (padding) + 3x44 (menu,
   accueil, recherche) + 2x44 (theme, cloche) + 20 (gaps) + 94 (pastille de
   role) = 358px. Elle tient donc a partir de ~366px, et deborde en dessous.
   Les cinq ACTIONS sont conservees — c'etait deja la regle posee a 640px ; ce
   qui saute est la pastille de role, seule information non actionnable de la
   barre, et elle reste lisible dans le pied de la sidebar (`.sidebar-user-role`)
   ainsi que dans le bandeau de role.
   ⚠️ `.role-pill` est stylee par le <style> injecte au runtime par
   `assets/libs/js/v3/app-layout.js` — donc APRES cette feuille. A specificite
   egale il gagnerait : d'ou `.topbar .role-pill` (0,2,0) et non `.role-pill`. */
@media (max-width: 399px) {
  .topbar .role-pill { display: none; }
}

/* =========================================================================
   VERROU D ABONNEMENT — bridage visible EN AMONT
   =========================================================================
   Une fonction reservee a une formule superieure doit PARAITRE verrouillee des
   l entree : le bridage etait purement serveur et rejetait au dernier clic,
   apres tout le formulaire. On grise, on badge, on explique — on ne cache pas :
   une entree qui disparait deroute, une entree verrouillee fait aussi voir ce
   que la formule payante apporterait.

   ⚠ `.btn:disabled` n existait PAS avant ce bloc : un bouton `disabled` avait
   donc exactement l apparence d un bouton actif (seul `.btn-disabled`, ajoute
   pour la compta, etait style). Un utilisateur ne pouvait pas voir qu il ne
   servait a rien de cliquer.
   ⚠ PAS de `pointer-events: none` ici, contrairement a `.btn-disabled` : il
   supprimerait l infobulle qui porte le motif du verrou. Le navigateur
   n emet de toute façon aucun evenement sur un <button disabled>.
   ========================================================================= */
.btn:disabled,
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:hover,
.btn[disabled]:hover {
  transform: none;
  filter: none;
  box-shadow: none;
}
/* Un <a> verrouille : `disabled` n existe pas sur un lien, c est
   `aria-disabled` qui fait foi — et lui, il faut bien neutraliser le clic. */
a.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}

/* La pastille de formule ne sert plus QUE dans la sidebar : un menu vertical a
   la place de la porter a droite du libelle. Dans une barre d actions, deux
   verrous simultanes donnaient deux pastilles ambre en rangee — trop dense, et
   la barre se cassait sur ecran etroit. Le cadenas y passe donc SUR le libelle
   du bouton (app_verrou_icone) et le motif descend dans `.verrou-note`.
   `.verrou-wrap`, qui n existait que pour coller la pastille au bouton, a ete
   retiree avec ses derniers points d appel. */
.verrou-badge { flex-shrink: 0; }

/* Le motif du verrou, en TEXTE VISIBLE. Une infobulle n existe ni au doigt sur
   tablette ni au lecteur d ecran : sans cette note, le bouton grise se lit
   comme une panne. */
.verrou-note {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s-2);
  margin: var(--s-3) 0 0;
  padding: var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  color: var(--text-2);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.verrou-note-icone { flex-shrink: 0; }
.verrou-cta {
  color: var(--brand-soft);
  font-weight: 600;
  text-decoration: underline;
  white-space: nowrap;
}
.verrou-cta:hover { color: var(--text); }

/* Entree de menu verrouillee : estompee mais TOUJOURS cliquable — elle mene a
   la page, qui explique. Le badge, lui, dit pourquoi avant meme le clic. */
.sidebar-link.is-locked { color: var(--text-3); }
.sidebar-link.is-locked .icon { opacity: 0.5; }
.sidebar-link.is-locked .badge {
  margin-left: auto;
  font-size: 10px;
  padding: 1px 7px;
}

/* Page entiere reservee (module compta d une formule non Pro). */
.verrou-panneau {
  text-align: center;
  padding: var(--s-8) var(--s-6);
}
.verrou-panneau-icone { font-size: 40px; line-height: 1; margin-bottom: var(--s-3); }
.verrou-panneau-titre {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display);
  font-size: var(--fs-xl);
}
.verrou-panneau-desc,
.verrou-panneau-msg {
  margin: 0 auto var(--s-3);
  max-width: 62ch;
  color: var(--text-2);
  font-size: var(--fs-md);
  line-height: 1.6;
}
.verrou-panneau-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-3);
  margin-top: var(--s-4);
}

/* ═══ A1 — ENCAISSEMENTS PAR MAGASIN ET PAR MOYEN ═══════════════════════════
   ⚠️⚠️ DES CARTES, PAS UN TABLEAU. Cet ecran se consulte AU TELEPHONE, le
   releve de l'operateur mobile ouvert a cote : c'est le geste reel de
   verification. Six colonnes y seraient soit illisibles, soit defilantes — et
   un chiffre qu'on doit aller chercher en faisant glisser est un chiffre qu'on
   ne verifie pas.
   ⭐ `auto-fit` + `minmax` fait passer la grille a une colonne sous 768px SANS
   media query. ⚠️ `minmax(0, 1fr)` et non `1fr` : la taille minimale par defaut
   d'une piste est `auto`, donc un montant a neuf chiffres POUSSE la colonne et
   fait deborder la tuile — anti-pattern connu du design system, et defaut deja
   paye sur la landing. */
.cx-grille {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--s-4);
}

.cx-carte {
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  padding: var(--s-4);
  background: var(--bg-card);
  min-width: 0;
}

.cx-entete { margin-bottom: var(--s-3); }

/* Les trois seaux, en lignes : au telephone, une valeur monetaire longue a
   besoin de toute la largeur. En colonnes, elle se couperait ou retrecirait. */
.cx-seaux { margin: 0; display: flex; flex-direction: column; gap: var(--s-2); }
.cx-seau {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  min-width: 0;
  /* ⚠️ UI-3 — `wrap` plutot que debordement : quand le libelle et le montant ne
     tiennent plus sur une ligne, le montant PASSE A LA LIGNE au lieu de sortir
     de la carte. Il porte aussi la mention de nature de CAISSE-2, qui prend
     ainsi sa propre ligne pleine largeur. */
  flex-wrap: wrap;
}
.cx-seau dt { color: var(--text-2); font-size: var(--fs-sm); }
.cx-seau dd { margin: 0; text-align: right; min-width: 0; }

/* ⚠️⚠️ CAISSE-2 — LA NATURE DU MONTANT, SOUS SON LIBELLE.
   Un montant hors-especes est une DECLARATION, pas un constat : les especes se
   comptent dans un tiroir, le mobile money ne se constate nulle part dans
   Sando. La mention le dit en permanence, meme a zero.
   ⚠️ Registre DISCRET, jamais alarmant : ni liserre, ni couleur d'alerte. Elle
   informe sur la nature de la ligne, elle ne signale pas un probleme — et il
   n'y en a pas : c'est le produit qui ne sait pas confirmer, pas le gerant qui
   n'aurait pas prouve.
   ⚠️ UI-3 : aucune troncature, aucun `nowrap`. Le texte passe a la ligne, et le
   `min-width: 0` laisse le <dt> retrecir au lieu de pousser le montant hors de
   sa tuile — un montant rogne est FAUX, pas seulement moins lisible. */
.cx-seau dt { min-width: 0; }
/* ⚠️ Le selecteur porte `dd` VOLONTAIREMENT : `.cx-seau dd` aligne a droite et
   annule les marges, et sa specificite bat celle d'une simple classe. Sans le
   `dd` ici, la mention se rendait collee au bord droit, sous le montant — donc
   lue comme une precision du CHIFFRE, quand elle qualifie la LIGNE. */
.cx-seau dd.cx-seau-nature {
  /* ⚠️ Pleine largeur : posee dans le <dt>, elle poussait le montant hors de sa
     carte — mesure a 360 px, « 128 940 000 GNF » rendu « 128 940 000 GN ». */
  flex: 0 0 100%;
  margin-top: 2px;
  text-align: left;
  font-size: var(--fs-xs);
  font-weight: 400;
  line-height: 1.4;
  color: var(--text-3);
}

/* ⚠️ LE SEAU « MOYEN NON ENREGISTRE » SE VOIT, MAIS N'ACCUSE PAS.
   Un liserre ambre discret dit « il y a quelque chose a savoir ici » ; il ne
   dit pas « anomalie ». Pas de rouge, pas de fond alarmant : ce seau signale
   une ignorance du PRODUIT, jamais une faute d'une personne.
   ⚠️ Et la couleur ne porte JAMAIS seule l'information — le libelle « Moyen
   non enregistre » suffit a lui seul, la teinte ne fait que le souligner. */
.cx-seau--attention {
  border-left: 2px solid var(--amber);
  padding-left: var(--s-2);
  margin-left: calc(var(--s-2) * -1);
}

.cx-note {
  margin: var(--s-3) 0 0;
  color: var(--text-3);
  font-size: var(--fs-xs);
  line-height: 1.5;
}

.cx-pied {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: 1px solid var(--line);
}
.cx-pied > div { min-width: 0; }
