/* ==========================================================================
   OPTIMIZ BTP — Composants
   Nommage BEM strict. Un composant = UN bloc de règles, jamais deux blocs
   concurrents pour la même classe.
   ========================================================================== */

/* ==========================================================================
   Boutons
   ========================================================================== */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: .01em;
  text-align: center;
  text-decoration: none;
  transition: background var(--motion-fast) var(--ease),
              border-color var(--motion-fast) var(--ease),
              color var(--motion-fast) var(--ease),
              opacity var(--motion-fast) var(--ease);
}

/* Or en aplat, texte noir dessus : 11,4:1 dans les deux thèmes. */
.button--primary {
  background: var(--accent);
  color: var(--accent-ink);
}

.button--primary:hover,
.button--primary:focus-visible {
  background: var(--accent);
  color: var(--accent-ink);
  opacity: .88;
}

.button--ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--fg);
}

.button--ghost:hover,
.button--ghost:focus-visible {
  background: var(--surface-hover);
  border-color: var(--accent-line);
  color: var(--fg);
}

.button--block { width: 100%; }

.button[disabled],
.button[aria-disabled="true"] {
  opacity: .5;
  cursor: not-allowed;
}

/* ==========================================================================
   En-tête de site
   ========================================================================== */

/* ⚠️ Inversé le 2026-08-24. L'en-tête était transparent par défaut et ne
   devenait opaque qu'après défilement : sur une page en thème CLAIR ouvrant
   sur une image sombre (/construction-renovation/, /performance-organisation/),
   les liens de navigation étaient en --muted = noir à 64 % posé sur une photo
   sombre — mesuré 1,4:1, illisible, exactement le défaut signalé.
   Désormais l'en-tête porte TOUJOURS le fond de sa page : la navigation est
   lisible partout, dans les deux thèmes, sans une seule règle par page. */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  transition: background var(--motion-base) var(--ease),
              border-color var(--motion-base) var(--ease);
}

/* Posé par le JS après ~80px de défilement — état de référence, inchangé. */
.site-header[data-scrolled] {
  background: var(--bg);
  border-bottom-color: var(--line);
}

/* SEULE exception : une page qui ouvre sur un vrai .hero, c'est-à-dire un
   média plein cadre AVEC son voile noir et son texte blanc par-dessus
   (aujourd'hui l'accueil, et elle seule depuis que les pages de pôle sont
   passées en .page-media). Là, l'en-tête se fond dans le hero tant qu'on n'a
   pas défilé — le voile lui garantit son contraste.
   ⚠️ Écrit en :not([data-scrolled]) et pas en simple surcharge : la
   spécificité de `body:has(.hero) .site-header` (0-2-1) bat celle de
   `.site-header[data-scrolled]` (0-2-0). Sans le :not, l'en-tête serait resté
   transparent même après défilement — sur l'accueil, donc en pleine page. */
body:has(.hero) .site-header:not([data-scrolled]) {
  background: transparent;
  border-bottom-color: transparent;
}

/* Sans logo ni CTA distinct dans le header (choix du 2026-08-03), la nav est
   le seul contenu à largeur variable : centrer l'inner centre la nav sur
   desktop, et centre le bouton burger seul sur mobile — cohérent dans les
   deux cas puisqu'aucun élément ne reste à contrebalancer. */
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  min-height: var(--header-h);
}

.site-header__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex-shrink: 0;
  text-decoration: none;
}

/* ⚠️ PROVISOIRE — en attente du logo vectoriel.
   Mesuré sur le fichier fourni : 50% de pixels transparents, 44% d'encre
   sombre, 6% de clairs. En thème sombre (le défaut), l'emblème est donc
   quasi invisible. brightness(0) invert(1) le ramène en silhouette blanche :
   net et cohérent avec le registre, au prix du jaune de la grue.
   → À remplacer par une vraie déclinaison monochrome dès réception du SVG. */
.site-header__logo {
  width: auto;
  height: 36px;
  filter: brightness(0) invert(1);
}

/* En thème clair, l'emblème garde ses couleurs d'origine. */
[data-theme="light"] .site-header__logo {
  filter: none;
}

/* Le nom porte l'identité : l'emblème seul ne nomme pas la marque. */
.site-header__wordmark {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--fg);
}

/* ==========================================================================
   Navigation principale (desktop)
   ========================================================================== */

.site-nav {
  display: none;
  align-items: center;
  gap: var(--space-6);
}

/* Gap fluide plutôt qu'un compromis figé : 16px quand la place manque
   (1024px), jusqu'à 24px quand elle existe. Mesuré le 2026-08-03 — avec un
   gap fixe de 24px il ne restait que 36px de marge à 1024px, trop mince
   pour absorber les métriques réelles de Space Grotesk. */
.site-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(1rem, .2rem + 1.2vw, 1.5rem);
}

/* Majuscules via text-transform, jamais en changeant le texte source : le
   nom accessible reste "Opérations complexes" pour un lecteur d'écran, seul
   le rendu visuel est capitalisé. Même traitement que .eyebrow ailleurs sur
   le site — cohérence typographique, pas une règle inventée pour la nav. */
.site-nav__link {
  position: relative;
  display: inline-block;
  padding-block: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}

.site-nav__link:hover { color: var(--fg); }

.site-nav__link[aria-current="page"] { color: var(--fg); }

/* Filet d'état actif — l'or en trait, pas en texte : valable dans les
   deux thèmes puisqu'il ne porte aucune information textuelle. */
.site-nav__link[aria-current="page"]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
}

/* Point de bascule fixé PAR LA MESURE, pas par convention.
   Historique, parce qu'il explique la valeur :
     - libellés complets + logo + CTA ......... 1177px → débordait de 153px à 1024px
     - libellés courts, sans logo/CTA ..........  617px à 1024px (5 entrées + 2)
     - libellés COMPLETS + MAJUSCULES (2026-08-03), sans logo/CTA .. 1035px intrinsèque
   Le retour aux libellés complets fait déborder à nouveau à 1024px — mesuré
   précisément par la position de chaque lien (pas seulement scrollWidth, qui
   ne détecte pas un débordement à GAUCHE d'un conteneur centré : "Accueil"
   sortait de l'écran par la gauche à -13px sans qu'aucun scroll horizontal
   n'apparaisse). Point de rupture réel : 1040px KO, 1056px OK de justesse.
   → bascule remontée à 70rem (1120px), qui laisse 33px/48px de marge.
   ⚠️ À re-mesurer si une entrée de menu ou son libellé change. */
@media (min-width: 70rem) {
  .site-nav { display: flex; }
}

/* ==========================================================================
   Bouton d'ouverture du menu (mobile)
   ========================================================================== */

