/* ============================================================
   Déneigement Alex Fillion (Joliette) — site client · LuxAura Studio, 2026
   Thème « JOUR ROYAL » : fond neige, bleu royal en accent, ambre de gyrophare
   en ponctuation. C'est l'INVERSE du thème sombre de la démo de référence dont
   ce site reprend la mécanique (blizzard canvas, séquence horizontale, Lenis,
   loader, curseur flocon).

   RÈGLE DU FICHIER : aucune couleur en dur hors du bloc :root ci-dessous.
   Ombres, dégradés, survols, scrim du héros et flocons du canvas passent tous
   par une variable — les flocons sont même LUS dans les tokens --flocon /
   --flocon-nuit par effets.js. Un changement de palette se fait ici, une fois.

   RENVERSEMENT DES TOKENS SUR LES FONDS BLEU PROFOND : au lieu de sur-écrire
   chaque règle, les blocs #pourquoi, .marquee et .pied redéfinissent LOCALEMENT
   --encre, --note, --vert-f et les bordures (voir « TOKENS INVERSES »). Tout ce
   qui vit dedans devient lisible sans qu'aucune règle ne le sache.
   ============================================================ */

/* ---------- Racine fluide ACCESSIBLE ----------
   1rem = 10 px @1728px, suit le réglage de police du navigateur (WCAG 1.4.4).
   Le vw sec (0.5787vw) casserait ce réglage. Échelle inchangée : c'est celle
   que le client a aimée dans la démo qu'on lui a montrée. */
html { font-size: clamp(0.611rem, 0.3125rem + 0.29vw, 0.72rem); }
@media (max-width: 991px) { html { font-size: clamp(0.611rem, 0.3125rem + 0.42vw, 0.72rem); } }
@media (max-width: 767px) { html { font-size: clamp(0.6rem, 0.47rem + 0.6vw, 0.72rem); } }
/* Les min des clamp garantissent les planchers CLAUDE.md en px RÉELS
   (corps ≥ 16 px mobile / ≥ 17,6 px desktop), quelle que soit la fenêtre. */

:root {
  /* ── Socle CLAIR ─────────────────────────────────────────────────────────
     Ratios CALCULÉS (WCAG 2.1, luminance relative sRGB), pas jugés à l'œil.
     Les trois paliers de fond sont : neige (page) · glace (blanc) · glace-2. */
  --neige: #f4f7fc;    /* [USAGE : fond général de la page] — bleu très pâle, jamais un blanc pur qui brûle */
  --glace: #ffffff;    /* [USAGE : fonds de cartes · texte des boutons pleins · loader] */
  --glace-2: #e8eef8;  /* [USAGE : fonds de sections alternées (Zone et coordonnées, CTA)] */
  --glace-3: #d9e3f3;  /* [USAGE : séparateurs pleins · fonds de champs au survol] */

  --encre: #0d1424;    /* [USAGE : tout le texte principal] — 17,1:1 sur --neige */
  --note: #3c4a63;     /* [USAGE : textes secondaires · légendes · kickers] — 8,3:1 sur --neige, OPAQUE (un gris en rgba se délave sur la photo du héros) */
  --bord: rgba(13, 20, 36, 0.14);      /* [USAGE : bordures fines · séparateurs] */
  --bord-fort: rgba(13, 20, 36, 0.38); /* [USAGE : bordures de boutons · champs de formulaire] */

  /* --nuit : le BLEU ROYAL PROFOND. C'est lui qui porte les blocs pleins
     (« Pourquoi nous », bandeau défilant, pied de page) et le scrim local du
     héros. Blanc dessus : 12,2:1. */
  --nuit: #0f2a8a;
  --nuit-rgb: 15, 42, 138;   /* composantes de --nuit, pour les rgba() (scrim du héros, raccords de bord) */
  --encre-rgb: 13, 20, 36;   /* composantes de --encre, pour les rgba() */

  /* ── Accent : BLEU ROYAL (couleur de marque demandée au questionnaire) ────
       #1f3fd6 sur --neige #f4f7fc ......... 7,14:1   (AA texte normal ✓)
       #1f3fd6 sur --glace #ffffff ......... 7,66:1   ✓
       #1f3fd6 sur --glace-2 #e8eef8 ....... 6,57:1   ✓
       #1f3fd6 sur --glace-3 #d9e3f3 ....... 5,92:1   ✓
       texte --glace #ffffff SUR bouton plein #1f3fd6 ... 7,66:1  (AA ✓)
       texte --glace #ffffff SUR bouton survolé #1633b0 . 9,84:1  ✓ */
  --vert: #1f3fd6;     /* [USAGE : RÉSERVE — aligné sur l'accent pour qu'une surcharge de section ne réintroduise pas une 2e teinte] */
  --vert-f: #1f3fd6;   /* BLEU ROYAL · [USAGE : boutons pleins · liens · crochets · kickers · prix] */
  --vert-n: #1633b0;   /* bleu royal foncé · [USAGE : survol des liens et des traits] */
  /* Le BOUTON PLEIN garde le bleu royal même posé sur un bloc bleu profond (un
     bouton en glacier pâle y disparaîtrait) : il a sa propre paire de tokens,
     que le renversement plus bas ne touche pas. */
  --accent-bouton: #1f3fd6;    /* [USAGE : fond des boutons pleins] */
  --accent-bouton-n: #1633b0;  /* [USAGE : fond des boutons pleins au survol] */

  /* ── Ponctuation AMBRE (gyrophare) — 3 usages maximum sur la page ─────────
     #f9c134 est l'ambre de gyrophare : il tient 7,39:1 sur le bleu profond,
     mais seulement 1,54:1 sur le fond neige. Il ne sert donc QUE sur --nuit.
     Sur fond clair, l'ambre passe à --jaune-2 (#b87a10, 3,35:1 sur --neige) :
     même famille, valeur assombrie pour tenir le 3:1 des éléments non textuels
     (WCAG 1.4.11) — c'est lui qui peint les étoiles des avis. */
  --jaune: #f9c134;    /* [USAGE : ponctuation ambre sur les fonds bleu profond] */
  --jaune-2: #b87a10;  /* [USAGE : étoiles des avis, sur fond clair] — 3,35:1 sur --neige */
  --phare: #cfe3ff;    /* glacier pâle · [USAGE : accent et lueurs sur les fonds bleu profond] — 9,36:1 sur --nuit */

  /* ── Tokens INVERSES : ce que deviennent l'encre et l'accent quand ils
        tombent sur un fond --nuit (voir le bloc « TOKENS INVERSES » plus bas) */
  --encre-sur-nuit: #ffffff;   /* 12,2:1 sur --nuit */
  --accent-sur-nuit: #cfe3ff;  /* 9,36:1 sur --nuit */
  --filigrane: rgba(255, 255, 255, 0.13); /* [USAGE : le rappel géant en filigrane du pied de page] */
  /* Halo CLAIR au ras des glyphes — remplace l'ancien halo bleu nuit depuis que
     le héros montre la photo NUE et que son texte est en encre. */
  --halo-clair: rgba(255, 255, 255, 0.92);
  --halo-clair-2: rgba(255, 255, 255, 0.7);
  --halo-sombre: rgba(var(--nuit-rgb), 0.95);   /* option 2 du 2 sept. : texte blanc + halo sombre SERRÉ sur la photo nue */
  --halo-sombre-2: rgba(var(--nuit-rgb), 0.55);

  /* --rouge : GARDÉ en réserve pour l'urgence, aucun usage aujourd'hui. */
  --rouge: #c62134;    /* [USAGE : réserve] — 5,7:1 sur --neige */
  --rouge-f: #9c1928;  /* [USAGE : réserve] */
  --brique: #9c1928;   /* [USAGE : réserve] */

  /* ── Flocons du canvas (lus par effets.js) ───────────────────────────────
     Ils étaient blancs sur la nuit de la référence : sur un fond neige, ils
     disparaissaient. Bleu royal translucide sur les fonds clairs et sur la
     photo du héros ; glacier pâle sur le bloc bleu profond. */
  --flocon: rgba(31, 63, 214, 0.92);        /* [USAGE : blizzard des sections claires] — monté de .58 à .92 le 2 sept. (verdict Yannick : la neige bleue ne se voyait presque pas sur intro/services) */
  --flocon-nuit: rgba(207, 227, 255, 0.85); /* [USAGE : blizzard du bloc « Pourquoi nous »] */
  --flocon-hero: rgba(255, 255, 255, 0.88);  /* flocons BLANCS sur la photo du héros (verdict Yannick, 2 sept. 2026) : le bleu royal rendait le héros trop bleu */

  /* ── Typo (inchangée : Anton + Geist + Geist Mono) ── */
  --police-titre: 'Anton', 'Anton Repli', 'Arial Narrow', Impact, sans-serif; /* [USAGE : TOUS les titres · logo] */
  --police-corps: 'Geist', 'Inter', system-ui, sans-serif;           /* [USAGE : tout le texte courant] */
  --police-mono: 'Geist Mono', 'SF Mono', Consolas, monospace;       /* [USAGE : kickers · étiquettes · prix] */
  --h1-min: 5.4rem;     /* [USAGE : taille MINIMALE du grand titre du héros] */
  --h1-max: 11rem;      /* [USAGE : taille MAXIMALE du grand titre du héros] */
  --h1-graisse: 400;    /* Anton n'existe qu'en 400 (déjà noire) · [USAGE : graisse de tous les titres] */
  --h1-interlettre: -0.005em; /* [USAGE : espacement des lettres des titres] */
  --fs-corps: 2.63rem;  /* ≈ 18 px réels @1728 · [USAGE : paragraphes · descriptions] */
  --fs-note: 2.85rem;   /* [USAGE : notes · légendes · sous-textes] */
  --fs-ui: 2.58rem;     /* [USAGE : boutons · liens de navigation] */
  --fs-badge: 2rem;     /* [USAGE : kickers · mentions du pied de page] */

  /* ── Layout ── */
  --pad-container: 1.93rem;   /* [USAGE : marges latérales de toute la page] */
  --col-gap: 3.6rem;          /* [USAGE : écart entre les colonnes et cartes] */
  --radius: 1.97rem;          /* [USAGE : arrondi des cartes et des champs] */
  --bord-1: max(1px, 0.1rem); /* [USAGE : épaisseur de toutes les bordures] */
}

