/* theme.css — tokens + light/dark. Les couleurs de marque (--brand-*) sont injectées
   par PHP dans le <head> (theme_css_vars()). Ici on dérive tout le reste.
   RÈGLE : aucune couleur de marque en dur — toujours var(--brand-primary/accent). */

/* ═══════════════════════════════════════════════════════════════════════════════
   TROIS RÔLES, TROIS TOKENS — ne jamais les confondre (doctrine : STANDARDS-UI §5bis)

   --brand-primary / --brand-accent  PEINTURE BRUTE, injectée par bootstrap.php:174-179.
                                     Pas dark-aware. Ne sort JAMAIS de ce fichier.
   --navy / --red / --green          APLAT : on POSE dessus une encre claire (#fff).
                                     Reste donc volontairement SOMBRE dans les deux modes.
   --navy-ink / --red-ink            ENCRE : texte, icône, anneau de focus, posés SUR
   --green-ink                       --bg / --bg-2 / --surface / --*-soft.
                                     Bouge franchement en sombre — et, depuis V32-D,
                                     plancherisée AUSSI en clair (blocs « CHEMIN DE REPLI »
                                     et « DEUX CHEMINS, DEUX RÔLES »).
                                     ⚠️ ENCRE ≠ APLAT : ne jamais la poser en background,
                                     un fond sombre en mode clair (--adm-chrome du shell
                                     admin) inverse le sens du correctif.
   --navy-soft / --red-soft          TEINTE de fond, recomposée sur la surface du mode.
   --green-soft

   ⚠️ --navy ne peut pas servir les deux rôles : l'éclaircir pour le texte casserait
   .pill--navy / .btn--primary / .nav__link--active (blanc sur navy) qui tiennent
   aujourd'hui 5,29:1 et tomberaient à ~2,1:1. C'est pourquoi l'encre est un token à part.

   ⚠️ La doctrine ne s'arrête PAS aux couleurs de marque. --green est un token FIXE
   (jamais décliné par client, cf. CLAUDE.md) et il portait exactement le même défaut :
   une seule définition, jamais recomposée en sombre, employée à la fois en aplat (18
   sites) et en encre (36 sites) → 3,38:1 en sombre. Tout token porteur des DEUX rôles
   se scinde, marque ou pas. Différence : sur un token fixe, aucune contrainte
   white-label ⇒ valeurs directes, pas de plancher oklch(from …) à calibrer contre une
   marque inconnue.
   ═══════════════════════════════════════════════════════════════════════════════ */

