/*
 * ╔══════════════════════════════════════════╗
 * ║  SEiiA — Trésorerie                      ║
 * ║  seiia.eu                                ║
 * ║  Dark Corporate / Teal Accents           ║
 * ║  Brand: #0097af · #002c30                ║
 * ╚══════════════════════════════════════════╝
 */

/* ============================================
   VARIABLES
   ============================================ */
:root {
    /* ── Tokens de MARQUE (réglables via /admin/charte-graphique) ──────────
       Source de vérité de l'identité visuelle d'un tenant. Une charte
       entreprise surcharge UNIQUEMENT ces variables ; l'override cascade
       ensuite sur les thèmes clair ET sombre via les dérivés ci-dessous.
       Ne PAS référencer --brand-* directement dans les composants : passer
       toujours par --gold / --bg-sidebar / --radius / --font-family. */
    --brand-primary: #0097af;        /* couleur d'accent principale */
    --brand-primary-light: #00b8d4;
    --brand-primary-dark: #007a8f;
    --brand-sidebar: #002c30;        /* fond sidebar + top-bar (teal foncé) */
    --brand-radius: 12px;
    --brand-font: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
    /* Couleurs sémantiques réglables (succès / danger / avertissement / info).
       --green/--red/--orange/--blue en dérivent → un override cascade partout.
       Le mode daltonien ([data-a11y="cvd"]) les remplace par une palette sûre. */
    /* Assombri le 2026-07-28 vs #10b981 : le texte BLANC des boutons/badges
       « succès » n'y avait que 2.54:1, très en dessous du seuil WCAG AA de
       4.5:1 — même défaut que celui déjà corrigé sur --brand-danger et
       --brand-info. #047857 : 5.48:1. Mesure : scripts/audit_contraste.py. */
    --brand-success: #047857;
    --brand-danger: #dc2626;   /* assombri vs #ef4444 → texte blanc 4.8:1 (WCAG AA) sur boutons/badges/toasts */
    --brand-warning: #f59e0b;
    --brand-info: #2563eb;     /* assombri vs #3b82f6 → texte blanc 5.2:1 (WCAG AA) */
    /* Options de mise en page (réglables IRIS — défauts = valeurs actuelles,
       donc zéro régression tant qu'aucune charte ne les surcharge). */
    --brand-font-scale: 1;          /* échelle globale du texte */
    --brand-heading-weight: 700;    /* graisse des titres */
    --brand-line-height: 1.6;       /* interlignage du corps */
    --brand-card-padding: 24px;     /* densité : padding des cartes */
    --brand-border-width: 1px;      /* épaisseur des bordures de cartes */
    --brand-button-radius: 8px;     /* arrondi des boutons */
    /* Couleur d'identité par neurone (icônes barre modules + titres sidebar).
       Réglables via IRIS ; défauts = couleurs historiques (zéro régression). */
    --neuro-synapse: #0097af;
    --neuro-axone: #7b61ff;
    --neuro-dendrite: #60a5fa;
    --neuro-soma: #34d399;
    /* 2026-08-19 : etait #06b6d4, a dE76 = 12.0 de --neuro-synapse — la zone
       bleu-cyan hebergeait 4 modules en 18 degres de teinte (synapse, flux, care,
       cortex). Synapse porte la marque, donc c'est flux qui bouge : vert (flux
       logistique), dE 23.1 du voisin le plus proche, et c'est le SEUL candidat
       teste a passer aussi 3:1 sur les deux fonds de tuile (3.54 / 4.07 ; l'ancien
       cyan etait a 1.97 en theme clair). #2563eb a ete ecarte : il vaut exactement
       --blue/--info, on aurait refait l'erreur « identite = couleur semantique ».
       ATTENTION : RH est olive (L=47) et flux vert (L=47) — meme luminosite,
       23 degres d'ecart de teinte. Mesure OK, mais si l'oeil hesite, l'alternative
       chiffree est #16a34a (L=57, dL=12) au prix d'un lisere a 2.67 en clair. */
    --neuro-flux: #15803d;
    --neuro-production: #fb923c;
    /* 2026-08-19 : etait #0097af, soit EXACTEMENT --neuro-synapse et la couleur
       de marque — deux modules indiscernables dans la barre laterale comme dans
       la fenetre des modules. Synapse garde la marque ; Care prend un teal plus
       fonce : meme famille (soin), dE76 = 14.8 de la marque, et le lisere des
       tuiles passe 3:1 dans les DEUX themes (3.31 / 4.35). */
    --neuro-care: #0e7490;
    --neuro-sentinel: #ef4444;
    --neuro-cortex: #00c8ff;
    /* 2026-08-19 : etait #f59e0b = --warning/--orange — l'identite du module
       portait la couleur qui signifie « attention » partout ailleurs. Un framboise
       (#e11d48) avait ete essaye : dE 13.9 de --neuro-sentinel, au-dessus du seuil
       mais la planche de controle montrait deux rouges voisins a l'oeil pour deux
       modules tres utilises (RH / Qualite). Olive retenu : c'est la teinte la plus
       ISOLEE de la palette (dE 39.0 du voisin le plus proche, 38.3 des tokens
       semantiques) et elle passe 3:1 sur les deux fonds de tuile (3.55 / 4.05).
       Choix de distinction, pas d'evocation : aucun rose ne pouvait s'eloigner
       assez de Qualite. Verifier avec scripts/audit_identite_modules.py. */
    --neuro-pulse: #4d7c0f;
    --neuro-memoire: #f0c040;
    --neuro-hippocampe: #14b8a6;
    --neuro-glia: #a78bfa;
    --neuro-iris: #ec4899;
    /* 2026-08-19 : etait #94a3b8 = EXACTEMENT --text-secondary — le module n'avait
       pas de couleur, il avait la teinte du texte secondaire. Bleu-ardoise : garde
       l'esprit « systeme » mais devient une identite (dE 20.7 de --text-muted,
       23.0 de --neuro-dendrite). */
    --neuro-admin: #5b7db1;
    /* 2026-08-19 : etait #4f46e5, a dE76 = 11.7 de --neuro-axone — deux violets
       quasi jumeaux, et la zone en comptait 3 (axone, glia, bolero) sur 1 degre.
       Axone (Commerce) est un module majeur, bolero n'est visible que des admins :
       c'est donc bolero qui bouge. Violet franc, dE 21.4 d'axone, 39.3 des tokens
       semantiques, et il passe les deux fonds de tuile (3.30 / 4.37). */
    --neuro-bolero: #9333ea;
    --neuro-codex: #e0a458;
    --neuro-janus: #84cc16;
    --neuro-dd: #d946ef;
    /* 2026-08-20 : la Salle de jeux n'avait pas d'identite, elle portait
       var(--gold) = var(--brand-primary), donc la couleur de MARQUE — elle
       changeait avec le tenant au lieu d'avoir la sienne. Dans le produit par
       defaut cela vaut #0097af, soit EXACTEMENT --neuro-synapse (dE 0, le
       defaut corrige sur Care le 2026-08-19) ; chez un tenant qui a une charte
       c'est sa marque a lui (E-COBOT : #2E8B57, vert). Le nom « gold » est
       trompeur depuis que la marque par defaut est passee au cyan.
       Corail retenu : de tous les candidats mesures c'est le plus ISOLE
       (dE 27.8 de sentinel, 28.8 d'iris, 38.0 du token semantique --red) — la
       zone 0-20 deg etait le dernier trou de teinte large. Choix de sens
       autant que de mesure : le seul module non metier de la maison ne doit
       ressembler a aucun module metier. Contrastes dans les eaux de la palette
       (6.59 sur la tuile sombre, 5.56 sur la sidebar, 2.18 sur la tuile claire
       — au niveau des identites claires existantes : glia 2.21, dendrite 2.06).
       En dichromatie, ce corail s'approche de soma (deuteranopie, dE 9.5) et
       d'iris (tritanopie, dE 9.5) : 2 paires ajoutees aux 33 que la palette
       comptait deja, et les moins serrees des deux listes (les pires sont a
       dE 0.4 et 0.9). Verifier avec scripts/audit_identite_modules.py et
       scripts/audit_daltonisme.py. */
    --neuro-jeux: #fb7185;
    /* CERVELET (2026-08-20, ADR_013), valeur MESUREE le 2026-08-21.
       Bronze sombre : la zone teal/cyan, violette et bleue est saturee ; le
       warm fonce restait libre et n'a pas de charge semantique d'alerte.
       audit_identite_modules : 0 paire sous le seuil de 12 en vision normale
       (le plus proche est codex #e0a458 a dE 35.8, meme teinte mais L 71.6
       contre 36.1 ici).
       Le premier essai #a16207 passait aussi en vision normale, mais partageait
       clarte ET saturation avec pulse #4d7c0f (L~47, C~57) : seule la teinte les
       separait, donc ils se confondaient des qu'un daltonien perd cette
       information (dE 6.4 en protanopie). Descendre la clarte a 36 tranche par
       le canal qui SURVIT a la dichromatie : audit_daltonisme passe de 38 a 36
       confusions, la paire cervelet/pulse en protanopie disparait, celle en
       deuteranopie remonte de 8.4 a 9.2. Contraste du titre de sidebar 5.09:1.
       Ne pas « harmoniser » vers un bronze plus clair sans relancer les deux
       audits : on retomberait sur pulse ou sur codex. */
    --neuro-cervelet: #7a4a05;
    /* MYÉLINE — DSI & conformité. Cuivre : la conduction, et la teinte la
       plus distincte disponible (dE76 24,5 du module le plus proche,
       audit scripts/audit_identite_modules.py, seuil 12). */
    --neuro-myeline: #b45309;

    --bg-primary: #0a0e17;
    --bg-secondary: #111827;
    --bg-card: #1a2235;
    --bg-card-hover: #1f2a40;
    --bg-sidebar: var(--brand-sidebar);
    --bg-input: #1e293b;

    --gold: var(--brand-primary);
    --gold-light: var(--brand-primary-light);
    /* Accent des zones qui restent SOMBRES quel que soit le theme (barre
       laterale). --gold/--gold-light y sont inutilisables : le theme clair
       les assombrit pour un fond clair, et l'item de menu actif tombait a
       3.50:1 (mesure). #00b8d4 sur le voile teal : 5.10:1. Suit les
       chartes tenant ET les palettes daltonisme via --brand-primary-light. */
    /* 2026-08-20 : ECLAIRCI. `--brand-primary-light` seul suffisait dans la palette
       par defaut (5.10:1 sur le voile de l'item actif) mais PAS dans les modes
       daltonisme, qui retintent AUSSI le fond de la barre : l'item de menu actif
       retombait a 3.67:1 (okabe, rouge-vert) et 3.39:1 (bleu-jaune). Mesure sur le
       rendu, pas sur les tokens. Avec 25% de blanc : defaut 6.21, okabe 5.17,
       bleu-jaune 5.07 — et toute charte tenant future en beneficie. */
    --sidebar-accent: color-mix(in srgb, var(--brand-primary-light) 75%, #fff);
    --gold-dark: var(--brand-primary-dark);
    --gold-glow: rgba(0, 151, 175, 0.15);
    /* Texte des boutons .btn-gold : suit le contraste de la couleur de marque.
       Défaut = texte foncé (le teal #0097af le porte bien : 5.1:1). Un tenant
       qui choisit une primaire FONCÉE (vert E-COBOT…) reçoit du blanc via
       --on-gold recalculé côté charte (charte.py::ideal_on_color). */
    --on-gold: #111827;

    --blue: var(--brand-info);
    --blue-dark: #1e40af;
    --green: var(--brand-success);
    --green-dark: #047857;
    --red: var(--brand-danger);
    --red-dark: #b91c1c;
    --orange: var(--brand-warning);
    --orange-light: rgba(245, 158, 11, 0.15);
    --purple: #8b5cf6;

    /* Alias sémantiques (compatibilité templates) */
    --success: var(--green);
    --danger: var(--red);
    --info: var(--blue);
    --warning: var(--orange);

    --text-primary: #f1f5f9;
    --text-secondary: #94a3b8;
    /* Contraste mesuré (scripts/audit_contraste.py, 2026-07-28) : #64748b ne
       donnait que 3.07:1 sur --bg-input et 3.33:1 sur --bg-card, sous le seuil
       WCAG AA de 4.5:1 — d'où les libellés, aides et placeholders « difficiles
       à lire » en mode sombre. #8593a8 : 4.69:1 au pire (champ), 5.09:1 en
       carte, et reste nettement plus discret que --text-secondary. */
    --text-muted: #8593a8;
    /* Alias de compatibilite (cf. --warning-text) : sans usage, mais conserve pour
       que var(--text-gold) reste valide. Ne PAS supprimer comme « token mort ». */
    --text-gold: var(--gold);

    /* #1e293b était à 1.08:1 sur --bg-card et 1.00:1 sur --bg-input : les
       cartes, lignes de tableau et contours de champs ne se détachaient PAS du
       fond. #334259 les rend visibles (1.56:1 sur carte, 1.90:1 sur la page)
       sans alourdir le design. NB : le seuil WCAG « non textuel » de 3:1 n'est
       pas atteint — l'y porter changerait franchement l'aspect, c'est un
       arbitrage de design, pas une correction de lisibilité. */
    --border: #334259;
    /* Contour des CHAMPS de formulaire — composants d'interface au sens WCAG
       1.4.11, donc seuil non textuel 3:1 requis (mesuré : 3.1:1 sur --bg-input,
       3.3:1 sur --bg-card). Les bordures décoratives (cartes, séparateurs)
       restent sur --border — arbitrage design documenté ci-dessus. */
    --border-input: #778699;
    --border-gold: rgba(0, 151, 175, 0.3);

    /* ── Variantes TEXTE des couleurs sémantiques (2026-07-28) ──────────
       --blue/--green/--red/--orange/--purple servent de FOND à du texte blanc
       (boutons, badges, toasts) ET de couleur de TEXTE sur les fonds de l'app.
       Les deux usages tirent dans des sens opposés : les éclaircir pour lire le
       texte casse le contraste des boutons. On sépare donc les rôles — les
       tokens ci-dessus restent les couleurs de SURFACE (inchangés, zéro
       régression), ceux-ci sont les couleurs de TEXTE.

       Dérivés par color-mix des tokens de MARQUE, et non figés en dur : la
       charte graphique d'un tenant continue ainsi de cascader jusqu'ici.
       Pourcentages choisis pour atteindre 4.5:1 (WCAG AA) sur le pire fond du
       thème — mesuré par scripts/audit_contraste.py. */
    --blue-text: color-mix(in srgb, var(--brand-info) 70%, #fff);      /* 4.83:1 */
    --green-text: color-mix(in srgb, var(--brand-success) 60%, #fff);  /* 5.65:1 */
    --red-text: color-mix(in srgb, var(--brand-danger) 70%, #fff);     /* 4.54:1 */
    --orange-text: var(--orange);                                      /* 6.81:1 déjà conforme */
    --purple-text: color-mix(in srgb, var(--purple) 80%, #fff);        /* 4.74:1 */
    --info-text: var(--blue-text);
    --success-text: var(--green-text);
    --danger-text: var(--red-text);
    /* Alias de compatibilite : aucun usage aujourd'hui, mais la famille -text est
       systematique (green/red/blue/orange/purple) et un dev qui ecrit
       var(--warning-text) par symetrie doit obtenir une couleur, pas du vide.
       Ne PAS supprimer au motif « token mort » (audit 2026-08-20). */
    --warning-text: var(--orange-text);

    --shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
    --shadow-gold: 0 0 20px rgba(0, 151, 175, 0.12);

    --radius: var(--brand-radius);
    --radius-sm: 8px;
    --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

    /* Typography scale — multipliée par --brand-font-scale (défaut 1 = inchangé) */
    --font-xs: calc(10px * var(--brand-font-scale, 1));
    --font-sm: calc(11px * var(--brand-font-scale, 1));
    --font-base: calc(13px * var(--brand-font-scale, 1));
    --font-md: calc(14px * var(--brand-font-scale, 1));
    --font-lg: calc(16px * var(--brand-font-scale, 1));
    --font-xl: calc(18px * var(--brand-font-scale, 1));
    --font-2xl: calc(24px * var(--brand-font-scale, 1));
    --font-3xl: calc(28px * var(--brand-font-scale, 1));
    --font-family: var(--brand-font);

    /* Chart defaults (shared across all dashboards) */
    --chart-text: #94a3b8;
    --chart-grid: rgba(30, 41, 59, 0.5);
}

/* ============================================
   MODE DALTONIEN (accessibilité — par utilisateur)
   ============================================
   Choisi via le sélecteur de la top-bar (œil) — localStorage `mysynapse-a11y`
   → html[data-a11y="<profil>"]. Applique un THÈME ACCESSIBLE COMPLET pour
   l'utilisateur : couleur principale + accents + fond sidebar + couleurs de
   statut, tous remplacés par une palette distinguable selon le type de
   daltonisme. C'est un réglage PAR UTILISATEUR (chacun voit le thème qui lui
   convient), indépendant de la charte de marque du tenant.

   Le sélecteur `html[data-a11y=...]` (spécificité 0,1,1) prime volontairement
   sur le `:root` injecté par la charte du tenant → le thème accessible de
   l'utilisateur l'emporte sur la personnalisation de marque.

   ⚠️ SOURCE DE VÉRITÉ des hex : ce bloc. Les mêmes valeurs (statuts) sont
   reprises dans le sélecteur top-bar (templates/base.html) et la page IRIS
   Accessibilité (modules/iris.py CVD_PROFILES) pour l'aperçu — garder cohérent.

   Profils (cf. recommandations Okabe-Ito 2008 / Paul Tol / guides daltonisme) :
   • universel (okabe) — palette Okabe-Ito (accent bleu franc), sûre pour TOUS. Recommandé.
   • rouge-vert (deutéranopie/protanopie, ~6% des hommes) — accent bleu, danger orange vif.
   • bleu-jaune (tritanopie, rare) — accent violet (évite le bleu, confusable), axe rouge-vert. */
html[data-a11y="cvd"],            /* alias rétro-compat (ancien on/off) */
html[data-a11y="okabe"] {
    --brand-primary:       #0072b2;   /* bleu Okabe (accent principal accessible) */
    --brand-primary-light: #56b4e9;   /* bleu ciel */
    --brand-primary-dark:  #004c78;
    --brand-sidebar:       #0a2a40;   /* bleu très foncé (sidebar/entête) */
    --brand-success: #009e73;   /* vert bleuté */
    --brand-danger:  #d55e00;   /* vermillon */
    --brand-warning: #e69f00;   /* orange ambre */
    --brand-info:    #0072b2;   /* bleu franc */
    /* Accent principal retinté (cascade --brand-primary → --gold partout) :
       le mode devient VISIBLE sur toute l'appli (boutons, liens, états actifs,
       barres KPI), pas seulement sur les 4 statuts. Bleu Okabe sûr. */
    --brand-primary: #0072b2;
    --brand-primary-light: #3a8fd0;
    --brand-primary-dark: #005a8c;
    --gold-glow: rgba(0, 114, 178, 0.15);
    --purple: #cc79a7;          /* pourpre Okabe (sûr) */
    /* 2026-08-20 : ce token MANQUAIT ici. Sans lui, l'encre des boutons de marque
       restait celle du thème (#111827) sur le bleu Okabe : 3.42:1, sous le seuil.
       Le correctif existait déjà — mais avait été écrit dans le bloc `redgreen`,
       dont le commentaire parle pourtant d'Okabe. Blanc sur #0072b2 : 5.19:1. */
    --on-gold: #ffffff;
}
html[data-a11y="redgreen"] {
    --brand-primary:       #0072b2;   /* bleu (sûr en rouge-vert) */
    --brand-primary-light: #56b4e9;
    --brand-primary-dark:  #004c78;
    --brand-sidebar:       #0a2a40;
    --brand-success: #009e73;   /* vert bleuté (froid, ≠ rouge) */
    --brand-danger:  #e8590c;   /* orange-rouge vif (visible en protanopie) */
    --brand-warning: #f2c200;   /* jaune doré (forte luminance) */
    --brand-info:    #0072b2;   /* bleu profond */
    --brand-primary: #0072b2;   /* accent bleu sûr (≠ rouge/vert) */
    --brand-primary-light: #3a8fd0;
    --brand-primary-dark: #005a8c;
    --gold-glow: rgba(0, 114, 178, 0.15);
    --purple: #cc79a7;
    /* Okabe ET rouge-vert partagent la primaire #0072b2 : blanc 5.19:1, le noir
       n'y faisait que 3.42:1. Le bloc okabe a sa propre déclaration (2026-08-20) —
       ne pas croire que celle-ci le couvre, un bloc ne cascade pas vers l'autre. */
    --on-gold: #ffffff;
}
html[data-a11y="blueyellow"] {
    --brand-primary:       #8e2dc2;   /* violet (≠ bleu/jaune confusables) */
    --brand-primary-light: #b15ce0;
    --brand-primary-dark:  #6a1f93;
    --brand-sidebar:       #26133a;   /* violet très foncé */
    --brand-success: #1b8a3f;   /* vert saturé (axe rouge-vert intact) */
    --brand-danger:  #d81b1b;   /* rouge saturé */
    --brand-warning: #e8740a;   /* orange */
    --brand-info:    #8e2dc2;   /* violet (≠ vert, évite bleu↔vert) */
    --brand-primary: #8e2dc2;   /* accent violet (≠ vert, évite bleu↔jaune) */
    --brand-primary-light: #a64fd6;
    --brand-primary-dark: #6f1f9c;
    --gold-glow: rgba(142, 45, 194, 0.15);
    --purple: #8e2dc2;
    --on-gold: #ffffff;   /* primaire violet #8e2dc2 : blanc 6.4:1 (le noir échouait à 2.8) */
}

/* ── Mode daltonien : renforcement de contraste (couleur + FORME) ──
   `html[data-a11y]` = un profil est actif (attribut retiré quand désactivé).
   1) Corrige les fonds d'alerte codés en dur qui ne suivaient PAS les variables
      → ils s'adaptent désormais au profil choisi.
   2) Ajoute un repère de FORME (bordure gauche épaisse, soulignement) en plus de
      la couleur — conforme WCAG 1.4.1 « ne pas reposer sur la couleur seule ». */
html[data-a11y] .alert-success { background: color-mix(in srgb, var(--green) 16%, transparent);  border-color: var(--green);  border-left: 4px solid var(--green); }
html[data-a11y] .alert-error   { background: color-mix(in srgb, var(--red) 16%, transparent);    border-color: var(--red);    border-left: 4px solid var(--red); }
html[data-a11y] .alert-warning { background: color-mix(in srgb, var(--orange) 16%, transparent); border-color: var(--orange); border-left: 4px solid var(--orange); }
html[data-a11y] .alert-info    { background: color-mix(in srgb, var(--blue) 16%, transparent);   border-color: var(--blue);   border-left: 4px solid var(--blue); }
html[data-a11y] .status-ok,
html[data-a11y] .status-ecart  { text-decoration: underline; text-underline-offset: 2px; }

/* Survol des options du sélecteur de profil daltonien (top-bar). */
.a11y-opt:hover { background: var(--bg-secondary); }

/* Le menu déroulant vit dans la top-bar : en thème clair, la top-bar force
   --text-primary en BLANC (pour son fond sombre), mais le menu a un fond clair
   (--bg-card #fff) → texte blanc sur blanc = libellés illisibles. On réinitialise
   les variables aux couleurs PAGE pour ce menu (même correctif que #tb-ent-dropdown). */
[data-theme="light"] #a11y-dropdown {
    --text-primary: #1e293b;
    --text-secondary: #475569;
    --text-muted: #556372;
    --bg-card: #ffffff;
    --bg-secondary: #f1f5f9;
    --border: #e2e8f0;
}

/* ============================================
   LIGHT THEME
   ============================================ */
[data-theme="light"] {
    --bg-primary: #f1f5f9;
    --bg-secondary: #e2e8f0;
    --bg-card: #ffffff;
    --bg-card-hover: #f8fafc;
    --bg-sidebar: var(--brand-sidebar);
    --bg-input: #f8fafc;

    /* En clair, on assombrit l'accent de marque pour garder le contraste
       sur fond clair (dérivé de --brand-primary, pas une couleur figée). */
    --gold: color-mix(in srgb, var(--brand-primary) 81%, #000);
    --gold-light: var(--brand-primary);
    --gold-dark: color-mix(in srgb, var(--brand-primary) 63%, #000);
    --gold-glow: rgba(0, 122, 143, 0.1);

    --text-primary: #0f172a;
    --text-secondary: #475569;
    /* 2.56:1 sur --bg-card avec #94a3b8 : illisible. #556372 → 4.99:1 au pire
       (fond secondaire). Même mesure que le mode sombre. */
    --text-muted: #556372;
    --text-gold: var(--gold);

    /* 1.48:1 sur blanc : bordures quasi absentes. #b4c0cf → 1.85:1. */
    --border: #b4c0cf;
    /* Champs de formulaire : 3:1 (cf. :root) — 3.1:1 sur --bg-input clair. */
    --border-input: #808fa5;
    --border-gold: rgba(0, 122, 143, 0.3);

    /* Variantes TEXTE (cf. bloc :root) — mélange vers le NOIR ici, seuil AA
       atteint sur le pire fond clair (--bg-secondary). */
    --blue-text: color-mix(in srgb, var(--brand-info) 95%, #000);      /* 4.55:1 */
    --green-text: color-mix(in srgb, var(--brand-success) 90%, #000);  /* 5.22:1 */
    --red-text: color-mix(in srgb, var(--brand-danger) 90%, #000);     /* 4.66:1 */
    --orange-text: color-mix(in srgb, var(--brand-warning) 55%, #000); /* 5.03:1 */
    --purple-text: color-mix(in srgb, var(--purple) 80%, #000);        /* 4.94:1 */

    --shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
    --shadow-gold: 0 0 20px rgba(0, 122, 143, 0.08);

    --orange-light: rgba(245, 158, 11, 0.1);
}

/* Keep sidebar & top-bar dark in light mode */
[data-theme="light"] .sidebar,
[data-theme="light"] .top-bar,
[data-theme="light"] .sidebar {
    --bg-sidebar: var(--brand-sidebar);
    /* Ces zones restent sombres en thème clair : les variantes TEXTE doivent y
       reprendre le réglage du thème sombre, sinon un vert foncé « clair »
       atterrit sur le teal foncé de la sidebar.
       2026-08-19 : `.sidebar` AJOUTÉ au sélecteur. Le commentaire ci-dessus la
       visait déjà, mais elle n'héritait que des surfaces — ses textes passaient
       en encre sombre sur le teal (.sbf-copyright mesuré à 1.19:1). Désormais
       13.66:1 / 5.84:1 / 4.80:1 sur #002c30. */
    --blue-text: color-mix(in srgb, var(--brand-info) 70%, #fff);
    --green-text: color-mix(in srgb, var(--brand-success) 60%, #fff);
    --red-text: color-mix(in srgb, var(--brand-danger) 70%, #fff);
    --orange-text: var(--orange);
    --purple-text: color-mix(in srgb, var(--purple) 80%, #fff);
    --border: #004050;
    --text-primary: #f1f5f9;
    --text-secondary: #94a3b8;
    /* #64748b (valeur d'avant le fix 2026-07-28) ne faisait que ~2.6:1 sur les
       surfaces sombres de la sidebar/top-bar — aligné sur le token sombre. */
    --text-muted: #8593a8;
    --gold: var(--brand-primary);
    --gold-light: var(--brand-primary-light);
    --gold-dark: var(--brand-primary-dark);
    --gold-glow: rgba(0, 151, 175, 0.15);
    --border-gold: rgba(0, 151, 175, 0.3);
}
[data-theme="light"] .top-bar {
    background: var(--brand-sidebar);
    border-bottom-color: #004050;
}
/* Light mode : la sidebar reste sombre (teal). Ses SURFACES internes (boutons,
   cartes, inputs) doivent rester sombres aussi, sinon contenu clair sur fond
   clair = illisible (ex. sélecteur d'espace « SAV » : texte blanc sur lavande). */
[data-theme="light"] .sidebar {
    --bg-secondary: #013b41;
    --bg-card: #024c54;
    --bg-input: #013b41;
}
/* Dropdown entreprise top-bar : doit utiliser les couleurs PAGE, pas top-bar */
[data-theme="light"] #tb-ent-dropdown {
    --text-primary: #1e293b;
    --text-secondary: #475569;
    --bg-card: #ffffff;
    --bg-secondary: #f1f5f9;
    --border: #e2e8f0;
    --shadow: 0 4px 16px rgba(0,0,0,0.12);
    --gold-glow: rgba(0,151,175,0.1);
    --gold: #0097af;
}
[data-theme="light"] #tb-ent-dropdown a {
    color: #1e293b !important;
}
[data-theme="light"] #tb-ent-dropdown span {
    color: #1e293b;
}
/* Dropdown notifications top-bar : même problème — héritait des variables
   .top-bar (text-primary blanc) → texte blanc sur fond blanc. Force les
   couleurs PAGE pour que les titres/bodies/dates soient lisibles. */
[data-theme="light"] #notif-dropdown {
    --text-primary: #1e293b;
    --text-secondary: #475569;
    /* #94a3b8 sur carte blanche : 2.56:1 — même valeur que le thème clair. */
    --text-muted: #556372;
    --bg-card: #ffffff;
    --bg-secondary: #f1f5f9;
    --border: #e2e8f0;
    --shadow: 0 4px 16px rgba(0,0,0,0.12);
    --gold: #0097af;
    --gold-glow: rgba(0,151,175,0.1);
}
[data-theme="light"] #notif-dropdown a,
[data-theme="light"] #notif-dropdown span,
[data-theme="light"] #notif-dropdown div {
    color: inherit;
}
/* Dropdown "Mes activités" top-bar : même problème que #notif-dropdown —
   héritait de --text-primary blanc (scope .top-bar) sur fond --bg-card clair
   → titres invisibles en mode clair. Force les couleurs PAGE. */
[data-theme="light"] #act-dropdown {
    --text-primary: #1e293b;
    --text-secondary: #475569;
    /* #94a3b8 sur carte blanche : 2.56:1 — même valeur que le thème clair. */
    --text-muted: #556372;
    --bg-card: #ffffff;
    --bg-secondary: #f1f5f9;
    --border: #e2e8f0;
    --shadow: 0 4px 16px rgba(0,0,0,0.12);
    --gold: #0097af;
    --gold-glow: rgba(0,151,175,0.1);
}
[data-theme="light"] #act-dropdown a,
[data-theme="light"] #act-dropdown span,
[data-theme="light"] #act-dropdown div {
    color: inherit;
}
/* Barre de recherche globale (top-bar) : même problème — le scope .top-bar
   force --text-primary blanc mais ne redéfinit pas --bg-secondary (reste clair)
   → le texte saisi est blanc sur fond clair = invisible en mode clair. L'input
   utilise background:var(--bg-secondary) + color:var(--text-primary) en inline,
   donc on corrige via les VARIABLES (l'inline var() les résout) : fond sombre
   cohérent avec la top-bar + texte clair lisible. */
[data-theme="light"] #globalSearchInput {
    --bg-secondary: #013b41;
    --text-primary: #f1f5f9;
}

[data-theme="light"] .data-table th {
    background: #e2e8f0;
    color: #007a8f;
    border-bottom-color: #007a8f;
}

[data-theme="light"] .login-page {
    background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 50%, #cbd5e1 100%);
}

[data-theme="light"] .login-box {
    background: #ffffff;
    border-color: rgba(0, 122, 143, 0.3);
}

[data-theme="light"] .form-input,
[data-theme="light"] .form-select {
    background: #f8fafc;
    border-color: #cbd5e1;
    color: #0f172a;
}

[data-theme="light"] .matrix-table th {
    background: #e2e8f0;
    color: #007a8f;
}

/* ============================================
   RESET & BASE
   ============================================ */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: var(--font-family);
    background: var(--bg-primary);
    color: var(--text-primary);
    line-height: var(--brand-line-height, 1.6);
    min-height: 100vh;
}
/* Graisse des titres réglable (IRIS) — défaut 700 = inchangé */
h1, h2, h3, .page-title { font-weight: var(--brand-heading-weight, 700); }

a { color: var(--gold); text-decoration: none; transition: var(--transition); }
a:hover { color: var(--gold-light); }

/* ============================================
   LAYOUT
   ============================================ */
.app-layout {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
.app-body {
    display: flex;
    flex: 1;
    margin-top: 40px;
}

/* Top bar */
.top-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 40px;
    background: var(--bg-sidebar);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 16px 0 16px;
    z-index: 110;
}
.top-bar-left {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-shrink: 0;
}
.top-bar-left img {
    height: 22px;
    max-width: 100px;
    object-fit: contain;
}
.top-bar-left .top-bar-sep {
    width: 1px;
    height: 18px;
    background: var(--border);
}
.top-bar-left .top-bar-app {
    font-size: 12px;
    color: var(--text-muted);
    font-weight: 500;
    letter-spacing: 0.5px;
}
.top-bar-center {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 500;
    color: var(--text-muted);
    letter-spacing: 0.3px;
    font-family: 'JetBrains Mono', monospace;
    pointer-events: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    flex-shrink: 1;
}
.top-bar-center .clock-sep {
    opacity: 0.4;
}
/* Élément du bandeau masqué par l'utilisateur (popup « Personnaliser », cf.
   modules/topbar_prefs.py). Rendu côté serveur → pas de clignotement au load ;
   la case à cocher ne fait que basculer cette classe → pas de rechargement.
   `!important` OBLIGATOIRE : plusieurs éléments du bandeau reçoivent un
   `display` en style INLINE (le widget chat passe à `display:flex` depuis son
   poll). Sans lui — ou avec l'attribut `hidden` — le style inline gagnerait et
   l'élément masqué réapparaîtrait. */
.tb-off {
    display: none !important;
}
.top-bar-right {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
.top-bar-right .tb-btn {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font-size: 16px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s;
    text-decoration: none;
    position: relative;
}
.top-bar-right .tb-btn:hover {
    background: rgba(255,255,255,0.08);
    color: var(--text-primary);
}
/* État actif (ex. bouton Mode daltonien activé) */
.top-bar-right .tb-btn.active {
    background: var(--gold);
    color: var(--on-gold, #fff);
    box-shadow: 0 0 0 2px var(--gold-glow);
}
/* ── Harmonisation des icônes du bandeau (2026-07-20) ──
   Chaque glyphe est enveloppé dans un `.tb-ico` : emojis rendus en gris
   monochrome sobre au repos, couleur pleine au survol / à l'état actif. Le
   wrapper est INDISPENSABLE — un `filter` posé sur le `.tb-btn` griserait aussi
   les badges de notification (le filtre englobe les enfants, impossible à
   annuler côté enfant). Ici seul le glyphe est filtré : les badges gardent leur
   couleur pour ressortir. Aucun changement de comportement. */
.top-bar-right .tb-btn .tb-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    filter: grayscale(1);
    opacity: 0.62;
    color: var(--text-secondary);
    transition: opacity 0.18s ease, filter 0.18s ease, color 0.18s ease;
}
.top-bar-right .tb-btn:hover .tb-ico,
.top-bar-right .tb-btn.active .tb-ico {
    filter: grayscale(0);
    opacity: 1;
    color: var(--text-primary);
}
.top-bar-right .tb-sep {
    width: 1px;
    height: 20px;
    background: var(--border);
    margin: 0 4px;
}
.top-bar-right .tb-user {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--gold-dark);
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 12px;
    color: var(--bg-primary);
    cursor: pointer;
    border: 2px solid transparent;
    transition: border-color 0.2s;
}
.top-bar-right .tb-user:hover {
    border-color: var(--gold);
}

/* Sidebar */
.sidebar {
    width: 220px;
    background: var(--bg-sidebar);
    /* `color` doit etre declaree ICI : la couleur heritee de body transmet la
       VALEUR deja resolue (encre sombre en theme clair), pas la reference
       var(). Sans cette ligne, les tokens de texte redefinis pour la sidebar ne
       sont jamais consultes et tout texte non style y devient illisible en
       theme clair (mesure : .sbf-copyright a 1.19:1). Identique en sombre. */
    color: var(--text-primary);
    border-right: 1px solid var(--border);
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 40px;                       /* sous la top-bar (bandeau modules retiré) */
    height: calc(100vh - 40px);
    z-index: 105;
    transition: var(--transition);
}

/* Sélecteur de neurone (sidebar — espace de travail) */
.snrn { padding: 12px 14px; border-bottom: 1px solid var(--border); position: relative; }
.snrn-label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.snrn-current {
    display: flex; align-items: center; gap: 10px; width: 100%;
    padding: 8px 10px; background: var(--bg-secondary);
    border: 1px solid var(--border); border-radius: 8px; cursor: pointer;
    transition: background .2s, border-color .2s;
    color: var(--text-primary); font-size: 14px; font-weight: 700; text-align: left;
}
.snrn-current:hover { background: var(--bg-card); border-color: var(--gold); }
.snrn-icon { font-size: 16px; flex-shrink: 0; line-height: 1; }
.snrn-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.snrn-chevron { color: var(--text-muted); font-size: 12px; flex-shrink: 0; }
/* Raccourcis « Récents » sous le sélecteur — révélés par JS (classe .show, PAS
   l'attribut [hidden] qui perdrait contre display:flex). Le changement de module
   lui-même passe par la fenêtre plein écran (.modbrowse-*), plus par un menu
   déroulant : 20 modules ne s'y lisaient pas (scroll interne, pas de vue d'ensemble). */
.snrn-recents { display: none; margin-top: 10px; }
.snrn-recents.show { display: block; }
.snrn-recents-label {
    font-size: 9px; color: var(--text-muted); text-transform: uppercase;
    letter-spacing: 0.5px; margin: 0 0 4px 2px;
}
.snrn-recent {
    display: none; align-items: center; gap: 8px; padding: 5px 8px;
    border-radius: 6px; text-decoration: none; color: var(--text-secondary);
    font-size: 12px; font-weight: 500; transition: background .15s, color .15s;
}
.snrn-recent.show { display: flex; }
.snrn-recent:hover { background: var(--bg-secondary); color: var(--text-primary); text-decoration: none; }
.snrn-recent-icon { font-size: 13px; flex-shrink: 0; line-height: 1; }
.snrn-recent-label { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── Navigateur de modules — fenêtre plein écran (« Tout parcourir ») ── */
.modbrowse-overlay {
    display: none; position: fixed; inset: 0;
    background: rgba(0,0,0,0.55); backdrop-filter: blur(4px);
    z-index: 10000; align-items: flex-start; justify-content: center;
    padding: 4vh 20px 20px; overflow-y: auto;
}
.modbrowse-overlay.open { display: flex; }
.modbrowse-panel {
    width: 100%; max-width: 1100px;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: 0 24px 64px rgba(0,0,0,0.45);
    overflow: hidden;
}
.modbrowse-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 18px 24px; border-bottom: 1px solid var(--border);
}
.modbrowse-title {
    display: flex; align-items: center; gap: 12px; margin: 0;
    font-size: 1.25rem; font-weight: 700; color: var(--text-primary);
}
.modbrowse-title-icon { font-size: 1.4rem; }
.modbrowse-close {
    background: transparent; border: none; cursor: pointer;
    color: var(--text-muted); font-size: 28px; line-height: 1;
    width: 40px; height: 40px; border-radius: var(--radius-sm);
    display: flex; align-items: center; justify-content: center;
    transition: background .15s, color .15s;
}
.modbrowse-close:hover { background: var(--bg-secondary); color: var(--red-text); }
/* Barre de filtre — le popup est désormais l'unique chemin de changement de module :
   à 20 modules, la frappe doit pouvoir remplacer le balayage visuel. */
.modbrowse-toolbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 16px; flex-wrap: wrap; padding: 14px 24px;
    border-bottom: 1px solid var(--border); background: var(--bg-secondary);
}
.modbrowse-search {
    flex: 1 1 260px; min-width: 0; display: flex; align-items: center; gap: 8px;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: var(--radius-sm); padding: 0 12px;
    transition: border-color .15s;
}
.modbrowse-search:focus-within { border-color: var(--gold); }
.modbrowse-search-icon { font-size: 14px; color: var(--text-muted); flex-shrink: 0; }
.modbrowse-search-input {
    /* Pas d'`outline: none` ici : la regle globale :focus-visible (fin de
       fichier) la neutraliserait de toute facon, et l'anneau clavier est
       souhaite en plus du lisere de .modbrowse-search:focus-within. */
    flex: 1; min-width: 0; padding: 10px 0;
    background: transparent; border: none;
    color: var(--text-primary); font-family: inherit; font-size: 0.95rem;
}
.modbrowse-hint { display: flex; align-items: center; gap: 4px; font-size: 0.72rem; color: var(--text-muted); }
.modbrowse-hint kbd {
    font-family: inherit; font-size: 0.7rem; padding: 1px 5px;
    background: var(--bg-card); border: 1px solid var(--border);
    border-radius: 4px; color: var(--text-secondary);
}
.modbrowse-empty {
    display: none; padding: 0 24px 24px;
    text-align: center; color: var(--text-muted); font-size: 0.9rem;
}
.modbrowse-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px; padding: 20px 24px 24px;
}
.modbrowse-card {
    position: relative; display: flex; flex-direction: column;
    align-items: center; gap: 10px; padding: 20px 16px;
    background: var(--bg-secondary); border: 1px solid var(--border);
    border-radius: var(--radius); text-decoration: none; color: var(--text-primary);
    transition: transform .15s, border-color .15s, box-shadow .15s, background .15s;
}
.modbrowse-card:hover {
    transform: translateY(-3px); border-color: var(--card-accent, var(--gold));
    box-shadow: 0 10px 24px rgba(0,0,0,0.18); color: var(--text-primary); text-decoration: none;
}
.modbrowse-card-active {
    border-color: var(--card-accent, var(--gold));
    box-shadow: inset 0 0 0 1px var(--card-accent, var(--gold));
}
/* Carte survolée au clavier (flèches) — distincte du module courant (.modbrowse-card-active). */
.modbrowse-card-kb {
    border-color: var(--card-accent, var(--gold));
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--card-accent, var(--gold)) 55%, transparent);
}
.modbrowse-card-icon { font-size: 2rem; line-height: 1; }
.modbrowse-card-label { font-size: 0.95rem; font-weight: 600; text-align: center; color: var(--text-primary); }
.modbrowse-card-badge {
    position: absolute; top: 8px; right: 8px;
    font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
    /* L'accent porte le lisere, pas le texte : il va du violet fonce au jaune
       et aucune encre unique n'y tient (blanc : 1.70:1 sur Dossier ; encre
       sombre : 2.82:1 sur Turbo). Ici 16:1 en sombre, 17.8:1 en clair. */
    color: var(--text-primary); background: var(--bg-card);
    border: 1.5px solid var(--card-accent, var(--gold));
    padding: 1px 6px; border-radius: 999px;
}
@media (max-width: 768px) {
    .modbrowse-overlay { padding: 3vh 12px 12px; }
    .modbrowse-toolbar { padding: 12px 16px; }
    .modbrowse-hint { display: none; }          /* pas de clavier physique */
    .modbrowse-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; padding: 16px; }
    .modbrowse-card { padding: 20px 12px; }
    .modbrowse-card-icon { font-size: 2rem; }
}

.sidebar-brand {
    padding: 24px 20px;
    border-bottom: 1px solid var(--border);
    text-align: center;
}

.sidebar-brand h1 {
    font-size: 18px;
    font-weight: 700;
    color: var(--gold);
    letter-spacing: 3px;
    text-transform: uppercase;
}

.sidebar-brand .subtitle {
    font-size: 10px;
    color: var(--text-muted);
    letter-spacing: 2px;
    text-transform: uppercase;
    margin-top: 4px;
}

.sidebar-nav {
    flex: 1;
    padding: 16px 0;
    overflow-y: auto;
}

/* ============================================
   PIED DE SIDEBAR (compte / aide / langue / AppStore / repli) — façon GitLab
   ============================================ */
.sb-footer {
    flex-shrink: 0;
    border-top: 1px solid var(--border);
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    background: var(--bg-sidebar);
}
.sbf-account {
    display: flex; align-items: center; gap: 10px;
    padding: 6px 8px; border-radius: 8px; text-decoration: none;
    color: var(--text-primary); transition: background .15s;
}
.sbf-account:hover { background: var(--bg-secondary); }
.sbf-avatar, .sbf-avatar-img {
    width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
}
.sbf-avatar {
    display: flex; align-items: center; justify-content: center;
    background: var(--gold); color: var(--on-gold, #000); font-weight: 700; font-size: 13px;
}
.sbf-account-name {
    flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    font-size: 13px; font-weight: 600;
}
.sbf-actions { display: flex; align-items: center; gap: 5px; }
.sbf-btn {
    flex: 1; height: 32px;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--border); border-radius: 7px;
    background: var(--bg-secondary); color: var(--text-secondary);
    font-size: 15px; cursor: pointer; text-decoration: none; transition: all .15s;
}
.sbf-btn:hover { border-color: var(--gold); color: var(--gold); background: var(--bg-card); }
.sbf-lang { position: relative; flex: 1; display: flex; }
.sbf-lang .sbf-btn { width: 100%; }
.sbf-lang-dropdown {
    display: none; position: absolute; bottom: calc(100% + 6px); left: 0; right: 0;
    background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.3); z-index: 200; overflow: hidden;
}
.sbf-lang-dropdown.open { display: block; }
.sbf-lang-opt {
    padding: 8px 12px; cursor: pointer; display: flex; align-items: center; gap: 8px;
    font-size: 0.82rem; color: var(--text-primary); transition: background .15s;
}
.sbf-lang-opt:hover { background: var(--bg-secondary); }
.sbf-lang-opt.active { background: var(--gold-glow); font-weight: 700; }
.sbf-collapse {
    display: flex; align-items: center; gap: 8px; width: 100%;
    padding: 8px 10px; border: none; border-radius: 7px; background: transparent;
    color: var(--text-muted); font-size: 12px; cursor: pointer; transition: all .15s;
}
.sbf-collapse:hover { background: var(--bg-secondary); color: var(--text-primary); }
.sbf-collapse-icon { font-size: 10px; flex-shrink: 0; }

