/* ==========================================================================
   B.E.T. MARCHAL P. INGÉNIERIE — Feuille de styles principale
   Design system + composants. Aucune dépendance externe.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Jetons de design
   -------------------------------------------------------------------------- */
:root {
  /* Marque — bleus profonds */
  /* Plan le plus profond : base du héros et pied de page. Il existe pour que
     le marine cesse d'être une valeur unique et devienne une profondeur. */
  --navy-950: #030e19;
  --navy-900: #061727;
  --navy-800: #0a2540;
  --navy-700: #0f3253;
  --navy-600: #164468;
  --blue-600: #145b91;
  --blue-500: #1b74b4;
  --blue-400: #2f92d4;
  --blue-300: #7fbde8;
  --blue-050: #eaf3fa;

  /* Accent cuivre — usage parcimonieux (filets, survols, numéros) */
  --copper-600: #9c5d28;
  --copper-700: #864d1f;
  --copper-500: #b4713c;
  --copper-400: #cc8c56;
  --copper-050: #faf1e9;

  /* Neutres */
  --ink: #101f2d;
  --slate-700: #35485a;
  --slate-600: #4d6478;
  --slate-500: #6b8296;
  /* Gris de texte discret : #6b8296 ne donnait que 3,99:1 sur blanc. */
  --slate-550: #5b7387;
  --slate-400: #94a8b8;
  --slate-300: #c2d0db;
  --slate-200: #dde5ec;
  --slate-100: #eef3f7;
  --paper: #f7f9fb;
  --white: #ffffff;

  /* Sémantique */
  --bg: var(--white);
  --bg-alt: var(--paper);
  --text: var(--ink);
  --text-muted: var(--slate-600);
  --text-subtle: var(--slate-550);
  --border: var(--slate-200);
  --border-strong: var(--slate-300);
  --accent: var(--copper-500);
  /* Le cuivre clair (3,91:1) ne passe pas en texte : version foncée pour tout
     ce qui se lit, la claire restant réservée aux aplats et aux filets. */
  --accent-ink: var(--copper-600);
  --brand: var(--blue-600);
  /* Erreur de saisie. Le rouge est repris des `.alert--err` existantes pour
     que le formulaire et les pages de résultat parlent la même langue.
     #8e2f1d donne 6,4:1 sur blanc : lisible, et pas seulement « rouge » —
     un daltonien protanope distingue mal la teinte mais lit le message. */
  --erreur: #a8331d;
  --erreur-ink: #8e2f1d;
  --erreur-bg: #fdf0ee;
  --erreur-bord: #f0c4bc;

  /* Typographie */
  --font-display: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* Échelle remontée d'un cran : le texte courant ne descend pas sous 17 px et
     les libellés en capitales pas sous 14 px. L'interlettrage de ces derniers
     est resserré en conséquence — il compense la petite taille, il n'est pas
     un effet de style.

     Plancher à trois paliers, arbitré en quorum :
       --fs-sm   16 px  micro-mentions et commandes compactes UNIQUEMENT
                        (fil d'Ariane, badges, filtres, libellés de champ,
                        légendes, mention légale de pied de page)
       --fs-base 17 px  tout paragraphe porteur d'information
       --fs-md   18 px  corps courant
     Règle : aucun texte qui informe ne descend à --fs-sm. Le retour client
     était explicite — la version précédente était jugée « pattes de mouches ».
     --fs-sm valait 15 px et servait notamment aux repères du héros, aux
     descriptions de secteurs et au texte de consentement du formulaire. */
  --fs-xs: 0.875rem;
  --fs-sm: 1rem;
  --fs-base: 1.0625rem;
  --fs-md: 1.125rem;
  --fs-lg: clamp(1.1875rem, 0.4vw + 1.1rem, 1.375rem);
  /* Le chapô partageait --fs-lg avec les h4 : impossible de régler l'un sans
     déplacer l'autre. Il a désormais son propre palier, un cran au-dessus,
     pour exister comme voix intermédiaire entre le titre et le corps. */
  --fs-lead: clamp(1.25rem, 0.5vw + 1.15rem, 1.4375rem);
  --fs-xl: clamp(1.375rem, 0.65vw + 1.22rem, 1.625rem);
  --fs-2xl: clamp(1.625rem, 1.1vw + 1.35rem, 2.25rem);
  --fs-3xl: clamp(2rem, 1.7vw + 1.5rem, 2.875rem);
  --fs-4xl: clamp(2.5rem, 2.7vw + 1.75rem, 3.875rem);

  /* Rythme */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 2.5rem;
  --sp-8: 3rem;
  --sp-9: 4rem;
  --sp-10: 5rem;
  --sp-11: 6.5rem;
  --section-y: clamp(3.5rem, 7vw, 6.5rem);

  /* Structure */
  --container: 1200px;
  --container-narrow: 860px;
  --radius-sm: 3px;
  --radius: 6px;
  --radius-lg: 10px;

  --shadow-sm: 0 1px 2px rgba(16, 31, 45, 0.06), 0 1px 3px rgba(16, 31, 45, 0.05);
  --shadow-md: 0 4px 12px rgba(16, 31, 45, 0.07), 0 2px 4px rgba(16, 31, 45, 0.04);
  --shadow-lg: 0 18px 44px rgba(10, 37, 64, 0.13), 0 4px 12px rgba(10, 37, 64, 0.06);

  --header-h: 84px;
  --header-h-sticky: 66px;

  --ease: cubic-bezier(0.4, 0.02, 0.2, 1);
  --dur: 0.32s;

  /* Avancement du défilement du bandeau, 0 à 1. Défini ici pour que toute
     règle qui le consomme reste valide hors de l'en-tête ; main.js ne le
     renseigne que sur .header, où il masque cette valeur par héritage. */
  --p: 0;
}

/* --------------------------------------------------------------------------
   2. Réinitialisation et bases
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* L'attribut `hidden` doit masquer, sans condition et sans exception.
 *
 * Le navigateur applique `[hidden] { display: none }` depuis SA feuille de
 * style. Toute règle écrite ici l'emporte sur elle, quelle que soit la
 * spécificité : une feuille d'auteur passe toujours avant celle de l'agent
 * utilisateur. Il a suffi de `.tile { display: block }` pour que les dix
 * tuiles écartées par un filtre gardent l'attribut `hidden` ET restent
 * affichées — le JavaScript faisait son travail, le CSS le défaisait en
 * silence, et le visiteur voyait un filtre inerte.
 *
 * Le piège s'était déjà tendu deux fois (`.consent__detail`, `.map__activer`),
 * chaque fois rattrapé au cas par cas. Cette règle le referme partout et pour
 * de bon. C'est l'un des très rares emplois légitimes de `!important` : on ne
 * surcharge pas une intention de mise en forme, on rétablit une sémantique
 * HTML que le CSS n'a pas à contredire. */
/* `hidden="until-found"` est la seule valeur qu'il ne faut pas masquer ainsi :
   le navigateur doit pouvoir la révéler lorsqu'une recherche dans la page tombe
   dessus, ce qu'un `display: none` lui interdirait. */
[hidden]:not([hidden="until-found"]) { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h-sticky) + 1.5rem);
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  line-height: 1.68;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: 'kern' 1, 'liga' 1, 'calt' 1;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 800;
  line-height: 1.14;
  letter-spacing: -0.022em;
  color: var(--navy-800);
  margin: 0 0 var(--sp-4);
  text-wrap: balance;
}

h1 { font-size: var(--fs-4xl); letter-spacing: -0.032em; }
h2 { font-size: var(--fs-3xl); letter-spacing: -0.028em; }
h3 { font-size: var(--fs-xl); font-weight: 700; }
h4 { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -0.015em; }

p { margin: 0 0 var(--sp-4); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--brand); text-decoration: none; transition: color var(--dur) var(--ease); }
a:hover { color: var(--accent-ink); }

img, svg, video { max-width: 100%; height: auto; display: block; }

/* Le style par défaut des navigateurs applique « margin: 1em 40px » aux
   <figure>, ce qui décalerait toutes les images et tuiles de la galerie. */
figure { margin: 0; }
figcaption { margin: 0; }

ul, ol { margin: 0 0 var(--sp-4); padding-left: 1.25rem; }
li { margin-bottom: var(--sp-2); }
li:last-child { margin-bottom: 0; }

strong { font-weight: 600; color: var(--navy-800); }

hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: var(--sp-7) 0;
}