.nav-toggle {
  display: grid;
  place-content: center;
  gap: 5px;
  width: var(--tap-min);
  height: var(--tap-min);
  padding: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.nav-toggle__bar {
  display: block;
  width: 20px;
  height: 1.5px;
  background: var(--fg);
  transition: transform var(--motion-fast) var(--ease),
              opacity var(--motion-fast) var(--ease);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) {
  transform: translateY(6.5px) rotate(45deg);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) {
  transform: translateY(-6.5px) rotate(-45deg);
}

/* Masquage en desktop — DOIT rester après la déclaration de .nav-toggle
   ci-dessus. Placé avant, il était écrasé par `display: grid` à spécificité
   égale, et le burger restait visible à côté de la nav complète.
   70rem : synchronisé avec le point de bascule de .site-nav ci-dessus. */
@media (min-width: 70rem) {
  .nav-toggle { display: none; }
}

/* ==========================================================================
   Panneau de navigation mobile
   Plein écran : un menu horizontal ne se transpose pas en petit écran.
   ========================================================================== */

.nav-panel {
  position: fixed;
  inset: var(--header-h) 0 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-8);
  padding: var(--space-7) var(--container-pad) var(--space-8);
  background: var(--bg);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.nav-panel[hidden] { display: none; }

.nav-panel__list > li + li { border-top: 1px solid var(--line-soft); }

.nav-panel__link {
  display: flex;
  align-items: center;
  /* 48px en menu mobile — au-dessus du minimum de 44px */
  min-height: 48px;
  padding-block: var(--space-3);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
}

.nav-panel__link[aria-current="page"] { color: var(--fg); }

.nav-panel__contact {
  display: grid;
  gap: var(--space-3);
}

/* 70rem : synchronisé avec le point de bascule de .site-nav. */
@media (min-width: 70rem) {
  .nav-panel { display: none; }
}

/* ==========================================================================
   Pied de page
   ========================================================================== */

.site-footer {
  padding-block: var(--section-pad-sm);
  border-top: 1px solid var(--line);
}

/* Pistes explicites plutôt qu'auto-fit : à la largeur du conteneur (1180px
   max), auto-fit calculait 3 colonnes pleines + la 4ᵉ ("Informations")
   esseulée sur sa propre ligne, avec un grand vide à sa droite — mesuré à
   1186/1440/1920px, jamais corrigé par la largeur de fenêtre puisque le
   conteneur plafonne. La colonne de marque reçoit plus de place : elle
   porte une phrase complète, pas une simple liste de liens. */
.site-footer__grid {
  display: grid;
  gap: var(--space-6) var(--space-7);
  grid-template-columns: 1fr;
}

@media (min-width: 48rem) {
  .site-footer__grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 60rem) {
  .site-footer__grid { grid-template-columns: minmax(16rem, 1.4fr) repeat(3, minmax(9rem, 1fr)); }
}

.site-footer__baseline {
  margin-top: var(--space-4);
  max-width: 34ch;
  color: var(--muted);
  font-size: var(--text-sm);
}

.site-footer__title {
  margin-bottom: var(--space-4);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

.site-footer__address {
  display: grid;
  gap: var(--space-4);
  font-size: var(--text-sm);
}

.site-footer__address a { text-decoration: none; }

/* Une ligne de coordonnées = son icône, puis son information (revenu de
   droite à gauche le 2026-08-25). align-items:center convient aux deux
   lignes à information unique (téléphone, email) : icône et texte sur un
   seul baseline, rien à arbitrer. Le bloc adresse (3 lignes) a sa propre
   variante juste en dessous — un repère centré sur trois lignes tombe au
   milieu de nulle part, ni sur le nom ni sur la ville. */
.site-footer__nap {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--muted);
}

/* Bloc adresse : icône alignée en HAUT du groupe, au niveau de la première
   ligne (la raison sociale) — elle « ouvre » le bloc plutôt que de flotter
   à sa moyenne verticale. Léger décalage (.15rem) : aligne optiquement le
   tracé sur la hauteur de capitale du texte, pas sur son propre cadre SVG. */
.site-footer__nap--address {
  align-items: flex-start;
}

.site-footer__nap--address .site-footer__icon {
  margin-top: .15rem;
}

a.site-footer__nap:hover,
a.site-footer__nap:focus-visible { color: var(--fg); }

/* min-width:0 + overflow-wrap : la piste du pied de page descend à 9rem
   (144px) et « contact@optimizbtp.fr » mesure ~160px à cette taille de
   texte. Sans ces deux règles, le mot refuse de se couper et pousse la
   colonne au-delà de sa piste.
   gap en --space-2 (8px), pas --space-1 (4px) : à 4px les trois lignes du
   bloc adresse (raison sociale / rue / code postal + ville) se lisaient
   comme un paragraphe compact plutôt que trois informations distinctes —
   reste nettement plus serré que les --space-4 (16px) qui séparent les TROIS
   blocs entre eux (adresse / téléphone / email), la hiérarchie visuelle
   ligne-dans-un-bloc vs bloc-à-bloc reste donc lisible. */
.site-footer__nap-text {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
  overflow-wrap: anywhere;
}

.site-footer__icon {
  flex: 0 0 auto;
  width: 1.125rem;
  height: 1.125rem;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ⛔ Thème clair : l'or tombe à 1,84:1 sur blanc. Purement décoratives, ces
   icônes ne trahissent aucune information en changeant de teinte — mais un
   tracé d'or de 1,5px sur blanc serait tout simplement illisible. Elles
   reprennent donc la couleur du texte fort. Même règle que .eyebrow et
   .statement__accent (piège P5). */
[data-theme="light"] .site-footer__icon { stroke: var(--muted-strong); }

.site-footer__list {
  display: grid;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.site-footer__list a { text-decoration: none; color: var(--muted); }
.site-footer__list a:hover { color: var(--fg); }

.site-footer__social {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  margin-top: var(--space-7);
  font-size: var(--text-sm);
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line-soft);
  color: var(--muted);
  font-size: var(--text-xs);
}

/* ==========================================================================
   Hero
   Le décalage du header sticky est repris ici : le hero commence sous lui.
   ========================================================================== */

/* min-height:100svh (2026-08-25, second passage — 88svh plafonné à 900px
   avant) : la vidéo doit couvrir l'INTÉGRALITÉ de la zone visible.
   Depuis le 2026-09-07, le hero ne porte plus AUCUN texte (titre, lead,
   actions descendus dans la section juste en dessous) : la vidéo doit être
   visible sans rien dessus, demande explicite. */
.hero {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 100svh;
  margin-top: calc(var(--header-h) * -1);
  overflow: hidden;
}

/* ---- Média de fond -------------------------------------------------------
   Le poster est le rendu par défaut ; la vidéo le recouvre quand elle est
   autorisée à jouer. AUCUN voile depuis le 2026-08-25 (demande explicite :
   la vidéo doit rester dans son rendu d'origine, sans contraste ni
   assombrissement ajouté). Le text-shadow ajouté ce jour-là en compensation
   a lui-même été RETIRÉ le 2026-08-25 (second passage, demande explicite :
   « les textes doivent apparaître sans effet visuel superflu »). Il ne
   reste donc plus AUCUN mécanisme de contraste artificiel — ni sur le
   média, ni sur le texte. Risque de contraste mesuré et assumé par
   l'utilisateur (voir DECISION-LOG 2026-08-25) : le texte peut devenir
   difficile à lire quand un passage clair du ciel défile derrière lui. */

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero__poster,
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__video {
  opacity: 0;
  transition: opacity var(--motion-slow) var(--ease);
}

/* Posé par le JS une fois la lecture réellement démarrée : évite un fondu
   vers une vidéo encore noire. */
.hero__video[data-hero-video="playing"] { opacity: 1; }

/* ⛔ .hero__media::after (voile en dégradé noir) SUPPRIMÉ le 2026-08-25 —
   demande explicite : « aucun contraste, assombrissement ou effet
   supplémentaire sur la vidéo ». Ne pas le réintroduire, même partiellement
   (un voile local derrière le seul bloc de texte serait quand même un
   assombrissement DE LA VIDÉO à cet endroit — ce que la demande exclut).
   La lisibilité passe désormais PAR LE TEXTE SEUL (text-shadow ci-dessous),
   jamais par une altération du média. */

/* Le hero est TOUJOURS sur fond sombre (voile noir), quel que soit le thème
   de la page. L'eyebrow en thème clair devient normalement une pastille
   (fond clair + texte sombre) : illisible ici. On force la variante texte
   simple, comme en thème sombre.
   ⚠️ Sans usage depuis le 2026-08-24 : le seul .hero restant (l'accueil) ne
   porte pas d'eyebrow. Conservée volontairement — elle redevient nécessaire
   à la seconde où un hero à texte en reçoit un, et la retirer ne gagne
   qu'une centaine d'octets. Ne pas la considérer comme du code mort à
   nettoyer sans avoir vérifié ce point. */
.hero .eyebrow,
[data-theme="light"] .hero .eyebrow {
  display: block;
  padding: 0;
  background: none;
  color: var(--gold);
}

/* .hero__zone (mention géographique) et .hero__actions/.hero__titre/.hero__lead
   (titre, lead, actions) SUPPRIMÉS le 2026-08-25 et 2026-09-07 avec leur
   markup — plus aucun usage sur le site, règles retirées avec lui plutôt que
   laissées en CSS mort. Le titre/lead/actions vivent désormais dans la
   section juste sous le hero (#hero-titre, .statement + .button standards).
   La mention régionale en prose reste portée par /contact/ (section
   « Zone d'intervention »). */

/* ==========================================================================
   Bande image d'ouverture — pages internes
   Remplace depuis le 2026-08-24 le hero à texte incrusté des 3 pages de pôle
   et d'À propos. Trois contraintes, toutes demandées explicitement :
     1. AUCUN voile ni filtre — c'est la différence de fond avec .hero, qui
        pose un voile noir à 88 % pour le contraste de son texte ;
     2. AUCUN texte par-dessus : le <h1> descend dans la .section--intro ;
     3. pas même la navigation — d'où l'absence du margin-top négatif de
        .hero. La bande démarre SOUS l'en-tête, redevenu opaque (voir
        .site-header). Poser l'un sans l'autre ferait réapparaître du texte
        sur l'image.
   Recadrage par object-fit, jamais par réencodage : les visuels sont déjà
   au bon poids.
   ========================================================================== */

/* Trois paliers explicites (mobile/48rem/70rem, ce dernier = bascule nav
   P14), en svh — jamais un px figé, seule façon de garantir « > 50 % de la
   hauteur visible » quelle que soit la hauteur réelle de l'écran. Agrandi
   deux fois le 2026-08-25 (58/60/62 → 64/66/68svh). UNE SEULE classe
   partagée par les 4 pages concernées : proportions identiques garanties
   par construction. min-height : garde-fou écran très bas ; max-height
   (desktop) : plafond au-delà de 1600px de hauteur, hors usage réel. */
.page-media {
  width: 100%;
  height: 64svh;
  min-height: 24rem;
  overflow: hidden;
  /* Le temps du décodage : jamais un aplat blanc en thème clair, qui
     produirait un flash au chargement. */
  background: var(--surface);
}

@media (min-width: 48rem) {
  .page-media { height: 66svh; min-height: 29rem; }
}

@media (min-width: 70rem) {
  .page-media { height: 68svh; min-height: 34rem; max-height: 54rem; }
}

.page-media__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* construction-renovation.webp : image source très large (1600×900) avec le
   sujet (fauteuil, plantes, étagère) masse à GAUCHE — vérifié en ouvrant le
   fichier directement. Sur une bande bien plus haute qu'avant (>50 % de la
   hauteur visible depuis le 2026-08-25), le rapport largeur/hauteur de la
   bande devient plus étroit que celui de l'image : object-fit:cover cadre
   alors en gardant TOUTE la hauteur et en rognant les CÔTÉS, pas le
   haut/bas (vérifié : à 375×471, le slice visible fait ~717px de large sur
   les 1600 d'origine — la hauteur, elle, n'est jamais rognée). Un centrage
   horizontal par défaut retombait donc au milieu du pan de mur vide, entre
   le fauteuil et la fenêtre. Décalé à gauche pour garder le groupe
   fauteuil + plantes + étagère dans le cadre à toutes les largeurs. */
.page-media__image[src*="construction-renovation.webp"] {
  object-position: 8% center;
}

/* ==========================================================================
   Formulaire
   ========================================================================== */

.form {
  display: grid;
  gap: var(--space-5);
}

.form__field {
  display: grid;
  gap: var(--space-2);
}

.form__label {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}

.form__required { color: var(--accent); }

/* ⛔ L'or ne tient pas le contraste sur fond clair : l'astérisque y reprend
   la couleur du texte. Il reste identifiable par sa position. */
[data-theme="light"] .form__required { color: inherit; }

.form__input {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  min-height: var(--tap-min);
  background: var(--surface);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color var(--motion-fast) var(--ease),
              background var(--motion-fast) var(--ease);
}

.form__input:hover { border-color: var(--accent-line); }

.form__input:focus-visible {
  border-color: var(--focus);
  background: var(--surface-hover);
}

/* ⚠️ Le popup natif d'un <select> est dessiné par le NAVIGATEUR, pas la
   page — --surface (translucide) y composait sur un fond opaque inconnu,
   d'où des options blanc sur quasi-blanc. Un <option> veut un fond OPAQUE
   connu (--bg, 21:1), jamais un jeton translucide. Seul <select> du site. */
.form__input option {
  background: var(--bg);
  color: var(--fg);
}

/* Survol/sélection : accord partiel selon navigateur — Chrome/Edge
   respectent ces règles sur <option>, Firefox et les pickers natifs mobiles
   imposent leur propre présentation (OS), sans rien casser dans les deux
   cas : au pire, ceci est ignoré et le surlignage natif du navigateur
   s'applique, déjà correctement contrasté par construction. Aplat d'or +
   encre noire (11,4:1, la même paire que .button--primary) plutôt qu'un
   token translucide : opaque à coup sûr, quel que soit le fond réel du
   popup. */
.form__input option:hover,
.form__input option:checked,
.form__input option:focus {
  background: var(--accent);
  color: var(--accent-ink);
}

/* L'état invalide n'apparaît qu'après interaction : signaler une erreur sur
   un champ jamais touché est hostile. */
.form__input[aria-invalid="true"] { border-color: var(--error); }

.form__error {
  color: var(--error);
  font-size: var(--text-sm);
}

.form__field--consent {
  grid-template-columns: auto 1fr;
  gap: var(--space-3);
  align-items: start;
}

.form__checkbox {
  width: 1.25rem;
  height: 1.25rem;
  margin-top: .2rem;
  accent-color: var(--accent);
}

.form__consent-label {
  font-size: var(--text-sm);
  color: var(--muted);
}

.form__status:empty { display: none; }

.form__status {
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--accent);
  background: var(--surface);
  font-size: var(--text-sm);
}

.form__status[data-state="error"] { border-left-color: var(--error); }
.form__status[data-state="success"] { border-left-color: var(--success); }

.form__note {
  font-size: var(--text-sm);
  color: var(--muted);
}


