/* ============================================================================
   Hotspots Online — système de design
   ----------------------------------------------------------------------------
   Direction visuelle inspirée de mobbin.com, adaptée à la gestion de hotspots :

     - toile neutre (#f7f7f7 clair / #0f0f0f sombre) et surfaces blanches, comme
       la bibliothèque de captures Mobbin — la couleur reste réservée aux états
       (en ligne, erreur, alerte), jamais utilisée pour décorer ;
     - bordures de 1 px en encre translucide (au lieu de gris bleutés) : elles
       restent lisibles sur les deux thèmes sans être recolorées ;
     - rayons généreux (14 px pour les cartes, 10 px pour les champs) et forme
       « pilule » pour tout ce qui se clique (boutons, badges, navigation) ;
     - typographie serrée sur les titres (letter-spacing négatif) et tabulaire
       pour les chiffres : les compteurs d'un tableau de bord ne doivent pas
       « sauter » d'une valeur à l'autre ;
     - animations courtes (140 à 340 ms) déclenchées par l'action de
       l'utilisateur ; tout est neutralisé sous prefers-reduced-motion.

   Les noms de classes existants sont conservés à l'identique : les vues n'ont
   pas à changer pour profiter du nouveau thème.
   ============================================================================ */

/* ---------- 0. Police auto-hébergée ---------- */
/* Poppins remplace Inter, qui venait de Google Fonts. Deux raisons :
   le réseau sortant est bloqué sur certaines installations — le site ne doit
   pas dépendre d'un CDN externe pour afficher son interface —, et Poppins est
   déjà la police de la caisse Android vendeur, donc les deux produits
   partagent la même charte.

   Quatre graisses seulement (400/500/600/700), copiées telles quelles et
   couvertes par la SIL OFL 1.1 (public/fonts/poppins/LICENCE-POPPINS.txt).
   Les graisses intermédiaires que la feuille emploie (450, 550, 650, 800)
   retombent sur la graisse déclarée la plus proche selon l'algorithme du
   navigateur : pas de faux gras fabriqué, et l'écart relatif entre titres et
   texte reste celui de la charte. Les `url('/fonts/...')` partent de la racine
   publique, comme app.css elle-même.

   `font-display: swap` : le texte s'affiche immédiatement dans la pile
   système puis bascule sur Poppins quand le fichier est reçu. Une police
   locale se charge en quelques millisecondes, mais un écran ne doit jamais
   rester vide à cause d'une police.

   Deux détails d'Inter deviennent inertes avec Poppins, sans casser le
   rendu : les fonctionnalités `cv11`/`ss01` posées sur `body` ne sont pas
   définies par Poppins (elles sont ignorées), et la police ne fournit pas de
   chiffres tabulaires — `font-variant-numeric: tabular-nums` reste déclaré
   sur les montants mais ne produit pas d'alignement en colonne. C'est
   pourquoi les données qui doivent s'aligner (codes, adresses, colonnes
   chiffrées) passent déjà par `--font-mono`, dont les replis sont à chasse
   fixe. */
@font-face {
  font-family: "Poppins";
  src: url("/fonts/poppins/Poppins-Regular.woff2") format("woff2"),
       url("/fonts/poppins/Poppins-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/fonts/poppins/Poppins-Medium.woff2") format("woff2"),
       url("/fonts/poppins/Poppins-Medium.ttf") format("truetype");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/fonts/poppins/Poppins-SemiBold.woff2") format("woff2"),
       url("/fonts/poppins/Poppins-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Poppins";
  src: url("/fonts/poppins/Poppins-Bold.woff2") format("woff2"),
       url("/fonts/poppins/Poppins-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---------- 1. Jetons de design ---------- */
:root {
  /* Toile et surfaces */
  --bg: #f7f7f7;
  --surface: #ffffff;
  --surface-2: #fafafa;
  --surface-3: #f0f0f0;
  --surface-inverse: #141414;

  /* Encres.
     Le secondaire doit rester lisible sur les trois surfaces où il vit
     (toile #f7f7f7, surface #fff, surface-3 #f0f0f0) : #767676 tombait à
     4,2:1 et #adadad à 2,1:1. On les assombrit jusqu'au seuil AA SANS aller
     vers le noir pur : l'échelle reste ink #141414 > ink-2 #404040 >
     muted #5a5a5a > faint #696969, donc la hiérarchie ne s'aplatit pas.
     La toile et les surfaces sont des gris neutres (R=G=B) : leur teinte est
     le neutre lui-même, et ces deux gris en dérivent — aucun bleu, aucun noir. */
  --ink: #141414;
  --ink-2: #404040;
  --muted: #5a5a5a;   /* 6,44:1 sur la toile ; 6,90:1 sur blanc */
  --faint: #696969;   /* 5,12:1 sur la toile ; 4,82:1 sur surface-3 */
  --on-inverse: #ffffff;

  /* Traits */
  --line: rgba(20, 20, 20, 0.09);
  --line-2: rgba(20, 20, 20, 0.16);

  /* Marque (verte : l'identité du produit) et accents sémantiques.
     --brand reste le vert d'identité, réservé aux aplats, traits et pastilles
     (3:1 suffit pour l'interface). Dès que le vert porte du TEXTE, il lui faut
     4,5:1 : --brand-ink est la même teinte (155°), seulement assombrie de
     #2f9e6f à #197049. Aucune bascule vers une autre famille de vert. */
  --brand: #2f9e6f;
  --brand-strong: #197049;
  --brand-ink: #197049;
  --brand-soft: rgba(47, 158, 111, 0.12);

  /* Notification non lue : un fond translucide FRANC, plus appuyé que
     --brand-soft, complété par un liseré plein à gauche (voir .notif-ligne et
     .notif-item). Deux signaux pour un seul état : la teinte seule se confond
     avec une ligne survolée ou avec la surface selon l'écran, le liseré tient. */
  --notif-unread: rgba(47, 158, 111, 0.18);
  --notif-unread-hover: rgba(47, 158, 111, 0.26);
  --highlight: #ff540b;        /* accent « mise en avant » (orange Mobbin) */
  --highlight-soft: rgba(255, 84, 11, 0.12);
  /* L'orange reste franc ; c'est l'encre posée DESSUS qui passe au brun très
     sombre : blanc sur #ff540b ne donnait que 3,22:1, #1a1200 en donne 5,77:1. */
  --highlight-ink: #1a1200;
  --link: #0065ff;
  /* Sémantique : mêmes teintes, assombries jusqu'à 4,5:1 sur leur propre
     lavis (success-soft, warning-soft...). On les mesure sur le lavis aplati
     au-dessus de surface-3 (#f0f0f0), le pire fond possible. */
  --success: #246a0b;          /* 4,95:1 sur son lavis ; 6,9:1 sur blanc */
  --success-soft: rgba(36, 106, 11, 0.12);
  --warning: #7d4a00;          /* 5,40:1 sur son lavis */
  --warning-soft: rgba(125, 74, 0, 0.12);
  --danger: #b82400;           /* 4,78:1 sur son lavis */
  --danger-soft: rgba(184, 36, 0, 0.10);
  --info: #0052cc;             /* 5,13:1 sur son lavis */
  --info-soft: rgba(0, 82, 204, 0.10);

  /* Élévations */
  --shadow-1: 0 1px 2px rgba(20, 20, 20, 0.05), 0 6px 20px rgba(20, 20, 20, 0.05);
  --shadow-2: 0 2px 6px rgba(20, 20, 20, 0.06), 0 18px 44px rgba(20, 20, 20, 0.10);

  /* Rayons */
  --r-xs: 6px;
  --r-sm: 10px;
  --r: 14px;
  --r-lg: 18px;
  --r-xl: 24px;
  --r-pill: 999px;

  /* Rythme des animations */
  --dur-1: 0.14s;
  --dur-2: 0.22s;
  --dur-3: 0.34s;
  --ease: cubic-bezier(0.2, 0.7, 0.25, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  /* Gabarit */
  --shell-h: 62px;
  --container: 1200px;

  /* Sans-serif : Poppins auto-hébergée (ci-dessus). Les replis système
     couvrent le cas où les fichiers manqueraient à un déploiement. */
  --font: "Poppins", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Monospace : JetBrains Mono n'est pas livrée avec le dépôt. L'empilement
     est conservé tel quel ; les replis (ui-monospace, Cascadia Code, SF Mono,
     Consolas, Menlo) sont tous des polices à chasse fixe réellement présentes
     sur les postes cibles — le code et les montants restent alignés même
     quand JetBrains Mono est absente. Changer cet empilement casserait
     l'alignement des colonnes de vouchers et des tableaux. */
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Code", "SF Mono", Consolas, Menlo, monospace;

  /* ------------------------------------------------------------------------
     Alias de compatibilité
     ------------------------------------------------------------------------
     Des vues écrivent encore certains anciens jetons en style en ligne
     (ex. `style="color:var(--accent)"`). On les fait pointer vers les nouveaux
     noms plutôt que de les renommer dans dix fichiers : la substitution des
     variables CSS se fait à l'usage, ces alias suivent donc automatiquement le
     thème clair ou sombre.
     `--accent` garde la valeur verte de la marque — c'est ce que ces styles en
     ligne attendaient avant la refonte. Il pointe sur --brand-ink (et non sur
     --brand) parce que ces styles en ligne l'utilisent presque toujours en
     `color:` : on ne peut pas modifier ces vues, mais l'alias leur donne
     d'office le vert lisible en texte sans changer une seule d'entre elles.
     ------------------------------------------------------------------------ */
  --accent: var(--brand-ink);
  --accent-hover: var(--brand-strong);
  --accent-strong: var(--brand-strong);
  --accent-dim: var(--brand-soft);
  --accent-line: var(--brand-soft);
  --text: var(--ink);
  --text-muted: var(--muted);
  --text-faint: var(--faint);
  --border: var(--line);
  --border-soft: var(--line);
  --bg-elev: var(--surface);
  --bg-card: var(--surface);
  --bg-2: var(--surface-2);
  --blue: var(--info);
  --radius: var(--r);
  --radius-sm: var(--r-sm);
  --radius-lg: var(--r-lg);
  --shadow: var(--shadow-1);
  --row-hover: var(--surface-2);
  --code-bg: var(--surface-inverse);
  --code-border: var(--line);
}

:root[data-theme="dark"] {
  --bg: #0f0f0f;
  --surface: #141414;
  --surface-2: #191919;
  --surface-3: #232323;
  --surface-inverse: #ededed;

  --ink: #ededed;
  --ink-2: #cfcfcf;
  --muted: #adadad;   /* 8,21:1 sur #141414 — déjà au-dessus du seuil */
  --faint: #8f8f8f;   /* 4,86:1 même sur surface-3 #232323 (avant : 3,66:1) */
  --on-inverse: #141414;

  --line: rgba(237, 237, 237, 0.10);
  --line-2: rgba(237, 237, 237, 0.20);

  --brand: #37b781;
  --brand-strong: #2f9e6f;
  /* Même rôle qu'en clair : le vert porteur de texte, éclairci cette fois,
     pour garder 4,5:1 sur les surfaces sombres et sur --brand-soft déjà mêlé
     à #141414 (6,99:1). */
  --brand-ink: #4fca95;
  --brand-soft: rgba(55, 183, 129, 0.16);

  /* Teintes de « non lue » : plus présentes en thème sombre, où un fond clair
     translucide se remarque moins qu'en thème clair. */
  --notif-unread: rgba(55, 183, 129, 0.22);
  --notif-unread-hover: rgba(55, 183, 129, 0.32);
  --highlight: #ff6a2b;
  --highlight-soft: rgba(255, 106, 43, 0.16);
  --highlight-ink: #1a1200;   /* 6,50:1 sur l'orange sombre */
  --link: #4d94ff;
  /* En sombre, les encres sémantiques claires passent déjà toutes le seuil
     sur leur lavis : inutile de les toucher, on garde l'identité. */
  --success: #4fbf24;
  --success-soft: rgba(79, 191, 36, 0.16);
  --warning: #e0a13c;
  --warning-soft: rgba(224, 161, 60, 0.16);
  --danger: #ff6a52;
  --danger-soft: rgba(255, 106, 82, 0.14);
  --info: #4d94ff;
  --info-soft: rgba(77, 148, 255, 0.14);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.55), 0 22px 52px rgba(0, 0, 0, 0.5);
}

/* ---------- 2. Base ---------- */
* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  font-feature-settings: "cv11", "ss01";
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
h1 { font-size: 26px; line-height: 1.2; }
h2 { font-size: 19px; }
h3 { font-size: 16px; }

a { color: var(--ink); text-decoration: none; transition: color var(--dur-1) var(--ease), opacity var(--dur-1) var(--ease); }
a:hover { color: var(--ink); opacity: 0.72; }

/* Les liens du corps de texte restent repérables (soulignement au survol). */
p a, .docs-content a { text-decoration: underline; text-decoration-color: var(--line-2); text-underline-offset: 3px; }
p a:hover, .docs-content a:hover { text-decoration-color: currentColor; }

::selection { background: var(--ink); color: var(--surface); }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: var(--r-pill); border: 3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--faint); }

/* Dialogue natif de confirmation (changement de pack). Le voile par défaut du
   navigateur (rgba(0,0,0,.1)) ne détache pas la boîte du contenu : il laisse
   les cartes du dessous aussi nettes qu'elle. Un voile à 55 % + un flou de
   2 px isolent la décision sans masquer que la page est toujours là. Le fond
   du dialogue lui-même reste var(--surface), donc le texte garde son ratio
   (aucun contraste calculé sur le voile). */
dialog::backdrop {
  background: rgba(20, 20, 20, 0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

/* ---------- 3. Coquille : barre supérieure ---------- */
.navbar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 18px;
  height: var(--shell-h);
  padding: 0 22px;
  /* Verre dépoli : la barre reste lisible au-dessus du contenu qui défile.
     La première déclaration sert de repli pour les navigateurs sans color-mix(). */
  background: var(--surface);
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--line);
  animation: hs-drop-in var(--dur-3) var(--ease-out) both;
}

.nav-brand { font-size: 16px; font-weight: 650; letter-spacing: -0.02em; }
.nav-brand a { display: inline-flex; align-items: center; gap: 8px; }
.nav-brand a:hover { opacity: 1; }
/* Le nom porte du texte : il prend l'encre de marque, pas le vert d'aplat. */
.nav-brand span { color: var(--brand-ink); }

/* Marque : petit carré encre, comme un logotype typographique. */
.nav-brand::before {
  content: "";
  display: none;
}

.nav-links { display: flex; align-items: center; gap: 4px; flex: 1; }
.nav-links a {
  padding: 7px 13px;
  border-radius: var(--r-pill);
  font-size: 14px;
  font-weight: 500;
  color: var(--muted);
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.nav-links a:hover { background: var(--surface-3); color: var(--ink); opacity: 1; }
/* Onglet actif = pilule encre pleine (signature Mobbin). */
.nav-links a.active { background: var(--ink); color: var(--on-inverse); }

.nav-user { display: flex; align-items: center; gap: 12px; color: var(--muted); margin-left: auto; }

/* Puce utilisateur + menu déroulant */
.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.user-chip:hover { border-color: var(--line-2); background: var(--surface-2); }
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--on-inverse);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.caret { width: 14px; height: 14px; color: var(--faint); transition: transform var(--dur-2) var(--ease); }
.user-chip[aria-expanded="true"] .caret { transform: rotate(180deg); }

.user-menu { position: relative; }
.user-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 232px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-2);
  opacity: 0;
  transform: translateY(-6px) scale(0.98);
  transform-origin: top right;
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease-out);
}
.user-menu-panel.is-open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
.user-menu-head { padding: 10px 12px 12px; border-bottom: 1px solid var(--line); margin-bottom: 6px; }
.user-menu-head .name { font-weight: 600; font-size: 14px; }
.user-menu-head .mail { color: var(--muted); font-size: 12.5px; word-break: break-all; }
.user-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}
.user-menu-item:hover { background: var(--surface-3); opacity: 1; }
.user-menu-item.danger { color: var(--danger); }
.user-menu-item.danger:hover { background: var(--danger-soft); }
.user-menu-item svg { width: 16px; height: 16px; flex: none; opacity: 0.75; }