::selection { background: var(--navy-800); color: #fff; }

:focus-visible {
  outline: 2px solid var(--blue-500);
  outline-offset: 3px;
  border-radius: 2px;
}

/* --------------------------------------------------------------------------
   3. Utilitaires de mise en page
   -------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: clamp(1.15rem, 4vw, 2.5rem);
}
.container--narrow { max-width: var(--container-narrow); }

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 4.5rem); }
.section--alt { background: var(--bg-alt); }
.section--dark {
  background: var(--navy-800);
  color: rgba(255, 255, 255, 0.82);
}
.section--dark h2,
.section--dark h3,
.section--dark h4 { color: #fff; }
.section--dark .section-head__lead { color: rgba(255, 255, 255, 0.72); }

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: -100px;
  z-index: 200;
  background: var(--navy-800);
  color: #fff;
  padding: 0.7rem 1.2rem;
  border-radius: var(--radius);
  font-weight: 600;
  font-size: var(--fs-sm);
  transition: top 0.2s var(--ease);
}
.skip-link:focus { top: var(--sp-4); color: #fff; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------------------
   4. Éléments de titraille
   -------------------------------------------------------------------------- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-family: var(--font-body);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin: 0 0 var(--sp-4);
}
.eyebrow::before {
  content: '';
  width: 28px;
  height: 2px;
  background: var(--copper-500);
  flex: none;
}
.eyebrow--center { justify-content: center; }
.section--dark .eyebrow { color: var(--blue-300); }
.section--dark .eyebrow::before { background: var(--blue-300); }

.section-head { max-width: 62ch; margin-bottom: var(--sp-8); }
.section-head--center { margin-inline: auto; text-align: center; }
.section-head--center .eyebrow { justify-content: center; }
.section-head__lead {
  font-size: var(--fs-lead);
  color: var(--text-muted);
  line-height: 1.6;
  margin-bottom: 0;
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.62;
  color: var(--text-muted);
}
.section--dark .lead { color: rgba(255, 255, 255, 0.74); }

/* --------------------------------------------------------------------------
   5. Boutons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.2;
  padding: 0.95rem 1.7rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: center;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.btn svg { flex: none; transition: transform var(--dur) var(--ease); }
.btn:hover svg { transform: translateX(3px); }

.btn--primary {
  background: var(--navy-800);
  color: #fff;
  box-shadow: var(--shadow-sm);
}
.btn--primary:hover {
  background: var(--brand);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.btn--accent { background: var(--copper-600); color: #fff; }
.btn--accent:hover { background: var(--copper-700); color: #fff; transform: translateY(-2px); }

.btn--ghost {
  background: transparent;
  color: var(--navy-800);
  border-color: var(--border-strong);
}
.btn--ghost:hover {
  background: var(--navy-800);
  border-color: var(--navy-800);
  color: #fff;
  transform: translateY(-2px);
}

.btn--on-dark {
  background: rgba(255, 255, 255, 0.07);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.28);
  backdrop-filter: blur(6px);
}
.btn--on-dark:hover {
  background: #fff;
  color: var(--navy-800);
  border-color: #fff;
  transform: translateY(-2px);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
}
.btn-row--center { justify-content: center; }

/* Lien fléché discret */
.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  font-size: var(--fs-base);
  color: var(--brand);
}
.arrow-link svg { transition: transform var(--dur) var(--ease); flex: none; }
.arrow-link:hover { color: var(--accent-ink); }
.arrow-link:hover svg { transform: translateX(4px); }

/* --------------------------------------------------------------------------
   6. En-tête et navigation
   -------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------
   DEUX NATURES DE MOUVEMENT, DÉLIBÉRÉMENT SÉPARÉES

   1. LA GÉOMÉTRIE SUIT LE DOIGT.
      Hauteur du bandeau et taille du logotype sont asservies à --p, avancement
      continu du défilement renseigné par main.js à chaque image.
      La version précédente basculait une classe à 24 px puis laissait une
      transition de 320 ms faire le mouvement : mesuré, le bandeau terminait sa
      course alors que la page avait avancé de 250 px de plus. Il ne suivait pas
      le geste, il le poursuivait — le « décalage » signalé. Retard mesuré après
      correction : 0,2 px à la molette lente.
      Le cadencement n'était pas en cause (16,7 ms de médiane, aucune image
      longue) : inutile de fuir les propriétés de mise en page.

   2. IL N'Y A PLUS D'INVERSION DE COULEURS — C'EST TOUT LE SUJET.
      Le bandeau était blanc une fois posé, et son texte passait donc du blanc
      au marine pendant que le fond passait du sombre au blanc. Ces deux
      courbes se croisent nécessairement sur un gris commun : contraste relevé
      1,17:1, texte invisible. On a d'abord basculé les teintes en une image
      pour ne pas s'y attarder, puis retardé ce basculement de 90 ms pour le
      faire tomber à mi-fondu — 3,80:1 au pire, donc lisible. Mais lisible
      n'est pas beau : le passage restait un aplat de gris, et aucune durée ni
      aucun retard n'y change quoi que ce soit.
      Le bandeau est donc sombre en permanence. Le texte reste blanc du premier
      au dernier pixel, il n'y a plus rien à croiser, et le fondu du fond va de
      transparent à marine — deux états sombres, puisque le seuil de pose est à
      50 px, bien avant la fin du héros. Le creux n'est pas atténué : il
      n'existe plus.

   3. CE QUI SE FOND ENCORE, ET CE QUI NE SE FOND PAS.
      Fond, filet et ombre se fondent sur 180 ms. Le flou d'arrière-plan
      bascule d'un coup avec le fond : fondu, il resterait allumé 180 ms après
      que le fond est redevenu transparent, et l'on verrait une bande floue
      posée sur le héros au retour vers le haut de page.
   --------------------------------------------------------------------------- */
.header {
  --p: 0;
  --course: calc(var(--header-h) - var(--header-h-sticky));

  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: calc(var(--header-h) - var(--course) * var(--p));
  display: flex;
  align-items: center;
  /* Marine translucide, jamais blanc : c'est ce qui supprime l'inversion des
     teintes du texte, et avec elle le gris que le fondu traversait. */
  background: rgba(6, 23, 39, 0.88);
  backdrop-filter: saturate(160%) blur(14px);
  -webkit-backdrop-filter: saturate(160%) blur(14px);
  border-bottom: 1px solid transparent;
  /* La hauteur ne figure pas ici : elle est pilotée par --p, et une transition
     la ferait traîner derrière le défilement. Le flou non plus : il bascule
     d'un coup avec le fond. */
  transition: background-color 0.18s var(--ease),
              box-shadow 0.18s var(--ease),
              border-color 0.18s var(--ease);
}
.header.is-stuck {
  /* Sur fond sombre, le filet se fait avec de la lumière et non de l'encre. */
  border-bottom-color: rgba(255, 255, 255, 0.09);
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.3);
}

/* Variante transparente au-dessus d'un héros sombre.
   Le flou n'est plus interpolé — il bascule avec le fond — donc « none » est
   de nouveau la valeur juste, et elle a le mérite de ne créer aucune couche de
   composition tant que le bandeau est transparent.
   Mesuré : sous le fond blanc à 94 %, le flou modifie tout de même 30 % des
   octets de la bande, avec un écart maximal de 10/255 et 5 % de la surface
   au-dessus du seuil de perception. Il n'est donc pas décoratif à vide, et on
   le conserve. */