:root {
    /* Aliases utilisés par les composants (mappés sur les tokens de marque) */
    --navy: var(--brand-primary);
    --red: var(--brand-accent);
    --navy-soft: var(--brand-primary-soft);
    --red-soft: var(--brand-accent-soft);

    /* ENCRES du mode clair — CHEMIN DE REPLI (moteurs sans syntaxe de couleur relative).
       Plancher LINÉAIRE, symétrique exact du repli sombre (bloc « ENCRES — repli linéaire »
       de .theme-dark) : là-bas 40 % de
       marque + white REMONTE l'encre, ici 55 % de marque + black la DESCEND. Avant ce lot
       ce repli était la marque BRUTE, sans aucun plafond → 2,03:1 mesuré sur une marque
       claire (cf. bloc « DEUX CHEMINS » ci-dessous).

       ⚠️ Interpolation en OKLAB, pas en OKLCH. black vaut L=0, a=0, b=0 : le mélange se
       réduit alors à MULTIPLIER (L, a, b) par 0,55, ce qui préserve la teinte PAR
       CONSTRUCTION, sans dépendre du traitement « powerless » de la teinte d'un noir
       achromatique. Sorties mesurées bit-identiques à la variante oklch (ΔL=ΔC=ΔH=0,00000),
       et à tout pourcentage : c'est un durcissement gratuit, pas un changement de rendu.
       Ne pas « harmoniser » en oklch pour faire comme les voisins — le piège polaire
       documenté sur --green-soft (bloc « VERT sémantique en sombre ») venait d'un second
       terme CHROMATIQUE (252°) ;
       ici le second terme est achromatique, et oklab rend le point indiscutable.

       55 % et pas 60/65 % : sur un balayage de 4 872 couples marque/fond, 65 % laisse
       182 échecs AA et 60 % en laisse encore 2 ; 55 % est le premier réglage à ZÉRO échec
       (pire cas 5,27:1). Le surcoût esthétique de 60→55 % est de +0,50 de contraste sur
       Servair, sur un chemin où l'identité de marque est de toute façon perdue — voir la
       démonstration des « fenêtres disjointes » ci-dessous avant de vouloir remonter. */
    --navy-ink: color-mix(in oklab, var(--brand-primary) 55%, black);
    --red-ink: color-mix(in oklab, var(--brand-accent) 55%, black);

    /* ENCRE POUR SURFACE SOMBRE EN PERMANENCE — token INVARIANT (V32-D).
       Quatrième rôle, distinct des trois du bloc de tête : --red-ink est une encre POUR
       FONDS CLAIRS (c'est ce que mesure et garantit tout le lot ci-dessus). Certaines
       surfaces sont sombres dans les DEUX modes — le chrome de la console admin :
       --adm-chrome/-2 valent var(--ink)/#0B0E19 en clair et #0A0D10/#05070A en sombre
       (admin-shell.css:10-11 et :26). Y poser --red-ink INVERSE le sens du correctif :
       l'assombrir y fait CHUTER le contraste (mesuré 1,32:1 sur .adm-shell__logo-word b).

       ⚠️ CE TOKEN NE DOIT PAS ÊTRE REDÉFINI DANS .theme-dark, et ce n'est PAS un oubli.
       Son fond est un quasi-noir dans les deux modes (luminances relatives ≈ 0,005 et
       ≈ 0,0016) : l'écart est absorbé par le +0,05 de la formule WCAG, moins de ~3 % sur
       le ratio. La valeur DOIT donc être identique des deux côtés. La règle « tout token
       de :root absent de .theme-dark fuite » ne vaut que pour les tokens DÉPENDANTS du
       mode ; pour un token invariant, cette absence EST l'intention. Précédent dans ce
       fichier : --green, :root-only et délibérément identique dans les deux modes.

       Valeur = le plancher du mode sombre (blocs « ENCRES — repli linéaire » de .theme-dark
       et « PLANCHER DE CLARTÉ (mode sombre) »), volontairement mimé à
       l'identique pour que le parallèle saute aux yeux. oklch et non oklab ici : white est
       achromatique (a=b=0), les deux espaces sont donc mathématiquement équivalents sur ce
       mélange — le choix est de COHÉRENCE avec les encres sombres, sans contrepartie.

       MESURÉ sur les QUATRE chromes sombres réels du produit — --ink (#0F1320) et --page-frame
       (#0A0D10) pour .admin-topbar, --adm-chrome-2 (#0B0E19 clair / #05070A sombre) pour
       .adm-shell__brand — × 8 marques × les DEUX chemins = 64 combinaisons, par deux
       implémentations indépendantes qui convergent au centième :
         · repli (déclaration ci-dessous) : 0/32 sous AA, pire cas 8,01:1. Ratio CROISSANT
           avec la clarté de la marque ⇒ pire cas = marque la plus SOMBRE, comportement
           monotone, aucune zone de bascule ;
         · moderne (bloc @supports plus bas) : 0/32 sous AA, pire cas 6,97:1 (accent Servair
           sur la topbar claire) — le pire cas n'y est PAS la marque la plus sombre.
       PIRE CAS ABSOLU : 6,97:1, marge +2,47 sur AA. Sûr pour toute marque white-label.

       ⚠️ Ne jamais documenter le seul repli ici : c'est le chemin MODERNE qui est servi à la
       quasi-totalité des utilisateurs. L'inversion entre les deux chemins est voulue et va
       dans le bon sens : max(l,0.78) PLAFONNE l'éclaircissement là où le repli à 40 % continue
       de blanchir. Le chemin moderne est donc plus conservateur sur les marques claires, et
       préserve mieux la bichromatie du logotype — l'argument même qui a fait refuser --adm-on
       sur les 11 sites consommateurs. */
    --red-ink-on-dark: color-mix(in oklch, var(--brand-accent) 40%, white);

    /* ---- VERT sémantique (token FIXE, jamais par client) ----
       APLAT — fond plein qui porte du #fff : .toggle--on, .admin-env__dot,
       .status-pill--success, .setup__step-dot, .adm-dot--green.
       INCHANGÉ dans les deux modes, exactement comme --navy : #fff dessus = 4,63:1 ✓,
       et il se sépare de ses fonds à 4,63:1 (surface claire) / 3,39:1 (surface sombre),
       donc ≥ 3:1 (WCAG 1.4.11) des deux côtés. NE PAS l'éclaircir pour le texte. */
    --green: oklch(0.55 0.12 145);

    /* ENCRE claire — même chroma, même teinte, L abaissée de 0,55 à 0,50.
       --green ne pouvait déjà PAS servir d'encre en CLAIR : 4,20:1 sur --bg et
       3,93:1 sur --green-soft, deux mesures sous AA. À L=0,50 : 5,72:1 sur --surface,
       5,21:1 sur --bg, 4,89:1 sur --green-soft (valeurs re-dérivées le 2026-08-18 par
       bin/contrast/certify.php ; les précédentes — 5,69 / 5,19 / 4,86 — provenaient d'un
       calculateur antérieur non certifié). Correction d'accessibilité, pas
       de retouche esthétique. */
    --green-ink: oklch(0.50 0.12 145);

    /* TEINTE claire — valeur reprise à l'IDENTIQUE des deux copies retirées de
       screens/admin-fraud.css et screens/admin-governance.css : zéro pixel bougé
       en clair, le token est simplement rapatrié dans le système. */
    --green-soft: oklch(0.94 0.05 145);

    /* ---- VERT TABLEUR (token FIXE, jamais par client) ----
       Sert UNIQUEMENT aux boutons d'export CSV (.btn--export). Ce vert n'est pas une couleur de
       marque et ne doit JAMAIS suivre --brand-primary : il signale « ceci ouvre un tableur », un
       repère appris ailleurs que chez nous. Le rendre thémable le viderait de son sens — même
       logique que --green, --navy et --red.
       DISTINCT de --green, volontairement : --green dit « succès / conforme » (statuts, dots,
       toggles). Les confondre ferait lire un export comme une validation.

       ⚠️ CONTRAIREMENT à --navy et --green, il A une variante sombre, et ce n'est pas un choix
       esthétique. Mesuré (bin/contrast/oklch.php, 2026-08-20) : à L=0,45 la plaque ne se sépare
       de --surface sombre qu'à 2,20:1 et de --bg sombre qu'à 2,61:1 — sous le seuil de 3:1 de
       WCAG 1.4.11 pour un composant d'interface. Le bouton se serait fondu dans le fond.
       Le balayage de L montre qu'UNE seule valeur (L=0,54) satisfait les deux modes à la fois,
       et elle tombe à deltaE 0,01 de --green : ce serait rendre l'export indiscernable du
       succès. D'où le patron déjà employé pour --red-ink-on-dark : plaque éclaircie en sombre,
       encre inversée. */
    --excel: oklch(0.45 0.10 156);            /* #14653D */
    --excel-hover: oklch(0.40 0.10 156);      /* #005730 */
    --excel-ink: #ffffff;

    /* Tokens fixes (jamais par client) */
    --display: 'Bricolage Grotesque', Helvetica, system-ui;
    --ui: Helvetica, 'Helvetica Neue', Arial, system-ui, sans-serif;

    /* Rayons / traits */
    --r-pill: 999px;
    --r-card: 22px;
    --r-panel: 16px;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ENCRES DU MODE CLAIR : DEUX CHEMINS, DEUX RÔLES — ne jamais en supprimer un
   (lot V32-D, 2026-08-18)

   Le mode clair définit --navy-ink / --red-ink DEUX fois, et c'est VOULU :
     · le repli :root, bloc « CHEMIN DE REPLI » ci-dessus, doit être SÛR    → plancher
       linéaire, inconditionnel ;
     · ce bloc @supports                                doit être FIDÈLE → plafond
       conditionnel, la marque n'est touchée QUE si elle est trop claire.
   Un moteur récent prend le second, un moteur ancien retombe sur le premier.

   POURQUOI DEUX CORRECTIFS ET PAS UN. Le défaut d'origine était double :
     1. le plafond de ce bloc était réglé trop HAUT (min(l, 0.55)) : le ratio plafonnait
        à 4,0-4,3:1, sous AA ;
     2. le repli n'évalue JAMAIS oklch(from …) — il ne verra donc AUCUN min(l, …), quelle
        que soit sa valeur. C'est lui qui produisait le 2,03:1, et il ne se manifeste que
        sur les moteurs sans couleur relative : INVISIBLE en développement.
   Baisser min() ne corrige donc QUE ce bloc-ci. Le repli se corrige à part, dans le bloc
   « CHEMIN DE REPLI ».

   POURQUOI 0.45 ET PAS 0.55 NI 0.50. À 0,55 le plafond ne se déclenche même pas sur
   l'accent Servair (L=0,538) : garde-fou décoratif. À 0,50, l'accent vert #2E7D32 reste
   à 4,30:1 — encore sous AA (mesure runbook §I5). À 0,45 : ZÉRO échec sur 4 872 couples
   marque/fond, pire cas 5,10:1. On ne livre pas un garde-fou qui échoue sur un cas connu.

   ⚠️ HORS GAMUT ATTENDU, PAS UN BUG. Conserver le chroma d'origine en abaissant la clarté
   pousse certaines marques hors du sRGB : 3 encres sur 8 mesurées sortent du gamut sur ce
   chemin, et le mapping est laissé au moteur (CSS Color 4 §13.2). Deux navigateurs peuvent
   donc rendre ces encres LÉGÈREMENT différemment. C'est le prix de la fidélité de teinte,
   et c'est la contrepartie exacte du bloc « CHEMIN DE REPLI », lui DÉTERMINISTE (0 sortie de gamut
   sur 8) mais infidèle. Ne pas « corriger » cet écart en bricolant le chroma.

   CE QUI BOUGE VRAIMENT EN CLAIR (l'ancien commentaire disait « aucun pixel ne bouge
   en clair » — c'est FAUX depuis ce lot, et ce n'était vrai que d'un seul des deux
   chemins) :
     · sur CE chemin, --brand-primary Servair (L=0,349) est SOUS le plafond : min() ne se
       déclenche pas, l'encre primaire est strictement inchangée. Seul l'accent (L=0,538)
       descend à 0,45. C'est le seul pixel qui bouge sur un moteur récent ;
     · sur le REPLI, tout descend, y compris une marque déjà sombre. Un plancher linéaire
       ne peut pas être conditionnel. Contrepartie explicitement acceptée par le
       propriétaire : « un contraste de 13,66 est laid ; un contraste de 2,03 est
       invisible ».

   À SIGNALER À L'ONBOARDING D'UN NOUVEAU CLIENT (conséquence de design, pas un défaut :
   ne pas « corriger »). Pour une marque de clarté MOYENNE (L 0,50-0,70), l'ENCRE est
   clampée à 0,45 alors que l'APLAT (--navy / --red sur .btn--primary, .pill--navy,
   .nav__link--active, .daycard--sel) reste à la clarté brute : sur un même écran, encre
   et aplat cessent visiblement d'être « le même bleu ». C'est inhérent au scindement
   encre/aplat déjà acté pour --navy et --green, mais c'est la première fois qu'il devient
   perceptible côté CLAIR. Une marque sombre comme Servair (L=0,349) n'est pas concernée :
   le plafond ne se déclenche pas.

   POURQUOI L'IDENTITÉ EST NÉCESSAIREMENT SACRIFIÉE SUR LE REPLI — à lire avant de
   remonter le pourcentage. Les deux fenêtres sont DISJOINTES : atteindre 0 échec AA
   exige P ≤ 62 %, préserver l'identité du primaire exigerait P ≥ 95,5 %. 33,5 points
   d'écart, aucun recouvrement. Cause structurelle : #013480 INTACT est déjà à 1,591 de
   contraste contre --ink — un bleu marine EST, perceptivement, presque du texte noir.
   Aucun réglage de P ne satisfait les deux objectifs. Ce n'est pas un compromis mal
   réglé, c'est un choix binaire, et il est tranché en faveur de la lisibilité.
   Le coût est MESURÉ, pas estimé : sur le primaire Servair, le rapport encre/--ink passe
   de 1,591 (marque intacte) à 1,002 sur le repli — l'encre de marque devient littéralement
   indiscernable du texte neutre. C'est le prix, il est connu, il est accepté ; le chemin
   moderne est précisément ce qui évite de le payer sur un navigateur récent.

   🔒 GARDE POSITIONNELLE — ÉDITER SUR PLACE, NE PAS RÉORGANISER. La protection du mode
   sombre repose sur DEUX mécanismes, et sur aucune spécificité (:root et .theme-dark
   valent tous deux 0,1,0 — elles sont ÉGALES) :
     1. niveau d'élément : .theme-dark est posé sur <body> (partials/head.php:45) tandis
        que :root vise <html> ; la déclaration propre à body l'emporte sur la valeur
        héritée de html ;
     2. ordre source : ces deux blocs :root sont AVANT le bloc .theme-dark
        (« Mode sombre (certains clients) »), plus bas dans ce fichier.
   Le mécanisme 1 suffit aujourd'hui, mais il tomberait si la classe de mode passait un
   jour sur <html> (refactor banal) — il ne resterait alors que l'ordre source. Déplacer
   un bloc :root APRÈS .theme-dark écrase les encres sombres : dégât mesuré 1,03 à
   3,20:1, texte quasi invisible. Garder les deux protections.
   ═══════════════════════════════════════════════════════════════════════════════ */
