/*
 * Design tokens — source unique des variables du site.
 * Chargé AVANT global.css dans src/components/Head.astro, donc disponible
 * partout (global.css + styles scoped des composants).
 *
 * Règle : ne jamais hardcoder une couleur dans un composant ou global.css —
 * toujours passer par un token var(--color-…). Cf. CLAUDE.md.
 */
:root {
  /* ── Sombre principal ("Black") ──
     Themable depuis la navbar via le mode (cf. plus bas). Stocké en
     composantes RGB pour que les variantes d'opacité suivent le thème. */
  --color-dark-rgb: 38, 37, 36; /* #262524 — mode par défaut */
  --color-dark: rgb(var(--color-dark-rgb));
  --color-dark-80: rgba(var(--color-dark-rgb), 0.8);
  --color-dark-60: rgba(var(--color-dark-rgb), 0.6);
  --color-dark-50: rgba(var(--color-dark-rgb), 0.5);
  --color-dark-40: rgba(var(--color-dark-rgb), 0.4);
  --color-dark-30: rgba(var(--color-dark-rgb), 0.3);
  --color-dark-20: rgba(var(--color-dark-rgb), 0.2);
  --color-dark-15: rgba(var(--color-dark-rgb), 0.15);
  --color-dark-10: rgba(var(--color-dark-rgb), 0.1);
  --color-dark-5: rgba(var(--color-dark-rgb), 0.05);

  /* ── Blanc (texte / éléments sur fond sombre) — fixe quel que soit le mode ── */
  --color-white-rgb: 255, 255, 255; /* composantes pour dérivés d'opacité custom */
  --color-white: #ffffff;
  /* 80 % : texte de paragraphe POSÉ SUR UNE IMAGE (description de la carte
     centrale de ZoomCarrousel). Ne fait pas partie de l'échelle d'encre des
     blocs — celle-ci s'arrête à 60 % (cf. --block-ink-soft dans BlockShell) : un
     texte sur média demande plus de densité qu'un texte secondaire sur fond
     plein. */
  --color-white-80: rgba(255, 255, 255, 0.8);
  --color-white-60: rgba(255, 255, 255, 0.6);
  --color-white-40: rgba(255, 255, 255, 0.4);
  --color-white-30: rgba(255, 255, 255, 0.3);
  --color-white-20: rgba(255, 255, 255, 0.2);
  --color-white-10: rgba(255, 255, 255, 0.1);
  /* 8 % : le guillemet de la citation compacte, posé sur le panneau sombre de
     sa carte (token White/8 de la maquette). Ne fait pas partie de l'échelle
     d'encre des blocs — c'est un ornement, pas un texte. */
  --color-white-8: rgba(255, 255, 255, 0.08);
  --color-white-6: rgba(255, 255, 255, 0.06);
  --color-white-4: rgba(255, 255, 255, 0.04);

  /* ── Fonds clairs ── */
  --color-creme: #f8f5f1;
  /* ── Pastilles de type de contrat (liste des offres, page carrières) ──
     Trois teintes pleines, hors échelle d'encre : elles CLASSENT, elles ne
     hiérarchisent pas. Elles ne suivent donc pas le thème sombre de la navbar —
     un CDI doit rester reconnaissable à sa couleur quel que soit le fond, comme
     un code couleur de tableau.

     ⚠️ La maquette ne prévoit que trois types alors que les offres réelles en
     comptent QUATRE : Alternance n'a pas de teinte. Elle reprend celle du stage,
     l'un et l'autre étant des contrats d'un temps — à trancher avec le designer. */
  --color-chip-cdi: #d8c7ff;
  --color-chip-stage: #e3e3e3;
  --color-chip-freelance: #dbffc7;

  /* ── Vert d'accent (puces des points clés de la cover de landing) ──
     Couleur fixe : c'est un accent, il ne suit pas le thème sombre de la
     navbar (il doit rester lisible sur les trois variantes de fond).
     C'est le token « Success » de Figma — la même valeur que le vert du CTA de
     la navbar ci-dessous, mais les deux gardent leur nom : ils ne désignent pas
     la même chose et le designer peut les séparer à nouveau. */
  --color-green: #528361;

  /* ── Vert du bouton principal du hero de la home (maquette refonte) ──
     Distinct de --color-green, qui est le vert VIF des puces : celui-ci est un
     vert sourd, tenu pour porter du texte blanc. Couleur d'accent fixe elle
     aussi — elle ne suit pas le thème sombre de la navbar, et reste lisible sur
     les trois variantes de fond.
     C'est le DÉFAUT du bouton : le CMS peut lui substituer n'importe quelle
     couleur (cf. le champ « Bouton 1 — fond » de la slice hero_home). */
  --color-green-cta: #3d754e;

  /* ── Vert du CTA de la navbar (« Discutons de votre projet ») ──
     Même famille et même règle que --color-green-cta ci-dessus, mais PAS la
     même valeur : les deux maquettes ne donnent pas le même vert (#3d754e pour
     le hero, #528361 pour la barre). Les deux tokens existent donc séparément
     tant que le designer n'a pas tranché — si c'est un seul et même vert, il
     n'y a qu'à faire pointer celui-ci sur l'autre. */
  --color-green-nav: #528361;
  /* ⚠️ PLUS CONSOMMÉ : le survol des CTA est désormais une règle unique du site
     (cf. `.ctaSweep` dans global.css) — le fond se déplie vers le libellé, donc
     ce vert-ci s'ÉCLAIRCIT au lieu de foncer. La valeur de la maquette est
     gardée ici parce que c'est la seule fois où le designer a dit ce que devait
     faire le survol d'un vert : y revenir tient en un `--cta-sweep:` sur la
     règle du bouton de la barre — mais le CTA du hero, dont le CMS peut changer
     le fond, ne pourrait pas suivre. */
  --color-green-nav-hover: #446e51;

  /* ── Tags catégorie blog (pastels fixes, texte sombre) ── */
  --color-tag-blue: #c7f6ff;
  --color-tag-violet: #d8c7ff;
  --color-tag-green: #dbffc7;
  --color-tag-grey: #e3e3e3;

  /* ── Bouton foncé (refonte) ── */
  --color-button-bg: var(--color-dark);
  --color-button-bg-hover: #0d0c0b;

  /* ── Espacements (déplacés depuis global.css pour centraliser les tokens) ── */
  --block-vertical-spacing: 100px;
  --block-vertical-spacing-mobile: 50px;
  --block-vertical-spacing-below-header: 60px;
  --spacing-between-title-and-content: 40px;
  --spacing-between-title-and-subtitle: 10px;
  --horizontal-padding: 20px;

  /* ── Grille à pointillés latéraux ──
     Convention commune à TOUS les composants qui affichent le motif de
     pointillés (FAQ, Hero, KPIs, HaveAProject, SavoirFaire, …) pour que les
     bordures soient parfaitement alignées d'un bloc à l'autre.
     L'élément bordé = conteneur centré `max-width: var(--grid-max-width)` +
     `padding: … var(--grid-content-padding)` + bordure, dans un parent qui a
     `padding: 0 var(--grid-gutter)` (gouttière mini). NE PAS changer ces
     valeurs sans répercuter sur l'ensemble — l'alignement en dépend. */
  --grid-max-width: 1208px; /* largeur de contenu du cadre */
  --grid-gutter: 84px; /* gouttière latérale mini (= px-84 maquette) */
  --grid-gutter-mobile: 16px; /* gouttière sous --grid-stack-breakpoint */
  /* Retrait contenu ↔ bordure pointillée. 31 et non 32 comme dans Figma : le
     trait y est centré sur la limite du cadre, alors qu'en CSS il tombe à
     l'intérieur. Le cadre mesure ainsi 1 + 31 + 1208 + 31 + 1 = 1272 px, soit
     exactement la cote de la maquette (donc 84 px de gouttière à 1440). C'est
     le retrait, et non --grid-max-width, qui absorbe l'écart : la largeur de
     contenu vaut 1208, dont dépendent les cotes internes des blocs (la rangée
     du carrousel de témoignages, entre autres) — quand elle change, ces cotes
     changent avec elle, elles sont annotées « = --grid-max-width ». */
  --grid-content-padding: 31px;
  /* Largeur HORS TOUT du cadre pointillé (contenu + retraits + bordures), donc
     la seule cote que la maquette donne directement : 1272 px à 1440. Écrite
     ici plutôt que recopiée dans les quatre composants qui posent un cadre. */
  --grid-frame-width: calc(
    var(--grid-max-width) + 2 * var(--grid-content-padding) + 2px
  );
  /* Abscisse des pointillés VERTICAUX du cadre, mesurée depuis le bord de la
     page : la gouttière, ou la moitié du vide restant une fois le cadre arrivé
     à sa largeur maximale. C'est la formule du cadre lui-même (largeur 100 %
     plafonnée à --grid-frame-width, centrée dans la gouttière) réécrite en une
     passe, pour tout ce qui doit se poser SUR ces pointillés sans pouvoir
     s'accrocher au cadre : les croix des joints entre blocs (BlockShell) comme
     celles de la barre de navigation (Navbar), qui sont des pseudo-éléments
     absolus et ne peuvent pas hériter de la bordure d'un frère.

     ⚠️ Le `100 %` se résout là où le token est UTILISÉ, pas ici : réservé aux
     propriétés dont le pourcentage se mesure sur la LARGEUR du bloc conteneur
     (`left`, `right`), et sur un conteneur dont la boîte de padding va d'un
     bord de la page à l'autre — `.blockShell`, `#menu` et `.megaPanel` la font
     tous les trois. */
  /* Abscisse des verticales du cadre : la gouttière quand la fenêtre est
     étroite, la moitié de ce qui déborde du cadre au-delà. C'est la SEULE cote
     qui pose la grille — les cadres s'y calent par leur marge, aucun ne se
     centre plus par `auto`.

     ⚠️ ARRONDIE À UN MULTIPLE DE 4 px, et c'est le cœur de l'affaire. Un filet
     fait 1 px CSS ; sur un écran à 125 % il en vaut 1,25 physique, et le
     navigateur doit choisir un pixel entier. Tant que la cote est fractionnaire
     — (viewport - 1272) / 2 tombe sur un demi-pixel une fois sur deux — ce choix
     dépend du CHEMIN DE RENDU : une bordure, un `border-image` et un fond ne
     tranchent pas une égalité du même côté. Deux filets écrits à la même cote
     atterrissaient alors sur deux colonnes différentes, et une croix se posait
     à côté du pointillé qu'elle doit croiser.

     4 px et pas 1 : c'est le pas qui retombe sur un nombre ENTIER de pixels
     physiques à toutes les densités que Windows et macOS proposent (100, 125,
     150, 175, 200 %… — toutes multiples de 0,25). À cette cote-là il n'y a plus
     rien à arrondir, donc plus rien sur quoi les chemins de rendu puissent
     diverger. Le cadre peut se retrouver jusqu'à 2 px hors du centre : sur des
     gouttières de 300 px, invisible.

     La première déclaration est le repli : `round()` demande Chrome 112,
     Safari 15.4 ou Firefox 118. Sans elle, un moteur plus ancien perdrait la
     custom property entière, et toute la grille avec. */
  --grid-frame-x: max(
    var(--grid-gutter),
    (100% - var(--grid-frame-width)) / 2
  );
  --grid-frame-x: max(
    var(--grid-gutter),
    round((100% - var(--grid-frame-width)) / 2, 4px)
  );
  /* Retrait du cadre DANS un conteneur qui porte déjà la gouttière (le bloc,
     le pied de page). Même cote que --grid-frame-x, mais mesurée depuis le
     bord du CONTENU et non depuis celui de l'écran : le plancher de gouttière
     y est déjà payé par le padding du conteneur, l'ajouter une seconde fois
     rentrerait le cadre de deux gouttières. Les deux tokens tombent au même
     X — 84 étant un multiple de 4, l'arrondi passe à travers l'addition. */
  --grid-frame-inset: max(
    0px,
    round((100% - var(--grid-frame-width)) / 2, 4px)
  );
  /* Bord DROIT du cadre, déduit du bord gauche et de la largeur RÉELLE —
     jamais mesuré depuis la droite du conteneur. Celle-ci n'est pas toujours
     entière (une barre de défilement peut valoir 15,2 px), et un `right:` posé
     dessus retombait à une fraction de pixel du filet, que le cadre, lui, pose
     depuis la gauche. Toute la grille part donc du MÊME bord.
     Le `min()` n'est pas décoratif : sous 1440 le cadre n'atteint plus sa
     largeur nominale, c'est la gouttière qui le bride. Sans lui, la croix de
     droite partait à 180 px hors de l'écran. */
  --grid-frame-right: calc(
    var(--grid-frame-x) + min(
        var(--grid-frame-width),
        100% - 2 * var(--grid-frame-x)
      )
  );
  --grid-stack-breakpoint: 1130px; /* Seuil unique de bascule desktop → mobile*/
  /* Seuil de repli des GRILLES de cartes vers une seule colonne. Il est distinct
     de --grid-stack-breakpoint, et pour la même raison que celui de la navbar
     (1220) l'est : ce sont deux questions différentes. « À partir de quand le
     cadre perd sa gouttière » se décide sur la largeur du cadre ; « à partir de
     quand une rangée de cartes ne tient plus » se décide sur la largeur d'UNE
     carte. Entre les deux valeurs — la bande des tablettes, iPad portrait comme
     paysage — la page n'a plus son cadre mais ses grilles gardent 2 colonnes.
     Sans ce palier, une carte de 530px passait d'un coup à 1080 de large pour
     68px de fenêtre en moins : la mise en page du téléphone, simplement étirée.
     700 et non 768 : à 736px de contenu, deux colonnes font encore 355px, soit
     la cote d'une carte de la maquette mobile. En dessous, elles s'empilent. */
  --grid-cards-breakpoint: 700px;

  /* ── Motif du pointillé ──
     La maquette pose un tiret de 2 suivi d'un vide de 2, soit une période de 4.
     Le mot-clé CSS `dashed` ne permet PAS de choisir ce motif : chaque moteur
     applique le sien (Chrome rend ici 4 pleins / 1 vide, donc un filet qui
     paraît presque continu). D'où un dégradé répété, posé en `border-image` :
     l'élément garde sa bordure de 1px — donc son modèle de boîte, dont dépendent
     toutes les marges négatives des joints — et seule la PEINTURE change.
     Deux variantes parce qu'un `border-image` n'a qu'une source pour ses quatre
     côtés : `-h` pour une bordure horizontale (haut/bas), `-v` pour une verticale
     (gauche/droite). Un cadre pointillé sur ses QUATRE côtés ne peut donc pas
     s'écrire ainsi — cf. la recette de fond à quatre couches, juste en dessous.
     Usage : `border-top: 1px solid transparent; border-image: var(--dash-h) 1;`

     ⚠️ La COULEUR ne se passe PAS par une variable depuis l'élément qui pose le
     filet. Une custom property est substituée là où elle est DÉCLARÉE, pas là
     où elle est utilisée : un `--dash-c` posé sur `.blockFrame` arrive trop
     tard, le dégradé de `:root` a déjà figé sa couleur — et le filet sortait
     peint en `currentColor`, c'est-à-dire dans l'encre du texte à 100 % au lieu
     de --block-line à 30 %. D'où une paire de dégradés par CONTEXTE de couleur,
     chacun déclaré avec la sienne : celle-ci pour ce qui vit HORS d'un bloc,
     `--block-line` sur `.blockShell`, `--seam-ink` pour le joint, blanc 30 %
     dans le pied de page. Seules les deux cotes du motif restent écrites ici.

     La période est DIVISÉE par la mise à l'échelle : c'est une texture, pas une
     cote de maquette. Sans ça, à --page-scale 1,113 la période de 4 tombe sur
     4,45 px d'écran, et le filet alterne des tirets de 2 et de 3 px. Divisée,
     elle retombe sur 4 px pleins à toutes les échelles — comme le 1px de la
     bordure elle-même, que le zoom n'épaissit pas davantage. */
  --dash-on: calc(2px / var(--page-scale));
  --dash-period: calc(4px / var(--page-scale));
  --dash-h: repeating-linear-gradient(
    to right,
    currentColor 0 var(--dash-on),
    transparent var(--dash-on) var(--dash-period)
  );
  --dash-v: repeating-linear-gradient(
    to bottom,
    currentColor 0 var(--dash-on),
    transparent var(--dash-on) var(--dash-period)
  );
  /* Cadre pointillé sur les QUATRE côtés : `border-image` ne convient pas (une
     seule source pour quatre bords, donc les verticales sortiraient pleines ou
     vides). On garde une bordure transparente de 1px pour le modèle de boîte et
     on peint les quatre filets en fond, calés sur la boîte de bordure :

       border: 1px solid transparent;
       background-image: var(--dash-h), var(--dash-h), var(--dash-v), var(--dash-v);
       background-size: 100% 1px, 100% 1px, 1px 100%, 1px 100%;
       background-position: 0 0, 0 100%, 0 0, 100% 0;
       background-repeat: no-repeat;
       background-origin: border-box;
       background-clip: border-box;

     Réservé aux éléments SANS fond propre (bouton de FAQ, carte de carrousel,
     rangée légale du footer, carreaux de pièces jointes) : un fond de couleur
     occuperait les mêmes couches. */

  /* Facteur de mise à l'échelle du site (cf. Head.astro, qui surcharge cette
     valeur en style inline sur <html>, et pageScale.ts pour les cotes de
     référence). Déclaré ici pour que les expressions qui le compensent —
     `calc(100dvh / var(--page-scale))` dans 404.astro et .megaScrim — restent
     valides quand le script n'a pas tourné : JS coupé, ou erreur en amont. */
  --page-scale: 1;

  /* ── Arrivée d'une ancre ──
     Ce que `scroll-margin-top` doit dégager sur la CIBLE d'un lien vers une
     section de la même page. Un seul token : la valeur vivait recopiée dans six
     fichiers (100, 105, 110, 110, 120…), qui avaient tous dérivé.

     Elle dépend d'une seule chose : la barre est-elle encore là quand on arrive ?
     Sous 1221px elle reste posée en haut (76 de haut, bordure comprise, sans
     ombre) et il faut la dégager ; au-dessus elle se rétracte au défilement
     (`#menu.navHidden` en `translateY(-100%)`, cf. Navbar.astro, même seuil) et
     il ne reste plus rien à dégager.

     AUCUN air en plus, et c'est un parti pris : le bloc visé porte déjà 88px de
     retrait interne, donc la respiration est là. Les 24px qu'on ajoutait par
     dessus ne se lisaient pas comme de l'air mais comme une lichette du bloc
     précédent restée à l'écran — un défaut, pas une intention. La section arrive
     donc pile sous la barre, ou pile en haut de l'écran quand elle est partie. */
  --anchor-offset: 76px; /* la hauteur de la barre, exactement */
}