/* ==========================================================================
   Pages légales
   Densité plus soutenue que le reste du site : ce sont des documents de
   référence, pas des pages de séduction. La lisibilité prime sur l'espace.
   ========================================================================== */

.legal { font-size: var(--text-sm); }

.legal h2 {
  margin-bottom: var(--space-4);
  font-size: var(--text-lg);
}

.legal h3 {
  margin: var(--space-5) 0 var(--space-3);
  font-size: var(--text-base);
}

.legal p { color: var(--muted); }
.legal p + p { margin-top: var(--space-3); }

.legal__list {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-3);
  color: var(--muted);
}

.legal__list li {
  padding-left: var(--space-4);
  border-left: 1px solid var(--line);
}

.legal a { color: var(--fg); }

.legal code {
  padding: .1em .4em;
  background: var(--surface);
  border-radius: var(--radius-sm);
  font-size: .95em;
}

/* Un tableau large doit défiler DANS son conteneur — jamais faire déborder
   la page. C'est la seule zone de défilement horizontal tolérée. */
.table-wrap {
  overflow-x: auto;
  margin-top: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.legal__table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

.legal__table th,
.legal__table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
}

.legal__table th {
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.legal__table td { color: var(--muted); }
.legal__table tbody tr:last-child td { border-bottom: 0; }

/* ==========================================================================
   Bannière de consentement
   ⚠️ Exigence CNIL : « Refuser » a le MÊME poids visuel que « Accepter ».
   Ne jamais réduire, griser ou reléguer le bouton de refus — ce serait un
   dark pattern, et c'est sanctionné.
   ========================================================================== */

.consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: var(--z-banner);
  background: var(--bg);
  border-top: 1px solid var(--line);
  box-shadow: var(--shadow-soft);
}

.consent__inner {
  display: grid;
  gap: var(--space-2) var(--space-8);
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding: var(--space-6) var(--container-pad);
}

.consent__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
}

.consent__text {
  max-width: 62ch;
  color: var(--muted);
  font-size: var(--text-sm);
}

/* Grille et non flex : trois pistes égales, donc trois boutons rigoureusement
   de même largeur — c'est l'exigence CNIL rendue visible, pas seulement
   respectée. En flex avec `flex: 1 1 12rem`, la largeur finale dépendait de
   la longueur du libellé dès que la ligne ne se répartissait pas exactement :
   « Personnaliser » ressortait plus large que « Tout refuser ». */
.consent__actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/* 60rem, pas 48rem. À 48rem (768px) la colonne de texte tombait à ~200px de
   large : le message de consentement s'étirait sur six lignes étroites à
   côté d'un bloc de boutons trois fois plus large. Mesuré à 800px de fenêtre.
   À 60rem la colonne de texte garde au moins ~430px. */
@media (min-width: 60rem) {
  .consent__inner {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }

  .consent__actions {
    grid-column: 2;
    grid-row: 1 / 3;
    margin-top: 0;
    /* Conteneur de largeur indéfinie (piste `auto`) : les trois pistes 1fr
       s'égalisent sur le plus large des trois contenus. Exactement le
       comportement voulu, sans figer une largeur en dur. */
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ---- Panneau de préférences --------------------------------------------- */

.consent-panel {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--container-pad);
  background: rgb(0 0 0 / .6);
  overflow-y: auto;
}

.consent-panel__inner {
  display: grid;
  gap: var(--space-4);
  width: min(100%, 40rem);
  max-height: 85vh;
  padding: var(--space-6);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow-y: auto;
}

.consent-panel__title {
  margin-bottom: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-xl);
}

/* Une catégorie = une carte, plutôt que quatre blocs séparés par un filet.
   Le filet du bas courait aussi sous la DERNIÈRE catégorie, juste au-dessus
   des boutons : deux séparateurs collés, sans rien entre les deux. */
.consent-panel__group {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  transition: border-color var(--motion-fast) var(--ease);
}

/* La carte active se souligne d'un filet d'or : l'état du panneau se lit
   d'un coup d'œil, sans avoir à comparer quatre interrupteurs.
   :not(:disabled) exclut « Strictement nécessaires », toujours cochée —
   la souligner ne dirait rien puisqu'elle ne peut pas changer. */
.consent-panel__group:has(input:checked:not(:disabled)) {
  border-color: var(--accent-line);
}

/* Intitulé à gauche, interrupteur à droite, description dessous sur toute la
   largeur. Les quatre interrupteurs s'alignent donc verticalement quelle que
   soit la longueur des intitulés — c'est ce qui rend le panneau lisible. */
.consent-panel__toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: var(--tap-min);
  cursor: pointer;
}

.consent-panel__group--locked .consent-panel__toggle { cursor: default; }

/* ⚠️ C'est CE conteneur qui encaisse le manque de place, pas l'interrupteur.
   Mesuré à 375px lors de la vérification du 2026-08-24 : intitulé (110px) +
   « Toujours actif » (130px) + interrupteur (44px) + gouttières = 316px pour
   271px disponibles — le panneau de préférences débordait horizontalement et
   affichait une barre de défilement latérale. flex-wrap fait passer le
   libellé d'état SOUS l'intitulé quand la ligne ne tient plus ; min-width:0
   autorise l'intitulé à se couper au lieu d'imposer sa largeur minimale. */
.consent-panel__toggle-text {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  flex: 1 1 auto;
  min-width: 0;
}

/* Interrupteur. La case reste une <input type="checkbox"> NATIVE : seul son
   rendu est repris (appearance:none). Clavier, barre d'espace, état coché
   annoncé par les lecteurs d'écran — tout continue de fonctionner sans une
   ligne de JS. Ne jamais la remplacer par un <div> stylisé. */
.consent-panel__toggle input {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  flex: 0 0 auto;
  width: 2.75rem;
  height: 1.5rem;
  margin: 0;
  background: var(--surface-hover);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease),
              border-color var(--motion-fast) var(--ease);
}

/* Le curseur. 44px de piste − 2px de bordures = 42px utiles ; pastille de
   16px, 3px de marge de chaque côté, donc 20px de course exactement. */
.consent-panel__toggle input::after {
  content: "";
  position: absolute;
  top: 50%;
  inset-inline-start: .1875rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--muted-strong);
  translate: 0 -50%;
  transition: translate var(--motion-fast) var(--ease),
              background var(--motion-fast) var(--ease);
}

.consent-panel__toggle input:checked {
  background: var(--accent);
  border-color: var(--accent);
}

/* Noir sur l'aplat d'or : 11,4:1, dans les deux thèmes. */
.consent-panel__toggle input:checked::after {
  background: var(--accent-ink);
  translate: 1.25rem -50%;
}

/* Une catégorie verrouillée reste lisible : on ne masque pas l'information
   sous prétexte qu'elle n'est pas modifiable. Le libellé « Toujours actif »
   ci-dessous dit POURQUOI — le gris seul ne le dirait à personne. */
.consent-panel__toggle input:disabled { opacity: .55; cursor: not-allowed; }

.consent-panel__badge {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--muted);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
}

.consent-panel__group-title {
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
  color: var(--fg);
}

.consent-panel__group-text {
  max-width: 58ch;
  color: var(--muted);
  font-size: var(--text-sm);
}

/* Les deux boutons du panneau — détachés des cartes par un filet, alignés à
   droite dès qu'il y a la place, « Enregistrer » en dernier (ordre du DOM :
   Annuler puis Enregistrer, jamais l'inverse). */
.consent-panel__actions {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin-top: var(--space-2);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}

@media (min-width: 34rem) {
  .consent-panel__actions {
    grid-template-columns: repeat(2, minmax(11rem, auto));
    justify-content: end;
  }
}

/* Sous 30rem, chaque pixel de gouttière compte : le panneau récupère 32px de
   largeur utile, soit de quoi garder l'intitulé et son libellé d'état sur la
   même ligne dans la plupart des cas plutôt que systématiquement empilés. */
@media (max-width: 30rem) {
  .consent-panel__inner { padding: var(--space-5); }
  .consent-panel__group { padding-inline: var(--space-4); }
}

/* ==========================================================================
   Diagnostic
   Sans JS, toutes les étapes sont visibles à la suite : c'est un formulaire
   lisible. Le script les masque et n'en révèle qu'une à la fois.
   ========================================================================== */

.assessment {
  display: grid;
  gap: var(--space-6);
}

.assessment__progress {
  display: grid;
  gap: var(--space-3);
}

.assessment__count {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  color: var(--muted);
}

.assessment__step {
  display: grid;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  border: 0;
}

.assessment__question {
  padding: 0 0 var(--space-4);
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semi);
  line-height: var(--leading-snug);
}

/* La zone cliquable est le label entier, pas seulement le rond du radio. */
.assessment__option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  min-height: var(--tap-min);
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease),
              background var(--motion-fast) var(--ease);
}

.assessment__option:hover {
  background: var(--surface-hover);
  border-color: var(--accent-line);
}

.assessment__option input {
  flex-shrink: 0;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: .25rem;
  accent-color: var(--accent);
}

/* Le focus porte sur l'input, mais c'est le label qui doit se voir. */
.assessment__option:has(input:focus-visible) {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
}

.assessment__option:has(input:checked) {
  border-color: var(--accent);
}

.assessment__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* ==========================================================================
   Lecture de résultat
   Toujours dans le DOM — donc lisible sans JS et indexable. Le script ne
   fait que mettre en avant la plus pertinente, sans masquer les autres :
   le visiteur peut ne pas se reconnaître dans le résultat calculé.
   ========================================================================== */

/* var(--space-5), pas --space-4 (2026-09-08, retour utilisateur : plus
   d'air titre → description, audité — 16px). .result a 3 enfants
   (.eyebrow, .result__title, .result__text) sur une seule grille : ce gap
   pilote les deux écarts à la fois, pas seulement titre→texte — effet de
   bord accepté ici, PAS un problème : l'écart eyebrow→titre de ce
   composant (16px) était déjà en dessous de --gap-eyebrow (28px, la
   convention du reste du site) ; le remonter à 24px le rapproche de cette
   convention au lieu de l'en éloigner. */
.result {
  display: grid;
  gap: var(--space-5);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  scroll-margin-top: calc(var(--header-h) + var(--space-6));
  transition: border-color var(--motion-base) var(--ease),
              background var(--motion-base) var(--ease);
}

.result[data-result-active] {
  border-color: var(--accent);
  background: var(--surface);
}

.result__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semi);
  line-height: var(--leading-snug);
}

.result__text { color: var(--muted); }

/* ==========================================================================
   Carte externe sous consentement
   Rien n'est chargé tant que la catégorie n'est pas accordée : ni iframe,
   ni script, ni requête. Le substitut explique pourquoi.
   ========================================================================== */

.map-embed {
  aspect-ratio: 4 / 3;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.map-embed__placeholder {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-4);
  height: 100%;
  padding: var(--space-5);
  background: var(--surface);
  font-size: var(--text-sm);
  color: var(--muted);
}

.map-embed iframe {
  width: 100%;
  height: 100%;
  border: 0;
}

