/* ==========================================================================
   MD Clinic — feuille de style principale
   Direction artistique reprise du prototype Claude Design « Site MD Clinic ».
   Les valeurs (couleurs, rayons, ombres, easings) sont extraites telles quelles.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Fonds */
  --cream:        #F2EDE3;
  --cream-alt:    #E9E7DB;
  --cream-deep:   #DED9CC;
  --white:        #FFFFFF;

  /* Encres — valeurs calées sur 4.5:1 minimum contre leur fond d'usage.
     Les anciennes (#7C8370, #838974, #6D7360) tombaient entre 2.92 et 4.21:1
     alors qu'elles portent du 11px capitales très espacé : le pire cas. */
  --ink:          #171A13;
  --ink-soft:     #1C1F17;
  --body:         #4E5347;
  --body-soft:    #55594B;
  --muted:        #696E5E;   /* 4.51:1 sur crème */
  --muted-2:      #656A59;   /* 4.50:1 sur crème-alt */
  --muted-3:      #696E5C;   /* 4.52:1 sur crème */

  /* Sauge */
  --sage:         #4E5B3C;
  --sage-deep:    #3D4830;
  --sage-light:   #8C9C7C;   /* aplats et bordures uniquement — jamais du texte */
  --sage-ink:     #5F6C52;   /* petits textes sauge sur fond clair : 4.51 à 5.59:1 */
  --sage-pale:    #C6CFB8;   /* 4.51:1 sur sauge */
  --gold:         #A98B62;

  /* Sur fond sombre */
  --on-dark:      #F4F2E8;
  --on-dark-soft: #C9CDBC;
  --on-dark-mute: #C8CEBC;   /* 4.51:1 sur sauge */
  --on-dark-warm: #DCE0D0;

  /* Anneau de focus — bascule selon la famille de fond (voir plus bas).
     L'ancienne règle unique posait #4E5B3C sur un fond #4E5B3C : 1.00:1,
     soit un anneau strictement invisible sur toute la section contact. */
  --focus-ring:   #434E34;

  /* Typo */
  --serif: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --sans:  'Jost', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;

  /* Rythme */
  --gutter:   clamp(20px, 5vw, 72px);
  --maxw:     1240px;
  --radius:   22px;
  --radius-l: 26px;

  /* Ombres */
  --shadow-card:   0 30px 60px -40px rgba(52, 62, 38, .5);
  --shadow-btn:    0 20px 44px -24px rgba(52, 62, 38, .9);
  --shadow-header: 0 18px 40px -34px rgba(28, 31, 23, .7);

  /* Mouvement
     `--ease` reste la courbe historique du prototype : tout ce qui existait
     avant continue de s'en servir. Les trois autres sont ajoutées pour les
     animations d'entrée, où une seule courbe pour tout donnait un site qui
     bouge « d'un seul bloc ».
       --ease-out    départ franc, arrivée qui s'étale : entrées et fondus
       --ease-inout  symétrique : va-et-vient, replis, changements d'état
       --ease-spring légère dépasse en fin de course : survols et boutons */
  --ease:        cubic-bezier(.2, .7, .2, 1);
  --ease-out:    cubic-bezier(.16, 1, .3, 1);
  --ease-inout:  cubic-bezier(.65, 0, .35, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* --topbar-h est mesurée en JS : le bandeau peut passer sur deux lignes. */
  scroll-padding-top: calc(var(--topbar-h, 96px) + 16px);
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--cream);
}

body {
  overflow-x: clip;
  font-family: var(--sans);
  font-weight: 300;
  color: var(--ink-soft);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Supprime le délai de 300 ms au double-tap, et pose un retour tactile
   discret plutôt que le rectangle bleu par défaut d'Android. */
a, button, summary, [role='button'] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(78, 91, 60, .12);
}

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

a { color: var(--sage); text-decoration: none; transition: color .3s ease; }
a:hover { color: var(--sage-deep); }

h1, h2, h3, h4 { font-family: var(--serif); font-weight: 300; margin: 0; }

p { margin: 0; text-wrap: pretty; }

::selection { background: var(--sage-pale); color: var(--ink); }

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

/* Les surfaces sombres redéfinissent le jeton : l'anneau devient clair.
   #F4F2E8 donne 6.48:1 sur sauge et 15.68:1 sur l'encre. */
.notice,
.marquee,
.clinic,
.gallery,
.contact,
.footer { --focus-ring: #F4F2E8; }

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

.skip-link {
  position: absolute;
  top: -100px;
  left: var(--gutter);
  z-index: 200;
  padding: 12px 22px;
  border-radius: 0 0 12px 12px;
  background: var(--sage);
  color: var(--on-dark);
  font-size: 13px;
  letter-spacing: .1em;
  transition: top .3s var(--ease);
}
.skip-link:focus { top: 0; color: var(--on-dark); }

/* --------------------------------------------------------------------------
   3. Utilitaires typographiques
   -------------------------------------------------------------------------- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 11px;
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--muted);
}
.eyebrow::before {
  content: '';
  width: 46px; height: 1px;
  flex: none;
  background: rgba(140, 156, 124, .8);
}
.eyebrow--plain { display: block; }
.eyebrow--plain::before { display: none; }
.eyebrow--light { color: var(--sage-pale); }

.lede {
  font-size: clamp(16px, 1.2vw, 19px);
  font-weight: 300;
  line-height: 1.75;
  color: var(--body);
  max-width: 42ch;
}

.section-title {
  font-size: clamp(30px, 3.8vw, 60px);
  line-height: 1.02;
  color: var(--ink);
}
.section-title em { font-style: italic; color: var(--sage); }

.wrap { max-width: var(--maxw); margin-inline: auto; }

/* --------------------------------------------------------------------------
   4. Boutons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 17px 30px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: .18em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: transform .35s var(--ease), background .35s ease,
              border-color .35s ease, color .35s ease;
}

.btn--primary {
  background: var(--sage);
  color: #F7F4EB;
  box-shadow: var(--shadow-btn);
}
.btn--primary:hover { background: var(--sage-deep); color: #F7F4EB; transform: translateY(-2px); }

.btn--ghost {
  border-color: rgba(28, 31, 23, .2);
  color: #2A2E23;
}
.btn--ghost:hover {
  border-color: rgba(28, 31, 23, .5);
  color: var(--ink);
}

.btn--light { background: var(--on-dark); color: #2A3420; }
.btn--light:hover { background: var(--white); color: #2A3420; transform: translateY(-2px); }

.btn--outline-light { border-color: rgba(244, 242, 232, .35); color: var(--on-dark); }
.btn--outline-light:hover {
  border-color: var(--on-dark);
  background: rgba(255, 255, 255, .08);
  color: var(--white);
}

.btn--dark { background: #1F2A15; color: #F0F2E7; }
.btn--dark:hover { opacity: .85; color: #F0F2E7; }

.link-underline {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding-top: 21px;
  font-size: 11px;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--sage);
  border-bottom: 1px solid rgba(78, 91, 60, .3);
  padding-bottom: 6px;
}
.link-underline:hover { color: var(--sage-deep); border-color: var(--sage-deep); }
.link-underline--light { color: var(--on-dark); border-color: rgba(244, 242, 232, .3); }
.link-underline--light:hover { color: var(--sage-pale); border-color: var(--sage-pale); }

/* --------------------------------------------------------------------------
   5. Barre de progression + en-tête
   -------------------------------------------------------------------------- */
.progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--sage-light), var(--gold));
  z-index: 90;
  will-change: transform;
}

/* Bandeau d'ouverture + en-tête forment un seul bloc fixe, sinon l'en-tête
   se superpose au bandeau et masque le bouton de rendez-vous. */
.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 85;
  /* Encoche et barre d'état des téléphones récents. */
  padding-top: env(safe-area-inset-top, 0);
}

.header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 14px 32px;
  padding: 26px var(--gutter);
  background: transparent;
  /* `padding-block` anime bien une propriété de mise en page, ce que l'on évite
     en général. Exception assumée et bornée : l'en-tête est `position: fixed`,
     donc hors flux — rien dans le document ne se réagence — et la transition ne
     joue qu'une fois par franchissement du seuil, pas à chaque frame de scroll.
     Le rétrécissement fait partie du design validé. */
  transition: padding-block .5s var(--ease), background .5s ease,
              box-shadow .5s ease, backdrop-filter .5s ease;
}
.header.is-solid {
  padding-block: 14px;
  background: rgba(242, 237, 227, .86);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow-header);
}

/* Sans JavaScript, `is-solid` n'est jamais posée : l'en-tête reste transparent
   sur toute la hauteur de la page, et ses libellés — encre sombre — finissent
   par passer sur la section clinique et sur le pied de page, tous deux sombres.
   On lui donne alors son fond en permanence. On perd la transparence sur le
   hero ; on garde un menu lisible partout, ce qui vaut mieux.
   `html:not(.js)` : la classe est posée par boot.js avant le premier rendu. */
html:not(.js) .header {
  background: rgba(242, 237, 227, .92);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow-header);
}