@media (min-width: 1221px) {
  :root {
    --anchor-offset: 0px; /* la barre est partie : rien à dégager */
  }
}

/*
 * Couleurs sombres sélectionnables depuis la navbar (sélecteur de couleur).
 * Chaque thème surcharge --color-dark-rgb → tout ce qui est « sombre » suit.
 * Appliqué via data-theme sur <html> + persisté en localStorage (cf. Navbar.astro
 * pour le sélecteur, et Head.astro pour l'application sans flash au chargement).
 * Le défaut (aucun data-theme) = Stone #262524, défini sur :root plus haut.
 */
:root[data-theme="violet"] {
  --color-dark-rgb: 44, 17, 53; /* #2c1135 */
}
:root[data-theme="rouge"] {
  --color-dark-rgb: 53, 17, 17; /* #351111 */
}
/* `vert` est l'ancienne clé de ce thème, devenu « Cyan ». Elle reste ici
   parce que le choix de couleur est mémorisé dans localStorage
   (galadrim-theme, cf. Head.astro) : la retirer aurait renvoyé en silence au
   thème par défaut tous ceux qui avaient choisi le vert. */
:root[data-theme="cyan"],
:root[data-theme="vert"] {
  --color-dark-rgb: 17, 53, 51; /* #113533 */
}
:root[data-theme="or"] {
  --color-dark-rgb: 53, 44, 17; /* #352c11 */
}
:root[data-theme="marine"] {
  --color-dark-rgb: 17, 19, 53; /* #111335 */
}