.header--overlay:not(.is-stuck) {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* Les teintes du bandeau ne dépendent plus de son état : elles sont claires,
   toujours, sur les neuf pages et à toutes les hauteurs de défilement. C'est
   la condition pour qu'il n'y ait rien à interpoler au moment de la pose. */
.header .logo,
.header .logo:hover { color: #fff; }
.header .nav__link { color: rgba(255, 255, 255, 0.86); }
.header .nav__link:hover,
.header .nav__link[aria-current='page'] { color: #fff; }
.header .nav-toggle__bar { background: #fff; }
.header .header__cta {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  border-color: rgba(255, 255, 255, 0.32);
}
.header .header__cta:hover { background: #fff; color: var(--navy-800); border-color: #fff; }

/* Plus rien à orchestrer du côté des teintes : elles ne changent pas. Le
   survol garde donc sa transition normale, sur les neuf pages.

   Un fondu de fond reste un mouvement : qui demande à en être dispensé
   retrouve la pose franche. */
@media (prefers-reduced-motion: reduce) {
  .header { transition: none; }
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  width: 100%;
}

/* Logo */
/* Logotype
   Le lettrage est peint en `currentColor` : il suffit de changer `color` sur le
   contexte pour passer du marine au blanc. Les dégradés de la vague sont
   pilotés par variables, parce que le marine du ruban bas disparaîtrait sur le
   fond du pied de page. */
.logo {
  display: inline-flex;
  align-items: center;
  flex: none;
  color: var(--navy-800);
  transition: color var(--dur) var(--ease);
}
.logo:hover { color: var(--navy-800); }
.logo {
  /* Rapport entre la taille posée et la taille de repos : 44 / 54. */
  --logo-mini: 0.8148;
}
.logo svg {
  display: block;
  height: 54px;
  width: auto;
  /* L'en-tête rétrécit au défilement : le logo suit, sinon il déborde du
     bandeau. Par mise à l'échelle et non par hauteur — une hauteur relance la
     mise en page et une nouvelle rastérisation du SVG à chaque image, quand
     une échelle se règle sur le compositeur. Les deux états extrêmes sont
     rigoureusement ceux d'avant : 54 px au repos, 44 px posé. */
  transform: scale(calc(1 - (1 - var(--logo-mini)) * var(--p)));
  transform-origin: left center;
}
/* Le survol agrandit le verrou entier, et non le SVG : ce dernier porte déjà
   la mise à l'échelle du défilement, les deux se marcheraient dessus. */
.logo--horiz { transform-origin: left center; transition: transform var(--dur) var(--ease); }
.logo--horiz:hover { transform: scale(1.025); }

.logo--empile svg {
  /* Hors de l'en-tête, --p n'existe pas : on neutralise la mise à l'échelle
     plutôt que de laisser une déclaration invalide. */
  transform: none;
  height: 124px;
  /* Le pied de page est très sombre : le ruban bas s'y effacerait. On
     éclaircit les deux dégradés de +0,081 en clarté OKLCH — un décalage
     uniforme, et non teinte par teinte : les deux vagues de la charte
     « nocturne » sont proches en clarté, et des écarts individuels les
     auraient confondues. La plus sombre passe ainsi de 1,69:1 à 2,68:1
     sur le marine du pied. */
  --logo-v1a: #5B7BFF;
  --logo-v1b: #6586FF;
  --logo-v1c: #6F92FF;
  --logo-v2a: #4058DA;
  --logo-v2b: #4963E6;
  --logo-v2c: #526FF3;
}

/* Sur les très petits écrans, le logotype et le bouton de menu se disputent
   la largeur : on resserre la signature plutôt que de mordre sur la gouttière. */
@media (max-width: 420px) {
  .logo { --logo-mini: 0.9048; }   /* 38 / 42 */
  .logo svg { height: 42px; }
  .logo--empile svg { height: 104px; }
}

/* Navigation principale */
.nav { display: flex; align-items: center; gap: clamp(0.4rem, 1.4vw, 1.5rem); }
.nav__list {
  display: flex;
  align-items: center;
  gap: clamp(0.3rem, 1.2vw, 0.6rem);
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav__list li { margin: 0; }
.nav__link {
  position: relative;
  display: block;
  padding: 0.55rem 0.7rem;
  font-size: 0.9375rem;
  font-weight: 500;
  color: var(--slate-700);
  white-space: nowrap;
  border-radius: var(--radius-sm);
  transition: color var(--dur) var(--ease);
}
.nav__link::after {
  content: '';
  position: absolute;
  left: 0.7rem;
  right: 0.7rem;
  bottom: 0.15rem;
  height: 2px;
  background: var(--copper-500);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.nav__link:hover { color: var(--navy-800); }
.nav__link:hover::after { transform: scaleX(1); }
.nav__link[aria-current='page'] { color: var(--navy-800); font-weight: 600; }
.nav__link[aria-current='page']::after { transform: scaleX(1); }

.header__cta {
  padding: 0.65rem 1.25rem;
  font-size: 0.9375rem;
  background: var(--navy-800);
  color: #fff;
  border: 1px solid var(--navy-800);
  border-radius: var(--radius);
  font-weight: 600;
  white-space: nowrap;
  transition: all var(--dur) var(--ease);
}
.header__cta:hover { background: var(--brand); border-color: var(--brand); color: #fff; }

/* Bouton menu mobile */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;
  height: 44px;
  padding: 0 9px;
  background: none;
  border: 0;
  cursor: pointer;
  /* Le panneau .nav est un descendant de l'en-tête : sans z-index supérieur,
     il recouvrirait le bouton et la croix de fermeture deviendrait inatteignable. */
  position: relative;
  z-index: 101;
}
.nav-toggle__bar {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--navy-800);
  border-radius: 2px;
  transition: transform var(--dur) var(--ease), opacity 0.2s var(--ease), background var(--dur) var(--ease);
}
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(1) { transform: translateY(7px) 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(-7px) rotate(-45deg); }

/* Menu ouvert : le panneau est blanc, la croix doit donc rester sombre —
   y compris au-dessus d'un héros foncé, où les barres sont blanches par défaut.
   Sélecteur volontairement plus spécifique que la règle « --overlay ». */
body.nav-open .header .nav-toggle__bar { background: var(--navy-800); }

.nav-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 23, 39, 0.5);
  backdrop-filter: blur(3px);
  opacity: 0;
  visibility: hidden;
  z-index: 98;
  transition: opacity var(--dur) var(--ease), visibility var(--dur) var(--ease);
}
.nav-backdrop.is-open { opacity: 1; visibility: visible; }

/* Logotype (285 px) + menu déployé (747 px) + gouttière = 1056 px de contenu
   nécessaires : le menu horizontal ne tient qu'à partir de 1136 px. En dessous
   il était rogné hors écran, sans que `overflow-x: hidden` ne le laisse voir.
   Seuil répliqué dans main.js — les deux doivent rester alignés. */
@media (max-width: 1135px) {
  .nav-toggle { display: flex; }
  .nav {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(340px, 86vw);
    z-index: 99;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding: calc(var(--header-h) + 1rem) var(--sp-5) var(--sp-6);
    background: #fff;
    box-shadow: -14px 0 44px rgba(10, 37, 64, 0.14);
    transform: translateX(102%);
    visibility: hidden;
    overflow-y: auto;
    transition: transform var(--dur) var(--ease), visibility var(--dur) var(--ease);
  }
  .nav.is-open { transform: translateX(0); visibility: visible; }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__link {
    padding: 0.95rem 0;
    font-size: 1.0625rem;
    color: var(--navy-800);
    border-bottom: 1px solid var(--slate-100);
    border-radius: 0;
  }
  .nav__link::after { display: none; }
  .nav__link[aria-current='page'] { color: var(--accent-ink); }
  .header__cta { margin-top: var(--sp-5); text-align: center; }
  /* Le panneau mobile est blanc : les liens y reprennent l'encre, quel que
     soit l'état du bandeau. Sélecteurs alignés en spécificité sur les règles
     claires de la section 6 — c'est l'ordre du fichier qui tranche. */
  .header .nav__link { color: var(--navy-800); }
  .header .nav__link:hover { color: var(--navy-800); }
  .header .nav__link[aria-current='page'] { color: var(--accent-ink); }
  .header .header__cta {
    background: var(--navy-800);
    color: #fff;
    border-color: var(--navy-800);
  }
  .header .header__cta:hover { background: var(--brand); border-color: var(--brand); color: #fff; }
}

body.nav-open { overflow: hidden; }

/* --------------------------------------------------------------------------
   7. Motif technique réutilisable (trame « plan »)
   -------------------------------------------------------------------------- */
.grid-pattern {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
  background-size: 62px 62px;
  mask-image: radial-gradient(ellipse 90% 70% at 30% 40%, #000 20%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse 90% 70% at 30% 40%, #000 20%, transparent 75%);
}

/* --------------------------------------------------------------------------
   8. Héros — page d'accueil
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  background: linear-gradient(150deg, var(--navy-950) 0%, var(--navy-800) 45%, var(--navy-700) 100%);
  color: rgba(255, 255, 255, 0.84);
  padding-top: calc(var(--header-h) + clamp(2.5rem, 6vw, 5rem));
  padding-bottom: clamp(3rem, 6vw, 5.5rem);
  overflow: hidden;
}
.hero::after {
  content: '';
  position: absolute;
  right: -14%;
  top: -28%;
  width: 62%;
  aspect-ratio: 1;
  background: radial-gradient(circle, rgba(47, 146, 212, 0.2) 0%, transparent 66%);
  pointer-events: none;
}

.hero__grid {
  position: relative;
  z-index: 2;
  display: grid;
  /* La colonne de texte est large parce que le chapô est JUSTIFIÉ. À 1.15fr
     elle mesurait 602 px, soit 46 caractères par ligne à la taille du chapô —
     une justesse trop courte pour une justification : le moteur devait étirer
     les blancs de plusieurs fois leur largeur et le bloc se creusait de
     rivières. À 1.3fr la ligne passe à environ 57 caractères et les blancs
     retrouvent une largeur presque naturelle. La photo perd 18 % de largeur ;
     c'est le prix, et le bloc de texte porte l'argument commercial. */
  grid-template-columns: 1.3fr 0.7fr;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: center;
}
/* Une piste de grille a un `min-width: auto` implicite : un contenu insécable
   élargit alors la piste au-delà du conteneur, et TOUS les frères de la grille
   héritent de cette largeur. C'est ce qui faisait déborder le héros entier de
   4 px sous 412 px de large. `overflow-x: hidden` masquait le symptôme.  */
.hero__grid > * { min-width: 0; }

/* Titre en trois lignes : l'identité, la discipline, la promesse. Chaque ligne
   est un bloc — la coupure est décidée par le contenu, plus par un
   `white-space: nowrap` qui, sous 412 px, élargissait la piste de grille et
   faisait déborder le héros entier de 4 px. Les tailles descendent en cascade
   depuis `--fs-4xl`, en `em`, pour que les trois lignes se réduisent ensemble
   sur les petits écrans. */
.hero__title {
  color: #fff;
  /* `--fs-4xl` plafonne à 2,5 rem en bas d'échelle, ce qui ne tient plus compte
     de la largeur réelle sous 445 px : à 375 px le nom et la ligne de discipline
     débordaient et le titre voulu en trois lignes en faisait cinq, « et fluides »
     restant seul. `min()` rend la main au viewport dans cette zone, sans la
     marche qu'aurait introduite une requête média. Au-dessus de 445 px les deux
     expressions se rejoignent : rien ne change sur les grands écrans. */
  font-size: min(var(--fs-4xl), 9vw);
  margin-bottom: var(--sp-5);
}
.hero__title > span { display: block; }

.hero__name {
  /* Les capitales viennent de la CSS, pas du contenu : « B.E.T. Marchal P. »
     reste copiable et correctement énoncé par un lecteur d'écran. Le crénage
     serré de `h1` (-0.032em) est fait pour du bas de casse ; sur des capitales
     il colle les lettres. */
  text-transform: uppercase;
  letter-spacing: -0.008em;
}
.hero__discipline {
  color: var(--blue-300);
  /* Taille choisie par la mesure, pas à l'estime : « Ingénierie thermique et
     fluides » doit tenir sur UNE ligne à toutes les largeurs où le héros est en
     deux colonnes, sinon le titre voulu en trois lignes en fait quatre. À
     0,62 em il ne restait que 0,7 px de marge juste au-dessus du point de
     bascule (993 px) — une police rendue autrement ailleurs suffisait à le faire
     casser. À 0,58 em la marge la plus faible mesurée est de 32 px. */
  /* Plancher : la ligne de discipline ne peut pas descendre sous le chapô
     qu'elle surplombe. À 320 px elle se replie malgré tout — aucune taille ne
     satisfait à la fois la tenue sur une ligne et ce plancher, et c'est le
     plancher qui doit l'emporter. */
  font-size: max(0.58em, 1.25rem);
  letter-spacing: -0.022em;
  margin-top: 0.16em;
}
.hero__promise {
  /* La cascade en `em` suit le titre, mais sur mobile 0,46 em tomberait sous la
     taille du chapô : la troisième ligne d'un h1 ne peut pas être plus petite
     que le texte courant. `max()` pose ce plancher. */
  font-size: max(0.42em, var(--fs-lg));
  font-weight: 600;
  letter-spacing: -0.012em;
  color: rgba(255, 255, 255, 0.74);
  margin-top: 0.3em;
}

.hero__lead {
  font-size: var(--fs-lead);
  line-height: 1.62;
  color: rgba(255, 255, 255, 0.76);
  max-width: 54ch;
  margin-bottom: var(--sp-6);
  /* Bloc justifié, SANS césure. La coupure de mots en fin de ligne a été
     essayée puis retirée : même bridée à quatre lettres avant la coupure, elle
     laissait passer des « indé-pendant » et des « équipe-ments » que le client
     juge inesthétiques — et il a raison, un trait d'union en fin de ligne
     appelle l'œil sur une rupture qui ne porte aucun sens.
     La contrepartie est un écartement des mots un peu plus libre ; il est
     mesuré et reste sous le seuil visible (voir LISEZ-MOI, § 8). */
  text-align: justify;
  -webkit-hyphens: manual;
  hyphens: manual;
}
@media (max-width: 480px) {
  /* Sous 480 px la ligne tombe à une trentaine de caractères : justifier sans
     césure y creuserait des trous entre les mots. Drapeau à droite. */
  .hero__lead { text-align: left; }
}

.hero__since {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.95rem 0.4rem 0.5rem;
  margin-bottom: var(--sp-5);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 6px;
  font-size: var(--fs-base);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.86);
  backdrop-filter: blur(8px);
}
.hero__since b {
  /* Pastille « 1968 » : blanc sur cuivre clair ne donnait que 3,91:1. */
  background: var(--copper-600);
  color: #fff;
  font-weight: 700;
  padding: 0.18rem 0.6rem;
  border-radius: 100px;
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
}

/* Panneau photo */
.hero__media {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.4);
  aspect-ratio: 3 / 2;
}
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
.hero__media::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  z-index: 3;
  background: linear-gradient(90deg, var(--copper-500), var(--copper-400));
}
.hero__media::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Deux dégradés et non un seul. Le premier, diagonal, assombrit le coin
     inférieur et donne du volume. Le second est un pied franc : la légende
     passe sur deux lignes dès que le cadre se resserre — c'est le cas depuis
     que la colonne de texte a été élargie — et la seconde ligne remontait
     alors dans la zone claire de la photo, illisible sur le béton. Le pied
     couvre 4,5 rem, soit deux lignes de légende plus sa marge basse. */
  background:
    linear-gradient(200deg, rgba(6, 23, 39, 0) 40%, rgba(6, 23, 39, 0.45) 100%),
    linear-gradient(to top, rgba(6, 23, 39, 0.82) 0, rgba(6, 23, 39, 0.5) 4.5rem,
                    rgba(6, 23, 39, 0) 8rem);
}
.hero__caption::before {
  content: '';
  display: inline-block;
  width: 10px; height: 2px;
  background: var(--copper-400);
  margin-right: 0.5rem;
  vertical-align: middle;
}
.hero__caption {
  position: absolute;
  /* La colonne de la photo s'étant resserrée, la légende passe sur deux lignes
     autour de 1280 px. `balance` répartit les mots au lieu de laisser un mot
     seul sur la seconde ligne. */
  text-wrap: balance;
  left: 1.15rem;
  /* Sans `right`, la légende collait le bord droit du cadre quand elle passait
     sur deux lignes. */
  right: 1.15rem;
  bottom: 1.15rem;
  z-index: 2;
  font-size: var(--fs-sm);
  font-weight: 500;
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
}

/* Cartouche de repères sous le héros.
   Un plan porte son cartouche : un cadre en pied de feuille où se lisent les
   informations qui identifient l'ouvrage. Les quatre chiffres y étaient posés
   en rangée flottante, séparés par du vide ; encadrés et cloisonnés de filets,
   ils se lisent comme un relevé et non comme quatre arguments épars.
   Statique par décision d'arbitrage : aucun compteur animé. Faire défiler des
   chiffres jusqu'à 1968 théâtralise une date au lieu de l'attester, et
   « RE 2020 » n'est pas une quantité. */
.hero__facts {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin-top: clamp(2.5rem, 5vw, 4rem);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.035);
  overflow: hidden;
}
.fact {
  padding: clamp(1.1rem, 2vw, 1.6rem);
  border-left: 1px solid rgba(255, 255, 255, 0.12);
}
.fact:first-child { border-left: 0; }
.fact::before {
  content: '';
  display: block;
  width: 22px; height: 2px;
  background: var(--copper-500);
  margin-bottom: 0.65rem;
}
.fact__value {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.75rem, 2.4vw, 2.35rem);
  font-weight: 800;
  color: #fff;
  line-height: 1;
  letter-spacing: -0.03em;
  margin-bottom: 0.4rem;
}
.fact__label {
  font-size: var(--fs-base);
  color: rgba(255, 255, 255, 0.62);
  line-height: 1.45;
}