/* ═══ TOKENS INVERSES ═══
   Les trois blocs pleins en bleu royal profond. Ils ne sur-écrivent AUCUNE
   règle : ils redéfinissent les tokens, et tout ce qui vit dedans (texte,
   kickers, traits, bordures, liens) devient lisible tout seul. */
/* .hero-texte EST dans cette liste (verdict Yannick, 2 sept., option 2) : la
   photo reste NUE — ni scrim, ni voile, ni filtre — mais son texte est BLANC,
   et c'est un halo SOMBRE serré au ras des glyphes qui porte le contraste.
   L'option 1 (texte en encre + halo clair) a été écartée le même jour ; les
   tokens --halo-clair / --halo-clair-2 en sont les restes, plus utilisés. */
#pourquoi, .marquee, .pied, #chargement, .hero-texte {
  --encre: var(--encre-sur-nuit);
  --note: var(--accent-sur-nuit);
  --vert: var(--accent-sur-nuit);
  --vert-f: var(--accent-sur-nuit);
  --vert-n: var(--encre-sur-nuit);
  --bord: rgba(255, 255, 255, 0.18);
  --bord-fort: rgba(255, 255, 255, 0.45);
}


/* ---------- Polices auto-hébergées (bibliothèque Awwwards, OFL) ----------
   Aucune Google Fonts externe (règle CLAUDE.md) : 3 woff2 servis en local,
   41 Ko au total. */