/*
 * Quart de tour des croix d'intersection.
 * Déclaré ici, en global, parce que DEUX écritures dessinent la même croix et
 * doivent tourner pareil : le joint entre deux blocs (BlockShell.astro) et le
 * filet bas de la barre de navigation (Navbar.astro). Un @keyframes posé dans
 * l'un des deux <style> serait renommé par la portée d'Astro et invisible de
 * l'autre — d'où les croix du header restées immobiles.
 *
 * Le mouvement tient dans les 300 dernières millisecondes du cycle, le reste
 * tenant la pose : 90° ramènent une croix sur elle-même, le retour à 0 ne se
 * voit donc pas. Les CONSOMMATEURS fixent la période (10s) et le décalage de
 * phase ; ici on ne décrit que le geste.
 *
 * ⚠️ Ces 300 ms sont un PLAFOND autant qu'un réglage : les phases des croix
 * sont espacées de 0,7 s, et celles de la navbar tombent à mi-chemin, donc à
 * 0,35 s de leurs voisines. Un geste plus long ferait tourner deux croix
 * ensemble — ce que la mise en scène cherche précisément à éviter.
 *
 * ⚠️ C'est `rotate` qui est animé, donc une propriété que le COMPOSITEUR prend
 * en charge — et il faut que ça le reste. L'angle est passé un temps par une
 * propriété perso enregistrée, pour esquiver la couche composée qu'une
 * animation de transformation crée pour toute sa durée de vie : cette couche
 * cale son origine sur un pixel ÉCRAN entier, et les croix de DROITE, dont la
 * cote tombait alors sur une fraction, glissaient d'un quart de pixel hors du
 * pointillé. Le détour coûtait cher : une propriété perso n'étant PAS
 * compositable, ses vingt-quatre animations forçaient un recalcul de style à
 * CHAQUE IMAGE, en permanence — 583 ms de style sur 4 s mesurées contre 13 ms
 * sans, près du quart du fil principal pour une décoration. Elles repeignaient
 * du même coup en continu derrière le voile du méga-menu, dont le flou
 * d'arrière-plan doit alors se recalculer à chaque image.
 * Le détour est devenu inutile le jour où --grid-frame-x a été arrondie au
 * pixel entier : sans fraction à perdre, la couche ne décale plus rien.
 * Vérifié aux densités 1, 1,25, 1,5 et 2. Si la grille redevenait
 * fractionnaire, le défaut reviendrait — et c'est la grille qu'il faudrait
 * recaler, pas l'animation.
 */
@keyframes croixTourne {
  0%,
  97% {
    rotate: 0deg;
    animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  }
  100% {
    rotate: 90deg;
  }
}