/* ==========================================================================
   Liste de qualificatifs
   Les textes client énumèrent beaucoup : « Techniques · Budgétaires ·
   Contractuels · Organisationnels ». Une puce classique alourdirait ;
   un filet latéral tient le rythme sans bruit visuel.
   ========================================================================== */

/* ⚠️ Palier explicite à 2 colonnes (2026-08-26) : en auto-fit pur, la
   fenêtre ~700-950px donnait 3 pistes pour des listes de 4 items — le 4ᵉ
   restait seul avec deux colonnes de vide (mesuré à 768px). Même défaut,
   même remède que la grille du pied de page (2026-08-10). L'auto-fit reste
   au-delà de 60rem : il y donne 4 colonnes pour les listes de 4 et 5 pour
   celle de 5 (/a-propos/), qu'un repeat(4,1fr) figé aurait cassée en 4+1.
   ⚠️ .tag-list--stack doit rester déclarée APRÈS : même spécificité, c'est
   l'ordre source qui tranche. */
.tag-list {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}

@media (min-width: 34rem) {
  .tag-list { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 60rem) {
  .tag-list { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
}

/* Renforcée le 2026-08-26 : un filet de 2px à 38 % d'or sur fond
   transparent laissait les items flotter sans ancrage. Filet en --accent
   plein (c'est un FILET, pas du texte — autorisé dans les deux thèmes,
   contrairement au piège P5), aplat --surface pour donner du corps, rayon à
   DROITE seulement pour que le filet reste un bord franc. */
.tag-list__item {
  padding: var(--space-4) var(--space-4) var(--space-4) var(--space-5);
  background: var(--surface);
  border-left: 2px solid var(--accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

/* Sur une section en --surface, l'aplat ci-dessus serait strictement
   invisible (surface sur surface) : l'item y monte d'un cran. */
.section--surface .tag-list__item { background: var(--surface-hover); }

/* Variante en colonne unique, pour les listes longues en regard d'un texte */
.tag-list--stack { grid-template-columns: 1fr; }

/* Variante carte : la grille de .tag-list (dont la ligne unique dès 60rem)
   reste intacte, seule la boîte change — filet complet + surface + survol,
   comme .step-grid/.approach-grid, à la place du filet latéral d'origine.
   Modificateur scopé : les 10 autres usages de .tag-list sur le site ne
   sont pas touchés. Taille de texte alignée sur --text-lg (2026-09-06,
   retour utilisateur) : c'est la taille de TOUS les titres de carte du site
   (.service-card__title, .card__title, .offer-card h3, .approach-grid__txt)
   — --text-base d'origine ne valait que pour .tag-list hors carte. */
.tag-list--card .tag-list__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  font-size: var(--text-lg);
  text-align: center;
  transition: border-color var(--motion-base) var(--ease),
              background var(--motion-base) var(--ease);
}

.tag-list--card .tag-list__item:hover {
  background: var(--surface-hover);
  border-color: var(--accent-line);
}

/* Index décoratif, réservé à .tag-list--card (l'ordre n'a pas de valeur
   sémantique — même raisonnement que .ledger__num / .approach-grid__index). */
.tag-list__index {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
}

/* ⛔ Thème clair : l'or ne porte jamais de texte (1,84:1 — piège P5). */
[data-theme="light"] .tag-list__index { color: var(--muted-strong); }

/* ==========================================================================
   Registre — rangées éditoriales séparées par des filets
   UNE famille, TROIS arrangements (2026-09-06), plutôt que trois composants
   concurrents : la page À propos empilait 6 sections « titre + liste » sur 7.
     · seule            → index éditorial pleine largeur
     · dans .chapter    → liste défilante face à un titre figé
     · dans .split--feature → satellites face à un panneau plein
   Aucune boîte, aucune ombre : la profondeur vient des filets et des
   surfaces (contrat visuel, DESIGN-REFERENCE §3.3) — c'est ce qui distingue
   ce registre d'une grille de cartes.
   ========================================================================== */

.ledger { border-top: 1px solid var(--line); }

.ledger__row {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-5);
  align-items: baseline;
  padding: var(--space-6) var(--space-5);
  border-bottom: 1px solid var(--line);
  transition: background var(--motion-base) var(--ease),
              padding-left var(--motion-base) var(--ease);
}

/* Décalage de 8px seulement (24 → 32) : à 16px de saut, le survol
   sautillait. */
.ledger__row:hover { background: var(--surface); padding-left: var(--space-6); }

/* Le filet d'or se déploie du HAUT vers le bas : le survol acquiert un sens
   de lecture, au lieu d'un simple changement d'état. */
.ledger__row::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform var(--motion-base) var(--ease);
}

.ledger__row:hover::before { transform: scaleY(1); }

/* Index décoratif : l'ordre n'a pas de valeur sémantique ici (ce sont des
   constats, pas des étapes) — d'où <ul> + aria-hidden dans le balisage,
   jamais <ol>. Même raisonnement que .service-card__step. */
.ledger__num {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--muted);
  transition: color var(--motion-base) var(--ease);
}

.ledger__row:hover .ledger__num { color: var(--accent); }

.ledger__txt {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
}

/* ---- Démarche en 4 cartes (/a-propos/) -----------------------------------
   Ex-.ledger--lead : seul usage du registre en pleine largeur, remplacé par
   une grille de cartes à la demande. Même famille visuelle que .step-grid
   (/diagnostic/) — filet + surface, jamais d'ombre — et même grille 2×2 fixe.
   .ledger reste inchangée : elle sert ailleurs (constats, valeurs) et son
   contrat (DESIGN-REFERENCE §3.3) est justement de NE PAS être une carte. */

.approach-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(2, 1fr);
}

@media (max-width: 48rem) {
  .approach-grid { grid-template-columns: 1fr; }
}

/* Variante empilée : même famille (.tag-list--stack) pour les listes en
   regard d'un texte, où 2 colonnes réduiraient le contenu défilant à côté
   d'un panneau déjà plus haut (cas du panneau « engagement », /a-propos/). */
.approach-grid--stack { grid-template-columns: 1fr; }

/* Texte centré, scopé à ce bloc uniquement (ne concerne pas « démarche » ni
   « constats », qui restent alignés à gauche). */
.approach-grid--stack .approach-grid__item {
  align-items: center;
  text-align: center;
}

.approach-grid__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color var(--motion-base) var(--ease),
              background var(--motion-base) var(--ease);
}

.approach-grid__item:hover {
  background: var(--surface-hover);
  border-color: var(--accent-line);
}

.approach-grid__index {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
}

/* ⛔ Thème clair : l'or ne porte jamais de texte (1,84:1 — piège P5). */
[data-theme="light"] .approach-grid__index { color: var(--muted-strong); }

/* --text-lg, PAS --text-xl (hérité par erreur de l'ancien .ledger--lead,
   pensé pour une ligne pleine largeur — plus la bonne référence en carte) :
   c'est la taille de TOUS les titres de carte du site (.service-card__title,
   .card__title, .offer-card h3) et des deux autres .ledger__txt de CETTE
   page (constats, engagement) — la comparaison qui compte ici. */
.approach-grid__txt {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
}

/* ---- Chapitre : titre figé, contenu défilant -----------------------------
   position:sticky en CSS pur — aucune librairie, aucun écouteur de scroll.
   Ne s'active qu'au-delà de 60rem : en dessous, la colonne de titre n'a plus
   de hauteur à parcourir et le sticky n'aurait aucun effet. */
.chapter { display: grid; gap: var(--space-7); }

@media (min-width: 60rem) {
  .chapter {
    grid-template-columns: minmax(0, .42fr) minmax(0, .58fr);
    gap: var(--space-8);
    align-items: center;
  }

  /* ⚠️ Le sticky est OPT-IN, jamais le défaut (2026-09-06). Il n'a de sens
     que si la colonne défilante est NETTEMENT plus haute que le titre :
     mesuré sur le bloc « Pour qui » de /performance-organisation/ (2 items),
     188px de contenu pour un titre de 154px, soit 34px de course — le titre
     s'épinglait puis relâchait aussitôt, ce qui se lisait comme un décalage
     parasite. Sur /a-propos/ (4 items), la course est de 310px : là, il
     travaille vraiment. Avant d'ajouter ce modificateur ailleurs, MESURER
     la course, ne pas la supposer. */
  .chapter--sticky { align-items: start; }
  .chapter--sticky .chapter__aside {
    position: sticky;
    top: calc(var(--header-h) + var(--space-6));
  }
}

/* ---- Panneau majeur face à ses satellites --------------------------------
   Le contraste ne vient PAS d'une image (écartée le 2026-09-06) mais de
   l'opposition plein / filet : aplat --surface et filet d'or à gauche d'un
   côté, rangées en filets seuls de l'autre. */
.callout--feature {
  display: grid;
  align-content: center;
  padding: var(--space-7);
  border-left-width: 4px;
}

/* --text-xl et non --text-2xl : à 42px, « Chaque dysfonctionnement »
   (/performance-organisation/) réclamait 570px pour 556px disponibles — le
   premier mot restait seul sur sa ligne, mesuré. Et à --text-2xl le panneau
   avait la MÊME taille que le <h2> de sa section, ce qui aplatissait la
   hiérarchie : un panneau de constat se place sous le titre, pas à côté.
   text-wrap:balance en renfort, pour qu'aucune ligne ne devienne orpheline
   si le texte change. */
.callout--feature p {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  text-wrap: balance;
}

@media (min-width: 60rem) {
  /* gap hérité de .split = --space-9 (96px) : le panneau et ses satellites
     forment UN ensemble, 96px les séparait comme deux blocs étrangers. */
  .split--feature {
    grid-template-columns: 1.3fr .7fr;
    gap: var(--space-8);
    align-items: stretch;
  }
}

/* ==========================================================================
   Parcours jalonné
   « Trois étapes, selon le niveau d'engagement » décrit une PROGRESSION que
   trois cartes égales nient : elles se lisent comme trois options
   parallèles. Ici une ligne traverse les trois temps, chacun marqué par un
   jalon numéroté posé dessus.
   Jalon = aplat d'or + encre noire (11,4:1 dans les DEUX thèmes) et non du
   texte or, illisible sur fond clair (piège P5). Angle vif plutôt que
   cercle : registre institutionnel, pas décoratif.
   ========================================================================== */

.steps { display: grid; gap: var(--space-8); }

.steps__item {
  position: relative;
  padding-top: var(--space-8);
  border-top: 1px solid var(--line);
}

.steps__node {
  position: absolute;
  top: 0;
  left: 0;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  height: 2.5rem;
  padding-inline: var(--space-3);
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  letter-spacing: var(--tracking-wide);
}

/* Le nom de l'étape et sa promesse forment UNE unité — 8px les lie. La
   description est un cran en dessous — 16px l'en détache. Deux valeurs
   différentes, donc une hiérarchie lisible : à 12px/12px, tout était
   équidistant et l'ensemble paraissait tassé. */