@font-face {
  font-family: 'Anton';
  src: url('../fonts/anton.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
/* Repli MÉTRIQUE d'Anton, pour que le `swap` ne fasse pas sauter la page.
   CALIBRÉ AU NAVIGATEUR, pas au jugé. Protocole : une chaîne de capitales de
   35 signes à font-size 100px, line-height normal, getBoundingClientRect sur
   chaque famille. Relevé :
     Anton .............. 1415 px de large · boîte de ligne 151 px  ⟵ la cible
     Impact ............. 1501 px (+6 %)   · 122 px
     Arial Narrow ....... 2084 px (+47 %)  · 115 px
   Impact est donc le SEUL repli local honnête pour une condensée aussi serrée
   qu'Anton — Arial Narrow, malgré son nom, est presque 50 % plus large et
   ferait sauter toute la mise en page au swap. D'où l'ordre local() ci-dessous,
   inversé par rapport à la pile de la bibliothèque.
   Réglage retenu, vérifié par itération : size-adjust 95 % → 1417 px (cible
   1415, écart 0,1 %) · ascent 134 % + descent 25 % → 151 px (exact).
   ⚠️ Limite assumée : sur une machine SANS Impact (plusieurs Mac récents),
   local() retombe sur Arial Narrow et le repli redevient large. C'est le prix
   d'un repli local ; la vraie protection reste le <link rel=preload> d'Anton,
   qui fait que ce repli n'est visible que quelques dizaines de ms.
   Ce @font-face ne télécharge RIEN — il habille une police déjà installée. */
@font-face {
  font-family: 'Anton Repli';
  src: local('Impact'), local('Arial Narrow'), local('ArialNarrow');
  size-adjust: 95%;
  ascent-override: 134%;
  descent-override: 25%;
  line-gap-override: 0%;
  font-display: swap;
}
@font-face {
  font-family: 'Geist';
  src: url('../fonts/geist.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('../fonts/geist-mono.woff2') format('woff2');
  font-weight: 400 600;
  font-display: swap;
}

/* ---------- Reset court ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* `picture` est dans la liste : un wrapper <picture> est INLINE par défaut et
   pose un espace parasite sous l'image (source de CLS n°3 de la QA). Le
   <picture> du héros sortait bien en `inline` avant le 2 sept. — celui de la
   barre passait déjà en block, mais seulement parce que .nav-logo est un flex
   (blocification de l'enfant), donc par accident et non par règle. */
img, svg, video, canvas, picture { display: block; max-width: 100%; }
button { font: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; }
ul, ol { list-style: none; }

/* Le clip sur body seul laissait la boîte de défilement de html s'élargir :
   sur téléphone la page glissait latéralement (débordement du héros — attrapé
   la page glisse latéralement). Les deux niveaux doivent clipper. */
html { overflow-x: clip; }
body {
  font-family: var(--police-corps);
  font-size: var(--fs-corps);
  line-height: 1.62;
  color: var(--encre);
  background: var(--neige);
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

/* ---------- Échelle de titres (uppercase, interligne serré) ---------- */
.h0, .h1, .h2, .h3, .h4, h1, h2, h3 {
  font-family: var(--police-titre);
  font-weight: var(--h1-graisse);
  text-transform: uppercase;
  /* Un interligne de 1.05 clippe les accents FRANÇAIS : le É de RÉSIDENTIEL
     touche le kicker au-dessus. Le français exige plus d'air que l'anglais
     au-dessus des capitales. */
  /* Anton a des capitales très hautes et des accents qui montent haut : 1.06
     resserre le bloc SANS toucher aux accents (vérifié sur « ENTRÉE » et
     « RÉSIDENTIEL », les deux cas limites français). */
  line-height: 1.06;
  letter-spacing: var(--h1-interlettre);
}
.kicker + h1, .kicker + h2, .kicker + h3, .kicker + .h4 { margin-top: 1.4rem; }
/* La condensée rend ~30 % de largeur : on la rend en HAUTEUR. Les h2 restent
   bornés par les panneaux de 100vw de la séquence horizontale — 6.6rem est le
   plafond mesuré avant qu'un titre de section cherche une 3e ligne sur un
   portable 1366×768. */
.h0 { font-size: 16.5rem; }
h1, .h1 { font-size: clamp(var(--h1-min), 8vw, var(--h1-max)); }
h2, .h2 { font-size: 6.6rem; }
h3, .h3 { font-size: 4.5rem; }
.h4 { font-size: 3.5rem; }
@media (max-width: 991px) {
  .h0 { font-size: 10.4rem; }
  h2, .h2 { font-size: 5rem; }
  h3, .h3 { font-size: 3.6rem; }
}

/* Les kickers sont en mono : étiquette d'instrument, pas titre. */
.kicker {
  font-family: var(--police-mono);
  font-size: var(--fs-badge);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--note);
}
/* Le trait court devant l'étiquette : repère de relevé. */
.kicker::before {
  content: ''; display: inline-block; width: 2.4rem; height: 1px;
  margin-right: 1.2rem; vertical-align: 0.35em; background: var(--vert-f);
}
.note { font-size: var(--fs-note); color: var(--note); }

/* ---------- Grille 16 colonnes full-bleed ---------- */
.grille {
  display: grid;
  grid-template-columns:
    [pleine-deb] minmax(var(--pad-container), 1fr)
    [contenu-deb] repeat(16, minmax(0, 8.4rem))
    [contenu-fin] minmax(var(--pad-container), 1fr)
    [pleine-fin];
  column-gap: var(--col-gap);
}
.grille > * { grid-column: contenu-deb / contenu-fin; }
.pleine { grid-column: pleine-deb / pleine-fin; }

section { position: relative; }
/* La barre est FIXE : sans ce dégagement, une ancre dépose la section pile sous
   elle (mesuré : kicker à 69 px, bas de la barre à 68 px — 1 px de marge, et le
   kicker se lit collé au filet de la barre). Le dégagement suit l'échelle rem,
   donc il tient à toutes les largeurs. */
:target, main > [id], .piste-hiver [id] { scroll-margin-top: 12rem; }
/* Chaque section RÉ-ÉVALUE la couleur de texte localement : sans ça, la
   couleur héritée de body resterait figée et la vue « par section » de la
   tweaks bar ne pourrait pas re-teinter le texte d'UNE seule section. */
header.nav, main > [id], .piste-hiver [id], .hero-texte { color: var(--encre); }
/* (#pourquoi et footer.pied redéclarent déjà la leur plus bas — même effet) */
.section-pad { padding-block: 12rem; }
@media (max-width: 767px) { .section-pad { padding-block: 7rem; } }

/* ---------- Boutons (plat, prononcé) ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
  padding: 1.6rem 3rem;
  font-size: var(--fs-ui);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  /* Le bouton a son PROPRE rayon, une capsule pleine, au lieu de partager
     --radius avec les cartes et les champs. */
  border-radius: 999rem;
  border: var(--bord-1) solid transparent;
  transition: background-color 0.3s, color 0.3s, border-color 0.3s, transform 0.3s, box-shadow 0.3s;
  min-height: 4.4rem;
}
.btn-plein { background: var(--accent-bouton); color: var(--glace); }
/* Survol : le bouton se SOULÈVE avec un halo de sa propre couleur, au lieu du
   simple échange de fond. Le halo reste sous le seuil de la lueur radiale
   interdite en maquette (c'est une ombre de bouton, pas un fond de section). */
.btn-plein:hover {
  background: var(--accent-bouton-n); transform: translateY(-0.3rem);
  box-shadow: 0 0.8rem 2rem -0.4rem color-mix(in srgb, var(--accent-bouton) 45%, transparent);
}
.btn-contour { border-color: var(--bord-fort); color: var(--encre); }
.btn-contour:hover { border-color: var(--encre); transform: translateY(-0.3rem); }
/* :active APRÈS les :hover — à spécificité égale c'est le dernier qui gagne, et
   l'enfoncement doit l'emporter sur le soulèvement. */
.btn:active { transform: translateY(1px); box-shadow: none; }

:where(a, button, input, select, textarea, summary):focus-visible {
  outline: max(2px, 0.28rem) solid var(--encre);
  outline-offset: 0.25rem;
  border-radius: 0.3rem;
}

/* ---------- Utilitaires ---------- */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}
.sep-congere { width: 100%; height: auto; color: var(--nuit); }

/* ============================================================
   SECTIONS
   ============================================================ */

/* ---------- S0 Loader (bloc bleu profond — TOKENS INVERSES) ---------- */
#chargement {
  position: fixed; inset: 0; z-index: 90;
  background: var(--nuit);
  display: flex; align-items: flex-end; justify-content: space-between;
  padding: var(--pad-container);
}
#chargement { color: var(--encre); }
#chargement .chargement-compteur {
  font-family: var(--police-mono); color: var(--vert-f); line-height: 1;
}
.chargement-mots {
  display: flex; flex-direction: column; gap: 0.6rem; overflow: hidden;
  height: 3em; text-align: right; font-family: var(--police-mono); font-size: var(--fs-ui);
  text-transform: uppercase; letter-spacing: 0.18em; color: var(--note);
}
/* Ligne d'horizon sous le décompte : on voit la route se dégager. */
#chargement::after {
  content: ''; position: absolute; left: var(--pad-container); right: var(--pad-container);
  bottom: calc(var(--pad-container) - 1rem); height: 1px; background: var(--bord);
}

/* ---------- Nav ---------- */
/* La barre ne pose pas de bandeau opaque sur la photo du héros : transparente en
   haut de page, elle se voile en NEIGE dès qu'on défile (classe .nav-fond posée
   par premium.js). Tant qu'elle est transparente, elle repose sur le raccord
   sombre du haut de la photo : son texte y passe donc en clair (règle plus bas,
   desktop seulement — sous 1000 px la barre est toujours pleine). */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.8rem var(--pad-container);
  background: transparent;
  border-bottom: var(--bord-1) solid transparent;
  transition: background-color 0.4s, border-color 0.4s, backdrop-filter 0.4s;
}
.nav.nav-fond {
  background: color-mix(in srgb, var(--neige) 95%, transparent);
  /* Pas de backdrop-filter : recomposition GPU plein écran sur une nav fixe,
     pour un effet invisible sous une barre déjà opaque à 95 %. */
  border-bottom-color: var(--bord);
}
/* La barre transparente repose sur la photo NUE (le raccord sombre du haut a été
   retiré le 2 sept.) : son texte passe donc en BLANC, avec le même halo sombre
   que le texte du héros. Mesuré sur les pixels du ciel sous la barre : 5,29:1
   dans le pire cas, avant même l'apport du halo. */