/* Bascule de thème */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--r-pill);
  cursor: pointer;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink);
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
.theme-toggle:hover { border-color: var(--line-2); background: var(--surface-2); }
.theme-toggle:active { transform: scale(0.94); }
.theme-toggle svg { width: 17px; height: 17px; transition: transform var(--dur-3) var(--ease-out), opacity var(--dur-2) var(--ease); }
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
.theme-toggle:hover svg { transform: rotate(18deg); }

/* Menu mobile */
.nav-burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.nav-burger svg { width: 18px; height: 18px; }

/* ---------- 4. Coquille : contenu ---------- */
.container { max-width: var(--container); margin: 0 auto; padding: 26px 20px 72px; }

/* Entrée de page : chaque bloc monte de 6 px, en léger décalage (60 ms). */
.container > * { animation: hs-rise var(--dur-3) var(--ease-out) both; }
.container > *:nth-child(2) { animation-delay: 0.05s; }
.container > *:nth-child(3) { animation-delay: 0.1s; }
.container > *:nth-child(4) { animation-delay: 0.15s; }
.container > *:nth-child(5) { animation-delay: 0.2s; }
.container > *:nth-child(n + 6) { animation-delay: 0.24s; }

.page-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 20px; }
.page-head h1 { font-size: 26px; }
.page-head .sub { color: var(--muted); font-size: 13.5px; margin-top: 3px; }
.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.section-label {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}

/* Pied de page discret (les pages de l'application sont denses : il rappelle
   l'existence de la documentation et de la version). */
.shell-footer {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 20px 30px;
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  align-items: center;
  color: var(--faint);
  font-size: 12.5px;
}
.shell-footer a { color: var(--faint); }
.shell-footer a:hover { color: var(--ink); }
/* Le séparateur « · » est décoratif, mais il reste du texte lu par l'œil :
   à opacity 0.5, --faint tombait à 2,07:1 en clair et 2,34:1 en sombre.
   On garde le retrait visuel par la couleur (--faint, déjà ≥ 4,5:1) et non
   par une transparence qui casse le contraste. */
.shell-footer .sep { opacity: 1; }
/* Contacts du pied de page applicatif : mêmes entrées que la vitrine, mais
   alignées sur la densité plus compacte de l'application. */
.shell-footer .contact-links {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.contact-link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--faint);
  text-decoration: none;
  transition: color 0.14s ease;
}
.contact-link:hover { color: var(--ink); }
.contact-link .ci { width: 15px; height: 15px; flex: none; transition: color 0.14s ease; }
.contact-link:hover .ci { color: var(--brand); }

/* Onglets de page : barre de sections d'un même écran.
   Le tableau de bord en ouvre deux — la vue d'ensemble et les portails captifs
   achetés. Chaque onglet est un LIEN (l'onglet vit dans l'adresse, voir
   dashboard/index.blade.php) : même langage visuel que la navigation, pilule
   et actif en encre pleine, mais sans dépendre de JavaScript. */