/* Le monogramme « MD » est une image découpée dans le logo (build.mjs, fonction
   `marque`) ; « CLINIC » reste du texte, entre deux filets comme sur le logo. */
.brand { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; min-height: 44px; line-height: 1; color: var(--ink-soft); }
.brand:hover { color: var(--ink-soft); }
.brand__logo { display: block; width: auto; height: 32px; transition: height .5s var(--ease); }
.header.is-solid .brand__logo { height: 27px; }
.brand__sub { display: flex; align-items: center; gap: 6px; font-size: 9px; letter-spacing: .42em; color: var(--muted-3); }
.brand__sub::before,
.brand__sub::after { content: ''; width: 8px; height: 1px; background: currentColor; opacity: .6; }
/* L'espacement des lettres s'ajoute aussi après le dernier C : sans cette
   compensation, le filet de droite est plus écarté que celui de gauche. */
.brand__sub::after { margin-left: -.42em; }

.nav {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 12px clamp(16px, 2.6vw, 34px);
  font-size: 12px;
  letter-spacing: .18em;
  text-transform: uppercase;
}
/* `:not(.btn)` est indispensable : sinon `.nav a` (0,1,1) l'emporte sur
   `.btn--primary` (0,1,0) et le libellé du bouton devient invisible. */
.nav a:not(.btn) { color: #4A5140; }
.nav a:not(.btn):hover { color: var(--sage-deep); }
.nav a:not(.btn)[aria-current='page'] { color: var(--sage); }
.nav .btn { padding: 15px 22px; letter-spacing: .18em; box-shadow: none; }
.nav .btn:hover { transform: none; background: var(--sage-light); color: var(--white); }

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: radial-gradient(120% 100% at 78% 16%, #FBF7EF 0%, #F1ECE1 46%, #E5E5D8 100%);
}

.hero__caustic {
  position: absolute;
  inset: -20% -10% auto -30%;
  height: 140%;
  pointer-events: none;
  opacity: .45;
  background: conic-gradient(from 200deg at 30% 40%,
    rgba(255, 214, 224, .32), rgba(214, 235, 255, .28), rgba(214, 235, 206, .32),
    rgba(255, 246, 214, .32), rgba(255, 214, 224, .32));
  filter: blur(90px);
  animation: causticDrift 34s ease-in-out infinite;
}

.hero__grid {
  position: relative;
  z-index: 3;
  flex: 1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  align-items: center;
  gap: clamp(20px, 4vw, 56px);
  padding: calc(var(--topbar-h, 96px) + clamp(40px, 7vh, 80px))
           var(--gutter)
           clamp(48px, 8vh, 90px);
}

.hero__copy { max-width: 580px; }

.hero__title {
  font-size: clamp(48px, 7vw, 116px);
  line-height: .94;
  letter-spacing: -.015em;
  margin: 24px 0 0;
  color: var(--ink);
}
.hero__title em { font-style: italic; color: var(--sage); }
.hero__title .char { display: inline-block; }
.hero__title .space { display: inline-block; width: .26em; }

.hero__actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 38px; }

.hero__foot {
  position: relative;
  z-index: 4;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding: 0 var(--gutter) 32px;
  font-size: 11px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--muted-2);
}
.hero__scroll { display: flex; align-items: center; gap: 12px; }
.hero__scroll-rail {
  position: relative;
  display: block;
  width: 1px; height: 46px;
  background: rgba(140, 156, 124, .35);
  overflow: hidden;
}
.hero__scroll-rail span {
  position: absolute; inset: 0;
  background: var(--sage);
  animation: scrollHint 2.4s ease-in-out infinite;
}

/* --- Prisme --- */
.prism {
  position: relative;
  width: 100%;
  max-width: 660px;
  margin-left: auto;
  aspect-ratio: 1 / 1;
  perspective: 1400px;
}
.prism__inner {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  transition: transform .7s var(--ease);
}
.prism__float { position: absolute; inset: 0; animation: prismFloat 16s ease-in-out infinite; will-change: transform; }

.prism__halo {
  position: absolute; inset: 4%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 45%,
    rgba(255,255,255,.9) 0%, rgba(255,248,230,.5) 38%, rgba(255,255,255,0) 72%);
  filter: blur(26px);
  animation: haloBreathe 9s ease-in-out infinite;
}

.prism__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 58%, rgba(0,0,0,.72) 68%, rgba(0,0,0,0) 76%);
          mask-image: radial-gradient(circle at 50% 50%, #000 58%, rgba(0,0,0,.72) 68%, rgba(0,0,0,0) 76%);
}
.prism__img--a,
.prism__img--b { mix-blend-mode: screen; }
.prism__img--a {
  filter: hue-rotate(-42deg) saturate(3) blur(3px);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 46%, rgba(0,0,0,0) 72%);
          mask-image: radial-gradient(circle at 50% 50%, #000 46%, rgba(0,0,0,0) 72%);
  animation: dispA 13s ease-in-out infinite;
}
.prism__img--b {
  filter: hue-rotate(58deg) saturate(3.4) blur(4px);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 44%, rgba(0,0,0,0) 70%);
          mask-image: radial-gradient(circle at 50% 50%, #000 44%, rgba(0,0,0,0) 70%);
  animation: dispB 17s ease-in-out infinite;
}

.prism__sweep, .prism__spectrum {
  position: absolute; inset: 0;
  overflow: hidden;
}
.prism__sweep {
  mix-blend-mode: soft-light;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 54%, rgba(0,0,0,0) 74%);
          mask-image: radial-gradient(circle at 50% 50%, #000 54%, rgba(0,0,0,0) 74%);
}
.prism__sweep i {
  position: absolute; top: -30%; left: 0;
  width: 46%; height: 160%;
  background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.85) 45%, rgba(255,255,255,0));
  filter: blur(8px);
  animation: facetSweep 9s cubic-bezier(.5,0,.5,1) infinite;
}
.prism__spectrum {
  mix-blend-mode: screen;
  opacity: .5;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 50%, rgba(0,0,0,0) 72%);
          mask-image: radial-gradient(circle at 50% 50%, #000 50%, rgba(0,0,0,0) 72%);
}
.prism__spectrum i {
  position: absolute; top: -25%; left: 0;
  width: 30%; height: 150%;
  background: linear-gradient(90deg, rgba(255,140,190,0), rgba(255,140,190,.5) 30%,
    rgba(150,220,255,.5) 55%, rgba(180,255,200,.45) 75%, rgba(255,240,150,0));
  filter: blur(14px);
  animation: facetSweep 9s 1.1s cubic-bezier(.5,0,.5,1) infinite;
}

.prism__sparks span {
  position: absolute;
  border-radius: 50%;
  background: #fff;
}
.prism__sparks span:nth-child(1) { top: 12%; left: 46%; width: 7px; height: 7px; box-shadow: 0 0 12px 4px rgba(255,255,255,.85); animation: twinkle 5.5s .3s ease-in-out infinite; }
.prism__sparks span:nth-child(2) { top: 30%; left: 79%; width: 5px; height: 5px; box-shadow: 0 0 10px 3px rgba(255,246,220,.85); animation: twinkle 6.5s 1.4s ease-in-out infinite; }
.prism__sparks span:nth-child(3) { top: 64%; left: 15%; width: 4px; height: 4px; box-shadow: 0 0 9px 3px rgba(255,255,255,.8);  animation: twinkle 7s 2.6s ease-in-out infinite; }
.prism__sparks span:nth-child(4) { top: 84%; left: 58%; width: 6px; height: 6px; box-shadow: 0 0 11px 4px rgba(255,255,255,.8); animation: twinkle 6s 3.4s ease-in-out infinite; }

/* --------------------------------------------------------------------------
   7. Bandeau défilant
   -------------------------------------------------------------------------- */
.marquee {
  overflow: hidden;
  background: var(--sage);
  color: #EFEDE1;
  padding: 22px 0;
  border-block: 1px solid rgba(255, 255, 255, .08);
}
.marquee__track { display: flex; width: max-content; animation: marquee 34s linear infinite; will-change: transform; }
.marquee__group {
  display: flex;
  align-items: center;
  gap: 46px;
  padding-right: 46px;
  font-family: var(--serif);
  font-size: clamp(26px, 3.4vw, 46px);
  letter-spacing: .01em;
  white-space: nowrap;
}
.marquee__group em { font-style: italic; }
.marquee__group .dot { opacity: .45; }

/* --------------------------------------------------------------------------
   8. Protocole (4 étapes)
   -------------------------------------------------------------------------- */
.steps { position: relative; background: var(--cream); }

.steps__sticky {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr));
  align-items: center;
  gap: clamp(24px, 5vw, 72px);
  padding: clamp(70px, 9vh, 100px) var(--gutter) clamp(40px, 6vh, 64px);
}

.steps__title { font-size: clamp(34px, 4.4vw, 68px); line-height: 1.02; margin: 18px 0 0; color: var(--ink); }
.steps__title em { font-style: italic; color: var(--sage); }
.steps__intro { font-size: 16px; line-height: 1.75; color: var(--body); max-width: 38ch; margin: 22px 0 0; }