/* Le héros passe en une colonne à 992 px et non à 900 px comme le reste du
   site. Raison mesurée : entre 901 et 991 px, la colonne de texte tombe à
   465-490 px et « Ingénierie thermique et fluides » n'y tient plus sur une
   ligne — le titre voulu en trois lignes en faisait quatre. Rendre la pleine
   largeur au texte dans cette bande sert aussi la composition : à 960 px, deux
   colonnes serraient déjà le titre contre la photo. */
@media (max-width: 992px) {
  .hero__grid { grid-template-columns: 1fr; }
  .hero__media { aspect-ratio: 16 / 10; }
  /* En deux colonnes, le cloisonnement change de logique : le filet gauche ne
     doit tomber que sur la colonne de droite, et la seconde rangée reçoit un
     filet haut pour que le cadre reste un quadrillage et non deux blocs collés. */
  .hero__facts { grid-template-columns: repeat(2, 1fr); }
  .fact:nth-child(odd) { border-left: 0; }
  .fact:nth-child(n + 3) { border-top: 1px solid rgba(255, 255, 255, 0.12); }
}
@media (max-width: 520px) {
  .hero__facts { grid-template-columns: 1fr; }
  .fact { border-left: 0; }
  .fact:not(:first-child) { border-top: 1px solid rgba(255, 255, 255, 0.12); }
}

/* --------------------------------------------------------------------------
   9. Héros — pages intérieures
   -------------------------------------------------------------------------- */
.page-hero {
  position: relative;
  background: linear-gradient(140deg, var(--navy-900) 0%, var(--navy-800) 55%, var(--navy-700) 100%);
  color: rgba(255, 255, 255, 0.8);
  padding-top: calc(var(--header-h) + clamp(2.5rem, 5vw, 4rem));
  padding-bottom: clamp(2.75rem, 5vw, 4rem);
  overflow: hidden;
}
.page-hero__inner { position: relative; z-index: 2; max-width: 68ch; }
.page-hero h1 { color: #fff; font-size: var(--fs-3xl); margin-bottom: var(--sp-4); }
.page-hero__lead {
  font-size: var(--fs-lead);
  color: rgba(255, 255, 255, 0.74);
  line-height: 1.6;
  margin-bottom: 0;
}

/* Fil d'Ariane */
.breadcrumb {
  position: relative;
  z-index: 2;
  margin-bottom: var(--sp-5);
  font-size: var(--fs-sm);
}
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.breadcrumb li { margin: 0; display: flex; align-items: center; gap: 0.5rem; }
.breadcrumb li + li::before {
  content: '';
  width: 12px;
  height: 1px;
  background: rgba(255, 255, 255, 0.3);
}
.breadcrumb a { color: rgba(255, 255, 255, 0.6); }
.breadcrumb a:hover { color: #fff; }
.breadcrumb [aria-current='page'] { color: rgba(255, 255, 255, 0.92); font-weight: 500; }

/* --------------------------------------------------------------------------
   10. Grilles et cartes
   -------------------------------------------------------------------------- */
.grid { display: grid; gap: var(--sp-5); }
.grid--2 { grid-template-columns: repeat(2, 1fr); }
.grid--3 { grid-template-columns: repeat(3, 1fr); }
.grid--4 { grid-template-columns: repeat(4, 1fr); }

@media (max-width: 980px) {
  .grid--3, .grid--4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .grid--2, .grid--3, .grid--4 { grid-template-columns: 1fr; }
}

/* Justification : seulement là où la justesse le permet.
   Mesuré sur le site à 1280 px, l'étirement du pire blanc d'un paragraphe :

     .cta p            72 car./ligne   x1,3   ← justifiable
     .form__note       68 car./ligne   x1,7   (écarté : deux lignes seulement,
                                              dont une seule justifiée)
     .step p           82 car./ligne   x3,5
     .section-head__lead 65 car./ligne x5,2
     .split p          59 car./ligne   x5,3
     .card p           33 car./ligne   x16,5  ← impensable

   Les cartes sont donc l'endroit où il ne faut surtout pas justifier : à
   33 caractères par ligne, sans césure, le moteur y étirerait un blanc jusqu'à
   seize fois sa largeur. C'est la justesse qui commande, pas le goût. */
.cta p:not(.cta__shortcuts) { text-align: justify; }

/* Alignement des rangées de cartes.
   Un titre sur une ligne face à des voisins sur deux décale tout ce qui suit :
   mesuré à 30 px sur la grille d'expertises de l'accueil, là où « Énergies
   renouvelables » tient sur une ligne et ses voisines sur deux.

   `subgrid` fait partager aux cartes les rangées de la grille parente : icône,
   titre et paragraphe s'alignent d'une carte à l'autre, quel que soit le nombre
   de lignes de chaque titre — donc aussi aux largeurs intermédiaires, où les
   titres passent de deux à trois lignes. Une hauteur figée n'y arriverait pas.

   `row-gap: 0` est indispensable : sans lui, la gouttière de la grille parente
   s'insérerait ENTRE l'icône, le titre et le texte de chaque carte.

   Repli pour un moteur sans `subgrid` : deux lignes réservées au titre. Moins
   fin, mais aligné dans le cas courant. */
@supports (grid-template-rows: subgrid) {
  .grid > .card {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    row-gap: 0;
  }
}
@supports not (grid-template-rows: subgrid) {
  .card h3 { min-height: 2.28em; }
}

/* Carte générique */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1.5rem, 2.4vw, 2.1rem);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.card::before {
  content: '';
  position: absolute;
  /* Soulignement cuivre en pied plutôt qu'un liseré dégradé en tête : le
     dégradé arc-en-ciel au survol est le tic visuel des bibliothèques de
     composants, et le cuivre est déjà la couleur d'accent du site. */
  left: clamp(1.5rem, 2.4vw, 2.1rem); right: clamp(1.5rem, 2.4vw, 2.1rem);
  bottom: -1px;
  height: 2px;
  background: var(--copper-500);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
/* Le relief au survol ne se justifie que si la carte est réellement
   actionnable — sinon il promet un clic qui n'arrive pas. Les cartes
   d'expertise de l'accueil sont donc devenues des liens vers la section
   correspondante d'expertises.html, et le lien couvre toute la carte.
   Le titre reste le seul texte de lien : c'est lui qu'annoncera un lecteur
   d'écran, pas le paragraphe entier. */
.card__link { color: inherit; }
.card__link::after {
  content: '';
  position: absolute;
  inset: 0;
}

.card--link:hover,
.card--link:focus-within {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: transparent;
}
.card--link:hover::before,
.card--link:focus-within::before { opacity: 1; }
.card--link:hover .card__link,
.card--link:focus-within .card__link { color: var(--brand); }

/* L'outline par défaut ne cerclerait que le texte du titre, alors que la zone
   cliquable est la carte entière. On la cadre donc en entier. */
.card--link:focus-within {
  outline: 2px solid var(--blue-500);
  outline-offset: 3px;
}
.card__link:focus-visible { outline: none; }
.card h3 { margin-bottom: var(--sp-3); }
.card p { color: var(--text-muted); font-size: var(--fs-base); margin-bottom: 0; }
.card__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: var(--sp-5);
  /* Cadre tracé plutôt qu'aplat arrondi : plus proche d'un repère de plan
     que d'une icône d'application. */
  border-radius: 4px;
  background: transparent;
  border: 1.5px solid var(--blue-300);
  color: var(--brand);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.card--link:hover .card__icon,
.card--link:focus-within .card__icon { background: var(--navy-800); border-color: var(--navy-800); color: #fff; }
.card__icon svg { width: 26px; height: 26px; }

/* Carte numérotée (prestations, méthode) */
.step {
  position: relative;
  padding: var(--sp-5) 0 var(--sp-5) clamp(3.25rem, 5vw, 4.25rem);
  border-bottom: 1px solid var(--border);
}
.step:last-child { border-bottom: 0; }
.step__num {
  position: absolute;
  left: 0;
  top: var(--sp-5);
  font-family: var(--font-display);
  /* 13 px passait sous le plancher des libellés, alors que ce numéro porte
     l'ordre de la séquence — c'est de l'information, pas un ornement.
     `tabular-nums` aligne les chiffres d'une étape à l'autre. */
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--accent-ink);
  padding-top: 0.22rem;
}
.step h3,
.step h4 { margin-bottom: var(--sp-2); font-size: var(--fs-lg); font-weight: 700; }
.step p { color: var(--text-muted); font-size: var(--fs-base); margin-bottom: 0; }

/* Listes à puces cochées */
.check-list { list-style: none; margin: 0; padding: 0; }
.check-list li {
  position: relative;
  padding-left: 1.85rem;
  margin-bottom: 0.7rem;
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: 1.58;
}
.check-list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.52em;
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--copper-500);
  transform: rotate(45deg);
}
.check-list--dark li { color: rgba(255, 255, 255, 0.76); }
.check-list--dark li::before { background: var(--blue-300); }
.check-list--cols {
  columns: 2;
  column-gap: var(--sp-7);
}
.check-list--cols li { break-inside: avoid; }
@media (max-width: 720px) { .check-list--cols { columns: 1; } }