.tabs {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  background: transparent;
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 500;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.tab:hover { background: var(--surface-3); color: var(--ink); opacity: 1; }
.tab.active { background: var(--ink); border-color: var(--ink); color: var(--on-inverse); font-weight: 550; }
/* Compteur de l'onglet : le nombre d'éléments qu'il liste. */
.tab-count {
  min-width: 20px;
  padding: 1px 7px;
  text-align: center;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tab.active .tab-count { background: var(--on-inverse); color: var(--ink); }

/* ---------- 5. Cartes, tuiles, grilles ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 20px;
  margin-bottom: 16px;
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.card:hover { border-color: var(--line-2); }
.card h3 { margin: 0 0 14px; font-size: 15.5px; }
.card > .table-wrap:last-child, .card > .empty:last-child { margin-bottom: -4px; }

.grid { display: grid; gap: 14px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.grid.cols-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.grid.cols-4 { grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }

/* Rythme vertical : .card ne porte qu'une marge BASSE (deux blocs voisins se
   suivent donc à 16 px), mais une grille de tuiles n'en porte aucune — sans la
   règle ci-dessous, la carte qui suit une rangée de tuiles vient coller au
   bord des tuiles (défaut visible en tête des tableaux de bord). La règle est
   écrite en sélecteur de voisinage pour ne pas ajouter de marge aux grilles
   imbriquées dans une carte, qui portent la leur. */
.grid + .card,
.grid + .grid { margin-top: 16px; }

.tile,
.device-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 18px;
  transition: transform var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.tile:hover,
.device-card:hover { transform: translateY(-2px); border-color: var(--line-2); box-shadow: var(--shadow-2); }

/* Tuile de tableau de bord : une pastille d'icône, la valeur, son libellé.
   La tuile est un empilement vertical — sans `flex`, la pastille (élément en
   ligne) laisserait un blanc sous elle et les valeurs ne s'aligneraient plus
   d'une tuile à l'autre de la même rangée. */
.tile {
  display: flex;
  flex-direction: column;
  padding: 16px 18px 17px;
}

/* Pastille d'icône : fond de surface neutre et trait d'encre — la couleur
   reste réservée aux états (voir l'en-tête du fichier), seul l'écran qui met
   une tuile en avant la teinte. */
.tile-icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  margin-bottom: 13px;
  border: 1px solid var(--line);
  border-radius: 11px;
  background: var(--surface-3);
  color: var(--ink-2);
  transition: color var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.tile-icon svg { width: 17px; height: 17px; }
.tile:hover .tile-icon { color: var(--ink); border-color: var(--line-2); }

.tile .kpi {
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* Unité (%, MiB, FCFA) : sortie du chiffre par `Mikrotik::formatBytesParts()`
   pour être rendue à l'échelle du texte et en gris. Collée au grand nombre,
   « 118.8MiB » brouillait la magnitude du chiffre. */
.tile .kpi .unit,
.tile .value .unit,
.tile .duree .unit {
  margin-left: 3px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--muted);
}
/* Durée découpée (uptime) : elle se lit d'un coup d'œil et sans les secondes.
   À la taille du chiffre clé, « 3 j 02 h 45 min » déborderait de la tuile ; à
   cette taille, les quatre unités restent alignées. */
.tile .duree {
  font-size: 21px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.25;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.tile .duree .segment + .segment { margin-left: 7px; }
/* Dans une durée, l'unité sert de séparateur : un cran plus discrète que
   l'unité d'un octet, qui qualifie le nombre entier. */
.tile .duree .unit { font-size: 13px; margin-left: 2px; }
.tile .label { color: var(--muted); font-size: 12.5px; margin-top: 5px; }
/* Valeur non numérique d'une tuile (nom de pack, date d'échéance, libellé) :
   même emplacement que le chiffre clé, mais à l'échelle du texte. */
.tile .value { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
.tile .hint { font-size: 12px; color: var(--faint); margin-top: 6px; }
/* Tuile « en avant » : un liseré de marque, utilisé une seule fois par écran. */
.tile.accent { border-color: var(--brand-soft); background: linear-gradient(180deg, var(--brand-soft), transparent 60%), var(--surface); }
.tile.accent .kpi { color: var(--brand-ink); }
.tile.accent .tile-icon { color: var(--brand); border-color: transparent; background: var(--brand-soft); }

/* Tuiles de « Votre abonnement actuel » : trois valeurs vivent DANS une carte,
   elles ne portent donc aucune surface propre. La charte interdit une carte
   dans une carte : `.sub-tuiles` retire fond, trait, rayon et relief du gabarit
   partagé, et ne garde que la pastille, la valeur et les filets de séparation.
   Les encres sont celles du produit — aucune couleur nouvelle. */
.sub-tuiles > .grid {
  gap: 0;
  border-top: 1px solid var(--line);
}
.sub-tuiles .tile {
  padding: 14px 18px;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--line);
  border-radius: 0;
  box-shadow: none;
}
.sub-tuiles .tile:first-child { padding-left: 0; border-left: 0; }
.sub-tuiles .tile:hover { transform: none; box-shadow: none; }
/* Sur un téléphone la bande passe sur une colonne : le filet vertical devient
   horizontal, sinon deux filets de part et d'autre d'une ligne pleine largeur
   n'auraient plus de sens. */
@media (max-width: 640px) {
  .sub-tuiles .grid { grid-template-columns: 1fr; }
  .sub-tuiles .tile { padding: 12px 0; border-left: 0; border-top: 1px solid var(--line); }
  .sub-tuiles .tile:first-child { padding-top: 14px; border-top: 0; }
}

.device-card { box-shadow: var(--shadow-1); }
.device-card .name { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.device-card .host { color: var(--muted); font-size: 13px; margin: 4px 0 12px; font-family: var(--font-mono); }
.device-card .actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* Cartes d'abonnement (Tarifs / Mon abonnement) */
.plan-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: transform var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.plan-card:hover { transform: translateY(-2px); border-color: var(--line-2); box-shadow: var(--shadow-2); }
.plan-card.is-current { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand-soft); }
.plan-badge {
  align-self: flex-start;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  background: var(--brand-soft);
  color: var(--brand-ink);
  font-size: 12px;
  font-weight: 600;
}
.plan-name { font-size: 16px; font-weight: 600; }
.plan-price { font-size: 24px; font-weight: 650; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.plan-price .unit { font-size: 13px; font-weight: 500; color: var(--muted); letter-spacing: 0; }
.plan-limit { font-size: 13px; color: var(--muted); }
.plan-desc { font-size: 13.5px; color: var(--muted); }
.plan-features { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; font-size: 13.5px; color: var(--ink-2); }
.plan-features li { display: flex; gap: 8px; align-items: flex-start; }
.plan-features li::before {
  content: "";
  flex: none;
  width: 15px;
  height: 15px;
  margin-top: 2px;
  border-radius: 50%;
  background: var(--brand-soft) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%232f9e6f' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center / 10px no-repeat;
}

/* Appel à l'action / encart d'information */
.callout {
  border: 1px dashed var(--line-2);
  border-radius: var(--r);
  padding: 16px 18px;
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 13.5px;
}

/* Carte d'un portail captif acquis (onglet du tableau de bord) : elle reprend
   la carte de routeur, avec l'aperçu du modèle et la date d'acquisition. */
.portal-owned .name { display: flex; align-items: center; gap: 9px; }
.portal-swatch {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 7px;
  border: 1px solid var(--line);
}
.portal-owned .owned-date { font-size: 12.5px; margin: 2px 0 12px; }
.portal-owned .owned-note { font-size: 12.5px; margin: 10px 0 0; }

/* ---------- 6. Boutons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 8px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.btn:hover { background: var(--surface-3); border-color: var(--line-2); color: var(--ink); opacity: 1; }
.btn:active { transform: scale(0.98); }

/* Action principale : pilule encre (l'action unique d'un écran). */
.btn.primary { background: var(--ink); border-color: var(--ink); color: var(--on-inverse); font-weight: 550; }
.btn.primary:hover { background: var(--ink-2); border-color: var(--ink-2); box-shadow: 0 6px 18px rgba(20, 20, 20, 0.18); }

.btn.danger { background: transparent; border-color: var(--danger-soft); color: var(--danger); }
.btn.danger:hover { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }

.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn.ghost:hover { background: var(--surface-3); color: var(--ink); }

.btn.sm { padding: 5px 11px; font-size: 12.5px; }

/* Action réduite à son pictogramme (liste des codes) : carré, cible tactile
   confortable, libellé complet porté par `title` et `aria-label`. */
.btn.btn-icon { width: 34px; height: 34px; padding: 0; gap: 0; }
.btn.btn-icon svg { width: 16px; height: 16px; }
.btn.block { width: 100%; }
.btn[disabled], .btn.is-loading { opacity: 0.6; pointer-events: none; }

/* État de chargement : le libellé s'efface derrière une barre indéterminée. */
.btn.is-loading::after {
  content: "";
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: hs-spin 0.7s linear infinite;
}

/* ---------- 7. Formulaires ---------- */
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 6px; font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
.form-group .help, .help { font-size: 12px; color: var(--muted); margin-top: 5px; }

/* Erreur d'un champ : même taille et même rythme que le texte d'aide, mais
   dans l'encre sémantique. Trois classes (0,3,0) l'emportent sur
   `.form-group .help` (0,2,0) quel que soit l'ordre de la feuille : le style
   en ligne qui contournait cette spécificité n'a plus lieu d'être. */
.form-group .help.champ-erreur { color: var(--danger); }

/* Rangée d'un champ à bascule (mot de passe) : l'input garde sa largeur, le
   bouton « Afficher » se cale à droite. */
.champ-mdp { display: flex; align-items: center; gap: 8px; }

/* Le bouton de bascule naît avec l'attribut `hidden`. `.btn` impose
   display:inline-flex (0,1,0) et l'emporterait sur le `[hidden]` de l'agent
   utilisateur : cette règle plus spécifique (0,3,0) rend `hidden` réellement
   masquant. Sans JavaScript, un bouton qui ne fait rien vaut moins que pas de
   bouton. */
.btn.bascule-mdp[hidden] { display: none; }

input, select, textarea {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  transition: border-color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input:hover, select:hover, textarea:hover { border-color: var(--line-2); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: 0 0 0 3px var(--surface-3);
}
input[type="checkbox"], input[type="radio"] {
  width: 17px;
  height: 17px;
  padding: 0;
  accent-color: var(--ink);
  vertical-align: -3px;
  cursor: pointer;
}
select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23767676' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 15px;
  padding-right: 34px;
}
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.inline-form { display: inline; }

/* ---------- 7 bis. Champs de fichier ---------- */
/* Le bouton natif « Choisir un fichier » est gris et hors charte : on garde
   l'input (il porte le nom du champ, donc l'envoi du formulaire) et on
   l'habille. La zone en pointillés appelle le dépôt ; le bouton reprend les
   jetons du thème (--ink sur --surface), et comme ces jetons s'inversent d'eux-
   mêmes, une seule règle tient dans les deux thèmes. */
input[type="file"] {
  display: block;
  padding: 8px;
  border: 1px dashed var(--line-2);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}
input[type="file"]:hover { border-color: var(--ink); background: var(--surface-3); }
input[type="file"]:focus-visible {
  outline: none;
  border-color: var(--ink);
  box-shadow: 0 0 0 3px var(--surface-3);
}
input[type="file"]::file-selector-button,
input[type="file"]::-webkit-file-upload-button {
  margin: 0 12px 0 0;
  padding: 8px 14px;
  border: 0;
  border-radius: var(--r-sm);
  background: var(--ink);
  color: var(--surface);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: opacity var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
input[type="file"]:hover::file-selector-button,
input[type="file"]:hover::-webkit-file-upload-button { opacity: .88; }
input[type="file"]:active::file-selector-button,
input[type="file"]:active::-webkit-file-upload-button { transform: translateY(1px); }

/* Un champ de capture et son bouton « Retirer », sur une ligne : l'input prend
   la place qui reste, le bouton ne se comprime pas quand la fenêtre rétrécit. */
.ligne-fichier { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
/* L'étiquette « Écran N » garde une largeur fixe : les champs de la colonne
   commencent tous au même x, et on lit d'un coup d'œil quel écran on change. */
.ligne-fichier [data-screen-label] { flex: 0 0 72px; white-space: nowrap; color: var(--muted); font-size: 12px; }
.ligne-fichier .champ-fichier { flex: 1; min-width: 0; }
.ligne-fichier .btn { flex: 0 0 auto; }
#screensAddButton { margin-top: 2px; }

/* Rappel du navigateur : un élément marqué [hidden] ne s'affiche pas. La règle
   est ici parce que .btn { display: inline-flex } (section 6) l'emportait sur
   l'attribut — un bouton masqué restait visible. Ce n'est pas propre aux champs
   de fichier : tout [hidden] du site en profite. */
[hidden] { display: none; }

/* ---------- 8. Données : tableaux, badges, compteurs ---------- */
/* Le conteneur défile horizontalement (les tableaux ont 8 à 9 colonnes et
   débordent la carte : 1205 px nécessaires pour 1158 px disponibles sur la
   liste des utilisateurs). Un conteneur qui défile devient le référent du
   collage de ses enfants : l'ancien `top: var(--shell-h)` (62 px, la hauteur de
   la barre supérieure) décalait donc l'en-tête de 62 px vers le bas EN
   PERMANENCE, par-dessus la première ligne du tableau — le chevauchement
   corrigé ici, mesuré sur toutes les listes de l'application.
   L'en-tête suit désormais le tableau. Pour le figer malgré tout, il faut
   borner la hauteur du conteneur (`max-height`) et remettre `top: 0` : dans ce
   cas le collage s'applique à l'intérieur du conteneur, sans décalage. */
.table-wrap { overflow-x: auto; border-radius: var(--r-sm); }
table.table { width: 100%; border-collapse: collapse; font-size: 14px; }
table.table th {
  padding: 9px 12px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  color: var(--faint);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-align: left;
}
table.table td {
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  vertical-align: middle;
}
table.table tbody tr { transition: background var(--dur-1) var(--ease); animation: hs-rise var(--dur-2) var(--ease-out) both; }
/* Les lignes se posent en cascade, sans dépasser 12 rangs (au-delà, l'effet
   retarderait l'affichage au lieu de l'accompagner). */
table.table tbody tr:nth-child(1) { animation-delay: 0.02s; }
table.table tbody tr:nth-child(2) { animation-delay: 0.04s; }
table.table tbody tr:nth-child(3) { animation-delay: 0.06s; }
table.table tbody tr:nth-child(4) { animation-delay: 0.08s; }
table.table tbody tr:nth-child(5) { animation-delay: 0.1s; }
table.table tbody tr:nth-child(6) { animation-delay: 0.12s; }
table.table tbody tr:nth-child(7) { animation-delay: 0.14s; }
table.table tbody tr:nth-child(8) { animation-delay: 0.16s; }
table.table tbody tr:nth-child(9) { animation-delay: 0.18s; }
table.table tbody tr:nth-child(10) { animation-delay: 0.2s; }
table.table tbody tr:nth-child(11) { animation-delay: 0.22s; }
table.table tbody tr:nth-child(n + 12) { animation-delay: 0.24s; }
table.table tbody tr:hover td { background: var(--surface-2); color: var(--ink); }
table.table tbody tr:last-child td { border-bottom: 0; }
table.table td.mono, .mono { font-family: var(--font-mono); font-size: 13px; font-variant-numeric: tabular-nums; }
table.table td .actions, table.table .actions { display: flex; gap: 6px; align-items: center; justify-content: flex-end; flex-wrap: wrap; }

/* Liste des codes hotspot : elle porte des dates et des durées qui doivent
   tenir sur UNE ligne. La page prend toute la largeur utile (le `:has` élargit
   le conteneur commun) et les cellules ne se replient plus ; sur écran étroit,
   le `table-wrap` garde son défilement horizontal.
   La règle du shell (`.shell .container`) est plus spécifique que `.container`
   seule : on la reprend ici, sinon la page restait bornée à 1280 px. */
.container:has(.table--large) { max-width: none; }
.shell .container:has(.table--large) { max-width: none; padding-left: 10px; padding-right: 10px; }
.card:has(.table--large) { padding: 10px; }
table.table--large th, table.table--large td { white-space: nowrap; padding-left: 4px; padding-right: 4px; }
/* La colonne d'actions garde ses trois pictogrammes sur UNE ligne : sans
   `width: 1%`, l'algorithme du tableau la comprimait et `flex-wrap` les
   empilait, ce qui doublait la hauteur des lignes. */
table.table--large th:last-child, table.table--large td:last-child { width: 1%; white-space: nowrap; }
/* Même spécificité que `table.table td .actions` (deux éléments, deux
   classes) : sinon son `flex-wrap: wrap` l'emportait et les icônes
   s'empilaient. */
table.table--large td .actions { flex-wrap: nowrap; gap: 4px; }

.empty { text-align: center; color: var(--muted); padding: 40px 14px; }
.empty p { margin: 0 0 6px; }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.badge.online { background: var(--success-soft); color: var(--success); }
.badge.offline { background: var(--danger-soft); color: var(--danger); }
.badge.unknown { background: var(--surface-3); color: var(--muted); }

/* États d'un code : jamais utilisé (neutre) et expiré/archivé (avertissement). */
.badge.inactive { background: var(--surface-3); color: var(--muted); }
.badge.expired { background: var(--warning-soft); color: var(--warning); }
/* Pastille « Vendeur » : bleu d'information, distinct du vert des comptes
   administrateurs et du neutre des propriétaires. `--info` et `--info-soft`
   sont définis dans les deux thèmes : la pastille suit le thème sans règle
   supplémentaire. */
.badge.vendeur { background: var(--info-soft); color: var(--info); }

.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; }
.dot.green { background: var(--success); box-shadow: 0 0 0 3px var(--success-soft); }
.dot.red { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-soft); }
.dot.grey { background: var(--faint); }
/* Pastille d'activité : respiration lente, uniquement sur l'état « en ligne ». */
.dot.green { animation: hs-breathe 2.6s var(--ease) infinite; }

.count-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--r-pill);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
}
.count-badge strong { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }

.v-idx {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  padding: 0 7px;
  border-radius: var(--r-pill);
  background: var(--surface-3);
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-bottom: 6px;
}

.pagination-bar { display: flex; align-items: center; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.pagination-bar .muted { margin: 0 auto; }

/* Espace entre le compteur et la carte qu'il coiffe : le compteur est dans la
   zone de résultats (il change avec le filtre), la carte suit dessous. */
.resultats-entete { margin-bottom: 12px; }

/* Zone de résultats en cours de rafraîchissement : on la ternit plutôt que de
   la vider — le tableau précédent reste lisible pendant la requête. */
[data-resultats].is-chargement { opacity: 0.55; }

/* ---------- 9. Retours : alertes, toasts, chargement ---------- */
.alert {
  display: flex;
  gap: 10px;
  padding: 12px 15px;
  margin-bottom: 14px;
  /* Bordure uniforme d'1 px : le liseré de 3 px à gauche était un tic visuel
     (interdit par la charte de finition) et déstabilisait le rythme des
     cartes. Le sens de l'alerte se lit à la TEINTE du fond ET du trait. */
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-size: 13.5px;
  animation: hs-rise var(--dur-2) var(--ease-out) both;
}
/* Quatre côtés, même teinte sémantique, épaisseur 1 px. Le texte reste sur le
   lavis correspondant : ratios mesurés en clair 5,58 / 4,78 / 5,13 / 5,40 et
   en sombre 5,99 / 5,40 / 5,08 / 6,17 — tous ≥ 4,5:1. */
.alert-success { background: var(--success-soft); border-color: var(--success); color: var(--success); }
.alert-danger, .alert.error { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
.alert-info { background: var(--info-soft); border-color: var(--info); color: var(--info); }
.alert-warning { background: var(--warning-soft); border-color: var(--warning); color: var(--warning); }

.spinner { color: var(--muted); font-size: 13px; }

.toast-root {
  position: fixed;
  top: calc(var(--shell-h) + 12px);
  right: 18px;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(380px, calc(100vw - 36px));
}
.toast {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--ink);
  font-size: 13.5px;
  line-height: 1.45;
  box-shadow: var(--shadow-2);
  animation: hs-toast-in var(--dur-3) var(--ease-out) both;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}
.toast::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--muted);
}
.toast.toast-success::before { background: var(--success); }
.toast.toast-error::before { background: var(--danger); }
.toast.toast-info::before { background: var(--info); }
.toast.toast-hide { opacity: 0; transform: translateX(24px) scale(0.98); }
/* Barre de vie : indique le temps restant avant fermeture automatique. */
.toast-progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  transform-origin: left;
  background: var(--muted);
  opacity: 0.3;
  animation: hs-toast-life var(--toast-life, 4.5s) linear forwards;
}
.toast.toast-success .toast-progress { background: var(--success); opacity: 1; }
.toast.toast-error .toast-progress { background: var(--danger); opacity: 1; }
.toast.toast-info .toast-progress { background: var(--info); opacity: 1; }
.toast-close {
  margin-left: auto;
  flex: none;
  padding: 0 2px;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  border-radius: var(--r-xs);
  transition: color var(--dur-1) var(--ease);
}
.toast-close:hover { color: var(--ink); }

