/* ==========================================================================
   MONT-SITE — FEUILLE DE STYLE UNIQUE
   --------------------------------------------------------------------------
   Aucun framework. CSS Grid + Flexbox natifs, piloté par variables CSS.
   43 Ko brut (dont beaucoup de commentaires) → ~11 Ko une fois gzippé par
   Apache. La V1 chargeait 227 Ko de Bootstrap dont ~10 % servait réellement.
   Ici tout est écrit pour ce site — la seule règle en avance de phase est
   `select.input`, prévue pour les formulaires des pages services.

   IDENTITÉ « REFUGE » — froid dominant, bois en accent
     ~80 %  granite / ardoise / neige / glacier   → structure, fonds, texte
     ~15 %  sable / bois                          → surfaces calmes, textures
     ~5  %  ambre                                 → CTA uniquement

   L'ambre n'apparaît QUE sur ce qui doit être cliqué. C'est la règle qui fait
   que le bouton « Devis gratuit » saute aux yeux. Ne pas la diluer.

   SOMMAIRE
     01. Police
     02. Reset
     03. Tokens
     04. Base typographique
     05. Layout
     06. Textures & décors
     07. Boutons
     08. Navigation
     09. Hero
     10. Services
     11. À propos
     12. Réalisations
     13. FAQ
     14. Formulaire de contact
     15. Pied de page
     16. Animations d'apparition
     17. Pages intérieures
     18. Blog
     19. Pages d'erreur
     20. Accessibilité & impression
   ========================================================================== */


/* ==========================================================================
   01. POLICE — Outfit, auto-hébergée
   Variable : un seul fichier couvre les graisses 500 → 800.
   font-display:swap → texte visible tout de suite, pas de FOIT.
   ========================================================================== */

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 500 800;
  font-display: swap;
  src: url('../fonts/outfit-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 500 800;
  font-display: swap;
  src: url('../fonts/outfit-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* ==========================================================================
   02. RESET
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Compense la navbar fixe quand on arrive sur une ancre (#services…). */
  scroll-padding-top: 5.5rem;
}

body {
  min-height: 100vh;
  font-family: 'Outfit', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-weight: 500;
  font-size: var(--fs-body);
  line-height: 1.65;
  color: var(--text);
  background: var(--snow);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

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

input, button, textarea, select { font: inherit; color: inherit; }

button { cursor: pointer; border: none; background: none; }

/* Les fieldsets arrivent avec bordure, marges et padding du navigateur, et
   un min-width:auto qui les empêche de rétrécir dans une grille. */
fieldset { border: 0; padding: 0; min-width: 0; }
legend { padding: 0; }

a { color: inherit; text-decoration: none; }

ul, ol { list-style: none; padding: 0; }

h1, h2, h3, h4 { line-height: 1.15; text-wrap: balance; }

p { text-wrap: pretty; }


/* ==========================================================================
   03. TOKENS
   ========================================================================== */

:root {

  /* ── Froid — la dominante (80 %) ─────────────────────────────────────── */
  --ink:          #0B1620;   /* nuit alpine — fond le plus sombre           */
  --granite-900:  #0E1A24;
  --granite-800:  #16293A;
  --granite:      #1E3A4C;   /* granite bleu — sections sombres             */
  --granite-600:  #2E5670;
  --granite-400:  #4A7B96;
  --glacier:      #5EA8C4;   /* bleu glacier — liens, icônes, filets        */
  --glacier-300:  #8FC4D8;
  --frost:        #C7D6DF;   /* givre — bordures sur fond clair             */
  --frost-100:    #E4EDF2;
  --snow:         #F4F7F9;   /* neige — fond de page                        */
  --white:        #FFFFFF;

  /* ── Bois — l'accent chaud (15 %) ────────────────────────────────────── */
  --sand-50:      #FAF6F0;
  --sand:         #E8DCCB;   /* sable — grandes surfaces calmes             */
  --wood:         #B99871;   /* bois chaud — filets, grain, séparateurs     */
  --wood-dark:    #8A6E4B;

  /* ── Ambre — CTA uniquement (5 %) ────────────────────────────────────── */
  --amber:        #D97A2B;
  --amber-600:    #BE621A;
  --amber-300:    #F0A868;
  /* Seule variante autorisée DANS DU TEXTE, et uniquement via `.hl`.
     L'ambre des boutons ne monte qu'à 2,9:1 sur fond clair : illisible en
     texte courant (WCAG AA en exige 4,5:1). Celle-ci tient 4,9:1 sur le
     sable et 5,2:1 sur le blanc. Elle est volontairement plus sourde que
     `--amber` : le bouton reste la seule vraie tache de couleur vive. */
  --amber-700:    #A85614;

  /* ── Texte ───────────────────────────────────────────────────────────── */
  --text:              #16293A;
  --text-muted:        #5B7285;
  --text-invert:       #F4F7F9;
  --text-invert-muted: rgba(244, 247, 249, .74);

  /* ── Typographie fluide (clamp = zéro media-query sur les tailles) ────── */
  --fs-display: clamp(2.4rem, 1.35rem + 4.4vw, 4.4rem);
  --fs-h2:      clamp(1.85rem, 1.25rem + 2.5vw, 3rem);
  --fs-h3:      clamp(1.15rem, 1.05rem + .45vw, 1.4rem);
  --fs-lead:    clamp(1.05rem, .98rem + .38vw, 1.25rem);
  --fs-body:    1.0125rem;
  --fs-sm:      .875rem;
  --fs-xs:      .78rem;

  /* ── Rythme vertical ─────────────────────────────────────────────────── */
  --sp-section: clamp(4.5rem, 3rem + 6vw, 8rem);
  --container:  1200px;
  --gutter:     clamp(1.15rem, .7rem + 1.6vw, 2rem);
  /* Hauteur des crêtes SVG. Une seule source : les sections qui en portent une
     doivent réserver exactement cette hauteur dans leur padding, sinon leur
     contenu passe derrière la silhouette. */
  --ridge-h:    clamp(48px, 6vw, 96px);

  /* ── Angles ──────────────────────────────────────────────────────────────
     Tout le site est à angle vif. Choix assumé : l'arrondi généralisé est la
     signature visuelle des frameworks (Bootstrap, Tailwind par défaut) et
     adoucit indistinctement tout ce qu'il touche. Sur une identité granite,
     l'angle net évoque la roche taillée — et distingue immédiatement le site
     de la production standard.

     Les variables sont conservées et TOUT le site les utilise, y compris les
     boutons et les pastilles d'icônes (qui étaient en 999px / 50 %). Passer
     l'ensemble du site à 2px ou 4px = modifier ces quatre lignes.
     Seule exception dans la feuille : `.glow`, un halo flouté — c'est un
     effet de lumière, pas une boîte. */
  --r-sm: 0;
  --r:    0;
  --r-lg: 0;
  --r-xl: 0;

  /* ── Ombres — teintées granite, jamais du noir pur ───────────────────── */
  --sh-sm: 0 1px 2px rgba(11, 22, 32, .05), 0 2px 6px rgba(11, 22, 32, .05);
  --sh:    0 4px 12px rgba(11, 22, 32, .07), 0 12px 28px -8px rgba(11, 22, 32, .10);
  --sh-lg: 0 12px 32px -8px rgba(11, 22, 32, .14), 0 28px 64px -16px rgba(30, 58, 76, .20);
  --sh-amber: 0 6px 20px -4px rgba(217, 122, 43, .45);

  /* ── Transitions ─────────────────────────────────────────────────────── */
  --ease: cubic-bezier(.4, 0, .2, 1);
  --t:    .25s var(--ease);
  --t-lg: .5s var(--ease);
}

::selection { background: var(--amber); color: var(--white); }


/* ==========================================================================
   04. BASE TYPOGRAPHIQUE
   ========================================================================== */

.display {
  font-size: var(--fs-display);
  font-weight: 800;
  letter-spacing: -.035em;
}

.h2 {
  font-size: var(--fs-h2);
  font-weight: 800;
  letter-spacing: -.028em;
}

.h3 {
  font-size: var(--fs-h3);
  font-weight: 700;
  letter-spacing: -.015em;
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--text-muted);
}

/* Mot-clé illustré à l'intérieur d'un chapô. Le `nowrap` tient l'icône et le
   mot solidaires : ils ne peuvent pas se retrouver de part et d'autre d'un
   retour à la ligne. Dimensions en `em` — l'icône suit la taille du texte qui
   la porte, quelle que soit la clamp() du contexte.

   ⚠️ `inline-block` et surtout PAS `inline-flex` : la baseline d'une boîte
   inline-flex est celle de son premier enfant. Ici c'est le SVG, qui n'a pas
   de baseline — le navigateur prend alors le bas de sa marge, et tout le
   groupe se retrouve décalé verticalement par rapport au texte qui l'entoure
   (le « et » de l'énumération flottait visiblement plus bas). En inline-block,
   la baseline de la boîte est celle de sa dernière ligne de texte : elle
   s'aligne exactement sur le reste du paragraphe. */
.kw {
  display: inline-block;
  white-space: nowrap;
  color: var(--granite);
}
.kw strong { font-weight: 700; }
.kw svg {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  margin-right: .3em;
  vertical-align: -.16em;   /* cale l'icône sur la hauteur d'x du mot */
  color: var(--granite-400);
}
.on-dark .kw { color: var(--white); }
.on-dark .kw svg { color: var(--glacier); }

.small     { font-size: var(--fs-sm); }
.muted     { color: var(--text-muted); }
.on-dark .muted,
.muted-invert { color: var(--text-invert-muted); }

/* Met en valeur une énumération au fil du texte — gras + ambre.
   ⚠️ SEULE entorse admise à la règle « ambre = ce qui se clique », et elle
   est encadrée : la teinte est `--amber-700`, plus sourde et plus foncée
   que celle des boutons (contraste oblige, voir le § TOKENS). Un CTA reste
   donc la seule tache d'ambre VIVE de la page. À n'employer qu'une fois par
   section : deux `.hl` voisins et plus rien ne ressort.
   Sur fond sombre, l'ambre-700 ne passerait pas — on retombe sur l'ambre
   clair, qui, lui, y est lisible. */
.hl { font-weight: 700; color: var(--amber-700); }
.on-dark .hl { color: var(--amber-300); }

/* Surligne un mot-clé dans un titre — trait de bois sous le texte.
   Préféré à la couleur dans les titres : c'est ce qui laisse l'ambre
   n'apparaître qu'en de très rares endroits (cf. `.hl`).

   Le trait est un background et non un ::after absolu : avec
   box-decoration-break, il est redessiné sur CHAQUE ligne quand le texte
   se coupe. Un ::after ne saurait souligner que la dernière — c'est ce
   qui imposait un white-space:nowrap, et donc un débordement horizontal
   du titre dès que la phrase marquée dépassait la largeur de l'écran.
   Un background se peint aussi derrière le texte sans z-index négatif,
   qui cassait au moindre contexte d'empilement sur un parent. */
.mark {
  /* Opacité du trait, réglable par contexte (fond clair / fond sombre). */
  --mark-a: 50%;
  /* Léger débordement latéral, comme un coup de surligneur. La marge
     négative compense le padding : le texte voisin ne bouge pas. */
  padding-inline: .06em;
  margin-inline: -.06em;
  background-image: linear-gradient(90deg,
    color-mix(in srgb, var(--wood) var(--mark-a), transparent),
    color-mix(in srgb, var(--sand) var(--mark-a), transparent));
  background-repeat: no-repeat;
  background-size: 100% .3em;
  background-position: 0 calc(100% - .1em);
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
}

/* Libellé de section, au-dessus du h2. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--granite-600);
  margin-bottom: .9rem;
}
.eyebrow::before {
  content: '';
  width: 1.75rem;
  height: 2px;
  background: var(--wood);
}
.on-dark .eyebrow { color: var(--glacier-300); }
.on-dark .eyebrow::before { background: var(--glacier); }


/* ==========================================================================
   05. LAYOUT
   ========================================================================== */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: 780px; }

.section { padding-block: var(--sp-section); }

.section-head {
  max-width: 640px;
  margin-bottom: clamp(2.5rem, 1.5rem + 2.5vw, 4rem);
}
.section-head--center {
  margin-inline: auto;
  text-align: center;
}
/* Variante centrée. Le filet unique du libellé est calé à gauche : sous un bloc
   centré il pend d’un côté, d’où le `display: none` d’origine — qui laissait le
   libellé tout nu au-dessus du titre. On le double symétriquement à la place,
   en dégradé fuyant vers l’extérieur : deux traits pleins feraient un
   « — TEXTE — » de faire-part, le fondu laisse le mot arriver de la page. */
.section-head--center .eyebrow::before,
.section-head--center .eyebrow::after {
  content: '';
  display: block;
  flex: none;
  width: clamp(1.5rem, .75rem + 2vw, 2.75rem);
  height: 2px;
  background: linear-gradient(to right, transparent, var(--wood));
}
.section-head--center .eyebrow::after {
  background: linear-gradient(to left, transparent, var(--wood));
  /* `letter-spacing` ajoute son espace APRÈS la dernière lettre : sans cette
     reprise, le filet de droite paraît décollé de .16em de plus que celui de
     gauche. Le décalage est invisible isolément, mais un libellé centré se lit
     par sa symétrie — c’est exactement là qu’il se voit. */
  margin-left: -.16em;
}
.on-dark .section-head--center .eyebrow::before {
  background: linear-gradient(to right, transparent, var(--glacier));
}
.on-dark .section-head--center .eyebrow::after {
  background: linear-gradient(to left, transparent, var(--glacier));
}
.section-head .lead { margin-top: 1rem; }

/* Grille auto-responsive : les cartes se replacent seules, sans breakpoint.
   --min pilote la largeur minimale d'une colonne. */
.grid {
  display: grid;
  gap: var(--gap, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--min, 280px), 100%), 1fr));
}

.stack   { display: flex; flex-direction: column; gap: var(--gap, 1rem); }
.cluster { display: flex; flex-wrap: wrap; gap: var(--gap, 1rem); align-items: center; }

.on-dark { color: var(--text-invert); }
.on-dark h1, .on-dark h2, .on-dark h3 { color: var(--white); }

.bg-snow    { background: var(--snow); }
.bg-white   { background: var(--white); }
.bg-sand    { background: var(--sand-50); }
.bg-granite { background: var(--granite); }
.bg-ink     { background: var(--ink); }


/* ==========================================================================
   06. TEXTURES & DÉCORS
   Ce qui porte l'identité montagne. Tout est en SVG ou en dégradé : zéro
   requête réseau, zéro octet d'image, insensible au zoom.
   ========================================================================== */

/* — Grain de bois —————————————————————————————————————————————————————————
   feTurbulence très étirée horizontalement (baseFrequency X faible,
   Y élevée) = fibres de bois. Opacité volontairement basse : on veut une
   matière perçue, jamais un motif de planches identifiable — c'est la
   différence entre « chalet contemporain » et « crêperie savoyarde ». */
.tx-wood { position: relative; isolation: isolate; }
.tx-wood::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .35;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.008 0.75' numOctaves='4' seed='7'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='400' height='400' filter='url(%23w)'/%3E%3C/svg%3E");
  mix-blend-mode: multiply;
}

/* — Grain fin (neige / papier) : casse le côté « aplat numérique » ——————— */
.tx-grain { position: relative; isolation: isolate; }
.tx-grain::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' seed='3'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)' opacity='0.09'/%3E%3C/svg%3E");
}

/* — Courbes de niveau (carte topographique) ——————————————————————————————
   Motif signature du site. Fichier SVG externe : trop long en data-URI. */
.tx-topo { position: relative; isolation: isolate; }
.tx-topo::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: .5;
  background: url('../img/topo.svg') repeat;
  background-size: 640px;
}
.on-dark.tx-topo::before { opacity: .22; filter: invert(1); }

/* — Séparateur de crêtes entre deux sections ——————————————————————————————
   Le SVG est posé dans le HTML (contrôle du remplissage par section).
   Il déborde légèrement (-1px) pour éviter le filet blanc dû aux
   arrondis de sous-pixels sur certains navigateurs. */
.ridge {
  display: block;
  width: 100%;
  height: var(--ridge-h);
  margin-bottom: -1px;
}
.ridge--flip { transform: rotate(180deg); margin-top: -1px; margin-bottom: 0; }

/* Section qui se termine par une crête : le SVG doit toucher le bord bas,
   pas flotter au-dessus du padding. On le sort donc du flux et on réserve
   sa hauteur dans le padding inférieur. */
.section--ridged { position: relative; padding-bottom: calc(var(--sp-section) + var(--ridge-h)); }
.section--ridged > .ridge { position: absolute; left: 0; right: 0; bottom: -1px; margin: 0; }