.steps__meter { display: flex; align-items: center; gap: 16px; margin-top: 34px; }
.steps__count { font-family: var(--serif); font-size: 40px; color: var(--sage); line-height: 1; }
.steps__rail { position: relative; flex: 1; max-width: 220px; height: 1px; background: rgba(28, 31, 23, .14); }
.steps__bar { position: absolute; inset: 0; background: var(--sage-light); transform: scaleX(.25); transform-origin: 0 50%; will-change: transform; transition: transform .4s var(--ease); }
.steps__total { font-size: 11px; letter-spacing: .24em; color: var(--muted-2); }

.steps__list { display: grid; align-items: start; }

.step {
  padding: clamp(20px, 2.4vw, 32px);
  border-radius: var(--radius);
  background: var(--white);
  border: 1px solid rgba(28, 31, 23, .06);
  box-shadow: var(--shadow-card);
}
.step__head { display: flex; align-items: baseline; gap: 14px; }
.step__num { font-size: 11px; letter-spacing: .24em; color: var(--sage-ink); }
.step__title { font-weight: 400; font-size: clamp(22px, 2vw, 30px); color: var(--ink); }
.step__text { font-size: 15px; line-height: 1.7; color: var(--body-soft); margin: 12px 0 0; }

/* --------------------------------------------------------------------------
   9. Zones (accordéon horizontal)
   -------------------------------------------------------------------------- */
.zones {
  background: var(--cream-alt);
  padding: clamp(70px, 12vh, 140px) var(--gutter) clamp(80px, 14vh, 150px);
}
.zones__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  max-width: var(--maxw);
  margin: 0 auto clamp(28px, 5vh, 52px);
}
.zones__hint { font-size: 11px; letter-spacing: .26em; text-transform: uppercase; color: var(--muted-2); }

/* Hauteur dictée par le contenu, pas imposée : à 540px fixes, le panneau
   laissait 301px de vide entre le texte et son pied. Le minimum couvre ce
   dont l'onglet vertical a besoin (« Sur mesure » fait ~160px une fois pivoté). */
.zones__row {
  display: flex;
  gap: 10px;
  max-width: var(--maxw);
  margin-inline: auto;
  min-height: 285px;
  align-items: stretch;
}

.zone {
  display: flex;
  flex: 0 0 74px;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--radius-l);
  background: var(--white);
  border: 1px solid rgba(28, 31, 23, .07);
  transition: flex-grow .6s var(--ease), box-shadow .6s var(--ease);
}
.zone.is-open { flex-grow: 1; box-shadow: var(--shadow-card); }

/* Onglet toujours visible : c'est la poignée, et le seul repère quand la
   carte est repliée. Le libellé passe à la verticale pour tenir en 74px. */
.zone__tab {
  flex: none;
  width: 74px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 24px 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--sans);
  color: var(--body);
  transition: background .5s ease, color .5s ease;
}
.zone__tab:hover { background: rgba(140, 156, 124, .09); }
.zone.is-open .zone__tab { background: var(--sage); color: var(--on-dark); }

.zone__num { font-size: 11px; letter-spacing: .24em; color: var(--sage-ink); transition: color .5s ease; }
.zone.is-open .zone__num { color: var(--sage-pale); }
.zone__nom {
  writing-mode: vertical-rl;
  transform: rotate(180deg);          /* lecture de bas en haut */
  font-size: 13px;
  letter-spacing: .22em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Le panneau n'existe visuellement que déplié. `visibility` le retire aussi
   du parcours de tabulation quand il est fermé. */
.zone__panel {
  flex: 1;
  min-width: clamp(240px, 30vw, 420px);
  padding: clamp(26px, 2.8vw, 40px);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  opacity: 0;
  visibility: hidden;
  transition: opacity .45s ease .05s, visibility 0s .5s;
}
.zone.is-open .zone__panel { opacity: 1; visibility: visible; transition: opacity .5s ease .15s, visibility 0s; }

/* Sans JavaScript, l'accordéon ne s'ouvre pas : seule la zone marquée
   `is-open` dans le HTML était lisible, les cinq autres restaient en
   `visibility: hidden` définitivement — cinq blocs de contenu perdus. On
   déplie alors tout, en pile, comme le fait déjà la mise en page sous 860px. */
html:not(.js) .zones__row { flex-direction: column; height: auto; }
html:not(.js) .zone { flex: 0 0 auto; flex-direction: column; }
html:not(.js) .zone__tab {
  width: 100%;
  flex-direction: row;
  justify-content: flex-start;
  gap: 16px;
  padding: 20px clamp(20px, 5vw, 32px);
  min-height: 62px;
}
html:not(.js) .zone__nom { writing-mode: horizontal-tb; transform: none; }
html:not(.js) .zone__panel {
  flex: none;
  min-width: 0;
  opacity: 1;
  visibility: visible;
  transition: none;
}

.zone__titre { font-weight: 400; font-size: clamp(24px, 2.2vw, 34px); line-height: 1.12; margin: 0; color: var(--ink); }
.zone__texte { font-size: 15px; line-height: 1.7; color: var(--body-soft); margin: 14px 0 0; max-width: 52ch; }
.zone__pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 28px;
  padding-top: 8px;
  border-top: 1px solid rgba(28, 31, 23, .1);
  font-size: 12px;
  letter-spacing: .1em;
  color: var(--sage);
}
.zone__pied > * { padding-block: 15px; }
.zone__pied a { text-decoration: underline; text-underline-offset: 3px; }

@media (max-width: 860px) {
  /* En pile : l'onglet redevient horizontal, le panneau s'ouvre vers le bas. */
  .zones__row { flex-direction: column; height: auto; }
  .zone { flex: 0 0 auto; flex-direction: column; }
  .zone__tab {
    width: 100%;
    flex-direction: row;
    justify-content: flex-start;
    gap: 16px;
    padding: 20px clamp(20px, 5vw, 32px);
    min-height: 62px;
  }
  .zone__nom { writing-mode: horizontal-tb; transform: none; }
  .zone__panel {
    flex: none;
    min-width: 0;
    width: 100%;
    height: 0;
    padding-block: 0;
    transition: height .5s var(--ease), opacity .35s ease, padding .5s var(--ease), visibility 0s .5s;
  }
  .zone.is-open .zone__panel {
    height: auto;
    padding-block: clamp(4px, 1vh, 10px) clamp(24px, 3vh, 34px);
    transition: height .5s var(--ease), opacity .4s ease .1s, padding .5s var(--ease), visibility 0s;
  }
}

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

/* Montants des panneaux : écrits par build.mjs (attributs `data-prix`). En
   capitales espacées, « 150 € » se lisait comme une étiquette ; en romain,
   il se lit comme un prix. */
.zone__pied b {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 19px;
  letter-spacing: 0;
  color: var(--ink);
}
.zone__pied sup { font-size: .72em; line-height: 0; }

/* --------------------------------------------------------------------------
   9b. Grille tarifaire — épilation laser
   Écrite par build.mjs à partir de LASER : aucun prix ne se saisit dans le
   HTML. Même grammaire que les panneaux de zones (cartes blanches sur
   crème, filets fins) pour se lire comme leur prolongement.

   Colonnes fixées plutôt qu'`auto-fill` : cinq groupes plus le diagnostic
   font six cartes, soit 3 × 2 ou 2 × 3 sans trou. En remplissage libre, la
   grille tombait en 4 + 1 à 1100px, une carte orpheline sur sa ligne.
   -------------------------------------------------------------------------- */
.tarifs {
  max-width: var(--maxw);
  margin: clamp(56px, 9vh, 100px) auto 0;
}
.tarifs__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 24px;
  margin-bottom: clamp(20px, 3.5vh, 34px);
}
.tarifs__titre { font-size: clamp(30px, 3.4vw, 48px); line-height: 1.05; color: var(--ink); }
.tarifs__titre em { font-style: italic; color: var(--sage); }
.tarifs__sous { margin: 0; font-size: 11px; letter-spacing: .26em; text-transform: uppercase; color: var(--muted-2); }

.tarifs__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
}
@media (min-width: 600px) { .tarifs__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 980px) { .tarifs__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.tarifs__groupe {
  padding: 24px 24px 10px;
  border-radius: var(--radius);
  background: var(--white);
  border: 1px solid rgba(28, 31, 23, .07);
}
.tarifs__groupe h4 {
  margin: 0 0 4px;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 11px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--sage-ink);
}
.tarifs__entete { display: flex; align-items: center; gap: 14px; margin-bottom: 8px; }
.tarifs__entete h4 { margin: 0; }
.tarifs__groupe ul { list-style: none; margin: 0; padding: 0; }

/* Médaillon des pictogrammes : le filet doré des grilles de la clinique. La
   silhouette est commune aux cinq groupes (build.mjs, `SILHOUETTE`) ; seule
   la zone traitée passe en sauge. */