/* Squelette de chargement (listes et mesures AJAX). */
.skeleton {
  background: linear-gradient(90deg, var(--surface-3) 25%, var(--surface-2) 37%, var(--surface-3) 63%);
  background-size: 400% 100%;
  border-radius: var(--r-sm);
  animation: hs-shimmer 1.4s var(--ease) infinite;
  min-height: 14px;
}

/* ---------- 10. Écrans d'authentification ---------- */
.auth-wrap {
  /* Les écrans d'authentification occupent la hauteur restante sous la barre
     supérieure, et non 100vh (le conteneur les accueille déjà). */
  min-height: calc(100vh - var(--shell-h) - 40px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 26px 18px;
  /* Décor discret : deux halos très légers, pour éviter la page blanche nue. */
  background:
    radial-gradient(60% 50% at 50% 0%, var(--brand-soft), transparent 70%),
    var(--bg);
}
/* Le conteneur retire son rembourrage quand il encadre un écran plein. */
.container:has(> .auth-wrap) { padding-top: 0; padding-bottom: 0; }
.auth-box { width: 100%; max-width: 400px; animation: hs-rise var(--dur-3) var(--ease-out) both; }
.auth-box .brand { text-align: center; margin-bottom: 20px; font-size: 21px; font-weight: 650; letter-spacing: -0.02em; }
.auth-box .brand span { color: var(--brand-ink); }
.auth-box .alt { text-align: center; margin-top: 16px; color: var(--muted); font-size: 13.5px; }
.auth-box .card { padding: 24px; }
/* Bascule connexion / inscription : une piste creuse porte deux onglets-liens,
   l'actif posé sur la surface de la carte. Elle reste au-dessus de la carte
   pour que le choix se lise avant le formulaire, et elle n'ajoute aucun
   JavaScript : ce sont deux vraies destinations. */
.auth-onglets {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px;
  padding: 3px;
  margin-bottom: 14px;
  background: var(--surface-3);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
}
.auth-onglet {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 38px;
  padding: 8px 12px;
  border-radius: calc(var(--r-pill) - 3px);
  color: var(--muted);
  font-size: 13.5px;
  font-weight: 550;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.auth-onglet:hover { color: var(--ink); }
.auth-onglet.est-actif {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}
/* En sombre, l'ordre s'inverse : la piste doit être la plus creuse et l'onglet
   actif le plus clair. Sans ce renversement, l'onglet actif paraissait en
   retrait alors qu'il est la page courante. */
:root[data-theme="dark"] .auth-onglets { background: var(--surface); }
:root[data-theme="dark"] .auth-onglet.est-actif {
  background: var(--surface-3);
  box-shadow: none;
}

/* ---------- 11. Documentation ---------- */
.docs-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 32px; align-items: start; }
.docs-sidebar { position: sticky; top: calc(var(--shell-h) + 20px); display: grid; gap: 3px; font-size: 13.5px; }
.docs-sidebar a { padding: 7px 11px; border-radius: var(--r-sm); color: var(--muted); }
.docs-sidebar a:hover { background: var(--surface-3); color: var(--ink); opacity: 1; }
.docs-content { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r); padding: 28px; }
.docs-content h2 { margin: 26px 0 10px; font-size: 18px; }
.docs-content h2:first-child { margin-top: 0; }
.docs-content h3 { margin: 18px 0 8px; font-size: 15px; }
.docs-content p, .docs-content li { color: var(--ink-2); font-size: 14px; }
.docs-content ul, .docs-content ol { padding-left: 20px; }
.docs-hero { padding: 44px 0 20px; }