@media (min-width: 1000px) {
  .nav:not(.nav-fond) a:not(.btn), .nav:not(.nav-fond) .nav-logo span {
    color: var(--encre-sur-nuit);
    text-shadow: 0 0 0.25rem var(--halo-sombre), 0 0 0.6rem var(--halo-sombre),
                 0 0 1.2rem var(--halo-sombre-2);
  }
}
.nav-logo {
  font-family: var(--police-titre); font-size: 2.8rem; letter-spacing: 0.02em;
  text-decoration: none; text-transform: uppercase;
}
.nav-logo-mot span { color: var(--vert-f); }
/* Le logo fourni est sur fond BLANC (pas de détourage possible sans manger la
   neige et les lettres argentées du badge) : on l'assume en plaque arrondie —
   lisible sur la photo du héros comme sur la barre voilée en neige. */
.nav-logo { display: flex; align-items: center; gap: 1.2rem; }
.nav-logo-img {
  height: 4.4rem; width: auto;
  background: var(--glace); border-radius: 0.9rem; padding: 0.3rem 0.6rem;
}
.nav nav { display: flex; align-items: center; gap: 2.8rem; }
.nav-liens { display: flex; align-items: center; gap: 2.8rem; }
.nav nav a:not(.btn) {
  font-family: var(--police-mono);
  font-size: var(--fs-ui); font-weight: 500; text-decoration: none;
  letter-spacing: 0.12em; text-transform: uppercase;
}
/* Le bouton du menu n'existe qu'en petite largeur (voir la media query). */
.nav-burger { display: none; }
.nav nav a:not(.btn):hover { color: var(--vert-f); }
.nav-cta { padding: 1rem 2rem; min-height: 0; }
/* ---------- Menu mobile ----------
   La barre desktop a besoin de ~1000 px pour tenir (bouton coupé à 768, logo sur
   deux lignes jusqu'à ~1000) : le burger couvre toute la bande 768-999. */
@media (max-width: 999px) {
  /* Sous 1000 px, la barre porte le menu déroulant : elle reste pleine en tout
     temps, sinon le panneau ouvert au-dessus de la photo n'aurait pas de fond. */
  .nav { background: color-mix(in srgb, var(--neige) 96%, transparent); border-bottom-color: var(--bord); }
  .nav-burger {
    display: inline-flex; flex-direction: column; justify-content: center;
    gap: 0.5rem; width: 4.4rem; height: 4.4rem; padding: 1rem;
    border: var(--bord-1) solid var(--bord); border-radius: var(--radius);
  }
  .nav-burger span {
    display: block; height: max(2px, 0.2rem); width: 100%;
    background: var(--encre); border-radius: 1px;
    transition: transform 0.28s, opacity 0.2s;
  }
  /* Trois barres qui deviennent une croix : l'état ouvert doit se voir. */
  .nav-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(0.7rem) rotate(45deg); }
  .nav-burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
  .nav-burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-0.7rem) rotate(-45deg); }

  .nav nav { gap: 1.2rem; }
  .nav-liens {
    position: absolute; top: 100%; left: 0; right: 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--neige);
    border-bottom: var(--bord-1) solid var(--bord);
    /* Fermé = replié à zéro, sans display:none : la transition reste possible
       et le contenu sort du parcours de tabulation grâce à visibility. */
    max-height: 0; overflow: hidden; visibility: hidden;
    transition: max-height 0.32s ease, visibility 0.32s;
  }
  .nav.menu-ouvert .nav-liens { max-height: 60vh; visibility: visible; }
  .nav-liens a {
    padding: 2rem var(--pad-container);
    border-top: var(--bord-1) solid var(--bord);
  }
}

/* ---------- S1 Héros ----------
   Photo absolue pleine section, texte par-dessus, PHOTO NUE (verdict Yannick,
   2 sept. 2026) : ni scrim, ni voile, ni filtre — les flocons du blizzard
   doivent rester visibles sur l'image. Le texte du héros est BLANC, et son
   contraste tient à un halo SOMBRE (--halo-sombre) au ras des glyphes.
   ⚠️ Chiffré à la QA du 2 sept. sur les pixels RÉELS de la photo : la neige au
   sol descend à 1,00:1 face au blanc. C'est le halo — et lui seul — qui remonte
   le voisinage immédiat de la lettre à ~12,2:1. Ne jamais alléger le halo sans
   remesurer ces pixels-là. */
.hero {
  min-height: 100svh; align-items: center; padding-top: 12rem; padding-bottom: 6rem;
  /* Fond de chargement en glace pâle : plus de bleu profond visible sous ou
     avant la photo (verdict Yannick, 2 sept.). */
  overflow: clip; background: var(--glace-3);
}
/* pleine-deb/pleine-fin : dans une grille, un enfant absolu s'ancre sur SA zone
   de grille — sans ça la photo restait dans les marges de contenu. Hauteur
   bornée au premier écran : c'est là que la machine doit être vue. */
.hero-fond {
  position: absolute; grid-column: pleine-deb / pleine-fin;
  inset: 0 0 auto 0; height: 100svh; z-index: 0; overflow: clip;
}
/* Le <picture> n'a pas de hauteur propre : sans cette ligne, le height:100% de
   l'img se résout contre un parent en `auto` et l'image retombe à sa hauteur
   intrinsèque (706 px sur 800, 500 px sur 844 mobile — bande pâle sous les
   boutons, attrapée à la QA du 2 sept.). */
.hero-fond picture { display: block; height: 100%; }
/* Aucun filtre : l'image sort telle quelle (verdict Yannick, 2 sept.). */
.hero-fond img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 64%;
}
/* ⚠️ Jamais `transparent` dans ces dégradés : en CSS il vaut rgba(0,0,0,0),
   donc l'interpolation traverse du NOIR et salit la photo. On écrit la couleur
   du voile en alpha 0, via les composantes du token (--nuit-rgb). */
/* UN SEUL raccord, celui du BAS (≤ 13 %), pour que la photo se fonde dans le
   bleu profond de la section suivante. Le raccord du HAUT a été retiré le
   2 sept. : il assombrissait le ciel sous la barre de navigation. */
.hero-fond::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(var(--nuit-rgb), 0.88) 0%, rgba(var(--nuit-rgb), 0) 13%);
}
/* span 11 : mesuré au navigateur à 1440 px, le kicker « DÉNEIGEMENT RÉSIDENTIEL
   ET COMMERCIAL · JOLIETTE » demande ~780 px et cassait en deux lignes dans le
   span 10. Une étiquette d'instrument sur deux lignes n'est plus une étiquette. */