.picto {
  flex: none;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--cream);
  box-shadow: 0 0 0 1px rgba(169, 139, 98, .5);
}
.silhouette__corps { fill: #D2CCBA; }
.silhouette__zone { fill: var(--sage); }
.picto--clair {
  background: rgba(244, 242, 232, .1);
  box-shadow: 0 0 0 1px rgba(244, 242, 232, .35);
  color: var(--on-dark);
}
.tarifs__groupe li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid rgba(28, 31, 23, .07);
  font-size: 15px;
  line-height: 1.4;
  color: var(--body);
}
.tarifs__groupe li:last-child { border-bottom: 0; }
.tarifs__prix {
  font-family: var(--serif);
  font-size: 21px;
  line-height: 1;
  color: var(--ink);
  white-space: nowrap;
}

/* Sixième carte : le premier rendez-vous, en sauge pour se détacher des
   zones sans quitter la grille. */
.tarifs__groupe--diag { background: var(--sage); border-color: transparent; }
.tarifs__groupe--diag h4 { color: var(--sage-pale); }
.tarifs__groupe--diag li { color: var(--on-dark-warm); }
.tarifs__groupe--diag .tarifs__prix { color: var(--on-dark); }
.tarifs__note { margin: 0 0 14px; font-size: 14px; line-height: 1.6; color: var(--on-dark-mute); }

/* --------------------------------------------------------------------------
   10. Section sombre « La clinique »
   -------------------------------------------------------------------------- */
/* Galerie en accordéon : les miroirs facettés reprennent le motif du prisme
   du hero, en volume réel. La photo mise en avant s'élargit et repousse les
   autres. Elle assure la transition du crème vers le fond sombre de la
   section clinique, qui la suit immédiatement.

   Pas d'`aria-expanded` ici : rien n'est révélé ni masqué, une image est
   seulement agrandie. `aria-pressed` décrit correctement « celle-ci est
   sélectionnée ». Les quatre photos restent lisibles par les technologies
   d'assistance quel que soit l'état. */
.gallery {
  background: var(--ink);
  padding: clamp(44px, 9vh, 100px) var(--gutter) 0;
}
.gallery__row {
  display: flex;
  gap: 12px;
  max-width: var(--maxw);
  margin-inline: auto;
  height: clamp(320px, 58vh, 560px);
}
.gallery__item {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  overflow: hidden;
  border-radius: 14px;
  transition: flex-grow .65s var(--ease);
}
.gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: saturate(.8) brightness(.82);
  transition: filter .55s ease, transform .8s var(--ease);
}
.gallery__item[aria-pressed='true'] { flex-grow: 3.4; }
.gallery__item[aria-pressed='true'] img { filter: none; }

@media (max-width: 760px) {
  /* Le survol n'existe pas au doigt : la rangée devient une pile et
     l'élément actif gagne en hauteur au tap. */
  .gallery__row { flex-direction: column; height: auto; }
  .gallery__item { flex: none; height: 200px; transition: height .55s var(--ease); }
  .gallery__item[aria-pressed='true'] { height: 380px; }
}

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

.clinic {
  position: relative;
  overflow: hidden;
  background: var(--ink);
  color: var(--on-dark);
  padding: clamp(80px, 14vh, 160px) var(--gutter) clamp(80px, 14vh, 150px);
  transform-origin: 50% 0%;
  will-change: transform;
}
.clinic__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: clamp(30px, 5vw, 70px);
  align-items: center;
  max-width: var(--maxw);
  margin-inline: auto;
}
.clinic__title { font-size: clamp(32px, 4.4vw, 68px); line-height: 1.04; margin: 20px 0 0; max-width: 18ch; }
.clinic__text  { font-size: 16px; line-height: 1.8; color: var(--on-dark-soft); max-width: 40ch; margin: 24px 0 0; }

/* Même grammaire que les cartes du journal. Sur fond sombre, l'ombre ne suffit
   pas à détacher la feuille : un liseré clair très fin s'en charge. */
.clinic__figure { margin: 0; }
.clinic__figure a {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  box-shadow: 0 28px 56px -30px rgba(0, 0, 0, .85),
              0 0 0 1px rgba(244, 242, 232, .12);
  transition: transform .55s var(--ease), box-shadow .55s var(--ease);
}
.clinic__figure img { width: 100%; height: auto; }
.clinic__figure a:hover {
  transform: translateY(-5px);
  box-shadow: 0 40px 70px -34px rgba(0, 0, 0, .9),
              0 0 0 1px rgba(244, 242, 232, .2);
}
.clinic__caption {
  margin-top: 16px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--on-dark-mute);
  letter-spacing: .02em;
}

/* --------------------------------------------------------------------------
   11. Chiffres
   -------------------------------------------------------------------------- */
/* Le spectre — un seul geste, porté à pleine échelle.
   Remplace une grille de 4 cellules « grand chiffre + petit label », qui est
   le gabarit par défaut de la catégorie et se lisait comme une fiche technique.
   Trois de ses quatre nombres décrivaient l'appareil ; ils passent en ligne
   d'appui, et les quatre longueurs d'onde deviennent l'affirmation.
   Les faits sont inchangés : mêmes chiffres, tirés de la fiche de la clinique. */
.spectrum {
  background: var(--cream);
  padding: clamp(80px, 14vh, 150px) var(--gutter);
  overflow: hidden;
}
.spectrum__inner { max-width: var(--maxw); margin-inline: auto; }
.spectrum__title {
  font-size: clamp(34px, 4.6vw, 72px);
  line-height: 1.02;
  color: var(--ink);
  margin: 18px 0 clamp(40px, 7vh, 76px);
  max-width: 16ch;
}
.spectrum__title em { font-style: italic; color: var(--sage); }

.spectrum__list {
  list-style: none;
  margin: 0; padding: 0;
  display: grid;
  /* 240px et non 200 : en dessous, `auto-fit` calcule 5 colonnes pour 4
     éléments à 1240px et les rétrécit inutilement. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: clamp(26px, 3vw, 44px) clamp(20px, 2.6vw, 40px);
}
.spectrum__item {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 20px;
  border-top: 1px solid rgba(28, 31, 23, .14);
}
/* La mesure est le sujet : plein corps de la display, comme le hero. */
.spectrum__nm {
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(52px, 7vw, 104px);
  line-height: .88;
  letter-spacing: -.02em;
  color: var(--sage);
  font-variant-numeric: tabular-nums;
}
.spectrum__nm i { font-style: normal; font-size: .28em; letter-spacing: .1em; margin-left: .12em; color: var(--muted); }
.spectrum__cible { font-size: 14.5px; line-height: 1.55; color: var(--body); }

/* Tout le reste se tait pour que la mesure porte. */
.spectrum__foot {
  margin-top: clamp(40px, 7vh, 76px);
  padding-top: 24px;
  border-top: 1px solid rgba(28, 31, 23, .14);
  font-size: 12.5px;
  letter-spacing: .08em;
  color: var(--muted);
}
.spectrum__foot b { font-weight: 400; color: var(--ink); }

/* --------------------------------------------------------------------------
   12. Journal / fiches
   -------------------------------------------------------------------------- */
.journal {
  position: relative;
  overflow: hidden;
  transform-origin: 50% 0%;
  will-change: transform;
  background: var(--cream-alt);
  padding: clamp(70px, 12vh, 140px) var(--gutter) clamp(90px, 14vh, 160px);
}
.journal__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 18px;
  max-width: var(--maxw);
  margin: 0 auto clamp(34px, 6vh, 64px);
}
.journal__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: clamp(18px, 2.4vw, 34px);
  max-width: var(--maxw);
  margin-inline: auto;
}
.card { margin: 0; display: flex; flex-direction: column; gap: 16px; }

/* Les fiches sont des visuels pleine page, avec leur propre fond : elles se
   suffisent. Pas de cadre autour, sinon on encadre deux fois. La feuille est
   la carte — coins arrondis, ombre portée, et elle se soulève au survol. */
.card__media {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
  box-shadow: 0 18px 40px -26px rgba(52, 62, 38, .7),
              0 1px 3px rgba(52, 62, 38, .12);
  transition: transform .55s var(--ease), box-shadow .55s var(--ease);
}
.card__media img { width: 100%; height: auto; }
.card:hover .card__media {
  transform: translateY(-5px);
  box-shadow: 0 34px 62px -32px rgba(52, 62, 38, .8),
              0 2px 5px rgba(52, 62, 38, .14);
}
.card__caption {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  font-size: 15px;
  line-height: 1.6;
  color: var(--body);
}
.card__caption strong { font-weight: 400; color: var(--ink); }

/* Le lien doit se lire comme une action, pas comme la fin de la phrase. */
.card__link {
  display: inline-block;
  padding-block: 18px 12px;
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--sage);
  border-bottom: 1px solid rgba(78, 91, 60, .3);
  padding-bottom: 5px;
  transition: color .3s ease, border-color .3s ease, gap .3s var(--ease);
}
.card__link::after { content: ' →'; }
.card:hover .card__link { color: var(--sage-deep); border-color: var(--sage-deep); }

/* --------------------------------------------------------------------------
   13. Contact / pied de page
   -------------------------------------------------------------------------- */