/* Extraits d'API / codes : fond encre, comme un terminal. */
.api-snippet, .code {
  background: var(--surface-inverse);
  color: var(--on-inverse);
  border-radius: var(--r-sm);
  padding: 13px 15px;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.65;
  overflow-x: auto;
}

/* ---------- 12. Vouchers (écran + impression) ---------- */
.vouchers { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.voucher {
  background: #fff;
  color: #111;
  border: 1px solid #e5e5e5;
  border-radius: var(--r-sm);
  padding: 16px;
  text-align: center;
  page-break-inside: avoid;
}
.voucher .v-title { font-size: 13.5px; font-weight: 700; margin-bottom: 8px; }
.voucher .v-user { font-family: var(--font-mono); font-size: 15px; font-weight: 700; letter-spacing: 0.04em; word-break: break-all; }
.voucher .v-pass { font-family: var(--font-mono); font-size: 18px; font-weight: 700; letter-spacing: 0.06em; margin: 6px 0 10px; word-break: break-all; }
/* La fiche voucher est TOUJOURS blanche, même en thème sombre (elle est
   imprimée) : son gris reste donc fixe, et assez sombre pour le papier. */
.voucher .v-meta { font-size: 12px; color: #555; }
.voucher svg { max-width: 120px; height: auto; }

/* ---------- 13. Utilitaires ---------- */
.muted { color: var(--muted); }
.flex { display: flex; gap: 8px; align-items: center; }
.spread { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
.mt-2 { margin-top: 10px; }
.mt-4 { margin-top: 20px; }
.mb-0 { margin-bottom: 0; }
.text-danger { color: var(--danger); }
.text-green { color: var(--brand-ink); }
.wrap { max-width: var(--container); margin: 0 auto; padding: 0 20px; }

/* ---------- 14. Animations ---------- */
@keyframes hs-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes hs-drop-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@keyframes hs-toast-in { from { opacity: 0; transform: translateX(24px) scale(0.98); } to { opacity: 1; transform: none; } }
@keyframes hs-toast-life { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@keyframes hs-pop { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: none; } }
@keyframes hs-spin { to { transform: rotate(360deg); } }
@keyframes hs-shimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
@keyframes hs-breathe {
  0%, 100% { box-shadow: 0 0 0 3px var(--success-soft); }
  50% { box-shadow: 0 0 0 6px transparent; }
}

/* Apparition au défilement : .is-visible est posé par app.js (IntersectionObserver). */
.reveal { opacity: 0; transform: translateY(10px); }
.reveal.is-visible { animation: hs-rise var(--dur-3) var(--ease-out) both; }

/* Bascule de thème avec l'API View Transitions (navigateurs récents) :
   le nouveau thème est révélé en cercle depuis le bouton. */
::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
::view-transition-new(root) { animation: hs-theme-reveal 0.45s var(--ease-out); }
@keyframes hs-theme-reveal { from { clip-path: circle(0% at var(--theme-x, 92%) var(--theme-y, 5%)); } to { clip-path: circle(140% at var(--theme-x, 92%) var(--theme-y, 5%)); } }

/* L'utilisateur qui a demandé moins d'animations n'en reçoit aucune. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
  html { scroll-behavior: auto; }
}

/* ---------- 15. Adaptations mobile ---------- */
@media (max-width: 860px) {
  .docs-layout { grid-template-columns: 1fr; }
  .docs-sidebar { position: static; display: flex; gap: 6px; overflow-x: auto; padding-bottom: 4px; }
}

@media (max-width: 720px) {
  .navbar { gap: 10px; padding: 0 14px; }
  .nav-burger { display: inline-flex; }
  /* La navigation passe en tiroir : la barre ne doit garder que l'essentiel. */
  .nav-links {
    position: fixed;
    inset: var(--shell-h) 0 auto;
    z-index: 55;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 12px 14px 16px;
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow-2);
    opacity: 0;
    transform: translateY(-8px);
    pointer-events: none;
    transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease-out);
  }
  body.nav-open .nav-links { opacity: 1; transform: none; pointer-events: auto; }
  .nav-links a { padding: 11px 13px; border-radius: var(--r-sm); }
  .nav-user > span, .user-chip .user-name { display: none; }
  .container { padding: 20px 14px 60px; }
  .page-head h1 { font-size: 22px; }
  .tile .kpi { font-size: 26px; }
  .tile-icon { width: 32px; height: 32px; margin-bottom: 11px; }
  .tile .duree { font-size: 19px; }
  .form-row { grid-template-columns: 1fr; }
  .toast-root { right: 12px; left: 12px; width: auto; }
}

/* ---------- 16. Notifications (cloche + page) ---------- */

/* La cloche vit dans la barre supérieure, à côté du menu du compte. Le bouton
   reprend la forme des autres commandes de la barre (pilule, trait fin). */
.notif { position: relative; }
.notif-button {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: var(--r-pill); cursor: pointer;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.notif-button:hover { border-color: var(--line-2); background: var(--surface-2); }
.notif-button svg { width: 17px; height: 17px; }

/* Le compteur : présent uniquement s'il y a des non lues. */
.notif-count {
  position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: var(--r-pill);
  background: var(--highlight); color: var(--highlight-ink);
  font-size: 12px; font-weight: 650; line-height: 18px; text-align: center;
  font-variant-numeric: tabular-nums;
  border: 2px solid var(--surface);
}

/* Panneau : mêmes conventions que le menu du compte (surface, trait fin,
   ombre douce), ouvert par app.js. Invisible sans JavaScript. */
.notif-panel {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 70;
  width: 380px; max-width: calc(100vw - 28px);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow-2); padding: 6px;
  opacity: 0; transform: translateY(-6px) scale(.98); pointer-events: none;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease-out);
}
.notif-panel.is-open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }

.notif-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 11px 11px; border-bottom: 1px solid var(--line); margin-bottom: 6px;
}
.notif-head-titre { font-weight: 600; font-size: 14px; }
.notif-lien {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--link); font: inherit; font-size: 12.5px; font-weight: 550;
}
.notif-lien:hover { text-decoration: underline; }

.notif-item {
  display: flex; gap: 10px; padding: 10px 11px; border-radius: var(--r-sm);
  transition: background var(--dur-1) var(--ease);
}
.notif-item:hover { background: var(--surface-3); opacity: 1; }

/* Non lue dans le panneau de la cloche : le même signal que dans la page.
   La classe `is-unread` y était posée depuis le début sans qu'aucune règle ne
   la prenne : une notification non lue et une notification lue s'affichaient
   à l'identique sous la cloche, seul le compteur en haut donnait l'ordre de
   grandeur. */