.hero-texte { grid-column: contenu-deb / span 11; position: relative; z-index: 3; }
/* PLUS AUCUN SCRIM NI VOILE SUR LE HÉROS (verdict Yannick, 2 sept. 2026) :
   la photo se montre NUE, les flocons bleus du blizzard doivent rester visibles
   dessus. Le contraste du texte est tenu par un halo CLAIR au ras des glyphes
   (règle du contraste local), pas par une région assombrie. */
.hero-h1 .ligne { display: block; overflow: hidden; white-space: nowrap; }
.hero-h1 { font-size: clamp(var(--h1-min), 5.5vw, var(--h1-max)); }
/* Le texte du héros est BLANC sur la photo nue : le halo est donc SOMBRE, et il
   se colle au ras des glyphes. C'est le contraste le plus local qui existe — il
   assombrit le voisinage immédiat de la lettre, pas une région de l'image.
   Le même --halo-sombre sert à la barre de navigation transparente. */
.hero-texte .kicker, .hero-h1, .hero-sous {
  text-shadow: 0 0 0.25rem var(--halo-sombre), 0 0 0.6rem var(--halo-sombre),
               0 0.1rem 1.4rem var(--halo-sombre-2);
}
.hero-texte .kicker, .hero-sous {
  text-shadow: 0 0 0.2rem var(--halo-sombre), 0 0 0.5rem var(--halo-sombre),
               0 0 1rem var(--halo-sombre), 0 0.1rem 1.4rem var(--halo-sombre-2);
}
.hero-texte .btn-contour { border-color: var(--encre-sur-nuit); color: var(--encre-sur-nuit); text-shadow: 0 0 0.3rem var(--halo-sombre), 0 0 0.8rem var(--halo-sombre-2); box-shadow: 0 0 0 0.05rem var(--halo-sombre-2), 0 0.2rem 1.2rem var(--halo-sombre-2); }
.hero-sous {
  margin-top: 2.4rem; max-width: 62rem; font-size: var(--fs-note);
  letter-spacing: 0.01em;
}
.hero-cta { display: flex; flex-wrap: wrap; gap: 1.6rem; margin-top: 3.6rem; }

@media (max-width: 991px) {
  .hero { padding-top: 12rem; }
  .hero-texte { grid-column: contenu-deb / contenu-fin; }
  /* Empilé, le héros dépasse un écran : la photo prend TOUTE la hauteur de la
     section (et non 78svh), sinon un bloc de bleu profond vide s'ouvre sous les
     boutons. Le raccord du bas la fond quand même dans la section suivante. */
  .hero-fond { height: 100%; }
}
@media (max-width: 767px) {
  /* Une ligne de H1 en nowrap à la taille desktop déborde de l'écran : la
     display condensée garde sa propre échelle en petite largeur. */
  .hero-h1 { font-size: clamp(3.3rem, 9.6vw, 5.4rem); }
  .hero-fond img { object-position: 50% 56%; }
  /* Photo nue ici aussi : ne reste que la fonte dans le bleu profond, en bas. */
  .hero-fond::after {
    background: linear-gradient(to top, rgba(var(--nuit-rgb), 0.94) 0%, rgba(var(--nuit-rgb), 0) 15%);
  }

}

/* ---------- S2 Intro 2 temps ---------- */
/* Le texte descend de 13 à 9 colonnes : l'image prend le vide qui restait à
   droite (colonnes 11-16). La ligne de texte reste sous 62rem de toute façon. */
.intro-bloc { grid-column: contenu-deb / span 9; }
.intro-l1 { color: var(--note); }
.intro-l2 { color: var(--encre); }
.intro-p { max-width: 62rem; margin-top: 3.2rem; }
.intro-image {
  grid-column: span 6 / contenu-fin; align-self: center;
  width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover;
  border-radius: var(--radius);
}
/* Empilé (mode statique desktop), l'image passe SOUS le texte, pleine largeur —
   le mobile y arrive tout seul par le repli une-colonne de la grille. */
html.anim-statique .intro-bloc,
html.anim-statique .intro-image { grid-column: contenu-deb / contenu-fin; }
html.anim-statique .intro-image { aspect-ratio: 16 / 7; margin-top: 4rem; }
@media (max-width: 767px) { .intro-image { aspect-ratio: 16 / 10; margin-top: 3rem; } }

/* ---------- S2-S3 : la séquence (blizzard + écrans) ----------
   La zone intro → services vit dans .piste-hiver (séquence HORIZONTALE desktop,
   bloc dédié plus bas). Les règles ici sont le SOCLE commun + le repli vertical
   (mobile, tier statique, sans JS). */
/* ⚠️ Toutes les règles #services sont scopées à .piste : #services n'est un
   écran de la séquence horizontale QUE dans ce contexte. Non scopées, elles
   suivraient une copie où #services est une section normale et l'étaleraient sur
   plusieurs écrans de large. */
.piste #services { background: var(--glace); }
.blizzard {
  position: absolute; inset: 0; z-index: 0; overflow: clip; pointer-events: none;
}
.blizzard canvas { position: sticky; top: 0; width: 100%; height: 100vh; }
.neige-arriere { filter: blur(2.5px); }
.neige-avant { margin-top: -100vh; }
.ecran {
  min-height: 100vh;
  display: flex; align-items: center;
  /* Voile LÉGER : le blizzard doit se voir à travers. */
  background: color-mix(in srgb, var(--glace) 42%, transparent);
}
.ecran-contenu { grid-column: contenu-deb / contenu-fin; padding-block: 10rem; width: 100%; }
.cartes-prix {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--col-gap);
  margin-top: 4rem;
}
/* CARTES DE SERVICE — style « fiche technique » : séparateurs horizontaux fins,
   pas de boîte, coins nets, montant en mono. Le rayon partagé --radius ne bouge
   pas : il sert encore aux champs du formulaire. */
.carte-prix {
  background: none; border: 0; border-radius: 0;
  border-top: max(2px, 0.22rem) solid var(--bord-fort);  /* Chromium tronque les bordures sous-pixel : 0.2rem (1,96 px) retombait a 1 px et le filet ne se voyait plus. */
  border-bottom: var(--bord-1) solid var(--bord);
  padding: 2.6rem 0 3rem;
  transition: border-top-color 0.3s, transform 0.3s;
}
/* Survol DISCRET : le filet du haut s'allume en bleu royal. */
.carte-prix:hover { border-top-color: var(--vert-f); }
/* L'image coiffe la fiche : coins arrondis (seule courbe de la carte, le reste
   de la géométrie reste à angles nets) et ratio fixe, pour que les trois cartes
   d'un écran alignent leurs titres à la même hauteur. */
/* `height: auto` est OBLIGATOIRE : les attributs width/height du HTML sont des
   hints de présentation (donc du CSS), et un `height` figé neutralise
   l'aspect-ratio — la carte sortait à 562 px de haut au lieu de 16:10. */
.carte-image {
  width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover;
  border-radius: var(--radius); margin-bottom: 2.2rem;
}
.carte-desc { margin-top: 1.2rem; font-size: var(--fs-note); color: var(--note); }
.carte-montant {
  margin-top: 2.4rem; font-size: var(--fs-note); color: var(--note);
  display: flex; align-items: baseline; gap: 0.8rem;
}
/* Le montant est en MONO à chasse fixe : les trois cartes alignent leurs prix à
   la colonne près, comme un relevé. Aucune grille de prix n'a encore été fournie
   par le client, donc tout est « Sur soumission » — la taille descend d'autant,
   parce qu'un mot de 14 signes ne se lit pas à la même échelle qu'un chiffre.
   Quand la grille arrivera, remonter à 4.4rem et remettre tabular-nums. */