/* Liste de définitions (fiche technique) */
.spec-list { margin: 0; }
.spec-list__row {
  display: grid;
  grid-template-columns: minmax(150px, 0.42fr) 1fr;
  gap: var(--sp-4);
  padding: 0.95rem 0;
  border-bottom: 1px solid var(--border);
}
.spec-list__row:last-child { border-bottom: 0; }
.spec-list dt {
  font-weight: 600;
  color: var(--navy-800);
  font-size: var(--fs-base);
}
.spec-list dd {
  margin: 0;
  color: var(--text-muted);
  font-size: var(--fs-base);
}
@media (max-width: 620px) {
  .spec-list__row { grid-template-columns: 1fr; gap: 0.2rem; }
}

/* --------------------------------------------------------------------------
   11. Bloc texte + image alterné
   -------------------------------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: center;
}
.split--reverse .split__media { order: -1; }
.split__media {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  aspect-ratio: 4 / 3;
}
.split__media img { width: 100%; height: 100%; object-fit: cover; }

/* Les photographies du dossier sont en majorité en format paysage, d'où le
   4/3 par défaut. Deux sont verticales : les cadrer en paysage n'en garde
   qu'une bande médiane, ce qui supprime précisément le sujet — ici, le bâtiment.
   Ce modificateur rend à l'image son rapport natif et la laisse à sa taille
   réelle plutôt que de l'étirer. */
.split__media--portrait {
  aspect-ratio: 383 / 613;
  max-width: 380px;
  margin-inline: auto;
  box-shadow: var(--shadow-md);
}
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .split--reverse .split__media { order: 0; }
}

/* Cartouche décoratif (fond bleu derrière l'image) */
.split__media--framed { box-shadow: none; }
.split__media--framed::before {
  content: '';
  position: absolute;
  inset: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  z-index: 2;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   12. Galerie de réalisations
   -------------------------------------------------------------------------- */
/* Mention de provenance, placée avant la galerie : elle conditionne la lecture
   des douze photos et n'a plus de valeur une fois qu'on les a parcourues. */
.gallery__note {
  margin: 0 0 var(--sp-5);
  font-size: var(--fs-base);
  color: var(--text-subtle);
  max-width: 70ch;
}

.gallery {
  display: grid;
  /* `min()` : sous 340 px environ, une piste rigide de 300 px déborde de la
     gouttière. La piste cède plutôt que la mise en page. */
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: var(--sp-5);
}
.tile {
  position: relative;
  display: block;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--navy-800);
  aspect-ratio: 4 / 3;
  /* Filet interne : traite la vignette comme une planche photo. */
  box-shadow: var(--shadow-sm), inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.8s var(--ease), opacity var(--dur) var(--ease);
}
.tile::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Le dégradé doit couvrir le libellé de catégorie autant que le titre :
     plusieurs photos sont très claires (sanitaires, toitures ensoleillées) et
     le libellé se plaçait dans une zone où le voile était encore trop faible. */
  /* Le renvoi vers l'expertise a rallongé la légende d'une ligne : le libellé
     de catégorie est remonté dans une zone où le voile ne le couvrait plus,
     et sur les photos claires — hall bassin, toiture ensoleillée — il devenait
     illisible. Le voile monte donc d'autant. */
  background: linear-gradient(to top,
              rgba(6, 23, 39, 0.95) 0%,
              rgba(6, 23, 39, 0.88) 30%,
              rgba(6, 23, 39, 0.60) 55%,
              rgba(6, 23, 39, 0.18) 76%,
              rgba(6, 23, 39, 0) 92%);
  transition: opacity var(--dur) var(--ease);
}
.tile:hover img { transform: scale(1.02); }
.tile__body {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  padding: clamp(1.1rem, 2vw, 1.5rem);
}
.tile__cat {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue-300);
  margin-bottom: 0.35rem;
}
.tile__title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: 1.25;
  /* La légende est ancrée en bas de la photo : un titre sur une ligne remontait
     donc la catégorie de 28 px par rapport à ses voisines. Deux lignes
     réservées alignent les trois étages de la légende sur toute une rangée. */
  min-height: 2.5em;
  text-wrap: balance;
  letter-spacing: -0.015em;
  color: #fff;
  margin: 0;
}
/* Renvoi vers la section d'expertise correspondante. La galerie était un
   cul-de-sac : une photo qui accroche ne menait nulle part. Le renvoi n'existe
   que sur la page Réalisations, où la tuile est une <figure> et non un lien —
   imbriquer un lien dans un lien serait invalide. */
.tile__expertise {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.6rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--blue-300);
}
.tile__expertise svg { flex: none; transition: transform var(--dur) var(--ease); }
.tile__expertise:hover { color: #fff; }
.tile__expertise:hover svg { transform: translateX(3px); }
.tile__expertise:focus-visible { outline: 2px solid var(--blue-300); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  .tile__expertise:hover svg { transform: none; }
}
/* La galerie reste volontairement uniforme : les tuiles conservent toutes le
   même rapport, ce qui évite les trous en fin de rangée et garde une grille
   cohérente lorsque les filtres masquent une partie des éléments. */

/* --------------------------------------------------------------------------
   13. Secteurs — liste en damier
   -------------------------------------------------------------------------- */
.sectors {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.sector {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding: clamp(1.35rem, 2.2vw, 1.85rem);
  background: #fff;
  transition: background var(--dur) var(--ease);
}
.sector:hover { background: var(--blue-050); }
.sector__icon {
  flex: none;
  width: 26px;
  height: 26px;
  color: var(--brand);
  margin-top: 2px;
}
.sector__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--fs-base);
  color: var(--navy-800);
  margin: 0 0 0.25rem;
  letter-spacing: -0.01em;
}
.sector p { font-size: var(--fs-base); color: var(--text-subtle); margin: 0; line-height: 1.5; }

/* --------------------------------------------------------------------------
   14. Qualifications / badges
   -------------------------------------------------------------------------- */
.quals { display: grid; gap: var(--sp-4); }
.qual {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* Une entrée de qualification n'est pas actionnable : elle ne réagit donc pas
   au survol. Le relief y promettait un clic qui n'arrive pas, exactement comme
   sur les cartes informatives. */
.qual__code {
  flex: none;
  display: grid;
  place-items: center;
  min-width: 62px;
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius-sm);
  background: var(--navy-800);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--fs-base);
  letter-spacing: 0.02em;
}
.qual__body h3 { margin: 0 0 0.2rem; font-size: var(--fs-base); font-weight: 600; }
.qual__body p { margin: 0; font-size: var(--fs-base); color: var(--text-subtle); }

.badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.95rem;
  border: 1px solid var(--border-strong);
  border-radius: 100px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--slate-700);
  background: #fff;
}
.badge svg { width: 15px; height: 15px; color: var(--copper-500); flex: none; }

/* Une pastille cliquable doit se signaler comme telle : les autres pastilles du
   site sont des étiquettes inertes, et rien ne distinguerait celle-ci sans
   relief au survol ni cadre au clavier. Même principe que `.card--link`. */