/* Épingler la sidebar large — n'existe QUE dans la plage du rail tablette
   (769–1180 px), où la sidebar se réduit d'office à des icônes. Masqué
   partout ailleurs : au-delà elle est déjà large, en deçà c'est la bottom-nav.
   La règle qui l'affiche vit dans le @media du rail (chercher `.sbf-wide`). */
.sbf-wide {
    display: none; align-items: center; gap: 8px; width: 100%;
    padding: 8px 10px; border: none; border-radius: 7px; background: transparent;
    color: var(--text-muted); font-size: 12px; cursor: pointer; transition: all .15s;
}
.sbf-wide:hover { background: var(--bg-secondary); color: var(--text-primary); }
.sbf-wide-icon { font-size: 12px; flex-shrink: 0; }
/* Épinglée : l'état actif se lit sans survol (le title seul ne suffit pas). */
html.pref-sidebar-wide .sbf-wide { color: var(--gold); }

/* Numéro de version + build (pied de sidebar) */
.sbf-version {
    display: flex; align-items: center; justify-content: center; gap: 5px;
    padding: 4px 10px 2px; font-size: 10px; line-height: 1.4;
    color: var(--text-muted); opacity: 0.7;
    letter-spacing: 0.3px; user-select: none;
}
.sbf-version-app { font-weight: 600; }
.sbf-version-sep { opacity: 0.5; }
.sbf-version-build { font-variant-numeric: tabular-nums; }