.carte-montant .prix {
  font-family: var(--police-mono); font-variant-numeric: tabular-nums;
  font-size: 2.6rem; font-weight: 600; line-height: 1.15;
  color: var(--vert-f); letter-spacing: -0.01em;
}
@media (max-width: 991px) {
  /* minmax(0,1fr) et non 1fr : un 1fr nu prend la taille MIN-CONTENT de la
     carte, qui déborde du conteneur dès qu'un mot est long. */
  .cartes-prix { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- S4 Pourquoi (bloc bleu profond — TOKENS INVERSES) ---------- */
#pourquoi { background: var(--nuit); color: var(--encre); overflow: clip; }
#pourquoi .inverse { color: var(--encre); }
#pourquoi .kicker.inverse { color: var(--note); }
/* Aucune lueur radiale (règle LuxAura : ces halos se lisent comme un défaut
   d'affichage). */
.pourquoi-lueur, .pont-lueur { display: none; }
.pourquoi-tete { grid-column: contenu-deb / span 10; margin-bottom: 5rem; }
.pourquoi-liste { grid-column: contenu-deb / span 12; }
.pourquoi-liste li {
  display: flex; gap: 2.8rem; align-items: baseline;
  padding: 2.6rem 0; font-size: 2.2rem; line-height: 1.45;
  border-bottom: var(--bord-1) solid var(--bord);
}
/* Les numéros suivent --vert-f, qui vaut le glacier pâle dans ce bloc (TOKENS
   INVERSES) : un repère d'ordre, pas un signal. */
.pourquoi-num {
  font-family: var(--police-mono); font-size: var(--fs-badge); font-weight: 500;
  letter-spacing: 0.18em; color: var(--vert-f); flex-shrink: 0;
}

/* ---------- S5 Marquee (décor, en OUTLINE) ----------
   Texte outline géant sur le bleu profond : le contraste est une propriété de la
   construction (trait glacier pâle sur --nuit = 9,36:1) et non un réglage à
   re-vérifier au prochain changement de palette. Le bandeau est aria-hidden et
   doublé par un .sr-only pour les lecteurs d'écran. */
.marquee {
  background: var(--nuit); overflow: clip;
  padding: 1.8rem 0; border-block: var(--bord-1) solid var(--bord);
}
.marquee-piste {
  display: flex; white-space: nowrap; width: max-content;
  animation: defile 30s linear infinite;
  font-family: var(--police-titre);
  font-size: clamp(4.2rem, 6.4vw, 9rem);
  line-height: 1.08;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  /* FALLBACK, actif partout : glacier PLEIN sur le bleu profond — 9,36:1.
     Un navigateur sans -webkit-text-stroke lit ce bandeau parfaitement. */
  color: var(--vert-f);
}
/* Là où le trait existe (tous les moteurs modernes, préfixe -webkit- compris
   par Firefox), on vide le remplissage et on garde le contour. Le trait porte
   exactement la couleur du fallback : le ratio ne bouge pas d'un point. */
@supports (-webkit-text-stroke: 1px red) {
  .marquee-piste {
    color: transparent;
    -webkit-text-stroke: 0.19rem var(--vert-f);
  }
}
@keyframes defile { to { transform: translateX(-50%); } }

/* ---------- S6 Avis (option payée par le client) ---------- */
#avis { overflow: clip; }
.avis-sous { margin-top: 1.2rem; }
.avis-grille {
  grid-column: contenu-deb / contenu-fin;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--col-gap);
  margin-top: 4rem;
}
/* Même logique de fiche que les cartes de services : filet haut, coins nets,
   pas de boîte. Deux géométries dans une même page, c'est ce qui fait
   « gabarit » ; une seule, c'est ce qui fait « studio ». */
.avis-carte {
  border: 0; border-radius: 0; background: none;
  border-top: max(2px, 0.22rem) solid var(--bord-fort);  /* Chromium tronque les bordures sous-pixel : 0.2rem retombait à 1 px et le filet ne se voyait plus. */
  border-bottom: var(--bord-1) solid var(--bord);
  padding: 2.6rem 0 3rem;
  transition: border-top-color 0.3s;
}
.avis-carte:hover { border-top-color: var(--vert-f); }
/* Les étoiles prennent l'ambre ASSOMBRI : le gyrophare #f9c134 ne tient que
   1,54:1 sur le fond neige, sous le 3:1 exigé des éléments non textuels.
   currentColor traverse le <use> : la couleur posée ici atteint le <path>. */
.etoiles { color: var(--jaune-2); }
.avis-carte blockquote { margin-top: 1.6rem; font-size: 1.9rem; line-height: 1.55; }
.avis-carte blockquote p + p { margin-top: 1rem; }
.avis-carte figcaption { margin-top: 2rem; font-size: var(--fs-note); color: var(--note); font-weight: 600; }
@media (max-width: 767px) {
  .avis-grille { grid-template-columns: 1fr; }
}

/* ---------- S7 Soumission + zone + coordonnées (UNE section depuis le 2 sept. 2026 :
   fusion contact / CTA / formulaire, verdict Yannick) ---------- */
#soumission { background: var(--glace-2); }
.form-tete { grid-column: contenu-deb / contenu-fin; }
/* Formulaire à gauche (3 parts), zone + coordonnées à droite (2 parts) : la
   liste des villes répond à « suis-je dans leur zone ? » juste avant qu'on
   remplisse, et le téléphone sert ceux qui préfèrent appeler. */
.form-colonnes {
  grid-column: contenu-deb / contenu-fin;
  display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--col-gap);
  margin-top: 4rem; align-items: start;
}
.form-cote { display: grid; gap: 5rem; align-content: start; }
.form-cote h3 { margin-bottom: 2.4rem; }
.zone-liste { display: grid; grid-template-columns: 1fr 1fr; gap: 0.4rem 2rem; }
.zone-liste li {
  font-family: var(--police-titre); font-size: 2.8rem; text-transform: uppercase;
  padding: 1rem 0; border-bottom: var(--bord-1) solid var(--bord);
}
.zone-note { margin-top: 2.6rem; }
.zone-note a { color: var(--vert-f); font-weight: 600; }
.contact-liste li { padding: 0.9rem 0; font-size: 2rem; }
.contact-liste a { text-decoration-color: var(--vert-f); text-underline-offset: 0.4rem; overflow-wrap: anywhere; }
.contact-sociaux { display: flex; gap: 1.6rem; margin-top: 2.6rem; }
.contact-sociaux a {
  display: grid; place-items: center; width: 4.8rem; height: 4.8rem;
  border: var(--bord-1) solid var(--bord-fort); border-radius: 50%;
  transition: border-color 0.3s, color 0.3s;
}
.contact-sociaux a:hover { border-color: var(--vert-f); color: var(--vert-f); }
@media (max-width: 991px) {
  .form-colonnes { grid-template-columns: minmax(0, 1fr); gap: 6rem; }
  .zone-liste { grid-template-columns: 1fr; }
}