.contact {
  position: relative;
  overflow: hidden;
  background: var(--sage);
  color: var(--on-dark);
  padding: clamp(90px, 16vh, 180px) var(--gutter) clamp(40px, 6vh, 70px);
}
.contact__glow {
  position: absolute;
  top: -20%; right: -10%;
  width: 46vw; max-width: 620px;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, rgba(255,255,255,.16), rgba(255,255,255,0) 70%);
  pointer-events: none;
}
.contact__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: clamp(30px, 5vw, 80px);
  align-items: end;
  max-width: var(--maxw);
  margin-inline: auto;
}
.contact__title { font-size: clamp(38px, 6vw, 96px); line-height: .98; margin: 20px 0 0; }
.contact__title em { font-style: italic; }
.contact__actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 40px; }
.contact__actions .btn { padding: 18px 32px; }

.contact__aside { display: flex; flex-direction: column; gap: 26px; font-size: 15px; color: var(--on-dark-warm); }
/* `height: auto` est indispensable : l'attribut HTML height="560" alimente une
   règle de la feuille navigateur, ce qui rend les deux dimensions définies et
   neutralise `aspect-ratio`. Le logo carré était rendu en 210×560 et recadré. */
.contact__logo { width: clamp(150px, 18vw, 210px); height: auto; border-radius: 18px; }
.contact__block { display: grid; gap: 6px; }
.contact__block b { font-weight: 400; color: var(--on-dark); }
.contact__lien {
  justify-self: start;
  padding-block: 8px;
  color: var(--on-dark);
  text-decoration: underline;
  text-decoration-color: rgba(244, 242, 232, .4);
  text-underline-offset: 4px;
}
.contact__lien:hover { color: var(--white); text-decoration-color: currentColor; }

/* Instagram et Snapchat — pied de page et section contact, tous deux sur
   fond sauge. 44px : cible tactile. */
.reseaux { display: flex; gap: 10px; margin: 14px 0 0; }
.footer .reseaux__lien,
.reseaux__lien {
  display: inline-grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid rgba(244, 242, 232, .3);
  color: var(--on-dark);
  transition: background .25s ease, border-color .25s ease;
}
.reseaux__lien:hover { background: rgba(244, 242, 232, .1); border-color: rgba(244, 242, 232, .6); color: var(--white); }
.contact__block .reseaux { margin-top: 2px; }

.contact__foot {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  max-width: var(--maxw);
  margin: clamp(50px, 9vh, 90px) auto 0;
  padding-top: 26px;
  border-top: 1px solid rgba(244, 242, 232, .16);
  font-size: 11px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--on-dark-mute);
}
.contact__foot a { color: var(--on-dark-mute); }
.contact__foot a:hover { color: var(--on-dark); }

/* --------------------------------------------------------------------------
   14. Bandeau « ouverture »
   -------------------------------------------------------------------------- */
.notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 18px;
  padding: 12px var(--gutter);
  background: var(--ink);
  color: var(--on-dark);
  font-size: 11.5px;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-align: center;
}
.notice a { color: var(--sage-pale); border-bottom: 1px solid rgba(195, 204, 180, .4); display: inline-block; padding-block: 6px; }
.notice a:hover { color: var(--white); border-color: var(--white); }

/* --------------------------------------------------------------------------
   Pied de page — partagé par les 9 pages, donc dans la feuille commune
   -------------------------------------------------------------------------- */
.footer {
  background: var(--sage);
  color: var(--on-dark-warm);
  padding: clamp(50px, 8vh, 90px) var(--gutter)
           calc(clamp(30px, 5vh, 50px) + env(safe-area-inset-bottom, 0px));
}
.footer__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: clamp(26px, 4vw, 60px);
  max-width: var(--maxw);
  margin-inline: auto;
  font-size: 15px;
}
.footer b { font-weight: 400; color: var(--on-dark); display: block; margin-bottom: 10px; }
.footer a { color: var(--on-dark-warm); display: block; padding: 12px 0; }
.footer a:hover { color: var(--white); }
.footer__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  max-width: var(--maxw);
  margin: clamp(40px, 7vh, 70px) auto 0;
  padding-top: 24px;
  border-top: 1px solid rgba(244, 242, 232, .16);
  font-size: 11px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--on-dark-mute);
}
.footer__foot a { display: inline-block; padding: 14px 0; color: var(--on-dark-mute); }

/* --------------------------------------------------------------------------
   15. Animations
   -------------------------------------------------------------------------- */
@keyframes prismFloat {
  0%   { transform: translate3d(0,0,0) rotate(0deg) scale(1); }
  25%  { transform: translate3d(6px,-14px,0) rotate(1.4deg) scale(1.012); }
  50%  { transform: translate3d(0,-22px,0) rotate(0deg) scale(1.02); }
  75%  { transform: translate3d(-6px,-12px,0) rotate(-1.4deg) scale(1.012); }
  100% { transform: translate3d(0,0,0) rotate(0deg) scale(1); }
}
@keyframes dispA {
  0%,100% { transform: translate3d(-7px,4px,0) scale(1.004); opacity: .30; }
  50%     { transform: translate3d(8px,-6px,0) scale(1.014); opacity: .52; }
}
@keyframes dispB {
  0%,100% { transform: translate3d(8px,-5px,0) scale(1.012); opacity: .42; }
  50%     { transform: translate3d(-9px,6px,0) scale(1.002); opacity: .22; }
}
@keyframes facetSweep {
  0%   { transform: translateX(-140%) rotate(-18deg); }
  100% { transform: translateX(140%) rotate(-18deg); }
}
@keyframes causticDrift {
  0%,100% { transform: rotate(0deg) scale(1); opacity: .5; }
  50%     { transform: rotate(180deg) scale(1.12); opacity: .85; }
}
@keyframes twinkle {
  0%,100% { opacity: 0; transform: scale(.4); }
  50%     { opacity: 1; transform: scale(1); }
}
@keyframes haloBreathe {
  0%,100% { opacity: .55; transform: scale(1); }
  50%     { opacity: .95; transform: scale(1.06); }
}
@keyframes marquee {
  from { transform: translate3d(0,0,0); }
  to   { transform: translate3d(-50%,0,0); }
}
@keyframes letterIn {
  from { opacity: 0; transform: translate3d(0,.6em,0) rotate(7deg); filter: blur(7px); }
  to   { opacity: 1; transform: translate3d(0,0,0) rotate(0deg); filter: blur(0); }
}
@keyframes scrollHint {
  0%   { transform: translateY(-100%); opacity: 0; }
  35%  { opacity: 1; }
  100% { transform: translateY(100%); opacity: 0; }
}

/* --------------------------------------------------------------------------
   15b. Révélations pilotées par IntersectionObserver

   Tout est conditionné à `.js`, posée par `assets/js/boot.js` avant le premier
   rendu. C'est une correction de fond : sans cette garde, un visiteur qui
   désactive JavaScript recevait une page dont la moitié des blocs restaient à
   `opacity: 0` sans que rien ne puisse jamais les lever.

   Trois variantes, choisies avec `data-reveal` :
     (défaut)  montée de 26px + fondu
     fade      fondu seul — pour ce qui ne doit pas bouger (légendes, filets)
     mask      volet qui s'ouvre du bas vers le haut — pour les visuels
     scale     léger rapprochement — pour les blocs qui portent une image
   -------------------------------------------------------------------------- */
.js [data-anim='reveal'] {
  --rv-y: 26px;
  opacity: 0;
  transform: translate3d(0, var(--rv-y), 0);
  transition: opacity .85s var(--ease-out), transform 1s var(--ease-out),
              filter .85s var(--ease-out), clip-path 1.05s var(--ease-out);
}
.js [data-anim='reveal'].is-in {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  filter: none;
}

.js [data-anim='reveal'][data-reveal='fade']  { --rv-y: 0px; }
.js [data-anim='reveal'][data-reveal='scale'] { --rv-y: 18px; transform: translate3d(0, var(--rv-y), 0) scale(.97); }
.js [data-anim='reveal'][data-reveal='scale'].is-in { transform: none; }
.js [data-anim='reveal'][data-reveal='blur']:not(.is-in) { --rv-y: 14px; filter: blur(9px); }

/* Le volet ne touche ni à l'opacité ni au transform : il peut donc se poser
   sur une figure qui porte déjà un parallaxe.

   Le découpage est porté par les enfants, jamais par l'élément observé.
   Ce n'est pas un détail de style : dans Chromium, un `clip-path` sur la
   cible réduit son rectangle d'intersection à zéro, l'IntersectionObserver
   conclut qu'elle n'est jamais visible et ne déclenche donc jamais la
   révélation qui l'aurait ouverte. Le volet resterait fermé pour toujours.

   `transition-delay: inherit` fait redescendre aux enfants le décalage posé
   en ligne sur le parent par `data-delay` / `data-stagger`. */
.js [data-anim='reveal'][data-reveal='mask'] { opacity: 1; transform: none; }
.js [data-anim='reveal'][data-reveal='mask'] > * {
  clip-path: inset(0 0 101% 0);
  transition: clip-path 1.05s var(--ease-out);
  transition-delay: inherit;
}
.js [data-anim='reveal'][data-reveal='mask'].is-in > * { clip-path: inset(0 0 0 0); }