/* Le halo de soleil `.glow` a été retiré le 2026-07-31 en même temps que le
   fond SVG à plat : sur une vraie photo, qui porte déjà sa propre lumière et
   sa propre direction d'éclairage, un dégradé radial ambre surimposé se lit
   comme un artefact de lentille. Conséquence heureuse : plus AUCUN
   `border-radius` en dur dans la feuille, les 23 restants passent tous par
   les variables. */


/* ==========================================================================
   07. BOUTONS
   ========================================================================== */

.btn {
  --btn-px: 1.75rem;              /* variabilisé : .btn--swap en a besoin pour son calcul */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  padding: .95rem var(--btn-px);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: .01em;
  text-align: center;
  white-space: nowrap;
  transition: transform var(--t), box-shadow var(--t), background-color var(--t), color var(--t), border-color var(--t);
}
.btn:active { transform: translateY(1px); }
.btn svg { width: 1.05em; height: 1.05em; flex: none; }

/* Le seul élément ambre de la page. */
.btn--primary {
  background: var(--amber);
  color: var(--white);
  box-shadow: var(--sh-amber);
}
.btn--primary:hover {
  background: var(--amber-600);
  transform: translateY(-2px);
  box-shadow: 0 10px 28px -4px rgba(217, 122, 43, .55);
}

.btn--dark {
  background: var(--granite);
  color: var(--white);
}
.btn--dark:hover { background: var(--granite-800); transform: translateY(-2px); }

.btn--ghost {
  border: 1.5px solid var(--frost);
  color: var(--granite);
  background: var(--white);
}
.btn--ghost:hover { border-color: var(--granite-400); background: var(--frost-100); }

.on-dark .btn--ghost,
.btn--ghost-invert {
  background: rgba(255, 255, 255, .07);
  border: 1.5px solid rgba(255, 255, 255, .3);
  color: var(--white);
  backdrop-filter: blur(6px);
}
.on-dark .btn--ghost:hover,
.btn--ghost-invert:hover {
  background: rgba(255, 255, 255, .16);
  border-color: rgba(255, 255, 255, .55);
}

.btn--lg    { --btn-px: 2.25rem; padding: 1.1rem var(--btn-px); font-size: 1rem; }
.btn--block { width: 100%; }

/* --------------------------------------------------------------------------
   CTA du hero — l'icône traverse le libellé, de sa gauche à sa droite.
   --------------------------------------------------------------------------
   Trois contraintes ont dicté ce montage :

   1. Rien ne bouge dans la page. L'icône est hors flux et le libellé se décale
      d'un demi-pas en sens inverse, si bien que le groupe reste centré aux deux
      extrémités du trajet ET que la largeur du bouton est, au pixel près, celle
      qu'il aurait avec l'icône en flux (libellé + pas + 2 × padding).
   2. Uniquement des `transform`. `left`/`margin` animés relanceraient le layout
      à chaque frame ; ici tout se joue en compositing. D'où l'astuce du rail :
      `.btn__swap-icon` est une piste large de tout le trajet avec l'icône collée
      à son bord gauche — la déplacer de `100%` déplace donc l'icône d'exactement
      la distance voulue, sans jamais avoir à connaître la largeur du texte.
   3. Le clavier a droit à la même animation : `:focus-visible` est de la fête.

   `prefers-reduced-motion` neutralise le tout via la règle globale (§ fin de
   fichier) : l'icône se téléporte, elle ne glisse plus. */
.btn--swap {
  --swap-gap: .6rem;                              /* gouttière icône ↔ libellé */
  --swap-step: calc(1.05em + var(--swap-gap));    /* largeur icône + gouttière */
  --swap-t: .45s cubic-bezier(.55, 0, .25, 1);
  padding-inline: calc(var(--btn-px) + var(--swap-step) / 2);
  gap: 0;
  /* Le rail dépasse largement du bouton une fois déplacé. Il est invisible,
     mais un bloc hors flux qui dépasse agrandit la zone scrollable de ses
     ancêtres : on le confine ici. L'icône et le libellé, eux, restent en
     permanence à `--btn-px` des bords — rien n'est jamais rogné. */
  overflow: hidden;
}

.btn__swap {
  position: relative;                             /* référent du rail */
  display: inline-block;
  transform: translateX(calc(var(--swap-step) / 2));
  transition: transform var(--swap-t);
}
.btn--swap:hover .btn__swap,
.btn--swap:focus-visible .btn__swap {
  transform: translateX(calc(var(--swap-step) / -2));
}

/* Le survol ne soulève plus le bouton : la traversée de l'icône EST l'animation,
   et superposer les deux mouvements brouillait la lecture. L'ombre est donc
   figée sur celle du repos (plus de fausse élévation) — reste le passage à
   l'ambre foncé, qui suffit comme retour d'état. L'enfoncement au clic, lui,
   est réaffirmé : `.btn:active` est déclaré plus haut dans la feuille et serait
   sinon écrasé par le `transform: none` ci-dessous. */
.btn--swap:hover,
.btn--swap:focus-visible {
  transform: none;
  box-shadow: var(--sh-amber);
}
.btn--swap:active { transform: translateY(1px); }

/* Le rail : de « un pas avant le libellé » à « une gouttière après ». */
.btn__swap-icon {
  position: absolute;
  top: 50%;
  left: calc(var(--swap-step) * -1);
  width: calc(100% + var(--swap-step) + var(--swap-gap));
  height: 1.05em;
  margin-top: -.525em;                            /* moitié de la hauteur */
  pointer-events: none;
  transition: transform var(--swap-t);
}
.btn__swap-icon svg { display: block; }
.btn--swap:hover .btn__swap-icon,
.btn--swap:focus-visible .btn__swap-icon {
  transform: translate3d(100%, 0, 0);
}

/* Lien fléché — la flèche avance au survol. */
.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--granite-600);
  transition: color var(--t), gap var(--t);
}
.arrow-link:hover { color: var(--amber-600); gap: .8rem; }
.arrow-link svg { width: 1.05em; height: 1.05em; }
/* Sur fond sombre, le granite du repos disparaîtrait dans le fond et
   l'ambre-600 du survol n'y passerait pas le contraste : les deux teintes
   remontent d'un cran, comme le fait déjà `.hl`. */
.on-dark .arrow-link { color: var(--glacier-300); }
.on-dark .arrow-link:hover { color: var(--amber-300); }


/* ==========================================================================
   08. NAVIGATION
   ========================================================================== */

.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  z-index: 200;
  padding: .75rem 1.25rem;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: var(--white);
  color: var(--granite);
  font-weight: 700;
}
.skip-link:focus { top: 0; }

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  transition: background-color var(--t-lg), box-shadow var(--t-lg), padding var(--t-lg);
  /* Variable et pas une valeur en dur : le déroulant « Services » s'accroche
     à l'arête BASSE de la barre, il lui faut donc la hauteur de ce padding.
     `.nav.is-stuck` ne change plus que cette variable — voir .nav__submenu.
     La transition reste posée sur `padding` : il interpole normalement quand
     la variable change. La variable, elle, saute — donc un déroulant laissé
     ouvert pendant le défilement se recale d'un coup. Cas de bord assumé. */
  --nav-pad: 1.1rem;
  padding-block: var(--nav-pad);
}
/* Posée par main.js au-delà de 40px de défilement. */
.nav.is-stuck {
  --nav-pad: .55rem;
  background: rgba(14, 26, 36, .92);
  backdrop-filter: blur(14px);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .08), 0 8px 32px -12px rgba(11, 22, 32, .6);
}

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.nav__brand { display: flex; align-items: center; gap: .7rem; flex: none; }
/* Agrandi le 2026-08-27 (40 → 52 px, et 34 → 44 px une fois la barre figée).
   ⚠️ PLAFOND À 80 px : le fichier fait 160 × 160, il faut le double de la
   taille d'affichage pour rester net sur un écran 2×. Au-delà de 80 px, il
   faut ré-exporter depuis `assets/img/_originaux/`, pas juste pousser la
   valeur ici.
   ⚠️ Ces deux hauteurs pilotent la hauteur de la barre, qui est FIXE. Si
   elles montent encore, vérifier `scroll-padding-top` (5,5rem = 88 px, la
   marge qui empêche une ancre de se cacher sous la barre figée) et le
   `top: 6.5rem` des colonnes collantes (.faq__aside, .contact__aside).
   Le déroulant « Services », lui, suit tout seul : il s'accroche en
   `calc(100% + var(--nav-pad))`. */
.nav__brand img { height: 52px; width: auto; transition: height var(--t-lg); }
.nav.is-stuck .nav__brand img { height: 44px; }

.nav__menu {
  display: flex;
  align-items: center;
  gap: .35rem;
  /* Pousse le menu à droite en absorbant tout l'espace libre, ce que faisait
     jusqu'ici le `justify-content: space-between` de `.nav__inner`. Il a
     fallu l'expliciter à l'arrivée du sélecteur de langue (2026-09-06) :
     avec TROIS enfants, `space-between` aurait planté le menu au milieu de
     la barre. Une marge auto se sert avant `justify-content`, donc le
     résultat est : logo à gauche, menu à droite, sélecteur juste après. */
  margin-left: auto;
}

.nav__link {
  position: relative;
  padding: .55rem .85rem;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: rgba(255, 255, 255, .82);
  transition: color var(--t);
}
/* `.is-ancestor` = « la page courante est dans mon déroulant ». Même repère
   que la page courante elle-même, et c'est voulu : le déroulant est fermé au
   repos, un repère qui ne vivrait que sur le sous-lien ne se verrait jamais. */
.nav__link:hover,
.nav__link[aria-current='page'],
.nav__link.is-ancestor { color: var(--white); }
/* Filet glacier qui se déploie du centre. */
.nav__link::after {
  content: '';
  position: absolute;
  left: 50%;
  right: 50%;
  bottom: .3rem;
  height: 2px;
  background: var(--glacier);
  transition: left var(--t), right var(--t);
}
.nav__link:hover::after,
.nav__link[aria-current='page']::after,
.nav__link.is-ancestor::after { left: .85rem; right: .85rem; }

.nav__cta { margin-left: .6rem; padding: .7rem 1.4rem; }

/* --------------------------------------------------------------------------
   DÉROULANT « SERVICES » — sans JavaScript
   Ouvert par :hover et par :focus-within, donc utilisable au clavier sans une
   ligne de script. L'entrée parente reste un vrai lien vers /#services : si
   rien ne s'ouvre (survol impossible, JS ou non), la destination existe.

   ⚠️ Le panneau pend sous l'ARÊTE BASSE de la barre, pas sous le lien : sinon
   il remonterait dans la barre et son filet supérieur la barrerait en travers.
   D'où `top: calc(100% + var(--nav-pad))`. Mais cet écart couperait le :hover
   pendant la descente de la souris — le ::after le comble, invisible.
   -------------------------------------------------------------------------- */
/* `align-self: stretch` n'est pas cosmétique : sans lui le <li> est centré
   dans une rangée dont la hauteur est donnée par le CTA, plus haut que les
   liens. Son bord bas tomberait donc ~9px au-dessus du bas de la zone de
   contenu, et le `calc(100% + var(--nav-pad))` du panneau raterait l'arête
   de la barre d'autant. Étiré, le <li> finit pile où finit le contenu. */
.nav__item--sub {
  position: relative;
  align-self: stretch;
  display: flex;
  align-items: center;
}
.nav__item--sub::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  height: var(--nav-pad);
}

/* ⚠️ `inline-block`, PAS `inline-flex` — même piège que `.kw` (voir § services) :
   la baseline d'une boîte inline-flex est celle de son premier enfant, ici un
   SVG qui n'en a pas ; le navigateur prend alors le bas de sa marge et le
   chevron remonte au-dessus de la ligne. En inline-block il se cale par
   `vertical-align`. Le `display: block` du SVG supprime l'espace de ligne
   qui, sinon, décentrerait à nouveau la boîte. */
.nav__link .nav__caret {
  display: inline-block;
  vertical-align: -.2em;
  margin-left: .3rem;
  transition: transform var(--t);
}
.nav__caret svg { display: block; width: 15px; height: 15px; }
.nav__item--sub:hover .nav__caret,
.nav__item--sub:focus-within .nav__caret { transform: rotate(180deg); }

.nav__submenu {
  position: absolute;
  top: calc(100% + var(--nav-pad));
  left: 0;
  z-index: 1;
  min-width: 17rem;
  padding: .4rem;
  background: rgba(14, 26, 36, .97);
  backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: 0 18px 40px -14px rgba(11, 22, 32, .7);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-.4rem);
  transition: opacity var(--t), transform var(--t), visibility var(--t);
}
.nav__item--sub:hover .nav__submenu,
.nav__item--sub:focus-within .nav__submenu {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.nav__sublink {
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .65rem .75rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: rgba(255, 255, 255, .82);
  transition: color var(--t), background-color var(--t);
}
.nav__sublink:hover,
.nav__sublink:focus-visible { color: var(--white); background: rgba(255, 255, 255, .07); }

/* Page courante dans le déroulant : le fond du survol, plus un filet glacier
   au bord gauche. `inset box-shadow` et non `border-left` — une bordure
   décalerait le texte de 2px par rapport aux trois autres entrées. */
.nav__sublink[aria-current='page'] {
  color: var(--white);
  background: rgba(255, 255, 255, .07);
  box-shadow: inset 2px 0 0 var(--glacier);
}

/* Icônes en glacier, jamais en ambre : ces liens se cliquent, mais l'ambre
   reste au seul CTA de la barre — sinon il ne ressort plus de rien. */
.nav__sublink-icon { display: inline-flex; flex: none; color: var(--glacier); }
.nav__sublink-icon svg { width: 19px; height: 19px; }

/* --------------------------------------------------------------------------
   SÉLECTEUR DE LANGUE                                  ajouté le 2026-09-06
   --------------------------------------------------------------------------
   Un seul lien — drapeau + sigle — qui mène à l'autre langue.

   ⚠️ IL N'EST PAS DANS `.nav__menu`, ET C'EST TOUTE LA DEMANDE. C'est un
      frère du burger dans `.nav__inner` : sur mobile il reste donc SUR LA
      BARRE, à gauche du burger, sans qu'il faille ouvrir le tiroir.

   D'où la gymnastique d'ordre, à lire dans ce sens :
     · l'ORDRE DU DOM sert le mobile — sélecteur avant burger, pour que le
       burger garde l'extrémité droite, sa place conventionnelle ;
     · `order: 2` corrige pour le desktop, où le sélecteur passe à droite du
       menu et du CTA. Il est remis à 0 dans la media-query mobile.
   L'ordre du DOM est celui que suivent le clavier et les lecteurs d'écran :
   c'est lui qui doit être juste là où la barre est la plus contrainte.

   ⚠️ AUCUN AMBRE. C'est cliquable, mais l'ambre est réservé aux CTA (règle
      du CLAUDE.md) : posé ici, il ferait concurrence au bouton « Devis
      gratuit » à quelques centimètres de là. Le texte reste blanc.

   VERRE DÉPOLI, SANS FILET (2026-09-06). Le contour d'1px a sauté : c'est
   la MATIÈRE qui détache le bouton, plus un trait.

   ⚠️ IL DOIT RESTER PRESQUE INVISIBLE — consigne explicite du même jour,
      après un premier réglage jugé trop présent. Le bouton ne doit PAS
      prendre le dessus sur la photo qui passe derrière lui : on ne cherche
      pas l'effet, juste de quoi asseoir le sigle. D'où des valeurs basses
      partout, et une seule ombre au lieu de trois :
        · fond blanc à 6 % — assez pour exister, pas pour laver la photo ;
        · `blur(10px)` seulement, avec un `saturate(120%)` léger. Le blur
          seul ternit ce qu'il floute ; la saturation lui rend ses couleurs
          sans ajouter de présence. C'est un correctif, pas un effet.
        · un reflet d'arête haute à 12 %, et RIEN d'autre. La portée sous le
          bouton et l'ombre de pied ont sauté : elles le décollaient de la
          barre, c'est exactement ce qu'on ne veut plus.

   ⚠️ LE REFLET EST À UN SEUL BORD, ET C'EST VOULU. Un `inset 0 0 0 1px`
      aurait redessiné le filet qu'on vient d'enlever. Une lumière se pose
      sur l'arête haute, elle ne fait pas le tour.

   ⚠️ NE PAS REMONTER CES VALEURS « pour que ça se voie mieux ». C'est de
      là qu'on revient. Le bouton se trouve par sa POSITION — bout de barre,
      toujours au même endroit — pas par son contraste avec le fond.

   ⚠️ ANGLES VIFS : `--r-sm` vaut 0 et le reste. Le verre dépoli arrondi est
      la signature d'iOS ; sur une identité granite, la plaque taillée net.

   ⚠️ LE FLOU NE MORD PAS QUAND LA BARRE EST FIGÉE, et ce n'est pas un bug.
      `.nav.is-stuck` porte son propre `backdrop-filter` : elle devient la
      racine d'arrière-plan de ses descendants, qui ne floutent plus la page
      mais son aplat `rgba(14, 26, 36, .92)` — déjà uni. Reste la plaque
      translucide, ce qui est le bon rendu sur un fond opaque. Le verre joue
      donc en HAUT DE PAGE, barre transparente sur la photo du hero : c'est
      exactement là qu'il y a quelque chose à flouter.
   -------------------------------------------------------------------------- */
.nav__lang {
  order: 2;
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  flex: none;
  padding: .42rem .6rem;
  border: 0;
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .06);
  /* Préfixe conservé : Safari ne l'a lâché qu'en 18, et le lecteur
     anglophone du Pays du Mont Blanc est massivement sur iPhone. */
  -webkit-backdrop-filter: blur(10px) saturate(120%);
          backdrop-filter: blur(10px) saturate(120%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12);
  color: rgba(255, 255, 255, .88);
  line-height: 1;
  transition: background-color var(--t), box-shadow var(--t), color var(--t);
}
.nav__lang:hover,
.nav__lang:focus-visible {
  background: rgba(255, 255, 255, .13);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22);
  color: var(--white);
}