/* ---------- Formulaire de soumission (colonne gauche de S7) ---------- */
.form-bloc { min-width: 0; }
.form-sous { margin-top: 1.6rem; color: var(--note); max-width: 62rem; }
.form-soum {
  display: grid; grid-template-columns: 1fr 1fr; gap: 2.4rem var(--col-gap);
  align-items: start;
}
.form-groupe { display: flex; flex-direction: column; gap: 0.8rem; }
.form-large { grid-column: 1 / -1; }
.form-groupe label {
  font-family: var(--police-mono); font-size: var(--fs-badge);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--note);
}
/* `select` et `textarea` sont dans les mêmes sélecteurs que `input` : sans ça,
   le menu déroulant garde l'apparence brute du système et jure avec le reste. */
.form-groupe input,
.form-groupe select,
.form-groupe textarea {
  width: 100%; padding: 1.6rem 2rem; font: inherit; color: var(--encre);
  background: var(--glace);
  border: var(--bord-1) solid var(--bord-fort); border-radius: var(--radius);
  transition: border-color 0.25s;
}
.form-groupe textarea { resize: vertical; min-height: 12rem; }
.form-groupe input:hover,
.form-groupe select:hover,
.form-groupe textarea:hover { border-color: var(--vert-f); }
.form-consentement {
  grid-column: 1 / -1;
  display: flex; gap: 1.2rem; align-items: flex-start;
  font-size: var(--fs-note); line-height: 1.45; color: var(--note);
}
.form-consentement input { margin-top: 0.45rem; width: 1.8rem; height: 1.8rem; flex-shrink: 0; accent-color: var(--vert-f); }
.form-consentement a { color: var(--vert-f); font-weight: 600; }
.form-envoi { grid-column: 1 / -1; justify-self: start; margin-top: 1.2rem; }
.form-etat { grid-column: 1 / -1; }
.form-etat-ok { color: var(--vert-f); font-weight: 600; }
.form-etat-erreur { color: var(--rouge); font-weight: 600; }
@media (max-width: 767px) {
  .form-soum { grid-template-columns: 1fr; }
  .form-envoi { justify-self: stretch; text-align: center; justify-content: center; }
}

/* ---------- S10 Pied (bloc bleu profond — TOKENS INVERSES) ---------- */
/* overflow:clip — le rappel géant en filigrane est plus large que l'écran en
   mobile ; c'est voulu (il doit être coupé), mais sans clip il élargit la page. */
.pied { background: var(--nuit); color: var(--encre); position: relative; overflow: clip; }
.pied .sep-congere { color: var(--nuit); position: absolute; top: 0; transform: translateY(-98%); }
.pied-interieur { padding-block: 10rem 4rem; }
/* Même plaque blanche qu'en barre de navigation (logo fourni sur fond blanc). */
.pied-logo {
  grid-column: contenu-deb / contenu-fin; justify-self: start;
  height: 10rem; width: auto; margin-bottom: 4rem;
  background: var(--glace); border-radius: 1.4rem; padding: 0.8rem 1.2rem;
}
.pied-rappel {
  grid-column: contenu-deb / contenu-fin;
  color: var(--filigrane); line-height: 0.95;
}
.pied-nav {
  grid-column: contenu-deb / contenu-fin;
  display: flex; flex-wrap: wrap; gap: 2.8rem; margin-top: 5rem;
  font-size: var(--fs-ui); text-transform: uppercase; letter-spacing: 0.08em;
}
.pied-nav a { text-decoration: none; }
.pied-nav a:hover { color: var(--vert-f); }
.pied-mentions {
  grid-column: contenu-deb / contenu-fin;
  margin-top: 4rem; font-size: var(--fs-note);
}
.pied-signature {
  grid-column: contenu-deb / contenu-fin;
  margin-top: 1.2rem; font-size: var(--fs-badge); color: var(--note);
}
.pied-signature a, .pied-mentions a { color: var(--vert-f); }

/* ---------- S11 Légal (:target) ----------
   La section vit HORS du pied de page : dans le pied, elle hériterait des
   TOKENS INVERSES et sortirait en texte blanc sur fond neige. */
.legal { display: none; background: var(--neige); color: var(--encre); }
.legal:target { display: block; }
.legal-interieur { max-width: 78rem; margin-inline: auto; padding: 8rem var(--pad-container); }
.legal-interieur h2, .legal-interieur h3 { margin-top: 3rem; }
.legal-interieur p { margin-top: 1.4rem; font-size: var(--fs-note); }
.legal-interieur a { color: var(--vert-f); }
.legal-close { display: inline-block; font-weight: 600; }
.legal-close:hover { text-decoration: underline; }

/* ---------- Curseur FLOCON (pointeur fin seulement) ---------- */
/* Base HORS media query : si la page a été chargée avec une souris puis
   basculée en mode tactile (DevTools mobile sans recharger), le div existe
   encore mais perd ses règles « pointer: fine » — sans cette ligne il devenait
   un bloc à pleine largeur en fin de page : un flocon géant dans une section
   vide (bug vu par Yannick le 2 sept. sur l'aperçu Leduc, même famille). */
.curseur { display: none; }
@media (pointer: fine) {
  .curseur {
    display: block;
    position: fixed; z-index: 99; width: 3rem; height: 3rem;
    pointer-events: none; top: -1.5rem; left: -1.5rem;
    color: var(--vert-f); opacity: 0;
    transition: transform 0.25s, color 0.25s, opacity 0.25s;
  }
  .curseur svg { width: 100%; height: 100%; animation: flocon-tourne 14s linear infinite; }
  .curseur.actif { opacity: 0.85; }
  .curseur.survole { transform: scale(1.55); color: var(--jaune-2); opacity: 1; }
  @keyframes flocon-tourne { to { transform: rotate(360deg); } }
}

/* ---------- Reduced motion : tout a l'etat final ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; }
}

/* ═══ SÉQUENCE HORIZONTALE ═══
   Active seulement : desktop ≥992px ET html SANS .anim-statique (classe posée
   par premium.js quand reduced-motion / tier LuxAnim statique / GSAP absent).
   Partout ailleurs, le socle vertical ci-dessus fait le repli tout seul.
   ⚠️ Le seuil est 992 et non 768 : sous 992 px, .cartes-prix passe en UNE
   colonne (palier 991) et trois cartes empilées débordent du viewport collé
   (100vh, clippé) — la 3e carte était rognée sur iPad. Même seuil dans
   premium.js (var horizontal).
   ⚠️ LA HAUTEUR SUIT LE NOMBRE DE PANNEAUX : la course vaut (n−1) × 100vw.
   Ici 3 panneaux (intro + 2 écrans de services) ⇒ 200vw. Un panneau ajouté ou
   retiré se paie ICI, sinon la piste s'arrête avant la fin ou traîne à vide. */