a.badge--link {
  text-decoration: none;
  transition: border-color 0.2s var(--ease), transform 0.2s var(--ease),
              box-shadow 0.2s var(--ease);
}
a.badge--link:hover {
  border-color: var(--copper-500);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(10, 37, 64, 0.14);
}
a.badge--link:focus-visible {
  outline: 3px solid var(--blue-500);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  a.badge--link { transition: none; }
  a.badge--link:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   14 bis. Frise chronologique
   -------------------------------------------------------------------------- */
.timeline {
  position: relative;
  margin: 0;
  padding-left: clamp(1.75rem, 3vw, 2.5rem);
  border-left: 2px solid var(--border);
}
.timeline__item { position: relative; padding-bottom: var(--sp-7); }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__item::before {
  content: '';
  position: absolute;
  left: calc(-1 * clamp(1.75rem, 3vw, 2.5rem) - 8px);
  top: 0.45rem;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #fff;
  border: 3px solid var(--copper-500);
  box-shadow: 0 0 0 4px #fff;
}
.timeline__year {
  display: block;
  font-family: var(--font-display);
  /* Une date est une information, pas un ornement : elle suit donc le palier
     des mentions et non celui des libellés en capitales. */
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--accent-ink);
  margin-bottom: 0.35rem;
}
.timeline__item h3 { font-size: var(--fs-lg); margin-bottom: var(--sp-2); }
.timeline__item p { color: var(--text-muted); font-size: var(--fs-base); margin-bottom: 0; }

/* --------------------------------------------------------------------------
   14 ter. Équipe
   -------------------------------------------------------------------------- */
.person {
  display: flex;
  gap: var(--sp-5);
  align-items: flex-start;
  padding: clamp(1.5rem, 2.4vw, 2rem);
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.person__initials {
  flex: none;
  display: grid;
  place-items: center;
  width: 62px;
  height: 62px;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--navy-700), var(--brand));
  color: #fff;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.25rem;
  letter-spacing: 0.01em;
}
.person__name {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--navy-800);
  margin: 0 0 0.15rem;
}
.person__role {
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent-ink);
  margin-bottom: var(--sp-3);
}
.person p { font-size: var(--fs-base); color: var(--text-muted); margin: 0; }
@media (max-width: 520px) { .person { flex-direction: column; gap: var(--sp-4); } }

/* --------------------------------------------------------------------------
   14 quater. Encadré remarque
   -------------------------------------------------------------------------- */
.callout {
  display: flex;
  gap: var(--sp-4);
  padding: clamp(1.35rem, 2.2vw, 1.85rem);
  background: var(--blue-050);
  border-left: 3px solid var(--brand);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.callout__icon { flex: none; width: 22px; height: 22px; color: var(--brand); margin-top: 3px; }
.callout p { font-size: var(--fs-base); color: var(--slate-700); margin: 0; }
.callout strong { color: var(--navy-800); }
.section--dark .callout {
  background: rgba(255, 255, 255, 0.06);
  border-left-color: var(--blue-300);
}
.section--dark .callout p { color: rgba(255, 255, 255, 0.8); }
.section--dark .callout strong { color: #fff; }
.section--dark .callout__icon { color: var(--blue-300); }

/* --------------------------------------------------------------------------
   14 quinquies. Barre de filtres (réalisations)
   -------------------------------------------------------------------------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-bottom: var(--sp-6);
}
.filter-btn {
  padding: 0.55rem 1.1rem;
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--slate-600);
  background: #fff;
  border: 1px solid var(--border-strong);
  border-radius: 100px;
  cursor: pointer;
  transition: all var(--dur) var(--ease);
}
.filter-btn:hover { border-color: var(--navy-800); color: var(--navy-800); }
.filter-btn[aria-pressed='true'] {
  background: var(--navy-800);
  border-color: var(--navy-800);
  color: #fff;
}

/* Ancres : décalage sous l'en-tête fixe */
[id] { scroll-margin-top: calc(var(--header-h-sticky) + 1.5rem); }

/* --------------------------------------------------------------------------
   15. Appel à l'action
   -------------------------------------------------------------------------- */
.cta {
  position: relative;
  overflow: hidden;
  background: linear-gradient(135deg, var(--navy-900), var(--navy-700));
  color: rgba(255, 255, 255, 0.82);
}
.cta__inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: 1.4fr auto;
  gap: var(--sp-7);
  align-items: center;
}
.cta h2 { color: #fff; margin-bottom: var(--sp-3); font-size: var(--fs-2xl); }
.cta p { color: rgba(255, 255, 255, 0.72); margin-bottom: 0; max-width: 56ch; }
.cta .btn-row { margin-top: 0; }

/* Raccourcis de conversion : trois entrées qui mènent au formulaire avec la
   nature du projet déjà renseignée. Discrets par construction — ils
   accompagnent l'appel principal, ils ne le concurrencent pas.
   `.cta p` impose une mesure de 56ch, juste pour un paragraphe de lecture mais
   qui repliait ici les trois liens sur deux lignes : levée explicitement. */
.cta p.cta__shortcuts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1.6rem;
  max-width: none;
  margin: var(--sp-7) 0 0;
  padding-top: var(--sp-5);
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  font-size: var(--fs-base);
}
.cta__shortcuts span { color: rgba(255, 255, 255, 0.62); }
.cta__shortcuts a {
  color: var(--blue-300);
  font-weight: 500;
  border-bottom: 1px solid rgba(127, 189, 232, 0.35);
  padding-bottom: 1px;
}
.cta__shortcuts a:hover { color: #fff; border-bottom-color: #fff; }
.cta__shortcuts a:focus-visible { outline: 2px solid var(--blue-300); outline-offset: 3px; }
@media (max-width: 820px) {
  .cta__inner { grid-template-columns: 1fr; gap: var(--sp-5); }
}

/* --------------------------------------------------------------------------
   16. Contact
   -------------------------------------------------------------------------- */
.contact-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: stretch;
}
/* Les deux colonnes n'ont pas le même contenu et finissaient jusqu'à 74 px
   l'une au-dessus de l'autre selon la largeur. L'encadré de la colonne gauche
   explique comment rédiger la demande : le pousser en pied de colonne aligne
   les deux bas de bloc et le place en regard du bouton d'envoi, ce qui est
   aussi sa place logique. */
.contact-grid > * { display: flex; flex-direction: column; }
.contact-grid .callout { margin-top: auto; }
@media (max-width: 900px) {
  .contact-grid .callout { margin-top: var(--sp-6); }
}
@media (max-width: 900px) { .contact-grid { grid-template-columns: 1fr; } }

.contact-item {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-5) 0;
  border-bottom: 1px solid var(--border);
}
.contact-item:first-of-type { padding-top: 0; }
.contact-item:last-of-type { border-bottom: 0; }
.contact-item__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--radius);
  background: var(--blue-050);
  color: var(--brand);
}
.contact-item__icon svg { width: 20px; height: 20px; }
.contact-item__label {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-subtle);
  margin-bottom: 0.3rem;
}
.contact-item__value {
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--navy-800);
  line-height: 1.5;
}
.contact-item__value a { color: var(--navy-800); }
.contact-item__value a:hover { color: var(--accent-ink); }
.contact-item__note { font-size: var(--fs-base); color: var(--text-subtle); margin-top: 0.25rem; }

/* Formulaire */
.form { display: grid; gap: var(--sp-4); }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
@media (max-width: 620px) { .form__row { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: 0.4rem; }
.field label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--navy-800);
}
.field label .req { color: var(--accent-ink); }
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.8rem 0.95rem;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.field textarea { resize: vertical; min-height: 148px; line-height: 1.6; }
.field select { appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%236b8296' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  padding-right: 2.4rem;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--blue-500);
  box-shadow: 0 0 0 3px rgba(27, 116, 180, 0.14);
}
.field input:user-invalid,
.field textarea:user-invalid { border-color: #c0492f; }

/* Piège à robots — invisible pour l'utilisateur */
.hp-field {
  position: absolute !important;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.form__consent {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: 1.5;
}
.form__consent input { margin-top: 0.22rem; flex: none; width: 16px; height: 16px; accent-color: var(--brand); }

.form__note { font-size: var(--fs-base); color: var(--text-subtle); margin: 0; }

/* Contrôle anti-robot Turnstile. Le conteneur est vide jusqu'à ce que le script
   de Cloudflare y insère son cadre : sans cette règle, la grille du formulaire
   lui réserve quand même deux gouttières, et le bloc « case à cocher / bouton »
   se décale d'un cran à l'arrivée du widget. */
.cf-turnstile:empty { display: none; }

.alert {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--radius);
  font-size: var(--fs-base);
  border: 1px solid;
}
.alert svg { flex: none; width: 20px; height: 20px; margin-top: 2px; }
.alert--ok { background: #edf7f0; border-color: #b6ddc3; color: #1c5c33; }
.alert--err { background: #fdf0ee; border-color: #f0c4bc; color: #8e2f1d; }
.alert--stack { text-align: left; }
.alert h1 { font-size: 1.3rem; line-height: 1.3; margin: 0 0 0.4rem; color: inherit; }
.alert p { margin: 0; }
.alert strong { display: block; margin-bottom: 0.15rem; }
/* Variante posée dans le formulaire : elle informe sans prendre la place
   d'une page de résultat. */
.alert--compact { padding: var(--sp-3) var(--sp-4); font-size: var(--fs-sm); }

/* --------------------------------------------------------------------------
   Formulaire — signalement des champs à compléter
   --------------------------------------------------------------------------
   Le rouge ne porte jamais l'information seul : il accompagne un message écrit
   et `aria-invalid`. Une bordure rouge sans phrase laisse deviner ce qui ne va
   pas ; sur un lecteur d'écran, elle ne dit rien du tout.
   -------------------------------------------------------------------------- */
.field.is-erreur input,
.field.is-erreur select,
.field.is-erreur textarea {
  border-color: var(--erreur);
  background: var(--erreur-bg);
}
.field.is-erreur input:focus,
.field.is-erreur select:focus,
.field.is-erreur textarea:focus {
  outline-color: var(--erreur);
}
.field.is-erreur label { color: var(--erreur-ink); }

.field__erreur {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--erreur-ink);
}

/* La case de consentement n'a pas de cadre à colorer : on lui en donne un.
   C'est le champ le plus oublié du formulaire, et le seul dont l'oubli
   renvoyait le visiteur sur une page d'erreur en lui faisant tout retaper. */
.form__consent.is-erreur {
  /* Le libellé est un conteneur flex : sans retour à la ligne autorisé, le
     message d'erreur se serrait en colonne à droite de la case, en huit lignes
     de trois mots. */
  flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-4);
  margin: 0 calc(var(--sp-4) * -1);
  border-radius: var(--radius);
  background: var(--erreur-bg);
  box-shadow: inset 3px 0 0 var(--erreur);
  color: var(--erreur-ink);
}
.form__consent.is-erreur input { accent-color: var(--erreur); outline: 2px solid var(--erreur); outline-offset: 2px; }
.form__consent.is-erreur .field__erreur { flex-basis: 100%; margin-top: 0.35rem; }

.form__notice:empty { display: none; }

/* Plan d'accès — image fixe auto-hébergée, produite par tools/staticmap.py.
   Remplace l'ancien cadre embarqué `.map-embed`, qui reposait sur MapLibre GL
   donc sur WebGL : sans WebGL le visiteur n'avait qu'un rectangle vide, et le
   cadre imposait au passage la seule requête externe du site.
   Pas de `aspect-ratio` ici : l'image porte ses propres width/height, elle ne
   peut donc ni sauter ni se déformer au chargement. */
.map {
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--slate-100);
}
.map__link {
  display: block;
  position: relative;
  transition: transform var(--dur) var(--ease);
}
.map__link img { width: 100%; display: block; }
/* Le plan est cliquable : il lui faut une affordance, et la même au clavier
   qu'à la souris. L'outline par défaut serait rognée par `overflow: hidden`,
   d'où un liseré intérieur. */