/* Repli sans `backdrop-filter` (Firefox avec la fonctionnalité coupée, vieux
   Android). Rien à flouter, et 6 % de blanc ne suffisent plus à rien : le
   bouton ne serait qu'un texte flottant — c'était le rôle du filet supprimé.
   ⚠️ 10 %, pas 17 % : le repli doit rester DISCRET lui aussi. Il compense
      l'absence de flou, il ne rattrape pas l'effet. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .nav__lang { background: rgba(255, 255, 255, .10); }
  .nav__lang:hover,
  .nav__lang:focus-visible { background: rgba(255, 255, 255, .18); }
}

.nav__lang-flag { display: inline-flex; }
/* ⚠️ SEULE LA HAUTEUR est fixée : les deux drapeaux n'ont pas le même ratio
   (3:2 pour la France, 2:1 pour le Royaume-Uni, leurs proportions
   officielles) et les forcer au même gabarit tasserait visiblement l'Union
   Jack. Comme un seul drapeau s'affiche à la fois, l'écart de largeur ne se
   compare jamais. Voir flag() dans includes/icons.include.php.
   Le liseré n'est pas décoratif : sans lui, le blanc du drapeau se fond dans
   le blanc du texte voisin sur la barre sombre. */
.nav__lang-flag svg {
  display: block;
  height: 13px;
  width: auto;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .35);
}

.nav__lang-abbr {
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .06em;
}

.nav__burger {
  display: none;
  width: 44px;
  height: 44px;
  border-radius: var(--r-sm);
  align-items: center;
  justify-content: center;
  color: var(--white);
}
/* Le voile n'existe que pour le tiroir mobile — au-delà du point de rupture
   il n'a aucune raison d'être dans la page. */
.nav__scrim { display: none; }

/* Idem pour les icônes des entrées : sur la barre desktop, qui tient sur une
   ligne et se lit d'un coup d'œil, elles n'ajouteraient que du bruit. */
.nav__link-icon { display: none; }

.nav__burger svg { width: 26px; height: 26px; }
.nav__burger .icon-close { display: none; }
.nav__burger[aria-expanded='true'] .icon-open  { display: none; }
.nav__burger[aria-expanded='true'] .icon-close { display: block; }

/* --------------------------------------------------------------------------
   TIROIR MOBILE
   L'état ouvert est porté par `.nav.is-open` — une seule classe pour le
   voile, le tiroir et le logo. Le point de rupture 960px est aussi écrit
   dans main.js (fermeture au passage en desktop) : les garder en phase.

   ⚠️ `.nav.is-stuck` porte un `backdrop-filter`, ce qui fait de la navbar le
   bloc conteneur de ses descendants `position: fixed`. D'où les dimensions
   explicites (`height: 100dvh`, `width: 80%`) plutôt qu'un `inset` : elles
   donnent le même résultat que la navbar soit densifiée ou non.
   -------------------------------------------------------------------------- */
@media (max-width: 960px) {
  /* Le tiroir est peint au-dessus du bouton resté en flux : sans ce z-index,
     le burger — qui sert aussi de bouton « fermer » — deviendrait
     inclicquable une fois le menu ouvert. */
  .nav__burger { display: flex; position: relative; z-index: 3; }

  /* SÉLECTEUR DE LANGUE — à l'extrémité GAUCHE de la barre.
     `order: 0` annule le `order: 2` du desktop et rend la main à l'ordre du
     DOM, où le sélecteur précède le burger. Le burger garde ainsi
     l'extrémité droite, sa place conventionnelle, et le sélecteur reste
     atteignable sans ouvrir le tiroir — c'est tout l'objet du montage.

     ⚠️ IL EST PLUS ÉTROIT QU'EN DESKTOP, et ce n'est pas cosmétique : la
        barre mobile doit loger le logo CENTRÉ, le sélecteur et le burger sur
        une seule ligne. Chaque pixel repris ici est un pixel de marge gagné
        avant que le sélecteur ne vienne mordre le logo — ce qu'il faisait à
        320px avec le padding du desktop. Voir la media-query 359px plus bas,
        qui traite le cas où cela ne suffit plus. */
  .nav__lang {
    order: 0;
    /* ⚠️ À GAUCHE DE LA BARRE, PAS SEULEMENT À GAUCHE DU BURGER
       (demandé le 2026-09-06). `margin-right: auto` absorbe tout l'espace
       libre laissé par le `justify-content: flex-end` de `.nav__inner` :
       le sélecteur file à l'extrémité gauche, le burger garde la droite, et
       le logo — sorti du flux juste en dessous — reste centré entre les deux.
       Un `order` n'y aurait rien changé : les deux boutons étaient déjà
       collés du même côté, c'est l'ESPACE entre eux qu'il fallait ouvrir. */
    margin-right: auto;
    /* 44px : la cible tactile minimale. Le padding vertical du desktop
       n'y suffisait pas (≈30px), et un bouton de barre trop court se rate
       au pouce. La largeur, elle, se resserre. */
    min-height: 44px;
    padding: 0 .5rem;
    gap: .35rem;
    /* ⚠️ LA LISTE DE BASE EST RECOPIÉE, `opacity` EN PLUS. Écrire
       `transition: opacity` seul — ce qui était le cas — ÉCRASE la liste
       de `.nav__lang` : l'état actif du verre (fond + reflet) claquait
       alors sans transition au toucher. L'opacité, elle, est indispensable
       ici : c'est elle qui éteint le sélecteur à l'ouverture du tiroir. */
    transition: background-color var(--t), box-shadow var(--t), color var(--t),
                opacity var(--t);
  }
  .nav__lang-abbr { letter-spacing: .04em; }

  /* Le tiroir ne couvre que 80 % de la largeur : comme le logo, le
     sélecteur resterait visible sous le voile, à moitié mangé par le
     panneau — et cliquable au travers dans certains navigateurs. On
     l'éteint exactement comme `.nav__brand`. */
  .nav.is-open .nav__lang { opacity: 0; pointer-events: none; }

  /* Logo centré dans la barre (demandé le 2026-08-25). C'est le LOGO qui sort
     du flux, pas le burger : en positionnant le burger, `right: 0` se calerait
     sur la boîte de padding du conteneur et il perdrait la gouttière. Ici le
     burger reste en flux, poussé à droite, et le logo se centre sur la barre
     entière — pas sur l'espace qui lui resterait à gauche. */
  /* `gap` resserré : les 1,5rem du desktop séparaient le logo du menu.
     ⚠️ Depuis que le sélecteur part à gauche (`margin-right: auto`), ce gap
     ne sépare plus rien au-dessus de 360px — les deux boutons sont aux deux
     bouts de la barre. Il redevient utile SOUS 360px, où le logo rentre dans
     le flux et où le trio se retrouve serré sur une seule ligne. */
  .nav__inner { position: relative; justify-content: flex-end; gap: .75rem; }
  .nav__brand {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    transition: opacity var(--t);
  }

  /* Le tiroir ne couvre que 80 % de la largeur : le logo resterait visible
     sous le voile, à moitié mangé par le panneau. On l'efface. */
  .nav.is-open .nav__brand { opacity: 0; pointer-events: none; }

  /* Voile : ferme le menu au toucher et éteint la page derrière. */
  .nav__scrim {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100vh;
    height: 100dvh;
    background: rgba(11, 22, 32, .6);
    opacity: 0;
    visibility: hidden;
    transition: opacity .4s var(--ease), visibility .4s;
  }
  .nav.is-open .nav__scrim { opacity: 1; visibility: visible; }

  .nav__menu {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 2;
    width: 80%;
    height: 100vh;
    height: 100dvh;
    /* Deux déclarations : si `max()`/`env()` n'est pas compris, la seconde
       est ignorée et le raccourci tient toujours. En une seule ligne, tout
       le padding sauterait — le tiroir passerait sous le burger. */
    padding: 5.5rem 1.5rem 2rem;
    padding-bottom: max(2rem, env(safe-area-inset-bottom));
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--granite-900);
    /* Filet d'angle : bord gauche + bord bas, en ambre. Demandé par Maxime le
       2026-08-04 en remplacement du liseré gris, invisible sur le granite.
       ⚠️ Écart assumé à la règle « l'ambre ne sert qu'à ce qui se clique » :
       ce filet est décoratif. Il reste à 4px, et le seul autre ambre du tiroir
       est le CTA — c'est ce qui garde l'écart supportable.
       Les deux bords se raccordent d'eux-mêmes en angle (même couleur). */
    border-left: 4px solid var(--amber);
    border-bottom: 4px solid var(--amber);
    box-shadow: -24px 0 48px -12px rgba(11, 22, 32, .55);
    /* Tiroir : rangé hors écran à droite. Pas de display:none, sinon pas de
       transition ; `visibility` le retire quand même de l'ordre de
       tabulation une fois fermé. */
    transform: translateX(100%);
    transition: transform .4s var(--ease), visibility .4s;
    visibility: hidden;
  }
  .nav.is-open .nav__menu { transform: translateX(0); visibility: visible; }

  /* Les entrées entrent en cascade derrière le panneau. Les délais sont
     neutralisés avec le reste des animations par prefers-reduced-motion. */
  .nav__menu > li {
    opacity: 0;
    transform: translateX(1.25rem);
    transition: opacity .3s var(--ease), transform .3s var(--ease);
  }
  .nav.is-open .nav__menu > li { opacity: 1; transform: none; }
  .nav.is-open .nav__menu > li:nth-child(1) { transition-delay: .12s; }
  .nav.is-open .nav__menu > li:nth-child(2) { transition-delay: .18s; }
  .nav.is-open .nav__menu > li:nth-child(3) { transition-delay: .24s; }
  .nav.is-open .nav__menu > li:nth-child(4) { transition-delay: .30s; }
  /* 5 depuis que les pages intérieures ouvrent sur « Accueil » : sans ce
     dernier palier, le CTA arriverait d'un coup pendant que les autres
     entrent encore. */
  .nav.is-open .nav__menu > li:nth-child(5) { transition-delay: .36s; }

  .nav__link {
    display: flex;
    align-items: center;
    gap: .85rem;
    padding: 1.05rem .25rem;
    font-size: 1.15rem;
    font-weight: 600;
    color: rgba(255, 255, 255, .88);
    border-bottom: 1px solid rgba(255, 255, 255, .1);
    border-radius: 0;
  }
  .nav__link::after { display: none; }

  /* Le filet du desktop est masqué ici (::after ci-dessus) : dans le tiroir,
     le repère de page courante devient un trait glacier posé SUR la bordure
     basse que chaque entrée porte déjà. En `inset box-shadow`, donc sans un
     pixel de décalage — une bordure de 2px repousserait toute la liste. */
  .nav__link[aria-current='page'],
  .nav__link.is-ancestor,
  .nav__sublink[aria-current='page'] {
    color: var(--white);
    background: none;
    box-shadow: inset 0 -2px 0 var(--glacier);
  }

  /* Dans le tiroir, le déroulant n'a plus lieu d'être : il n'y a ni survol
     fiable au doigt, ni contrainte de largeur. Les 4 services sont donc
     dépliés en permanence, en retrait sous « Services ». Le chevron perdrait
     son sens — il annoncerait une ouverture déjà faite. */
  /* ⚠️ Sélecteur descendant obligatoire : la règle desktop est
     `.nav__link .nav__caret`, un `.nav__caret` seul perdrait la spécificité —
     une media-query n'en ajoute pas. */
  .nav__link .nav__caret { display: none; }
  /* Le pont de survol ne sert qu'au déroulant desktop. Et le <li> redevient
     un bloc : en flex-row, le lien et son sous-menu se mettraient côte à côte
     au lieu de s'empiler. */
  .nav__item--sub::after { display: none; }
  .nav__item--sub { display: block; }

  .nav__submenu {
    position: static;
    /* Le `min-width: 17rem` du panneau desktop débordait le tiroir de 24px. */
    min-width: 0;
    padding: 0;
    background: none;
    backdrop-filter: none;
    border: 0;
    box-shadow: none;
    opacity: 1;
    visibility: visible;
    transform: none;
  }
  .nav__sublink {
    padding: .8rem .25rem .8rem 1.75rem;
    font-size: 1rem;
    color: rgba(255, 255, 255, .7);
    border-bottom: 1px solid rgba(255, 255, 255, .07);
  }
  .nav__sublink:hover,
  .nav__sublink:focus-visible { background: none; }
  .nav__sublink-icon svg { width: 18px; height: 18px; }

  /* Icônes en glacier, pas en ambre : les entrées se cliquent, mais l'ambre
     doit rester au seul CTA du tiroir, sinon il ne ressort plus de rien. */
  .nav__link-icon {
    display: inline-flex;
    flex: none;
    color: var(--glacier);
  }
  .nav__link-icon svg { width: 20px; height: 20px; }

  /* Le CTA reste au contact des liens. Il a été collé un temps en bas du
     tiroir (`margin-top: auto`) : sur un écran haut, il se retrouvait à
     500px de la dernière entrée, séparé de la navigation qu'il conclut. */
  .nav__menu > li:last-child { margin-top: 1.5rem; }
  .nav__cta {
    display: flex;
    width: 100%;
    margin: 0;
    padding: .95rem 1.4rem;
  }
}

/* --------------------------------------------------------------------------
   TRÈS PETITS ÉCRANS — le logo redescend dans le flux
                                                        ajouté le 2026-09-06
   --------------------------------------------------------------------------
   Sous 360px, la barre ne peut plus tenir ses trois éléments avec le logo
   CENTRÉ : logo (52px avant défilement) + sélecteur de langue + burger +
   gouttières dépassent la largeur disponible, et le sélecteur venait mordre
   le logo de quelques pixels. Mesuré à 320px, où le chevauchement était de
   11px avant le resserrement du sélecteur ci-dessus.

   Trois façons de le régler, et le choix se justifie :
     · retirer le sigle et ne garder que le drapeau → contredit la demande
       (« drapeau PLUS acronyme »), et un drapeau seul est de toute façon une
       mauvaise affordance : plusieurs pays parlent la même langue ;
     · rapetisser encore le sélecteur → on passerait sous la cible tactile de
       44px, donc sous le seuil d'accessibilité ;
     · RENDRE LE LOGO AU FLUX, à gauche. C'est celle-ci.

   Le centrage du logo est une demande de Maxime (2026-08-25), mais il vise
   l'ÉQUILIBRE de la barre — ce n'est pas une contrainte d'identité. Sur un
   écran où cet équilibre n'est plus atteignable, un logo aligné à gauche
   reste la disposition la plus courante du web mobile, et c'est celle
   qu'avait la barre avant cette date.

   ⚠️ Le seuil est calé sur la GÉOMÉTRIE, pas sur un modèle d'appareil : 360px
      est la largeur en dessous de laquelle le calcul ne passe plus. Si la
      taille du logo, celle du sélecteur ou les gouttières changent, ce seuil
      est à RECALCULER, pas à recopier.
   -------------------------------------------------------------------------- */