.steps__title {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semi);
  margin-bottom: var(--space-2);
}

.steps__obj {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  margin-bottom: var(--space-4);
}

.steps__txt { color: var(--muted); font-size: var(--text-sm); }

@media (min-width: 60rem) {
  .steps { position: relative; grid-template-columns: repeat(3, 1fr); gap: var(--space-7); }

  /* La ligne devient continue d'un bout à l'autre du parcours : un
     border-top par colonne l'aurait interrompue à chaque gouttière. */
  .steps::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 1px;
    background: var(--line);
  }

  .steps__item { border-top: 0; }
}

/* ==========================================================================
   Manifeste
   Phrase forte isolée. Centrée, respirante — c'est le seul endroit où le
   centrage est autorisé (le reste du site est aligné à gauche).
   ========================================================================== */

/* ⚠️ Bug trouvé le 2026-08-25 en mesurant (pas en relisant) à 1920px : la
   PREMIÈRE phrase du manifeste se coupait elle-même en deux lignes
   imprévues. Cause réelle : .statement vit dans .section-heading, qui pose
   son propre max-width:var(--measure) = 62ch — calculé dans la police du
   CORPS de texte (Inter), pas celle de .statement (Space Grotesk, plus
   large par caractère). En block (width:auto), .statement se voyait donc
   comprimé à la largeur du PARENT (664,9px mesurés à 1920px) au lieu de sa
   propre limite de 34ch (964,9px à la même taille) — et la phrase la plus
   longue ("La maîtrise ne se revendique pas.", 675,7px hors contrainte)
   dépassait cette largeur imposée de l'extérieur.
   width:fit-content découple .statement de la largeur du parent : il se
   dimensionne sur SON PROPRE contenu, plafonné par SON PROPRE max-width —
   jamais par celui, plus étroit, d'un ancêtre dans une autre police. */
.statement {
  width: fit-content;
  max-width: 34ch;
  margin-inline: auto;
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  font-weight: var(--weight-medium);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  text-align: center;
  text-wrap: balance;
}

/* ⚠️ width:fit-content (sur .statement) ne suffisait pas : fit-content
   plafonne à la largeur dispo du PARENT, ici .section-heading (62ch, dans
   la police du corps de texte) — le vrai problème vient de LUI, pas de
   .statement. :has() lui retire son max-width dès qu'un .statement
   l'enveloppe (avec ou sans --stacked, un h1/h2 .statement porte toujours
   sa propre police plus large) ; partout ailleurs, ce plafond de lecture
   reste légitime. Élargi le 2026-09-07 pour couvrir #hero-titre (accueil),
   qui utilise .statement seul, sans --stacked. */
.section-heading:has(.statement) {
  max-width: none;
}

/* Même piège, même remède que .statement ci-dessus : les 62ch de
   .section-heading sont comptés dans la police du corps de texte, pas dans
   celle du h2 (Space Grotesk, plus large par caractère) — à 42ch de LARGEUR
   RÉELLE le titre tombait sur 3 lignes. :has() retire le plafond hérité,
   42ch (dans la police du h2) le replafonne pour forcer 2 lignes, balance
   les équilibre. Mesuré : 913px = 3 lignes, 950px = 2 lignes (1258px de
   viewport) — 42ch ≈ 1150px ici, marge confortable au-dessus du seuil. */
.section-heading:has(#cibles-titre) {
  max-width: none;
}

#cibles-titre {
  max-width: 42ch;
  margin-inline: auto;
  text-wrap: balance;
}

/* Même correctif, appliqué le 2026-09-07 aux deux titres d'ouverture de
   l'accueil (« Notre rôle », « Le constat ») : centrés à la demande, et
   élargis pour ne plus hériter du plafond en police de corps. */
.section-heading:has(#positionnement-titre),
.section-heading:has(#constat-titre) {
  max-width: none;
}

#positionnement-titre,
#constat-titre {
  max-width: 42ch;
  margin-inline: auto;
  text-wrap: balance;
}

/* 2026-09-08 (suite) — /diagnostic/, sur demande explicite : le <h1> et le
   <h2> "Trois lectures…" tombaient chacun sur 2 lignes, plafonnés par les
   62ch de .section-heading calculés en police de CORPS alors que ces titres
   sont en Space Grotesk (plus large par caractère, même piège que
   #cibles-titre plus haut) — ET, ici, le vrai goulot était le CONTENEUR
   PARENT (.container--narrow, 820px) : levé le plafond de .section-heading
   sans élargir .container--narrow n'aurait rien changé. Mesuré avant
   correctif à 1440/1920px (police déjà à sa taille maximale du clamp() aux
   deux largeurs, donc même mesure aux deux) : <h1> a besoin de 1026px sur
   une ligne, <h2> de 851px — au-delà des 820px que .container--narrow
   laissait disponibles, quel que soit .section-heading. Les deux sections
   sont donc passées à .container (1180px, 1100px de contenu) — voir le
   passage équivalent de .container--narrow à .container pour #offres-titre
   (D54) : même famille de correctif, cette fois pour un <h1>/<h2> seul
   plutôt que pour une grille de cartes. */
/* 2026-09-08 (nouvelle demande) — les trois <h1>/<h2> ci-dessous, déjà élargis
   pour tenir sur une ligne, restaient alignés à GAUCHE : sur grand écran, le
   bloc `.container` (1180px) est bien centré dans la page, mais du texte
   aligné à gauche À L'INTÉRIEUR démarre nettement avant le centre visuel de
   l'écran — l'utilisateur a clarifié vouloir un centrage RÉEL du texte, pas
   seulement une cohérence de bord gauche entre sections (le correctif
   précédent, toujours valable pour le RESTE de la page). Confirmé par
   l'utilisateur (AskUserQuestion) : titres et chapôs COURTS centrés, comme
   le fait déjà l'accueil (#positionnement-titre, #constat-titre) — listes,
   cartes DTFC/DTFR et grille « Comment cela se passe » restent alignées à
   gauche, un texte long ou une grille centrés étant moins lisibles.
   🔴 BUG CORRIGÉ (même jour) : ce bloc ciblait `#page-titre`, l'ID standard du
   <h1> réutilisé sur 12 pages du site (chaque page de pôle, à-propos, contact,
   les pages légales…) — pas un ID propre à /diagnostic/. La règle de centrage
   du chapô/note s'appliquait donc SUR TOUTES CES PAGES : leur <h1>, resté
   aligné à gauche (aucune n'a `.section-heading--center`), se retrouvait avec
   un chapô centré juste en dessous — le exact défaut de cohérence que ce
   fichier met en garde ailleurs (voir le commentaire sur .related dans
   operations-complexes.html/performance-organisation.html). Trouvé en
   auditant ces pages à la demande de l'utilisateur, pas signalé par lui.
   Le <h1> de /diagnostic/ porte désormais un ID qui LUI est propre
   (`#diagnostic-titre`, posé dans diagnostic.html) : plus aucune collision
   possible avec les 11 autres pages qui gardent `#page-titre`. */