@supports (color: oklch(from red l c h)) {
    :root {
        --navy-ink: oklch(from var(--brand-primary) min(l, 0.45) c h);
        --red-ink: oklch(from var(--brand-accent) min(l, 0.45) c h);

        /* Sens INVERSE des deux lignes ci-dessus, et c'est normal : min() plafonne une
           encre destinée aux fonds CLAIRS, max() plancherise celle destinée au chrome
           SOMBRE. Ne pas « harmoniser » les deux : ce sont deux rôles opposés qui
           cohabitent dans ce bloc parce qu'ils partagent la même condition de moteur. */
        --red-ink-on-dark: oklch(from var(--brand-accent) max(l, 0.78) c h);
    }
}

/* ---------- Mode clair (template actuel) ---------- */
.theme-light {
    --bg: #F7F4EE;
    --bg-2: #EFEADF;
    --surface: #FFFFFF;
    --ink: #0F1320;
    --ink-70: rgba(15, 19, 32, 0.70);
    --ink-60: rgba(15, 19, 32, 0.55);
    --ink-40: rgba(15, 19, 32, 0.32);
    --line: rgba(15, 19, 32, 0.08);
    --line-strong: rgba(15, 19, 32, 0.18);
    --plate-rim: #EEE7D5;
    --plate-inner: #FAF6EB;
    --page-frame: #DAD3C2;
}