/* Titres découpés en mots — chaque mot remonte derrière une ligne invisible.
   `overflow: hidden` couperait les jambages du Cormorant : on rend au masque
   la place que les descendantes réclament, puis on la reprend en marge. */
.js [data-words]:not(.is-split):not(.is-in),
.js [data-letters]:not(.is-split):not(.is-in) { opacity: 0; }
.word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: .16em;
  margin-bottom: -.16em;
}
.word__i {
  display: inline-block;
  transform: translate3d(0, 110%, 0);
  transition: transform 1s var(--ease-out);
}
.is-in .word__i { transform: translate3d(0, 0, 0); }

.hero__title .char { opacity: 0; animation: letterIn 1s var(--ease) forwards; }

/* --------------------------------------------------------------------------
   15c. Micro-interactions

   Le site avait sa grande chorégraphie de scroll mais rien à l'échelle du
   geste : un bouton survolé montait de 2px, un lien changeait de couleur, et
   c'était tout. Ce qui suit répond au pointeur — c'est là que se joue la
   sensation de finition.
   -------------------------------------------------------------------------- */

/* Balayage lumineux sur les boutons pleins. Le reflet traverse une fois par
   survol ; il n'est pas en boucle, sinon le bouton clignote dans le champ de
   vision périphérique pendant toute la lecture. */
.btn { position: relative; overflow: hidden; isolation: isolate; }
.btn--primary::after,
.btn--light::after,
.btn--dark::after {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: -1;
  background: linear-gradient(105deg,
              transparent 32%, rgba(255, 255, 255, .30) 48%, transparent 64%);
  transform: translate3d(-130%, 0, 0);
  transition: transform .85s var(--ease-out);
}
.btn--light::after,
.btn--dark::after { background: linear-gradient(105deg, transparent 32%, rgba(78, 91, 60, .16) 48%, transparent 64%); }
.btn--primary:hover::after,
.btn--light:hover::after,
.btn--dark:hover::after,
.btn--primary:focus-visible::after,
.btn--light:focus-visible::after { transform: translate3d(130%, 0, 0); }

/* Le bouton s'enfonce à l'appui : sans retour tactile, un clic sur écran
   tactile ne se distingue pas d'un clic manqué. */
.btn:active { transform: translateY(0) scale(.975); transition-duration: .09s; }

/* Le contour des boutons fantômes se remplit depuis le bas plutôt que de
   changer de couleur d'un coup. */
.btn--ghost, .btn--outline-light { transition: transform .35s var(--ease), border-color .45s var(--ease-out), color .35s ease; }
.btn--ghost::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(255, 255, 255, .55);
  transform: scaleY(0);
  transform-origin: 50% 100%;
  transition: transform .45s var(--ease-out);
}
.btn--ghost:hover::after,
.btn--ghost:focus-visible::after { transform: scaleY(1); }

/* Soulignement de navigation : il pousse depuis la gauche, et repart par la
   droite quand le pointeur s'en va — le trait ne rebrousse jamais chemin.

   Le `padding-block` n'existe que pour écarter le trait du texte. Il est donc
   borné au-dessus de 561px, là où la nav est une barre horizontale : sous ce
   seuil elle devient un panneau déplié dont les liens sont dimensionnés pour
   le doigt (44px minimum), et cette règle — plus spécifique que
   `.nav__links a` — les rabotait à 31px. */
.nav a:not(.btn) { position: relative; }
@media (min-width: 561px) {
  .nav a:not(.btn) { padding-block: 6px; }
}
.nav a:not(.btn)::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform .45s var(--ease-out);
}
.nav a:not(.btn):hover::after,
.nav a:not(.btn):focus-visible::after { transform: scaleX(1); transform-origin: 0 50%; }
/* La page courante garde son trait en permanence : c'est un repère, pas un
   survol. Il double `aria-current`, qui reste la source pour les lecteurs. */
.nav a:not(.btn)[aria-current='page']::after { transform: scaleX(1); opacity: .45; }

/* Dans le panneau mobile, chaque lien porte déjà un filet de séparation :
   un second trait au même endroit ne se lirait plus comme un soulignement. */
@media (max-width: 560px) {
  .nav__links a:not(.btn)::after { display: none; }
}

/* La flèche des liens « lire la fiche » avance au survol. */
.link-underline, .card__link { position: relative; }
.card__link::after { transition: none; }
.card__link { transition: color .3s ease, border-color .3s ease, letter-spacing .35s var(--ease-out); }
.card:hover .card__link, .card__link:focus-visible { letter-spacing: .24em; }
.link-underline { transition: color .3s ease, border-color .3s ease, letter-spacing .35s var(--ease-out); }
.link-underline:hover, .link-underline:focus-visible { letter-spacing: .3em; }

/* Le visuel respire dans son cadre quand la carte se soulève. Les fiches sont
   des planches de texte : au-delà de 1,03 le rendu se ramollit. */
.card__media img,
.clinic__figure img { transition: transform .8s var(--ease-out); }
.card:hover .card__media img,
.clinic__figure a:hover img { transform: scale(1.028); }

/* Le bandeau défilant s'arrête au survol : on ne peut pas lire un texte qui
   fuit, et le lecteur qui s'y attarde veut justement le lire. */
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track { animation-play-state: paused; }

/* Le halo du prisme réagit au survol du hero — très légèrement : c'est un
   décor, il ne doit pas voler l'attention aux deux boutons d'appel. */
.hero:hover .prism__halo { animation-duration: 6.5s; }

/* --------------------------------------------------------------------------
   16. Chorégraphie desktop (scroll pinné) — activée par JS via .has-scrollfx
   -------------------------------------------------------------------------- */
@media (min-width: 900px) {
  /* Longueurs calées sur le déplacement réel, pas au jugé.
     Protocole : 4 cartes × ~0,35 écran. L'ancien 380vh imposait 855px de
     scroll par carte, soit presque un écran plein pour un simple fondu.
     Zones : la piste ne parcourt que ~1290px utiles ; 360vh demandait
     2,5px de scroll par pixel translaté, d'où l'impression de patiner.
     250vh ramène le rapport à ~1,05 — le contenu suit le doigt. */
  .has-scrollfx .steps { height: 240vh; }
  /* On garde la pleine hauteur : le vide y est symétrique et se lit comme de
     l'air voulu. Le dimensionner sur le contenu le collait en haut de fenêtre
     et reportait tout l'espace mort en dessous — asymétrique, donc perçu comme
     inachevé. Le vrai levier était la durée, traitée par la hauteur de section. */
  .has-scrollfx .steps__sticky {
    position: sticky;
    top: 0;
    height: 100svh;
    overflow: hidden;
  }
  /* Les 4 cartes restent visibles, l'active passe au premier plan.
     L'ancienne version les empilait en `grid-area: 1/1` et n'en montrait
     qu'une : 164px de contenu dans 900px de fenêtre, d'où le vide. Et le
     compteur « 01 / 04 » annonçait quatre étapes qu'on ne voyait jamais. */
  .has-scrollfx .steps__list { gap: 14px; }

  /* Passage de relais continu.
     `--w` est écrit à chaque frame par le JS : 1 sur la carte visée, il
     décroît linéairement vers ses voisines. Deux cartes se croisent donc au
     lieu de commuter d'un coup — c'est ce qui manquait, la version précédente
     basculait une classe et laissait une transition de 0,5 s rattraper un
     saut que le scroll avait déjà dépassé.
     Aucune `transition` ici, volontairement : la valeur arrive déjà lissée,
     une transition par-dessus ne ferait qu'ajouter du retard. */
  .has-scrollfx .step {
    --w: 0;
    position: relative;
    opacity: calc(.34 + var(--w) * .66);
    transform: scale(calc(.968 + var(--w) * .032)) translate3d(calc(var(--w) * 8px), 0, 0);
    transform-origin: 0 50%;
    box-shadow: none;
  }
  /* L'ombre ne s'interpole pas au calc : on la porte sur un calque dont on
     fait varier l'opacité, ce qui revient au même à l'œil et coûte moins. */
  .has-scrollfx .step::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    box-shadow: var(--shadow-card);
    opacity: var(--w);
    pointer-events: none;
  }
}

/* --------------------------------------------------------------------------
   17. Repli mobile / sans chorégraphie
   -------------------------------------------------------------------------- */
.steps__list { gap: 18px; }




@media (max-width: 899px) {
  .steps { padding-block: clamp(70px, 10vh, 110px); }
  .steps__sticky { padding-block: 0; }
  .header { padding: 18px var(--gutter); }
  .brand__logo { height: 28px; }
  .nav { font-size: 11px; gap: 10px 16px; }
  .nav .btn { padding: 10px 18px; }
  .hero__foot { flex-direction: column; align-items: flex-start; gap: 14px; }
  .contact__actions .btn { width: 100%; }
}

/* Bouton de dépliage — masqué en desktop, seul chemin de navigation en mobile.
   Les liens y étaient auparavant en `display: none` sans remplacement : la home
   n'offrait plus aucun accès aux pages de contenu sous 561px. La première
   correction (liens repliés sur une 2e ligne) portait le bandeau fixe à 268px,
   soit 33% de l'écran — pire que le mal. D'où ce dépliage. */