@media (max-width: 359px) {
  /* Le logo reprend sa place dans le flux : `space-between` le pose à gauche
     et laisse le couple sélecteur + burger à droite. */
  .nav__inner { justify-content: space-between; }
  .nav__brand {
    position: static;
    transform: none;
  }
  /* L'effacement du logo à l'ouverture du tiroir n'a plus lieu d'être : posé
     à gauche, il n'est plus sous le panneau (qui couvre 80 % à droite) et
     disparaîtrait pour rien. */
  .nav.is-open .nav__brand { opacity: 1; }

  /* ⚠️ LE SÉLECTEUR RENTRE DANS LE RANG. Au-dessus de 360px il file à
     l'extrémité gauche, mais ici c'est le LOGO qui occupe cette place :
     garder le `margin-right: auto` mangerait tout l'espace libre et
     neutraliserait le `space-between` ci-dessus — logo et drapeau se
     retrouveraient collés. Remis à zéro, les trois éléments se répartissent
     sur la barre. C'est la disposition d'avant le 2026-09-06, conservée ici
     parce que c'est la seule qui tienne sous 360px. */
  .nav__lang { margin-right: 0; }
}


/* ==========================================================================
   09. HERO
   ========================================================================== */

.hero {
  position: relative;
  display: flex;
  align-items: center;
  /* dvh : évite le saut de hauteur quand la barre d'URL mobile se replie. */
  min-height: 100vh;
  min-height: 100dvh;
  /* La marge basse réserve la hauteur des crêtes (posées en absolu au bord
     inférieur). Sans ce calc, le contenu du hero passe DERRIÈRE les montagnes
     dès que la fenêtre est un peu courte : mesuré à 5 px de chevauchement sur
     un 1440 × 800, et le bandeau de preuves tombait sous la ligne de
     flottaison sur un 13 pouces. */
  padding-block: 8rem calc(5rem + var(--ridge-h));
  background: var(--ink);
  overflow: hidden;
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -3;
}
.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* La photo fait du 3:2, le hero du 16:9 environ : le recadrage rogne en
     haut et en bas. 40 % garde le sommet de l'Aiguille dans le champ. */
  object-position: center 40%;
}

/* Voile en deux couches, calibré pour une PHOTO CLAIRE (neige + ciel) et un
   texte aligné à gauche :
     · horizontal — assombrit fortement la colonne de texte, puis relâche vers
       la droite pour laisser le sommet respirer. C'est ce qui garantit le
       contraste AA du texte blanc sans noyer la photo.
     · vertical   — dégage la navbar en haut et fonce le bas, sans quoi la
       silhouette des crêtes (remplie en --snow) serait invisible sur la neige. */
.hero__veil {
  position: absolute;
  inset: 0;
  z-index: -2;
  background:
    linear-gradient(95deg,
      rgba(11, 22, 32, .74) 0%,
      rgba(11, 22, 32, .60) 30%,
      rgba(11, 22, 32, .28) 56%,
      rgba(11, 22, 32, .08) 80%,
      rgba(11, 22, 32, .04) 100%),
    linear-gradient(180deg,
      rgba(11, 22, 32, .42) 0%,
      rgba(11, 22, 32, .02) 30%,
      rgba(11, 22, 32, .04) 58%,
      rgba(11, 22, 32, .72) 100%);
}

.hero__content { position: relative; text-align: left; }

.hero__badge {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem 1.15rem;
  margin-bottom: 1.75rem;
  border-radius: var(--r-sm);
  border: 1px solid rgba(143, 196, 216, .35);
  background: rgba(30, 58, 76, .45);
  backdrop-filter: blur(8px);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--frost);
}
.hero__badge svg { width: 1rem; height: 1rem; color: var(--glacier-300); }

.hero h1 { margin-bottom: 1.5rem; color: var(--white); }
/* Sur la photo sombre, le trait doit être plus dense pour rester lisible. */
.hero h1 .mark { --mark-a: 80%; }

.hero__lead {
  max-width: 560px;
  margin: 0 0 2.5rem;
  font-size: var(--fs-lead);
  color: rgba(244, 247, 249, .86);
}
.hero__lead strong { font-weight: 700; color: var(--white); }

.hero__actions { justify-content: flex-start; --gap: .9rem; }

/* Indicateur de défilement. */
/* Placé AU-DESSUS des crêtes, pas dessus. Il était en z-index 2 donc dessiné
   par-dessus la silhouette remplie en --snow : gris clair sur blanc cassé,
   parfaitement invisible. */
.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: calc(1.25rem + var(--ridge-h));
  transform: translateX(-50%);
  z-index: 2;
  color: rgba(244, 247, 249, .5);
  animation: bob 2.4s var(--ease) infinite;
}
.hero__scroll:hover { color: var(--white); }
.hero__scroll svg { width: 22px; height: 22px; }
@keyframes bob {
  0%, 100% { transform: translate(-50%, 0); }
  50%      { transform: translate(-50%, 8px); }
}

/* Crêtes en pied de hero, en 2 plans pour la profondeur. */
.hero__ridges {
  position: absolute;
  inset: auto 0 0;
  z-index: 1;
  pointer-events: none;
  line-height: 0;
}


/* ==========================================================================
   10. SERVICES
   ========================================================================== */

/* Une carte par ligne, à toutes les tailles d'écran : chaque service est une
   bannière pleine largeur, pas une vignette dans une mosaïque. `--min: 100%`
   suffit — la grille auto-fit de `.grid` ne peut plus former qu'une colonne,
   aucune media-query n'est nécessaire.
   ⚠️ Ne PAS réintroduire de `grid-column: span 2` ici : dans une grille à une
   colonne, il en créerait une deuxième, implicite. */
.services { --min: 100%; --gap: 1.25rem; }

/* Carte claire, texte sombre. Le fond montagne reste visible à droite, lavé
   par un voile blanc qui devient opaque sous la colonne de texte : la lisibi-
   lité ne dépend jamais du dessin qui passe derrière. */
.svc {
  /* Direction du dégradé du voile. Une seule variable pilote le sens de
     lecture de la carte : la basculer à son symétrique (265deg) suffit à
     renvoyer la zone opaque — donc le texte — de l'autre côté.
     Voir l'alternance en bas de section. */
  --veil-dir: 95deg;

  position: relative;
  min-height: 220px;
  padding: clamp(1.35rem, 1rem + 1.1vw, 2rem);
  border-radius: var(--r-lg);
  overflow: hidden;
  isolation: isolate;
  background: var(--white);
  color: var(--text);
  border: 1px solid var(--frost);
  /* Filet bois : structure la carte sans toucher à l'ambre, réservé aux CTA.
     C'est aussi l'écho du bandeau de sortie de section, lui à filet ambre. */
  border-left: 3px solid var(--wood);
  box-shadow: var(--sh-sm);
  transition: transform var(--t-lg), box-shadow var(--t-lg), border-color var(--t);
}
.svc:hover {
  transform: translateY(-4px);
  box-shadow: var(--sh);
  border-left-color: var(--wood-dark);
}

.svc__img {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.svc__img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* La carte est bien plus large que haute : on recadre sur la bande où se
     trouvent les crêtes, pas sur le ciel ni sur le pied du dessin. */
  object-position: 50% 60%;
  transition: transform .7s var(--ease);
}
.svc:hover .svc__img img { transform: scale(1.04); }

/* Voile par défaut (mobile) : le texte occupe toute la largeur, le dessin ne
   reste qu'en filigrane. Le dégradé horizontal n'arrive qu'à partir de 760px,
   quand il y a la place de dégager une zone d'image à droite. */
.svc__veil {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(255, 255, 255, .90);
  transition: background var(--t-lg);
}

/* Colonne de contenu. `stretch` (défaut) et surtout pas `flex-start` : un
   alignement au départ dimensionne chaque enfant sur son contenu, et les
   paragraphes débordent de la carte sur les petites largeurs. */
.svc__body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* Pastille + titre sur une même ligne, la pastille à gauche du h3. */
.svc__head {
  display: flex;
  align-items: center;
  gap: .85rem;
  margin-bottom: .7rem;
}

.svc__icon {
  /* La pastille est carrée et ne se comprime jamais : c'est le titre qui
     passe à la ligne quand la place manque, pas l'icône qui s'écrase. */
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--r);
  background: var(--frost-100);
  border: 1px solid var(--frost);
  color: var(--granite-600);
  transition: background var(--t), color var(--t);
}
.svc:hover .svc__icon { background: var(--white); color: var(--granite); }
.svc__icon svg { width: 1.5rem; height: 1.5rem; }

/* Marges portées par `.svc__head` — le titre n'en a plus besoin. */
.svc__title  { color: var(--granite-800); }
/* +25 % sur le petit corps, demandé par Maxime le 2026-08-04 : .875rem × 1.25
   = 1.09375rem, soit 17,5px. Écrit en `calc()` et non en dur pour garder le
   lien avec le token — le facteur dit d'où vient la valeur, ce qu'un
   `1.09375rem` orphelin dans la feuille ne dirait pas.
   ⚠️ Le paragraphe des cartes est désormais PLUS GRAND que le corps de texte
   du site (`--fs-body`, 16,2px). C'est assumé : les cartes sont des bannières
   pleine largeur, c'est le contenu principal de la section. Ne pas en déduire
   qu'il faut aligner les autres petits textes (pastilles, footer, sous-titre
   du bandeau de sortie) — eux restent en `--fs-sm`. */
.svc__text   { font-size: calc(var(--fs-sm) * 1.25); color: var(--text-muted); margin-bottom: 1.1rem; }

.svc__tags   { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: 1.25rem; }
.svc__tag {
  padding: .25rem .7rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--frost);
  background: rgba(255, 255, 255, .7);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--granite-600);
}

/* La carte entière est cliquable, mais le lien reste unique pour les
   lecteurs d'écran et pour Google (pas de lien imbriqué). */
.svc__overlay-link {
  position: absolute;
  inset: 0;
  z-index: 3;
}
.svc:hover .arrow-link { color: var(--amber-600); gap: .8rem; }

/* À partir de 760px la carte se lit en deux zones : le texte d'un côté, les
   crêtes de l'autre. La largeur du texte est bornée par la même valeur que le
   palier opaque du voile — les deux doivent rester synchronisées.

   ⚠️ Le `max-width` est porté par `.svc__body`, PAS par ses enfants. C'est ce
   qui garantit que titre, paragraphe, pastilles et lien partagent exactement
   le même bord : borner chaque enfant séparément les laisserait se dimensionner
   sur leur contenu dès qu'on décale la colonne (voir l'alternance ci-dessous),
   et les bords gauches partiraient en escalier. */
@media (min-width: 760px) {
  .svc__body { max-width: min(72ch, 54%); }
  .svc__veil {
    background: linear-gradient(var(--veil-dir),
      rgba(255, 255, 255, .97)  0%,
      rgba(255, 255, 255, .95) 56%,
      rgba(255, 255, 255, .62) 74%,
      rgba(255, 255, 255, .22) 90%,
      rgba(255, 255, 255, .08) 100%);
  }
  .svc:hover .svc__veil {
    background: linear-gradient(var(--veil-dir),
      rgba(255, 255, 255, .97)  0%,
      rgba(255, 255, 255, .94) 56%,
      rgba(255, 255, 255, .52) 74%,
      rgba(255, 255, 255, .12) 90%,
      rgba(255, 255, 255, .02) 100%);
  }
}

/* ── Alternance sur grand écran ─────────────────────────────────────────────
   Une carte sur deux se lit en miroir : texte à droite, crêtes à gauche.
   Création et Référencement gardent le texte à gauche, Refonte et Autres
   développements le passent à droite.

   Quatre choses basculent ensemble — en oublier une rend le texte illisible,
   posé sur la partie transparente du voile :
     1. le sens du dégradé, via `--veil-dir` : 265deg est le symétrique exact
        de 95deg (360 − 95), et la variable évite de recopier les deux
        dégradés (état de repos + survol) à l'envers ;
     2. la colonne de texte, poussée par une marge auto ;
     3. la pastille, qui passe de l'autre côté du titre ;
     4. le filet bois, qui marque toujours le bord du texte.

   Le filet permute ses LARGEURS (3px ↔ 1px) au lieu d'en ajouter un second :
   le total horizontal des bordures reste de 4px, donc les cartes conservent
   au pixel près la même largeur et rien ne se décale dans la grille. */
@media (min-width: 1024px) {
  /* ── Coupe en diagonale (demandée par Maxime le 2026-08-25) ───────────────
     L'arête du CÔTÉ DESSIN devient oblique : la carte n'est plus un rectangle
     mais un quadrilatère qui suit la pente des crêtes. Comme tout le reste de
     l'alternance, elle est en miroir une carte sur deux — le biais tombe donc
     toujours du côté opposé au texte.

     ⚠️ Le point le plus RENTRANT est à 64 % (36 % pour les paires), alors que
     la colonne de texte est bornée à 54 % : c'est cette marge de 10 points qui
     garantit qu'aucun mot ne se fait couper. Creuser la diagonale sans
     resserrer `.svc__body` mangerait le texte.

     ⚠️ Grand écran UNIQUEMENT. Sous 1024px la carte n'a plus deux zones — le
     dessin n'est qu'un filigrane derrière le texte pleine largeur — et une
     coupe y rognerait des phrases. Elle reste rectangulaire, c'est voulu.

     ⚠️ **RIEN DE PEINT HORS DE LA BOÎTE NE SURVIT.** Le `clip-path` s'applique
     APRÈS le `filter` et après le calcul du `box-shadow` : l'ombre portée
     comme un `drop-shadow` sont découpés avec le reste. Une première version
     dessinait le filet oblique en `drop-shadow` net — invisible pour cette
     raison exacte. Le seul contournement serait un élément enveloppe portant
     l'ombre autour de l'enfant détouré, mais `.grid.services` ne doit contenir
     que les 4 `<article>` (l'alternance repose sur `:nth-child`). **Les cartes
     n'ont donc pas d'ombre portée au-delà de 1024px, c'est assumé** : `--sh-sm`
     est à .05 d'opacité, et le survol garde son soulèvement, son filet bois
     qui fonce et sa pastille qui s'inverse.

     ⚠️ La `border` est peinte sur le RECTANGLE : la découpe la tranche, et
     l'arête oblique restait nue pendant que les trois autres gardaient leur
     filet. Il est donc redessiné par `.svc::after`, un liseré détouré en
     parallélogramme qui longe la diagonale PAR L'INTÉRIEUR — donc à l'abri du
     `clip-path` du parent, exactement comme une bordure est à l'intérieur de
     sa boîte.

     `--svc-edge` est la largeur HORIZONTALE du liseré, pas son épaisseur : la
     diagonale penche de 44 à 47° selon la hauteur de la carte, l'épaisseur vue
     perpendiculairement vaut donc 1,5 × cos(pente) ≈ 1,05px, soit le
     `1px solid var(--frost)` des autres bords. ⚠️ Ne pas le convertir en
     « épaisseur » : c'est la pente qui fait la conversion, et elle dépend de
     la hauteur du contenu. */
  .svc {
    --svc-edge: 1.5px;

    clip-path: polygon(0 0, 64% 0, 92% 100%, 0 100%);
    box-shadow: none;
  }
  .svc:hover { box-shadow: none; }

  .svc::after {
    content: '';
    position: absolute;
    inset: 0;
    /* Au-dessus du dessin (-2) et du voile (-1), qui sinon le recouvriraient.
       `pointer-events: none` : le lien de survol de la carte est en z-index 3,
       ce liseré ne doit lui voler aucun clic. */
    z-index: 2;
    pointer-events: none;
    background: var(--frost);
    clip-path: polygon(64% 0, 92% 100%,
                       calc(92% - var(--svc-edge)) 100%,
                       calc(64% - var(--svc-edge)) 0);
  }
  /* ⚠️ La diagonale des cartes paires n'est PAS le miroir de celle des
     impaires — c'est la seule des cinq bascules de l'alternance qui ne
     s'inverse pas, et c'est voulu (demandé par Maxime le 2026-08-25, croquis
     à l'appui). Les deux pentes sont IDENTIQUES : 28 % de course horizontale
     sur toute la hauteur, du haut vers le bas-droite. La carte impaire perd
     son coin haut-DROIT (64 % → 92 %), la paire son coin bas-GAUCHE
     (0 % → 28 %). Résultat : deux obliques strictement parallèles qui
     rythment la section, là où un miroir aurait dessiné un chevron.
     Un miroir reste possible en repassant à `(36% 0, 100% 0, 100% 100%,
     8% 100%)` — mais ce n'est pas ce qui a été demandé. */
  .svc:nth-child(even) {
    clip-path: polygon(0 0, 100% 0, 100% 100%, 28% 100%);
    --veil-dir: 265deg;
  }
  /* L'intérieur de la carte est à DROITE de cette oblique : le liseré se pose
     donc en décalant vers la droite, comme sur les impaires. */
  .svc:nth-child(even)::after {
    clip-path: polygon(0 0, 28% 100%,
                       calc(28% + var(--svc-edge)) 100%,
                       var(--svc-edge) 0);
  }

  .svc:nth-child(even) .svc__body { margin-inline-start: auto; }

  /* Pastille à droite du titre — le miroir va jusqu'au bout : l'icône reste
     collée au bord du texte, donc au bord droit de la carte.
     `row-reverse` inverse aussi le début de l'axe principal : la rangée se
     tasse spontanément à droite, aucun `justify-content` à ajouter.
     Sans effet sur l'ordre de lecture : les SVG d'`icons.include.php` sortent
     en `aria-hidden="true" focusable="false"`, ils ne sont ni annoncés ni
     atteignables au clavier — l'ordre du DOM reste icône puis titre. */
  .svc:nth-child(even) .svc__head { flex-direction: row-reverse; }

  /* ── ⚗️ TEST du 2026-08-04 — tout le contenu au fer à droite ───────────────
     Bloc volontairement isolé et sans autre dépendance : le SUPPRIMER EN
     ENTIER ramène le texte au fer à gauche, dans une colonne toujours décalée
     à droite (l'état d'avant). Rien d'autre à défaire.

     Trois déclarations parce que trois natures de boîtes, et `text-align` ne
     couvre que la première :
       · titre et paragraphe = du contenu en ligne → `text-align` ;
       · les pastilles sont un conteneur flex → `justify-content`, `text-align`
         n'a aucune prise sur des items de flex ;
       · le lien fléché aussi : son `inline-flex` est blockifié en `flex` du
         fait qu'il est enfant de `.svc__body`, également un flex.

     ⚠️ Le point faible du rendu est le PARAGRAPHE, pas le reste : au fer à
     droite, le bord gauche devient irrégulier, et c'est ce bord que l'œil
     cherche à chaque retour à la ligne. Sur trois lignes ça se sent déjà.
     Pour garder le titre, les pastilles et le lien à droite mais rendre le
     paragraphe lisible, il suffit d'ajouter :
         .svc:nth-child(even) .svc__text { text-align: left; }             */
  .svc:nth-child(even) .svc__body  { text-align: right; }
  .svc:nth-child(even) .svc__tags  { justify-content: flex-end; }
  .svc:nth-child(even) .arrow-link { justify-content: flex-end; }

  .svc:nth-child(even) {
    border-left-width:  1px;
    border-left-color:  var(--frost);
    border-right-width: 3px;
    border-right-color: var(--wood);
  }
  /* `.svc:hover` rallume le filet à gauche : il faut le neutraliser ici,
     sinon un liseré bois apparaît du mauvais côté au survol. */
  .svc:nth-child(even):hover {
    border-left-color:  var(--frost);
    border-right-color: var(--wood-dark);
  }
}