/* Mention de copyright (pied de sidebar) : aucune regle de base n'existait, le
   bloc heritait donc du corps de la sidebar (14 px) et s'imposait plus que le
   contenu. Discret et sous la ligne de version, sans passer sous le seuil de
   contraste : --text-muted vaut #8593a8 sur le teal (~4.8:1), donc PAS
   d'opacity supplementaire ici (cf. fix 2026-08-19). */
.sbf-copyright {
    padding: 0 10px 4px; text-align: center;
    font-size: 9px; line-height: 1.35; letter-spacing: 0.2px;
    color: var(--text-muted); user-select: none;
}

.nav-section {
    padding: 8px 20px 4px;
    font-size: 10px;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.55); /* couleur fixe sur fond sidebar sombre */
    text-transform: uppercase;
    letter-spacing: 1.5px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    user-select: none;
    transition: color 0.2s;
    margin-top: 4px;
}
.nav-section:hover { color: var(--gold-light); }
.nav-section .nav-chevron {
    font-size: 10px;
    transition: transform 0.25s ease;
    display: inline-block;
}
.nav-section.collapsed .nav-chevron { transform: rotate(-90deg); }
.nav-group {
    overflow: hidden;
    max-height: 600px;
    transition: max-height 0.3s ease, opacity 0.25s ease;
    opacity: 1;
}
.nav-group.collapsed {
    max-height: 0;
    opacity: 0;
    pointer-events: none;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 16px;
    color: var(--text-secondary);
    transition: var(--transition);
    border-left: 3px solid transparent;
    font-size: 12px;
}
.nav-item .icon { font-size: 14px; width: 20px; text-align: center; }

.nav-item:hover {
    background: var(--gold-glow);
    color: var(--sidebar-accent);
    border-left-color: var(--gold);
}

.nav-item.active {
    background: var(--gold-glow);
    /* Mesure 2026-08-19 : var(--gold) donnait 3.50:1 a 13px sur le voile teal,
       dans les DEUX themes. C'est l'element le plus regarde de la barre. */
    color: var(--sidebar-accent);
    border-left-color: var(--gold);
    font-weight: 600;
}

.nav-item .icon { font-size: 18px; width: 24px; text-align: center; }

.nav-badge {
    margin-left: auto;
    background: var(--red);
    color: white;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 8px;
    border-radius: 10px;
}

/* Main content */
.main-content {
    flex: 1;
    margin-left: 220px;
    padding: 32px;
    min-height: 100vh;
}

/* ============================================
   CARDS
   ============================================ */
.card {
    background: var(--bg-card);
    border: var(--brand-border-width, 1px) solid var(--border);
    border-radius: var(--radius);
    padding: var(--brand-card-padding, 24px);
    transition: var(--transition);
}

.card:hover {
    border-color: var(--border-gold);
    box-shadow: var(--shadow-gold);
}

.card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
    padding-bottom: 16px;
    border-bottom: 1px solid var(--border);
}

.card-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ============================================
   KPI CARDS
   ============================================ */
.kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 20px;
    margin-bottom: 32px;
}

.kpi-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 24px;
    position: relative;
    overflow: hidden;
    transition: var(--transition);
}

.kpi-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    background: var(--gold);
}

.kpi-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--shadow-gold);
    border-color: var(--border-gold);
}

.kpi-card .kpi-label {
    font-size: 12px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-bottom: 8px;
}

.kpi-card .kpi-value {
    font-size: 28px;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.2;
}

.kpi-card .kpi-sub {
    font-size: 12px;
    color: var(--text-secondary);
    margin-top: 4px;
}

.kpi-card.green::before { background: var(--green); }
.kpi-card.red::before { background: var(--red); }
.kpi-card.blue::before { background: var(--blue); }

/* ═══════════════════════════════════════════════════════════ */
/* COMPOSANT TABS CANONIQUE — à utiliser sur toutes les pages  */
/* Classe : .page-tabs > .page-tab                             */
/* Remplace progressivement : .fc-tabs, .kpi-tabs, etc.        */
/* ═══════════════════════════════════════════════════════════ */
.page-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 20px;
    padding: 5px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow-x: auto;
    scrollbar-width: thin;
}
.page-tab {
    padding: 9px 18px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    text-decoration: none;
    background: transparent;
    border: none;
    border-radius: var(--radius-sm);
    transition: var(--transition);
    cursor: pointer;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 7px;
}
.page-tab:hover {
    color: var(--text-primary);
    background: var(--bg-card);
}
.page-tab.active {
    color: var(--on-gold, #000);   /* auto-contraste : --gold peut être sombre (marque) */
    font-weight: 700;
    background: var(--gold);
    box-shadow: var(--shadow);
}
.page-tab .tab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    font-size: 11px;
    font-weight: 700;
    color: var(--text-primary);
    background: var(--bg-card);
    border-radius: 10px;
}
.page-tab.active .tab-badge {
    background: var(--bg-primary);
    color: var(--gold);
}

/* Container de contenu d'onglet (pour tabs JS, pas pages séparées) */
.page-tab-content { display: none; }
.page-tab-content.active { display: block; }
.kpi-card.orange::before { background: var(--orange); }
.kpi-card.purple::before { background: var(--purple); }

/* ============================================
   TABLES
   ============================================ */
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.data-table th {
    background: var(--bg-secondary);
    color: var(--gold);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1px;
    padding: 12px 16px;
    text-align: left;
    border-bottom: 2px solid var(--gold-dark);
    position: sticky;
    top: 0;
    z-index: 10;
}

.data-table td {
    padding: 10px 16px;
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    transition: var(--transition);
}

.data-table tr:hover td {
    background: var(--bg-card-hover);
    color: var(--text-primary);
}

.data-table .row-orange td {
    background: var(--orange-light);
    border-left: 3px solid var(--orange);
}

.data-table .amount { text-align: right; font-family: 'JetBrains Mono', monospace; }
.data-table .amount.positive { color: var(--green-text); }
.data-table .amount.negative { color: var(--red-text); }

/* ============================================
   BUTTONS
   ============================================ */
.btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 20px;
    border: none;
    border-radius: var(--brand-button-radius, var(--radius-sm));
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: var(--transition);
    text-decoration: none;
}

/* Un bouton désactivé doit SE VOIR désactivé.
   ─────────────────────────────────────────────
   Sans cette règle, il garde l'apparence d'un bouton actif ET le
   `cursor: pointer` ci-dessus : l'écran promet un clic qui ne se passera pas.
   L'utilisateur clique, rien n'arrive, et rien ne lui dit pourquoi.

   Mesuré le 2026-09-04 par le filet géométrique, qui compare l'opacité, le
   fond et le curseur d'un contrôle désactivé à ceux d'un contrôle actif :
   « Appliquer » et « Rattacher » sur /axone/opportunites, « Appliquer » sur
   /fabrication/ — et AUCUNE règle globale n'existait, alors que 30 gabarits
   posent un bouton désactivé. Le défaut n'était pas sur deux écrans, il était
   PARTOUT ; c'est le design system qui devait répondre, pas chaque page. */
.btn[disabled],
.btn:disabled {
    opacity: .45;
    cursor: not-allowed;
}

/* a11y : le teal porte mieux le texte FONCÉ (5.1–7.4:1) que le blanc (3.5:1).
   Gradient éclairci (--gold-light→--gold) + texte foncé, comme .page-tab.active.
   Hover via brightness() pour ne jamais dégrader le contraste. */
.btn-gold {
    background: linear-gradient(135deg, var(--gold-light), var(--gold));
    color: var(--on-gold, #111827);   /* auto-contraste selon la couleur de marque */
}
.btn-gold:hover {
    filter: brightness(1.06);
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(0, 151, 175, 0.3);
    color: var(--on-gold, #111827);
}

/* Bleu/rouge : tokens assombris (#2563eb / #dc2626) → texte blanc ≥4.5:1. */
.btn-blue { background: var(--blue); color: #fff; }
.btn-blue:hover { background: #1d4ed8; }   /* blanc 6.7:1 */

/* Vert clair : blanc 2.5:1 → texte foncé 7.0:1 (AAA). */
.btn-green { background: var(--green); color: #111827; }
.btn-green:hover { filter: brightness(1.05); }

.btn-red { background: var(--red); color: #fff; }
.btn-red:hover { background: #b91c1c; }     /* blanc 6.5:1 */

.btn-outline {
    background: transparent;
    border: 1px solid var(--border-gold);
    color: var(--gold);
}
.btn-outline:hover {
    background: var(--gold-glow);
    border-color: var(--gold);
    color: var(--gold-light);
}

.btn-sm { padding: 6px 12px; font-size: 12px; }

/* ============================================
   FORMS
   ============================================ */
.form-group { margin-bottom: 16px; }

.form-label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 1px;
    margin-bottom: 6px;
}

.form-input, .form-select {
    width: 100%;
    padding: 10px 14px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-primary);
    font-size: 14px;
    transition: var(--transition);
}

/* Sans cette règle, les placeholders gardent le gris PAR DÉFAUT du navigateur
   (#757575 sous Chrome) : 4.40:1 sur --bg-input clair et ~3:1 en sombre, sous
   le seuil WCAG AA de 4.5:1. --text-muted est calibré pour ce rôle sur les
   deux thèmes (5.88:1 clair, 4.69:1 sombre — scripts/audit_contraste.py).
   opacity:1 : Firefox applique sinon une opacité réduite par défaut. */
input::placeholder,
textarea::placeholder {
    color: var(--text-muted);
    opacity: 1;
}

/* Contour des champs à 3:1 (WCAG 1.4.11) : on re-scope la VARIABLE --border sur
   les contrôles de formulaire plutôt que border-color — ainsi les dizaines de
   templates qui écrivent `border:1px solid var(--border)` (inline ou en classe
   locale type .rfq-input) héritent du contour renforcé sans modification, et
   les règles qui posent border-color directement (:focus doré, états d'erreur)
   continuent de gagner. Les checkbox/radio en profitent aussi (même seuil). */
input, select, textarea {
    --border: var(--border-input);
}

.form-input:focus, .form-select:focus {
    outline: none;
    border-color: var(--gold);
    box-shadow: 0 0 0 3px var(--gold-glow);
}

/* ============================================
   CONTROLE BANQUE
   ============================================ */
/* Contrôle Banque — tableau compact */
.ctrl-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}
.ctrl-table thead th {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-muted);
    padding: 8px 10px;
    border-bottom: 1px solid var(--border);
    text-align: center;
    white-space: nowrap;
}
.ctrl-table thead th:first-child { text-align: left; }
.ctrl-table tbody td {
    padding: 8px 6px;
    border-bottom: 1px solid var(--border);
    text-align: center;
    vertical-align: middle;
}
.ctrl-table tbody tr:last-child td { border-bottom: none; }
.ctrl-table tbody tr:hover { background: rgba(148,163,184,0.04); }
.ctrl-bank-name { text-align: left !important; }
.ctrl-badge {
    display: inline-block;
    font-size: 11px;
    font-weight: 700;
    padding: 3px 10px;
    border-radius: var(--radius-sm);
    white-space: nowrap;
}
.ctrl-compact {
    padding: 4px 6px !important;
    font-size: 12px !important;
    text-align: center;
    max-width: 130px;
}
.btn-extract-solde {
    background: none;
    border: 1px solid var(--gold);
    border-radius: 4px;
    color: var(--gold);
    font-size: 12px;
    padding: 2px 6px;
    cursor: pointer;
    line-height: 1;
    flex-shrink: 0;
}
.btn-extract-solde:hover { background: rgba(212,175,55,0.1); }

.status-ok { color: var(--green-text); font-weight: 700; }
.status-ecart { color: var(--red-text); font-weight: 700; }
.status-attente { color: var(--text-muted); font-style: italic; }

/* ============================================
   ALERTS / FLASH
   ============================================ */
.alert {
    padding: 14px 20px;
    border-radius: var(--radius-sm);
    margin-bottom: 20px;
    font-size: 14px;
    display: flex;
    align-items: center;
    gap: 12px;
    animation: slideIn 0.3s ease-out;
}

.alert-success { background: rgba(16, 185, 129, 0.15); border: 1px solid var(--green-dark); color: var(--green-text); }
.alert-error { background: rgba(239, 68, 68, 0.15); border: 1px solid var(--red-dark); color: var(--red-text); }
.alert-warning { background: rgba(245, 158, 11, 0.15); border: 1px solid var(--orange); color: var(--orange-text); }
.alert-info { background: rgba(59, 130, 246, 0.15); border: 1px solid var(--blue-dark); color: var(--blue-text); }

@keyframes slideIn {
    from { opacity: 0; transform: translateY(-10px); }
    to { opacity: 1; transform: translateY(0); }
}

/* ============================================
   LOGIN PAGE
   ============================================ */
.login-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #0a0e17 0%, #111827 50%, #0d1321 100%);
    position: relative;
    overflow: hidden;
}

.login-page::before {
    content: '';
    position: absolute;
    top: -50%;
    left: -50%;
    width: 200%;
    height: 200%;
    background: radial-gradient(circle at 30% 40%, rgba(0, 151, 175, 0.07) 0%, transparent 50%),
                radial-gradient(circle at 70% 60%, rgba(0, 44, 48, 0.05) 0%, transparent 50%);
    animation: bgPulse 15s ease-in-out infinite alternate;
}

@keyframes bgPulse {
    from { transform: scale(1) rotate(0deg); }
    to { transform: scale(1.1) rotate(5deg); }
}

.login-box {
    background: var(--bg-card);
    border: 1px solid var(--border-gold);
    border-radius: 16px;
    padding: 48px;
    width: 420px;
    text-align: center;
    position: relative;
    box-shadow: var(--shadow), var(--shadow-gold);
}

.login-box h1 {
    font-size: 24px;
    font-weight: 700;
    color: var(--gold);
    letter-spacing: 4px;
    text-transform: uppercase;
    margin-bottom: 4px;
}

.login-box .login-subtitle {
    font-size: 12px;
    color: var(--text-muted);
    letter-spacing: 2px;
    text-transform: uppercase;
    margin-bottom: 32px;
}

.login-divider {
    width: 60px;
    height: 2px;
    background: linear-gradient(90deg, transparent, var(--gold), transparent);
    margin: 24px auto;
}

.btn-oauth {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: 100%;
    padding: 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-input);
    color: var(--text-primary);
    font-size: 15px;
    font-weight: 500;
    cursor: pointer;
    transition: var(--transition);
    margin-bottom: 12px;
    text-decoration: none;
}

.btn-oauth:hover {
    border-color: var(--gold);
    background: var(--gold-glow);
    color: var(--gold-light);
    transform: translateY(-1px);
}

/* ============================================
   CHARTS AREA
   ============================================ */
.chart-container {
    position: relative;
    height: 300px;
    padding: 20px;
}

/* ============================================
   PAGINATION
   ============================================ */
.pagination {
    display: flex;
    gap: 4px;
    justify-content: center;
    margin-top: 24px;
}

.pagination a, .pagination span {
    padding: 8px 14px;
    border-radius: var(--radius-sm);
    font-size: 13px;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    transition: var(--transition);
}

.pagination a:hover { border-color: var(--gold); color: var(--gold); }
.pagination .active {
    background: var(--gold);
    color: var(--on-gold, var(--bg-primary));   /* auto-contraste : --gold peut être sombre (marque) */
    border-color: var(--gold);
    font-weight: 600;
}

/* ============================================
   MATRIX REPORT TABLE
   ============================================ */
.matrix-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 12px;
}

.matrix-table th {
    background: var(--bg-secondary);
    color: var(--gold);
    padding: 8px 10px;
    font-weight: 600;
    font-size: 11px;
    text-align: right;
    border-bottom: 2px solid var(--gold-dark);
}

.matrix-table th:first-child { text-align: left; }

.matrix-table td {
    padding: 6px 10px;
    text-align: right;
    border-bottom: 1px solid var(--border);
    font-family: 'JetBrains Mono', monospace;
    color: var(--text-secondary);
}

.matrix-table td:first-child {
    text-align: left;
    font-family: 'Inter', sans-serif;
    font-weight: 500;
    color: var(--text-primary);
}

.matrix-table tr:hover td { background: var(--bg-card-hover); }
.matrix-table .total-row td {
    font-weight: 700;
    color: var(--gold);
    border-top: 2px solid var(--gold-dark);
    background: var(--gold-glow);
}

/* ============================================
   RESPONSIVE
   ============================================ */
@media (max-width: 1024px) {
    .sidebar { width: 200px; }
    .main-content { margin-left: 200px; padding: 20px; }
    /* !important : bat les styles inline repeat(N,1fr) posés sur .kpi-grid dans
       les templates (sinon 4-5 colonnes forcées → chiffres coupés lettre à lettre). */
    .kpi-grid { grid-template-columns: repeat(2, 1fr) !important; }
}