.notif-item.is-unread {
  background: var(--notif-unread);
  box-shadow: inset 3px 0 0 var(--brand);
}
.notif-item.is-unread:hover { background: var(--notif-unread-hover); }
.notif-item.is-unread .notif-titre { color: var(--ink); }
.notif-item:not(.is-unread) .notif-titre { color: var(--ink-2); font-weight: 550; }
.notif-corps { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.notif-titre { font-size: 13.5px; font-weight: 600; }
.notif-texte { font-size: 12.5px; color: var(--muted); line-height: 1.45; }
.notif-meta { font-size: 12px; color: var(--faint); }

/* Pastille de genre : la couleur porte le sens, le texte reste en encre. */
.notif-dot {
  flex: none; width: 8px; height: 8px; margin-top: 6px; border-radius: 50%;
  background: var(--faint);
}
.notif-dot.genre-attente { background: var(--warning); }
.notif-dot.genre-succes { background: var(--success); }
.notif-dot.genre-echec { background: var(--danger); }
.notif-dot.genre-info { background: var(--info); }

.notif-vide { padding: 18px 12px; text-align: center; color: var(--muted); font-size: 13px; }
.notif-foot {
  display: block; margin-top: 4px; padding: 10px; text-align: center;
  border-top: 1px solid var(--line); color: var(--muted);
  font-size: 12.5px; font-weight: 550;
}
.notif-foot:hover { color: var(--ink); opacity: 1; }

/* Page /notifications : une ligne par notification. Les non lues portent un
   fond translucide franc et un liseré de marque à gauche ; les lues restent sur
   la surface, titre allégé. La couleur seule ne suffit pas à porter l'état (œil
   peu sensible aux teintes, écran mal étalonné) : le liseré est un signal de
   forme, et le titre change de graisse. */
.notif-ligne {
  display: flex; gap: 12px; padding: 16px 18px;
  border-bottom: 1px solid var(--line);
  transition: background var(--dur-1) var(--ease);
}
.notif-ligne:last-child { border-bottom: 0; }
.notif-ligne.is-unread {
  background: var(--notif-unread);
  box-shadow: inset 3px 0 0 var(--brand);
}
.notif-ligne.is-unread .notif-titre { color: var(--ink); font-weight: 650; }
.notif-ligne:not(.is-unread) .notif-titre { color: var(--ink-2); font-weight: 550; }
.notif-ligne-corps { min-width: 0; flex: 1; }
.notif-ligne-tete {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}
.notif-ligne .notif-texte { margin: 4px 0 10px; }

/* Entrée « Notifications » du tiroir de navigation : elle n'existe que sur
   écran étroit, où la cloche quitte la barre faute de place. */
.nav-notif-mobile { display: none; }
.nav-notif-mobile svg { width: 16px; height: 16px; }
/* Dans le tiroir, le compteur se pose en bout de ligne au lieu d'être superposé
   au bouton comme sur la cloche. */
.nav-notif-mobile .notif-count {
  position: static; min-width: 17px; height: 17px; line-height: 17px;
  border: 0; margin-left: auto;
}

/* Sous 680 px, la cloche disparaît et l'entrée du tiroir prend le relais :
   sinon la page /notifications n'était joignable qu'en tapant son adresse, et
   personne ne voyait ses notifications depuis un téléphone. */
@media (max-width: 680px) {
  .notif { display: none; }
  .nav-notif-mobile { display: flex; align-items: center; gap: 9px; }
}

/* ---------- 17. Impression (vouchers) ---------- */
/* La planche du comptoir : 4 colonnes sur 5 lignes, soit 20 codes par feuille.
   À l'écran la grille suit la largeur de la fenêtre ; sur papier elle est fixe,
   parce qu'une planche se découpe, se compte et se range : 20 codes par feuille
   se vérifient d'un coup d'œil, un flux continu non.
   Les mesures viennent d'une A4 (297 mm de haut moins 16 mm de marges) : cinq
   lignes de 53 mm tiennent, une sixième passerait à la feuille suivante. */
@page { size: A4; margin: 8mm; }

@media print {
  .navbar, .page-head, .toolbar, .no-print, .shell-footer, .toast-root { display: none !important; }
  body { background: #fff; color: #000; }
  .container { max-width: none; padding: 0; }
  .card { border: 0; box-shadow: none; padding: 0; background: #fff; }
  .container > * { animation: none; }

  .vouchers {
    grid-template-columns: repeat(4, 1fr);
    /* 5 rangées de 51 mm + 4 gouttières de 4 mm = 271 mm, soit 10 mm de marge
       sous la hauteur imprimable d'une A4 (297 mm moins 16 mm de marges). Viser
       l'égalité exacte (53 mm) faisait déborder d'un pixel à l'arrondi : chaque
       planche de 20 codes poussait alors une feuille BLANCHE derrière elle. */
    grid-auto-rows: 51mm;
    gap: 4mm;
  }
  .voucher {
    border: 1px solid #ccc;
    /* Une carte ne se coupe jamais en deux entre deux feuilles. */
    break-inside: avoid;
    /* Le contenu débordait de la carte (64 mm pour 53) : la ligne de profil
       sortait de la feuille. Les tailles ci-dessous le ramènent à ~47 mm, QR
       compris (84 px ≈ 22 mm, largement lisible par un téléphone). */
    overflow: hidden;
    padding: 6px;
    text-align: center;
  }
  /* EXCEPTION DOCUMENTÉE au plancher de 12 px : ces tailles ne s'appliquent
     qu'à la feuille A4 imprimée, pas à un écran. À 96 dpi, 9,5 px ≈ 2,5 mm de
     hauteur de texte sur le papier — lisible à la main, et indispensable pour
     que 20 codes tiennent sur une seule planche sans la couper. Aucune règle
     d'écran du produit ne descend sous 12 px. */
  .voucher .v-title { font-size: 11px; margin-bottom: 2px; }
  .voucher .v-user { font-size: 12.5px; }
  .voucher .v-pass { font-size: 14px; margin: 1px 0 2px; }
  .voucher .v-meta { font-size: 9.5px; }
  /* La coupure de page tombe après chaque 20e code : aucune feuille ne contient
     la moitié d'une planche. */
  .voucher:nth-child(20n) { break-after: page; }
  .voucher svg { max-width: 84px; height: auto; }
}

/* ============================================================================
   17. Coquille « panneau flottant » et tableaux de bord
   ----------------------------------------------------------------------------
   Direction reprise de la maquette dashboard-ui.png : la page devient une toile
   vert lime sur laquelle FLOTTE un panneau applicatif arrondi. Le panneau porte
   la barre du haut (marque, navigation en pilules, compte), puis deux colonnes :
   le rail d'icônes et la zone de contenu, seule à défiler.

   POURQUOI LE PANNEAU DÉFILE LUI-MÊME. La barre du haut est un BORD du panneau :
   si la page défilait, la barre glisserait hors des coins arrondis et laisserait
   apparaître la toile derrière elle. `.shell-scroll` est donc le seul conteneur
   de défilement du bureau ; sous 720 px la coquille redevient pleine page et le
   corps reprend la main, ce qui préserve le tiroir de navigation existant.

   Cette section n'ajoute qu'une couche : elle ne réécrit que ce que la coquille
   impose (fond de page, barre du haut non collante, rail, cartes de mesure).
   Les autres pages gardent leur contenu et héritent du chrome.
   ============================================================================ */

/* ---------- 17.1 Jetons : toile, séries de graphiques, grille ---------- */
:root {
  --canvas: #d8e8b6;
  --canvas-2: #bfdb95;
  --panel-gap: 18px;
  --panel-r: 28px;
  --rail-w: 62px;

  /* Séries des graphiques. Une couleur = une mesure, jamais une décoration :
     l'encre associée est calculée pour rester lisible SUR la pastille pleine
     (blanc sur le vert et le violet, presque noir sur l'ambre). Les teintes
     sont conservées ; seules les valeurs sont assombries assez pour que le
     libellé posé dessus tienne 4,5:1 ET que la barre se détache de la carte
     (3:1). Avant : 2,40:1 pour le blanc sur #7fb800, 2,13:1 pour l'ambre
     sur #fafafa. */
  --chart-1: #557d00;
  --chart-1-ink: #ffffff;
  --chart-1-soft: rgba(85, 125, 0, 0.16);
  --chart-2: #c97c00;
  --chart-2-ink: #2a1a00;
  --chart-2-soft: rgba(201, 124, 0, 0.18);
  --chart-3: #7350ea;
  --chart-3-ink: #ffffff;
  --chart-3-soft: rgba(115, 80, 234, 0.14);
  --chart-4: #2569c7;
  --chart-4-ink: #ffffff;
  --track: rgba(20, 20, 20, 0.16);
  --panel-shadow: 0 26px 70px rgba(28, 45, 5, 0.28), 0 2px 6px rgba(28, 45, 5, 0.14);
}

:root[data-theme="dark"] {
  --canvas: #a3c179;
  --canvas-2: #86a75c;
  --chart-1: #b6e04c;
  --chart-1-ink: #16210a;
  --chart-1-soft: rgba(182, 224, 76, 0.20);
  --chart-2: #f2a03d;
  --chart-2-ink: #241300;
  --chart-2-soft: rgba(242, 160, 61, 0.20);
  --chart-3: #a48cff;
  --chart-3-ink: #150c33;
  --chart-4: #4d94ff;
  --chart-4-ink: #061428;
  --track: rgba(237, 237, 237, 0.18);
  --panel-shadow: 0 30px 80px rgba(15, 26, 0, 0.45), 0 2px 8px rgba(15, 26, 0, 0.30);
}

/* ---------- 17.2 La toile et le panneau ---------- */
body {
  background: var(--canvas);
  background-image: radial-gradient(130% 100% at 8% -10%, var(--canvas-2), transparent 62%);
  background-attachment: fixed;
}

.shell {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  margin: var(--panel-gap);
  height: calc(100vh - (var(--panel-gap) * 2));
  height: calc(100dvh - (var(--panel-gap) * 2));
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--panel-r);
  box-shadow: var(--panel-shadow);
  overflow: hidden;
}

.shell-body { display: grid; grid-template-columns: var(--rail-w) minmax(0, 1fr); min-height: 0; }

/* Un visiteur non connecté (connexion, inscription, mot de passe) n'a ni rail ni
   raccourcis : le panneau se contente d'une colonne. */
.shell--sans-rail .shell-body { grid-template-columns: minmax(0, 1fr); }

/* `min-height: 0` est indispensable : sans lui, la piste de grille refuse de
   rétrécir sous la taille de son contenu et le panneau déborde au lieu de
   défiler. Le contenu est une colonne : une page courte (tableau de bord d'un
   compte sans routeur) garde ainsi son pied de page AU BAS du panneau, au lieu
   de laisser un vide sous lui. */
.shell-scroll { display: flex; flex-direction: column; overflow-y: auto; overflow-x: hidden; min-height: 0; }
/* `flex-grow` SANS `flex-shrink: 0`, plus une largeur EXPLICITE : `.container`
   porte `margin: 0 auto`, et une marge automatique dans l'axe transversal
   annule l'étirement d'un élément flex — sans `width: 100%`, la largeur du
   contenu prend le dessus (un tableau large déborde alors la page au lieu de
   défiler dans son cadre). */
.shell-scroll > .container { width: 100%; flex-grow: 1; min-width: 0; }

/* ---------- 17.3 Rail d'icônes ---------- */
.rail { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 2px 0 16px; }

.rail-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
.rail-btn:hover { background: var(--surface-3); border-color: var(--line-2); color: var(--ink); }
.rail-btn:active { transform: scale(0.95); }
.rail-btn svg { width: 17px; height: 17px; }
/* L'entrée courante est pleine, pas seulement teintée : la couleur seule ne
   suffit pas à porter « vous êtes ici ». */
.rail-btn.is-active { background: var(--ink); border-color: var(--ink); color: var(--on-inverse); }

.rail-espace { flex: 1; }

.rail-add {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--ink);
  border-color: transparent;
  color: var(--on-inverse);
  box-shadow: var(--shadow-1);
}
.rail-add:hover { background: var(--ink-2); border-color: transparent; color: var(--on-inverse); transform: translateY(-1px); }
.rail-add svg { width: 18px; height: 18px; }

/* ---------- 17.4 Barre du haut : marque, pilules, compte ---------- */
.navbar {
  position: static;
  height: auto;
  gap: 14px;
  /* LA BARRE SE REPLIE AU LIEU DE DEBORDER. Sans `flex-wrap`, la largeur de
     contenu de la barre est un PLANCHER pour toute la coquille : un compte qui
     affiche ses six entrées (tableau de bord, routeurs, abonnement, portails,
     documentation, notifications) réclamait 1294 px de fenêtre, mesure faite au
     navigateur. En dessous, le panneau dépassait à droite — et comme
     `.shell-scroll` masque le débordement horizontal, la pastille de compte et
     les boutons d'action étaient COUPES, pas déplacés. `flex-wrap` ne change
     rien tant que la barre tient sur une ligne : la maquette garde sa ligne
     unique sur les écrans larges, et se replie sur deux lignes sur un portable.
     `row-gap` sépare les lignes repliées du `gap` des colonnes. */
  flex-wrap: wrap;
  row-gap: 10px;
  padding: 14px 22px 12px;
  background: transparent;
  border-bottom: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  animation: hs-drop-in var(--dur-3) var(--ease-out) both;
}

/* La marque devient une pastille ronde, comme le logotype de la maquette. */
.nav-brand { display: inline-flex; align-items: center; gap: 10px; font-size: 15px; }
.nav-brand::before {
  content: "H";
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--on-inverse);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0;
}