/* Sortie de section — volontairement détaché des quatre cartes qui précèdent :
   ce n'est pas un cinquième service, c'est la porte de sortie. Quatre écarts
   assumés, aucun décoratif :
     · un blanc au-dessus bien plus large que le `--gap` de la grille (1.25rem),
       pour que l'œil comprenne que la liste est finie avant de lire le bloc ;
     · une surface SABLE là où les cartes sont blanches — c'est la seule
       rupture chaud/froid de la section, et elle annonce le sable de la
       section « à propos » ;
     · aucun filet latéral : le filet est la signature d'une carte. L'ambre en
       filet était de surcroît une entorse à la règle « ambre = ce qui se
       clique » — il ne reste que sur le bouton, qui ressort d'autant plus ;
     · aucune ombre : les cartes flottent et se soulèvent au survol, ce bandeau
       est encastré dans la page et ne réagit pas.

   ⚠️ Le sable est le fond le plus foncé de la section : `--text-muted` sur
   `--sand-50` tient à 4,65:1, au-dessus du seuil AA (4,5:1) exigé par le
   `--fs-sm` du sous-titre. Assombrir ce fond ferait passer le texte en
   dessous — il faudrait alors descendre le texte à `--granite-600`. */
.svc-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem 2rem;
  margin-top: clamp(2.5rem, 1.6rem + 2.6vw, 4.5rem);
  padding: clamp(1.75rem, 1.2rem + 1.8vw, 2.75rem);
  border-radius: var(--r);
  background: var(--sand-50);
  border: 1px solid var(--sand);
}
.svc-cta__text  { min-width: min(100%, 28ch); }
.svc-cta__title { color: var(--granite-800); }
.svc-cta__sub {
  margin-top: .4rem;
  max-width: 60ch;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.svc-cta__sub strong { color: var(--granite-800); font-weight: 700; }

/* Sous 560px, le bouton prend toute la largeur : cible tactile pleine. */
@media (max-width: 559px) {
  .svc-cta .btn { width: 100%; justify-content: center; }
}


/* ==========================================================================
   11. À PROPOS
   ========================================================================== */

/* Grille en aires plutôt qu'en colonnes : c'est ce qui permet à la photo et
   au titre de partager la première ligne sur mobile — le corps du texte passe
   dessous, pleine largeur — puis à la photo de reprendre une colonne pleine
   hauteur sur grand écran, sans toucher à l'ordre du DOM. */
.about__grid {
  display: grid;
  align-items: center;
  grid-template-columns: clamp(96px, 26vw, 150px) minmax(0, 1fr);
  grid-template-areas:
    "media head"
    "body  body";
  column-gap: clamp(1rem, .5rem + 2vw, 1.75rem);
  row-gap: clamp(1.5rem, 1rem + 2vw, 2.25rem);
}
.about__media { grid-area: media; }
.about__head  { grid-area: head; }
.about__body  { grid-area: body; }

/* Sous 900px le titre partage sa ligne avec la photo : à la taille h2 pleine
   il se briserait en cinq lignes dans une colonne de ~220px. */
@media (max-width: 599px) {
  .about__head .h2 { font-size: clamp(1.3rem, 1.05rem + 1.15vw, 1.6rem); }
  .about__head .eyebrow { margin-bottom: .55rem; }
}
/* Entre 600 et 900px la colonne de droite devient large : le titre y
   reprend sa taille pleine, et la photo peut respirer un peu. */
@media (min-width: 600px) and (max-width: 899px) {
  .about__grid {
    grid-template-columns: clamp(150px, 24vw, 240px) minmax(0, 1fr);
    column-gap: clamp(1.75rem, 1rem + 2vw, 2.5rem);
  }
}

@media (min-width: 900px) {
  .about__grid {
    /* Colonne photo nettement resserrée : à .9fr le portrait occupait presque
       la moitié de la section pour une seule image. */
    grid-template-columns: minmax(0, .55fr) minmax(0, 1.45fr);
    grid-template-areas:
      "media head"
      "media body";
    column-gap: clamp(2rem, 1rem + 4vw, 4.5rem);
    row-gap: 1.1rem;
    align-items: start;
  }
  /* La photo couvre les deux lignes : on la recentre sur leur hauteur totale. */
  .about__media { align-self: center; }
}

.about__media { position: relative; }
.about__media img {
  width: 100%;
  border-radius: var(--r-lg);
  box-shadow: var(--sh-lg);
  aspect-ratio: 4 / 5;
  object-fit: cover;
}
/* Cadre bois décalé — rappelle la matière sans texture photographique. */
.about__media::before {
  content: '';
  position: absolute;
  inset: 1.25rem -1.25rem -1.25rem 1.25rem;
  border: 2px solid var(--wood);
  border-radius: var(--r-lg);
  z-index: -1;
  opacity: .55;
}

/* Bouquetin en filigrane, ancré au coin de la photo. */
.about__ibex {
  position: absolute;
  right: -1.5rem;
  bottom: -2.5rem;
  width: clamp(90px, 14vw, 150px);
  color: var(--granite);
  opacity: .12;
  pointer-events: none;
}
/* icon() ne pose ni width ni height : sans cette règle le SVG retomberait
   sur la taille par défaut d'un objet remplacé (300 × 150). */
.about__ibex svg { width: 100%; height: auto; }

/* Cadre et bouquetin sont dimensionnés pour une photo large. Sur la vignette
   mobile, le décalage de 1.25rem passait sous le titre voisin et le bouquetin
   faisait la moitié de l'image : les deux sont ramenés à l'échelle. */
@media (max-width: 899px) {
  .about__media::before { inset: .6rem -.6rem -.6rem .6rem; }
  .about__ibex {
    right: -.5rem;
    bottom: -1rem;
    width: clamp(44px, 11vw, 70px);
  }
}

/* Panneau « notre méthode » — le seul bloc encadré de la section.
   Filet de bois à gauche plutôt qu'une bordure complète : la section est
   déjà sur fond sable, un cadre fermé y ferait une boîte dans une boîte.
   Le filet marque le bloc sans l'enfermer, et reste à angle vif. */
.about__method {
  margin-top: 2rem;
  padding: 1.4rem 1.6rem;
  border-left: 3px solid var(--wood);
  background: var(--white);
  box-shadow: var(--sh-sm);
  color: var(--text-muted);
}
.about__method-title {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  margin-bottom: .7rem;
  font-size: var(--fs-h3);
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -.015em;
  color: var(--text);
}
.about__method-icon {
  flex: none;
  /* Cale l'icône sur la première ligne du titre, pas sur son centre :
     le titre passe sur deux lignes dès que la colonne se resserre. */
  margin-top: .1em;
  color: var(--wood-dark);
}
.about__method-icon svg { width: 1.35rem; height: 1.35rem; }
.about__method strong { font-weight: 700; color: var(--text); }

/* Le filet garde la respiration qu'avait la signature avant elle : sans lui,
   le bouton se collait aux points forts. */
.about__cta {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--frost);
}

/* Points forts. */
.perks { --min: 240px; --gap: 1.25rem; margin-top: 2.25rem; }
.perk {
  display: flex;
  gap: .9rem;
  align-items: flex-start;
}
.perk__icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-sm);
  background: var(--white);
  border: 1px solid var(--frost);
  color: var(--granite-600);
  box-shadow: var(--sh-sm);
}
.perk__icon svg { width: 1.2rem; height: 1.2rem; }
/* +15 % sur `--fs-sm` : à 14px les quatre points forts se lisaient comme une
   note de bas de page alors qu'ils portent les arguments de vente de la
   section. 14 × 1.15 = 16,1px, soit le corps de texte du site. En `calc()`
   comme `.svc__text`, pour garder le lien avec le token. */
.perk__title { font-weight: 700; font-size: calc(var(--fs-sm) * 1.15); margin-bottom: .15rem; }
.perk__text  { font-size: calc(var(--fs-sm) * 1.15); color: var(--text-muted); line-height: 1.5; }


/* ==========================================================================
   12. RÉALISATIONS
   --------------------------------------------------------------------------
   Une galerie, pas une grille de cartes de plus. Trois principes :

     1. LE CADRE NAVIGATEUR fait tout le travail de crédibilité. Sans lui une
        capture n'est qu'une image ; avec la barre d'adresse et le domaine
        écrit en clair, c'est un site en ligne. Les pastilles sont CARRÉES —
        les rondes des vrais navigateurs sont le seul arrondi qui aurait pu
        s'inviter dans la feuille, et ce n'était pas une raison suffisante.

     2. FOND SOMBRE, IMAGES CLAIRES. Chaque capture arrive avec les couleurs
        de son propre site (le vert de NutriEz, etc.). Sur le granite elles
        se détachent comme des tirages sur un mur, et leurs teintes ne se
        battent plus avec le fond de page.

     3. LE SURVOL FAIT DÉFILER LA PAGE, pas l'image. `object-position` animé
        de haut en bas : sur une capture pleine page, le visiteur voit le site
        se dérouler. C'est le seul effet du site qui montre du CONTENU au lieu
        de décorer. Sur une capture au format du cadre, il ne se passe
        strictement rien — d'où le choix de `object-position` plutôt qu'un
        `translateY`, qui, lui, aurait décollé l'image de son cadre.
   ========================================================================== */

.realisations { --min: 300px; --gap: clamp(1.25rem, .9rem + 1.2vw, 2rem); }

/* ── CARROUSEL SOUS LE SEUIL DES 3 COLONNES ────────────────────────────────
   Demande du 2026-08-27 : dès que l'écran ne peut plus aligner les trois
   réalisations, on ne casse plus la grille en 2 puis 1 colonne — on garde
   les trois sur UNE SEULE LIGNE et on fait défiler horizontalement.

   ⚠️ Cette règle REMPLACE l'ancien centrage de la carte orpheline
   (`:last-child:nth-child(odd)`, fenêtre 690–1000px). Il n'y a plus de
   disposition à deux colonnes du tout, donc plus d'orpheline possible :
   l'ancienne règle était intégralement recouverte par celle-ci. Ne pas la
   réintroduire sans supprimer le carrousel d'abord.

   BORNE PRISE À LA MESURE, comme l'ancienne — et le premier chiffre était
   FAUX, pour une raison qui mérite d'être écrite :

     ⚠️ UNE MEDIA QUERY SE COMPARE À `innerWidth`, BARRE DE DÉFILEMENT
        INCLUSE. La grille, elle, ne dispose que de `clientWidth`, barre
        EXCLUE. Les deux diffèrent de la largeur de la barre (~15px sous
        Windows, 0 avec les barres en surimpression de macOS).

   Mesuré ici : la grille perd sa 3e colonne dès que `clientWidth` descend
   sous ~1004px. Avec une barre de 15px, cela arrive à `innerWidth` ≈ 1019 —
   soit AU-DESSUS d'une borne posée à 1010, qui laissait donc passer une
   bande où la grille tombait à 2 colonnes avec une 3e carte orpheline.
   (Relevés : innerWidth 1018 → clientWidth 1003 → 2 colonnes ;
              innerWidth 1024 → clientWidth 1009 → 3 colonnes.)

   D'où 1030px, choisi pour couvrir le pire cas (barre large ~20px) : à
   1031px de `innerWidth`, il reste au moins 1011px de `clientWidth`, assez
   pour les trois colonnes. Le prix est une bande de ~5px où l'on affiche un
   carrousel là où la grille aurait tenu — sans conséquence, les trois cartes
   restent sur une ligne. Le défaut inverse, lui, serait visible.
   ⚠️ Si `--min` ou les gouttières changent, CETTE BORNE EST À REMESURER.

   ZÉRO JAVASCRIPT : `overflow-x` + `scroll-snap` suffisent. Si le script de
   la page échoue, le carrousel fonctionne toujours — même principe que
   l'accordéon en <details> et que `.reveal`.

   ⚠️ `overflow-x: auto` fait de la piste un CONTENEUR DE DÉFILEMENT, et
   force `overflow-y` à `auto` avec lui. D'où le `padding-block` : sans lui,
   le `translateY(-6px)` du survol et l'ombre des cartes seraient rognés en
   haut et en bas. (C'est aussi ce mécanisme qui avait neutralisé le
   `position: sticky` de `.contact__aside` — ici aucun sticky à l'intérieur,
   donc rien à craindre.)

   ACCESSIBILITÉ : chaque carte porte un `.rea__overlay-link` réel. La
   tabulation passe donc de carte en carte et le navigateur les amène dans le
   champ tout seul — inutile de rendre la piste focusable, ce qui ajouterait
   un arrêt de tabulation même en mode grille.
   `prefers-reduced-motion` est déjà couvert par la règle globale de fin de
   feuille, qui force `scroll-behavior: auto !important`. */
@media (max-width: 1030px) {
  .realisations {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* La piste déborde jusqu'aux bords du conteneur : une carte coupée au ras
       du cadre se lit comme « ça continue », alors qu'une carte coupée à
       l'intérieur d'une marge se lit comme un défaut de mise en page. */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    /* Sans quoi une carte amenée par le clavier se collerait au bord. */
    scroll-padding-inline: var(--gutter);
    padding-block: 1rem;
    /* Un geste vertical amorcé sur la piste doit continuer à faire défiler la
       PAGE, et le défilement horizontal ne doit pas remonter à la page. */
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, .28) transparent;
  }

  /* Une carte pleine + l'amorce de la suivante. C'est l'amorce qui signale
     qu'il y en a d'autres — bien mieux qu'une barre de défilement, invisible
     par défaut sur mobile. */
  .realisations > * {
    flex: 0 0 clamp(268px, 84%, 400px);
    scroll-snap-align: start;
  }

  /* ⚠️ `.reveal` NEUTRALISÉ ICI, et c'est délibéré. L'IntersectionObserver
     n'observe que le viewport, pas le défilement interne de la piste : la
     3e carte, hors champ horizontalement, resterait à `opacity: 0` puis se
     révélerait EN COURS DE BALAYAGE — un fondu de 0,7s déclenché au moment
     précis où le doigt l'amène à l'écran. Sur une piste horizontale, pas
     d'animation vaut mieux qu'une animation en retard.
     Sélecteur préfixé `.js` pour passer devant `.js .reveal` (§ 16), qui est
     déclaré PLUS BAS dans la feuille et gagnerait à spécificité égale. */
  .js .realisations > .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