.section-heading:has(#diagnostic-titre),
.section-heading:has(#lectures-titre),
.section-heading:has(#offres-titre) {
  max-width: none;
}

/* Chapô et note de l'intro, chapô de l'offre : plafonnés à var(--measure)
   ET auto-centrés (margin-inline:auto) dans le bloc élargi ci-dessus — un
   max-width sur un enfant ne dépasse jamais l'espace laissé par le parent,
   seulement le réduire, donc rien ne casse ; sans le margin-inline:auto, un
   bloc devenu plus étroit que son parent resterait pourtant collé à GAUCHE
   (text-align:center ne centre que le contenu INLINE, jamais la boîte d'un
   enfant de niveau bloc). Sans cette règle, ces paragraphes suivraient aussi
   leur titre jusqu'à 1100px, bien au-delà d'une largeur de lecture
   confortable pour une phrase de prose. */
.section-heading:has(#diagnostic-titre) .section-heading__lead,
.section-heading:has(#diagnostic-titre) .form__note,
.section-heading:has(#offres-titre) .section-heading__lead {
  max-width: var(--measure);
  margin-inline: auto;
}

/* /à-propos/ (2026-09-08, retour utilisateur) — même famille que le bloc
   #diagnostic-titre ci-dessus, ID propre à la page pour la même raison
   (P27) : #a-propos-titre ne collisionne avec aucun autre ID du site.
   Conteneur déjà passé à .container--wide dans le HTML (1400px, le <h1> a
   besoin de 1168px sur une ligne — .container par défaut, 1100px de
   contenu, ne suffisait pas, contrairement à /diagnostic/). */
.section-heading:has(#a-propos-titre) {
  max-width: none;
}
.section-heading:has(#a-propos-titre) .section-heading__lead {
  max-width: var(--measure);
  margin-inline: auto;
}

/* Les 3 pages de pôle + /contact/ (2026-09-08, retour utilisateur) — même
   famille que les blocs #diagnostic-titre et #a-propos-titre ci-dessus, ID
   propre à chaque page pour la même raison (P27). Mesuré (Loi 2) avant
   correctif, largeur naturelle sur une ligne vs 1100px de contenu
   disponibles dans .container par défaut :
     — #operations-complexes-titre : 2094px → 4 lignes actuellement (plafond
       62ch en police de corps), 2 lignes une fois le plafond retiré, sans
       toucher au conteneur. Pas de chapô sur cette page.
     — #performance-organisation-titre : 2056px → même constat, 2 lignes.
     — #construction-renovation-titre : 2406px (titre le plus long des 3,
       13 mots) → seulement 3 lignes une fois le plafond retiré à largeur de
       conteneur inchangée ; 2 lignes UNIQUEMENT si le conteneur passe en
       .container--wide (1320px de contenu, déjà fait dans le HTML) — seule
       des 3 pages de pôle à en avoir besoin.
     — #contact-titre : 675px → tenait déjà en 2 lignes maladroites à cause
       du seul plafond 62ch (664.9px, à peine 10px sous les 675px requis) ;
       1 ligne dès que le plafond est retiré, conteneur par défaut inchangé.
   Toutes centrées (section-heading--center, déjà posé dans le HTML). */
.section-heading:has(#operations-complexes-titre),
.section-heading:has(#performance-organisation-titre),
.section-heading:has(#construction-renovation-titre),
.section-heading:has(#contact-titre) {
  max-width: none;
}
.section-heading:has(#performance-organisation-titre) .section-heading__lead,
.section-heading:has(#construction-renovation-titre) .section-heading__lead,
.section-heading:has(#contact-titre) .section-heading__lead {
  max-width: var(--measure);
  margin-inline: auto;
}

/* /performance-organisation/, section « Chaque entreprise possède… »
   (2026-09-08, retour utilisateur) — même famille que les blocs ci-dessus.
   #approche-entreprise-titre : ID propre à cette section, PAS #approche-titre
   (partagé avec operations-complexes.html, P27). Mesuré : 1925px sur une
   ligne pour 12 mots — 2 lignes une fois le plafond retiré, sans toucher au
   conteneur (comme le bloc d'intro de cette même page, ci-dessus). */
.section-heading:has(#approche-entreprise-titre) {
  max-width: none;
}
.section-heading:has(#approche-entreprise-titre) .section-heading__lead {
  max-width: var(--measure);
  margin-inline: auto;
}

/* Section « Une analyse préalable… » sur les 3 pages de pôle (2026-09-08,
   retour utilisateur) — même famille que les blocs ci-dessus. 3 ID propres à
   chaque page (D78 pour construction-renovation, ce correctif pour les 2
   autres), PAS #methode-titre (partagé entre les 3, P27). Même texte de
   titre partout → même mesure (857px sur une ligne, tient dans les 1100px de
   .container par défaut sur les 3 pages, aucun élargissement nécessaire).
   Chapôs de longueur différente selon la page (1 phrase courte sur
   operations-complexes, 1 phrase sur construction-renovation ×2, 2 phrases
   plus longues sur performance-organisation) : tous visés par le même
   sélecteur .section-heading__lead ci-dessous, chacun s'enroule sur le
   nombre de lignes que sa propre longueur impose une fois centré et
   re-plafonné — pas de traitement uniforme forcé au-delà de ça. */
.section-heading:has(#construction-methode-titre),
.section-heading:has(#operations-methode-titre),
.section-heading:has(#performance-methode-titre) {
  max-width: none;
}
.section-heading:has(#construction-methode-titre) .section-heading__lead,
.section-heading:has(#operations-methode-titre) .section-heading__lead,
.section-heading:has(#performance-methode-titre) .section-heading__lead {
  max-width: var(--measure);
  margin-inline: auto;
}

.statement__accent { color: var(--accent); }

/* ⚠️ Ne PAS fusionner dans la règle ci-dessus : .statement__accent sert
   aussi à des continuations d'une seule phrase (/performance-organisation/),
   qu'un display:block global couperait en deux. Ce modificateur ne vise que
   les statements de DEUX PHRASES COMPLÈTES (le manifeste), où la coupure
   doit tenir à toutes les largeurs — text-wrap:balance ne force jamais un
   retour, il n'équilibre que des lignes déjà produites (constaté à 1920px,
   où les 34ch suffisaient à tout loger sur une ligne). */
.statement--stacked .statement__accent {
  display: block;
}

/* ⛔ En thème clair, l'or ne peut pas porter du texte (1,84:1).
   L'accent y passe par la graisse plutôt que par la couleur. */
[data-theme="light"] .statement__accent {
  color: inherit;
  font-weight: var(--weight-bold);
}

/* ==========================================================================
   Pastille
   Mention courte encadrée. Or en APLAT DE FOND, texte noir dessus : 11,4:1
   dans les deux thèmes, sans exception ni variante conditionnelle. C'est la
   seule façon d'afficher de l'or lisiblement en thème clair — l'or en
   couleur de texte y tombe à 1,84:1 (piège P5, tokens.css).
   ========================================================================== */

.badge {
  display: inline-block;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-semi);
  letter-spacing: var(--tracking-wide);
  line-height: var(--leading-snug);
  text-transform: uppercase;
}

.badge--accent {
  background: var(--accent);
  color: var(--accent-ink);
}

/* Bandeau d'objectif — encadré discret, sans fond coloré */
.callout {
  padding: var(--space-5) var(--space-6);
  border-left: 3px solid var(--accent);
  background: var(--surface);
}

.callout__label {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* ==========================================================================
   Carte « zone d'intervention » (/contact/)
   Tracé géographique repris de la référence client (2026-08-10, demande
   explicite) — les coordonnées des régions et départements sont un fait
   géographique, pas une création de la référence, donc reprises telles
   quelles. En revanche, aucune classe ni règle CSS n'est copiée (Loi 13) :
   noms et styles ci-dessous sont propres à ce site, sur les mêmes jetons
   (--gold/--surface/--line/--muted) que le reste du design system, pas des
   couleurs recopiées. Les 12 tracés de départements ne servent qu'au survol
   (aucune interaction tactile n'en dépend) : dégradé au clavier via <title>
   sur chaque tracé, région et département confondus.
   ========================================================================== */

.zone-map {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: var(--space-8);
  align-items: center;
}

@media (max-width: 48rem) {
  .zone-map { grid-template-columns: 1fr; gap: var(--space-6); }
}

/* 34rem depuis le 2026-08-24 (28rem auparavant) : +21 % de tracé, soit 544px
   rendus au lieu de 448px à la largeur maximale du conteneur. La piste de
   gauche mesure ~596px à 1180px de conteneur — la carte y tient donc sans
   toucher la colonne de légende. */
.zone-map__svg {
  width: 100%;
  max-width: 34rem;
  height: auto;
  display: block;
  margin-inline: auto;
}

.zone-map__region {
  fill: var(--surface);
  stroke: var(--line);
  stroke-width: .9;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  transition: fill var(--motion-base) var(--ease);
}

.zone-map__region:hover { fill: var(--surface-hover); }

.zone-map__aura {
  fill: var(--accent-soft);
  stroke: var(--gold);
  stroke-width: 2.2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 10px rgb(225 186 102 / .45));
}

.zone-map__departement {
  /* transparent, pas none : un fill:none ne compte pas comme « peint » pour
     le hit-test SVG (pointer-events:visiblePainted par défaut) — seul le
     contour de .8px aurait alors capté le survol, une cible quasi
     impossible à atteindre à la souris. Trouvé en testant le survol, pas à
     la lecture du code : la référence avait la même valeur. */
  fill: transparent;
  stroke: var(--gold);
  stroke-width: .8;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  opacity: .55;
  transition: fill var(--motion-base) var(--ease), opacity var(--motion-base) var(--ease);
}

.zone-map__departement:hover { fill: var(--accent-line); opacity: 1; }

.zone-map__side {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.zone-map__legend {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}

.zone-map__key {
  width: 1rem;
  height: 1rem;
  flex: 0 0 1rem;
  margin-top: .2rem;
  border-radius: 2px;
}

.zone-map__key--aura { background: var(--accent-soft); border: 2px solid var(--gold); }
.zone-map__key--fr { background: var(--surface); border: 1px solid var(--line); }

.zone-map__legend strong {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-semi);
  color: var(--fg);
}

.zone-map__legend em {
  display: block;
  font-style: normal;
  font-size: var(--text-xs);
  color: var(--muted);
  margin-top: var(--space-1);
  line-height: 1.5;
}

.zone-map__note {
  font-size: var(--text-sm);
  color: var(--muted);
  border-top: 1px solid var(--line);
  padding-top: var(--space-5);
}

/* .zone-map__side est un flex en colonne : sans align-self, un enfant
   inline-block est étiré sur toute la largeur de la colonne et la pastille
   devient une bannière. */
.zone-map__side .badge { align-self: flex-start; }

@media (prefers-reduced-motion: reduce) {
  .zone-map__region, .zone-map__departement { transition: none; }
}

/* ==========================================================================
   Carte de prestation avec visuel
   ⚠️ Les visuels sont libres de droits, PAS des réalisations du client.
   Aucun libellé de cette carte ne doit laisser croire le contraire.
   ========================================================================== */

.service-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color var(--motion-base) var(--ease);
}

.service-card:hover { border-color: var(--accent-line); }

.service-card__media {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--surface);
}

.service-card__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--motion-slow) var(--ease);
}

.service-card:hover .service-card__image { transform: scale(1.03); }

@media (prefers-reduced-motion: reduce) {
  .service-card:hover .service-card__image { transform: none; }
}

.service-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-5);
}

/* Index de progression (01/02/03) : « Trois niveaux » décrit une gradation
   que rien ne matérialisait — les cartes se lisaient comme trois options
   parallèles. Optionnel : les cartes sans index sont inchangées. */
.service-card__step {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
}

/* ⛔ Thème clair : l'or ne porte jamais de texte (1,84:1 — piège P5). */
[data-theme="light"] .service-card__step { color: var(--muted-strong); }

.service-card__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
}

/* ==========================================================================
   Carte
   Profondeur par filet et surface, jamais par ombre portée.
   ========================================================================== */

/* var(--space-5), pas --space-4 (2026-09-08, retour utilisateur : plus
   d'air titre → description, audité — 16px, le plus serré relevé du site).
   .card n'a que 2 enfants dans tous ses usages (.card__title, .card__text) :
   ce gap ne pilote QUE cette relation, aucun effet de bord sur un 3ᵉ
   élément. */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color var(--motion-base) var(--ease),
              background var(--motion-base) var(--ease);
}

.card:hover {
  background: var(--surface-hover);
  border-color: var(--accent-line);
}

.card__title {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
}

.card__text {
  color: var(--muted);
  font-size: var(--text-sm);
}

/* Maillage de fin de page — détaché du bloc CTA par un filet, pour que la
   bascule « CTA centré → liens alignés à gauche » se lise comme un choix. */
.related {
  display: grid;
  gap: var(--space-6);
  padding-top: var(--space-7);
  border-top: 1px solid var(--line);
}

.related__label {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* Toute la carte devient cliquable sans imbriquer de liens : le pseudo-élément
   du lien de titre couvre la carte. Le texte reste sélectionnable. */
.card--linked { position: relative; }

.card--linked .card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

.card__link { text-decoration: none; }

/* ==========================================================================
   Assistant de qualification
   Widget flottant, présent sur les 13 pages. Construit entièrement par JS
   (assistant.js) — comme la bannière de consentement, il n'existe pas sans
   JavaScript, donc aucun bouton mort n'apparaît si le script échoue.
   Le lanceur ne devient visible qu'après une décision de consentement, pour
   ne jamais se superposer à la bannière de cookies (voir assistant.js).
   ========================================================================== */

/* Mis en avant le 2026-08-24 : filet d'or au lieu du filet neutre, et un halo
   diffus de la même teinte. Volontairement retenu — le fond reste --bg, le
   libellé garde son contraste plein, et l'or n'entre QUE par le contour et
   l'ombre. Un aplat d'or aurait fait un bouton publicitaire posé sur toutes
   les pages, exactement le registre que ce site refuse. */
.assistant-launch {
  position: fixed;
  right: var(--space-5);
  bottom: var(--space-5);
  z-index: var(--z-float);
  display: none;   /* révélé via .is-visible, jamais par défaut */
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-6) var(--space-3) var(--space-5);
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-soft), 0 0 0 4px var(--accent-glow);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease),
              box-shadow var(--motion-fast) var(--ease),
              transform var(--motion-fast) var(--ease);
}

.assistant-launch.is-visible { display: inline-flex; }

.assistant-launch:hover,
.assistant-launch:focus-visible {
  border-color: var(--accent);
  box-shadow: var(--shadow-soft), 0 0 0 6px var(--accent-glow-strong);
}

.assistant-launch:active { transform: scale(.97); }

/* Pastille — battement lent : 3,2s, opacité de 1 à .45, jamais jusqu'à 0.
   Elle ne doit pas clignoter (un clignotement rapide est à la fois agressif
   et un risque avéré pour les personnes photosensibles ; WCAG 2.3.1 fixe la
   limite à 3 flashs par seconde, on est ici à moins d'un tiers de battement
   par seconde). Une seule pastille sur la page entière, donc aucun effet de
   guirlande. */
.assistant-launch__dot {
  position: relative;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
  animation: assistant-pulse 3.2s var(--ease) infinite;
}

/* Onde qui s'ouvre autour de la pastille, sur le même cycle.
   ⚠️ opacity:0 déclaré EN DUR, pas seulement dans les keyframes : en
   mouvement réduit, motion.css force animation-duration à .01ms sans
   fill-mode — l'élément revient alors à son état CSS de repos. Sans ce 0,
   l'anneau resterait figé, opaque, comme une cible autour du point. */
.assistant-launch__dot::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  border: 1px solid var(--accent);
  opacity: 0;
  animation: assistant-halo 3.2s var(--ease) infinite;
}

@keyframes assistant-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}

@keyframes assistant-halo {
  0%        { opacity: .55; transform: scale(.65); }
  70%, 100% { opacity: 0;   transform: scale(1.6); }
}

/* Ceinture et bretelles : motion.css neutralise déjà toute animation en
   mouvement réduit, on le dit ici explicitement pour que la règle survive à
   un futur remaniement de motion.css. */
@media (prefers-reduced-motion: reduce) {
  .assistant-launch__dot,
  .assistant-launch__dot::after { animation: none; }
}

/* Masqué sous le seuil où le libellé et le bouton risqueraient de déborder
   d'un petit écran : le point seul suffit à signaler la présence du widget. */
@media (max-width: 26rem) {
  .assistant-launch__label { display: none; }
  .assistant-launch { padding: var(--space-3); }
}

/* Le lanceur se cache pendant que le panneau est ouvert, pour ne pas rester
   visible derrière (ou à côté) du panneau plein écran sur mobile.
   :has() cible le frère SUIVANT dans le DOM — launch est inséré avant panel
   (document.body.append(launch, panel) dans assistant.js), donc seule cette
   direction fonctionne ; un ~ direct dans l'autre sens ne matcherait jamais. */
.assistant-launch:has(~ .assistant-panel.is-open) { display: none; }

/* ---- Panneau ------------------------------------------------------------- */

.assistant-panel {
  position: fixed;
  z-index: var(--z-modal);
  inset: auto var(--space-5) var(--space-5) auto;
  display: flex;
  flex-direction: column;
  width: min(24rem, calc(100vw - 2 * var(--space-5)));
  max-height: min(38rem, calc(100vh - 2 * var(--space-5)));
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  opacity: 0;
  transform: translateY(12px) scale(.98);
  transform-origin: bottom right;
  transition: opacity var(--motion-base) var(--ease),
              transform var(--motion-base) var(--ease);
  overflow: hidden;
}

.assistant-panel[hidden] { display: none; }
.assistant-panel.is-open { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .assistant-panel { transition: opacity var(--motion-fast) linear; transform: none; }
}

/* Plein écran sur mobile : la même logique que le panneau de navigation. */
@media (max-width: 30rem) {
  .assistant-panel {
    inset: 0;
    width: 100%;
    max-height: 100%;
    border-radius: 0;
    border: 0;
  }
}

.assistant-panel__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}