/* ---------- Mode sombre (certains clients) ---------- */
.theme-dark {
    --bg: #101418;
    --bg-2: #181D23;
    --surface: #1E242B;
    --ink: #F2F0EB;
    --ink-70: rgba(242, 240, 235, 0.70);
    --ink-60: rgba(242, 240, 235, 0.55);
    --ink-40: rgba(242, 240, 235, 0.32);
    --line: rgba(242, 240, 235, 0.08);
    --line-strong: rgba(242, 240, 235, 0.18);
    --plate-rim: #2A2F36;
    --plate-inner: #232830;
    --page-frame: #0A0D10;

    /* APLATS — restent sombres : ils portent du #fff.
       Blanc sur --navy (72%) = 5,29:1 ✓ · blanc sur --red (brut) = 5,73:1 ✓
       NE PAS les éclaircir : c'est ce qui casserait .pill--navy / .btn--primary. */
    --navy: color-mix(in oklch, var(--brand-primary) 72%, white);

    /* VERT TABLEUR — SEULE exception à la règle du dessus, motivée en clair dans :root :
       la plaque sombre se noyait dans --surface (2,20:1 < 3:1). Éclaircie à L=0,70, elle
       s'en sépare à 6,11:1 et de --bg sombre à 7,22:1 ; l'encre passe donc au sombre, à
       7,02:1 sur la plaque. Les trois mesures sont AA (bin/contrast/oklch.php, 2026-08-20). */
    --excel: oklch(0.70 0.10 156);            /* #67B184 */
    --excel-hover: oklch(0.76 0.10 156);      /* éclaircit au survol, comme en clair il fonce */
    --excel-ink: oklch(0.20 0.04 156);        /* #041B0E */

    /* TEINTES de fond, recomposées sur la surface sombre (inchangées). */
    --navy-soft: color-mix(in oklch, var(--brand-primary) 25%, #1E242B);
    --red-soft: color-mix(in oklch, var(--brand-accent) 25%, #1E242B);

    /* ENCRES — repli linéaire pour les moteurs sans syntaxe de couleur relative.
       40% de marque + blanc : Servair primary → 6,31:1 sur --navy-soft, 6,77:1 sur
       --surface ; accent → 6,77:1 sur --red-soft, 8,49:1 sur --surface (re-dérivé le 2026-08-18 ;
       les valeurs précédentes 6,82 / 8,51 étaient périmées). Tient même
       pour une marque quasi noire (L=0 → 4,66:1), donc sûr pour tout couple client. */
    --navy-ink: color-mix(in oklch, var(--brand-primary) 40%, white);
    --red-ink: color-mix(in oklch, var(--brand-accent) 40%, white);

    /* ---- VERT sémantique en sombre ----
       L'APLAT --green ne bouge PAS (il porte du #fff : 4,61:1). Seules la teinte et
       l'encre sont recomposées.

       ⚠️ Pourquoi une valeur directe et non un color-mix(… 25%, #1E242B) comme
       --navy-soft / --red-soft : l'interpolation OKLCH est POLAIRE. Mélanger le vert
       (teinte 145°) avec la surface sombre — quasi neutre mais de teinte 252° et de
       chroma 0,016 — interpole les DEUX teintes et sort à ~225° : le « vert doux »
       deviendrait bleu-gris. L'artefact ne se voit pas sur --navy (264°, voisin de 252°),
       il se voit sur le vert. --green étant un token fixe, la valeur directe est licite
       et préserve la teinte 145. */
    --green-soft: oklch(0.30 0.05 145);

    /* ENCRE sombre — L=0,70, chroma et teinte de l'aplat conservées.
       6,13:1 sur --surface · 5,25:1 sur --green-soft · 6,65:1 sur --bg-2 ·
       7,25:1 sur --bg. (--green employé nu en encre y était à 3,38:1.) */
    --green-ink: oklch(0.70 0.12 145);
}

/* PLANCHER DE CLARTÉ (mode sombre) — la vraie correction white-label.
   max(l, 0.78) NE TOUCHE PAS une marque déjà claire (elle garde sa teinte exacte, donc
   aucun délavage) et remonte les marques sombres juste ce qu'il faut. Le plancher est
   calé sur le pire cas de gamut : --red (hue 17°, C=0.215) sort du sRGB au-dessus de
   L≈0.60 et se fait mapper ; à 0.78 il retombe à ≥4,7:1 sur --red-soft (à 0.72 il n'était
   qu'à 4,17:1 — c'est pourquoi 0.72 ne suffisait pas). --navy y est à 7,09:1.
   Chroma et teinte sont préservées : la couleur reste la marque du client. */
@supports (color: oklch(from red l c h)) {
    .theme-dark {
        --navy-ink: oklch(from var(--brand-primary) max(l, 0.78) c h);
        --red-ink: oklch(from var(--brand-accent) max(l, 0.78) c h);
    }
}

/* ---------- Base ---------- */
* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    background: var(--page-frame);
    color: var(--ink);
    font-family: var(--ui);
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
button { font-family: inherit; }

/* Conteneur d'écran (largeur de maquette, centré dans le cadre) */
.screen {
    background: var(--bg);
    color: var(--ink);
    max-width: 1280px;
    margin: 0 auto;
    min-height: 100vh;
}

/* Utilitaires typographiques */
.display { font-family: var(--display); }
.overline {
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--ink-60);
}