.rea {
  /* Ratio de la fenêtre de capture. 2/1 = ce que voit réellement un écran
     large une fois la barre du navigateur déduite, et c'est aussi le format
     des captures déjà en stock (800 × 392). Une capture pleine page, bien
     plus haute, est recadrée sur son sommet puis défile au survol. */
  --shot-ar: 2 / 1;

  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--granite-800);
  border: 1px solid rgba(255, 255, 255, .12);
  /* Le filet bois, comme sur les cartes de service : c'est la signature d'une
     carte sur ce site. Il porte ici la part chaude d'une section qui n'en a
     aucune autre. */
  border-left: 3px solid var(--wood);
  box-shadow: var(--sh);
  transition: transform var(--t-lg), box-shadow var(--t-lg), border-color var(--t);
}
.rea:hover {
  transform: translateY(-6px);
  box-shadow: var(--sh-lg);
  border-color: rgba(255, 255, 255, .22);
  border-left-color: var(--wood-dark);
}

/* ── Barre du navigateur ─────────────────────────────────────────────────── */
.rea__bar {
  display: flex;
  align-items: center;
  gap: .65rem;
  flex: none;
  padding: .5rem .7rem;
  background: var(--granite-900);
  border-bottom: 1px solid rgba(255, 255, 255, .10);
}
.rea__dots { display: flex; gap: .3rem; flex: none; }
.rea__dots i {
  width: .45rem;
  height: .45rem;
  background: rgba(255, 255, 255, .22);
  transition: background var(--t);
}
.rea:hover .rea__dots i { background: rgba(255, 255, 255, .34); }

/* Barre d'adresse. `min-width: 0` est indispensable : sans lui, un enfant de
   flex refuse de descendre sous la largeur de son contenu et le domaine le
   plus long (private-ski-instructor.com) élargirait la carte au lieu d'être
   tronqué. */
.rea__url {
  flex: 1;
  min-width: 0;
  padding: .2rem .6rem;
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .08);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--glacier-300);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Le cadenas du navigateur — en `shield`, faute de cadenas dans le jeu
   d'icônes, et le sens passe très bien. `inline-block` et non `flex` : le
   texte doit pouvoir être tronqué par l'ellipse, ce qu'un conteneur flex
   empêcherait. */
.rea__url svg {
  display: inline-block;
  width: .85em;
  height: .85em;
  margin-right: .35em;
  vertical-align: -.14em;
  color: var(--glacier);
  opacity: .8;
}

/* ── Capture ─────────────────────────────────────────────────────────────── */
.rea__shot {
  position: relative;
  aspect-ratio: var(--shot-ar);
  overflow: hidden;
  background: var(--granite-900);
}
/* Le filet intérieur évite qu'une capture à fond blanc se fonde dans la
   bordure de la carte : il redonne un bord à l'image. */
.rea__shot::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(11, 22, 32, .18);
}
.rea__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 0;
  /* Lent à dessein : c'est une page qu'on fait défiler, pas une transition
     d'interface. En dessous d'une seconde, l'effet se lit comme un saut. */
  transition: object-position 2.4s var(--ease);
}
.rea:hover .rea__shot img { object-position: 50% 100%; }

/* La règle globale de `prefers-reduced-motion` ramène toutes les durées à
   0,01ms : sans cette reprise, le défilé ne serait pas supprimé mais joué
   d'un coup — exactement le mouvement brutal que la préférence écarte. */
@media (prefers-reduced-motion: reduce) {
  .rea:hover .rea__shot img { object-position: 50% 0; }
}

/* ── Texte ───────────────────────────────────────────────────────────────── */
.rea__body {
  display: flex;
  flex-direction: column;
  flex: 1;                 /* les cartes d'une même rangée finissent alignées */
  padding: clamp(1.15rem, .95rem + .5vw, 1.5rem);
}

/* Même rôle que `.eyebrow`, sans son filet : à l'intérieur d'une carte, un
   trait de plus ferait doublon avec le filet bois du bord gauche. */
.rea__sector {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--glacier-300);
  margin-bottom: .35rem;
}
.rea__title { color: var(--white); margin-bottom: .5rem; }
.rea__text  { font-size: var(--fs-sm); color: var(--text-invert-muted); margin-bottom: 1.1rem; }

/* `margin-top: auto` colle le lien au bas de la carte : les trois flèches
   s'alignent quelles que soient les longueurs de texte au-dessus. */
.rea__go { margin-top: auto; }

.rea__overlay-link { position: absolute; inset: 0; z-index: 3; }
.rea:hover .arrow-link { color: var(--amber-300); gap: .8rem; }

/* Une carte sans lien (site injoignable, `url => null`) ne doit pas faire
   croire qu'elle est cliquable. */
.rea:not(:has(.rea__overlay-link)):hover { transform: none; box-shadow: var(--sh); }
.rea:not(:has(.rea__overlay-link)) { cursor: default; }

/* Ligne de sortie — volontairement pas un bandeau : la section « à propos »
   juste au-dessus se termine déjà sur un bouton, un second bloc d'appel
   ferait insistance. */
.rea-note {
  margin-top: clamp(2rem, 1.4rem + 1.6vw, 3rem);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--text-invert-muted);
}
.rea-note a {
  color: var(--white);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: var(--wood);
}
.rea-note a:hover { color: var(--amber-300); text-decoration-color: var(--amber-300); }


/* ==========================================================================
   13. FAQ
   --------------------------------------------------------------------------
   Accordéon en <details>/<summary> natifs : ouverture au clavier, restitution
   correcte par les lecteurs d'écran et zéro ligne de JavaScript. Le contenu
   d'un <details> fermé reste dans le DOM, donc indexé par Google — c'est
   justement ce qu'il faut pour que les réponses soient reprises en extrait.
   ========================================================================== */

.faq__grid {
  display: grid;
  gap: clamp(1.75rem, 1rem + 3vw, 3.5rem);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 940px) {
  .faq__grid { grid-template-columns: .85fr 1.15fr; }
  .faq__aside { position: sticky; top: 6.5rem; }
}

.faq__list { border-top: 1px solid var(--frost); }

.faq__item {
  border-bottom: 1px solid var(--frost);
}

.faq__item summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.25rem;
  padding: 1.25rem 0;
  cursor: pointer;
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: -.015em;
  color: var(--granite-800);
  list-style: none;
  transition: color var(--t);
}
/* Retire le triangle par défaut (WebKit d'un côté, standard de l'autre). */
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::marker { content: ''; }

.faq__item summary:hover { color: var(--amber-600); }

/* Chevron dessiné en CSS : deux traits, aucun asset. */
.faq__item summary::after {
  content: '';
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: .1rem;
  border-radius: var(--r-sm);
  background: var(--frost-100)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%231E3A4C' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E")
    center / 1rem no-repeat;
  transition: transform var(--t), background-color var(--t);
}
.faq__item[open] summary::after {
  transform: rotate(180deg);
  background-color: var(--sand);
}

.faq__answer {
  padding-bottom: 1.4rem;
  max-width: 62ch;
  color: var(--text-muted);
  line-height: 1.7;
}
.faq__answer strong { color: var(--granite-800); font-weight: 700; }

/* Repli si la question reste sans réponse dans la liste. */
.faq__cta {
  margin-top: 2rem;
  padding: 1.5rem;
  border-radius: var(--r);
  background: var(--white);
  border: 1px solid var(--frost);
  box-shadow: var(--sh-sm);
}
.faq__cta p { font-size: var(--fs-sm); color: var(--text-muted); margin-bottom: 1rem; }


/* ==========================================================================
   14. FORMULAIRE DE CONTACT
   ========================================================================== */

.contact__grid {
  display: grid;
  gap: clamp(1.75rem, 1rem + 3vw, 3rem);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 940px) {
  .contact__grid { grid-template-columns: .8fr 1.2fr; }
  /* La colonne d'infos suit le défilement du formulaire, plus long. */
  .contact__aside { position: sticky; top: 6.5rem; }
}

/* — Fond photo de la section ————————————————————————————————————————————
   `isolation` est indispensable : la photo et le voile sont posés en
   z-index négatif, sans contexte d'empilement propre ils passeraient
   DERRIÈRE le fond du <body>. */
.contact-section {
  position: relative;
  /* ⚠️ `clip` et surtout PAS `hidden` : overflow:hidden fait de la section
     un conteneur de défilement, ce qui neutralise le position:sticky de
     .contact__aside — la colonne de coordonnées cessait de suivre le
     défilement du formulaire. `clip` découpe sans créer ce conteneur.
     Sur un navigateur qui ne connaît pas `clip`, la déclaration est
     ignorée : rien ne déborde ici de toute façon (la photo est en
     inset:0 + object-fit), et le sticky continue de fonctionner. */
  overflow: clip;
  isolation: isolate;
  /* Fond de repli : c'est lui qu'on voit si la photo tarde ou échoue, et
     c'est la teinte dans laquelle est remplie la crête de la section FAQ
     juste au-dessus. */
  background: var(--granite);
}

.contact__media { position: absolute; inset: 0; z-index: -3; }
.contact__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 45%;
}

/* Voile — deux exigences contradictoires à tenir en même temps :
     · le HAUT doit être granite opaque, sinon la crête de la section
       précédente (remplie en --granite) laisse une couture visible ;
     · le MILIEU doit laisser voir la photo, sans quoi autant garder
       l'aplat de couleur.
   D'où le dégradé vertical qui s'ouvre vite après le raccord. */
.contact__veil {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(180deg,
    var(--granite) 0%,
    rgba(22, 41, 58, .90) 9%,
    rgba(11, 22, 32, .62) 34%,
    rgba(11, 22, 32, .68) 100%);
}
@media (min-width: 940px) {
  /* En deux colonnes, la gauche porte du texte clair posé sur la photo et
     la droite une carte blanche opaque : on assombrit donc surtout la
     gauche, et la photo respire là où elle ne gêne personne. */
  .contact__veil {
    background:
      linear-gradient(90deg,
        rgba(11, 22, 32, .45) 0%,
        rgba(11, 22, 32, .26) 48%,
        rgba(11, 22, 32, 0) 100%),
      linear-gradient(180deg,
        var(--granite) 0%,
        rgba(22, 41, 58, .90) 9%,
        rgba(11, 22, 32, .40) 34%,
        rgba(11, 22, 32, .52) 100%);
  }
}

.contact__card {
  padding: clamp(1.5rem, 1rem + 2vw, 2.75rem);
  border-radius: var(--r-lg);
  background: var(--white);
  box-shadow: var(--sh-lg);
  border: 1px solid var(--frost-100);
  /* La section est en .on-dark (texte quasi blanc). La carte, elle, est
     blanche : sans cette ligne tout ce qui n'impose pas sa propre couleur
     hérite du blanc et devient invisible — c'était le cas des cartes du
     champ « Votre projet ». */
  color: var(--text);
}

.info-item { display: flex; gap: 1rem; align-items: flex-start; }

/* Sous 940px la colonne d'infos passe AU-DESSUS du formulaire : les deux blocs
   non actionnables (lieu, délai) disparaissent pour ne pas le repousser d'un
   écran. Email et téléphone restent — eux se cliquent. L'information n'est pas
   perdue : le footer porte la ville et le délai de réponse.
   ⚠️ Cette règle doit rester APRÈS `.info-item { display: flex }` — même
   spécificité, c'est l'ordre du fichier qui tranche. */
@media (max-width: 939.98px) {
  .info-item--aside { display: none; }
}
.info-item__icon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--r-sm);
  background: var(--granite);
  /* ⚠️ Écart assumé à « l'ambre ne va que sur ce qui se clique », demandé par
     Maxime le 2026-08-25 : le bloc coordonnées est justement l'endroit où l'on
     veut que l'œil tombe. Le glyphe seul passe à l'ambre — la pastille reste
     granite, sinon quatre aplats ambre voleraient la vedette au bouton
     d'envoi, qui est le seul vrai CTA de la section. 3,84:1 sur le granite,
     au-dessus du seuil de 3:1 des objets graphiques. */
  color: var(--amber);
}
.info-item__icon svg { width: 1.15rem; height: 1.15rem; }
.info-item__label { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .1em; color: var(--text-muted); font-weight: 700; }
/* Sur la photo du formulaire, `--text-muted` (gris ardoise) tombait sous 2:1 :
   le libellé était le maillon faible du bloc coordonnées, juste au-dessus
   d'une valeur déjà blanche. Il passe au blanc — la hiérarchie tient encore
   par la taille et les capitales, elle n'avait pas besoin du gris. */
.on-dark .info-item__label { color: var(--white); }

/* Soulignement ambre des libellés — demandé par Maxime le 2026-08-25, en même
   temps que les glyphes ambre. Le texte reste BLANC : l'ambre ne tient pas le
   contraste en petit texte sur la photo (2,5:1), alors qu'en filet il n'a
   qu'un seuil d'objet graphique à passer. C'est aussi ce qui garde le bloc
   sobre — quatre libellés entièrement ambre concurrenceraient le bouton
   d'envoi, seul vrai CTA de la section.
   `skip-ink: none` : le filet ne doit pas s'interrompre, c'est un trait tiré
   sous le mot, pas un souligné de traitement de texte. Les capitales n'ont de
   toute façon aucune jambage descendant à éviter. */
.info-item__label {
  text-decoration: underline solid var(--amber);
  text-decoration-thickness: 2px;
  text-underline-offset: .45em;
  text-decoration-skip-ink: none;
}
.info-item__value { font-weight: 600; }
.info-item a.info-item__value:hover { color: var(--amber-600); }

/* — Champs ———————————————————————————————————————————————————————————— */

.form-grid {
  display: grid;
  gap: 1.15rem;
  grid-template-columns: repeat(2, 1fr);
}
.field--full { grid-column: 1 / -1; }
@media (max-width: 620px) {
  .form-grid { grid-template-columns: 1fr; }
}

.field { display: flex; flex-direction: column; gap: .4rem; }

/* Chrome traite <legend> comme une boîte spéciale : dans un conteneur flex
   son rendu devient imprévisible. Le fieldset reste donc en bloc. */
fieldset.field { display: block; }

.field label,
.field legend {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--granite-800);
}
.field .req { color: var(--amber-600); }
.field .hint { font-size: var(--fs-xs); font-weight: 500; color: var(--text-muted); }

.input {
  width: 100%;
  padding: .8rem 1rem;
  border: 1.5px solid var(--frost);
  border-radius: var(--r-sm);
  background: var(--snow);
  font-size: var(--fs-sm);
  transition: border-color var(--t), box-shadow var(--t), background-color var(--t);
}
.input::placeholder { color: #93A7B5; }
.input:hover { border-color: var(--glacier-300); }
.input:focus {
  outline: none;
  background: var(--white);
  border-color: var(--granite-600);
  box-shadow: 0 0 0 3px rgba(94, 168, 196, .25);
}
textarea.input { resize: vertical; min-height: 140px; line-height: 1.6; }

select.input {
  appearance: none;
  padding-right: 2.5rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' stroke='%235B7285' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
}

/* Erreur : posée par main.js sur le .field, pas sur l'input — le message et
   la bordure sont pilotés par le même état. */
.field .error {
  display: none;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: #C0392B;
}
/* ⚠️ `background-color`, PAS le raccourci `background` : celui-ci remet
   `background-image` à `none` et le chevron de `select.input` disparaissait
   dès que le champ passait en erreur. Invisible tant qu'aucun <select> ne
   vivait dans un `.field` — c'est le cas depuis le sélecteur « Votre projet ». */
.field.is-invalid .input { border-color: #C0392B; background-color: #FDF3F2; }
.field.is-invalid .error { display: block; }

/* — Choix de projet en cartes radio ————————————————————————————————————
   Plus engageant qu'un <select> et plus rapide à remplir au pouce :
   la cible tactile fait toute la carte. */
.choices { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.choice { position: relative; }
.choice input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}
.choice__box {
  display: flex;
  align-items: center;
  gap: .6rem;
  height: 100%;
  padding: .8rem .9rem;
  border: 1.5px solid var(--frost);
  border-radius: var(--r-sm);
  background: var(--snow);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.25;
  /* Couleur explicite, jamais héritée : la carte est posée dans une section
     .on-dark, et un simple héritage rendait le libellé blanc sur --snow. */
  color: var(--granite-800);
  transition: border-color var(--t), background-color var(--t), color var(--t);
}
.choice__box svg { width: 1.15rem; height: 1.15rem; flex: none; color: var(--granite-400); transition: color var(--t); }
.choice input:hover + .choice__box { border-color: var(--glacier-300); }
.choice input:focus-visible + .choice__box { box-shadow: 0 0 0 3px rgba(94, 168, 196, .35); }
.choice input:checked + .choice__box {
  border-color: var(--granite);
  background: var(--granite);
  color: var(--white);
}
.choice input:checked + .choice__box svg { color: var(--glacier-300); }

/* — Curseur de budget —————————————————————————————————————————————————— */
.range-head { display: flex; justify-content: space-between; align-items: baseline; }
.range-value { font-weight: 800; color: var(--granite); font-size: 1.05rem; }

input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  border-radius: var(--r-sm);
  background: var(--frost);
  outline: none;
}
input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
  background: var(--amber);
  border: 3px solid var(--white);
  box-shadow: var(--sh-sm);
  cursor: pointer;
  transition: transform var(--t);
}
input[type='range']::-webkit-slider-thumb:hover { transform: scale(1.15); }
input[type='range']::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: var(--r-sm);
  background: var(--amber);
  border: 3px solid var(--white);
  box-shadow: var(--sh-sm);
  cursor: pointer;
}