/* Chaque entrée porte son icône : la pilule se lit sans être lue. */
/* `min-width: 0` : sans lui, la rangée de pilules refuse de descendre sous son
   contenu et impose sa largeur à la barre — le piège `min-width: auto` des
   éléments de grille, déjà corrigé pour les cartes (`.grid > *`). Les pilules
   se replient ensuite d'elles-mêmes. */
.nav-links { flex: 1 1 auto; gap: 8px; flex-wrap: wrap; min-width: 0; }
.nav-links a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--ink-2);
  font-size: 13.5px;
  font-weight: 550;
}
.nav-links a:hover { background: var(--surface-3); border-color: var(--line-2); color: var(--ink); }
.nav-links a.active { background: var(--ink); border-color: var(--ink); color: var(--on-inverse); }
.nav-links a svg { width: 16px; height: 16px; opacity: 0.85; }

/* Puce du compte : avatar à droite, identité à gauche (maquette). Le regard
   entre par le nom, l'avatar ferme la ligne. */
.user-chip { flex-direction: row-reverse; gap: 10px; padding: 4px 4px 4px 12px; background: var(--surface-2); }
.user-chip .caret { order: -1; }
.user-chip .avatar { width: 32px; height: 32px; font-size: 12px; }
.user-chip .user-ident { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.25; min-width: 0; }
.user-chip .user-name { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.user-chip .user-sub { font-size: 12px; color: var(--muted); max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* La barre du haut n'est plus dans la zone qui défile : les colonnes collantes
   des pages n'ont plus à compenser sa hauteur. */
.docs-sidebar { top: 20px; }

/* ---------- 17.5 En-tête de page et pilules de filtre ---------- */
.shell .container { max-width: 1280px; padding: 6px 26px 46px; }

.dash-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
/* Titre en capitales serrées : la maquette fait du titre une enseigne, pas une
   phrase. L'espacement des lettres reste légèrement positif, sinon les
   capitales se collent. */
.dash-title { font-size: 30px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.005em; line-height: 1.05; }
.dash-sub { color: var(--muted); font-size: 13px; margin-top: 7px; }
.dash-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.dash-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 15px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--muted);
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.pill b { color: var(--ink); font-weight: 600; }
.pill:hover { background: var(--surface-3); border-color: var(--line-2); color: var(--ink-2); opacity: 1; }
.pill.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-inverse); }
.pill.is-on b { color: var(--on-inverse); }
.pill .chev { width: 13px; height: 13px; opacity: 0.55; }

/* ---------- 17.6 Cartes de mesure ---------- */
.dash-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; margin-bottom: 14px; }

.metric {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 15px 16px 10px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  min-width: 0;
}
.metric-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.metric-label { font-size: 12px; font-weight: 650; letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted); }
.metric-menu { color: var(--faint); font-weight: 700; letter-spacing: 0.12em; font-size: 15px; line-height: 1; }
.metric-pair { display: flex; align-items: flex-start; gap: 22px; flex-wrap: wrap; }
.metric-val { font-size: 27px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.metric-delta { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 650; color: var(--ink-2); }
/* Le triangle est un SIGNE (le sens), la couleur le renforce : orientation ET
   teinte, pour rester lisible sans percevoir les couleurs. */
.metric-tri { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-bottom: 8px solid var(--chart-1); }
.metric-tri.bas { border-bottom: 0; border-top: 8px solid var(--chart-2); }
.metric-sub { display: block; font-size: 12px; font-weight: 500; color: var(--muted); margin-top: 3px; }
.metric-foot { margin-top: auto; }
.spark { display: block; width: 100%; height: 46px; }

/* ---------- 17.7 Cartes de graphique ---------- */
.chart-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); gap: 14px; margin-bottom: 14px; }
.chart-grid.egales { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.chart-card {
  display: flex;
  flex-direction: column;
  padding: 16px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  min-width: 0;
}
.chart-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.chart-title { font-size: 12px; font-weight: 650; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-2); }
.chart-note { font-size: 12px; color: var(--muted); margin-top: 4px; }