.assistant-panel__title {
  font-family: var(--font-display);
  font-weight: var(--weight-semi);
}

.assistant-panel__sub {
  margin-top: var(--space-1);
  color: var(--muted);
  font-size: var(--text-xs);
}

.assistant-panel__close {
  flex-shrink: 0;
  display: grid;
  place-content: center;
  width: 2rem;
  height: 2rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-size: var(--text-sm);
}
.assistant-panel__close:hover { border-color: var(--accent-line); }

.assistant-panel__progress {
  padding: 0 var(--space-5);
  padding-top: var(--space-4);
  flex-shrink: 0;
}
.assistant-panel__progress[hidden] { display: none; }

/* ---- Journal de conversation ---------------------------------------------- */

.assistant-log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.assistant-msg {
  max-width: 88%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
}

.assistant-msg--bot {
  align-self: flex-start;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-bottom-left-radius: var(--radius-sm);
}

.assistant-msg--moi {
  align-self: flex-end;
  background: var(--accent);
  color: var(--accent-ink);
  border-bottom-right-radius: var(--radius-sm);
}

.assistant-typing {
  align-self: flex-start;
  display: flex;
  gap: 4px;
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  border-bottom-left-radius: var(--radius-sm);
}
.assistant-typing span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted);
  animation: assistant-pulse 1.1s ease-in-out infinite;
}
.assistant-typing span:nth-child(2) { animation-delay: .15s; }
.assistant-typing span:nth-child(3) { animation-delay: .3s; }

@keyframes assistant-pulse {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-2px); }
}

@media (prefers-reduced-motion: reduce) {
  .assistant-typing span { animation: none; opacity: .6; }
}

/* Carte de recommandation — même famille visuelle que .card, en plus compact. */
.assistant-card {
  align-self: stretch;
  max-width: 100%;
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);
}
.assistant-card h4 {
  margin-top: var(--space-1);
  font-family: var(--font-display);
  font-size: var(--text-base);
}
.assistant-card p { margin-top: var(--space-2); color: var(--muted); }
.assistant-card__eyebrow {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--accent);
}
.assistant-card__segment { font-size: var(--text-xs); font-style: italic; }

/* ---- Zone de saisie -------------------------------------------------------- */

.assistant-input {
  flex-shrink: 0;
  padding: var(--space-4) var(--space-5) var(--space-5);
  border-top: 1px solid var(--line-soft);
}

.assistant-options {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.assistant-opt {
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-size: var(--text-sm);
  text-align: left;
  transition: border-color var(--motion-fast) var(--ease),
              background var(--motion-fast) var(--ease);
}
.assistant-opt:hover { background: var(--surface-hover); border-color: var(--accent-line); }

.assistant-field {
  display: flex;
  gap: var(--space-2);
}
.assistant-field input {
  flex: 1;
  min-width: 0;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-size: var(--text-base);   /* jamais < 16px : évite le zoom forcé iOS */
}
.assistant-field input:focus-visible { border-color: var(--focus); }
.assistant-field button {
  flex-shrink: 0;
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  padding-inline: var(--space-4);
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  border-radius: var(--radius);
  font-family: var(--font-display);
  font-weight: var(--weight-medium);
}

.assistant-skip {
  display: block;
  margin-top: var(--space-3);
  padding: var(--space-2) 0;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: var(--text-xs);
  text-decoration: underline;
  text-underline-offset: .2em;
}
.assistant-skip:hover { color: var(--fg); }

.assistant-final {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.assistant-final .button { width: 100%; }

.assistant-status {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border-left: 3px solid var(--success);
  font-size: var(--text-xs);
}
.assistant-status[data-state="error"] { border-left-color: var(--error); }
.assistant-status[hidden] { display: none; }

/* ==========================================================================
   Fiches d'offre — diagnostics payants (DTFC / DTFR)
   Utilisées sur /diagnostic/. Même famille visuelle que .service-card,
   avec un bloc prix/achat dédié.
   ========================================================================== */

.offer-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-7);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}

.offer-card__code {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--accent);
}

/* --text-xl, pas --text-lg comme les autres titres de carte du site
   (.service-card__title, .card__title, .approach-grid__txt) : exception
   délibérée pour CE composant. DTFC/DTFR sont les deux seules offres
   commercialisées du site, portées par une seule grille de deux — le
   traitement éditorial appuyé (retour utilisateur du 2026-09-08, avec
   référence visuelle) se justifie ici précisément parce que rien d'autre
   sur la page n'a à rivaliser avec ce titre en poids visuel. */
.offer-card h3 {
  font-size: var(--text-xl);
  line-height: 1.15;
}

.offer-card__for {
  color: var(--muted);
  font-size: var(--text-base);
}

/* Puces en tiret (marqueur natif de liste, pas un ::before) : ::marker reste
   correctement traité par les lecteurs d'écran comme un marqueur de liste,
   contrairement à un pseudo-élément de contenu décoratif ajouté à la main.
   Chaque item se sépare du suivant par un filet, pas par un espacement seul
   — registre « ticket », qui donne au contenu la densité d'une fiche
   technique plutôt que d'une liste à puces générique. */
.offer-card__list {
  display: flex;
  flex-direction: column;
  font-size: var(--text-sm);
}
.offer-card__list li {
  list-style-type: "– ";
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--line-soft);
}
.offer-card__list li::marker { color: var(--muted); }
.offer-card__list li:last-child { border-bottom: none; }

/* margin-top: auto ICI, pas sur .offer-card__buy en dessous : DTFC et DTFR
   ont un texte de liste de longueur différente, donc un nombre de lignes
   different selon la largeur — .offer-card__list n'a AUCUNE raison de finir
   à la même hauteur d'une carte à l'autre. .offer-card__meta et
   .offer-card__buy, eux, portent un texte IDENTIQUE sur les deux offres
   (« 60 min, visio ou sur site », « Réserver »…) : ancrer le premier des deux
   au bas de la carte (grille = même hauteur totale sur DTFC/DTFR, Loi du
   stretch par défaut) aligne le bloc Échange/Livraison/Prix/Bouton entier au
   pixel près, quel que soit l'enroulement du titre ou de la liste au-dessus. */
.offer-card__meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
  margin-top: auto;
  font-size: var(--text-xs);
}
/* Chaque mesure dans son propre filet plutôt qu'une ligne de séparation
   unique au-dessus du duo : la carte tout entière ne porte de profondeur
   QUE par des filets (D15, jamais d'ombre) — deux petites boîtes lisibles
   d'un coup d'œil, à l'image de la référence transmise le 2026-09-08. */
.offer-card__meta > div {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.offer-card__meta span {
  display: block;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}
.offer-card__meta strong { display: block; margin-top: var(--space-1); font-weight: var(--weight-medium); }

.offer-card__buy {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line-soft);
}
/* flex-shrink par défaut vaut 1 sur TOUT flex-item, bouton compris : sans ce
   correctif, le bouton rétrécit au même titre que .offer-card__price et son
   propre libellé s'enroule sur deux lignes (mesuré : 77px de haut au lieu de
   ~48px). Scopé ici plutôt que sur .button globalement — un bouton plus long
   ailleurs sur le site (« Envoyer les détails de mon projet », order.js) DOIT
   pouvoir continuer à s'enrouler plutôt que de déborder sur mobile. */
.offer-card__buy .button {
  flex-shrink: 0;
  white-space: nowrap;
}

.offer-card__price {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semi);
}
/* --text-sm, pas --text-xs : depuis le retrait du prix (2026-09-07), c'est
   TOUT le contenu visible de .offer-card__price (aucun texte ne reste dans
   le parent) — la taille du parent ne s'applique donc plus nulle part tant
   qu'aucun prix ne revient. Une seule ligne de texte a besoin de rester
   lisible par elle-même, pas d'imiter la sous-légende d'un prix affiché. */