/* Repères sous la piste.
   ------------------------------------------------------------------------
   ⚠️ CE N'EST PAS UNE GRILLE DE COLONNES ÉGALES, et c'en était une jusqu'au
   2026-08-25 : « une colonne par cran, chaque repère au milieu de la sienne »
   sonnait juste et était faux. Le milieu de la colonne i vaut (i + 0,5)/n de
   la largeur, alors que le cran i d'un input[type=range] tombe à i/(n-1) de
   la COURSE de la pastille. Deux lois différentes : mesuré à 1280px, chaque
   repère se retrouvait 26 à 38px à gauche de son cran, et la pastille posée
   sur « 2 000 € » s'affichait entre les repères 2000€ et 2500€.

   La course, elle, ne commence pas à 0 : la pastille fait 22px et son centre
   ne va que de 11px à W-11px. D'où les deux règles ci-dessous.

     1. Le rang est INSET de la moitié d'une pastille de chaque côté, donc sa
        boîte de contenu est exactement la course.
     2. Chaque repère est un élément de LARGEUR NULLE — un point, pas une
        case — dont le texte déborde symétriquement. `space-between` place
        alors les points à i/(n-1) de la course : la définition même du cran.
        Aucun compte n'est écrit ici, ajouter un palier ne demande rien.
   ------------------------------------------------------------------------ */
.range-ticks {
  display: flex;
  justify-content: space-between;
  padding-inline: 11px;              /* moitié de la pastille (22px) */
  margin-top: .5rem;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text-muted);
  /* Chiffres de même chasse : les repères ne dansent pas d'un cran à l'autre. */
  font-variant-numeric: tabular-nums;
}
.range-ticks li {
  width: 0;
  display: flex;
  justify-content: center;
  white-space: nowrap;
}
/* ⚠️ Les extrêmes sont centrés comme les autres — surtout PAS calés contre le
   bord du rang. Essayé : le dernier repère se retrouvait 22px à gauche de son
   cran (le seul encore faux après la correction) et venait toucher son voisin
   à 3,7px. Centré, il déborde de ~11px à droite du curseur : c'est un demi-
   libellé qui dépasse dans la gouttière, jamais hors de la page, et le prix
   de l'exactitude sur les deux crans que le visiteur atteint le plus vite. */

/* Neuf paliers (tranches de 500 €) ne tiennent pas sous 560px : mesuré à 375px,
   il ne reste 0px entre deux montants et le rang se lit comme un seul nombre.
   Un repère sur deux disparaît donc — restent ?, 1000€, 2000€, 3000€, 4000€+.

   ⚠️ `visibility: hidden` et surtout PAS `display: none` : une colonne
   supprimée redistribuerait la largeur, et les repères restants glisseraient
   hors des crans qu'ils désignent. Masqué, chacun garde sa colonne.

   Rien n'est perdu au passage : le montant exact du cran choisi s'affiche en
   toutes lettres au-dessus de la piste (`.range-value`), là où le doigt est. */
@media (max-width: 559px) {
  .range-ticks li:nth-child(even) { visibility: hidden; }
}

/* — Consentement RGPD ———————————————————————————————————————————————————
   flex-direction explicite : .consent porte aussi la classe .field, qui
   impose `column`. Sans ce reset, la case se retrouverait au-dessus de son
   libellé au lieu d'être à côté. */
.consent { display: flex; flex-direction: row; gap: .7rem; align-items: flex-start; }
.consent input {
  flex: none;
  width: 1.2rem;
  height: 1.2rem;
  margin-top: .18rem;
  accent-color: var(--granite);
  cursor: pointer;
}
.consent label { font-size: var(--fs-xs); font-weight: 500; color: var(--text-muted); line-height: 1.55; }
.consent a { color: var(--granite-600); text-decoration: underline; text-underline-offset: 2px; }

/* Piège à robots : invisible pour l'humain, présent pour le DOM.
   Ni display:none ni visibility:hidden — les bots récents les détectent. */
.hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* — États d'envoi ——————————————————————————————————————————————————————— */
.alert {
  display: none;
  gap: .75rem;
  align-items: flex-start;
  padding: 1rem 1.15rem;
  margin-bottom: 1.5rem;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  border: 1px solid;
}
.alert.is-visible { display: flex; }
.alert svg { width: 1.25rem; height: 1.25rem; flex: none; margin-top: .1rem; }
.alert--ok    { background: #ECFAF3; border-color: #A7E0C4; color: #16613D; }
.alert--error { background: #FDF3F2; border-color: #F0BDB6; color: #9E2B1E; }

.btn[aria-busy='true'] { pointer-events: none; opacity: .75; }
.btn__spinner { display: none; width: 1.05em; height: 1.05em; animation: spin .7s linear infinite; }
.btn[aria-busy='true'] .btn__spinner { display: block; }
.btn[aria-busy='true'] .btn__icon    { display: none; }
@keyframes spin { to { transform: rotate(360deg); } }

/* — Carte de la zone d'intervention ——————————————————————————————————————
   Carte blanche posée sur le fond photo, comme le formulaire : les deux
   blocs se lisent alors comme deux volets du même bloc « nous contacter ».

   Plus étroite que le reste de la section, et centrée : à 1200 px de large
   la carte devenait le plus gros élément de la page alors qu'elle n'est
   qu'un repère de localisation. Le formulaire doit rester ce qu'on voit
   en premier. */
.map {
  max-width: 760px;
  margin: clamp(2rem, 1.5rem + 2vw, 3.5rem) auto 0;
  border-radius: var(--r-lg);
  background: var(--white);
  border: 1px solid var(--frost-100);
  box-shadow: var(--sh-lg);
  overflow: hidden;
  color: var(--text);
}

.map__head { padding: clamp(1.25rem, 1rem + 1vw, 1.75rem); }
.map__areas {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.map__areas strong { color: var(--granite-800); font-weight: 700; }
.map__areas a {
  color: var(--granite-600);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}
.map__areas a:hover { color: var(--granite-800); }

/* Hauteur fixe plutôt qu'un ratio : un 16/9 sur 760 px faisait encore
   430 px de haut, et une carte n'a pas besoin de ça pour situer une
   vallée. La hauteur étant réservée d'avance, l'iframe n'introduit
   aucun décalage de mise en page (CLS = 0). */
.map__frame {
  height: clamp(200px, 26vw, 300px);
  border-top: 1px solid var(--frost-100);
  /* Fond visible le temps que Google réponde — le motif de courbes de
     niveau du site plutôt qu'un rectangle blanc. */
  background: var(--frost-100) url('../img/topo.svg') repeat center / 420px;
}

.map__frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}


/* ==========================================================================
   15. PIED DE PAGE
   ========================================================================== */

.footer {
  position: relative;
  padding-block: clamp(3rem, 2rem + 3vw, 4.5rem) 2rem;
  background: var(--ink);
  color: var(--text-invert-muted);
  font-size: var(--fs-sm);
}
.footer__grid {
  display: grid;
  gap: 2.5rem;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  margin-bottom: 3rem;
}
.footer__grid > :first-child { grid-column: span 1; min-width: 240px; }
/* Depuis le 2026-08-25 le pied de page ne compte plus que QUATRE blocs (la
   colonne « Zones d'intervention » a fusionné avec « Navigation »). Le
   `grid-column: span 2` de la marque faisait alors 5 cellules pour 4 colonnes
   auto-fit : « Contact » tombait seul sur une deuxième rangée, avec un trou au
   milieu de la première. Quatre colonnes explicites règlent les deux. */
@media (min-width: 900px) {
  .footer__grid { grid-template-columns: 1.5fr 1fr 1fr 1fr; }
  .footer__grid > :first-child { grid-column: auto; max-width: 380px; }
}

.footer__logo { height: 42px; width: auto; margin-bottom: 1rem; }
.footer__title {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--white);
  margin-bottom: 1rem;
}
.footer a { transition: color var(--t); }
.footer a:hover { color: var(--white); }
.footer li + li { margin-top: .5rem; }

/* Colonne « Contact » : l'icône et son libellé forment une seule ligne
   insécable. `flex: none` sur le SVG, sinon un libellé long le comprime. */
.footer__line {
  display: inline-flex;
  /* `flex-start` et pas `center` : « Notre fiche Google — Saint-Gervais-les-Bains »
     passe sur deux lignes dans une colonne de 254 px, et une icône centrée sur
     deux lignes flotte entre les deux. La marge la cale sur la première. */
  align-items: flex-start;
  gap: .55rem;
}
.footer__line svg {
  flex: none;
  margin-top: .2em;
  width: 1rem;
  height: 1rem;
  color: var(--glacier);
}
.footer a.footer__line:hover svg { color: var(--white); }

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding-top: 1.75rem;
  border-top: 1px solid rgba(255, 255, 255, .1);
  font-size: var(--fs-xs);
}

.to-top {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: var(--r-sm);
  background: var(--granite);
  color: var(--white);
  box-shadow: var(--sh);
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition: opacity var(--t), transform var(--t), visibility var(--t), background-color var(--t);
}
.to-top.is-visible { opacity: 1; visibility: visible; transform: translateY(0); }
.to-top:hover { background: var(--amber); }
.to-top svg { width: 1.15rem; height: 1.15rem; }


/* ==========================================================================
   16. ANIMATIONS D'APPARITION
   --------------------------------------------------------------------------
   L'état masqué n'est appliqué QUE si <html class="js"> — cette classe est
   posée par main.js dès son exécution. Si le JS échoue ou est bloqué, rien
   n'est jamais masqué : le contenu reste visible. La V1 posait opacity:0
   depuis le JS lui-même, ce qui rendait la page blanche en cas d'erreur.
   ========================================================================== */

.js .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* Cascade légère à l'intérieur d'une grille. */
.js .reveal[data-delay='1'] { transition-delay: .08s; }
.js .reveal[data-delay='2'] { transition-delay: .16s; }
.js .reveal[data-delay='3'] { transition-delay: .24s; }
.js .reveal[data-delay='4'] { transition-delay: .32s; }


/* ==========================================================================
   17. PAGES INTÉRIEURES (légales, et plus tard services / blog)
   ========================================================================== */

.page-head {
  position: relative;
  /* Même règle que le hero : la marge basse réserve la hauteur des crêtes. */
  padding-block: clamp(7rem, 5rem + 6vw, 10rem) calc(clamp(2.5rem, 1.5rem + 3vw, 4rem) + var(--ridge-h));
  background: var(--granite-900);
  color: var(--white);
  text-align: center;
  overflow: hidden;
  isolation: isolate;
}
.page-head .container { position: relative; }
.page-head h1 { margin-bottom: .6rem; }
.page-head__back {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  margin-bottom: 1.5rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--glacier-300);
}
.page-head__back:hover { color: var(--white); }
.page-head__back svg { width: 1em; height: 1em; transform: rotate(180deg); }
.page-head__ridges { position: absolute; inset: auto 0 0; z-index: 1; line-height: 0; }
/* Boutons sous le chapô des pages services (2026-10-08) : devis + réalisations. */
.page-head__actions { justify-content: center; margin-top: 1.75rem; }

/* Corps de texte long — mesure limitée à ~72 caractères par ligne. */
.prose {
  max-width: 72ch;
  margin-inline: auto;
  line-height: 1.8;
}
.prose h2 {
  margin-top: 2.75rem;
  margin-bottom: .75rem;
  padding-bottom: .45rem;
  border-bottom: 2px solid var(--frost);
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--granite);
}
.prose h2:first-child { margin-top: 0; }

/* Pastille posée DANS un <h2> du .prose — « optionnel » sur « Hébergement et
   maintenance ». Sable et bois, jamais d'ambre : rien ici ne se clique, et
   l'ambre est réservé aux CTA.
   `font-size` en `em` : la pastille suit le titre si sa taille change.
   Granite 600 sur sable 50 = 7,3:1, largement au-dessus du seuil AA malgré
   les ~13px du libellé. */
.prose h2 .h2__note {
  display: inline-block;
  margin-left: .55rem;
  padding: .2em .6em;
  border: 1px solid var(--sand);
  border-radius: var(--r-sm);
  background: var(--sand-50);
  font-size: .62em;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--granite-600);
  vertical-align: .2em;
  white-space: nowrap;
}
.prose h3 { margin-top: 1.75rem; margin-bottom: .4rem; font-size: 1.05rem; font-weight: 700; }
/* Icône en tête d'un <h3> du .prose. Posée en ligne plutôt qu'en flex sur le
   h3 : tous les autres h3 du site sont sans icône, les passer en conteneur
   flex changerait leur rendu pour rien.

   ⚠️ `display: inline-block` OBLIGATOIRE — la réinitialisation en tête de
   feuille pose `img, svg, video { display: block }`. Sans cette ligne l'icône
   occupe sa propre ligne au-dessus du titre, qui double de hauteur (38,6px au
   lieu de 19,3px, mesuré). Le même piège attend toute icône ajoutée dans du
   texte courant.

   Taille en `em` (elle suit le titre) et couleur bois — l'accent chaud de
   l'identité, 4,4:1 sur la neige, au-dessus du seuil de 3:1 des objets
   graphiques. Surtout pas d'ambre : ces titres ne se cliquent pas. */
.prose h3 svg {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  margin-right: .45rem;
  vertical-align: -.2em;
  color: var(--wood-dark);
}
.prose p  { margin-bottom: 1rem; }
.prose ul { margin: 0 0 1rem 1.25rem; }
.prose li { position: relative; padding-left: 1rem; margin-bottom: .35rem; }
.prose li::before {
  content: '';
  position: absolute;
  left: 0;
  top: .72em;
  width: 6px;
  height: 6px;
  background: var(--wood);
}
/* :not(.btn) — un bouton posé dans un .prose garde ses propres couleurs : sans ça,
   .prose a (0,2) l'emportait sur .btn--primary (0,1) et repeignait le CTA en
   granite souligné. */
.prose a:not(.btn) { color: var(--granite-600); text-decoration: underline; text-underline-offset: 3px; }
.prose a:not(.btn):hover { color: var(--amber-600); }

/* — Bloc tarif ————————————————————————————————————————————————————————————
   Le seul bloc sombre du corps d'une page service, posé HORS du `.prose` :
   il occupe toute la largeur du conteneur là où le texte est bridé à 72ch,
   et c'est ce décalage qui le fait trancher autant que la couleur.

   Montage repris de la section contact de l'accueil : photo en fond absolu,
   voile en dégradé par-dessus, texture topo en `.tx-topo` par-dessus encore.
   L'empilement dépend de `isolation: isolate` — media (-3) et voile (-2)
   passent SOUS le `::before` de .tx-topo (-1) sans jamais sortir de la boîte.

   Aucun ambre ici, alors que c'est un bloc « commercial » : rien ne s'y clique.
   L'accent est le bois, cohérent avec les puces de liste et les icônes de h3. */
.pricing {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  margin: 2.75rem 0;
  background: var(--granite);
  box-shadow: var(--sh);
}
.pricing__media { position: absolute; inset: 0; z-index: -3; }
.pricing__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* 35 % : la ligne de crête tombe alors dans le tiers haut du bloc, là où
     le voile est le plus dense — on garde la silhouette sans noyer le texte. */
  object-position: center 35%;
}
.pricing__veil {
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(100deg,
    rgba(11, 22, 32, .93) 0%,
    rgba(11, 22, 32, .88) 42%,
    rgba(11, 22, 32, .58) 100%);
}