.map__link:hover { transform: scale(1.012); }
.map__link:focus-visible {
  /* `box-shadow: inset` se peint sous le contenu : l'image du plan le
     recouvrait entièrement et le focus clavier était invisible. L'outline est
     peint après les descendants ; le décalage négatif le garde à l'intérieur du
     cadre, que `overflow: hidden` aurait sinon rogné. */
  outline: 3px solid var(--blue-500);
  outline-offset: -3px;
}
.map__credit {
  padding: 0.7rem 1.1rem;
  border-top: 1px solid var(--border);
  background: var(--white);
  font-size: var(--fs-sm);
  color: var(--text-subtle);
}
.map__credit a { color: var(--text-subtle); text-decoration: underline; }
.map__credit a:hover { color: var(--accent-ink); }

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

/* --------------------------------------------------------------------------
   17. Pied de page
   -------------------------------------------------------------------------- */
.footer {
  background: var(--navy-950);
  color: rgba(255, 255, 255, 0.62);
  font-size: var(--fs-base);
  padding-top: var(--sp-10);
}
.footer__grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr 1.2fr;
  gap: var(--sp-7) var(--sp-6);
  padding-bottom: var(--sp-8);
}
@media (max-width: 900px) { .footer__grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer__grid { grid-template-columns: 1fr; } }

.footer .logo { color: #fff; }
.footer__about { margin-top: var(--sp-4); max-width: 38ch; line-height: 1.62; font-size: var(--fs-base); }

.footer__title {
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.42);
  margin: 0 0 var(--sp-4);
}
.footer__list { list-style: none; margin: 0; padding: 0; }
.footer__list li { margin-bottom: 0.6rem; }
.footer__list a, .footer address a { color: rgba(255, 255, 255, 0.72); }
.footer__list a:hover, .footer address a:hover { color: var(--blue-300); }
.footer address { font-style: normal; line-height: 1.68; }
.footer__hours { color: rgba(255, 255, 255, 0.5); }

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-5);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: var(--fs-sm);
  color: rgba(255, 255, 255, 0.44);
}
.footer__bottom nav { display: flex; flex-wrap: wrap; gap: var(--sp-5); }
.footer__bottom a { color: rgba(255, 255, 255, 0.58); }
.footer__bottom a:hover { color: #fff; }
.footer__bottom p { margin: 0; }

/* --------------------------------------------------------------------------
   18. Page 404
   -------------------------------------------------------------------------- */
.error-page {
  min-height: 78vh;
  display: grid;
  place-items: center;
  text-align: center;
  padding-top: calc(var(--header-h) + var(--sp-8));
  padding-bottom: var(--sp-9);
}
.error-page__code {
  font-family: var(--font-display);
  font-size: clamp(4.5rem, 16vw, 10rem);
  font-weight: 800;
  line-height: 0.9;
  letter-spacing: -0.05em;
  color: var(--slate-200);
  margin-bottom: var(--sp-4);
}

/* --------------------------------------------------------------------------
   19. Animations d'apparition
   -------------------------------------------------------------------------- */
/* L'état masqué n'est posé QUE si le navigateur exécute des scripts. Sans
   cette garde, un visiteur sans JavaScript — ou un chargement de main.js qui
   échoue — se retrouvait devant un site vide : c'est le script d'apparition
   qui rend le contenu visible. La requête média `scripting` évite d'avoir à
   poser une classe depuis un script en ligne, que la CSP (`script-src 'self'`)
   interdit de toute façon. Les navigateurs qui ne la connaissent pas affichent
   simplement le contenu sans animation — dégradation correcte. */
@media (scripting: enabled) {
  [data-reveal] {
    opacity: 0;
    /* 10 px plutôt que 20 : un déplacement court se lit comme un réglage
       soigné, là où une translation ample fait « modèle de page ». */
    transform: translateY(10px);
    transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  }
  /* `will-change` a été retiré, pas déplacé. Il réservait une couche de
     composition sur les 147 blocs à apparition — 45 sur le seul accueil — et
     n'était jamais relâché. Le relâcher sur `.is-visible` ne réglait rien non
     plus : cette classe est posée au DÉBUT de la transition, l'indice tombait
     donc juste avant de servir. Or `opacity` et `transform` sont déjà composées
     par le GPU sans indice : la propriété ne rendait aucun service. */
  [data-reveal].is-visible { opacity: 1; transform: none; }

  /* Mouvement signature : le tiret cuivre du surtitre se trace de gauche à
     droite, en écho au geste du dessinateur. Il repose sur la classe posée par
     l'observateur existant — aucune ligne de JavaScript supplémentaire.
     La règle collapse le tiret seulement tant que son bloc n'est pas révélé :
     la largeur par défaut reste 28 px, donc rien ne peut rester invisible. */
  .eyebrow::before { transition: width 0.5s var(--ease) 0.15s; }
  [data-reveal]:not(.is-visible) .eyebrow::before { width: 0; }
}

/* Second mouvement signature — le dévoilement de la photo du héros.
   L'image se découvre de haut en bas plutôt que d'apparaître en fondu : c'est
   le geste du calque qu'on tire sur un plan. Réservé à CETTE image, et à elle
   seule. Répété sur les douze tuiles de la galerie, il retarderait la
   perception de la preuve — c'est précisément ce que l'arbitrage a écarté.

   Deux gardes se cumulent volontairement :
     `scripting: enabled`          l'état masqué n'existe pas sans JavaScript
     `prefers-reduced-motion`      l'effet n'est pas seulement raccourci, il
                                   n'est jamais déclaré
   Cette seconde garde est un choix : réduire une durée à 0,001 ms laisserait
   un `clip-path` s'appliquer, et une image durablement rognée serait un défaut
   bien plus grave qu'une animation de trop. */
@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .hero__media[data-reveal] {
    clip-path: inset(0 0 100% 0);
    transition: opacity 0.7s var(--ease), transform 0.7s var(--ease),
                clip-path 0.85s var(--ease);
  }
  .hero__media[data-reveal].is-visible { clip-path: inset(0); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
  .eyebrow::before { width: 28px !important; }
}

/* --------------------------------------------------------------------------
   20. Contenu éditorial (mentions légales, confidentialité)
   -------------------------------------------------------------------------- */
.prose h2 {
  font-size: var(--fs-xl);
  margin-top: var(--sp-8);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border);
}
.prose > h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.prose h3 { font-size: var(--fs-lg); margin-top: var(--sp-6); }
.prose ul { color: var(--text-muted); }
.prose p { color: var(--text-muted); }
.prose address { font-style: normal; color: var(--text-muted); line-height: 1.7; }

/* Tableau éditorial — le seul du site, sur la page Confidentialité. Il énumère
   les cookies déposés : une information qui se lit par colonnes et qu'un
   paragraphe rendrait illisible. */
.table-wrap {
  overflow-x: auto;
  margin: var(--sp-5) 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  -webkit-overflow-scrolling: touch;
}