@media (max-width: 768px) {
    .sidebar {
        transform: translateX(-100%);
        position: fixed;
        /* Au-dessus du bottom-nav (1000) pour passer devant la nav quand ouverte */
        z-index: 1100;
        /* Plein écran sur mobile (le top:88px desktop laisse la top-bar visible) */
        top: 0;
        height: 100vh;
        height: 100dvh;  /* dynamic viewport height — gère la barre Safari */
        max-width: 85vw;
        padding-top: env(safe-area-inset-top, 0);
        box-shadow: 4px 0 24px rgba(0,0,0,0.4);
    }
    .sidebar.open { transform: translateX(0); }
    /* Overlay au-dessus du bottom-nav, sous la sidebar — couvre tout l'écran */
    .sidebar-overlay {
        top: 0;
        z-index: 1050;
    }
    .main-content { margin-left: 0; }
    /* KPI : 2 colonnes sur mobile (≈170px/carte à 375px → le grand nombre tient
       sur une ligne). !important bat les styles inline repeat(4/5,1fr) des
       templates qui écrasaient les chiffres verticalement (« 3635 » en 3/6/3/5). */
    .kpi-grid { grid-template-columns: repeat(2, 1fr) !important; }
    /* Défensif : les en-têtes/barres bespoke en flex « space-between » (titre +
       boutons) passent en multi-lignes sur mobile. Évite que le titre s'écrase à
       ~1 caractère de large et se coupe verticalement (ex. « Emails » vertical)
       quand les boutons (flex-shrink:0) refusent de rétrécir. Ne wrappe que si le
       contenu déborde → inoffensif quand tout tient sur une ligne. */
    [style*="justify-content:space-between"],
    [style*="justify-content: space-between"] { flex-wrap: wrap; }
    .ctrl-table { font-size: 11px; }
    .ctrl-compact { max-width: 100px; font-size: 11px !important; }
    .top-bar { padding: 0 8px; }
    .top-bar-center { display: none; }
    .top-bar-left .top-bar-app { display: none; }
    .top-bar-left .top-bar-sep { display: none; }
    .top-bar-right .tb-sep { display: none; }
    /* Tap targets Apple HIG ≥ 44×44 px (au lieu de 28×28) */
    .top-bar-right .tb-btn { width: 44px; height: 44px; font-size: 16px; }
    /* Inputs : font-size ≥ 16px pour empêcher le zoom auto iOS au focus */
    .form-input, .form-select, .form-textarea,
    input[type="text"], input[type="email"], input[type="password"],
    input[type="number"], input[type="search"], input[type="tel"],
    input[type="url"], input[type="date"], input[type="datetime-local"],
    input[type="time"], select, textarea {
        font-size: 16px !important;
    }
    /* Boutons compacts : hauteur minimale 40px pour rester tappables */
    .btn-sm { min-height: 40px; padding: 8px 14px; }
    .btn { min-height: 44px; }
    /* Tableaux denses : scroll horizontal au lieu de casser le layout */
    .data-table, .matrix-table, .ctrl-table {
        display: block;
        max-width: 100%;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    /* Cartes : padding réduit pour gagner de l'espace mobile */
    .card { padding: 14px !important; }
    /* Onglets de page : tap targets agrandis */
    .page-tab { padding: 12px 14px; min-height: 44px; }
    /* Safe-area iPhone (encoche / Dynamic Island) : géré DANS le bloc
       « MODE MINIMAL » ci-dessous, qui porte les !important gagnants
       (padding-top = inset, hauteur = 56px + inset). Le padding-top posé ici
       serait annulé par le `padding: 0 10px !important` du bloc minimal. */
    /* Padding-bottom = bottom-nav height (60px) + safe area */
    .main-content { padding-bottom: calc(72px + env(safe-area-inset-bottom, 0)); }

    /* ── Top-bar mobile : MODE MINIMAL — ne garder que l'essentiel utilisable au pouce ── */
    /* Layout : flex space-between, juste logo à gauche + 3 boutons à droite */
    .top-bar {
        display: flex !important;
        justify-content: space-between !important;
        align-items: center !important;
        gap: 8px !important;
        /* Safe-area iPhone : padding-top = inset (encoche/Dynamic Island),
           hauteur = 56px + inset. box-sizing:border-box → sans le « +inset »
           la hauteur fixe mange le padding et le logo passe SOUS l'horloge iOS.
           env()=0 sur les appareils sans encoche → comportement inchangé. */
        padding: env(safe-area-inset-top, 0) 10px 0 10px !important;
        height: calc(56px + env(safe-area-inset-top, 0)) !important;
        box-sizing: border-box !important;
    }
    .top-bar-left { flex: 0 1 auto !important; min-width: 0 !important; overflow: hidden !important; }
    .top-bar-right { flex: 0 0 auto !important; gap: 4px !important; }

    /* Logo : SVG + "mySYNAPSE" compact, le reste caché */
    .top-bar-left a span.fw-800 { font-size: 13px !important; letter-spacing: 0.8px !important; }
    .top-bar-left a span.text-xs { display: none !important; }  /* "by SEiiA" */

    /* Cacher TOUT le reste à gauche : liens "● mySynapse", "🛠 AppStore", séparateurs */
    .top-bar-app, .top-bar-left .top-bar-sep { display: none !important; }

    /* Centre : horloge et recherche → cachés (recherche accessible via le module bar) */
    .top-bar-center, #top-bar-clock, .global-search-wrap { display: none !important; }

    /* Sélecteur entreprise : conserver uniquement le badge coloré (initiales), masquer nom + chevron.
       Deux markups coexistent (base.html) : <button> si l'user a plusieurs
       entreprises, simple <span> sinon. Cibler .tb-ent-nom couvre les deux ; le
       nth-of-type() précédent ne matchait que le <button> → chez les
       mono-entreprise « E-COBOT » s'affichait en entier, wrappait sur 2 lignes et
       se faisait clipper par le overflow:hidden du .top-bar-left. */
    .tb-ent-switcher { margin-left: 4px !important; min-width: 0 !important; }
    .tb-ent-switcher .tb-ent-nom,
    .tb-ent-switcher .tb-ent-chevron { display: none !important; }
    .tb-ent-switcher button { padding: 4px 6px !important; }

    /* À droite : ne garder QUE notif principale (cloche), avatar, logout. Tout le reste masqué. */
    .top-bar-right #topbar-journal-btn,
    .top-bar-right #topbar-notif-bell,
    .top-bar-right #btn-refresh,
    .top-bar-right #theme-toggle-btn,
    .top-bar-right #tb-aide-btn,
    .top-bar-right #tb-admin-btn,
    .top-bar-right .lang-switch,
    .top-bar-right .tb-sep,
    .top-bar-right a[href*="/chess"],
    .top-bar-right a[href*="/sport"] { display: none !important; }

    /* Avatar + logout : taille tap-friendly */
    .top-bar-right .tb-btn { width: 40px !important; height: 40px !important; }

    /* ── Module-bar (barre des Neurones) : scrollable horizontalement ── */
    /* La top-bar mobile fait 56px → la module-bar doit s'aligner dessous */
    .module-bar {
        top: 56px !important;
        height: 52px !important;
        justify-content: flex-start !important;  /* Scroll horizontal naturel */
        padding: 0 8px !important;
        gap: 4px !important;
        scroll-snap-type: x proximity;
        -webkit-overflow-scrolling: touch;
    }
    /* Le toggle ▲▼ remonte avec la top-bar */
    .module-bar-toggle { top: 108px !important; }  /* 56 + 52 */
    .module-bar-collapsed ~ .module-bar-toggle,
    html.pref-modulebar-collapsed #module-bar ~ .module-bar-toggle { top: 56px !important; }
    /* Tap targets module-tab : 44×44 minimum */
    .module-tab {
        padding: 10px 14px !important;
        min-height: 44px !important;
        scroll-snap-align: start;
        gap: 5px !important;
    }
    .module-icon { font-size: 22px !important; }  /* + grand pour rester lisible */
    .module-label { display: none !important; }   /* Espace économisé, icônes seules */

    /* ── SIDEBAR CONTEXTUELLE : cachée en mobile, le contenu doit prendre toute la largeur ── */
    /* Le screenshot Stock&Inventaire montrait la sidebar (220px) qui poussait le contenu hors écran. */
    .sidebar, .context-sidebar {
        transform: translateX(-100%) !important;
        position: fixed !important;
        z-index: 105;
    }
    .sidebar.open, .context-sidebar.open { transform: translateX(0) !important; }
    /* Le contenu principal doit utiliser TOUTE la largeur sur mobile, jamais de margin-left résiduel */
    .main-content,
    .app-body.has-module-bar .main-content,
    .app-body.has-module-bar.has-sidebar .main-content {
        margin-left: 0 !important;
        width: 100% !important;
        max-width: 100vw !important;
    }
    /* Top de l'app-body : sous la top-bar mobile (56px) + inset encoche iPhone,
       sinon le contenu se glisse derrière la barre agrandie. */
    .app-body, .app-body.has-module-bar { margin-top: calc(56px + env(safe-area-inset-top, 0)) !important; }
    /* Sidebar contextuelle si module ouvert : sous la nouvelle barre (en off-canvas) */
    .context-sidebar { top: calc(56px + env(safe-area-inset-top, 0)) !important; height: calc(100dvh - 56px - env(safe-area-inset-top, 0)) !important; width: 80vw !important; max-width: 280px; }

    /* ── KPIs page d'accueil : 2 colonnes au lieu d'auto-fit qui éparpille ── */
    .dash-kpis { grid-template-columns: repeat(2, 1fr) !important; gap: 8px !important; }
    .dash-kpi { padding: 0.7rem !important; gap: 0.5rem !important; min-width: 0 !important; }
    .dash-kpi-icon { width: 36px !important; height: 36px !important; font-size: 1.1rem !important; }
    .dash-kpi-val { font-size: 1.3rem !important; }
    .dash-kpi-label { font-size: 0.62rem !important; }
    /* Montants KPI (dashboards Glia/Cortex/Trésorerie) : 28px coupait les grands
       montants sur 2 lignes (« 603 784 € » → 603 / 784 €) en carte 2 colonnes.
       Réduit pour tenir sur UNE ligne. */
    .kpi-card .kpi-value { font-size: 1.35rem !important; line-height: 1.25 !important; }

    /* ── Anti-débordement horizontal global ── */
    /* Empêche tout scroll latéral causé par un élément qui dépasse (cards, titres, sidebar). */
    html, body { overflow-x: hidden !important; max-width: 100vw; }
    /* Empêche le « rebond » iOS (rubber-band) qui fait bouger la barre du bas
       fixe pendant le scroll (retour CEO) : overscroll-behavior coupe le
       chaînage/bounce, et translateZ(0) isole la barre sur sa propre couche. */
    html, body { overscroll-behavior-y: none; }
    /* Barres fixes haut + bas : couche GPU dédiée + fond opaque → elles ne
       clignotent plus / ne « décalent » plus pendant le défilement iOS, et le
       contenu ne transparaît jamais derrière (lisibilité). */
    .top-bar, .bottom-nav {
        transform: translateZ(0);
        -webkit-transform: translateZ(0);
        backface-visibility: hidden;
        -webkit-backface-visibility: hidden;
        will-change: transform;
    }
    /* ── APP-SHELL (body.has-app-shell, posée par base.html) ──
       Malgré overscroll-behavior + couche GPU ci-dessus, iOS fait toujours
       « bouger » les barres fixes : le rubber-band du DOCUMENT entraîne les
       éléments position:fixed pendant le défilement (retour CEO 2026-07-20).
       Ici le document ne défile plus du tout — body figé à la hauteur du
       viewport, le scroll vit DANS .app-body. Les barres n'ont plus rien qui
       puisse les déplacer. Les pages hors app-layout (login, embed,
       whiteboard) ne portent pas la classe et gardent le scroll document. */
    body.has-app-shell {
        /* ── La barre du bas avait DISPARU en PWA installée (retour CEO 2026-08-23) ──
           Deux défauts cumulés, invisibles sur un navigateur de bureau parce
           que vh, dvh et svh y valent la même chose :

           1. La règle de base `body` pose `min-height: 100vh` (l.561) : elle n'était
              jamais neutralisée ici. Un PLANCHER en `vh` sous une HAUTEUR en `dvh`,
              c'est contradictoire : dès que `vh > dvh` — tout iOS dès que les
              barres du système occupent de la place — le plancher gagne, le
              shell dépasse la zone visible, et la barre du bas (dernier item
              du flux depuis qu'elle n'est plus `fixed`) part SOUS le pli.
              `overflow:hidden` interdit alors d'aller la chercher : elle n'est
              pas déplacée, elle est inatteignable.
           2. `dvh` suit la zone visible du MOMENT. Toute imprécision, même
              transitoire, se paie par la disparition de la navigation
              principale du mobile.

           D'où `svh` : la plus PETITE hauteur de viewport (barres système
           déployées). Le contenu tient donc TOUJOURS, quel que soit l'état des
           barres. Coût assumé : dans Safari, une bande vide quand la barre
           d'URL se rétracte. En PWA installée — le cas de Seb — svh, dvh et
           lvh sont égaux : aucun coût.
           NE PAS « optimiser » en revenant à dvh sans retester sur un iPhone
           RÉEL, en PWA : c'est le 3ᵉ passage sur ces barres. */
        height: 100vh;    /* repli pour les moteurs sans unités de viewport modernes */
        height: 100svh;
        min-height: 0;
        overflow: hidden !important;
        /* Shell en colonne : les barres deviennent des ITEMS de ce shell et
           non plus des éléments `fixed` (cf. bloc « barres dans le flux »). */
        display: flex;
        flex-direction: column;
        /* Repère des flottants ancrés en bas (bandeau cookies, barre
           d'enregistrement, bannière PWA) : ils se calent sur le shell — donc
           sur la zone visible — au lieu du viewport iOS. Cf. bloc plus bas. */
        position: relative;
    }
    body.has-app-shell .app-layout {
        flex: 1 1 auto;
        min-height: 0;
        /* Repère des panneaux ancrés « en bas de l'écran » (feuilles de
           sous-menu) : ils se calent sur le shell, pas sur le viewport iOS.
           .app-layout s'arrêtant juste au-dessus de la barre du bas, un
           `bottom: 8px` suffit et reste juste quelle que soit la hauteur. */
        position: relative;
    }

    /* ── Les barres SORTENT de position:fixed (retour CEO 2026-08-22) ──
       Le shell ci-dessus fige bien le document, mais les deux barres étaient
       restées `fixed` : iOS les ancre alors au LAYOUT viewport (100vh, donc la
       hauteur SANS la barre d'URL) tandis que le shell mesure 100dvh (la zone
       réellement visible). L'écart entre les deux sortait la barre du bas hors
       du shell — elle flottait au milieu de l'écran, le contenu restant
       visible dessous — et il variait pendant le défilement, au rythme de la
       barre d'URL qui se rétracte : « les barres bougent ».
       Devenues items flex du shell, elles n'ont plus AUCUN viewport de
       référence : leur place est celle que le flux leur donne. C'est ce qui
       manquait au correctif du 2026-07-20, qui n'avait figé que le document. */
    body.has-app-shell .top-bar,
    body.has-app-shell .bottom-nav {
        /* `relative` et pas `static` : le menu du header et le liseré de
           l'onglet actif (.bn-item.active::before) sont en absolute et se
           positionnent par rapport à leur barre. */
        position: relative;
        flex: 0 0 auto;
        left: auto;
        right: auto;
        top: auto;
        bottom: auto;
    }
    /* ── Les autres flottants suivent les barres (retour CEO 2026-08-24) ──
       Même mécanisme, mêmes conséquences : ancrés en bas et laissés en
       `fixed`, le bandeau cookies, la barre d'enregistrement et la bannière
       PWA se calent sur le layout viewport iOS (hauteur SANS la barre d'URL)
       alors que le shell mesure la zone visible. Ils se retrouvaient trop bas,
       partiellement hors écran — un bouton « Enregistrer » à moitié coupé.
       En `absolute` dans le shell (`body` est le repère, cf. plus haut), leur
       `bottom` compte à partir du bas de ce qu'on voit vraiment.
       `!important` : #cookieBanner porte `position:fixed` en STYLE EN LIGNE,
       qu'aucune règle de feuille ne peut battre autrement. */
    body.has-app-shell #cookieBanner,
    body.has-app-shell #save-bar,
    body.has-app-shell .pwa-install-banner {
        position: absolute !important;
    }
    /* La sidebar, elle, RESTE `fixed` : elle vit dans `.app-body`, le conteneur
       qui défile. En `absolute` elle défilerait avec le contenu et se ferait
       clipper — le remède serait pire. On corrige donc seulement sa hauteur,
       qui était en `100vh` : sur iOS elle dépassait le bas de l'écran et son
       pied (AppStore, copyright) devenait inatteignable. Même unité que le
       shell — `svh`, la plus petite hauteur — pour rester cohérent avec la
       décision du 2026-08-23 documentée plus haut. La réserve en bas laisse le
       pied de la sidebar au-dessus de la barre de navigation. */
    body.has-app-shell .sidebar,
    body.has-app-shell .context-sidebar {
        height: calc(100vh - 56px);
        height: calc(100svh - 56px);
        padding-bottom: calc(60px + env(safe-area-inset-bottom, 0));
        box-sizing: border-box;
    }

    /* ── Entête : le logo seul (retour CEO 2026-08-22) ──
       « mySYNAPSE » + « by SEiiA » ne tenaient pas à côté du bouton Retour et
       des icônes d'action : le nom s'affichait tronqué en « my S », ce qui fait
       plus sale que pas de nom du tout. Le logo suffit à identifier l'app quand
       on est déjà dedans — et il reste cliquable vers l'accueil. */
    .tb-brand-name, .tb-brand-by { display: none; }
    /* Logo de marque d'un tenant (white-label) : borné pour la même raison. */
    .tb-brand-logo { max-width: 120px !important; }

    /* Les compensations de la top-bar `fixed` deviennent des trous : le
       contenu réservait 56px pour une barre qui occupe maintenant sa place. */
    body.has-app-shell .app-body,
    body.has-app-shell .app-body.has-module-bar,
    body.has-app-shell .app-body.has-module-bar.modulebar-collapsed,
    html.pref-modulebar-collapsed body.has-app-shell .app-body.has-module-bar {
        margin-top: 0 !important;
    }
    /* Idem en bas : plus besoin de réserver la hauteur de la bottom-nav, elle
       ne recouvre plus rien. On garde une respiration + la safe-area. */
    body.has-app-shell .main-content {
        padding-bottom: calc(12px + env(safe-area-inset-bottom, 0)) !important;
    }
    body.has-app-shell .app-body {
        flex: 1;
        min-height: 0;
        overflow-y: auto;
        overflow-x: hidden;
        -webkit-overflow-scrolling: touch;
        /* Le bounce reste permis DANS le conteneur, sans entraîner les barres. */
        overscroll-behavior-y: contain;
        /* Un SEUL scroller : sans ceci, .main-content (étiré par le flex +
           rendu overflow-y:auto par l'overflow-x:hidden global ci-dessous)
           défilerait à la place de .app-body. flex-start le laisse grandir à
           la hauteur de son contenu, c'est .app-body qui défile. */
        align-items: flex-start;
    }
    body.has-app-shell .main-content { overscroll-behavior-y: contain; }
    .app-layout, .app-body, main, .main-content { max-width: 100vw; overflow-x: hidden; }
    /* Cards et titres : forcer le wrap du texte long pour qu'il ne dépasse pas */
    .card, .dash-kpi, .dash-prio, .dash-todo-item,
    h1, h2, h3, h4, .dash-hello {
        word-wrap: break-word;
        overflow-wrap: break-word;
        word-break: normal;
    }
    /* Cards de priorités/tâches : élément de grille en min-width:auto → la colonne
       ne peut pas descendre sous la largeur du titre en nowrap, la carte sort de
       l'écran (retour terrain iPhone). min-width:0 libère le rétrécissement. */
    .dash-prio { min-width: 0 !important; }
    /* Titre des cards de priorités/tâches : doit wrapper, pas tronquer.
       Classes réelles = .dash-prio-name / .dash-prio-desc ; les .dash-prio-title
       et .dash-todo-title ciblées auparavant n'existent dans aucun markup, la
       règle n'avait donc aucun effet. */
    .dash-prio-name, .dash-prio-desc {
        white-space: normal !important;
        overflow: visible !important;
        text-overflow: clip !important;
    }
    /* H1 "Bonjour" : pas trop énorme sur mobile */
    .dash-hello { font-size: 1.6rem !important; }
}
@media (max-width: 1100px) {
    .top-bar-center { font-size: 11px; gap: 4px; }
    .top-bar-left .top-bar-app { display: none; }
    .top-bar-left .top-bar-sep:last-of-type { display: none; }
    .module-tab { padding: 10px 12px; font-size: 13px; gap: 5px; }
    .module-icon { font-size: 15px; }
}
@media (max-width: 900px) {
    .module-tab { padding: 8px 8px; font-size: 12px; gap: 3px; }
    .module-label { display: none; }
    .module-icon { font-size: 18px; }
    .module-tab { padding: 10px 14px; }
}

/* ============================================
   UTILITIES
   ============================================ */
.text-gold { color: var(--gold); }
.text-green { color: var(--green-text); }
.text-red { color: var(--red-text); }
.text-blue { color: var(--blue-text); }
.text-muted { color: var(--text-muted); }
.text-right { text-align: right; }
.text-center { text-align: center; }
.font-mono { font-family: 'JetBrains Mono', monospace; }
.mt-1 { margin-top: 8px; }
.mt-2 { margin-top: 16px; }
.mt-3 { margin-top: 24px; }
.mb-2 { margin-bottom: 16px; }
.mb-3 { margin-bottom: 24px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.flex { display: flex; }
.flex-between { display: flex; justify-content: space-between; align-items: center; }
.gap-2 { gap: 16px; }
.hidden { display: none; }

/* Entêtes de page (titre + rangée de boutons) : TOUJOURS autoriser le retour
   à la ligne. `flex-wrap: wrap` n'agit que si le contenu ne tient pas — sur
   grand écran il ne change donc rien, et sur écran étroit il évite de pousser
   la page en scroll horizontal.
   Ces 2 règles n'existaient qu'entre 769 et 1180 px : en dessous de 769, la
   rangée restait en `nowrap` et débordait (mesuré : 850 px de large dans un
   viewport de 375 px sur /contacts/ et /dashboard/, coupée sans ascenseur car
   masquée par `overflow-x:hidden`). Le pire des cas : contenu invisible ET
   inatteignable. Cf. tests/e2e/test_responsive_matrix.py. */
.main-content .flex-between { flex-wrap: wrap; row-gap: 8px; }
.main-content .flex-between > .flex { flex-wrap: wrap; row-gap: 8px; }

/* Scrollbar */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: var(--bg-primary); }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--gold-dark); }