.pricing__body {
  display: grid;
  gap: clamp(1.1rem, .8rem + 1.6vw, 2.5rem);
  align-items: center;
  padding: clamp(1.5rem, 1.1rem + 1.8vw, 2.75rem);
}
@media (min-width: 720px) {
  /* `auto 1fr` et non deux colonnes égales : le montant prend ce qu'il lui
     faut, le texte récupère le reste. */
  .pricing__body { grid-template-columns: auto 1fr; }
}

/* Le montant. `.pricing__amount` est un <p> : un chiffre de prix n'est pas un
   titre, il n'a rien à faire dans le plan de la page. */
.pricing__amount {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  margin: 0;
  padding-left: clamp(.9rem, .6rem + 1vw, 1.4rem);
  border-left: 3px solid var(--wood);
}
.pricing__eyebrow {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--glacier-300);
}
.pricing__figure {
  font-size: clamp(2.6rem, 1.9rem + 3.4vw, 4.2rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--white);
  white-space: nowrap;
}
/* La devise reste attachée au nombre mais ne pèse pas autant que lui. */
.pricing__cur {
  margin-left: .12em;
  font-size: .45em;
  font-weight: 700;
  vertical-align: .55em;
  color: var(--glacier-300);
}

.pricing__text h2 {
  margin: 0 0 .5rem;
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: -.02em;
  /* Pas de filet sous le titre, contrairement aux h2 du .prose : la bordure
     bois du montant tient déjà le rôle de ligne dans ce bloc. */
}
.pricing__text p { margin: 0; max-width: 60ch; }
/* Bouton de devis au milieu de la page, là où le visiteur lit le prix. */
.pricing__text .pricing__cta { margin-top: 1.25rem; }

/* Variante SANS MONTANT — /autres-developpements, où aucun prix plancher n'a
   de sens (le même intitulé couvre une automatisation de quelques jours et une
   plateforme SaaS).

   Deux effets, et le second est le moins évident : la grille repasse à une
   colonne, ET le filet bois migre du montant vers le texte. `.pricing__amount`
   était le SEUL accent de ce bloc — sans report, la variante serait la seule
   surface du site à n'en porter aucun.

   Déclarée après la media query de `.pricing__body`, à spécificité égale :
   c'est l'ordre du fichier qui la fait gagner aux deux paliers. Ne pas la
   remonter plus haut. */
.pricing__body--noprice { grid-template-columns: none; }
.pricing__body--noprice .pricing__text {
  padding-left: clamp(.9rem, .6rem + 1vw, 1.4rem);
  border-left: 3px solid var(--wood);
}
/* Encadré d'information. */
.callout {
  display: flex;
  gap: .9rem;
  padding: 1.15rem 1.35rem;
  margin: 1.75rem 0;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  border-left: 4px solid var(--glacier);
  background: var(--white);
  box-shadow: var(--sh-sm);
}
/* `>` — l'icône propre au callout, pas celles qu'un bouton posé dedans
   transporte : sans ça, .callout svg (même spécificité que .btn svg, mais
   plus bas dans la feuille) repeignait la flèche du CTA en granite et la
   gonflait à 1.3rem. */
.callout > svg { width: 1.3rem; height: 1.3rem; flex: none; margin-top: .2rem; color: var(--granite-600); }
.callout p:last-child { margin-bottom: 0; }

/* Variante « texte + bouton » — les CTA de bas de page service. Le callout
   avait été dessiné pour « icône + texte » : avec deux paragraphes dans le même
   flex, le texte se retrouvait essoré dans une colonne d'une centaine de pixels
   sur mobile pendant que le bouton occupait la moitié de la boîte.

   Le paragraphe de texte reçoit une base de 24rem et la boîte a le droit de
   passer à la ligne : tant qu'elle tient 24rem + le bouton, la disposition
   côte à côte est conservée (c'est le cas en pleine largeur de .prose, mesuré
   454px + 249px à 1280) ; en dessous, le bouton bascule seul sous le texte.
   Le `gap` sert alors d'interligne, d'où la marge du 1er paragraphe à zéro.

   ⚠️ CLASSE À PART, et non `.callout` tout court : `flex-wrap: wrap` sur la
   variante à icône fait sauter le texte SOUS l'icône. Avec `wrap`, un item dont
   la base vaut `auto` — donc la largeur du paragraphe déplié — passe à la ligne
   au lieu de se comprimer. Testé, vu, corrigé : ne pas généraliser la règle. */
.callout--cta { flex-wrap: wrap; align-items: flex-start; }
.callout--cta > p:first-child { flex: 1 1 24rem; margin-bottom: 0; }


/* ==========================================================================
   18. BLOG
   --------------------------------------------------------------------------
   Les 8 articles viennent de la V1, où ils étaient habillés en Bootstrap.
   Rien n'est repris de ce balisage : les composants ci-dessous rejouent les
   mêmes rôles (encadré, énumération numérotée, tableau comparatif) dans le
   vocabulaire du site — angle vif, filet bois, granite sur froid.

   ⚠️ L'ambre reste absent de TOUTE cette section sauf au survol d'un lien.
      Un article ne contient qu'une seule chose à cliquer : son CTA de fin.
   ========================================================================== */

/* — Index /blog ————————————————————————————————————————————————————————— */

.posts { --min: 300px; --gap: 1.5rem; }

/* Même grammaire que `.svc` : carte blanche, filet bois à gauche, elle se
   soulève au survol. La différence est le bandeau — les cartes services ont
   une photo de fond, un article n'en a pas. C'est l'icône de sa rubrique, sur
   granite, qui tient le rôle du visuel. */
.post {
  position: relative;
  display: flex;
  flex-direction: column;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--white);
  border: 1px solid var(--frost);
  border-left: 3px solid var(--wood);
  box-shadow: var(--sh-sm);
  transition: transform var(--t-lg), box-shadow var(--t-lg), border-color var(--t);
}
.post:hover {
  transform: translateY(-4px);
  box-shadow: var(--sh);
  border-left-color: var(--wood-dark);
}

.post__band {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 132px;
  background: var(--granite-800);
  color: var(--glacier-300);
  transition: color var(--t);
}
.post__band svg { width: 2.75rem; height: 2.75rem; }
.post:hover .post__band { color: var(--glacier); }

/* `flex: 1` sur le corps : quelle que soit la longueur du chapeau, les liens
   « Lire l'article » d'une rangée restent alignés sur la même ligne. */
.post__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: clamp(1.2rem, 1rem + .7vw, 1.6rem);
}

.post__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .7rem;
  margin-bottom: .75rem;
}
.post__tag {
  padding: .25rem .7rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--frost);
  background: var(--frost-100);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--granite-600);
}
.post__time { font-size: var(--fs-xs); color: var(--text-muted); }

.post__title { font-size: var(--fs-h3); font-weight: 700; letter-spacing: -.015em; color: var(--granite-800); }
.post__text {
  flex: 1;
  margin: .5rem 0 1.15rem;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* La carte entière est cliquable, mais un seul lien existe pour les lecteurs
   d'écran et pour Google — comme sur `.svc` et `.rea`. */
.post__overlay-link { position: absolute; inset: 0; z-index: 3; }
.post:hover .arrow-link { color: var(--amber-600); gap: .8rem; }


/* — En-tête d'article ——————————————————————————————————————————————————— */

/* Rubrique, posée dans le `.page-head` sombre. Bordure et fond translucides
   plutôt que des teintes en dur : le bandeau porte une texture, une pastille
   opaque y ferait un timbre collé. */
.article-cat {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  margin-bottom: 1.1rem;
  padding: .3rem .85rem;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .08);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--glacier-300);
}
.article-cat svg { width: 1.1em; height: 1.1em; }

.article-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .35rem 1.4rem;
  margin-top: 1.15rem;
  font-size: var(--fs-sm);
  color: var(--text-invert-muted);
}
.article-meta > * { display: inline-flex; align-items: center; gap: .45rem; }
.article-meta svg { width: 1.05em; height: 1.05em; color: var(--glacier-300); }


/* — Encadré d'alerte ———————————————————————————————————————————————————— */

/* Variante de `.callout`. Le callout de base est glacier = information ;
   celui-ci est bois = attention. Deux teintes de l'identité, aucune couleur
   nouvelle, et surtout pas d'ambre : un encadré ne se clique pas. */
.callout--warn {
  border-left-color: var(--wood-dark);
  background: var(--sand-50);
}
.callout--warn > svg { color: var(--wood-dark); }


/* — Énumération numérotée ————————————————————————————————————————————————
   Remplace les 43 `<div class="step-number">1</div>` de la V1. Le numéro est
   produit par un compteur CSS sur un vrai `<ol>` : impossible de le
   désynchroniser en insérant une étape au milieu, et l'ordre reste porté par
   le HTML pour les lecteurs d'écran.

   ⚠️ `.prose .step::before` (0,3,1) prend le pas sur `.prose li::before`
      (0,2,1) : c'est ce qui remplace la puce carrée bois par la pastille
      numérotée. Ne pas alléger ce sélecteur. */
.steps {
  counter-reset: step;
  display: flex;
  flex-direction: column;
  gap: .85rem;
  margin: 1.75rem 0;
}
.prose .step {
  counter-increment: step;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  align-items: start;
  gap: 0 1rem;
  padding-left: 0;
  margin-bottom: 0;
}
.prose .step::before {
  content: counter(step);
  position: static;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-sm);
  background: var(--granite);
  color: var(--white);
  font-size: var(--fs-sm);
  font-weight: 800;
}
.prose .step > :last-child { margin-bottom: 0; }

/* Variante encadrée — les étapes qui portent leur propre titre. */
.steps--cards .step {
  padding: 1.15rem 1.35rem;
  border-radius: var(--r);
  background: var(--white);
  border: 1px solid var(--frost);
  border-left: 3px solid var(--wood);
  box-shadow: var(--sh-sm);
}
.prose .steps--cards .step h3 { margin-top: 0; margin-bottom: .35rem; }


/* — Tableau comparatif ——————————————————————————————————————————————————
   `.table-wrap` défile horizontalement plutôt que de laisser le tableau
   pousser la page : la colonne de texte fait 72ch, un comparatif à trois
   colonnes n'y tient pas sur mobile. */
.table-wrap {
  overflow-x: auto;
  margin: 1.75rem 0;
  background: var(--white);
  border: 1px solid var(--frost);
  box-shadow: var(--sh-sm);
}
.table-wrap table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.table-wrap th {
  padding: .7rem .95rem;
  background: var(--granite-800);
  color: var(--white);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-align: left;
}
.table-wrap td {
  padding: .7rem .95rem;
  border-top: 1px solid var(--frost);
  vertical-align: top;
}
.table-wrap tbody tr:nth-child(even) { background: var(--frost-100); }


/* — Pastilles de noms ————————————————————————————————————————————————————
   La liste des moteurs génératifs. Aucune icône : le jeu local ne contient
   aucun logo de marque, et en dessiner serait contourner la règle « zéro
   ressource tierce » par la porte du graphisme. Les noms suffisent.

   Les deux dernières règles annulent l'héritage de `.prose li`. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
  margin: 1.25rem 0 1.75rem;
}
.prose .chip {
  padding: .3rem .85rem;
  margin-bottom: 0;
  border-radius: var(--r-sm);
  border: 1px solid var(--frost);
  background: var(--white);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--granite-600);
}
.prose .chip::before { content: none; }


/* — À lire aussi ————————————————————————————————————————————————————————— */

.post-nav { max-width: 72ch; margin-inline: auto; }
.post-nav__title {
  margin-bottom: 1.1rem;
  padding-bottom: .45rem;
  border-bottom: 2px solid var(--frost);
  font-size: 1.3rem;
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--granite);
}
.post-nav__list { display: flex; flex-direction: column; gap: .75rem; }
.post-nav__link {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.2rem;
  border-radius: var(--r);
  background: var(--snow);
  border: 1px solid var(--frost);
  border-left: 3px solid var(--wood);
  transition: background var(--t), border-color var(--t), box-shadow var(--t);
}
.post-nav__link:hover {
  background: var(--white);
  border-left-color: var(--wood-dark);
  box-shadow: var(--sh-sm);
}
.post-nav__icon {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-sm);
  background: var(--frost-100);
  border: 1px solid var(--frost);
  color: var(--granite-600);
}
.post-nav__icon svg { width: 1.25rem; height: 1.25rem; }
.post-nav__name { display: block; font-weight: 700; color: var(--granite-800); }
.post-nav__text { display: block; margin-top: .15rem; font-size: var(--fs-sm); color: var(--text-muted); }
.post-nav__go {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  color: var(--granite-400);
  transition: color var(--t), transform var(--t);
}
.post-nav__link:hover .post-nav__go { color: var(--amber-600); transform: translateX(4px); }

/* Sous 480px la flèche mange la largeur du texte : elle disparaît, la carte
   reste cliquable et le survol se lit déjà au fond et au filet. */
@media (max-width: 480px) {
  .post-nav__link { grid-template-columns: auto 1fr; }
  .post-nav__go { display: none; }
}


/* ==========================================================================
   19. PAGES D'ERREUR (403 · 404 · 500)
   --------------------------------------------------------------------------
   Elles reprennent le gabarit des pages légales : `.page-head` sombre, puis
   un `.prose`. Le seul ajout est le code d'erreur, posé au-dessus du titre.

   Aucun ambre hors du bouton de devis de la 404 : sur une page où le visiteur
   est perdu, une seule chose doit attirer l'œil.
   ========================================================================== */

/* Le code, dans le bandeau sombre. Il est décoratif — l'information est dans
   le `h1` juste dessous — d'où le glacier plutôt que le blanc : il pose le
   ton sans voler la vedette au titre.
   `tabular-nums` : les trois chiffres gardent la même chasse d'une page à
   l'autre, sinon le « 1 » de 404 ferait sauter le centrage optique. */
.error-code {
  display: block;
  margin-bottom: .25rem;
  font-size: clamp(4rem, 2.2rem + 9vw, 7.5rem);
  font-weight: 800;
  line-height: .95;
  letter-spacing: -.05em;
  font-variant-numeric: tabular-nums;
  color: var(--glacier-300);
}

/* Raccourcis vers les 4 services, sur la 404 uniquement. Même grammaire que
   `.post-nav__link` du blog — carte claire, filet bois à gauche, angle vif —
   en plus compact : ici il n'y a qu'un libellé à lire, pas un chapeau.
   Classe distincte et non partagée : les deux composants n'ont ni la même
   grille (2 colonnes contre 3) ni le même rôle, les fusionner obligerait à
   surcharger l'un des deux à chaque retouche. */
.error-nav {
  --min: 15rem;
  --gap: .75rem;
  margin: 1.75rem 0;
}
.error-nav__link {
  display: flex;
  align-items: center;
  gap: .9rem;
  height: 100%;
  padding: .9rem 1.1rem;
  border-radius: var(--r);
  background: var(--white);
  border: 1px solid var(--frost);
  border-left: 3px solid var(--wood);
  box-shadow: var(--sh-sm);
  font-weight: 700;
  color: var(--granite-800);
  transition: border-color var(--t), box-shadow var(--t), color var(--t);
}
.error-nav__link:hover {
  border-left-color: var(--wood-dark);
  box-shadow: var(--sh);
  color: var(--granite);
}
.error-nav__icon {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-sm);
  background: var(--frost-100);
  border: 1px solid var(--frost);
  color: var(--granite-600);
  transition: background var(--t), color var(--t);
}
.error-nav__link:hover .error-nav__icon { background: var(--snow); color: var(--granite); }
.error-nav__icon svg { width: 1.25rem; height: 1.25rem; }

/* La liste sort de la mesure de 72ch du `.prose` : deux colonnes de cartes
   n'ont pas à être bridées à la largeur confortable d'une ligne de texte.
   `list-style` est déjà à zéro par le reset ; la puce carrée bois de
   `.prose li`, elle, doit être annulée explicitement. */
.prose .error-nav { max-width: none; }
.prose .error-nav li { padding-left: 0; margin-bottom: 0; }
.prose .error-nav li::before { content: none; }


/* ==========================================================================
   20. ACCESSIBILITÉ & IMPRESSION
   ========================================================================== */

:focus-visible {
  outline: 3px solid var(--glacier);
  outline-offset: 2px;
}

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

/* Respect strict de la préférence système : plus aucune animation, et le
   contenu masqué en attente d'apparition est révélé immédiatement. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    /* Sans ça, une cascade (reveal, entrées du tiroir mobile) garderait ses
       délais : plus d'animation, mais une attente avant apparition. */
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  .js .reveal { opacity: 1; transform: none; }
}

@media print {
  .nav, .to-top, .hero__scroll, .ridge { display: none !important; }
  body { background: #fff; color: #000; }
  a::after { content: ' (' attr(href) ')'; font-size: .8em; }
}
