/* =============================================================
   LE SOCLE — tokens.css
   Partagé par TOUS les thèmes. Contient :
     A. LES BORNES (garde-fous WCAG) — valeurs nommées
     B. LES INVARIANTS DE MÉCANISME (plans, fusion média, hauteur de page)
     C. LES SEEDS CLIENT (brand L/C/H, radius, space-unit)
     D. LA DÉRIVATION (clamp + oklch) de tous les rôles couleur

   RÈGLE : la COULEUR n'existe QUE dans ce fichier. C'est ce qui garde
   `test/contrast.test.js` — une seule matrice, 0,30→0,85 × 0→0,30 × 360
   teintes — valable pour tous les thèmes à la fois. Un thème décide
   COMBIEN de couleur il emploie, jamais laquelle.

   L'ÉCHELLE est partie le 17 août 2026 : type, rythme, formes, mouvement
   et proportions vivent dans `themes/<nom>/echelle.css`. Elles étaient
   décrites ici comme « identiques sur tout le parc » — vrai tant qu'il
   n'existait qu'un thème, et c'est précisément ce qui a changé.

   Un thème n'a le droit de redéclarer AUCUN token de ce fichier :
   `test/themes.test.js` le vérifie par intersection d'ensembles.
   ============================================================= */

/* ---- ORDRE DES COUCHES (déclaré ici, appliqué partout) ------
   tokens.css est la première feuille chargée après les polices : c'est
   donc ici que l'ordre des couches se fixe, une seule fois.

   base        réinitialisation et éléments nus
   rythme      wrap / measure / stack / section — la grammaire de densité
   composants  les six blocs qui portent un mécanisme
   roles       la liste fermée des rôles de couleur
   utilities   Tailwind — généré, toujours prioritaire

   Les utilitaires sont EN DERNIER par construction : une classe posée
   dans le markup doit pouvoir corriger un composant sans !important et
   sans dépendre de l'ordre des fichiers. */
@layer base, rythme, composants, roles, utilities;