.nav__toggle {
  display: none;
  align-items: center;
  gap: 9px;
  padding: 13px 18px;          /* 44px de haut : seuil de cible tactile */
  border: 1px solid rgba(28, 31, 23, .22);
  border-radius: 999px;
  background: transparent;
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #4A5140;
  cursor: pointer;
}
.nav__toggle-bars {
  position: relative;
  width: 15px; height: 9px;
  flex: none;
}
.nav__toggle-bars::before,
.nav__toggle-bars::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 1.5px;
  background: currentColor;
  transition: transform .35s var(--ease), opacity .25s ease;
}
.nav__toggle-bars::before { top: 0; }
.nav__toggle-bars::after  { bottom: 0; }
.nav__toggle[aria-expanded='true'] .nav__toggle-bars::before { transform: translateY(3.75px) rotate(45deg); }
.nav__toggle[aria-expanded='true'] .nav__toggle-bars::after  { transform: translateY(-3.75px) rotate(-45deg); }

.nav__links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 12px clamp(16px, 2.6vw, 34px);
}

@media (max-width: 560px) {
  .nav__toggle { display: inline-flex; }
  .nav { gap: 10px; }
  .nav .btn { padding: 11px 18px; }
  /* Logo, Menu et Rendez-vous sur une seule ligne jusqu'à 360px : avec
     l'écart de 32px du desktop, l'en-tête passait sur deux lignes dès
     390px — la largeur des iPhone courants. */
  .header { column-gap: 12px; }

  /* Le panneau se déplie sous l'en-tête sans en modifier la hauteur. */
  .nav__links {
    position: absolute;
    top: 100%; left: 0; right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0 var(--gutter);
    max-height: 0;
    overflow: hidden;
    background: rgba(242, 237, 227, .97);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: var(--shadow-header);
    transition: max-height .4s var(--ease);
  }
  /* `max-height: 0` masque à l'œil mais pas au clavier ni aux lecteurs
     d'écran : les quatre liens restaient atteignables au tabulateur alors que
     rien n'était visible à l'écran. `visibility` les retire réellement, et le
     délai la laisse jouer après la fermeture pour ne pas couper l'animation. */
  .nav__links { visibility: hidden; transition: max-height .4s var(--ease), visibility 0s .4s; }
  .nav__links[data-open='true'] {
    max-height: 60vh; overflow-y: auto; overscroll-behavior: contain;
    visibility: visible; transition: max-height .4s var(--ease), visibility 0s;
  }

  /* Sans JavaScript, rien ne pose `data-open` : le menu serait définitivement
     inatteignable, et c'est le seul chemin vers les pages de contenu sous
     561px. On rend alors les liens au flux, et le bouton de dépliage — qui ne
     répond à rien — disparaît.

     Conséquence à traiter : le bloc fixe passe alors de 120 à 228 px, et
     recouvre le haut du hero de 73 px — la hauteur qui le compense,
     `--topbar-h`, est mesurée en JavaScript et vaut ici son repli de 96 px.
     Plutôt que de deviner une valeur qui dépend de la largeur, on rend le
     bandeau au flux : il défile avec la page, ne recouvre plus rien, et la
     compensation devient inutile. Voir la règle qui suit la media query. */
  html:not(.js) .nav__toggle { display: none; }
  html:not(.js) .nav__links {
    position: static;
    max-height: none;
    overflow: visible;
    visibility: visible;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 10px 18px;
    padding: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
  html:not(.js) .nav__links a { padding: 12px 0; border-bottom: none; }
  .nav__links a {
    padding: 15px 0;
    font-size: 12px;
    border-bottom: 1px solid rgba(28, 31, 23, .08);
  }
  .nav__links a:last-child { border-bottom: none; }
  .notice a { display: none; }
}

/* 360px, la largeur de nombreux Android : quelques pixels de marge en moins
   dans les deux boutons, sans toucher à leur hauteur de 44px. */
@media (max-width: 380px) {
  .nav { gap: 8px; }
  .nav .btn,
  .nav__toggle { padding-inline: 14px; }
}

/* --------------------------------------------------------------------------
   17a. Sous-menus « Laser » et « Soins esthétiques »

   Motif « disclosure » : un vrai <button> annonce son état par
   `aria-expanded` et contrôle une liste de liens ordinaires. Pas de rôle
   `menu` : il promettrait aux lecteurs d'écran une navigation aux flèches
   que ces liens n'ont pas à offrir.

   Un seul état, `aria-expanded`, posé par main.js. Au-dessus de 560px il
   ouvre un panneau flottant sous le bouton ; en dessous, un accordéon dans
   le panneau mobile. Le balisage est écrit par build.mjs (fonction `nav`).

   Placée après les sections 5 et 15c à dessein : `.nav .nav__sub a` a la
   même spécificité que `.nav a:not(.btn)`, c'est l'ordre qui tranche.
   -------------------------------------------------------------------------- */
.nav__group { position: relative; }

.nav__group-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 44px;             /* cible tactile : les tablettes ont ce menu */
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: #4A5140;
  cursor: pointer;
}
.nav__group-btn:hover,
.nav__group-btn[aria-expanded='true'] { color: var(--sage-deep); }
.nav__group.is-current > .nav__group-btn { color: var(--sage); }

/* Même soulignement que les liens de la barre (section 15c), arrêté avant
   le chevron. */
.nav__group-btn::after {
  content: '';
  position: absolute;
  left: 0; right: 17px; bottom: 9px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform .45s var(--ease-out);
}
.nav__group-btn:hover::after,
.nav__group-btn:focus-visible::after,
.nav__group-btn[aria-expanded='true']::after { transform: scaleX(1); transform-origin: 0 50%; }
.nav__group.is-current > .nav__group-btn::after { transform: scaleX(1); opacity: .45; }

.nav__chevron {
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: -3px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform .35s var(--ease);
}
.nav__group-btn[aria-expanded='true'] .nav__chevron { transform: translateY(3px) rotate(-135deg); }

/* Contenu des panneaux — commun aux deux présentations. Les libellés
   passent en casse normale : neuf noms de soins en capitales espacées ne
   se lisent plus. */
.nav__sub { font-size: 14.5px; letter-spacing: .01em; text-transform: none; }
.nav__list { list-style: none; margin: 0; padding: 0; }
.nav .nav__sub a {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 14px 8px 8px;
  border-radius: 12px;
  color: var(--body);
  transition: background .25s ease, color .25s ease;
}
.nav__nom { flex: 1 1 auto; }
/* Photo du soin, ou pictogramme pour le laser : même médaillon que sur les
   grilles de la clinique — fond blanc, filet doré. */
.nav__visuel {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--white);
  box-shadow: 0 0 0 1px rgba(169, 139, 98, .45);
  color: var(--sage);
}
.nav__visuel .silhouette__corps { fill: var(--sage-light); }
.nav .nav__sub a::after { display: none; }
.nav .nav__sub a:hover { background: rgba(140, 156, 124, .14); color: var(--sage-deep); }
.nav .nav__sub a[aria-current='page'] { color: var(--sage); }
.nav__prix { flex: none; margin-left: 16px; font-family: var(--serif); font-size: 18px; line-height: 1; color: var(--sage); }
.nav__note {
  margin: 6px 14px 4px;
  font-size: 10px;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--muted);
}
.nav__tout { margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(28, 31, 23, .08); }
/* Un cran plus spécifique que `.nav .nav__sub a` : sinon la taille des
   liens de soins, redéfinie en mobile, l'emporte sur celle-ci. */
.nav .nav__sub .nav__tout a { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--sage); }