.legend {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding-top: 12px;
  margin-top: auto;
  border-top: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--ink-2);
}
.legend-item { display: inline-flex; align-items: center; gap: 7px; }
.legend-swatch { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.legend-total { margin-left: auto; color: var(--muted); }
.legend-total b { color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums; }

/* ---------- 17.8 Colonnes à deux capsules ---------- */
/* Chaque colonne porte DEUX mesures : la capsule haute et la capsule basse
   partagent le même axe vertical, ce qui permet de lire d'un coup d'œil si le
   nombre de ventes et le chiffre d'affaires évoluent ensemble. La valeur reste
   ÉCRITE dans la capsule — une hauteur seule ne se lit pas au chiffre près, et
   deux mesures d'unités différentes cohabitent sans tromper.

   La colonne est une GRILLE à trois rangées (capsule haute, pastille-pivot,
   capsule basse) et non une pile centrée : c'est ce qui aligne toutes les
   pastilles-pivots sur une même ligne médiane, comme la maquette. Sur une pile
   centrée, une colonne dont la capsule haute est grande décalait son point vers
   le bas et la ligne des points ondulait d'une colonne à l'autre.

   Les hauteurs reçues sont donc des pourcentages de la MOITIÉ de colonne
   (16 % au minimum, 96 % au maximum), et non de la colonne entière. */
.bars { display: flex; align-items: stretch; gap: 10px; height: 216px; margin: 16px 0 18px; }
.bar-col { flex: 1 1 0; min-width: 0; height: 100%; display: grid; grid-template-rows: 1fr auto 1fr; justify-items: center; gap: 5px; }
.bar-col > .bar.neutre { align-self: end; }
.bar-col > .bar.s1 { align-self: start; }
.bar {
  width: 100%;
  max-width: 40px;
  min-height: 26px;
  border-radius: var(--r-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  transition: transform var(--dur-2) var(--ease);
}
.bar-col:hover .bar { transform: scale(1.05); }
.bar.s1 { background: var(--chart-1); color: var(--chart-1-ink); }
.bar.s2 { background: var(--chart-2); color: var(--chart-2-ink); }
.bar.s3 { background: var(--chart-3); color: var(--chart-3-ink); }
/* La capsule neutre est REMPLIE, pas blanche : sur une carte claire, un fond
   blanc bordé d'un trait fin disparaît et le chiffre semble flotter. */
.bar.neutre { background: var(--surface-3); color: var(--ink); border: 1px solid var(--line-2); }
.bar-pivot { width: 6px; height: 6px; border-radius: 50%; background: var(--faint); flex: none; }
/* LES ETIQUETTES DE L'AXE NE SONT PLUS ROGNEES. `overflow: hidden` coupait le
   dernier caractere des douze dates : a 1440 px chaque colonne recoit 30 px
   quand « 01/09 » en demande 33, et l'ecart se creuse a 1280 px. L'axe et la
   rangee de barres partagent le meme `gap`, donc elargir l'etiquette n'est pas
   possible sans decaler les barres : les etiquettes se contentent de DEBORDER
   dans l'ecart, ou il reste la place — deux voisines se rapprochent sans se
   toucher. `white-space: nowrap` garantit qu'aucune ne se coupe en deux. */
.bar-labels { display: flex; gap: 10px; margin-top: -10px; }
.bar-labels span { flex: 1 1 0; min-width: 0; text-align: center; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: visible; }

/* ---------- 17.9 Frise (ventes par routeur) ---------- */
.tl { margin-top: 16px; }
.tl-row { display: grid; grid-template-columns: var(--tl-label-w, 52px) minmax(0, 1fr); align-items: center; gap: 12px; margin-bottom: 12px; }
.tl-label { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-track {
  position: relative;
  height: 28px;
  /* Grille pointillée verticale : un repère de lecture, jamais une donnée.
     `--tl-pas` compte les INTERVALLES de la piste (6) ; `--tl-grads` compte les
     ÉTIQUETTES de dates de l'axe (7, soit un intervalle de plus). Deux comptes
     distincts tenus par la MÊME feuille de style : c'est les avoir confondus qui
     faisait tomber la dernière date sur une seconde ligne. */
  background-image: linear-gradient(to bottom, var(--track) 0 5px, transparent 5px 9px);
  background-size: calc(100% / var(--tl-pas, 6)) 100%;
  background-repeat: repeat-x;
}
.tl-bar {
  position: absolute;
  top: 0;
  height: 28px;
  min-width: 36px;
  border-radius: var(--r-pill);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  padding: 0 11px;
  font-size: 12px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
}
.tl-bar.s1 { background: var(--chart-1); color: var(--chart-1-ink); }
.tl-bar.s2 { background: var(--chart-2); color: var(--chart-2-ink); }
.tl-bar.s3 { background: var(--chart-3); color: var(--chart-3-ink); }
.tl-bar.s4 { background: var(--chart-4); color: var(--chart-4-ink); }
/* Pastilles : la maquette y met les visages des intervenants. Ici ce sont les
   SELLERS du routeur sur la période — un rond par personne, sans photo. */
.tl-pastilles { display: flex; margin-right: auto; padding-left: 2px; }
.tl-pastilles i {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--line-2);
  margin-right: -6px;
  /* Initiale de vendeur dans une pastille de 18 px : 12 px tient un caractère
     en capitales et reste dans la puce, c'est le plancher sans débordement. */
  font-size: 12px;
  font-weight: 700;
  font-style: normal;
  color: var(--ink-2);
  display: grid;
  place-items: center;
}
.tl-axis { display: grid; grid-template-columns: var(--tl-label-w, 52px) minmax(0, 1fr); gap: 12px; margin-top: 10px; }
.tl-graduations { display: grid; grid-template-columns: repeat(var(--tl-grads, 7), minmax(0, 1fr)); font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* La dernière graduation déborde du cadre : elle est alignée à droite. */
.tl-graduations span:last-child { text-align: right; }

/* ---------- 17.10 Surfaces déjà là, ramenées au panneau ---------- */
/* `min-width: 0` sur les cartes et les tuiles de grille : sans lui, un élément
   de grille refuse de rétrécir sous la largeur MINIMALE de son contenu, et un
   tableau large (le rapport de ventes, six colonnes) élargit toute la page au
   lieu de défiler dans son cadre `.table-wrap`. C'est le piège `min-width: auto`
   des grilles, et il ne se voit qu'en mobile. */
.grid > * { min-width: 0; }
.card { background: var(--surface-2); min-width: 0; }
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.card-head h3 { margin: 0; }
.tile { background: var(--surface); min-width: 0; }

/* ---------- 17.11 Adaptations ---------- */
@media (max-width: 1080px) {
  .dash-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .chart-grid, .chart-grid.egales { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
  /* Le panneau redevient la page : plein cadre, sans coins ni ombre, et c'est le
     corps qui défile à nouveau — le tiroir de navigation existant s'appuie sur
     des positions fixes par rapport à la fenêtre. */
  .shell { margin: 0; height: auto; min-height: 100vh; border: 0; border-radius: 0; box-shadow: none; overflow: visible; }
  .shell-body { grid-template-columns: minmax(0, 1fr); }
  .rail { display: none; }
  .shell-scroll { overflow: visible; }
  .navbar { height: var(--shell-h); padding: 0 14px; flex-wrap: nowrap; }
  .nav-brand::before { width: 28px; height: 28px; font-size: 13px; }
  .nav-brand > a { display: none; }
  .nav-links a { justify-content: flex-start; width: 100%; }
  .user-chip { padding: 4px; background: transparent; }
  .user-chip .user-ident, .user-chip .caret { display: none; }
  .shell .container { padding: 16px 14px 56px; }
  .dash-title { font-size: 22px; }
  .dash-metrics { grid-template-columns: minmax(0, 1fr); }
  .bars { height: 180px; }
  .metric-val { font-size: 24px; }
  .tl-row, .tl-axis { grid-template-columns: 40px minmax(0, 1fr); gap: 8px; }
}

/* Sous 480 px, DOUZE dates ne tiennent plus : chaque colonne tombe a 20 px de
   large quand « 01/09 » en demande 33, et les etiquettes qui debordent dans
   l'ecart finissent par se toucher (mesure : chevauchement des 11 voisines a
   400 et 440 px, plus aucun a 470 px). On garde une date sur deux — les
   capsules, elles, restent toutes dessinees : c'est la legende de l'axe qui
   s'allege, pas la mesure. */
@media (max-width: 480px) {
  .bar-labels span:nth-child(2n) { visibility: hidden; }
}

@media print {
  /* À l'impression, la coquille n'existe plus : la planche de vouchers doit
     sortir seule, sans toile lime ni panneau. */
  .shell, .shell-body, .shell-scroll { display: block; margin: 0; height: auto; border: 0; border-radius: 0; box-shadow: none; overflow: visible; }
  .rail, .dash-filters, .dash-actions { display: none !important; }
  body { background: #fff; }
  .metric, .chart-card { border: 1px solid #ccc; background: #fff; break-inside: avoid; }
}


/* ---------- Profils et tarifs : encadrés de synthèse + dialogues ---------- */
/* Les profils s'empilent de haut en bas et occupent toute la largeur : c'est
   une liste de réglages qu'on parcourt, pas une galerie. La fiche profite donc
   aussi de la largeur pour étaler son résumé. */
.profils-grille{display:flex;flex-direction:column;gap:16px;margin-bottom:24px}
.profil-item{display:flex;flex-direction:column;min-width:0}
.profil-carte{display:flex;flex-direction:column;gap:14px;width:100%}
.profil-haut{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
.profil-haut h3{margin:0 0 6px;font-size:17px;line-height:1.25}
.profil-prix{font-size:16px;font-weight:700;white-space:nowrap;color:var(--ink)}
.profil-details{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px 14px;margin:0;padding:14px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.profil-details dt{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
.profil-details dd{margin:2px 0 0;font-size:13.5px;font-weight:600;color:var(--ink)}
.profil-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:auto}
.profil-actions form{margin:0}

.dialogue-formulaire{position:fixed;inset:0;margin:auto;width:min(780px,calc(100vw - 32px));max-height:calc(100vh - 40px);padding:0;overflow:hidden;border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);color:var(--ink);box-shadow:var(--shadow-2)}
.dialogue-formulaire form{display:flex;flex-direction:column;max-height:calc(100vh - 40px)}
.dialogue-entete{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:18px 22px;border-bottom:1px solid var(--line)}
.dialogue-entete h3{margin:0;font-size:17px}
.dialogue-fermer{border:0;background:none;font-size:24px;line-height:1;cursor:pointer;color:var(--muted);padding:0 4px}
.dialogue-fermer:hover{color:var(--ink)}
.dialogue-corps{padding:20px 22px;overflow:auto}
.dialogue-pied{display:flex;justify-content:flex-end;gap:10px;padding:14px 22px;border-top:1px solid var(--line);background:var(--surface-2)}
@media (max-width:520px){.profil-details{grid-template-columns:1fr}.dialogue-corps{padding:16px}}

/* Confirmation par mot de passe : centrée dans la FENÊTRE VISIBLE, quelle que
   soit la hauteur de la page. La classe `.card` posait un `margin-bottom`, qui
   écrasait le centrage automatique du navigateur et collait le dialogue au bas
   de l'écran. `inset:0` + `margin:auto` recentrent les deux axes. */
dialog.dialogue-confirmation{
  position:fixed;inset:0;margin:auto;
  max-height:calc(100dvh - 32px);overflow:auto;
  padding:22px;border-radius:var(--r-lg);
}

/* ═══════════════════════════════════════════════════════════════════════════
   RAPPORTS STATISTIQUES
   Composants propres aux pages de rapports. Les jetons de couleur
   (--chart-1..4, --ink, --line) sont ceux du produit : rien n'est inventé ici,
   sinon les graphiques changeraient de palette au passage d'un thème.
   ═══════════════════════════════════════════════════════════════════════════ */
[data-reveal]{opacity:0;transform:translateY(8px);transition:opacity .5s ease,transform .5s ease}
[data-reveal].is-visible{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){[data-reveal]{opacity:1;transform:none;transition:none}}

.rapports-page{display:grid;gap:14px}
.rapport-section{background:var(--surface-2);border:1px solid var(--line);border-radius:var(--r-lg);padding:18px 18px 20px;display:grid;gap:14px}
.rapport-titre{font-size:16px;font-weight:700;margin:0;letter-spacing:.01em}
.rapport-note{font-size:12px;color:var(--muted);margin:0}
.rapport-vide{color:var(--muted);font-size:13px;margin:0;padding:10px 0}
.mesures{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}
.carte-mesure{border:1px solid var(--line);border-radius:var(--r);padding:14px;display:grid;gap:4px;background:var(--surface)}
.carte-mesure-titre{font-size:11px;letter-spacing:.07em;text-transform:uppercase;color:var(--muted);font-weight:650}
.carte-mesure-valeur{display:flex;align-items:baseline;gap:6px;font-size:26px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:-.02em;line-height:1.1}
.carte-mesure-unite{font-size:12px;font-weight:600;color:var(--muted)}
.carte-mesure-delta{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:650;color:var(--ink-2)}
.carte-mesure-delta.bas{color:var(--chart-2)}
.carte-mesure-tri{width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:8px solid var(--chart-1)}
.carte-mesure-delta.bas .carte-mesure-tri{border-bottom:0;border-top:8px solid var(--chart-2)}
.carte-mesure-sous{font-size:12px;font-weight:500;color:var(--muted)}
.rapport-grille{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:12px}
.rapport-grille.egales{grid-template-columns:repeat(2,minmax(0,1fr))}
.carte-rapport{border:1px solid var(--line);border-radius:var(--r);padding:14px;background:var(--surface)}
.graph{margin:0;display:grid;gap:8px;border:1px solid var(--line);border-radius:var(--r);padding:14px;background:var(--surface)}
.graph-corps{position:relative;height:var(--graph-hauteur,240px)}
.graph canvas{display:block;width:100% !important;height:100% !important}
.graph-titre{font-size:12px;font-weight:650;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-2);margin:0}
.graph-resume{font-size:12px;color:var(--muted);margin:0}
.graph-vide{font-size:13px;color:var(--muted);margin:0;padding:18px 0}
.graph-legende{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:12px;font-size:12px;color:var(--ink-2)}
.graph-legende li{display:inline-flex;align-items:center;gap:6px}
.graph-pastille{width:10px;height:10px;border-radius:3px;background:var(--teinte,var(--chart-1))}
.classement{list-style:none;margin:0;padding:0;display:grid;gap:8px}
.classement-ligne{display:grid;grid-template-columns:24px minmax(0,1.1fr) minmax(60px,1fr) auto;align-items:center;gap:10px;font-size:13px}
.classement-rang{font-size:11px;font-weight:700;color:var(--muted);font-variant-numeric:tabular-nums}
.classement-libelle{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.classement-barre{height:8px;border-radius:999px;background:var(--surface-3);overflow:hidden}
.classement-barre i{display:block;height:100%;border-radius:999px;background:var(--chart-3);transition:width .8s cubic-bezier(.22,.61,.36,1)}
.classement-valeur{font-variant-numeric:tabular-nums;font-weight:650;text-align:right}
.classement-part{margin-left:6px;font-size:11.5px;font-weight:600;color:var(--muted)}
.rapport-table td.num,.rapport-table th.num{text-align:right;font-variant-numeric:tabular-nums}
.rapport-details summary{cursor:pointer;color:var(--accent);font-size:12.5px;font-weight:600}
.rapport-profils{list-style:none;margin:8px 0 0;padding:0;display:grid;gap:4px;font-size:12.5px}
.rapport-profils b{font-variant-numeric:tabular-nums}
.sommaire{position:sticky;top:8px;z-index:2;display:flex;gap:6px;overflow-x:auto;padding:6px;border:1px solid var(--line);border-radius:var(--r);background:var(--surface-2);scrollbar-width:thin}
.sommaire a{display:inline-flex;align-items:center;gap:6px;padding:7px 11px;border-radius:999px;font-size:12.5px;font-weight:600;color:var(--ink-2);white-space:nowrap;text-decoration:none}
.sommaire a:hover{background:var(--surface-3);color:var(--ink)}
.sommaire svg{width:15px;height:15px}
@media (min-width:1100px){
  .rapports-page{grid-template-columns:200px minmax(0,1fr);align-items:start;column-gap:16px}
  .rapports-page > header{grid-column:1/-1}
  .sommaire{grid-column:1;flex-direction:column;align-items:stretch;top:16px}
  .rapport-section{grid-column:2}
}
@media (max-width:900px){.rapport-grille,.rapport-grille.egales{grid-template-columns:minmax(0,1fr)}}
@media (prefers-reduced-motion:reduce){.classement-barre i{transition:none}}