/* ============================================
   NO-SIDEBAR LAYOUT (home)
   ============================================ */
.app-body.no-sidebar .sidebar.hidden {
    display: none !important;
}
.app-body.no-sidebar .main-content {
    margin-left: 0 !important;
}

/* ============================================
   SIDEBAR TOGGLE (hamburger)
   ============================================ */
.sidebar-toggle-btn {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font-size: 18px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s;
    flex-shrink: 0;
}
.sidebar-toggle-btn:hover {
    background: rgba(255,255,255,0.08);
    color: var(--text-primary);
}

/* Sidebar collapsed state */
.sidebar.sidebar-hidden {
    transform: translateX(-100%);
    pointer-events: none;
}

/* When sidebar is shifted (hidden mode), main content takes full width */
.main-content.sidebar-shifted {
    margin-left: 0 !important;
}

/* ── Bouton replier sidebar ── */
.sidebar-collapse-btn {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 24px;
    height: 24px;
    border-radius: 6px;
    border: 1px solid var(--border);
    background: var(--bg-secondary);
    color: var(--text-muted);
    font-size: 10px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 10;
    transition: all 0.2s;
    opacity: 0.5;
}
.sidebar-collapse-btn:hover {
    opacity: 1;
    background: var(--gold);
    color: var(--on-gold, #000);
    border-color: var(--gold);
}

/* ── Bouton réouvrir sidebar ── */
.sidebar-expand-btn {
    position: fixed;
    top: 48px;
    left: 0;
    width: 24px;
    height: 48px;
    border-radius: 0 8px 8px 0;
    border: 1px solid var(--border);
    border-left: none;
    background: var(--bg-sidebar);
    color: var(--text-muted);
    font-size: 10px;
    cursor: pointer;
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 101;
    transition: all 0.2s;
}
.sidebar-expand-btn:hover {
    background: var(--gold);
    color: var(--on-gold, #000);
    width: 32px;
}
.sidebar-expand-btn.visible {
    display: flex;
}

/* Anti-flash : état "sidebar masquée" (préférence desktop persistante) appliqué
   dès le 1er paint via la classe html.pref-sidebar-hidden posée en <head>. Desktop
   only — sur mobile la sidebar utilise .open/overlay, on ne doit pas la forcer. */
@media (min-width: 769px) {
    html.pref-sidebar-hidden .sidebar { transform: translateX(-100%); pointer-events: none; }
    html.pref-sidebar-hidden .main-content { margin-left: 0 !important; }
    html.pref-sidebar-hidden .sidebar-expand-btn { display: flex; }
}

/* Smooth transitions */
.sidebar {
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.main-content {
    transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Overlay when sidebar is open on top of content */
.sidebar-overlay {
    display: none;
    position: fixed;
    top: 40px;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0,0,0,0.4);
    z-index: 99;
    opacity: 0;
    transition: opacity 0.3s ease;
}
.sidebar-overlay.active {
    display: block;
    opacity: 1;
}

/* ============================================
   MODULE BAR — Horizontal top navigation
   ============================================ */
.module-bar {
    position: fixed;
    top: 40px;
    left: 0;
    right: 0;
    height: 48px;
    background: linear-gradient(180deg, #001a1e 0%, #002c30 100%);
    border-bottom: 1px solid rgba(0, 151, 175, 0.2);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 0 16px;
    z-index: 102;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
}
.module-bar::-webkit-scrollbar { display: none; }

/* Bouton toggle bandeau — fixé, toujours visible */
.module-bar-toggle {
    position: fixed;
    left: 50%;
    top: 82px;
    transform: translateX(-50%);
    background: rgba(0,44,48,0.9);
    border: 1px solid rgba(0,151,175,0.2);
    color: var(--text-muted);
    font-size: 8px;
    width: 32px;
    height: 12px;
    border-radius: 0 0 6px 6px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s;
    z-index: 112;
    opacity: 0.4;
    padding: 0;
}
.module-bar-toggle:hover { opacity: 1; color: #00c8ff; background: rgba(0,44,48,1); }

/* État réduit du bandeau (.module-bar-collapsed posé par le JS ; le sélecteur
   html.pref-modulebar-collapsed applique le même état dès le 1er paint = anti-flash) */
.module-bar-collapsed,
html.pref-modulebar-collapsed #module-bar {
    height: 0 !important;
    min-height: 0;
    padding: 0 !important;
    border-bottom: none !important;
    overflow: hidden;
}
/* Quand réduit, le toggle remonte */
.module-bar-collapsed ~ .module-bar-toggle,
html.pref-modulebar-collapsed #module-bar ~ .module-bar-toggle { top: 40px; }

/* Ajuster app-body quand bandeau réduit : on RÉDUIT le margin-top (40px = top-bar
   seule) pour que le contenu remonte des 48px libérés. `top` n'avait aucun effet,
   .app-body étant en position static → le trou restait sous le bandeau réduit.
   Scopé desktop (≥769px) : sur mobile (≤768px) la module-bar est masquée et le
   margin reste géré par la media query 768px (margin-top:108px), inchangé. */
@media (min-width: 769px) {
    .app-body.has-module-bar.modulebar-collapsed,
    html.pref-modulebar-collapsed .app-body.has-module-bar { margin-top: 40px !important; }
}

.module-tab {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 10px 18px;
    font-size: 14px;
    font-weight: 600;
    color: #b0bec5;
    text-decoration: none;
    white-space: nowrap;
    border-radius: 6px 6px 0 0;
    border-bottom: 2px solid transparent;
    transition: all 0.2s ease;
    cursor: pointer;
    position: relative;
}
.module-tab:hover {
    color: var(--text-primary);
    background: rgba(255,255,255,0.05);
}
.module-tab.active {
    color: #00c8ff;
    border-bottom-color: #00c8ff;
    background: rgba(0, 200, 255, 0.08);
}
.module-icon {
    font-size: 18px;
    line-height: 1;
}
.module-label {
    letter-spacing: 0.3px;
}

/* Adjust app-body to account for module-bar */
.app-body.has-module-bar {
    margin-top: 40px; /* top-bar seule — bandeau modules retiré (navigation via sidebar) */
}

/* Context sidebar adjustments */
.context-sidebar {
    width: 220px;
}
@media (min-width: 769px) {
    .context-sidebar {                           /* desktop : sous la top-bar (bandeau modules retiré) */
        top: 40px !important;
        height: calc(100vh - 40px) !important;
    }
}

/* Module panel in sidebar */
.module-panel {
    display: none;
    padding: 4px 0;
}
.module-panel-title {
    padding: 12px 16px 8px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--text-muted);
    border-bottom: 1px solid rgba(255,255,255,0.06);
    margin-bottom: 4px;
}

/* ═══ Whiteboard en plein écran — cache sidebar ═══ */
body.wb-fullscreen .sidebar,
body.wb-fullscreen #sidebarExpandBtn,
body.wb-fullscreen .module-bar,
body.wb-fullscreen .top-bar {
    display: none !important;
}
body.wb-fullscreen .main-content,
body.wb-fullscreen main,
body.wb-fullscreen #main {
    margin-left: 0 !important;
    padding: 0 !important;
    max-width: 100% !important;
    width: 100% !important;
}
body.wb-fullscreen {
    overflow: hidden;
}

/* Sous-groupes à l'intérieur d'un module-panel (ex: Admin & Config).
   Sébastien 2026-05-15 : couleur blanche + gras pour meilleure lisibilité
   (l'ancien `color: var(--gold); opacity: 0.75` se confondait avec le fond
   sombre, illisible ton-sur-ton). */
.nav-group-title {
    padding: 10px 16px 4px;
    margin-top: 8px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: #ffffff;
    opacity: 1;
    border-top: 1px solid rgba(255,255,255,0.04);
}
/* Nom du module dans le titre de panneau : l'accent brut ne passe pas a 11px
   sur le teal de la barre (mesure : TURBO 2.38:1, AXONE 3.56:1). L'accent du
   module arrive par --np (pose en ligne dans base.html). */
.module-panel-title span {
    color: color-mix(in srgb, var(--np, var(--sidebar-accent)) 60%, #fff);
}

.module-panel-title + .nav-group-title {
    margin-top: 4px;
    border-top: none;
}

/* Make nav items slightly more compact in context sidebar */
.context-sidebar .nav-item {
    padding: 8px 16px;
    font-size: 13px;
    gap: 10px;
}

/* Main content when sidebar shows with module bar */
.app-body.has-module-bar .main-content {
    margin-left: 220px;
}
.app-body.has-module-bar .main-content.sidebar-shifted {
    margin-left: 0 !important;
}

/* Light theme adjustments for module bar */
[data-theme="light"] .module-bar {
    background: linear-gradient(180deg, #001a1e 0%, #002c30 100%);
    border-bottom-color: rgba(0, 151, 175, 0.2);
}
[data-theme="light"] .module-tab {
    color: #c8d6e0;
}
[data-theme="light"] .module-tab:hover {
    color: #f1f5f9;
}
[data-theme="light"] .module-tab.active {
    color: #00c8ff;
}

/* ============================================
   UTILITY CLASSES — TYPOGRAPHY
   ============================================ */
.text-xs   { font-size: var(--font-xs)  !important; }  /* 10px */
.text-sm   { font-size: var(--font-sm)  !important; }  /* 11px */
.text-base { font-size: var(--font-base) !important; }  /* 13px */
.text-md   { font-size: var(--font-md)  !important; }  /* 14px */
.text-lg   { font-size: var(--font-lg)  !important; }  /* 16px */
.text-xl   { font-size: var(--font-xl)  !important; }  /* 18px */
.text-2xl  { font-size: var(--font-2xl) !important; }  /* 24px */
.text-3xl  { font-size: var(--font-3xl) !important; }  /* 28px */
.text-4xl  { font-size: 32px !important; }
.text-5xl  { font-size: 48px !important; }

.text-muted     { color: var(--text-muted) !important; }
.text-secondary { color: var(--text-secondary) !important; }
.text-primary   { color: var(--text-primary) !important; }
.text-gold      { color: var(--gold) !important; }
.text-green     { color: var(--green-text) !important; }
.text-red       { color: var(--red-text) !important; }
.text-orange    { color: var(--orange-text) !important; }
.text-blue      { color: var(--blue-text) !important; }
.text-purple    { color: var(--purple-text) !important; }

.fw-400 { font-weight: 400 !important; }
.fw-500 { font-weight: 500 !important; }
.fw-600 { font-weight: 600 !important; }
.fw-700 { font-weight: 700 !important; }
.fw-800 { font-weight: 800 !important; }

.text-upper { text-transform: uppercase !important; }
.text-center { text-align: center !important; }

/* ============================================
   UTILITY CLASSES — DASHBOARD CARDS
   ============================================ */
.stat-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px;
}
.stat-card:hover {
    border-color: var(--border-gold);
    box-shadow: var(--shadow-gold);
}
.stat-value {
    font-size: var(--font-2xl);
    font-weight: 800;
    color: var(--text-primary);
    line-height: 1.2;
}
.stat-label {
    font-size: var(--font-sm);
    color: var(--text-muted);
    text-transform: uppercase;
    margin-top: 4px;
}
.stat-trend {
    font-size: var(--font-sm);
    margin-top: 8px;
}
.stat-trend.up   { color: var(--green-text); }
.stat-trend.down { color: var(--red-text); }

/* ============================================
   ENTÊTE DE PAGE — composant standard
   ============================================
   Objectif : homogénéiser les entêtes (sous le bandeau de module). La base
   .page-header normalise espacement + typo SANS forcer de layout → rétro-
   compatible avec les pages existantes (legacy <h1> ou .page-title). La
   variante .ph (posée par la macro Jinja `page_header`) ajoute le layout
   standard « titre + sous-titre + actions ». */
.page-header {
    margin: 0 0 20px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--border);
}
.page-header h1,
.page-header .page-title {
    margin: 0;
    font-size: var(--font-2xl);
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1.2;
}
.page-header .page-subtitle {
    margin: 4px 0 0;
}
.page-title {
    font-size: var(--font-2xl);
    font-weight: 700;
    color: var(--text-primary);
}
.page-subtitle {
    font-size: var(--font-base);
    color: var(--text-muted);
}

/* Variante standard (macro page_header) : titre à gauche, actions à droite */
.page-header.ph {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}
.page-header.ph .ph-main { min-width: 0; }
.page-header.ph .page-title {
    display: flex;
    align-items: center;
    gap: 10px;
}
.page-header.ph .ph-title-icon {
    color: var(--page-accent, var(--gold));   /* couleur du neurone courant */
    font-size: 1.05em;
    line-height: 1;
    flex-shrink: 0;
}
.page-header.ph .ph-actions {
    margin-left: auto;
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
}

/* Chart container */
.chart-container {
    position: relative;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px;
}
.chart-container:hover {
    border-color: var(--border-gold);
}
.chart-title {
    font-size: var(--font-md);
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 12px;
}
.chart-canvas-wrap {
    position: relative;
    height: 280px;
}

/* ============================================
   BOTTOM NAV MOBILE — Sprint 1 Phase C refonte mobile
   Visible < 768px uniquement, fixée en bas avec safe-area iPhone.
   5 boutons (Care / Axone / Synapse / Dendrite / + Plus).
   ============================================ */
.bottom-nav {
    display: none;  /* Caché desktop par défaut */
}
/* ============================================
   POLISH iPhone 15 Pro Max — Sprint 2 fini
   ============================================ */
@media (max-width: 768px) {
    /* Tap-highlight : remplace le rectangle gris bleu par-défaut iOS par
       un highlight discret en or (cohérent avec la marque). */
    a, button, [role="button"], .nav-item, .module-tab, .btn, .bn-item {
        -webkit-tap-highlight-color: rgba(244, 191, 78, 0.2);
    }
    /* Désactive la sélection texte sur les éléments d'UI tactiles
       (boutons, nav, headers) — évite le menu contextuel iOS gênant. */
    .btn, .bn-item, .nav-item, .top-bar-right .tb-btn, .module-tab,
    .bottom-nav, .top-bar {
        -webkit-user-select: none; user-select: none;
        -webkit-touch-callout: none;
    }
    /* Évite le double-tap-zoom iOS sur les boutons interactifs */
    .btn, .bn-item, .nav-item, .module-tab { touch-action: manipulation; }
    /* Cards : ombres plus discrètes sur petit écran (perf + esthétique) */
    .card { box-shadow: none; }
    /* Modaux : full-width sur mobile pour gagner de la place */
    .modal-content, .modal-dialog { max-width: 100% !important; margin: 0 !important; border-radius: 0 !important; }
    /* Empêche les images de déborder horizontalement (cause #1 de scroll latéral) */
    img, video, iframe { max-width: 100%; height: auto; }
    /* Empêche les <pre> et <code> de casser le layout */
    pre, code { overflow-x: auto; max-width: 100%; word-wrap: break-word; }
    /* Forms : labels en gras pour meilleure lisibilité (Pro Max ≈ 6.7") */
    .form-label { font-weight: 600; font-size: 13px; }
}

@media (max-width: 768px) {
    .bottom-nav {
        display: flex;
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;
        height: 60px;
        background: var(--bg-sidebar, #002c30);
        border-top: 1px solid rgba(255, 255, 255, 0.08);
        z-index: 1000;
        padding-bottom: env(safe-area-inset-bottom, 0);
        box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
    }
    .bottom-nav .bn-item {
        position: relative;
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        padding: 6px 4px;
        text-decoration: none;
        color: rgba(255, 255, 255, 0.55);
        font-size: 10px;
        font-weight: 600;
        background: none;
        border: none;
        cursor: pointer;
        min-height: 44px;  /* Apple HIG tap target */
        transition: color 0.15s, background 0.15s;
    }
    .bottom-nav .bn-item:hover,
    .bottom-nav .bn-item:focus-visible {
        color: var(--gold, #f4bf4e);
        background: rgba(244, 191, 78, 0.08);
        outline: none;
    }
    .bottom-nav .bn-item.active {
        color: var(--gold, #f4bf4e);
    }
    .bottom-nav .bn-item.active::before {
        content: '';
        position: absolute;
        top: 0;
        width: 24px;
        height: 3px;
        background: var(--gold, #f4bf4e);
        border-radius: 0 0 3px 3px;
    }
    .bottom-nav .bn-icon {
        font-size: 20px;
        line-height: 1;
    }
    .bottom-nav .bn-lbl {
        font-size: 10px;
        letter-spacing: 0.2px;
    }
    .bottom-nav .bn-more {
        border-left: 1px solid rgba(255, 255, 255, 0.06);
    }

    /* Cacher la module-bar horizontale sur mobile pour laisser la place
       au bottom-nav qui est plus ergonomique. La sidebar accessible via
       le bouton "+ Plus" garde tous les sous-modules. */
    .module-bar { display: none !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   SUBNAV HEADER GÉNÉRIQUE (.msnav-*) — bandeau de sous-navigation
   ───────────────────────────────────────────────────────────────────────
   Déployé sur DENDRITE, CARE, SOMA, PULSE, GLIA, CORTEX, SYNAPSE, SENTINEL.
   Factorisé depuis les bandeaux historiques Axone (.ax-*) et Supply Chain
   (.sc-*) : une seule feuille de style, l'accent couleur étant piloté par
   la variable --subnav-accent posée sur le conteneur .msnav de chaque module
   (cf. templates/subnav/<module>.html + modules/subnav.py).
   NB : Axone et Supply Chain gardent volontairement leur propre CSS (.ax-/.sc-).
   ═══════════════════════════════════════════════════════════════════════ */
.msnav { --subnav-accent: var(--gold); }

.msnav-breadcrumb {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: var(--radius) var(--radius) 0 0;
    font-size: 0.78rem;
    color: var(--text-muted);
    margin-top: 0;
    margin-bottom: -1px;
}
.msnav-bc-root {
    color: var(--text-secondary);
    text-decoration: none;
    font-weight: 600;
}
.msnav-bc-root:hover { color: var(--subnav-accent); }
.msnav-bc-sep { opacity: 0.5; }
.msnav-bc-section { font-weight: 500; }
.msnav-bc-page { color: var(--subnav-accent); font-weight: 600; }
.msnav-header-cta {
    font-size: 0.75rem !important;
    padding: 4px 10px !important;
}

.msnav-menubar {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    background: var(--bg-card);
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 1.25rem;
}

.msnav-menu { position: relative; }

.msnav-trigger {
    background: transparent;
    border: none;
    padding: 8px 14px;
    font-size: 0.88rem;
    color: var(--text-secondary);
    cursor: pointer;
    border-radius: var(--radius-sm);
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 500;
    transition: all 0.15s ease;
    white-space: nowrap;
}
.msnav-trigger:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
}
/* Onglet actif : pastille teintée + texte accent + soulignement.
   color-mix garantit la lisibilité quelle que soit la couleur d'accent du
   module (néon compris) — contrairement à un fond plein + texte blanc qui
   passe mal sur les accents clairs (#00ffc3, #34d399…). */
.msnav-menu-active .msnav-trigger {
    background: color-mix(in srgb, var(--subnav-accent) 16%, transparent);
    color: var(--subnav-accent);
    font-weight: 600;
    box-shadow: inset 0 -2px 0 var(--subnav-accent);
}
.msnav-menu-active .msnav-trigger:hover {
    background: color-mix(in srgb, var(--subnav-accent) 24%, transparent);
}
.msnav-menu-admin .msnav-trigger {
    margin-left: auto;
    border-left: 1px solid var(--border);
    padding-left: 18px;
}

.msnav-chevron { font-size: 0.7rem; opacity: 0.7; }

.msnav-items {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 280px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
    padding: 6px 0;
    z-index: 999;
    margin-top: 4px;
}
.msnav-menu-admin .msnav-items { left: auto; right: 0; }
.msnav-menu:hover .msnav-items,
.msnav-menu.msnav-open .msnav-items { display: block; }

.msnav-section {
    padding: 8px 14px 4px;
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 0.8px;
    border-top: 1px solid var(--border);
    margin-top: 4px;
}
.msnav-section:first-child { border-top: none; margin-top: 0; }

.msnav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 14px;
    color: var(--text-primary);
    text-decoration: none;
    font-size: 0.85rem;
    transition: background 0.12s ease;
}
.msnav-item:hover {
    background: var(--bg-secondary);
    color: var(--text-primary);
    text-decoration: none;
}
.msnav-item-active {
    background: color-mix(in srgb, var(--subnav-accent) 12%, transparent);
    color: var(--subnav-accent) !important;
    font-weight: 600;
}
.msnav-item-cta { color: var(--green-text) !important; font-weight: 600; }
.msnav-item-icon {
    width: 20px;
    font-size: 0.95rem;
    text-align: center;
    flex-shrink: 0;
}

/* Mobile : menus empilés en accordéon (comme .ax-/.sc-) */
@media (max-width: 768px) {
    .msnav-items {
        position: static;
        box-shadow: none;
        width: 100%;
        margin-top: 0;
    }
    .msnav-menubar { flex-direction: column; }
}

/* ═══════════════════════════════════════════════════════════════════════
   NUMÉRO DE TÂCHE — .ms-tnum
   ───────────────────────────────────────────────────────────────────────
   Le numéro visible d'une tâche Dendrite (#61011) : celui de son URL, et la
   racine de sa référence Git (MSYN-61011). Utilisé sur les cartes du Kanban,
   les lignes de liste et la fiche — donc factorisé ici plutôt que dupliqué
   dans chaque gabarit. Discret par défaut (le titre reste l'information
   principale), lisible et sélectionnable pour être copié dans un message ou
   un message de commit.
   ═══════════════════════════════════════════════════════════════════════ */
.ms-tnum {
    display: inline-block;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.72rem;
    font-weight: 700;
    line-height: 1.4;
    color: var(--text-muted);
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0 5px;
    white-space: nowrap;
    vertical-align: 2px;
    user-select: all;              /* un clic sélectionne le numéro entier */
}

/* Sur la fiche, à côté d'un titre de page : un cran plus grand. */
.ms-tnum-lg { font-size: 0.9rem; padding: 2px 8px; vertical-align: 3px; }

/* Numéro dans une cellule de tableau : colonne étroite, non éditable. */
.ms-tnum-cell { color: var(--text-muted); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.72rem; white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════════
   REFONTE MOBILE 2026-07 — LOT 1 : FONDATIONS (iPhone ≤ 768px)
   ───────────────────────────────────────────────────────────────────────
   Placé APRÈS les blocs mobiles historiques → gagne la cascade (même
   spécificité, source postérieure). Corrige les 4 douleurs de fond :
   tableaux coupés, formulaires qui débordent, cibles tactiles trop petites,
   page de connexion hors-écran. Le pattern « cartes » sur les listes clés
   arrive au LOT 3 ; ici on garantit surtout que RIEN n'est plus illisible.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
    /* ── A. Filet de sécurité universel sur les tableaux ──
       Beaucoup de listes utilisent un <table> « maison » NON enveloppé dans
       un conteneur scrollable. Avec html/body { overflow-x:hidden }, elles
       étaient COUPÉES (colonnes invisibles) au lieu d'être défilables.
       On rend tout <table> du contenu défilable, comme .data-table le fait
       déjà pour les tableaux qui portent cette classe. */
    .main-content table {
        display: block;
        width: 100%;
        max-width: 100%;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    /* ── B. Formulaires : toute grille repasse en 1 colonne ──
       Les 46 formulaires du codebase utilisent des grilles en style INLINE
       (style="display:grid; grid-template-columns:1fr 2fr 1fr"). Une règle
       !important surcharge un style inline non-important → une seule règle
       les met tous en 1 colonne (le `display:grid` inline + le `gap`
       inline sont préservés → bel espacement vertical). Scopé à <form>
       pour ne pas toucher les grilles de contenu des pages de détail. */
    form div[style*="grid-template-columns"],
    form .form-grid,
    form .form-row {
        grid-template-columns: 1fr !important;
    }

    /* ── C. Cibles tactiles Apple HIG (≥ 44 px) ── */
    .btn-sm { min-height: 44px !important; }
    /* Boutons d'action par ligne (👁 ✏️ 🗑) : ~22 px aujourd'hui → 44 px */
    .fl-act {
        min-width: 44px !important;
        min-height: 44px !important;
        padding: 8px 10px !important;
        display: inline-flex !important;
        align-items: center;
        justify-content: center;
        font-size: 1.15rem !important;
    }

    /* ── D. Page de connexion : ne jamais déborder de l'écran ── */
    .login-box {
        width: 100% !important;
        max-width: 100% !important;
        padding: 28px 20px !important;
        box-sizing: border-box;
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   REFONTE MOBILE 2026-07 — LOT 2 : NAVIGATION (iPhone ≤ 768px)
   ───────────────────────────────────────────────────────────────────────
   La top-bar mobile était SURCHARGÉE d'icônes tassées sur 375px (copilote,
   accessibilité, activités, chat, notifs, déconnexion) et la recherche
   globale était totalement inaccessible. On désencombre + on rend la
   recherche atteignable via un bouton loupe qui déploie une barre pleine
   largeur. (La cloche de notifs n'a jamais été cachée : la règle historique
   ciblait #topbar-notif-bell, un id legacy inexistant — la vraie cloche est
   #tb-notif-btn.)
   ═══════════════════════════════════════════════════════════════════════ */
/* Élément réservé au mobile (bouton loupe) — masqué par défaut sur desktop */
.tb-mobile-only { display: none; }

@media (max-width: 768px) {
    /* ── A. Désencombrer la top-bar ──
       On masque les accès SECONDAIRES qui restent atteignables ailleurs :
       accessibilité (/iris/accessibilité + Mon compte) et activités
       (to-do unifiée). On GARDE : recherche, chat, notifications, copilote,
       déconnexion. */
    .top-bar-right .tb-a11y-wrap,
    .top-bar-right .tb-act-wrap { display: none !important; }

    /* Bouton loupe : visible en mobile, tap target 40px (aligné .tb-btn) */
    .tb-mobile-only { display: inline-flex !important; align-items: center; justify-content: center; }

    /* ── A bis. La clé « mise en maintenance » sort de la barre (retour CEO
       2026-08-23 : « pas utile en visible directement ») ──
       Mettre un module en maintenance coupe l'accès à tous les utilisateurs :
       c'est rare, lourd, et ça ne se décide pas depuis un téléphone entre deux
       portes. Sur 375 px elle prenait une place que se disputent déjà le logo,
       l'entreprise et six icônes. Reste entièrement accessible sur ordinateur. */
    #mm-pastille-btn { display: none !important; }

    /* ── B. Sélecteur d'entreprise : cible tactile ≥44px + badge lisible ── */
    .tb-ent-switcher #tb-ent-btn,
    .tb-ent-switcher > button {
        min-height: 44px !important;
        padding: 4px 8px !important;
    }
    .tb-ent-switcher #tb-ent-btn > span:first-child,
    .tb-ent-switcher > span > span:first-child,
    .tb-ent-switcher .tb-ent-logo {
        width: 30px !important; height: 30px !important; font-size: 13px !important;
    }

    /* ── C. Recherche mobile déployable en pleine largeur ──
       Cachée par défaut (règle historique ligne ~1466). Quand
       body.mobile-search-open est actif (bouton loupe), bandeau fixe
       pleine largeur sous la top-bar 56px. Spécificité (0,2,1) + source
       postérieure → gagne sur le display:none historique. */
    body.mobile-search-open .global-search-wrap {
        display: block !important;
        position: fixed !important;
        top: 56px; left: 0; right: 0;
        margin: 0 !important;
        padding: 8px 10px !important;
        background: var(--bg-secondary);
        border-bottom: 1px solid var(--border);
        box-shadow: 0 6px 16px rgba(0,0,0,0.25);
        z-index: 120;
    }
    body.mobile-search-open #globalSearchInput {
        width: 100% !important;
        box-sizing: border-box;
    }
    body.mobile-search-open #globalSearchResults {
        left: 10px !important; right: 10px !important;
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   REFONTE MOBILE 2026-07 — menus déroulants de la top-bar (iPhone ≤ 768px)
   ───────────────────────────────────────────────────────────────────────
   Les dropdowns (notifications 420px, activités 380px, chat, entreprise…)
   sont ancrés `right:0` avec une largeur fixe → sur mobile ils dépassent du
   bord GAUCHE (texte coupé, cf. capture CEO). On les passe en `position:fixed`
   pleine largeur sous la top-bar, hauteur bornée + scroll interne.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
    #notif-dropdown,
    #act-dropdown,
    #a11y-dropdown,
    #tb-online-dropdown,
    #tb-ent-dropdown {
        position: fixed !important;
        top: calc(56px + env(safe-area-inset-top, 0)) !important;
        left: 8px !important;
        right: 8px !important;
        width: auto !important;
        max-width: none !important;
        min-width: 0 !important;
        max-height: 72vh !important;
        overflow-y: auto !important;
    }
}

/* ═══════════════════════════════════════════════════════════════════════
   ACTIONS AU SWIPE — piloté par static/js/swipe-actions.js
   Une ligne (.sw-row) : contenu translatable (.sw-content) au-dessus de deux
   panneaux d'action (.sw-action-left / -right) révélés au glissement.
   ═══════════════════════════════════════════════════════════════════════ */
.sw-row { position: relative; overflow: hidden; }
.sw-content { position: relative; z-index: 2; background: var(--bg-card); }
.sw-action {
    position: absolute; top: 0; bottom: 0; width: 112px; z-index: 1;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; color: #fff; font-size: 12px; font-weight: 700; cursor: pointer;
    border: none;
    /* ── Invisibles au repos (retour CEO 2026-08-23 : « les couleurs sont
       bizarres ») ──
       Le contenu (.sw-content) est censé recouvrir ces panneaux. Mais il est
       ENFANT de la ligne, donc borné par le padding latéral de celle-ci, alors
       que les panneaux sont en `absolute` et se calent sur le bord. Mesuré à
       390 px sur une ligne à `padding: 0 12px` : ligne 390 px, contenu 366 px
       → une bande de 12 px dépassait de chaque côté, EN PERMANENCE. Sur le chat
       (lignes plus larges) c'est le panneau entier qui restait à l'écran : des
       aplats rouges et bleus derrière chaque ligne.
       Plutôt que de courir après le padding de chaque module hôte, on masque
       les panneaux tant qu'aucun glissement n'est en cours : le JS pose
       `sw-sliding` sur la ligne pendant le geste. Ils ne peuvent donc plus
       apparaître par accident, quel que soit le CSS de la liste. */
    opacity: 0;
    pointer-events: none;
    transition: opacity .12s linear;
}
.sw-row.sw-sliding .sw-action { opacity: 1; pointer-events: auto; }
.sw-action-left { left: 0; }
.sw-action-right { right: 0; }
.sw-action .sw-ico { font-size: 20px; line-height: 1; }
.sw-del { background: var(--red); }
.sw-read { background: var(--blue); }
.sw-pin { background: var(--gold); color: var(--on-gold, #1a1a1a); }
.sw-archive { background: var(--text-muted); }
.sw-done { background: var(--green); }

/* ═══════════════════════════════════════════════════════════════════════
   REFONTE MOBILE 2026-07 — FILET GLOBAL anti-débordement (iPhone ≤ 768px)
   ───────────────────────────────────────────────────────────────────────
   Beaucoup d'écrans posent des grilles multi-colonnes en style INLINE (KPI ×4/5,
   coordonnées ×3, « sidebar px + contenu », dates ×2…) qui débordent de l'écran
   sur iPhone. On les ramène : 3-6 col → 2 col (cartes) ; colonne fixe px → 1 col ;
   2-3 col en fr (formulaires) → 1 col. + champs jamais plus larges que l'écran.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
    /* Libellés de KPI : l'interlettrage coûte plus que la place restante.
       Mesuré le 2026-09-03 sur /dashboard/ à 375 px : le mot « Encaissements »
       occupe 114 px pour 108 disponibles. Aucun repli n'est possible — un mot
       ne se coupe pas — et la carte est en `overflow: hidden`, donc la coupe
       est INVISIBLE : ni scroll, ni ellipse, juste une lettre qui manque.
       `letter-spacing: 1px` ajoute 13 px sur ce seul mot, pour 6 px manquants.
       On le retire sous 768 px ; la casse haute reste lisible sans lui. */
    .kpi-card .kpi-label { letter-spacing: 0; }

    /* Grilles de 3 à 6 colonnes → 2 colonnes */
    [style*="grid-template-columns:repeat(3"], [style*="grid-template-columns: repeat(3"],
    [style*="grid-template-columns:repeat(4"], [style*="grid-template-columns: repeat(4"],
    [style*="grid-template-columns:repeat(5"], [style*="grid-template-columns: repeat(5"],
    [style*="grid-template-columns:repeat(6"], [style*="grid-template-columns: repeat(6"] {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    }
    /* Grilles « colonne fixe px + contenu » → 1 colonne empilée */
    [style*="grid-template-columns:220px"], [style*="grid-template-columns: 220px"],
    [style*="grid-template-columns:240px"], [style*="grid-template-columns: 240px"],
    [style*="grid-template-columns:260px"], [style*="grid-template-columns: 260px"],
    [style*="grid-template-columns:280px"], [style*="grid-template-columns: 280px"],
    [style*="grid-template-columns:300px"], [style*="grid-template-columns: 300px"],
    [style*="grid-template-columns:320px"], [style*="grid-template-columns: 320px"],
    [style*="grid-template-columns:340px"], [style*="grid-template-columns: 340px"] {
        grid-template-columns: 1fr !important;
    }
    /* Grilles 2-3 colonnes en fractions (formulaires) → 1 colonne */
    [style*="grid-template-columns:1fr 1fr"], [style*="grid-template-columns: 1fr 1fr"],
    [style*="grid-template-columns:1fr 2fr"], [style*="grid-template-columns: 1fr 2fr"],
    [style*="grid-template-columns:2fr 1fr"], [style*="grid-template-columns: 2fr 1fr"] {
        grid-template-columns: 1fr !important;
    }
    /* Champs de saisie jamais plus larges que l'écran */
    input, select, textarea { max-width: 100% !important; box-sizing: border-box; }
    input[type="date"], input[type="datetime-local"], input[type="time"] { width: 100% !important; }
}

/* ═══════════════════════════════════════════════════════════════════════
   REFONTE MOBILE 2026-07 — LOT 3 : LISTES EN CARTES (iPhone ≤ 768px)
   ───────────────────────────────────────────────────────────────────────
   Sur mobile, les tableaux de FICHES (.ms-cardify) — toutes les listes
   AxoneSearch (devis, factures, contacts, tickets, interventions, robots,
   BC, BL, opportunités, enquêtes…) + les tableaux « maison » tagués
   ms-cardify-static — se transforment en CARTES EMPILÉES : une ligne = une
   carte, chaque cellule = « Libellé : valeur » (via data-label). Fini le
   scroll horizontal illisible. Les vraies matrices/tableaux de détail
   (non .ms-cardify) gardent le scroll horizontal du LOT 1.
   ═══════════════════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
    .ms-cardify {
        display: block;
        overflow: visible !important;   /* annule l'overflow-x du filet LOT 1 */
        border-collapse: collapse;
        /* Beaucoup de listes fixent un `min-width` (820-900px) pour rester
           lisibles en tableau sur PC. En mode CARTE cette largeur n'a plus de
           sens et devient un piège : la carte garde 820px, le libellé occupe
           ses 42% (≈344px) et la VALEUR part hors de l'écran — on ne lit plus
           qu'une colonne d'intitulés vides. Constaté sur Connaissances et les
           5 listes Care (incident mobile 2026-08-02). */
        min-width: 0 !important;
        width: 100% !important;
    }
    .ms-cardify thead { display: none; }          /* en-têtes portés par data-label */
    .ms-cardify tbody { display: block; }
    .ms-cardify tr {
        display: block;
        background: var(--bg-card);
        border: 1px solid var(--border);
        border-radius: var(--radius);
        margin-bottom: 10px;
        padding: 8px 12px;
        box-shadow: var(--shadow-sm, 0 1px 3px rgba(0,0,0,.08));
    }
    .ms-cardify td {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 12px;
        padding: 7px 0;
        border: none !important;
        border-bottom: 1px solid var(--border) !important;
        white-space: normal !important;   /* annule un éventuel nowrap de .data-table td */
        overflow-wrap: anywhere;          /* coupe emails/URL très longs */
        text-align: right;
        min-height: 0;
    }
    .ms-cardify tr td:last-child { border-bottom: none !important; }
    /* Libellé de colonne devant la valeur */
    .ms-cardify td[data-label]::before {
        content: attr(data-label);
        font-weight: 600;
        color: var(--text-muted);
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 0.4px;
        flex: 0 0 42%;
        text-align: left;
        white-space: normal;
    }
    /* Cellules sans libellé (cases à cocher, colonne Actions) : pleine largeur,
       alignées à droite, pas de pseudo-libellé. */
    .ms-cardify td:not([data-label]) { justify-content: flex-end; }
    .ms-cardify td[data-label="Actions"]::before,
    .ms-cardify td[data-label=""]::before { content: ""; }
    .ms-cardify td[data-label="Actions"] { justify-content: flex-end; }
    /* Première cellule (souvent le nom/numéro) mise en avant */
    .ms-cardify tbody tr td:first-child {
        font-weight: 700;
        font-size: 1.02rem;
    }
    /* Lignes de regroupement AxoneSearch : rester en bandeau pleine largeur */
    .ms-cardify tr.axsb-grp {
        background: var(--bg-secondary);
        padding: 8px 12px;
    }
    .ms-cardify tr.axsb-grp td {
        justify-content: flex-start;
        border-bottom: none !important;
        font-weight: 700;
    }
    .ms-cardify tr.axsb-grp td::before { content: ""; display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   SOCLE RESPONSIVE UNIVERSEL — 2026-07
   ───────────────────────────────────────────────────────────────────────
   Objectif : rendu correct de 320 px à 4K, quel que soit l'appareil (PC,
   tablette, téléphone, tactile Windows) et le navigateur (Chromium =
   Chrome/Edge/Brave, WebKit = Safari & tout iOS, Firefox). Doctrine
   complète : .claude/rules/responsive.md.

   Échelle de breakpoints CANONIQUE (ne pas en inventer d'autres) :
       480 / 640 / 768 / 1024 / 1280 / 1536
   Le ≤ 768 px est couvert par la REFONTE MOBILE 2026-07 ci-dessus.
   Ce socle comble : la ZONE TABLETTE (769–1100, iPad portrait/paysage,
   fenêtres PC réduites), le TACTILE UNIVERSEL (pointer:coarse = capacité,
   pas largeur — un iPad Pro fait 1024–1366 px et échappait à toutes les
   règles mobiles), et des GARANTIES tous-écrans (modales, médias).
   Placé en FIN de fichier → gagne la cascade à spécificité égale.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. Garanties TOUS écrans ─────────────────────────────────────────── */
/* Médias fluides partout (généralise le filet ≤768 existant). */
img, svg, video, canvas, iframe { max-width: 100%; }
img[width] { height: auto; }   /* garde le ratio quand max-width s'applique */

/* Modales : jamais plus hautes que la fenêtre, scroll interne.
   `dvh` (Safari 15.4+/Chrome 108+) suit les barres UI mobiles ; la ligne
   `vh` juste avant sert de fallback pour les navigateurs plus anciens. */
.modal-box, .modal-content, .modal-dialog {
    max-height: calc(100vh - 32px);
    max-height: calc(100dvh - 32px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* ── 2. Tactile UNIVERSEL — capacité, pas largeur d'écran ─────────────── */
@media (pointer: coarse) {
    .btn, .nav-item, .module-tab, .msnav-trigger, .msnav-item, .bn-item {
        min-height: 40px;
    }
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
    select { min-height: 40px; }
    .main-content table, .table-wrap { -webkit-overflow-scrolling: touch; }
}

/* ── 3. ZONE TABLETTE 769–1100 px ─────────────────────────────────────
   iPad portrait (768/810/834), iPad paysage (1024/1112/1180), fenêtres PC
   côte-à-côte. Prolonge les filets de la refonte mobile avec une densité
   intermédiaire (2-3 colonnes au lieu de 1-2). */
@media (min-width: 769px) and (max-width: 1100px) {
    /* Tableaux non enveloppés : défilables au lieu de coupés. */
    .main-content table {
        display: block;
        width: 100%;
        max-width: 100%;
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    /* Grilles inline 5-6 colonnes → 3 ; 4 colonnes → 2. */
    [style*="grid-template-columns:repeat(5"], [style*="grid-template-columns: repeat(5"],
    [style*="grid-template-columns:repeat(6"], [style*="grid-template-columns: repeat(6"] {
        grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    }
    [style*="grid-template-columns:repeat(4"], [style*="grid-template-columns: repeat(4"] {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    }
    /* Formulaires : les grilles inline repassent à 2 colonnes égales. */
    form div[style*="grid-template-columns"],
    form .form-grid,
    form .form-row {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    }
}

/* ── 4. PRIMITIVES opt-in (nouvelles pages & migrations d'écrans) ─────
   À utiliser dans tout NOUVEAU template au lieu de styles inline fixes. */
/* Conteneur scrollable pour tableau large (à poser autour du <table>). */
.table-wrap {
    overflow-x: auto;
    max-width: 100%;
    -webkit-overflow-scrolling: touch;
}
/* Grille de cartes/KPI fluide : s'adapte à TOUTE largeur sans breakpoint. */
.ms-grid {
    display: grid;
    gap: 16px;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}
/* Grille de formulaire fluide (2-3 colonnes PC → 1 colonne mobile). */
.ms-form-grid {
    display: grid;
    gap: 14px 18px;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
/* Modale canonique (généralisation du fix calendrier 2026-07). */
.ms-modal-overlay {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.6);
    z-index: 1000;
    align-items: center;
    justify-content: center;
    padding: 16px;
    padding-top: max(16px, env(safe-area-inset-top));
    padding-bottom: max(16px, env(safe-area-inset-bottom));
}
.ms-modal-overlay.show { display: flex; }
.ms-modal {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 24px;
    width: 100%;
    max-width: 560px;
    max-height: calc(100vh - 32px);
    max-height: calc(100dvh - 32px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    box-shadow: var(--shadow);
}
/* Hauteur pleine fenêtre fiable (remplace height:100vh au cas par cas). */
.ms-fullheight { height: 100vh; height: 100dvh; }
/* Visibilité par plage (préférer une vraie adaptation quand possible). */
@media (max-width: 768px)  { .ms-hide-sm { display: none !important; } }
@media (max-width: 1100px) { .ms-hide-md { display: none !important; } }
@media (min-width: 769px)  { .ms-only-sm { display: none !important; } }

/* ═══════════════════════════════════════════════════════════════════════
   RESPONSIVE LOT 2 — RAIL SIDEBAR TABLETTE (769–1180 px) — 2026-07
   ───────────────────────────────────────────────────────────────────────
   Sur iPad (portrait 768-834, paysage 1024-1180) et fenêtres PC réduites,
   la sidebar 200-220 px mangeait 25 % de l'écran. Elle devient un RAIL de
   60 px : icônes seules (astuce font-size:0 — certains libellés sont des
   nœuds texte nus), badges convertis en pastilles, sections toutes
   dépliées (leurs entêtes de repli sont masquées), sélecteur de neurone
   compact avec dropdown en surimpression. 100 % CSS — aucun changement
   base.html, revert trivial. La préférence utilisateur « sidebar masquée »
   (html.pref-sidebar-hidden, !important) reste prioritaire.
   ═══════════════════════════════════════════════════════════════════════ */
@media (min-width: 769px) and (max-width: 1180px) {
    /* ── Pourquoi `html:not(.pref-sidebar-wide)` devant chaque règle de rail ──
       Le rail était SUBI : entre 769 et 1180 px il s'imposait, et la seule
       préférence existante (pref-sidebar-hidden) masque la sidebar, elle ne la
       ré-élargit pas. Le seul recours était d'agrandir la fenêtre. Le garde
       laisse l'utilisateur ÉPINGLER la sidebar large (bouton du pied,
       localStorage `mysynapse-sidebar-wide`) : les règles d'origine, hors
       media query, reprennent alors la main d'elles-mêmes — aucune valeur
       n'est dupliquée ici, donc rien à resynchroniser plus tard.
       Les règles top-bar / module-bar plus bas ne sont VOLONTAIREMENT pas
       gardées : compacter la barre du haut reste souhaitable à cette largeur,
       sidebar épinglée ou non. */
    html:not(.pref-sidebar-wide) .sidebar { width: 60px; }
    /* Le selecteur 3-classes bat `.app-body.has-module-bar .main-content`
       (margin-left:220px, specificite 0,3,0) defini plus haut. */
    html:not(.pref-sidebar-wide) .main-content,
    html:not(.pref-sidebar-wide) .app-body.has-module-bar .main-content { margin-left: 60px; padding: 20px; }

    /* Sélecteur de neurone : icône seule (le rail fait 60 px). Les raccourcis
       « Récents » n'y tiennent pas — la bottom-nav couvre ce besoin sur mobile. */
    html:not(.pref-sidebar-wide) .snrn { padding: 10px 6px; }
    html:not(.pref-sidebar-wide) .snrn-label,
    html:not(.pref-sidebar-wide) .snrn-name,
    html:not(.pref-sidebar-wide) .snrn-chevron { display: none; }
    html:not(.pref-sidebar-wide) .snrn-current { justify-content: center; padding: 8px 6px; }
    html:not(.pref-sidebar-wide) .snrn-recents,
    html:not(.pref-sidebar-wide) .snrn-recents.show { display: none; }

    /* Titre du panneau de module (« SYNAPSE — Productivité ») : 28 px utiles
       dans le rail → il se coupait lettre à lettre. Le neurone courant est
       déjà porté par l'icône du sélecteur juste au-dessus. */
    html:not(.pref-sidebar-wide) .module-panel-title { display: none; }

    /* Entêtes de section masquées + tout déplié (le repli n'a plus de sens
       sans libellés ; on neutralise aussi l'état replié mémorisé). */
    /* !important requis : base.html pose `.nav-section-toggle{display:flex
       !important}` (bloc <style> « Titres de sections sidebar »). */
    html:not(.pref-sidebar-wide) .sidebar .nav-section-toggle { display: none !important; }
    html:not(.pref-sidebar-wide) .nav-group,
    html:not(.pref-sidebar-wide) .nav-group.collapsed {
        max-height: none;
        opacity: 1;
        pointer-events: auto;
    }
    /* Les 2 règles ci-dessus (+ celle-ci historiquement) visaient des classes
       ABSENTES du markup réel (.nav-group / .nav-section.collapsed) : sélecteurs
       morts. Le markup est plat : .nav-section-toggle + .nav-section-content
       frères. L'accordéon JS (toggleNavSection) pose `display:none` INLINE sur
       les sections non-actives → seul un !important le rebat. Sans ça, en mode
       rail une seule section reste visible et les autres, entêtes masquées,
       deviennent inaccessibles (le flyout de survol n'a plus d'icône). */
    html:not(.pref-sidebar-wide) .nav-section.collapsed .nav-section-content { display: block; }
    html:not(.pref-sidebar-wide) .sidebar .nav-section-content { display: block !important; }

    /* Les entêtes de section étant masquées, le rail était une colonne CONTINUE
       d'icônes : plus aucun repère de groupe entre « Communication » et
       « Entreprise ». Un filet rend les groupes de nouveau lisibles. */
    html:not(.pref-sidebar-wide) .sidebar .nav-section-content {
        border-top: 1px solid var(--border);
        margin-top: 6px;
        padding-top: 6px;
    }
    /* Pas de filet au-dessus de la PREMIÈRE section d'un panneau : il ferait
       doublon avec la bordure du sélecteur de neurone. `:first-of-type` serait
       un piège ici — il porte sur le type d'élément (div), et le markup place
       toujours .module-panel-title devant, donc il ne matcherait jamais. On
       cible la vraie séquence du gabarit : titre + entête + contenu. */
    html:not(.pref-sidebar-wide) .sidebar .module-panel-title + .nav-section-toggle + .nav-section-content {
        border-top: 0;
        margin-top: 0;
        padding-top: 0;
    }

    /* Items : icone centree, libelle (span OU noeud texte nu) masque.
       `.sidebar .nav-item` (0,2,0, plus tardif) bat `.context-sidebar .nav-item`. */
    html:not(.pref-sidebar-wide) .sidebar .nav-item {
        font-size: 0;
        gap: 0;
        justify-content: center;
        padding: 10px 0;
        position: relative;
    }
    html:not(.pref-sidebar-wide) .sidebar .nav-item .icon { font-size: 18px; width: auto; }
    /* Badge (compteurs notifs) → pastille compacte en coin */
    html:not(.pref-sidebar-wide) .sidebar .nav-badge {
        position: absolute;
        top: 2px;
        right: 8px;
        margin-left: 0;
        font-size: 9px;
        padding: 1px 4px;
        line-height: 1.2;
    }

    /* Pied de sidebar : avatar + boutons seuls */
    html:not(.pref-sidebar-wide) .sbf-account-name { display: none; }
    html:not(.pref-sidebar-wide) .sb-footer,
    html:not(.pref-sidebar-wide) .sbf-actions { padding-left: 4px; padding-right: 4px; }
    html:not(.pref-sidebar-wide) .sbf-account { justify-content: center; padding: 6px 0; }
    /* 3 boutons `flex:1` sur une ligne ne tiennent pas dans 60 px (ils
       débordaient à droite) → largeur intrinsèque + retour à la ligne. */
    html:not(.pref-sidebar-wide) .sbf-actions { flex-wrap: wrap; justify-content: center; }
    html:not(.pref-sidebar-wide) .sbf-btn { flex: 0 0 auto; width: 32px; }
    html:not(.pref-sidebar-wide) .sbf-lang { flex: 0 0 auto; }
    /* Dropdown langue : `left:0;right:0` le collait à la largeur du rail. */
    html:not(.pref-sidebar-wide) .sbf-lang-dropdown { right: auto; width: 150px; }
    /* Libellé du repli : « Réduire la barre latérale » s'empilait sur 3 lignes.
       L'icône suffit ; le libellé complet reste dans le title du bouton. */
    html:not(.pref-sidebar-wide) .sbf-collapse { justify-content: center; padding: 8px 0; }
    html:not(.pref-sidebar-wide) .sbf-collapse-label { display: none; }
    /* Version : « build <sha> » débordait. On garde « v3.x » ; le build reste
       lisible au survol (title de .sbf-version) et sur écran large. */
    html:not(.pref-sidebar-wide) .sbf-version { padding-left: 2px; padding-right: 2px; }
    html:not(.pref-sidebar-wide) .sbf-version-sep,
    html:not(.pref-sidebar-wide) .sbf-version-build { display: none; }
    /* Mention de copyright : illisible sur 28 px utiles (title conservé). */
    html:not(.pref-sidebar-wide) .sbf-copyright { display: none; }

    /* Bouton « épingler les libellés » : n'a de sens QUE dans cette plage
       (au-delà de 1180 px la sidebar est déjà large, en deçà de 769 px c'est
       la bottom-nav). Masqué partout ailleurs — voir .sbf-wide plus bas. */
    .sbf-wide { display: flex; }
    html:not(.pref-sidebar-wide) .sbf-wide { justify-content: center; padding: 8px 0; }
    html:not(.pref-sidebar-wide) .sbf-wide-label { display: none; }

    /* ── Tactile : le survol n'existe pas, donc le flyout non plus ──
       Ce breakpoint vise explicitement l'iPad (paysage 1024-1180). Or le
       secours du rail est un panneau au SURVOL : sur un écran tactile il ne
       s'ouvre jamais, et l'utilisateur n'a plus AUCUN moyen de savoir où mène
       une icône. On y affiche donc le libellé en permanence, sous l'icône —
       la seule réponse qui ne demande aucune interaction. Le rail passe à
       76 px : c'est le prix, et il reste 3× plus étroit que la sidebar.
       `.nav-label` est garanti par sidebar-rail-flyout.js (il enveloppe les
       libellés qui sont des nœuds texte nus) ; sans le JS on retombe sur le
       rail muet d'avant, pas sur un rendu cassé. */
    @media (pointer: coarse) {
        html:not(.pref-sidebar-wide) .sidebar { width: 76px; }
        html:not(.pref-sidebar-wide) .main-content,
        html:not(.pref-sidebar-wide) .app-body.has-module-bar .main-content { margin-left: 76px; }
        html:not(.pref-sidebar-wide) .sidebar .nav-item {
            flex-direction: column;
            gap: 2px;
            padding: 8px 2px;
            text-align: center;
        }
        html:not(.pref-sidebar-wide) .sidebar .nav-item .nav-label {
            display: -webkit-box;
            -webkit-line-clamp: 2;
            -webkit-box-orient: vertical;
            overflow: hidden;
            font-size: 9px;
            line-height: 1.15;
            letter-spacing: -0.2px;
            word-break: break-word;
        }
    }

    /* Module-bar : premiers onglets atteignables quand ça déborde
       (center + overflow rend le début de liste inaccessible). */
    .module-bar { justify-content: flex-start; }

    /* Top-bar : masquer les accès secondaires (mêmes cibles que le
       désencombrement mobile ≤768) — ils restent accessibles ailleurs. */
    .top-bar-right #topbar-journal-btn,
    .top-bar-right #btn-refresh,
    .top-bar-right #theme-toggle-btn,
    .top-bar-right #tb-aide-btn,
    .top-bar-right #tb-admin-btn,
    .top-bar-right .tb-a11y-wrap,
    .top-bar-right .tb-act-wrap,
    .top-bar-right .lang-switch,
    .top-bar-right .tb-sep,
    .top-bar-right a[href*="/chess"],
    .top-bar-right a[href*="/sport"] { display: none !important; }
    .top-bar { padding-left: 10px; padding-right: 10px; }
    .top-bar-right { gap: 4px; }
    /* La recherche complete reste visible en tablette -> loupe redondante ;
       sa largeur est bornee pour que la barre tienne a 810px. */
    .top-bar-right #mobileSearchToggle { display: none !important; }
    .top-bar .global-search-wrap { width: 180px; min-width: 0; }
    .top-bar .global-search-wrap input { width: 100%; min-width: 0; }

    /* (Le retour à la ligne des entêtes de page est désormais GLOBAL — voir
       `.main-content .flex-between` près des utilitaires .flex, en haut de ce
       fichier. Les 2 règles qui vivaient ici ne couvraient pas le mobile.) */
}

/* ═══════════════════════════════════════════════════════════════════════
   Rail (sidebar 60px, 769–1180px) — flyout de section au survol d'une icône.
   Les libellés étant masqués dans le rail (font-size:0), survoler une icône
   ouvre un panneau à droite : titre de section + ses pages cliquables. Peuplé
   par le JS de base.html (gaté sur ce même breakpoint). Masqué par défaut ;
   le JS ajoute .show. Pattern « flyout » d'une nav repliée (type VS Code). */
.rail-flyout {
    position: fixed;
    z-index: 1200;
    min-width: 200px;
    max-width: 260px;
    background: var(--bg-sidebar);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: var(--shadow, 0 8px 28px rgba(0, 0, 0, .45));
    padding: 6px;
    display: none;
}
.rail-flyout.show { display: block; }
.rail-flyout-title {
    padding: 7px 12px 6px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--text-muted);
}
.rail-flyout a.nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    font-size: 13px;
    line-height: 1.2;
    white-space: nowrap;
    border-radius: 7px;
    color: var(--text-secondary);
    text-decoration: none;
}
.rail-flyout a.nav-item .icon { font-size: 15px; width: 20px; text-align: center; flex-shrink: 0; }
.rail-flyout a.nav-item:hover { background: var(--bg-secondary); color: var(--text-primary); }
.rail-flyout a.nav-item.active { color: var(--gold); font-weight: 600; }

/* Fix flex universel : .main-content est un flex-item de .app-body ; sans
   min-width:0, son minimum = largeur de son contenu → une grille/table large
   POUSSE toute la page au-delà du viewport (scroll horizontal global).
   min-width:0 le laisse rétrécir ; le contenu large scrolle dans SES
   conteneurs (.table-wrap, tables) au lieu d'élargir la page. */
.main-content { min-width: 0; }


/* ═══════════════════════════════════════════════════════════════════════
   UX TABLEAUX — 2026-07 : affordance de scroll + cellules d'actions compactes
   ───────────────────────────────────────────────────────────────────────
   1) .table-wrap enrichi : les tableaux plus larges que leur conteneur sont
      auto-enveloppés par base.html (script « tables larges ») → scrollbar
      TOUJOURS visible + dégradés de bord (.twr-left/.twr-right posés par le
      script selon la position de scroll) = l'utilisateur VOIT qu'il y a
      d'autres colonnes. Corrige p.ex. Opportunités coupée à droite en 1920.
   2) .td-more : menu replié natif (<details>) pour les cellules qui
      empilaient 4-6 contrôles (Admin Utilisateurs : 2FA, actions) →
      lignes ~3× moins hautes, actions secondaires à 1 clic.
   ═══════════════════════════════════════════════════════════════════════ */
.table-wrap { position: relative; }
.table-wrap::before, .table-wrap::after {
    content: "";
    position: absolute;
    top: 0; bottom: 0;
    width: 26px;
    pointer-events: none;
    opacity: 0;
    transition: opacity .15s ease;
    z-index: 5;
}
.table-wrap::before { left: 0;  background: linear-gradient(to right, var(--bg-card), transparent); }
.table-wrap::after  { right: 0; background: linear-gradient(to left,  var(--bg-card), transparent); }
.table-wrap.twr-left::before  { opacity: 1; }
.table-wrap.twr-right::after { opacity: 1; }

/* Scrollbar horizontale visible en permanence (Windows la masque sinon) */
.table-wrap, .main-content table {
    scrollbar-width: thin;                       /* Firefox */
    scrollbar-color: var(--text-muted) transparent;
}
.table-wrap::-webkit-scrollbar,
.main-content table::-webkit-scrollbar { height: 10px; }
.table-wrap::-webkit-scrollbar-track,
.main-content table::-webkit-scrollbar-track { background: var(--bg-secondary); border-radius: 5px; }
.table-wrap::-webkit-scrollbar-thumb,
.main-content table::-webkit-scrollbar-thumb {
    background: var(--text-muted);
    border-radius: 5px;
    border: 2px solid var(--bg-secondary);
}
.table-wrap::-webkit-scrollbar-thumb:hover,
.main-content table::-webkit-scrollbar-thumb:hover { background: var(--gold); }

/* ── .td-more : actions secondaires repliées dans une cellule ──────────── */
.td-more { position: relative; display: inline-block; }
.td-more > summary {
    list-style: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-width: 30px;
    min-height: 26px;
    padding: 2px 8px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: 6px;
    color: var(--text-secondary);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    user-select: none;
    transition: border-color .15s, color .15s;
}
.td-more > summary::-webkit-details-marker { display: none; }
.td-more > summary:hover { border-color: var(--gold); color: var(--gold); }
.td-more[open] > summary { border-color: var(--gold); color: var(--gold); }
.td-more-panel {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 60;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    min-width: 240px;
    padding: 10px;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: var(--shadow);
}
.td-more-panel form { margin: 0; }
/* Dans un conteneur défilant, un panneau ancré à droite peut sortir à
   gauche de la zone visible sur mobile → bascule possible via .td-more-left */
.td-more.td-more-left .td-more-panel { right: auto; left: 0; }

/* ── .td-clip : texte long sur UNE ligne + ellipsis (title = texte complet).
   Pour les colonnes Contact/Commercial/Client des listes — un nom de société
   de 60+ caractères pliait la cellule sur 2-3 lignes. ─────────────────── */
.td-clip {
    display: inline-block;
    max-width: 240px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

/* Badges de statut/étape : jamais pliés sur deux lignes ni coupés en bord
   de zone défilante. Couvre les pills Axone (.statut-pill) et les badges
   rendus dans les cellules éditables inline (td[data-inline-field] > span). */
.main-content table .statut-pill,
.main-content table td[data-inline-field] > span { white-space: nowrap; }

/* ── En-tête de tableau COLLANT au scroll vertical de page ──────────────
   Sur une liste de 500+ lignes, les titres de colonnes disparaissaient au
   bout de 20 lignes. Offset = top-bar (40) + module-bar (48) ; adapté si
   bandeau replié ou mobile. NB : dans un conteneur défilant (.table-wrap,
   tableaux larges auto-enveloppés), sticky se fige par rapport au conteneur
   → inopérant, sans effet de bord : l'en-tête y reste simplement statique. */
.main-content .data-table thead th,
.main-content .ms-cardify thead th {
    position: sticky;
    top: 88px;
    z-index: 20;
}
/* Fond opaque requis sous un en-tête collant : les tables custom (opp-table,
   devis-table, bc-table, bl-table…) ont un th transparent — on reprend le
   fond de la carte hôte (rendu identique au repos). .data-table garde son
   propre fond (--bg-secondary). */
.main-content .ms-cardify:not(.data-table) thead th { background: var(--bg-card); }
html.pref-modulebar-collapsed .main-content .data-table thead th,
html.pref-modulebar-collapsed .main-content .ms-cardify thead th { top: 56px; }
/* ≤ 1100 px : `.main-content table` est `display:block; overflow-x:auto`
   (refonte mobile ≤768 + zone tablette 769–1100) → le TABLEAU lui-même est le
   contexte de défilement, le sticky s'y ancre et décale l'en-tête sur ses
   premières lignes (mesuré le 2026-09-09 à 491 px : un tableau à 1 ligne,
   ligne 100 % masquée). Inopérant ET nuisible à ces largeurs → statique.
   (Remplace l'ancien `top:56px`, qui ne faisait que déplacer le défaut.) */
@media (max-width: 1100px) {
    .main-content .data-table thead th,
    .main-content .ms-cardify thead th { position: static; top: auto !important; }
}
/* CORRECTIF (bug 2026-07-21, /rd/produits et toutes les listes Barre Synapse) :
   la note ci-dessus supposait le sticky « inerte » dans un conteneur défilant.
   FAUX. Le conteneur de résultats AxoneSearch a `overflow-x:auto` → `overflow-y`
   est calculé à `auto` (règle CSS) → contexte de défilement. Un `thead th`
   sticky `top:88px` s'y ancre : la contrainte « rester ≥88px sous le haut du
   conteneur » DÉCALE l'en-tête de 88px vers le BAS au repos → les 1res lignes
   passent AU-DESSUS de l'en-tête. On annule l'offset dans les conteneurs
   défilants connus (résultats `[id$="-results"]` + `.table-wrap`) : l'en-tête y
   redevient statique (rendu correct). Le sticky page reste actif sur les tables
   NON enveloppées (ex. admin Utilisateurs). */
.main-content [id$="-results"] .data-table thead th,
.main-content [id$="-results"] .ms-cardify thead th,
.main-content .table-wrap .data-table thead th,
.main-content .table-wrap .ms-cardify thead th {
    position: static;
    top: auto !important;
}
/* MÊME DÉFAUT, 3ᵉ famille (mesuré le 2026-09-09 sur /comptabilite/analyse-dette/
   charges-salaires) : un conteneur au `style` INLINE `overflow:auto` /
   `overflow-x:auto` / `max-height + overflow-y:auto` (`.card` de gabarit, pas
   `.table-wrap`) est aussi un contexte de défilement → l'en-tête descend de
   88px, la 1ʳᵉ ligne passe dessous, et un tableau à UNE ligne (« Aucun
   salaire enregistré ») paraît VIDE. 7 sites dans 5 gabarits le portaient.
   `overflow:hidden` crée le même contexte pour sticky : on cible tout `style`
   qui parle d'overflow. Les gabarits neufs posent `.table-wrap` (doctrine). */
.main-content [style*="overflow"] .data-table thead th,
.main-content [style*="overflow"] .ms-cardify thead th {
    position: static;
    top: auto !important;
}

/* ═══════════════════════════════════════════════════════════════════════
   BARRE D'OUTILS D'ÉCRAN — 2026-08 : .mstoolbar / .msseg / .msfilters
   ───────────────────────────────────────────────────────────────────────
   Primitives pour les écrans qui empilent plusieurs sélecteurs au-dessus
   du contenu (planning Care, plans de charge, tableaux de bord).

   Problème corrigé : ces écrans posaient 5 à 7 groupes de boutons côte à
   côte dans une seule rangée `flex-wrap`, chacun avec ~10 lignes de style
   inline dupliqué et SANS étiquette → on ne savait plus quel groupe
   qualifiait quoi (« Tout » = tout quoi ?). Ici :
   - .mstoolbar        = carte à rangées, une bordure entre les niveaux ;
   - .mstoolbar-row    = un niveau de lecture (ce que je regarde / comment) ;
   - .mstoolbar-group  = un sélecteur ÉTIQUETÉ (micro-label + contrôle) ;
   - .msseg            = le segmented control (remplace le style inline).

   Accent : porté par --seg-accent / --seg-on-accent, surchargeables par
   variante (.msseg-green/-blue/-neutral) — jamais de couleur en dur, donc
   suit automatiquement les thèmes clair/sombre et le mode daltonien.
   ═══════════════════════════════════════════════════════════════════════ */
.mstoolbar { padding: 0; margin-bottom: 16px; }
.mstoolbar-row {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 8px 18px;
    padding: 11px 16px;
}
/* Niveau 2 = options DE la vue courante : rattaché visuellement au niveau 1
   par une bordure, et légèrement en retrait pour marquer la subordination. */
.mstoolbar-row + .mstoolbar-row {
    border-top: 1px solid var(--border);
    background: color-mix(in srgb, var(--bg-secondary) 45%, transparent);
}
.mstoolbar-group { display: inline-flex; flex-direction: column; gap: 5px; }
.mstoolbar-label {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--text-muted);
    line-height: 1;
}
.mstoolbar-end { margin-left: auto; }
/* Groupe d'actions aligné sur la ligne de base des segments étiquetés. */
.mstoolbar-actions { display: inline-flex; gap: 6px; align-items: center; }

/* ── .msseg : segmented control (choix exclusif) ───────────────────────── */
.msseg {
    --seg-accent: var(--gold);
    --seg-on-accent: var(--on-gold, #111827);
    display: inline-flex;
    background: var(--bg-secondary);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.msseg-green   { --seg-accent: var(--green); --seg-on-accent: #fff; }
.msseg-blue    { --seg-accent: var(--blue);  --seg-on-accent: #fff; }
.msseg-neutral { --seg-accent: var(--text-secondary); --seg-on-accent: var(--bg-card); }

.msseg-item {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 7px 14px;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    color: var(--text-secondary);
    transition: var(--transition);
}
.msseg-item + .msseg-item { border-left: 1px solid var(--border); }
.msseg-item:hover:not(.msseg-item-active) {
    background: var(--bg-input);
    color: var(--text-primary);
}
.msseg-item-active { background: var(--seg-accent); color: var(--seg-on-accent); }
/* Variante compacte : sélecteurs secondaires (unité, granularité). */
.msseg-sm .msseg-item { padding: 6px 11px; font-size: 12px; }

/* ── .msfilters : bloc de filtres repliable ──────────────────────────────
   `padding: 0` obligatoire : le padding de `.card` s'ajouterait à celui du
   <summary>, portant la barre repliée à ~93 px au lieu de ~44 (mesuré). */
.msfilters { padding: 0; margin-bottom: 16px; }
.msfilters > summary {
    list-style: none;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 11px 16px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
    transition: var(--transition);
}
.msfilters > summary::-webkit-details-marker { display: none; }
.msfilters > summary:hover { color: var(--text-primary); }
.msfilters[open] > summary { border-bottom: 1px solid var(--border); }
/* Chevron : pivote à l'ouverture (repère d'état sans dépendre de la couleur). */
.msfilters > summary .msfilters-chevron { transition: transform .15s ease; }
.msfilters[open] > summary .msfilters-chevron { transform: rotate(90deg); }
/* Résumé des filtres actifs, lisible sans déplier. */
.msfilters-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: 100px;
    background: color-mix(in srgb, var(--gold) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--gold) 35%, transparent);
    color: var(--text-primary);
    font-size: 11px;
    font-weight: 600;
}
.msfilters-body { padding: 14px 16px; }

/* ── Tactile : cibles ≥ 40 px (capacité, pas largeur — cf. règle responsive) */
@media (pointer: coarse) {
    .msseg-item { min-height: 40px; padding-inline: 16px; }
    .msseg-sm .msseg-item { min-height: 40px; }
    .msfilters > summary { min-height: 44px; }
}
/* ── ≤ 640 px : un groupe par ligne, ses segments se partagent la largeur ──
   Deux dispositions plus denses ont été MESURÉES puis écartées :
   - groupes en demi-largeur (~170 px) : les segments tombent sous la largeur
     de leur libellé. En `nowrap`, « Trimestriel » se faisait rogner par
     l'overflow du .msseg (débordement MASQUÉ, invisible au scrollWidth) ;
   - + `overflow-wrap: anywhere` : plus de rognage, mais des mots cassés sur
     3 lignes → segments de 81 px et barre PLUS haute (449 px) qu'en empilé.
   Empilé, chaque segment tient sur une ligne (~40 px) sans troncature.
   Le vrai levier de hauteur n'est pas la compression mais le MASQUAGE des
   groupes secondaires : côté template, « Unité » et « Exporter » portent
   `.ms-hide-sm` (barre 462 → 340 px sur iPhone, mesuré). Corollaire : les
   libellés doivent être courts sous 769 px (`.ms-only-sm`), sinon un segment
   sort de la bande — `test_responsive_matrix.py` en fait échouer la CI. */
@media (max-width: 640px) {
    .mstoolbar-row { gap: 10px 12px; padding: 10px 12px; }
    .mstoolbar-group { flex: 1 1 100%; min-width: 0; }
    /* La bande DÉFILE si ses segments ne tiennent pas — elle ne les rogne
       jamais. `flex: 1 0 auto` (et non `1 1 0` + min-width:0) : les segments
       remplissent la ligne mais ne descendent PAS sous la largeur de leur
       libellé, sinon `.msseg{overflow:hidden}` coupe le texte sans élargir la
       page (« Par échéance » → « Par échéa » à 375 px). Le défilement reste
       dans le conteneur : la PAGE, elle, ne défile pas latéralement. */
    .msseg { width: 100%; overflow-x: auto; scrollbar-width: none; }
    .msseg::-webkit-scrollbar { display: none; }
    .msseg-item { flex: 1 0 auto; padding: 9px 8px; font-size: 12px; }
    /* Bandes compactes : jusqu'à 5 segments sur une ligne de ~300 px. */
    .msseg-sm .msseg-item { padding: 9px 6px; }
    .mstoolbar-end { margin-left: 0; }
    .mstoolbar-actions > .btn { flex: 1 1 auto; }
    .msfilters-body { padding: 12px; }
}

/* ═══ Accessibilité + perf : respect de prefers-reduced-motion ═══
   Chantier UX multi-environnement (Firefox/Ubuntu). L'utilisateur qui a activé
   « réduire les animations » au niveau de son OS voit :
   - animations/transitions quasi supprimées (WCAG 2.3.3) ;
   - flou GPU (backdrop-filter) neutralisé — coûteux sur machines modestes /
     Linux sans accélération matérielle, alors qu'un fond opaque de repli existe
     déjà sur les overlays concernés.
   Zéro changement pour les utilisateurs n'ayant pas activé ce réglage. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
    * {
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
    }
}


/* ══════════════════════════════════════════════════════════════════════
   FOCUS CLAVIER — anneau maitrise (WCAG 2.4.7)
   ══════════════════════════════════════════════════════════════════════
   :focus-visible ne se declenche qu'a la navigation clavier : aucun anneau
   n'apparait au clic souris. Specificite volontairement basse (0,1,0) pour
   que les composants qui gerent deja leur focus (champs de formulaire) gardent
   la main. En fin de fichier : a specificite egale, c'est la derniere regle
   qui gagne. */
:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
    border-radius: 2px;
}
/* Zones qui restent sombres quel que soit le theme : --gold y est assombri. */
.sidebar :focus-visible,
.top-bar :focus-visible,
.rail-flyout :focus-visible {
    outline-color: var(--sidebar-accent);
}


/* ══════════════════════════════════════════════════════════════════════
   ÉLÉMENT VISÉ — arriver « sur » un objet doit se VOIR
   ══════════════════════════════════════════════════════════════════════
   Posée par `static/js/ms_focus.js` quand un lien d'agrégateur désigne un
   objet déjà présent dans la page. Sans repère, viser une ligne au milieu de
   trente ne se distingue pas de ne rien faire — c'est le défaut qu'on répare.
   `prefers-reduced-motion` respecté : le repère reste, l'animation part. */
/* 3 s, pas 1,8 : le repere demarre pendant que la page finit de charger (on
   arrive dessus par une navigation), et sur un TABLEAU a fond blanc il est
   beaucoup plus discret que sur une carte. Mesure a l'ecran le 03/09 sur
   /document-requests/ : a 1,8 s il fallait figer l'animation pour le voir. */
.ms-vise {
    animation: msVise 3s ease-out 1;
}
.ms-vise > td {
    animation: msViseCellule 3s ease-out 1;
}
@keyframes msVise {
    0%, 60% {
        background: color-mix(in srgb, var(--gold) 18%, transparent);
        border-color: var(--gold);
    }
}
@keyframes msViseCellule {
    0%, 60% { background: color-mix(in srgb, var(--gold) 18%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
    .ms-vise, .ms-vise > td { animation: none; }
    .ms-vise { outline: 2px solid var(--gold); outline-offset: 2px; }
}


/* ═══════════════════════════════════════════════════════════════════════
   COMPATIBILITÉ CLASSES BOOTSTRAP — gabarits écrits « en Bootstrap » (2026-09-14)
   ═══════════════════════════════════════════════════════════════════════
   Mesuré le 14/09/2026 : 67 gabarits (dont les 10 de la famille Formation
   PULSE, 14 en RH au total) sont écrits avec la grille et les utilitaires
   Bootstrap — `row` / `col-md-*`, `card-body`, `form-select`, `btn-primary`,
   `table table-hover`, `badge bg-*`, `pagination`… — alors que Bootstrap n'a
   JAMAIS été chargé par base.html. Résultat : KPI empilés sur toute la
   largeur, tableaux sans bordure ni alignement, boutons nus (Seb, sur les
   souhaits de formation : « la page est moche !!! »).

   Cette section rend ces classes avec les jetons du design system, sans
   couleur en dur. Elle ne remplace pas une réécriture des gabarits — elle
   les rend LISIBLES en attendant, et elle est gardée par
   tests/test_theme_compat_bootstrap.py : toute classe Bootstrap employée
   par un gabarit doit avoir une règle ici. Ne pas ajouter de nouveau gabarit
   en Bootstrap : les primitives .ms-grid / .ms-form-grid / .table-wrap
   (fin de ce fichier) sont la règle pour le code neuf.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Conteneur et grille ─────────────────────────────────────────────── */
.container-fluid { width: 100%; }
.row { display: flex; flex-wrap: wrap; gap: 16px 0; margin-inline: -8px; }
.row > [class*="col-"], .row > .col { padding-inline: 8px; min-width: 0; box-sizing: border-box; }
.col { flex: 1 1 0; }
.col-12, .col-md-12, .col-lg-12 { flex: 0 0 100%; max-width: 100%; }
.col-md-1 { flex: 0 0 8.3333%; max-width: 8.3333%; }
.col-md-2 { flex: 0 0 16.6667%; max-width: 16.6667%; }
.col-md-3 { flex: 0 0 25%; max-width: 25%; }
.col-md-4 { flex: 0 0 33.3333%; max-width: 33.3333%; }
.col-md-5 { flex: 0 0 41.6667%; max-width: 41.6667%; }
.col-md-6 { flex: 0 0 50%; max-width: 50%; }
.col-md-7 { flex: 0 0 58.3333%; max-width: 58.3333%; }
.col-md-8 { flex: 0 0 66.6667%; max-width: 66.6667%; }
.col-md-9 { flex: 0 0 75%; max-width: 75%; }
.col-md-10 { flex: 0 0 83.3333%; max-width: 83.3333%; }
.col-lg-3 { flex: 0 0 25%; max-width: 25%; }
.col-lg-4 { flex: 0 0 33.3333%; max-width: 33.3333%; }
.col-lg-6 { flex: 0 0 50%; max-width: 50%; }
.col-lg-8 { flex: 0 0 66.6667%; max-width: 66.6667%; }
.col-sm-6 { flex: 0 0 50%; max-width: 50%; }
.g-3 { gap: 16px; }
.g-2 { gap: 12px; }
@media (max-width: 768px) {
    .row > [class*="col-md-"], .row > [class*="col-lg-"] { flex: 0 0 100%; max-width: 100%; }
}
@media (max-width: 480px) {
    .row > [class*="col-sm-"] { flex: 0 0 100%; max-width: 100%; }
}

/* ── Cartes ──────────────────────────────────────────────────────────── */
.card-body { padding: 16px 18px; }
.card-header { padding: 12px 18px; border-bottom: 1px solid var(--border); font-weight: 600; background: var(--bg-secondary); }
.card-footer { padding: 12px 18px; border-top: 1px solid var(--border); }
.card-title { font-weight: 600; margin: 0 0 6px; }
.card-body > h3 { margin: 4px 0 0; font-family: 'JetBrains Mono', monospace; font-weight: 800; font-size: 1.6rem; color: var(--gold); }
.shadow-sm { box-shadow: var(--shadow); }
.border-0 { border: 0 !important; }
.rounded { border-radius: var(--radius); }

/* ── Formulaires ─────────────────────────────────────────────────────── */
.form-control, .form-select {
    display: block; width: 100%; box-sizing: border-box;
    padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--bg-input); color: var(--text-primary); font: inherit;
}
.form-control:focus, .form-select:focus { outline: 2px solid var(--gold); outline-offset: 1px; }
textarea.form-control { min-height: 96px; resize: vertical; }
.form-text { display: block; font-size: .8rem; color: var(--text-muted); margin-top: 4px; }
.form-check { display: flex; align-items: center; gap: 8px; }
.form-check-input { width: 16px; height: 16px; }
.input-group { display: flex; align-items: stretch; }
.input-group > .form-control { flex: 1 1 auto; border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.input-group-text { padding: 8px 12px; border: 1px solid var(--border); border-left: 0; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; background: var(--bg-secondary); color: var(--text-muted); }

/* ── Boutons : chaque variante Bootstrap s'aligne sur .btn maison ────── */
.btn-primary { background: var(--gold); color: var(--bg-primary); border: 1px solid var(--gold); }
.btn-primary:hover { filter: brightness(1.08); }
.btn-secondary { background: var(--bg-secondary); color: var(--text-primary); border: 1px solid var(--border); }
.btn-success { background: var(--green); color: #fff; border: 1px solid var(--green); }
.btn-danger { background: var(--red); color: #fff; border: 1px solid var(--red); }
.btn-warning { background: var(--orange); color: #fff; border: 1px solid var(--orange); }
.btn-info { background: var(--blue); color: #fff; border: 1px solid var(--blue); }
.btn-outline-primary { background: transparent; color: var(--gold); border: 1px solid var(--gold); }
.btn-outline-secondary { background: transparent; color: var(--text-secondary); border: 1px solid var(--border); }
.btn-outline-success { background: transparent; color: var(--green); border: 1px solid var(--green); }
.btn-outline-danger { background: transparent; color: var(--red); border: 1px solid var(--red); }
.btn-outline-warning { background: transparent; color: var(--orange); border: 1px solid var(--orange); }
.btn-outline-info { background: transparent; color: var(--blue); border: 1px solid var(--blue); }
[class*="btn-outline-"]:hover { background: var(--bg-secondary); }
.btn-group { display: inline-flex; gap: 4px; }
.btn-group-sm > .btn, .btn.btn-sm { padding: 4px 10px; font-size: .85rem; }
.w-100 { width: 100%; }
.h-100 { height: 100%; }

/* ── Tableaux ────────────────────────────────────────────────────────── */
.table-responsive { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { padding: 10px 12px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; }
.table thead th, .table-light th { background: var(--bg-secondary); color: var(--gold); font-weight: 600; font-size: .85rem; }
.table-hover tbody tr:hover { background: color-mix(in srgb, var(--gold) 6%, transparent); }
.table-sm th, .table-sm td { padding: 6px 8px; }

/* ── Badges ──────────────────────────────────────────────────────────── */
.badge { display: inline-block; padding: 3px 9px; border-radius: 4px; font-size: .78rem; font-weight: 600; line-height: 1.2; color: #fff; background: var(--text-muted); }
.badge.bg-primary, .bg-primary { background: var(--gold) !important; color: var(--bg-primary) !important; }
.badge.bg-secondary, .bg-secondary { background: var(--text-muted) !important; color: #fff; }
.badge.bg-success, .bg-success { background: var(--green) !important; color: #fff; }
.badge.bg-danger, .bg-danger { background: var(--red) !important; color: #fff; }
.badge.bg-warning, .bg-warning { background: var(--orange) !important; color: #fff; }
.badge.bg-info, .bg-info { background: var(--blue) !important; color: #fff; }
.badge.bg-light, .bg-light { background: var(--bg-secondary) !important; color: var(--text-primary) !important; }
.badge.bg-dark, .bg-dark { background: var(--text-primary) !important; color: var(--bg-primary) !important; }
.rounded-pill { border-radius: 999px; }

/* ── Alertes ─────────────────────────────────────────────────────────── */
.alert { padding: 12px 16px; border-radius: var(--radius-sm); border-left: 4px solid var(--border); background: var(--bg-secondary); margin-bottom: 16px; }
.alert-info { border-left-color: var(--blue); }
.alert-warning { border-left-color: var(--orange); }
.alert-danger { border-left-color: var(--red); }
.alert-success { border-left-color: var(--green); }

/* ── Pagination ──────────────────────────────────────────────────────── */
.pagination { display: flex; gap: 6px; list-style: none; padding: 0; margin: 0; flex-wrap: wrap; }
.page-link { display: inline-block; padding: 6px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--gold); text-decoration: none; }
.page-item.active .page-link { background: var(--gold); color: var(--bg-primary); border-color: var(--gold); }
.page-item.disabled .page-link { opacity: .5; pointer-events: none; }

/* ── Utilitaires de mise en page et de texte ─────────────────────────── */
.d-flex { display: flex; }
.d-block { display: block; }
.d-inline-block { display: inline-block; }
.d-none { display: none; }
.flex-wrap { flex-wrap: wrap; }
.flex-column { flex-direction: column; }
.align-items-center { align-items: center; }
.align-items-end { align-items: flex-end; }
.align-items-start { align-items: flex-start; }
.justify-content-center { justify-content: center; }
.justify-content-between { justify-content: space-between; }
.justify-content-end { justify-content: flex-end; }
.text-center { text-align: center; }
.text-end { text-align: right; }
.text-start { text-align: left; }
.text-danger { color: var(--red-text, var(--red)); }
.text-success { color: var(--green); }
.text-warning { color: var(--orange-text, var(--orange)); }
.text-info, .text-primary { color: var(--blue-text, var(--blue)); }
.small { font-size: .85rem; }
.fst-italic { font-style: italic; }
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: 4px; }
.mb-2 { margin-bottom: 8px; }
.mb-4 { margin-bottom: 24px; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 4px; }
.mt-2 { margin-top: 8px; }
.mt-3 { margin-top: 16px; }
.mt-4 { margin-top: 24px; }
.me-1 { margin-right: 4px; }
.me-2 { margin-right: 8px; }
.me-3 { margin-right: 16px; }
.ms-1 { margin-left: 4px; }
.ms-2 { margin-left: 8px; }
.ms-auto { margin-left: auto; }
.py-2 { padding-block: 8px; }
.py-3 { padding-block: 16px; }
.py-4 { padding-block: 24px; }
.px-2 { padding-inline: 8px; }
.px-3 { padding-inline: 16px; }
.p-3 { padding: 16px; }
.p-4 { padding: 24px; }
.gap-1 { gap: 4px; }
.gap-3 { gap: 16px; }

/* ── Icônes « bi bi-* » (Bootstrap Icons, jamais chargées) ───────────── */
.bi { display: inline-block; font-style: normal; line-height: 1; }
.bi::before { content: "•"; }
.bi-plus-circle::before { content: "＋"; }
.bi-check-circle::before { content: "✓"; }
.bi-x-circle::before { content: "✕"; }
.bi-pencil::before { content: "✎"; }
.bi-trash::before { content: "🗑"; }
.bi-eye::before { content: "👁"; }
.bi-envelope::before { content: "✉"; }
.bi-info-circle::before { content: "ⓘ"; }
.bi-exclamation-circle::before, .bi-exclamation-triangle::before { content: "⚠"; }
.bi-arrow-left::before { content: "←"; }
.bi-arrow-clockwise::before { content: "↻"; }
.bi-telephone::before { content: "☎"; }
.bi-robot::before { content: "🤖"; }
.bi-mailbox::before { content: "📬"; }
.bi-sliders::before { content: "⚙"; }
.bi-save::before { content: "💾"; }
.bi-person-fill::before { content: "👤"; }
.bi-laptop::before { content: "💻"; }
.bi-funnel::before { content: "⏷"; }
.bi-file-earmark-excel::before { content: "📊"; }
.bi-download::before { content: "⬇"; }
.bi-diagram-3::before { content: "🗂"; }