:root{

  /* ---- A. BORNES / GARDE-FOUS ------------------------------
     Un garde-fou ne borne QUE des valeurs pilotées par le client. Une borne
     posée sur un littéral est décorative : elle ne peut jamais mordre.

     Toutes les valeurs ci-dessous sont MESURÉES par test/contrast.test.js en
     luminance sRGB réelle après clip naïf, au pire chroma ET à la pire teinte.
     Ne pas les relâcher : la marge est nulle par construction. */
  --guard-neutral-c-max: 0.020;  /* chroma des neutres → pas de teinte violente        */
  --guard-brand-c-max:   0.19;   /* chroma des aplats larges (marque criarde)          */
  --guard-fill-l-max:    0.50;   /* aplat SOMBRE : au-delà, le texte clair passe sous AA */
  --guard-fill-l-min:    0.61;   /* aplat CLAIR  : en deçà, l'encre passe sous AA        */
  --guard-strong-l-max:  0.46;   /* --brand-strong est du TEXTE → 4.5:1 sur relevé      */
  --guard-line-l-max:    0.52;   /* --brand-line porte du SENS → 3:1 sur relevé         */
  --guard-focus-l-max:   0.52;   /* anneau de focus → 3:1 sur relevé (SC 1.4.11)        */
  --guard-focus-c-min:   0.10;   /* anneau garde du chroma même si marque quasi-grise   */

  /* Constantes de thème — ne dépendent d'aucune graine cliente, donc pas
     des garde-fous. Les nommer ici les rend lisibles sans les déguiser. */
  --ink-l:     0.16;
  --muted-l:   0.47;
  --surface-l: 0.965;
  --raised-l:  0.925;
  --line-l:    0.88;

  /* ---- B. INVARIANTS DE MÉCANISME --------------------------
     Ce qui n'est ni une borne, ni une graine, ni du goût : les valeurs
     dont un composant a besoin pour FONCTIONNER. Un thème n'y touche pas,
     parce qu'il n'y a rien à y décider — l'ordre d'empilement d'un lien
     d'évitement n'est pas une direction artistique.

     L'ÉCHELLE, elle, est partie : type, rythme, formes, mouvement,
     proportions vivent dans `themes/<nom>/echelle.css` depuis le
     17 août 2026. Elles étaient annoncées ici « identiques sur tout le
     parc » — ce qui a cessé d'être vrai avec le second thème. */

  /* Plans : un seul niveau collant — enseigne et en-tête ne font qu'une barre.
     Le lien d'évitement passe au-dessus de tout : s'il se glissait sous la
     barre collante, il aurait le focus sans être visible — ce qui est pire
     que pas de lien du tout. */
  /* La barre d'action ancrée en bas passe au-dessus du contenu qui défile
     dessous, mais SOUS la barre du haut — donc sous le panneau de menu, qui
     est son enfant et descend jusqu'au bas de l'écran sur un petit téléphone.
     Le panneau porte déjà son propre bouton : le recouvrir par un second
     doublerait l'action et masquerait un libellé de route. */
  --z-cta:      10;
  --z-barre:    20;
  --z-skip:     40;

  /* Position hors écran du lien d'évitement au repos. En pourcentage de sa
     propre hauteur : il remonte quelle que soit sa taille de police. */
  --skip-hidden-y: -200%;

  /* Média « objet » — visuel détouré sur fond blanc (logo, tampon, packshot).
     multiply est exact pour ce cas et pour lui seul : blanc × surface =
     surface, donc le fond du fichier disparaît sans détourage ni JS. La
     contrepartie est qu'il exige une surface CLAIRE — sur un aplat de
     marque, il éteindrait l'encre du visuel. La règle correspondante pose
     donc --surface-raised, et test/media.test.js mesure cette condition. */
  --blend-objet: multiply;

  /* Hauteur minimale de page : dvh et non vh, pour que la barre d'adresse
     mobile ne laisse pas un vide sous le pied quand elle se rétracte. */
  --page-min-h: 100dvh;

  /* ---- C. SEEDS CLIENT (peau par défaut : « Framboise ») ----
     Les valeurs qu'une peau écrase, et elles seules. Une peau ne contient
     QUE des graines : toute couleur calculée qui y serait figée deviendrait
     une seconde source de vérité, et c'est déjà arrivé.

     `--radius` et `--space-unit` sont ici, ce qui en DÉRIVE est dans
     l'échelle du thème : le client choisit l'unité, le thème les rapports. */
  --brand-l: 0.52;   /* lightness OKLCH de la couleur de marque */
  --brand-c: 0.16;   /* chroma  */
  --brand-h: 8;      /* hue     */
  --radius: 0.6rem;
  --space-unit: 0.5rem;

  /* ---- D. DÉRIVATION (clamp + oklch, bornée par A) ---------- */

  /* Le chroma de marque, plafonné une fois pour toutes les grandes zones. */
  --brand-c-safe: min(var(--brand-c), var(--guard-brand-c-max));

  /* ---- Trois rôles, trois plafonds ------------------------
     La marque brute échoue jusqu'aux 3:1 d'un filet dès qu'elle est claire
     (1,14:1 à L 0,85 C 0,30 H 200). Elle ne peut donc porter aucun sens :
     chaque usage porteur a son token plafonné à ce que son rôle exige. */

  /* 1. BRUTE — logo et décor seuls. Aucun texte, aucun filet porteur de sens.
        Suit la graine sans plafond : c'est la couleur que le client a donnée. */
  --brand: oklch(var(--brand-l) var(--brand-c) var(--brand-h));

  /* 2. TEXTE — liens, .eyebrow, .door__tag, .step__n, « + » de la FAQ. 4,5:1. */
  --brand-strong: oklch(
      min(calc(var(--brand-l) - 0.10), var(--guard-strong-l-max))
      var(--brand-c-safe)
      var(--brand-h));

  /* 3. FILET PORTEUR DE SENS — bordure de la porte recommandée, filet haut de
        .point, filet gauche de .feature__lock. 3:1 (WCAG 2.2 SC 1.4.11).
        À ne pas confondre avec --line, qui est le filet neutre décoratif. */
  --brand-line: oklch(
      min(var(--brand-l), var(--guard-line-l-max))
      var(--brand-c)
      var(--brand-h));

  /* ---- Aplats larges : deux variantes posées AU BUILD -------
     La règle « pousser hors de la bande morte » est une CONDITION, que le CSS
     ne sait pas exprimer. Elle est donc figée dans le HTML à la génération :
     :root            → aplat SOMBRE, texte clair  (défaut)
     [data-fill=clair] → aplat CLAIR,  encre foncée (voir bas de fichier)

     Chaque variante est sûre sur TOUT le domaine de graines, pas seulement
     sur celui où le build la choisit. Conséquence : le test n'a aucune règle
     de bascule à recopier — il balaye les deux variantes sur tout le domaine.
     La bascule au build ne choisit donc pas la lisibilité, seulement laquelle
     des deux ressemble le plus au logo du client. */
  --fill-l: min(var(--brand-l), var(--guard-fill-l-max));
  --fill-hover-l: calc(var(--fill-l) - 0.07);
  --brand-fill:       oklch(var(--fill-l)       var(--brand-c-safe) var(--brand-h));
  --brand-fill-hover: oklch(var(--fill-hover-l) var(--brand-c-safe) var(--brand-h));
  --on-fill-light: oklch(0.98 min(var(--brand-c), var(--guard-neutral-c-max)) var(--brand-h));
  --brand-contrast: var(--on-fill-light);

  /* Encre : teintée par la marque, chroma plafonné. */
  --ink: oklch(
      var(--ink-l)
      min(var(--brand-c), var(--guard-neutral-c-max))
      var(--brand-h));
  --ink-muted: oklch(
      var(--muted-l)
      min(calc(var(--brand-c) * 0.7), var(--guard-neutral-c-max))
      var(--brand-h));

  /* Surfaces : near-white teinté marque à très bas chroma. */
  --surface: oklch(
      var(--surface-l)
      min(calc(var(--brand-c) * 0.15), var(--guard-neutral-c-max))
      var(--brand-h));
  --surface-raised: oklch(
      var(--raised-l)
      min(calc(var(--brand-c) * 0.28), var(--guard-neutral-c-max))
      var(--brand-h));
  --line: oklch(var(--line-l) min(calc(var(--brand-c) * 0.3), var(--guard-neutral-c-max)) var(--brand-h));

  /* Focus : chroma minimal garanti, luminance plafonnée → toujours visible. */
  --focus: oklch(
      min(var(--brand-l), var(--guard-focus-l-max))
      max(var(--brand-c), var(--guard-focus-c-min))
      var(--brand-h));

  /* Sémantique — hors marque, pour rester lisible même si la marque est verte/rouge.
     Statut de l'enseigne : ouvert / fermé / neutre. */
  --positive: oklch(0.55 0.13 150);
  --negative: oklch(0.55 0.17 27);
  --neutral-dot: var(--ink-muted);
}

/* ================= VARIANTE D'APLAT (posée au build) =============
   Écrite une fois sur <html> à la génération, jamais au runtime :
     <html data-fill="clair">  quand la marque est claire (jaune, cyan pâle)
     <html>                    sinon (défaut : aplat sombre)

   Les deux variantes tiennent AA sur tout le domaine de graines. Le build
   ne tranche donc pas la lisibilité, seulement la fidélité au logo.
   Une entrée du tableau de répartition thème/client (étape 7). */
[data-fill="clair"]{
  --fill-l: max(var(--brand-l), var(--guard-fill-l-min));
  --fill-hover-l: calc(var(--fill-l) + 0.06);
  --brand-contrast: var(--ink);
}

/* Les peaux client vivent dans skins/<client>.css — graines seules.
   Aucune couleur calculée n'y est écrite : la double source de vérité
   (valeur figée dans la peau ET recalculée ici) a déjà produit une dérive.

   Le bloc `prefers-reduced-motion` a suivi les durées dans
   `themes/<nom>/echelle.css` : il pose des VALEURS de durée, et un socle
   qui redéclare un token du thème serait exactement la dérive ci-dessus. */