.table-donnees {
  width: 100%;
  min-width: 480px;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.table-donnees th,
.table-donnees td {
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.table-donnees tbody tr:last-child th,
.table-donnees tbody tr:last-child td { border-bottom: 0; }

.table-donnees thead th {
  background: var(--paper);
  color: var(--text);
  font-weight: 600;
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
}

.table-donnees tbody th {
  font-weight: 500;
  color: var(--text);
  width: 1%;
  white-space: nowrap;
}

.table-donnees td:last-child { white-space: nowrap; width: 1%; }

.table-donnees code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.94em;
  background: var(--slate-100);
  padding: 0.12em 0.4em;
  border-radius: var(--radius-sm);
  color: var(--navy-700);
}

/* Sous 620 px, trois colonnes ne tiennent pas : le tableau se replie en blocs,
   un par cookie. Préféré au défilement latéral, qui oblige à balayer pour lire
   trois mots. L'en-tête disparaît ; la colonne dont le sens ne va pas de soi
   reprend son intitulé par ::before. */
@media (max-width: 620px) {
  .table-wrap { overflow-x: visible; }
  .table-donnees { min-width: 0; }
  .table-donnees thead { display: none; }
  .table-donnees,
  .table-donnees tbody,
  .table-donnees tr,
  .table-donnees th,
  .table-donnees td { display: block; width: auto; }

  .table-donnees tbody tr {
    padding: var(--sp-4);
    border-bottom: 1px solid var(--border);
  }
  .table-donnees tbody tr:last-child { border-bottom: 0; }

  .table-donnees tbody th,
  .table-donnees tbody td {
    border-bottom: 0;
    padding: 0 0 var(--sp-2);
    white-space: normal;
  }
  .table-donnees tbody tr > *:last-child { padding-bottom: 0; }

  .table-donnees td[data-libelle]::before {
    content: attr(data-libelle) " : ";
    font-weight: 600;
    color: var(--text);
  }
}

/* Encadré de retrait du consentement, sur la page Confidentialité. */
.mesure-choix {
  margin: var(--sp-5) 0 var(--sp-6);
  padding: var(--sp-5);
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
}
.mesure-choix__etat {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   20 ter. Bandeau de consentement

   Contrainte de forme dictée par le droit, pas par le goût : « Tout refuser »
   et « Tout accepter » partagent EXACTEMENT la même classe, donc la même
   taille, la même graisse, le même contraste et le même rang. Rendre le refus
   moins visible que l'acceptation est le manquement qui a valu à Google
   150 M€ et à Facebook 60 M€ en janvier 2022.

   Une barre basse, et non une fenêtre modale : le visiteur doit pouvoir lire
   la page pendant qu'il décide. Rien n'est déposé tant qu'il n'a pas tranché,
   donc rien ne presse.
   -------------------------------------------------------------------------- */
.consent {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 200;
  background: var(--navy-900);
  color: rgba(255, 255, 255, 0.9);
  border-top: 2px solid var(--copper-500);
  box-shadow: 0 -8px 32px rgba(3, 14, 25, 0.28);
  transform: translateY(100%);
  transition: transform var(--dur) var(--ease);
}
.consent.is-visible { transform: none; }

.consent__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--sp-4) var(--sp-5);
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-3) var(--sp-6);
}

.consent__titre {
  margin: 0 0 0.15rem;
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--copper-400);
}
.consent__texte {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.82);
  max-width: 68ch;
}
.consent__texte strong { color: #fff; }
.consent__texte a { color: var(--blue-300); }
.consent__texte a:hover { color: #fff; }

.consent__boutons {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

/* Les deux boutons de décision. Une seule classe, aucune variante : c'est ce
   qui garantit qu'ils resteront identiques quand quelqu'un retouchera la
   feuille dans six mois. */
.btn--consent {
  background: #fff;
  color: var(--navy-800);
  border: 1px solid #fff;
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 0.6rem 1.15rem;
  white-space: nowrap;
}
.btn--consent:hover,
.btn--consent:focus-visible { background: var(--blue-050); border-color: var(--blue-050); color: var(--navy-900); }

.consent__lien {
  background: none;
  border: 0;
  padding: 0.6rem 0.2rem;
  color: var(--blue-300);
  font: inherit;
  font-size: var(--fs-sm);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.consent__lien:hover { color: #fff; }

/* `[hidden]` est traité une fois pour toutes au § 2. */
.consent__detail:not([hidden]) {
  grid-column: 1 / -1;
  border-top: 1px solid rgba(255, 255, 255, 0.14);
  padding-top: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  align-items: flex-start;
}
.consent__ligne { margin: 0; }
.consent__bascule {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.82);
  cursor: pointer;
  max-width: 78ch;
}
.consent__bascule input {
  flex: none;
  width: 18px;
  height: 18px;
  margin-top: 0.15em;
  accent-color: var(--copper-500);
  cursor: pointer;
}
.consent__bascule strong { color: #fff; }
.consent__bascule code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
  background: rgba(255, 255, 255, 0.1);
  padding: 0.1em 0.35em;
  border-radius: var(--radius-sm);
}

@media (max-width: 820px) {
  .consent__inner { grid-template-columns: 1fr; gap: var(--sp-3); padding: var(--sp-4); }
  .consent__texte { font-size: var(--fs-xs); line-height: 1.5; }
  /* La deuxième phrase est portée par la page Confidentialité : sur téléphone
     le bandeau doit rester une bande, pas un écran. */
  .consent__texte .consent__ampleur { display: none; }
  .consent__boutons { width: 100%; }
  /* Sur téléphone les deux boutons partagent la largeur à parts égales :
     l'équivalence de traitement doit tenir à toutes les tailles d'écran. */
  .consent__boutons .btn--consent { flex: 1 1 0; justify-content: center; }
  .consent__lien { flex-basis: 100%; text-align: center; padding-top: 0; }
}

/* Le lien de retrait du pied de page est un bouton — il ouvre le bandeau et
   ne mène nulle part — mais il doit se lire comme ses voisins. */
.footer__cookies {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}

/* Carte.
   OpenStreetMap a été retiré : il n'y a plus d'image de repli. Tant que la
   carte Google n'est pas acceptée — ou tant que son adresse n'est pas
   renseignée — la page affiche ce bloc d'attente, qui n'appelle personne.
   Il occupe le même rapport que la carte, donc rien ne bouge à l'insertion. */
.map__attente {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  text-align: center;
  /* `width: 100%` n'est pas décoratif : avec une largeur automatique, le
     navigateur déduit la LARGEUR du rapport et de la hauteur minimale, soit
     600 px — le bloc débordait de 229 px sur un écran de 390. La largeur est
     donc imposée, et le rapport ne sert plus qu'à fixer la hauteur. */
  width: 100%;
  aspect-ratio: 1200 / 520;
  min-height: 260px;
  padding: var(--sp-6) var(--sp-5);
  background:
    radial-gradient(120% 140% at 50% 0%, rgba(22, 68, 104, 0.55), transparent 62%),
    linear-gradient(160deg, var(--navy-900), var(--navy-800));
  color: rgba(255, 255, 255, 0.82);
}
.map__attente__icone { color: var(--copper-400); display: block; }
.map__attente__icone svg { width: 30px; height: 30px; }

.map__attente__titre {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-300);
}
.map__attente__adresse {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: 1.35;
  color: #fff;
}
.map__attente__note {
  margin: 0;
  font-size: var(--fs-xs);
  color: rgba(255, 255, 255, 0.6);
  max-width: 46ch;
}
.map__attente__note a { color: var(--blue-300); }
.map__attente__note a:hover { color: #fff; }

/* Le bouton d'appel principal du site est marine : posé sur ce bloc marine, il
   disparaissait. On lui donne le traitement des boutons du bandeau — blanc sur
   fond sombre — pour qu'il se lise comme un bouton et non comme un titre. */
.map__attente .btn--primary {
  margin-top: var(--sp-1);
  background: #fff;
  color: var(--navy-800);
  border-color: #fff;
}
.map__attente .btn--primary:hover,
.map__attente .btn--primary:focus-visible {
  background: var(--blue-050);
  border-color: var(--blue-050);
  color: var(--navy-900);
}
/* `[hidden]` est traité une fois pour toutes au § 2. */

/* Le cadre Google reprend exactement la place du bloc d'attente. */
.map__cadre {
  display: block;
  width: 100%;
  aspect-ratio: 1200 / 520;
  min-height: 260px;
  border: 0;
}

/* Sous 620 px, le rapport paysage écrase le contenu : on laisse la hauteur
   suivre le texte plutôt que de le comprimer. */
@media (max-width: 620px) {
  .map__attente { aspect-ratio: auto; padding: var(--sp-7) var(--sp-4); }
}

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

/* --------------------------------------------------------------------------
   20 bis. Utilitaires
   Placés en fin de feuille : à spécificité égale, la dernière règle l'emporte,
   ce qui garantit qu'un utilitaire prime toujours sur le style du composant.
   -------------------------------------------------------------------------- */
/* Empêche une plage horaire de se couper en deux : « 8h30 –  » en fin de
   ligne et « 12h » au début de la suivante se lit comme une erreur. */
.u-nowrap { white-space: nowrap; }

.u-mt-4 { margin-top: var(--sp-4); }
.u-mt-6 { margin-top: var(--sp-6); }
.u-mt-8 { margin-top: var(--sp-8); }

/* --------------------------------------------------------------------------
   21. Impression
   -------------------------------------------------------------------------- */
@media print {
  /* `.map` n'est plus masqué : c'était un cadre embarqué, qu'une imprimante ne
     pouvait de toute façon pas rendre. C'est désormais une image de plan
     d'accès — exactement ce qu'on veut avoir sous la main sur une page Contact
     imprimée. */
  .header, .footer, .nav-toggle, .nav-backdrop, .cta, .btn-row, .skip-link { display: none !important; }
  .map { border: 1px solid #ccc; break-inside: avoid; }
  .map__credit { color: #555; }
  /* Le plan entier est un lien : l'URL développée par la règle ci-dessous se
     serait imprimée en travers du bas de la carte. Elle figure de toute façon
     dans le crédit placé juste en dessous. */
  .map__link::after { content: none !important; }
  body { font-size: 11pt; color: #000; background: #fff; }
  .hero, .page-hero, .section--dark { background: none !important; color: #000 !important; padding-top: 1rem; }
  .hero__title, .page-hero h1, .section--dark h2, .section--dark h3 { color: #000 !important; }
  .hero__lead, .page-hero__lead { color: #333 !important; }
  .section { padding-block: 1rem; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 9pt; color: #555; }
  .card, .qual { break-inside: avoid; border: 1px solid #ccc; }
  /* `clip-path` autant qu'opacité et translation : à l'impression, `scripting`
     vaut « enabled » et le mouvement n'est pas réduit, donc le dévoilement de
     la photo du héros s'appliquerait — une feuille imprimée avant que
     l'observateur n'ait révélé le bloc sortirait avec l'image rognée. */
  [data-reveal] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
}

/* --------------------------------------------------------------------------
   22. Étalonnage photographique
   Les douze photos proviennent du dossier client, avec des balances des blancs
   et des contrastes hétérogènes. Une correction unique et légère les fait lire
   comme une série cohérente, sans retoucher les fichiers.
   -------------------------------------------------------------------------- */
.tile img,
.hero__media img,
.split__media img,
.map__link img { filter: saturate(0.92) contrast(1.03); }