.offer-card__price em {
  display: block;
  font-style: normal;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--muted);
}

/* ---- Processus en 4 étapes ------------------------------------------------
   Même famille visuelle que .card / .offer-card : profondeur par filet et
   surface, jamais par ombre portée. Index numéroté = .service-card__step. */

.step-grid {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(2, 1fr);
}

@media (max-width: 48rem) {
  .step-grid { grid-template-columns: 1fr; }
}

.step-grid__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color var(--motion-base) var(--ease),
              background var(--motion-base) var(--ease);
}

.step-grid__item:hover {
  background: var(--surface-hover);
  border-color: var(--accent-line);
}

.step-grid__index {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
}

/* ⛔ Thème clair : l'or ne porte jamais de texte (1,84:1 — piège P5). */
[data-theme="light"] .step-grid__index { color: var(--muted-strong); }

.step-grid__item h4 {
  font-size: var(--text-base);
}
.step-grid__item p:last-child {
  color: var(--muted);
  font-size: var(--text-sm);
}

/* ==========================================================================
   Overlay de commande — modal centrée (contrairement à l'assistant, ancré
   en coin) : une commande mérite l'attention complète, pas un widget
   périphérique. Même rigueur d'accessibilité : role=dialog, focus piégé,
   Échap, restitution du focus à l'élément déclencheur.
   ========================================================================== */

.order-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: var(--space-5);
  background: rgb(0 0 0 / .6);
  overflow-y: auto;
  opacity: 0;
  transition: opacity var(--motion-base) var(--ease);
}
.order-overlay[hidden] { display: none; }
.order-overlay.is-open { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .order-overlay { transition: none; }
}

.order-panel {
  width: min(36rem, 100%);
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  overflow: hidden;
}

.order-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5);
  border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.order-code {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--accent);
}
.order-titre { font-family: var(--font-display); font-weight: var(--weight-semi); margin-top: var(--space-1); }
.order-close {
  flex-shrink: 0;
  display: grid;
  place-content: center;
  width: 2rem;
  height: 2rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
}
.order-close:hover { border-color: var(--accent-line); }

.order-steps {
  display: flex;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5) 0;
  flex-shrink: 0;
  font-size: var(--text-xs);
}
.order-steps__item { color: var(--muted); }
.order-steps__item.is-on { color: var(--fg); font-weight: var(--weight-medium); }

.order-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-5);
}
.order-body h3 { font-size: var(--text-base); margin-bottom: var(--space-4); }

/* Bascule visio/sur site — pilote quel calLink Cal.com est monté dans
   .order-calcom (voir calLinkPourMode() dans order.js), pas un état
   purement visuel. */
.order-mode {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
}
.order-toggle {
  min-height: var(--tap-min);
  padding: var(--space-2) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-size: var(--text-sm);
  transition: border-color var(--motion-fast) var(--ease), background var(--motion-fast) var(--ease);
}
.order-toggle:hover { border-color: var(--accent-line); }
.order-toggle.is-on {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
}

/* Cal.com monte son propre iframe DANS ce conteneur (services/calcom.js).
   Hauteur minimale explicite : sans elle, l'embed peut se retrouver
   compressé à 0 px avant que son propre contenu ne se mesure — recommandé
   par Cal.com elle-même pour l'embed « inline ». */
.order-calcom {
  min-height: 30rem;
  margin-bottom: var(--space-5);
}
.order-calcom__repli {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: var(--text-sm);
}

.order-legal {
  margin-top: var(--space-4);
  color: var(--muted);
  font-size: var(--text-xs);
}

.order-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-6);
}
.order-actions--stack { flex-wrap: wrap; }

.order-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}
.order-field { display: grid; gap: var(--space-2); }
.order-field--full { grid-column: 1 / -1; }
.order-field label {
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
}
.order-field input, .order-field select, .order-field textarea {
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--fg);
  font-size: var(--text-base);   /* jamais < 16px : évite le zoom forcé iOS */
}
.order-field input:focus-visible, .order-field select:focus-visible, .order-field textarea:focus-visible {
  border-color: var(--focus);
}

/* Même piège que .form__input (P23, PROJECT-MAP.md) : le popup natif d'un
   <select> est dessiné par le navigateur sur un fond opaque, pas sur --surface
   (translucide) de la page — d'où des options blanc sur quasi-blanc. */
.order-field option {
  background: var(--bg);
  color: var(--fg);
}
.order-field option:hover,
.order-field option:checked,
.order-field option:focus {
  background: var(--accent);
  color: var(--accent-ink);
}

@media (max-width: 30rem) {
  .order-form { grid-template-columns: 1fr; }
  .order-field--full { grid-column: 1; }
}

.order-erreur {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border-left: 3px solid var(--error);
  font-size: var(--text-sm);
}
.order-erreur[hidden] { display: none; }

.order-sum { display: grid; gap: var(--space-3); }
.order-sum__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--text-sm);
}
.order-sum__row span { color: var(--muted); }
.order-sum__total {
  display: flex;
  justify-content: space-between;
  padding-top: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
}

.order-status {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border-left: 3px solid var(--success);
  font-size: var(--text-xs);
}
.order-status[data-state="error"] { border-left-color: var(--error); }
.order-status[hidden] { display: none; }


/* ==========================================================================
   Pré-loader — UNIQUEMENT sur l'accueil. Retiré par preloader.js une fois
   la page chargée (filet de sécurité sans JS : voir header.html).
   ========================================================================== */

.preloader {
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  background: var(--bg);
  opacity: 1;
  transition: opacity var(--motion-slow) var(--ease);
}

/* Posé par preloader.js après [data-preloader-bar].is-loaded, pour laisser
   la barre finir sa course avant le fondu. pointer-events coupé tout de
   suite : la page redevient cliquable dès le début du fondu. */
.preloader.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.preloader__mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
}

/* ⚠️ Plaque claire, arbitrage du 2026-08-25 : le logo source est mesuré à
   89,5 % de noir/gris très sombre, invisible sur --bg noir sans elle.
   Couleurs du logo intactes (aucun filtre) — c'est la plaque qui porte la
   lisibilité, pas une recoloration. --shadow-soft : déjà le jeton des
   calques flottants (menus, modales), un pré-loader plein écran en est un. */
.preloader__plate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: var(--white);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-soft);
  animation: preloader-reveal .8s var(--ease) both;
  animation-delay: 80ms;
  /* transform seul (pas de scale) : évite au navigateur de redessiner
     l'ombre/les coins arrondis à chaque frame pendant la réapparition —
     c'était ce ré-échantillonnage répété du flou (box-shadow) à taille
     changeante qui produisait un premier tremblement. Un translateY+opacity
     pur se compose sans jamais re-rastériser.
     ⚠️ PAS de `will-change` ici (retiré le 2026-09-10, quelques heures après
     l'avoir ajouté) : mesuré sur l'enregistrement du client, image par
     image — une fois posé, il isolait la plaque sur son propre calque de
     composition pendant toute la durée du reveal du wordmark voisin
     (`.preloader__wordmark`, `letter-spacing` animé juste à côté, jusqu'à
     1380ms). Chaque repeint du wordmark forçait une recomposition du calque
     de la plaque, qui se rééchantillonnait alors sur deux positions
     sous-pixel différentes en alternance (mesuré : centre du dessin du
     logo oscillant de ~0,3 px, `dcx`/`dcy` non nuls et non monotones dans
     une fenêtre pourtant post-animation) — un tremblement réel mais
     invisible à `getBoundingClientRect()` (qui ne lit que la géométrie de
     layout, jamais le rendu du compositeur). Sans `will-change`, l'élément
     ne reste pas sur un calque isolé une fois son animation courte
     terminée : rien à re-échantillonner pendant que le voisin continue de
     peindre. */
}

.preloader__logo {
  width: clamp(4.5rem, 3rem + 7vw, 7rem);   /* 72→112px : élément central, pas un repère */
  height: auto;
  display: block;
}

/* Révélation propre, décalée après la plaque (80ms → 380ms) : un
   enchaînement à deux temps, registre « révélation de marque ».
   ⚠️ Simplifié le 2026-09-10 : la version précédente animait `letter-
   spacing` + `text-shadow` (tracking resserré→ouvert + halo doré) —
   confirmé par le client via le « paint flashing » de Chrome DevTools que
   CE mot repeint en continu pendant toute son animation (normal, ces deux
   propriétés ne se composent pas sur le GPU), alors que `.preloader__plate`
   juste au-dessus, elle, ne repeint JAMAIS et pourtant continue de
   trembler visuellement. Le repeint répété du voisin reste le seul
   suspect restant pour ce tremblement en dehors des deux causes déjà
   corrigées (voir DECISION-LOG). Remplacé par un simple fondu + montée
   (mêmes propriétés que `.preloader__plate`, purement composées, 0 repeint
   possible) et calé pour se terminer EXACTEMENT à 880ms, comme la plaque —
   aucune fenêtre où l'une bouge/repeint pendant que l'autre est déjà au
   repos. Perte assumée : plus de resserrement de tracking ni de halo. */
.preloader__wordmark {
  font-family: var(--font-display);
  font-size: clamp(1.375rem, .95rem + 2.1vw, 2.25rem);
  font-weight: var(--weight-bold);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--fg);
  animation: preloader-wordmark-reveal .5s var(--ease) both;
  animation-delay: 380ms;
}

.preloader__progress {
  width: min(11rem, 40vw);
}

/* La barre grimpe SEULE, en CSS pur, sans attendre de données de
   progression réelles (le site n'en a pas — pas de librairie de mesure de
   chargement, ce serait plus lourd que le pré-loader lui-même). Elle
   s'arrête volontairement à 88 % : jamais 100 % tant que la page n'est pas
   RÉELLEMENT prête, sinon la barre ment et le visiteur fixe un écran
   « fini » qui ne l'est pas. */
[data-preloader-bar] {
  animation: preloader-progression 1.6s var(--ease) forwards;
}

/* Posée par preloader.js au signal de chargement réel : l'animation cède
   la place à la transition déjà déclarée sur .progress__bar (width,
   var(--motion-slow)) — la barre termine sa course jusqu'à 100 % au moment
   exact où le fondu de sortie démarre, jamais avant, jamais après. */
[data-preloader-bar].is-loaded {
  animation: none;
  width: 100%;
}

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

/* Fondu + montée, identique dans l'esprit à .preloader__plate (voir plus
   haut pourquoi le tracking/halo animés ont été retirés le 2026-09-10). */
@keyframes preloader-wordmark-reveal {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@keyframes preloader-progression {
  0%   { width: 0%; }
  55%  { width: 62%; }
  100% { width: 88%; }
}

/* Mouvement réduit : couvert par la règle globale de motion.css (* en
   .01ms !important) — état final quasi instantané, pas de règle dédiée. */