@media (min-width: 561px) {
  .nav__sub {
    position: absolute;
    top: calc(100% + 6px);
    left: -14px;
    z-index: 2;
    min-width: 250px;
    padding: 8px;
    border: 1px solid rgba(28, 31, 23, .08);
    border-radius: 18px;
    /* Fond plein, sans backdrop-filter : l'en-tête en porte déjà un, et un
       second imbriqué ne floute que l'en-tête, pas la page. Sans flou, la
       moindre transparence laisse deviner le titre du hero, 64px plus bas. */
    background: var(--cream);
    box-shadow: 0 34px 60px -30px rgba(40, 48, 29, .5);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-6px);
    /* `visibility` attend la fin du fondu : pendant ces 350 ms, le panneau
       qui se ferme restait une cible. Un curseur immobile au-dessus — après
       un clic sur un soin de la page même, la page défile dessous — y
       déclenchait un `pointerenter`, et le survol le rouvrait aussitôt. */
    pointer-events: none;
    transition: opacity .2s ease, transform .35s var(--ease-out), visibility 0s .35s;
  }
  /* Le groupe de droite s'ouvre vers la gauche : à 600px de large, calé à
     gauche, il sortait de l'écran. */
  .nav__group--soins .nav__sub { left: auto; right: -14px; min-width: 360px; }
  /* Avec les photos, les soins dépassent 540px de haut : sur un portable en
     1366×768, les derniers passaient sous le bord de l'écran, hors d'atteinte
     puisque l'en-tête est fixe. Le défilement se fait dans le panneau — sur
     l'élément intérieur, pour ne pas rogner le pont `::before` ci-dessous. */
  .nav__sub-in {
    max-height: calc(100vh - 150px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* Pont entre le bouton et le panneau : sans lui, le pointeur qui descend
     traverse quelques pixels de vide et referme le panneau en chemin. */
  .nav__sub::before { content: ''; position: absolute; left: 0; right: 0; bottom: 100%; height: 10px; }
  .nav__group-btn[aria-expanded='true'] + .nav__sub {
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    transition: opacity .2s ease, transform .35s var(--ease-out), visibility 0s;
  }
  /* Sans JavaScript, rien ne pose `aria-expanded` : le survol et le focus
     clavier ouvrent le panneau à sa place. */
  html:not(.js) .nav__group:hover > .nav__sub,
  html:not(.js) .nav__group:focus-within > .nav__sub {
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    transition: none;
  }
}

@media (max-width: 560px) {
  /* Le panneau compte jusqu'à quatorze lignes une fois les soins dépliés :
     il prend la hauteur de l'écran sous l'en-tête plutôt que 60 %. */
  .nav__links[data-open='true'] {
    max-height: calc(100vh - var(--topbar-h, 96px));
    max-height: calc(100dvh - var(--topbar-h, 96px));
  }
  .nav__group + .nav__group { border-top: 1px solid rgba(28, 31, 23, .08); }
  .nav__group-btn {
    display: flex;
    width: 100%;
    justify-content: space-between;
    min-height: 50px;
    font-size: 12px;
  }
  .nav__group-btn::after { display: none; }
  .nav__chevron { margin-right: 4px; }

  /* Dépliage à la hauteur réelle : une grille 0fr → 1fr s'anime sans
     connaître la hauteur du contenu, là où `max-height` imposerait de la
     deviner. `visibility` retire les liens repliés de la tabulation. */
  .nav__sub {
    display: grid;
    grid-template-rows: 0fr;
    visibility: hidden;
    transition: grid-template-rows .35s var(--ease), visibility 0s .35s;
  }
  .nav__sub-in { min-height: 0; overflow: hidden; }
  .nav__group-btn[aria-expanded='true'] + .nav__sub {
    grid-template-rows: 1fr;
    visibility: visible;
    transition: grid-template-rows .35s var(--ease), visibility 0s;
  }
  .nav .nav__sub a {
    align-items: center;
    min-height: 44px;
    padding: 6px 6px 6px 8px;
    border-bottom: 0;
    font-size: 14.5px;
  }
  .nav__list { padding-bottom: 10px; }

  /* Sans JavaScript, les boutons ne déplient rien : les groupes restent
     ouverts, dans le flux (voir `html:not(.js) .nav__links`, section 17).
     Déplié tel quel, le menu repoussait le hero à 900px du haut de l'écran.
     On le resserre : les liens Laser en ligne, et pour les soins le seul
     lien « Tous les soins » — la page liste les neuf, avec leurs prix. */
  html:not(.js) .nav__links { flex-direction: column; align-items: stretch; flex-basis: 100%; }
  html:not(.js) .nav__sub { grid-template-rows: 1fr; visibility: visible; }
  html:not(.js) .nav__chevron { display: none; }
  html:not(.js) .nav__group-btn { min-height: 0; padding-top: 12px; cursor: default; }
  html:not(.js) .nav__list { display: flex; flex-wrap: wrap; column-gap: 20px; padding-bottom: 4px; }
  html:not(.js) .nav__note,
  html:not(.js) .nav__group--soins .nav__list > li:not(.nav__tout) { display: none; }
  html:not(.js) .nav__tout { margin-top: 0; padding-top: 0; border-top: 0; }
  html:not(.js) .nav .nav__sub a { padding-inline: 0; }
  html:not(.js) .nav__visuel { display: none; }
}

/* --------------------------------------------------------------------------
   17b. Transition entre pages

   Le site compte neuf pages qui se renvoient l'une à l'autre en permanence.
   Chaque clic produisait un clignotement blanc — d'autant plus visible que le
   fond est crème et que la moitié de la page est déjà identique d'une page à
   l'autre (bandeau, en-tête, pied). `@view-transition` demande au navigateur
   de fondre l'ancienne page dans la nouvelle ; couplé au préchargement au
   survol, la navigation ne se voit plus.

   Aucun risque : les navigateurs qui ne connaissent pas la règle l'ignorent
   et naviguent comme avant. Rien d'autre dans le site n'en dépend.
   -------------------------------------------------------------------------- */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation-duration: .18s; animation-timing-function: var(--ease-inout); }
::view-transition-new(root) { animation-duration: .28s; animation-timing-function: var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* Sans JavaScript, le bandeau retourne au flux.
   Il est `position: fixed` pour rester accessible pendant le défilement, et
   le hero compense sa hauteur avec `--topbar-h`, mesurée en JavaScript. Sans
   JavaScript, cette mesure n'a pas lieu : la valeur de repli est figée alors
   que la hauteur réelle varie avec la largeur et le nombre de lignes du menu.
   Un bandeau qui défile normalement n'a aucune hauteur à compenser — et rien
   à recouvrir. */
html:not(.js) .topbar { position: static; }
html:not(.js) .hero { padding-top: clamp(40px, 7vh, 80px); }
html:not(.js) .page-head { padding-top: clamp(40px, 7vh, 80px); }

/* --------------------------------------------------------------------------
   18. Accessibilité — mouvement réduit
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .hero__title .char { opacity: 1; }
  /* La garde `.js` monte la spécificité de l'état initial : il faut la
     reprendre ici, sinon la règle de repli ne l'emporte pas. */
  .js [data-anim='reveal'],
  [data-anim='reveal'] { opacity: 1; transform: none; filter: none; clip-path: none; }
  .js [data-words]:not(.is-split):not(.is-in),
  .js [data-letters]:not(.is-split):not(.is-in),
  [data-words], [data-letters] { opacity: 1; }
  .word { overflow: visible; }
  .word__i { transform: none; }
  .js [data-anim='reveal'][data-reveal='mask'] > * { clip-path: none; }
  /* Le prisme et ses calques.
     Neutraliser une animation en la jouant en 0,001 ms ne la neutralise pas :
     l'élément revient à sa valeur CSS de base, et pour ces calques la valeur
     de base est plus forte que tout ce que l'animation traverse — les deux
     couches de dispersion, le halo et les caustiques repassent à pleine
     opacité et délavent le prisme jusqu'au blanc. On les arrête vraiment, et
     on leur donne le repos qui correspond au milieu de leur course. */
  .prism__float, .hero__caustic, .marquee__track { animation: none; }
  .prism__halo, .prism__img--a, .prism__img--b,
  .prism__sweep i, .prism__spectrum i, .prism__sparks span { animation: none; }
  .prism__halo    { opacity: .62; transform: none; }
  .prism__img--a  { opacity: .34; transform: none; }
  .prism__img--b  { opacity: .30; transform: none; }
  .hero__caustic  { opacity: .55; transform: none; }
  /* Le balayage de facette n'a pas d'état de repos qui ait un sens : hors
     course, il se poserait en travers du prisme. On le retire. */
  .prism__sweep i, .prism__spectrum i { display: none; }
  .prism__sparks span { opacity: .75; transform: none; }
  .step { opacity: 1 !important; transform: none !important; pointer-events: auto !important; }
  .step::after { display: none; }
  /* Les balayages de survol sont du mouvement pur : on les retire au lieu de
     les jouer en 0,001 ms, ce qui produirait un scintillement. */
  .btn--primary::after, .btn--light::after, .btn--dark::after, .btn--ghost::after { display: none; }
  .card:hover .card__media img, .clinic__figure a:hover img { transform: none; }
}

/* --------------------------------------------------------------------------
   19. Impression
   -------------------------------------------------------------------------- */
@media print {
  .header, .progress, .marquee, .hero__caustic, .prism, .notice { display: none !important; }
  body { background: #fff; color: #000; }
  .has-scrollfx .steps { height: auto; }

  /* Une page imprimée n'est jamais parcourue : les blocs situés sous la ligne
     de flottaison n'ont pas croisé le viewport, donc rien ne les a révélés, et
     ils sortaient blancs sur le papier. On lève tous les états initiaux —
     l'impression n'a de toute façon aucun mouvement à jouer. */
  .js [data-anim='reveal'],
  .js [data-reveal-children] > *,
  .js [data-words], .js [data-letters] {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
  }
  .js [data-anim='reveal'][data-reveal='mask'] > *,
  .js .article__figure > .article__sheet { clip-path: none !important; }
  .word { overflow: visible; }
  .word__i { transform: none !important; }
  /* Les cartes du protocole sont pilotées par `--w`, jamais écrit hors écran. */
  .has-scrollfx .step { opacity: 1 !important; transform: none !important; }
  .has-scrollfx .step::after { display: none; }
  .zone__panel { opacity: 1 !important; visibility: visible !important; }
}