@media (min-width: 992px) {
  html:not(.anim-statique) .piste-hiver { height: calc(100vh + 200vw); }
  html:not(.anim-statique) .piste-colle {
    position: sticky; top: 0; height: 100vh; overflow: clip;
  }
  /* SENS INVERSÉ : `row-reverse` met la piste en MIROIR — le panneau 1 se
     retrouve tout à DROITE de la course, et le tween de premium.js part de
     -(course) pour revenir à 0 : les panneaux entrent par la GAUCHE. L'ordre du
     récit est intact (l'ordre du DOM est simplement lu de droite à gauche) —
     mais #services doit être renversé LUI AUSSI, sinon ses écrans se lisent à
     l'envers à l'intérieur du miroir. */
  html:not(.anim-statique) .piste {
    display: flex; flex-direction: row-reverse;
    width: max-content; position: relative; z-index: 1;
    height: 100%; will-change: transform;
  }
  html:not(.anim-statique) .piste > .panneau {
    width: 100vw; flex: 0 0 auto; min-height: 100vh; align-content: center;
  }
  html:not(.anim-statique) .piste #services {
    display: flex; flex-direction: row-reverse; background: transparent;
  }
  html:not(.anim-statique) .piste #services .ecran { width: 100vw; flex: 0 0 auto; }
  /* Le blizzard devient l'overlay du viewport sticky (le hack canvas-sticky
     du mode vertical n'a plus de sens ici) */
  html:not(.anim-statique) .blizzard canvas { position: absolute; inset: 0; height: 100%; }
  html:not(.anim-statique) .neige-avant { margin-top: 0; }
}

/* ═══ MOBILE : la grille 16 colonnes se replie en UNE colonne ═══
   Le socle 16 colonnes garde ses 15 gouttières de 3,6 rem quelle que soit la
   largeur : ~520 px de vide sur un écran de 390. Les colonnes, elles, tombent
   à 0 (minmax(0,…)) — donc le contenu était poussé HORS de l'écran et coupé
   par le `overflow-x: clip` du socle. Le clip cache le
   symptôme, pas la cause.
   Ici : une seule colonne de contenu, zéro gouttière, et TOUS les enfants
   ramenés sur cette colonne (sinon un `span 9` créerait des colonnes
   implicites et le débordement reviendrait par la porte d'à côté). */
@media (max-width: 767px) {
  .grille {
    grid-template-columns:
      [pleine-deb] var(--pad-container)
      [contenu-deb] minmax(0, 1fr)
      [contenu-fin] var(--pad-container) [pleine-fin];
    column-gap: 0;
  }
  .grille > * { grid-column: contenu-deb / contenu-fin; }
  .grille > .pleine, .grille > .hero-fond { grid-column: pleine-deb / pleine-fin; }

  /* ── Échelle de texte propre au mobile ──
     Les --fs-* du socle sont calibrés pour le desktop : reportés tels quels sur
     390 px, le corps sort à 26 px et le kicker prend trois lignes.
     Valeurs ci-dessous en px RÉELS à 390 px de large (racine ≈ 9,9 px), toutes
     au-dessus des planchers CLAUDE.md : corps 17 px (min 16) · UI 15 px
     (min 12,8) · badges 13 px (min 12). */
  :root {
    --fs-corps: 1.72rem;  /* ≈ 17 px */
    --fs-note: 1.68rem;   /* ≈ 16,6 px */
    --fs-ui: 1.5rem;      /* ≈ 15 px */
    --fs-badge: 1.32rem;  /* ≈ 13 px */
  }
  /* La barre : sans ça, le logo passe SOUS le bouton et se fait recouvrir. */
  .nav { padding: 1.2rem var(--pad-container); gap: 1.2rem; }
  .nav-logo { font-size: 1.5rem; line-height: 1.15; gap: 0.8rem; }
  .nav-logo-img { height: 3.6rem; border-radius: 0.7rem; padding: 0.2rem 0.4rem; }
  .pied-logo { height: 7rem; margin-bottom: 3rem; }
  .nav-cta { padding: 0.9rem 1.6rem; white-space: nowrap; }
  .hero { padding-top: 9rem; }
  .hero-cta .btn { flex: 1 1 auto; justify-content: center; }

  /* Même condensée, une display en capitales déborde vite sur 390 px : les
     titres de section descendent d'un cran de plus que le palier 991 px. */
  h2, .h2 { font-size: 3.9rem; }
  h3, .h3 { font-size: 3rem; }

  .zone-liste li { font-size: 2rem; }

  /* Le rappel géant du pied est un filigrane, pas une section : il ne doit pas
     prendre cinq lignes plein écran avant les liens. */
  .pied-rappel { font-size: 4.6rem; }
  .pied-interieur { padding-block: 6rem 3rem; }
}

/* ═══ PISTE HORIZONTALE — VIEWPORTS COURTS ═══
   CAUSE : .piste-colle est collée à `height: 100vh` avec `overflow: clip`, et
   .ecran centre son contenu (`align-items: center`). Dès qu'un panneau est plus
   haut que l'écran, le débordement part des DEUX côtés et se fait rogner — le
   kicker et le titre disparaissent en haut, les cartes en bas — sans aucun
   moyen d'y accéder, puisque le conteneur est clippé et non défilant.
   CORRECTIF, en deux temps :
   1. `safe center` — garde-fou permanent. Si un panneau déborde malgré tout
      (contenu plus long sur une future maquette, échelle Windows 150 %), il
      s'aligne en HAUT au lieu de couper le titre.
   2. Un traitement COMPACT sous 1050 px de hauteur : l'air vertical et les blocs
      de texte lourds deviennent fluides en vh. Au-dessus de 1050 px — donc sur
      1920×1080 — AUCUNE de ces règles ne s'applique.
   Mesuré : les panneaux tiennent dès 550 px de hauteur utile, texte à 18,6 px
   (plancher CLAUDE.md : 17,6 px).

   Deux boutons de calibration, à MESURER (la barre est en rem, donc les valeurs
   suivent l'échelle) :
   · --degagement-nav       hauteur de la barre fixe du haut (défaut 10rem =
                            une barre sur une ligne ; 14,5rem si elle en fait deux)
   · --degagement-piste-bas dégagement du bas (défaut 4rem) */
@media (min-width: 992px) {
  html:not(.anim-statique) .piste .ecran { align-items: safe center; }
  html:not(.anim-statique) .piste > .panneau { align-content: safe center; }
}
@media (min-width: 992px) and (max-height: 1050px) {
  html:not(.anim-statique) .piste .ecran-contenu {
    padding-top: var(--degagement-nav, 10rem);
    padding-bottom: max(var(--degagement-piste-bas, 4rem), clamp(2.4rem, 5vh, 10rem));
  }
  html:not(.anim-statique) .piste .cartes-prix { margin-top: clamp(1.4rem, 2.6vh, 4rem); }
  /* La fiche n'a plus de padding latéral (elle n'a plus de boîte) : seul l'air
     vertical se compresse sur les écrans courts. */
  html:not(.anim-statique) .piste .carte-prix { padding: clamp(1.4rem, 2.6vh, 2.6rem) 0 clamp(1.6rem, 2.9vh, 3rem); }
  html:not(.anim-statique) .piste .carte-montant { margin-top: clamp(0.9rem, 1.8vh, 2.4rem); }
  html:not(.anim-statique) .piste .carte-montant .prix { font-size: clamp(1.9rem, 2.9vh, 2.6rem); }
  html:not(.anim-statique) .piste .carte-desc { font-size: clamp(1.85rem, 2.9vh, var(--fs-note)); }
  /* L'image de carte suit la même compression : sur un 1366×768 le ratio 16:10
     seul mangeait ~280 px et poussait le prix hors du panneau clippé. */
  html:not(.anim-statique) .piste .carte-image {
    aspect-ratio: auto; height: clamp(7rem, 21vh, 27rem);
    margin-bottom: clamp(1rem, 2vh, 2.2rem);
  }
}
