/* =============================================================================
   alainmielle.fr — feuille de style unique.
   Langage repris du prototype « Statistiques » : toile très sombre, cartouches
   numérotés, titres à deux tons, chiffres géants en dégradé, révélation à
   l'entrée dans le champ. Palette dérivée du logo AM&I (marine profond +
   bleu vif). Tout se désarme sous prefers-reduced-motion.
   ========================================================================== */

/* ---------------------------------------------------------------- 1. Jetons */
:root {
  /* Toile — marine quasi noir, la teinte du logo poussée dans l'ombre. */
  --bg: #05070f;
  --bg-2: #080c18;
  --bg-3: #0b1120;
  --surface: rgb(255 255 255 / 3.2%);
  --surface-2: rgb(255 255 255 / 5.5%);
  --border: rgb(255 255 255 / 9%);
  --border-strong: rgb(255 255 255 / 16%);

  --text: #eef2fb;
  --text-2: #b9c2d6;
  --muted: #7f8aa3;
  --faint: #5a6479;

  /* Accents. Le bleu est celui de l'esperluette du logo. */
  --blue: #2f80ff;
  --blue-bright: #5aa9ff;
  --violet: #9b93ff;
  --cyan: #47d6e5;
  --green: #6fcf87;
  --amber: #ffab40;

  --grad: linear-gradient(96deg, var(--blue-bright), var(--violet) 52%, var(--cyan));
  --grad-warm: linear-gradient(96deg, var(--blue-bright), var(--violet) 45%, var(--green));

  --glow: 0 0 0 1px rgb(255 255 255 / 6%), 0 24px 70px -30px rgb(47 128 255 / 45%);
  --ring: rgb(47 128 255 / 35%);

  /* Rythme */
  --maxw: 74rem;
  --gut: clamp(1.25rem, 4vw, 3.5rem);
  --r-lg: 1.25rem;
  --r-md: 0.875rem;
  --r-sm: 0.5rem;

  /* Courbe unique, celle du prototype : décollage franc, arrivée posée. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Familles. Pas de CDN : piles système, la plus fine disponible d'abord —
     c'est ce qui rapproche le plus des lettres du logo. */
  --font-mark: "Futura", "Century Gothic", "Avenir Next", "Helvetica Neue",
    "Segoe UI", system-ui, sans-serif;
  --font: "Helvetica Neue", "Inter", "Segoe UI", system-ui, -apple-system,
    "Roboto", Arial, sans-serif;
}

/* Variante claire — même dessin, registre institutionnel. */
:root[data-theme="light"] {
  --bg: #f2f5fb;
  --bg-2: #ffffff;
  --bg-3: #e8edf7;
  --surface: #ffffff;
  --surface-2: #f7f9fd;
  --border: rgb(9 22 48 / 10%);
  --border-strong: rgb(9 22 48 / 18%);

  --text: #08152e;
  --text-2: #2f3d58;
  --muted: #5b6780;
  --faint: #8b95a9;

  --blue: #1257c9;
  --blue-bright: #1f6fe0;
  --violet: #6a5de0;
  --cyan: #0f9fb4;
  --green: #1f8f52;
  --amber: #c97a10;

  --glow: 0 0 0 1px rgb(9 22 48 / 5%), 0 24px 60px -34px rgb(18 87 201 / 30%);
  --ring: rgb(18 87 201 / 28%);
}

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

html {
  scroll-behavior: smooth;
  /* La barre de nav collante ne doit pas manger les titres d'ancre. */
  scroll-padding-top: 5.5rem;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: clamp(0.95rem, 0.42vw + 0.86rem, 1.0625rem);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  /* `clip` et non `hidden` : `hidden` ferait du corps un conteneur de
     défilement à part entière, ce qui déporte le défilement du document et
     perturbe ancrage, position collante et outils de mesure. */
  overflow-x: clip;
  transition: background-color 0.5s var(--ease), color 0.5s var(--ease);
}

/* `svg` en display:block : sans cela il reste en ligne, `margin:auto` ne le
   centre pas et un espace fantôme de ligne de base s'ajoute sous chaque icône.
   Toutes les icônes du site vivent dans des conteneurs flex ou grid, où block
   est de toute façon le comportement attendu. */
/* `height: auto` est indispensable : les images portent leurs dimensions
   intrinsèques en attribut (pour réserver la place et éviter le saut de mise en
   page), et sans cela une largeur posée en CSS les étirerait. */
img, svg { max-width: 100%; display: block; }
img { height: auto; }
a { color: inherit; text-decoration: none; }

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

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

/* ------------------------------------------------------- 2. Trame commune */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gut);
}

section { position: relative; }

.band { padding-block: clamp(4.5rem, 11vh, 9rem); }

/* Cartouche numéroté — le tiret, le numéro, le libellé espacé. */
.kicker {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0 0 1.15rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.kicker::before {
  content: "";
  width: 1.75rem;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  transform-origin: left;
  transition: transform 0.7s var(--ease) 0.1s;
}

html.js .kicker::before { transform: scaleX(0); }

html.js [data-in] .kicker::before,
html.js .kicker[data-in]::before { transform: none; }

.kicker b { color: var(--text-2); font-weight: 600; }

/* Dans un chapitre à environnement, le cartouche est **remonté d'un cran**.
   `--muted` a été réglé pour la toile presque noire de la page ; sur un décor
   éclairé — et les huit chapitres en ont désormais un — il passe pour un gris
   moyen, et à 11 px de corps le contraste tombe sous le seuil. C'est la faute
   déjà corrigée sur la description des repères (`--rep-txt`), au même endroit du
   raisonnement : la couleur d'un texte de plan dépend du **décor du chapitre**,
   pas de la toile de la page. Le numéro monte du même cran, sinon les deux tons
   se confondent et la hiérarchie du cartouche disparaît. Les deux valeurs sont
   des jetons, donc les deux registres suivent sans une règle de plus. */
[data-live] .kicker { color: var(--text-2); }
[data-live] .kicker b { color: var(--text); }

/* Titre à deux tons : l'affirmation en clair, la suite en retrait. */
.h2 {
  margin: 0 0 1.5rem;
  font-size: clamp(1.5rem, 2.6vw + 0.5rem, 2.85rem);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.032em;
  text-wrap: balance;
}

.h2 span { color: var(--muted); }

.lede {
  margin: 0 0 2rem;
  max-width: 46rem;
  font-size: clamp(1rem, 0.5vw + 0.9rem, 1.1875rem);
  line-height: 1.6;
  color: var(--text-2);
}

/* ------------------------------------------- 3. Révélation à l'entrée */
/* Amélioration progressive : sans JS — et donc dans les vignettes du panneau
   Design System, où les scripts ne tournent pas — tout est déjà visible. Le
   script pose `js` sur <html> et c'est seulement là que le masquage s'active. */
.rv { opacity: 1; }

html.js .rv {
  opacity: 0;
  transform: translateY(1.15rem);
  transition:
    opacity 0.8s var(--ease),
    transform 0.8s var(--ease);
  transition-delay: calc(var(--step, 0) * 80ms);
}

html.js .rv[data-in] { opacity: 1; transform: none; }

/* --------------------------------------------------- 4. Décor atmosphérique */
/* Aurores : trois taches floues qui dérivent lentement. Elles portent toute
   l'ambiance du fond, aucune image n'est nécessaire. */
.aurora {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  contain: strict;
}

.aurora i {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.5;
  will-change: transform;
}

.aurora i:nth-child(1) {
  width: 46vw; height: 46vw; top: -14vw; left: -8vw;
  background: radial-gradient(circle, rgb(47 128 255 / 55%), transparent 68%);
  animation: drift-a 26s ease-in-out infinite;
}

.aurora i:nth-child(2) {
  width: 40vw; height: 40vw; top: 34vh; right: -12vw;
  background: radial-gradient(circle, rgb(155 147 255 / 42%), transparent 68%);
  animation: drift-b 32s ease-in-out infinite;
}

.aurora i:nth-child(3) {
  width: 38vw; height: 38vw; bottom: -14vw; left: 22vw;
  background: radial-gradient(circle, rgb(71 214 229 / 30%), transparent 70%);
  animation: drift-c 38s ease-in-out infinite;
}

:root[data-theme="light"] .aurora i { opacity: 0.3; filter: blur(80px); }

@keyframes drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(6vw, 7vh, 0) scale(1.14); }
}
@keyframes drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.06); }
  50%      { transform: translate3d(-7vw, -6vh, 0) scale(0.92); }
}
@keyframes drift-c {
  0%, 100% { transform: translate3d(0, 0, 0) scale(0.95); }
  50%      { transform: translate3d(5vw, -8vh, 0) scale(1.16); }
}

/* Trame de points : rappel de la mappemonde pointillée du logo. */
.grid-dots {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: radial-gradient(circle, rgb(255 255 255 / 12%) 1px, transparent 1px);
  background-size: 34px 34px;
  mask-image: radial-gradient(ellipse 90% 60% at 50% 34%, #000 12%, transparent 72%);
  opacity: 0.5;
}

:root[data-theme="light"] .grid-dots {
  background-image: radial-gradient(circle, rgb(9 22 48 / 12%) 1px, transparent 1px);
  opacity: 0.6;
}

/* Halo qui suit le pointeur : le « pétillant » discret. */
.spot {
  position: fixed;
  z-index: 0;
  width: 34rem; height: 34rem;
  margin: -17rem 0 0 -17rem;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle, rgb(90 169 255 / 13%), transparent 62%);
  opacity: 0;
  transition: opacity 0.6s var(--ease);
  will-change: transform;
}

body[data-pointer] .spot { opacity: 1; }

.page { position: relative; z-index: 1; }

/* --------------------------------------------------------- 5. Barre de nav */
.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 1rem;
  height: 4.25rem;
  padding-inline: var(--gut);
  background: transparent;
  transition: background-color 0.45s var(--ease), border-color 0.45s var(--ease),
    backdrop-filter 0.45s var(--ease);
  border-bottom: 1px solid transparent;
}

.nav[data-stuck] {
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(18px) saturate(1.3);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
  border-bottom-color: var(--border);
}

.nav-in {
  display: flex;
  align-items: center;
  gap: 1rem;
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
}

/* La marque de la barre. Le lien n'est qu'un **emplacement** : il réserve la
   place que le dessin occupe au repos, et rien de plus. Le dessin lui-même est
   posé en absolu et débordant, ce qui lui permet de quadrupler de taille à
   l'ouverture sans déplacer un pixel de la barre — la hauteur de celle-ci est
   fixe et non négociable.
   Les mesures de l'emplacement et l'agencement condensé sont en section 21,
   avec le reste de la marque. */
.mono {
  position: relative;
  display: block;
  flex: none;
}

.mono .mk {
  position: absolute;
  left: 0;
  /* Le dessin débordant ne doit pas intercepter les clics des liens voisins. */
  pointer-events: none;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 1.4vw, 1.6rem);
  margin-left: auto;
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.01em;
}

.nav-links a {
  position: relative;
  padding: 0.35rem 0;
  color: var(--muted);
  transition: color 0.3s var(--ease);
}

.nav-links a::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 1px;
  background: var(--blue-bright);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s var(--ease);
}

.nav-links a:hover { color: var(--text); }
.nav-links a:hover::after,
.nav-links a[aria-current="true"]::after { transform: none; }
.nav-links a[aria-current="true"] { color: var(--text); }

.nav-links a { white-space: nowrap; }
.nav-links .lbl-s { display: none; }

@media (max-width: 900px) { .nav-links .opt { display: none; } }

@media (max-width: 760px) {
  .nav-links .lbl-l { display: none; }
  .nav-links .lbl-s { display: inline; }
}

@media (max-width: 560px) { .nav-links .opt2 { display: none; } }

/* Bascule clair / sombre */
.theme-btn {
  display: grid;
  place-items: center;
  width: 2.15rem; height: 2.15rem;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-2);
  cursor: pointer;
  transition: transform 0.4s var(--ease), border-color 0.3s, color 0.3s;
}

.theme-btn:hover { transform: rotate(28deg) scale(1.08); border-color: var(--border-strong); color: var(--text); }
.theme-btn svg { width: 1rem; height: 1rem; }
:root[data-theme="light"] .theme-btn .i-moon { display: none; }
:root:not([data-theme="light"]) .theme-btn .i-sun { display: none; }

/* Jauge de progression de lecture */
.progress {
  position: fixed;
  inset: 0 auto auto 0;
  z-index: 61;
  height: 2px;
  width: 100%;
  transform-origin: left;
  transform: scaleX(0);
  background: var(--grad);
  pointer-events: none;
}

/* -------------------------------------------------------- 6. Boutons */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.45rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease),
    border-color 0.3s var(--ease), color 0.3s var(--ease);
}

.btn-primary {
  background: var(--blue);
  color: #fff;
  box-shadow: 0 14px 40px -16px rgb(47 128 255 / 70%);
}

/* Éclat qui traverse le bouton au survol. */
.btn-primary::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 -60%;
  z-index: -1;
  width: 55%;
  background: linear-gradient(100deg, transparent, rgb(255 255 255 / 42%), transparent);
  transform: skewX(-18deg);
  transition: left 0.75s var(--ease);
}

.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 20px 48px -14px rgb(47 128 255 / 85%); }
.btn-primary:hover::before { left: 115%; }

.btn-ghost {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
}

.btn-ghost:hover { border-color: var(--border-strong); background: var(--surface-2); transform: translateY(-2px); }

.btn .arr { transition: transform 0.4s var(--ease); }
.btn:hover .arr { transform: translateX(4px); }

/* =========================================================== 7. HÉROS */
.hero {
  position: relative;
  display: grid;
  align-content: center;
  min-height: min(100svh, 56rem);
  padding-block: 5rem 2rem;
  overflow: hidden;
}

/* Les deux courbes lumineuses du bas du logo, redessinées et animées. */
.hero-swoosh {
  position: absolute;
  inset: auto 0 -2rem;
  z-index: 0;
  width: 100%;
  height: 42%;
  pointer-events: none;
  opacity: 0;
  animation: fade-in 1.6s var(--ease) 1.5s forwards;
}

/* Les courbes **appartiennent au fond**, et pourtant elles passaient devant le
   texte : relevé à l'écran, un trait traversait le libellé « Voir la chaîne de
   valeur ». La cause n'est pas une opacité mais l'ordre de peinture — dans un
   même contexte d'empilement, un élément **positionné** à `z-index: 0` est
   peint après le contenu statique, quel que soit l'ordre du balisage. Le bouton
   fantôme n'ayant que 3 % de fond, le trait se lisait au travers de ses lettres.

   On remonte donc le contenu plutôt que d'opacifier un bouton : c'est la
   superposition qui était fausse, pas le style du bouton. Posé sur `.hero-inner`
   et non sur `[data-live]` : le défaut existe aussi dans le repli. */
.hero-inner {
  position: relative;
  z-index: 1;
}

@keyframes fade-in { to { opacity: 1; } }

.hero-swoosh path {
  fill: none;
  stroke: url(#swooshGrad);
  stroke-width: 1.2;
  stroke-dasharray: 2400;
  stroke-dashoffset: 2400;
  animation: draw 3.4s var(--ease) forwards;
}

.hero-swoosh path:nth-child(2) { animation-delay: 0.25s; opacity: 0.6; }
.hero-swoosh path:nth-child(3) { animation-delay: 0.5s;  opacity: 0.38; }

@keyframes draw { to { stroke-dashoffset: 0; } }

/* -------- La marque -------- */
/* Un seul dessin pour tout le site — l'ouverture, la barre, le pied. **Quatre
   éléments, deux couleurs, beaucoup d'air** : c'est ce dépouillement qui fait le
   registre, et rien ne doit venir le remplir.

   Il est relevé au point près sur le PNG d'origine (941 × 941), dont voici les
   mesures ramenées au repère `viewBox="0 0 900 368"` :

     AM&I                  ligne de base 185, largeur 587, hauteur de cap 155
     filet bleu            centré, y 221, largeur 169 (29 % de la marque), épaisseur 3
     ALAIN MIELLE          ligne de base 288, largeur 587, cap 32
     INNOVATION & VISION   ligne de base 337, largeur 481, cap 17

   Trois écarts du premier tracé ont été corrigés. La **ligne des trois
   domaines** — « conseil stratégique | intelligence artificielle |
   transformation » — ne fait pas partie de ce lockup : elle le chargeait et lui
   ôtait précisément l'air qui le fait lire comme du haut de gamme. Elle est
   conservée en clair dans le pied de page, hors de la marque. Le **filet fin**
   posé au-dessus d'elle n'existait pas davantage dans l'original (vérifié au
   pixel). Et la **ligne de sens était 15 % trop large**. À l'inverse,
   `ALAIN MIELLE` fait exactement la largeur du monogramme dans l'original —
   c'est la relation la plus caractéristique du lockup, et elle était perdue.

   Ces largeurs sont tenues par `textLength` plutôt que par un interlettrage :
   une valeur d'interlettrage ne donne la bonne largeur qu'avec la police pour
   laquelle elle a été réglée, et la pile est ici une pile système — Futura,
   Century Gothic, Avenir, Helvetica. `textLength` répartit l'écart lui-même : la
   marque garde ses proportions sur une machine qui n'a aucune des trois
   premières. En contrepartie l'interlettrage n'est plus animable, et c'est un
   balayage qui découvre la signature — le geste est de toute façon plus juste
   ici. */
.mk { overflow: visible; }

/* La pile de la marque n'est **pas** celle des jetons, et c'est délibéré. Ce qui
   fait le registre de ce logotype, ce sont ses **fûts d'un cheveu** : macOS ne
   fournit Futura qu'en Medium, et toute graisse fine y était donc ignorée — le
   monogramme sortait deux fois trop gras. Avenir Next, placé en tête, apporte un
   Ultra Light géométrique ; Futura et Century Gothic restent derrière pour les
   machines qui n'ont pas Avenir, où l'on préfère la bonne forme à la bonne
   graisse. Les largeurs, elles, ne dépendent d'aucune de ces polices : elles sont
   verrouillées par `textLength`.
   Les trois graisses sont réglées à l'œil contre le PNG : le monogramme au plus
   fin, la signature et la ligne de sens assez présentes pour se lire à 10 px de
   haut dans la barre. */
.mk text {
  font-family: "Avenir Next", "Futura", "Century Gothic", "Helvetica Neue",
    "Segoe UI", system-ui, sans-serif;
  text-anchor: middle;
  dominant-baseline: auto;
}

/* Corps déduits des hauteurs de cap relevées sur le PNG (0,708 em pour Avenir
   Next). Les copies des masques reprennent les mêmes classes, donc les mêmes
   valeurs : elles ne peuvent pas se désaligner des originales. */
.lg-mono  { font-size: 219px;  font-weight: 100; }
.lg-name  { font-size: 45.2px; font-weight: 400; }
.lg-claim { font-size: 24px;   font-weight: 500; }

/* Deux couleurs, pas trois : le blanc du texte et le bleu de la marque. Le bleu
   ne porte que l'esperluette, le filet et la ligne de sens — c'est-à-dire le
   pivot, le trait et le sens. */
.lg-fill  { fill: var(--text); }
.lg-fill .amp { fill: var(--blue-bright); }
.lg-rule  { fill: var(--blue-bright); }
.lg-name  { fill: var(--text); }
.lg-claim { fill: var(--blue-bright); }

/* Le monogramme est dimensionné pour que la marque ET l'accroche tiennent
   ensemble dans la première fenêtre : c'est le rapport qui compte, pas la
   taille absolue. */
.logo {
  width: min(100%, 26rem);
  margin: 0 auto clamp(1.25rem, 3vh, 2.25rem);
}

/* Fenêtre haute : la marque peut respirer davantage. */
@media (min-height: 880px) {
  .logo { width: min(100%, 31rem); }
}

/* Balayage lumineux qui repasse sur la marque du héros, en boucle lente. C'est
   la seule chose qui bouge au repos : la construction du dessin, elle, est
   devenue l'ouverture de la page. */
.lg-sheen { opacity: 0; }

.lg-sheen--boucle { animation: sheen 6.5s linear 1.2s infinite; }

@keyframes sheen {
  0%   { opacity: 0;    transform: translateX(-55%); }
  8%   { opacity: 0.85; }
  38%  { opacity: 0;    transform: translateX(55%); }
  100% { opacity: 0;    transform: translateX(55%); }
}

/* -------- Accroche du héros -------- */
.hero-copy { text-align: center; }

.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 1.15rem;
  padding: 0.4rem 0.9rem 0.4rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--text-2);
}

.pulse {
  width: 0.45rem; height: 0.45rem;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 0 rgb(111 207 135 / 65%);
  animation: pulse 2.4s ease-out infinite;
}

@keyframes pulse {
  70%, 100% { box-shadow: 0 0 0 0.6rem rgb(111 207 135 / 0%); }
}

.hero-h1 {
  margin: 0 auto 1rem;
  max-width: 38rem;
  font-size: clamp(1.5rem, 2.6vw + 0.4rem, 2.75rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.038em;
  text-wrap: balance;
}

.hero-h1 span { color: var(--muted); }

.grad {
  background: var(--grad);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

.hero-sub {
  margin: 0 auto 1.75rem;
  max-width: 34rem;
  color: var(--text-2);
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; }

/* Indice de défilement */
.scroll-cue {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-top: clamp(1.25rem, 4vh, 2.5rem);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

.scroll-cue i {
  display: block;
  width: 1px; height: 2.75rem;
  background: linear-gradient(var(--border-strong), transparent);
  position: relative;
  overflow: hidden;
}

.scroll-cue i::after {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 40%;
  background: var(--blue-bright);
  animation: cue 2.6s var(--ease) infinite;
}

@keyframes cue {
  0%        { transform: translateY(-110%); opacity: 0; }
  35%, 55%  { opacity: 1; }
  100%      { transform: translateY(260%); opacity: 0; }
}

/* Fenêtre basse, **hors chapitre joué** : l'indice de défilement s'efface plutôt
   que de pousser l'appel à l'action hors de vue — c'est lui qui doit rester
   visible. La règle doit venir après la déclaration de `display` ci-dessus, à
   spécificité égale, sinon elle ne s'applique jamais ; et elle ne vaut que hors
   scène, où l'indice est dans le flux. Pendant le chapitre il est posé en
   absolu au bas de l'étage, ne pousse rien, et c'est justement au premier temps
   — la marque seule — qu'il est le plus utile. */
@media (max-height: 820px) {
  .hero:not([data-live]) .scroll-cue { display: none; }
}

/* -------- Chapitre 1 : le héros joué au défilement -------------------------
   Trois temps sur trois hauteurs de fenêtre : le monogramme se dessine et
   occupe seul l'écran ; il se réduit et cède la place à l'accroche ;
   l'accroche se retire à son tour et passe le relais au bandeau. Le fil naît
   sous le monogramme et descend vers le chapitre suivant.

   Les seuils sont portés par une **animation en pause** dont on déplace la tête
   de lecture avec un délai négatif tiré de `--p`. C'est le seul moyen, sans
   script supplémentaire, d'obtenir à la fois l'interpolation continue et un
   seuil franc : `visibility` bascule à un instant précis, et les deux boutons
   de l'accroche ne sont donc jamais atteignables au tabulateur avant d'être à
   l'écran. Le procédé sert aussi aux chapitres 3 et 7.

   Sans `data-live` — script absent, fenêtre trop petite, mouvement réduit —
   aucune de ces règles ne s'applique : le héros reste la première fenêtre
   d'origine, logo et accroche ensemble. */
.hero[data-live] {
  --p: 0;

  /* --- Le tempo du chapitre, en une seule table ---------------------------
     Même forme qu'aux repères et à la chaîne de valeur. `--rail` est en
     hauteurs de fenêtre, donc la **course collée** vaut `--rail - 1` : c'est
     elle, et non le rail, que la chorégraphie doit remplir. Un rail plus long
     que sa chorégraphie produit toujours le même défaut, et il faut le mesurer
     ainsi — la longueur du rail ne dit rien à elle seule.

     Le rail valait 2,5 écrans pour un contenu qui avait fini de se retirer à
     72 % de la course. Relevé à 800 px de fenêtre : la dernière ligne partait
     à y = 864, l'étage restait collé jusqu'à y = 1200, et le premier texte du
     chapitre suivant n'affleurait qu'à y = 1599 — **735 px de suite, presque
     un écran, sans rien au cadre que la barre, le fil et le présentateur**, à
     quelques pixels du haut du site. Le défaut était géométrique : aucun
     réglage d'opacité ne pouvait l'atteindre.

     Le rail est donc ramené à ce que son contenu occupe réellement. La course
     vaut 0,80 écran : 0,24 pour tenir la première fenêtre intacte — elle est
     complète au repos, on ne la construit pas —, puis 0,56, la demi-hauteur de
     lecture du site, pour la retirer. Le retrait se termine **avec** la course,
     à 92 % ; les 8 % restants ne servent qu'à refermer `visibility`. */
  --rail: 1.8;

  /* Le premier écran est **complet au repos**, et c'est le fond du sujet. La
     version précédente montait la marque seule, puis faisait venir l'accroche à
     un tiers du chapitre : à défilement nul, le visiteur n'avait donc sous les
     yeux qu'un monogramme de deux cents pixels au milieu d'une fenêtre de huit
     cents. Corriger l'alignement du rail ne suffisait pas — l'étage était bien
     collé en haut, mais son contenu était encore à venir.

     On ne construit pas la première fenêtre au défilement : on la donne, et
     c'est le défilement qui l'emporte. Il n'y a donc plus de temps
     d'assemblage, seulement un temps de retrait. */
  --out: clamp(0, calc((var(--p) - 0.30) / 0.62), 1);

  /* --- Les deux bouts du monde -------------------------------------------
     Le premier chapitre : il part du cran zéro — le marine de la marque, aucune
     teinte tournée — et bouge à peine. C'est délibéré, et c'est la contrainte la
     plus forte du site : la première fenêtre doit être *celle-là*, pas un monde
     à découvrir. Le décor n'y fait qu'une chose, tenir le cadre pendant le
     relais, là où il n'y avait que la toile globale et deux courbes.

     La trame est la plus large du site (3 rem) : c'est l'air autour de la
     marque, et c'est aussi la valeur à laquelle le contact reviendra. */
  --env-t0: var(--mo-0);   --env-g0: var(--mg-0);
  --env-t1: var(--mo-1);   --env-g1: var(--mg-1);

  display: block;

  /* Indispensable, et pas seulement pour la forme. La règle de base `.hero`
     centre son contenu — elle datait de l'époque où la section était une
     grille d'une seule fenêtre de haut. Or l'alignement de boîte s'applique
     désormais aussi à la disposition en bloc : hérité ici, `align-content:
     center` centrait **l'étage collant dans son rail**. Avec un rail de 180 svh
     et un étage de 100 svh, l'étage démarrerait donc à (180 − 100) / 2 = 40 svh
     du haut, il ne se collerait qu'après deux cinquièmes d'écran de défilement,
     et le visiteur atterrirait sur un écran vide sous la barre. C'est le
     premier écran du site : il doit être plein au premier pixel. Le décalage à
     mesurer est l'écart entre le haut de l'étage et le haut de la section, et
     il doit être nul. */
  align-content: start;

  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;
  /* `clip` et non `hidden` : `hidden` ferait de la section un conteneur de
     défilement, et l'étage collé ne collerait plus à rien. */
  overflow: clip;
}

.hero[data-live] .hero-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* --- Réglages de l'environnement du héros ----------------------------------
   Le plus retenu du site, pour la raison dite plus haut : la marque d'abord.
   Lumière basse, trame très en retrait, pas de sol — les deux courbes de
   lumière occupent déjà le bas du cadre, et un plancher en perspective les
   croiserait. La couche `.env-ciel` reste opaque : c'est elle qui empêche la
   toile globale de repasser devant au moment du relais. */
.hero[data-live] .env {
  --env-lum: 0.72;
  --env-champ-o: 0.3;
}

.hero[data-live] .hero-inner {
  display: grid;
  align-content: center;
  height: 100%;
}

/* Le monogramme est en place dès le repos, au-dessus de l'accroche, et il se
   renfonce avec elle quand le chapitre se retire. Plus de réduction à jouer :
   la place sous lui est déjà libre. */
.hero[data-live] .logo {
  transform-origin: 50% 30%;
  transform:
    perspective(70rem)
    translateZ(calc(var(--out) * 12rem))
    scale(calc(1 - 0.05 * var(--out)));
  opacity: calc(1 - var(--out) * 1.25);
}

.hero[data-live] .hero-copy {
  animation:
    hero-copy     1s linear paused both,
    hero-copy-tab 1s linear paused both;
  animation-delay: calc(var(--p) * -1s), calc(var(--qo, 0) * -1s);
}

/* Deux images-clés portent la même valeur de `visibility` de part et d'autre
   d'un intervalle pour que l'accroche y soit réellement hors d'atteinte :
   dès qu'une seule des deux bornes vaut `visible`, tout l'intervalle est
   visible. C'est la règle d'interpolation de la propriété, et c'est elle qui
   décide ici de l'ordre de tabulation. */
/* L'accroche est **là au premier pixel** et ne fait que se retirer : elle se
   renfonce en profondeur et se dissout. C'est l'inverse de la version
   précédente, qui la faisait arriver au tiers du chapitre et laissait donc la
   première fenêtre presque vide au repos.

   L'animation en pause reste le procédé, parce qu'elle seule donne le seuil
   franc de `visibility` : les deux boutons de l'accroche ne doivent pas être
   atteignables au tabulateur une fois hors champ. Deux images-clés portent la
   même valeur de part et d'autre de l'intervalle — dès qu'une seule des deux
   bornes vaut `visible`, tout l'intervalle l'est. */
/* **L'accroche ne se dissout plus : elle sort en montant.** La version
   précédente la faisait disparaître à 92 % de `--p`, c'est-à-dire au bout de la
   *course collée* — mais l'étage, lui, continue de monter sur une fenêtre
   entière après ce point, puisque le rail fait 1,8 fenêtre et l'étage 1. Le
   texte était donc effacé alors que l'étage occupait encore tout le cadre, et
   les repères n'entraient qu'ensuite : à peu près 400 px de défilement sur un
   cadre sans un mot. C'était le dernier reste du « premier écran vide ».

   Mesuré à 1280 × 800 : accroche éteinte dès y 589, repères au ras du bas du
   cadre seulement à partir de y 640, et occupant moins d'un tiers avant y 900.

   Maintenant l'accroche tient sa clarté jusqu'au bout et quitte le cadre
   **portée par l'étage** : elle monte hors champ pendant que les repères
   montent dedans. C'est le relais que le fil raconte déjà, joué par le texte.
   Il ne reste qu'un léger enfoncement — de la profondeur, pas un fondu. */
@keyframes hero-copy {
  0%,  30%  { opacity: 1; transform: perspective(60rem) translateZ(0)      scale(1); }
  100%      { opacity: 1; transform: perspective(60rem) translateZ(3.5rem) scale(1.015); }
}

/* Le seuil de tabulation vit sur **l'autre horloge**. `--p` sature à la fin de
   la course collée, donc il ne sait pas dire quand l'accroche est réellement
   sortie du cadre ; `--qo` continue de courir jusqu'à ce que le bloc entier
   soit passé, et lui le sait. Le seuil est à 84 % parce que l'accroche a quitté
   le haut du cadre à 72 % — la marge est franche, et deux images-clés portent
   la même valeur de part et d'autre pour que l'intervalle soit net.

   Deux animations en pause sur le même élément, chacune avec son propre délai :
   la seconde l'emporte pour `visibility`, la première garde l'opacité et la
   transformation. Sans script, `var(--qo, 0)` vaut 0 et l'accroche reste
   atteignable — c'est le bon repli. */
@keyframes hero-copy-tab {
  0%,  84%  { visibility: visible; }
  85%, 100% { visibility: hidden; }
}

/* Les traits de lumière appartiennent au fond : ils s'enfoncent plus lentement
   que la marque, et c'est ce décalage qui creuse la première fenêtre.

   L'opacité tirée de `--p` qui figurait ici **n'a jamais rien fait**, et c'est
   un piège de la même famille que ceux du haut de ce fichier : la règle de base
   fait entrer ces courbes par `animation: fade-in … forwards`, et une animation
   en état de remplissage l'emporte sur une déclaration normale. Relevé à
   `--p` = 0,92, l'opacité effective valait 1 là où la déclaration annonçait
   0,54. La déclaration est donc retirée plutôt que réparée à coups
   d'`!important` — parce que **c'est la valeur 1 que l'on veut**.

   Un étage collé quitte toujours le cadre sur sa propre hauteur — un écran,
   quel que soit le rail — et pendant ce trajet le chapitre suivant n'a pas
   encore de texte à l'image. Ces courbes sont posées au **bas** de l'étage :
   ce sont donc elles qui occupent le cadre pendant le relais, et de la lumière
   qui s'en va se lit tout autrement qu'un rectangle sombre. Elles se retirent
   avec l'étage, jamais à travers le cadre — c'est la seule translation admise
   ici, celle du décor, jamais celle du contenu. La descente est ramenée de
   22 vh à 12 vh, le rail étant plus court. */
.hero[data-live] .hero-swoosh { transform: translateY(calc(var(--p) * 12vh)); }

.hero[data-live] .scroll-cue {
  position: absolute;
  inset: auto 0 1.25rem;
  z-index: 3;
  margin: 0;
  /* L'invitation à défiler s'efface dès qu'on a commencé. Elle suivait `--a`,
     le temps d'assemblage, qui n'existe plus. Le facteur est ramené de 12 à 7
     avec le rail : c'est une distance de défilement qu'il exprime, pas une part
     de `--p` — à 12 sur une course de 640 px elle disparaissait en cinquante
     pixels, soit un cran de molette. */
  opacity: clamp(0, calc(1 - 7 * var(--p)), 1);
  pointer-events: none;
}

/* Le fil ne part pas du haut de l'écran : il naît sous le monogramme, une fois
   la marque encrée, et son extrémité haute se dissipe au lieu d'être coupée —
   c'est le seul segment du site qui a le droit à un dégradé, puisqu'il n'a
   aucun voisin au-dessus de lui. */
.hero .fil { top: 52%; }

.hero .fil > b { background: linear-gradient(180deg, transparent, var(--fil-c) 22%); }

.hero .fil::before { mask-image: linear-gradient(180deg, transparent, #000 26%); }

/* Le fil **naît pendant que l'accroche se retire** : sa venue occupe la même
   fenêtre que le retrait (30 → 92 %) au lieu du dixième initial. C'est le second
   temps du chapitre, dans sa forme la plus économe — pas un plan de plus à
   composer, l'objet qui relie tout le site qui s'allume au moment où le cadre
   se libère. */
.hero[data-live] .fil { opacity: clamp(0, calc((var(--p) - 0.30) / 0.62), 1); }

/* L'amorce : un trait oblique qui remonte du nœud vers le monogramme. Il ne va
   pas jusqu'à la marque — il en indique la direction, et s'éteint en chemin.
   C'est assez pour lire « le fil vient de là », et cela ne dépend d'aucune
   mesure de fenêtre. */
.hero .fil-amorce {
  position: absolute;
  top: 0;
  left: var(--fil-x);
  width: 8rem;
  height: 1px;
  background: linear-gradient(90deg, var(--fil-c), transparent);
  transform-origin: 0 50%;
  transform: rotate(-33deg);
}

/* Sous 48 rem, la gouttière n'est plus qu'un liseré au bord de l'écran et
   l'oblique passerait sous l'accroche. Le fil reste, l'amorce s'efface. */
@media (max-width: 767px) {
  .hero .fil-amorce { display: none; }
}

/* ================================================ 8. Bandeau de références */
/* --- Couture 1 : le bandeau est la jonction elle-même -----------------------
   Le bandeau n'est pas un chapitre, c'est **la couture entre le héros et les
   repères** : cinquante-cinq pixels en flux normal, jamais seuls dans une
   fenêtre. Il n'a donc pas d'environnement, et il n'en a pas besoin — mais il
   doit être **opaque**, sinon il ouvre au milieu de la jonction une fente de
   trente pour cent par où la toile globale repasse, entre deux décors qui, eux,
   couvrent tout. C'était la marche de la couture 1.

   Les deux filets restent : à cet endroit précis, un liseré haut et bas se lit
   comme la piqûre d'une couture, ce qui est exactement ce que le bandeau est. */
.strip {
  border-block: 1px solid var(--border);
  background: var(--bg-2);
  overflow: hidden;
}

.strip-track {
  display: flex;
  gap: 3.5rem;
  width: max-content;
  padding-block: 1.05rem;
  animation: marquee 34s linear infinite;
}

.strip:hover .strip-track { animation-play-state: paused; }

@keyframes marquee { to { transform: translateX(-50%); } }

.strip-item {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.strip-item::before {
  content: "";
  width: 0.3rem; height: 0.3rem;
  border-radius: 50%;
  background: var(--blue);
  flex: none;
}

/* ==================================================== 9. Chiffres géants */
.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: clamp(1.5rem, 3vw, 3rem);
  margin-top: 3rem;
  /* La marge basse que le navigateur donne d'office à un `dl` s'échappait :
     ni la colonne ni la section des repères n'ont de rembourrage bas pour la
     retenir, et elle se reportait donc entre les deux sections. Le fil, qui
     couvre exactement la hauteur de la sienne, s'arrêtait dix-sept pixels
     avant la chaîne de valeur — un trou dans le seul objet qui ne doit jamais
     s'interrompre. L'écart entre les deux chapitres est l'affaire du
     rembourrage de la section suivante, pas d'une marge héritée. */
  margin-bottom: 0;
}

.fig dt {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.5rem;
}

.fig dd {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 0.2rem;
}

.fig-num {
  font-size: clamp(2.6rem, 5.4vw, 4.5rem);
  font-weight: 600;
  line-height: 0.9;
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  background: var(--grad);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

.fig-unit {
  font-size: clamp(0.9rem, 1.5vw, 1.2rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--muted);
}

.fig p {
  margin: 0.65rem 0 0;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--muted);
}

/* -------- Chapitre 2 : un repère par plan ----------------------------------
   Première version à jeter : les quatre repères occupaient chacun sa hauteur
   d'écran et montaient à travers le champ. Deux défauts, et c'étaient les deux
   reproches du commanditaire réunis. D'abord le vide — un chiffre et trois
   lignes ne remplissent pas un écran, et entre deux repères il ne restait
   qu'une page déserte. Ensuite la translation : dès qu'un bloc monte à travers
   la fenêtre, l'œil comprend « je défile », et l'illusion tombe.

   Ici le cadre ne bouge plus : l'étage est collé, et c'est **la composition qui
   se recompose**. Un temps = une image pleine. Les quatre repères sont
   superposés dans la même case, et l'on passe de l'un à l'autre par un
   **fondu enchaîné doublé d'un travelling avant** : le repère qui s'en va
   grandit et se dissout comme si la caméra le traversait, celui qui arrive
   remonte de la profondeur. Aucune translation verticale — c'est la règle du
   chapitre.

   Le glyphe est énorme et sa taille dépend du **nombre de chiffres** (`--dg`) :
   un « 3 » seul doit occuper autant de champ qu'un « 32 », sinon le plan se
   vide, ce qui était précisément le défaut relevé sur la capture. */
.rep[data-live] {
  --p: 0;

  /* --- Le tempo du chapitre, en une seule table ---------------------------
     Toutes les bornes du chapitre sont ici, nommées, et les images-clés plus
     bas s'y réfèrent en pourcentage de `--p`. Le rail est long : quatre
     repères à lire, et le commanditaire trouvait le chapitre expédié.

     `--rail` est en hauteurs de fenêtre, donc `1 / --rail` est la part de `--p`
     qui vaut un écran de défilement : à 4,6 écrans, un écran ≈ 21,7 % de `--p`.
     Le **plan posé** de chaque repère dure 12 % de `--p`, soit 0,55 écran — la
     demi-hauteur de lecture demandée, mesurée et non estimée. L'arrivée en dure
     10 à 12 %, et elle est volontairement plus longue que la sortie : c'est ce
     qui donne le flottement plutôt que la cadence. */
  --rail: 4.6;
  /* L'accroche tient jusqu'à 10 % et s'efface à 18 %, juste quand le premier
     plan commence à monter. Réglée plus tôt, elle laissait huit pas de balayage
     — plus d'un tiers d'écran — sans une ligne de texte au cadre. */
  --entry: clamp(0, calc((var(--p) - 0.06) / 0.08), 1);

  /* `--t` : le rang du repère au cadre, 0 → 3. Il ne sert plus à la présence
     des plans — ce sont les images-clés qui la portent — mais au **décor**, qui
     doit savoir de quel monde il vient et vers lequel il va. Les bornes suivent
     les fenêtres de visibilité : premier plan posé vers 26 %, puis un repère
     tous les 24 %. */
  --t0: 0.255;
  --dt: 0.235;
  --t: clamp(0, calc((var(--p) - var(--t0)) / var(--dt)), 3);

  /* --- Les deux bouts du monde -------------------------------------------
     Le chapitre ne choisit pas son décor, il choisit **le chemin** entre les
     deux crans que ses voisins lui imposent : il part de la couture 1 et arrive
     à la couture 2. Le monde avance ici **par crans** — un par repère — et non
     linéairement : c'est ce qui fait sentir qu'on change de repère et pas
     seulement qu'on défile. Aux deux bouts, `--t` valant 0 puis 3, `--env-w`
     vaut bien 0 puis 1, et le raccord est exact. */
  --env-t0: var(--mo-1);   --env-g0: var(--mg-1);
  --env-t1: var(--mo-2);   --env-g1: var(--mg-2);
  --env-w: calc(var(--t) / 3);

  /* Le fil vient habiter le champ, puis retourne dans la gouttière avant la
     jonction : c'est `--env-relais` qui le porte, nul aux deux coutures. */
  --fil-champ: var(--env-relais);
  --fil-vers: calc(38% - 50% + min(50%, var(--maxw) / 2) - var(--gut) / 2);
  --fil-gras: 1px;
  --fil-lueur: 0.6;

  /* Les deux tons du texte du plan, nommés pour la même raison que les
     planchers `--sol-*` : ils dépendent du **décor du chapitre**, plus clair
     que la toile du site, et non des paragraphes de la page. Un ton de
     paragraphe y devenait un gris moyen — le reproche relevé sur la capture.
     Ils sont donc relevés dans les deux registres, et la hiérarchie est portée
     par le contraste entre les trois plans typographiques : libellé au ton
     médian, glyphe en dégradé, phrase au ton le plus franc. */
  --rep-txt: var(--text);
  --rep-dt: var(--text-2);

  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;
}

.rep[data-live] .rep-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* --- L'environnement d'un chapitre, mécanisme partagé -----------------------
   Le reproche d'origine était : « c'est toujours le même arrière-plan, ça fait
   encore effet scroll down ». Il ne visait pas l'animation mais le décor —
   partout la même toile, donc une seule lecture possible.

   Un chapitre traité porte donc `.env`, un environnement **plein cadre et
   opaque** qui couvre la toile globale et se métamorphose en avançant. Quatre
   couches, toutes procédurales : aucune image n'est chargée ni à produire.

     .env-ciel   la matière de fond, opaque — c'est elle qui donne au chapitre
                 son ciel plutôt que celui de la page
     .env-champ  une trame qui se réorganise : elle pivote et s'enfonce
     .env-sol    un sol en perspective, qui s'incline avec le travelling
     .env-halo   la lumière, qui se déplace avec la lecture

   Le mécanisme est écrit **une seule fois**, et il est **gardé par
   `[data-live]`** : hors chapitre joué — repli, mouvement réduit, fenêtre trop
   petite — le décor n'existe pas du tout. Sans cette garde, ses couches
   restaient dans le rendu et ne se taisaient que parce qu'une variable non
   définie rendait leur dégradé invalide : par chance, donc.

   Chaque chapitre le règle par variables :

     --env-t      le cran courant (0, 1, 2 …), qui fait avancer le monde
     --env-tour   degrés de teinte par cran — la métamorphose
     --env-a      la première couleur d'accent
     --env-b      la seconde
     --env-grain  le pas de la trame
     --env-sol-o  l'opacité du sol (0 pour un chapitre qui n'en veut pas)

   Tout est en transformations, opacités et un filtre de teinte : un décor plein
   cadre ne coûte donc aucune mise en page par image. C'est aussi lui qui porte
   le cadre quand un plan est parti et que le suivant n'est pas arrivé.

   --- La table du monde, et la doctrine des coutures ------------------------
   Le reproche suivant fut : « un chapitre finit, un autre commence, et le décor
   change d'un coup ». Il était juste, et il ne se corrigeait pas chapitre par
   chapitre : une **jonction appartient à deux chapitres à la fois**, et chacun
   était réglé pour lui seul. Relevé à la couture repères → chaîne de valeur,
   avant reprise : 54° de teinte et 16 px de trame franchis en un point, sur la
   ligne où les deux étages se touchent.

   Le décor n'est donc plus réglé pour un chapitre, il est réglé **par ses deux
   bouts** — et les deux bouts d'une même jonction sont *la même valeur*. La
   continuité à la couture est acquise **par construction**, comme celle du fil
   conducteur (section 19) : rien à synchroniser, rien à régler à l'œil.

   Deux scalaires suffisent, et tout le reste en descend :

     --mo-N   le **cran du monde** à la couture N. Il porte la teinte
              (× `--env-tour`), l'angle du dégradé conique, la rotation et
              l'enfoncement du champ de points, l'inclinaison du sol.
     --mg-N   le **pas de la trame** à la couture N.

   Le voyage chromatique est une **boucle fermée** : il part du marine de la
   marque, vire au violet au fil des repères, revient au bleu avec le circuit de
   la chaîne de valeur, se réchauffe au profil — on y parle d'un homme —,
   refroidit jusqu'au cyan dans la pellicule des prestations, et **retrouve le
   marine au contact**. L'excursion utile va de −1 à +3,6 crans, soit −18° à
   +65° : sur le bleu de la marque (216°) cela donne 198° d'un côté et 281° de
   l'autre, c'est-à-dire le cyan et le violet du dégradé de la charte, et rien
   au-delà. Le récit se referme sur la teinte qui l'a ouvert.
   -------------------------------------------------------------------------- */
:root {
  --env-tour: 18deg;   /* degrés de teinte par cran de monde */

  --mo-0: 0;     --mg-0: 3rem;      /* haut de page — le marine, l'air du héros */
  --mo-1: 0.5;   --mg-1: 2.6rem;    /* couture 1 · héros → repères */
  --mo-2: 3.6;   --mg-2: 1.4rem;    /* couture 2 · repères → chaîne de valeur */
  --mo-3: 1.3;   --mg-3: 2.1rem;    /* couture 3 · chaîne de valeur → profil */
  --mo-4: 3;     --mg-4: 2.9rem;    /* couture 4 · profil → domaines */
  --mo-5: 0.8;   --mg-5: 2.2rem;    /* couture 5 · domaines → prestations */
  --mo-6: -1;    --mg-6: 1.7rem;    /* couture 6 · prestations → témoignage */
  /* Les coutures 7 et 8 portent **la même valeur**, et c'est mesuré, non choisi :
     le contact fait 889 px pour une fenêtre de 800, et le pied qui le suit n'en
     fait que 318 — la section ne sort donc jamais entièrement par le haut, aucun
     curseur tiré d'elle n'atteint 1, et un monde qui avancerait pendant le
     contact n'arriverait jamais au bout de son chemin. Relevé avant correction :
     le décor s'arrêtait à 2,9° au lieu de 0. Le contact garde donc un monde
     **constant**, celui du marine, et la boucle se ferme franchement. C'est
     aussi ce que le chapitre raconte : la résolution n'explore plus rien. */
  --mo-7: 0;     --mg-7: 3rem;      /* couture 7 · témoignage → contact */
  --mo-8: 0;     --mg-8: 3rem;      /* couture 8 · contact → pied — la boucle se ferme */
}

/* Les curseurs de couture sont posés sur la **section** et non sur `.env` : le
   fil conducteur est frère de l'étage dans le flux, et il doit les lire lui
   aussi pour rentrer dans la gouttière avant la jonction. */
[data-live] {
  /* L'avancement du monde **dans** le chapitre, 0 → 1. C'est le seul réglage
     que chaque chapitre ait à donner, et il peut être par crans (les repères,
     un cran par repère) ou continu (la chaîne de valeur). */
  --env-w: var(--p, 0);

  /* Le cran du monde et le pas de trame, interpolés entre les deux bouts. */
  --env-t: calc(var(--env-t0, 0) + (var(--env-t1, 0) - var(--env-t0, 0)) * var(--env-w));
  --env-grain: calc(var(--env-g0, 2.4rem) + (var(--env-g1, 2.4rem) - var(--env-g0, 2.4rem)) * var(--env-w));

  /* Le relais. `--env-rA` monte à l'entrée du chapitre, `--env-rB` descend à sa
     sortie ; leur composition donne les deux curseurs dont tout le reste se
     sert, **nuls à la jonction par construction** :

       --env-relais   1 au cœur du chapitre, 0 aux deux coutures
       --env-couture  l'inverse — 1 aux deux coutures, 0 au cœur

     Ils sont écrits tous les deux ici et non l'un par complément de l'autre :
     `calc(1 - var(--x))` où `--x` porte un `calc()` est la déclaration invalide
     décrite plus haut, celle qui fait repartir une opacité à 1.

     Le relais dure une **demi-fenêtre** à chaque bout : la moitié de l'écran
     que les deux chapitres se partagent au passage. En part de `--p` cela vaut
     0,5 / `--rail`, et c'est écrit en **multipliant** par le rail — jamais en
     divisant par une variable qui porte un calcul. */
  --env-rA: clamp(0, calc(var(--p, 0) * var(--rail, 2.5) * 2), 1);
  --env-rB: clamp(0, calc((var(--p, 0) - 1) * var(--rail, 2.5) * 2 + 1), 1);
  --env-relais: calc(var(--env-rA) * (1 - var(--env-rB)));
  --env-couture: calc(var(--env-rB) + (1 - var(--env-rA)));

  /* Les deux accents sont ceux de la charte, et ils sont les **mêmes partout** :
     c'est le cran du monde qui les fait tourner. Deux accents propres à chaque
     chapitre auraient rouvert la faute qu'on vient de fermer — un changement
     d'accent à la jonction se lit comme un changement de fond. Ce qui reste
     propre au chapitre, c'est leur force : `--env-lum`. */
  --env-a: color-mix(in srgb, var(--blue) 32%, transparent);
  --env-b: color-mix(in srgb, var(--violet, var(--blue)) 24%, transparent);

  /* --- Le raccord : la règle générale des coutures ------------------------
     Deux scalaires chaînés suffisent pour la teinte et la trame, mais un
     chapitre règle aussi la **force** de ses couches, et là il n'y a pas de
     table à tenir : on impose que toute grandeur laissée au choix du chapitre
     **revienne à une valeur commune avant la jonction**. C'est `--env-relais`
     qui l'y ramène, nul aux deux bouts par construction.

     Un chapitre est donc libre de tout en son cœur — la pellicule descend à un
     tiers de lumière, le profil monte au-dessus de un — et aucune de ces
     libertés ne peut produire une marche à la couture. C'est ce qui a permis de
     ne pas chaîner ces grandeurs une à une, et de garder la table à deux
     colonnes. */
  --env-lum-r: 0.8;      /* la lumière au raccord */
  --env-champ-r: 0.4;    /* la force du champ au raccord */
  --env-sol-r: 0;        /* le sol : aucun chapitre ne le partage, il s'efface */
}

[data-live] .env {
  /* Les trois grandeurs de raccord sont calculées **ici** et non sur la section,
     et ce n'est pas indifférent : chaque chapitre pose ses réglages sur son
     `.env`, or une propriété personnalisée n'est lisible que vers le bas. Posé
     sur la section, le calcul ne voyait donc jamais les valeurs du chapitre et
     retombait sur ses défauts — le sol des repères avait disparu, la pellicule
     n'était plus sombre, et cela s'est vu dès la première mesure de la couture 8.
     Le calcul descend au niveau où les réglages sont écrits. */
  --env-lum-vu:   calc(var(--env-lum-r)   + (var(--env-lum, 1)       - var(--env-lum-r))   * var(--env-relais));
  --env-champ-vu: calc(var(--env-champ-r) + (var(--env-champ-o, 0.5) - var(--env-champ-r)) * var(--env-relais));
  --env-sol-vu:   calc(var(--env-sol-r)   + (var(--env-sol-o, 0)     - var(--env-sol-r))   * var(--env-relais));

  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Plein régime, du premier au dernier pixel du chapitre. Le plancher de 0,60
     qui figurait ici était **la cause du défaut de couture** : aux deux bouts de
     chaque chapitre le décor s'effaçait de quatre dixièmes, la toile globale
     repassait devant les deux moitiés du cadre, et le monde propre du chapitre
     entrant surgissait ensuite d'un coup. La métamorphose ne se joue plus sur
     l'opacité — elle se joue sur la teinte, la trame et la lumière, qui, elles,
     se raccordent. Le réglage reste nommé : un chapitre peut encore se retenir. */
  opacity: var(--env-vu, 1);
  filter: hue-rotate(calc(var(--env-t) * var(--env-tour)));
}

[data-live] .env-ciel {
  position: absolute;
  inset: -25%;
  /* La matière opaque, et elle seule : l'angle du conique suit le cran du
     monde, donc il traverse la couture sans marche. */
  background: conic-gradient(from calc(var(--env-t) * 22deg) at 50% 50%,
    var(--bg) 0deg, var(--bg-2) 90deg, var(--bg) 180deg, var(--bg-2) 270deg, var(--bg) 360deg);
  transform: rotate(calc(var(--env-t) * 2.4deg)) scale(calc(1.05 + var(--env-relais) * 0.05));
}

/* Les deux accents ont leur propre couche, et c'est leur opacité qui donne au
   chapitre sa **lumière**. C'est le seul réglage dont la pellicule avait besoin
   pour être une scène sombre sans cesser d'être le même décor — plutôt qu'un
   décor de plus à écrire. */
[data-live] .env-ciel::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 44% at 28% 30%, var(--env-a), transparent 70%),
    radial-gradient(48% 40% at 76% 68%, var(--env-b), transparent 72%);
  opacity: var(--env-lum-vu);
}

[data-live] .env-champ {
  position: absolute;
  inset: -40%;
  background-image: radial-gradient(circle, var(--border-strong) 1px, transparent 1.6px);
  background-size: var(--env-grain) var(--env-grain);
  transform:
    perspective(80rem)
    rotateX(calc(18deg + var(--env-t) * 4deg))
    rotate(calc(var(--env-t) * -6deg))
    translateZ(calc(var(--env-t) * -6rem))
    scale(calc(1 + var(--env-relais) * 0.04));
  opacity: var(--env-champ-vu);
}

[data-live] .env-sol {
  position: absolute;
  inset: -60% -30% -30%;
  background:
    repeating-linear-gradient(90deg, transparent 0 5.5rem, var(--border) 5.5rem 5.6rem),
    repeating-linear-gradient(0deg,  transparent 0 5.5rem, var(--border) 5.5rem 5.6rem);
  /* L'inclinaison suivait `--p` : elle sautait donc de dix degrés à chaque
     jonction, `--p` retombant de 1 à 0. Elle suit maintenant le cran du monde,
     qui est continu. */
  transform:
    perspective(70rem)
    rotateX(calc(62deg + var(--env-t) * 3deg))
    translateY(calc(var(--env-t) * -4.5rem))
    scale(1.25);
  transform-origin: 50% 100%;
  /* Les bords du sol se dissipent, sinon la grille finit par un trait net. Le
     masque est ici et non sur `.env` : le ciel doit couvrir tout le cadre. */
  -webkit-mask-image: radial-gradient(120% 100% at 50% 62%, #000 26%, transparent 76%);
          mask-image: radial-gradient(120% 100% at 50% 62%, #000 26%, transparent 76%);
  opacity: var(--env-sol-vu);
}

/* En registre clair la grille du sol a besoin d'un cran de plus pour exister.
   Le supplément suit le relais lui aussi, sinon il rouvrirait à la couture la
   marche que le raccord vient de fermer. */
:root[data-theme="light"] [data-live] .env-sol {
  opacity: calc(var(--env-sol-vu) + 0.15 * var(--env-relais));
}

/* Le relais de lumière — c'est lui qui fait de chaque passage une avancée et
   non une fin suivie d'un début.

   À la jonction, la lumière du chapitre est **ramassée sur le fil**, au point
   même où les deux étages se touchent ; elle s'ouvre ensuite dans le chapitre,
   puis s'y referme avant la couture suivante. Le chapitre sortant rend donc sa
   lumière au fil, et le chapitre entrant la reprend au même endroit : quelque
   chose du sortant **devient** quelque chose de l'entrant, et c'est le même
   objet aux huit coutures — le fil, qui ne quitte jamais l'image.

   L'origine du geste suit `--env-w` : au premier pixel du chapitre elle est en
   haut du cadre — la couture d'où l'on vient —, au dernier elle est en bas,
   celle où l'on va. Entre les deux l'échelle vaut 1 et l'origine ne compte
   plus. */
[data-live] .env-halo {
  position: absolute;
  inset: 0;
  background: radial-gradient(52rem 40rem at 34% 46%, var(--env-a), transparent 72%);
  transform-origin: var(--fil-x) calc(var(--env-w) * 100%);
  transform:
    translate3d(calc(var(--env-t) * 9%), calc(var(--env-t) * -2%), 0)
    scale(calc(0.16 + 0.84 * var(--env-relais)));
  opacity: calc((0.34 + 0.66 * var(--env-relais)) * var(--env-lum-vu));
}

/* --- Réglages de l'environnement des repères -------------------------------
   Le chapitre le plus long du site, et celui qui a le plus à montrer : il garde
   les quatre couches et la pleine lumière. Le sol en perspective est ici chez
   lui — c'est le seul chapitre où le monde avance par crans, et un plancher qui
   s'enfonce d'un cran à chaque repère dit cette avancée mieux qu'un fondu. */
.rep[data-live] .env {
  --env-lum: 1;
  --env-champ-o: 0.55;
  /* À poser explicitement : le plancher de la couche vaut désormais **zéro** au
     raccord, puisque aucun chapitre voisin ne partage le sol. Un chapitre qui en
     veut un doit donc le demander — l'oubli s'était vu tout de suite, le sol
     avait purement disparu des repères. */
  --env-sol-o: 0.75;
}

.rep[data-live] .rep-inner {
  position: relative;
  z-index: 1;
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  padding-block: 4.6rem clamp(1.5rem, 5vh, 3.5rem);
}

.rep[data-live] .rep-kicker { align-self: start; margin: 0; }

/* L'accroche et les repères partagent la case : l'une se dissout sur place
   pendant que l'autre monte de la profondeur. Pas de sortie par le haut. */
.rep[data-live] .rep-intro,
.rep[data-live] .figures { grid-row: 2; grid-column: 1; }

.rep[data-live] .rep-intro {
  max-width: 46rem;
  pointer-events: none;
  transform: scale(calc(1 - 0.12 * var(--entry)));
  transform-origin: 0 50%;
  opacity: calc(1 - var(--entry));
  filter: blur(calc(var(--entry) * 6px));
}

.rep[data-live] .figures {
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  margin: 0;
  transform: none;
  /* La pile entière n'entre qu'avec le chapitre. Sans cela, `--t` est borné à
     zéro tant qu'on n'a pas atteint `--t0`, donc le premier plan était « au
     cadre » dès le tout début et se superposait à l'accroche : on lisait le
     titre en travers du chiffre géant. C'est le même garde-fou que sur les
     domaines. */
  opacity: var(--entry);
  /* La profondeur est réelle : les plans s'éloignent en `translateZ`, ils ne
     sont pas simplement mis à l'échelle. */
  perspective: 90rem;
  perspective-origin: 38% 50%;
}

.rep[data-live] .fig {
  grid-area: 1 / 1;
  position: relative;
  display: grid;
  /* Colonnes égales, et non 1,3 / 0,7. Un chiffre seul n'occupait pas les
     treize dixièmes qu'on lui réservait : il restait une bande morte au milieu
     du plan, et la phrase se trouvait rejetée au bord droit sur une mesure de
     trente signes — hachée. Le glyphe garde sa taille, qui ne dépend que de
     `--dg` et de la fenêtre : c'est la place non utilisée qui passe au texte. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas: "t d" "n d";
  column-gap: clamp(1.25rem, 3vw, 3rem);
  align-content: center;
  min-height: 0;

  /* La tête de lecture du plan est le curseur lui-même : une animation en pause
     dont on déplace la lecture par un délai négatif. C'est le procédé qui porte
     déjà l'accroche du héros et le commentaire de la chaîne de valeur, et c'est
     le seul qui tienne ici.

     Il remplace une arithmétique en `clamp()` qui calculait la présence depuis
     l'écart au temps courant. Cette arithmétique était **instable** : selon le
     moment, la substitution repartait invalide et l'opacité reprenait sa valeur
     initiale — 1. Deux plans se retrouvaient alors à pleine clarté en même
     temps, et l'on lisait « 82 ans » avec deux fois le mot « ans », un
     paragraphe net sur un paragraphe flou. Le balayage de `--p` par pas de 0,01
     le montrait sans ambiguïté : somme des opacités lisibles à 2,00.

     Les images-clés, elles, ne se substituent pas : ce sont des seuils, et les
     fenêtres de visibilité des quatre plans **ne se recouvrent pas**. Là où deux
     plans se succèdent, tous deux passent par une opacité nulle au même instant
     — le décor tient le cadre le temps d'un pas de molette. Verbeux : quatre
     jeux d'images-clés pour quatre plans, parce qu'une image-clé ne sait pas lire
     `--i`. C'est le prix de la fiabilité, et le schéma de valeur paie déjà le
     même pour ses cinq commentaires. */
  animation: 1s linear paused both;
  animation-delay: calc(var(--p) * -1s);
  transition: none;
}

.rep[data-live] .fig:nth-child(1) { animation-name: rep-plan-1; }
.rep[data-live] .fig:nth-child(2) { animation-name: rep-plan-2; }
.rep[data-live] .fig:nth-child(3) { animation-name: rep-plan-3; }
.rep[data-live] .fig:nth-child(4) { animation-name: rep-plan-4; }

/* Présence du texte d'un plan, distincte de celle du plan lui-même.

   Il fallait résoudre une tension : « jamais deux textes lisibles ensemble »
   veut que le sortant soit parti avant que l'entrant n'arrive ; « jamais de
   cadre vide » veut exactement le contraire. Résultat, un creux à chaque
   changement de plan — mesuré à 4 % du chapitre, près d'un cinquième d'écran
   sans autre contenu que le décor.

   La sortie n'est pas de raccourcir le creux mais de donner au cadre autre chose
   à porter : **le chiffre géant du repère suivant arrive avant son texte**. Un
   glyphe n'est pas une ligne à lire, il ne concurrence donc pas la lecture, et il
   tient l'image à lui seul.

   D'où deux horloges dans un seul jeu d'images-clés : `opacity` porte le plan et
   son glyphe — fenêtres larges, qui se recouvrent aux passages — et
   `--texte-vu` porte le libellé et le paragraphe, fenêtres étroites qui ne se
   recouvrent jamais. Enregistrer la propriété est la condition pour qu'elle
   s'interpole ; sa valeur initiale est 1, donc sans animation — repli, mouvement
   réduit — le texte est simplement là. */
@property --texte-vu {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

/* --- Les quatre plans, un axe d'entrée différent chacun --------------------
   « Pas forcément du haut vers le bas » : aucun des quatre n'entre par le même
   geste, et aucun n'utilise de translation verticale — c'est elle qui redit
   « je défile ».

     plan 1  travelling avant  — il remonte de la profondeur
     plan 2  dérive latérale   — il glisse depuis la droite, lentement
     plan 3  révélation sur place — il se desserre et se pose, sans venir d'ailleurs
     plan 4  recul de caméra   — il arrive de trop près et s'installe

   Les fenêtres de visibilité ne se recouvrent pas : là où deux plans se
   succèdent, tous deux passent par une opacité nulle au même instant, et c'est
   le décor qui tient le cadre le temps de ce passage.

   Les bornes, en pourcentage de `--p`, sur un rail de 4,6 écrans (1 écran ≈
   21,7 %) : arrivée longue de 10 à 12 %, **plan posé de 12 %** — soit 0,55 écran
   de lecture, la demi-hauteur demandée — puis une sortie brève de 2 %. Les
   durées sont volontairement inégales d'un plan à l'autre : c'est ce qui donne
   le flottement au lieu de la cadence. */
@keyframes rep-plan-1 {
  0%,  9%  { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: perspective(90rem) translateZ(-16rem) scale(0.94); }
  16%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: perspective(90rem) translateZ(0) scale(1); }
  16%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: perspective(90rem) translateZ(0) scale(1); }
  20%, 31%  { visibility: visible; opacity: 1; --texte-vu: 1; transform: perspective(90rem) translateZ(0) scale(1); }
  34%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: perspective(90rem) translateZ(0) scale(1); }
  38%       { visibility: visible; opacity: 0; --texte-vu: 0; transform: perspective(90rem) translateZ(6rem) scale(1.03); }
  39%, 100% { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: perspective(90rem) translateZ(6rem) scale(1.03); }
}

@keyframes rep-plan-2 {
  0%,  32%  { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: translate3d(14%, 0, 0) scale(0.985); }
  40%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: translate3d(0, 0, 0) scale(1); }
  40%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: translate3d(0, 0, 0) scale(1); }
  44%, 55%  { visibility: visible; opacity: 1; --texte-vu: 1; transform: translate3d(0, 0, 0) scale(1); }
  58%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: translate3d(0, 0, 0) scale(1); }
  62%       { visibility: visible; opacity: 0; --texte-vu: 0; transform: translate3d(-5%, 0, 0) scale(1.01); }
  63%, 100% { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: translate3d(-5%, 0, 0) scale(1.01); }
}

@keyframes rep-plan-3 {
  0%,  56%  { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: scale(1.09); }
  64%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: scale(1); }
  64%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: scale(1); }
  68%, 79%  { visibility: visible; opacity: 1; --texte-vu: 1; transform: scale(1); }
  82%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: scale(1); }
  84%       { visibility: visible; opacity: 0; --texte-vu: 0; transform: scale(0.98); }
  85%, 100% { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: scale(0.98); }
}

/* Le dernier plan tient jusqu'au bout : c'est sur lui que la chaîne de valeur
   prend le relais, et le chapitre ne se termine donc pas sur un cadre vide. */
@keyframes rep-plan-4 {
  0%,  78%  { visibility: hidden;  opacity: 0; --texte-vu: 0; transform: perspective(90rem) translateZ(9rem) scale(1.05); }
  86%       { visibility: visible; opacity: 1; --texte-vu: 0; transform: perspective(90rem) translateZ(0) scale(1); }
  90%, 100% { visibility: visible; opacity: 1; --texte-vu: 1; transform: perspective(90rem) translateZ(0) scale(1); }
}

/* Pas de `visibility` ici, à la différence des fiches des prestations : un
   repère ne contient ni lien ni bouton, il n'y a donc aucun arrêt de focus à
   retirer. Et les quatre restent lus par une synthèse vocale, ce qui est le
   comportement voulu — le contenu est écrit, la mise en scène est visuelle. */

/* Le libellé et le paragraphe suivent l'horloge du **texte**, plus étroite que
   celle du plan : ils arrivent après le glyphe et partent avant lui. C'est ce
   décalage qui remplit les creux des changements de plan. */
.rep[data-live] .fig dt,
.rep[data-live] .fig p { opacity: var(--texte-vu); }

/* Le libellé n'est plus une légende, c'est le titre du plan : un cran plus
   grand, plus espacé, et remonté d'un ton. Il est suivi d'un **filet en
   dégradé qui s'ouvre** sur l'horloge du texte — une forme, donc on a le droit
   de la découper, alors qu'un volet en travers des capitales couperait des
   lettres. C'est lui qui donne au libellé son geste d'arrivée. */
.rep[data-live] .fig dt {
  grid-area: t;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 1.15rem;
  font-size: 0.75rem;
  letter-spacing: 0.22em;
  color: var(--rep-dt);
}

.rep[data-live] .fig dt::after {
  content: "";
  flex: 1;
  max-width: 7.5rem;
  height: 1px;
  background: var(--grad);
  transform: scaleX(var(--texte-vu));
  transform-origin: 0 50%;
}

/* Le chiffre et son unité forment le **glyphe** : ils suivent l'horloge du plan,
   la large. C'est eux qui tiennent l'image quand aucun texte n'est lisible. */
.rep[data-live] .fig dd {
  grid-area: n;
  gap: 0.75rem;
  align-items: baseline;
}

/* Le glyphe occupe le champ. Sa taille descend du nombre de chiffres : un
   chiffre seul est tiré plus grand pour tenir la même surface que deux.

   Il **déborde** par la gauche, et c'est assumé : un chiffre coupé par le bord
   du cadre se lit comme un cadrage de caméra, et il remplit l'image au lieu de
   flotter au milieu d'une bande sombre. Le débordement est en `em`, donc il
   suit la taille du glyphe quelle que soit la fenêtre. */
.rep[data-live] .fig-num {
  --fs: calc(50 - var(--dg, 2) * 11);
  font-size: clamp(7rem, calc(var(--fs) * 1vw), 32rem);
  line-height: 0.78;
  letter-spacing: -0.065em;
  margin-left: -0.14em;
}

.rep[data-live] .fig-unit {
  font-size: clamp(1.2rem, 2.6vw, 2.4rem);
  color: var(--text-2);
}

/* Le paragraphe tient le haut du cadre au lieu de se poser à mi-hauteur : la
   bande sombre en haut à droite était la dernière zone morte du plan.

   Ce n'est pas un paragraphe de page, c'est **le texte d'un plan**, et il était
   encore composé comme le premier : corps des descriptions du site, interligne
   de lecture courante, couleur des paragraphes — celle qui est pensée pour la
   toile presque noire, et qui sur le violet du chapitre passe pour un gris
   moyen. Trois corrections, dans cet ordre d'importance :

     — la **clarté**. La couleur est nommée (`--rep-txt`) et vaut le ton de
       texte le plus franc du registre, dans les deux thèmes : le décor du
       chapitre est plus clair que la toile, un ton de paragraphe n'y suffit
       pas. Le retrait, ici, est l'affaire de l'horloge du texte, pas de la
       couleur ;
     — la **mesure**, exprimée en `em` du paragraphe lui-même et non en `rem` :
       une longueur de ligne se règle en signes, et seul le corps du texte les
       compte. 21 em donnent une quarantaine de signes par ligne à toute
       fenêtre, là où une valeur en `rem` dérivait avec le corps. Et l'interligne
       s'ouvre de 1,55 à 1,66 : à ce corps, c'est lui qui fait respirer le bloc ;
     — le **corps**, monté d'un cran. Le glyphe garde la vedette par sa taille,
       la phrase gagne son rang par sa clarté. */
.rep[data-live] .fig p {
  grid-area: d;
  align-self: start;
  max-width: 21em;
  margin: 0;
  padding-top: 0.2em;
  font-size: clamp(1.2rem, 0.85vw + 1rem, 1.85rem);
  line-height: 1.66;
  letter-spacing: -0.008em;
  color: var(--rep-txt);
  text-wrap: pretty;
}

/* --- Le texte arrive mot à mot ---------------------------------------------
   « Le texte doit arriver, pas apparaître. » Le découpage en mots est celui du
   témoignage, appliqué aux descriptions ; la tête de lecture est ici
   `--texte-vu`, l'horloge **du texte** du plan — donc la fenêtre étroite, celle
   qui ne se recouvre jamais d'un plan à l'autre.

   Deux garde-fous, et ils sont dans la doctrine du fichier :

     — on découpe une forme, jamais une lettre. Le mot est l'unité : chaque
       `.w` se lève en entier, aucune découpe ne traverse un glyphe ;
     — l'enveloppe ne change pas. Le paragraphe garde son `opacity:
       var(--texte-vu)`, mesuré à « jamais deux textes lisibles ensemble ».
       La cascade se joue **à l'intérieur** de cette enveloppe : elle ne peut
       donc pas rouvrir la faute que les images-clés ferment.

   Le « + 6 » réserve une marge de fin — le dernier mot est levé avant que
   l'horloge n'atteigne 1 — et la division par 2,5 étale le lever de chaque mot
   sur deux rangs et demi, sinon il claque. */
.rep .fig p .w { display: inline-block; }

html.js .rep[data-live] .fig p .w {
  --mp: calc((var(--texte-vu) * (var(--nm, 16) + 6) - var(--w)) / 2.5);

  opacity: clamp(0, var(--mp), 1);
  transform: translateY(calc((1 - clamp(0, var(--mp), 1)) * 0.42em));
  transition: none;
}

/* Le fil quitte la marge et vient habiter le champ : dans le chapitre joué, il
   devient la colonne de lumière qui monte derrière le glyphe. C'est le même
   objet, déplacé — pas un second fil. La migration est portée par le mécanisme
   partagé (`--fil-champ`, section 19), réglé en tête de chapitre.

   Le tronc n'a plus de dégradé, et c'est une correction : il s'éteignait à ses
   deux extrémités, donc **aux deux coutures**, là où la doctrine du fil interdit
   précisément qu'une clarté varie — seuls le premier et le dernier segment du
   site y ont droit, faute de voisin. La colonne de lumière est désormais portée
   par l'**aura**, qui s'élargit dans le champ : une forme, et non une longueur,
   donc elle ne peut pas introduire de marche. */
.rep[data-live] .fil::before {
  width: calc(1.6rem + var(--fil-champ) * 2.6rem);
  margin-left: calc((1.6rem + var(--fil-champ) * 2.6rem) / -2);
}

/* Fenêtre étroite : la scène ne se joue pas, mais si elle se joue tout juste,
   le chiffre reprend la ligne et la description passe dessous. */
@media (max-width: 860px) {
  .rep[data-live] .fig {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "t" "n" "d";
  }

  .rep[data-live] .fig p { margin-top: 0.9rem; max-width: none; }
  /* Le glyphe reprend l'axe du cadre : le fil vise donc le milieu, et non 38 %. */
  .rep[data-live] .fil { --fil-vers: calc(min(50%, var(--maxw) / 2) - var(--gut) / 2); }
}

/* ======================================= 10. Schéma vivant : chaîne de valeur */
.vc-shell {
  margin-top: 2.5rem;
  padding: clamp(1rem, 2.5vw, 2rem);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background:
    radial-gradient(ellipse 70% 90% at 12% 100%, rgb(47 128 255 / 10%), transparent 60%),
    var(--surface);
  box-shadow: var(--glow);
}

.vc-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.vc-label {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}

.vc-gauge { display: flex; align-items: baseline; gap: 0.45rem; }

/* Compteur en CSS pur. Une propriété personnalisée déclarée <integer> devient
   interpolable, et `counter()` sait l'afficher : le chiffre monte donc sans
   une ligne de script. `initial-value` porte la valeur d'arrivée, de sorte
   qu'un moteur qui ignorerait @property affiche quand même 100. */
@property --vc-n {
  syntax: "<integer>";
  initial-value: 100;
  inherits: false;
}

.vc-gauge b {
  font-size: clamp(1.9rem, 3.6vw, 2.9rem);
  font-weight: 600;
  line-height: 0.9;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  background: var(--grad);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  counter-reset: v var(--vc-n);
  animation: vc-count 9s linear infinite;
}

.vc-gauge b::after { content: counter(v); }

@keyframes vc-count {
  0%,   20%  { --vc-n: 0; }
  33%,  40%  { --vc-n: 34; }
  53%,  60%  { --vc-n: 68; }
  73%, 100%  { --vc-n: 100; }
}

.vc-gauge span { font-size: 0.8125rem; font-weight: 600; color: var(--muted); }

/* Le « + » ne s'allume qu'avec le prolongement : il dit « et au-delà ». Il
   partage `--proj` avec la projection, donc les deux apparaissent d'un seul
   tenant, sans second réglage à tenir. */
.vc-gauge .vc-plus {
  margin-left: 0.2rem;
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--cyan);
  opacity: var(--proj);
}

.vc-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.vc-legend i {
  display: inline-block;
  width: 1.1rem; height: 2px;
  margin-right: 0.4rem;
  vertical-align: middle;
  border-radius: 2px;
  background: var(--blue-bright);
}

.vc-legend .l-proj { background: none; border-top: 2px dashed var(--cyan); height: 0; }

/* --- Le plan : SVG derrière, cartouches HTML positionnés dessus. --- */
.vc {
  position: relative;
  container-type: inline-size;
  aspect-ratio: 1240 / 420;
}

.vc svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }

.vc-axis { stroke: var(--border); stroke-width: 1; }

/* --- Minuterie unique -------------------------------------------------------
   Tout le schéma est cadencé par une seule boucle de 9 s découpée en cinq
   temps de 1,8 s (20 %) :
     temps 0  les deux intrants s'allument et confluent
     temps 1  compréhension des besoins
     temps 2  réponse + prévision
     temps 3  transformation
     temps 4  prolongement : la pérennité dans le temps

   Aucun élément n'utilise `animation-delay`. C'est délibéré : un délai décale
   aussi l'instant de rebouclage, si bien qu'au retour du ruban à zéro les
   jalons décalés restaient allumés — des billes flottant sans courbe sous
   elles. Ici chaque élément partage exactement la même timeline de 9 s et
   porte sa fenêtre dans ses propres images-clés : tout se remet à zéro au même
   instant, par construction.

   Une seule valeur, `--lit`, décrit l'état d'un temps : 0 pas encore atteint,
   1 en cours, 0,55 déjà franchi. Tout le reste — opacité, couleur, rayon,
   halo de texte — s'en déduit par calcul. Trois états lisibles d'un coup d'œil,
   et une seule chose à régler.

   Rien ne s'interrompt : le schéma tourne en continu, survol compris. */

@property --lit { syntax: "<number>"; initial-value: 0; inherits: true; }

/* Cinq états de temps, la largeur du volet et l'opacité de la projection sont
   portés par des propriétés distinctes — toutes animées sur `.vc-shell`, qui
   n'est jamais masqué. C'est indispensable : `.vc` passe en `display: none`
   sous 860 px, et un sous-arbre retiré du rendu voit ses animations détruites
   puis relancées. Les faire vivre là aurait suffi à désynchroniser durablement
   le schéma au moindre redimensionnement de fenêtre au-delà de la rupture. */
@property --l0 { syntax: "<number>"; initial-value: 0; inherits: true; }
@property --l1 { syntax: "<number>"; initial-value: 0; inherits: true; }
@property --l2 { syntax: "<number>"; initial-value: 0; inherits: true; }
@property --l3 { syntax: "<number>"; initial-value: 0; inherits: true; }
@property --l4 { syntax: "<number>"; initial-value: 0; inherits: true; }
@property --clipw { syntax: "<length>"; initial-value: 1075px; inherits: true; }
@property --proj  { syntax: "<number>"; initial-value: 0; inherits: true; }

.vc-shell {
  animation:
    lit0 9s linear infinite,
    lit1 9s linear infinite,
    lit2 9s linear infinite,
    lit3 9s linear infinite,
    lit4 9s linear infinite,
    clipw 9s linear infinite,
    projv 9s linear infinite;
}

/* Les fenêtres suivent l'avancée du ruban : un temps s'allume au moment où la
   courbe atteint son point. */
@keyframes lit0 { 0%       { --l0: 0; } 6%,  18% { --l0: 1; } 22%, 100% { --l0: 0.55; } }
@keyframes lit1 { 0%, 22%  { --l1: 0; } 30%, 40% { --l1: 1; } 44%, 100% { --l1: 0.55; } }
@keyframes lit2 { 0%, 42%  { --l2: 0; } 50%, 60% { --l2: 1; } 64%, 100% { --l2: 0.55; } }
@keyframes lit3 { 0%, 62%  { --l3: 0; } 70%, 80% { --l3: 1; } 84%, 100% { --l3: 0.55; } }
/* Le cinquième temps n'existe que dans la pile étroite, où la pérennité est une
   étape de la liste et non un prolongement tracé. */
@keyframes lit4 { 0%, 82%  { --l4: 0; } 88%, 96% { --l4: 1; } 99%, 100% { --l4: 0.5; } }

@keyframes clipw {
  0%,   20%  { --clipw: 500px; }
  33%,  40%  { --clipw: 660px; }
  53%,  60%  { --clipw: 880px; }
  73%, 100%  { --clipw: 1075px; }
}

@keyframes projv {
  0%,  78%  { --proj: 0; }
  84%, 97%  { --proj: 0.95; }
  100%      { --proj: 0; }
}

/* Chaque jalon ne fait que désigner le temps qu'il suit : une simple
   déclaration, aucune animation propre, donc rien à resynchroniser. */
.vc-lit               { --lit: var(--l0); }
.vc-lit[data-s="1"]   { --lit: var(--l1); }
.vc-lit[data-s="2"]   { --lit: var(--l2); }
.vc-lit[data-s="3"]   { --lit: var(--l3); }
.vc-lit[data-s="4"]   { --lit: var(--l4); }

/* Le volet de révélation. Sa largeur avance par paliers, sa lisière est un
   dégradé : le ruban se dissipe au lieu de s'arrêter net. Une seule mécanique
   révèle l'aire, le trait et l'éclat — inutile d'animer des pointillés. */
.vc-clip { width: var(--clipw); }

.vc-area { fill: url(#vcArea); }

/* Le ruban : une aura large et floue sous un trait fin. C'est ce qui le fait
   lire comme de la lumière et non comme la courbe d'un tableur. */
.vc-aura {
  fill: none;
  stroke: url(#vcLine);
  stroke-width: 11;
  stroke-linecap: round;
  opacity: 0.4;
  filter: url(#vcHalo);
}

.vc-ribbon {
  fill: none;
  stroke: url(#vcLine);
  stroke-width: 2.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: url(#vcSoft);
}

/* L'éclat qui court le long du ruban, sans fin : le flux. */
.vc-pulse {
  fill: none;
  stroke: #fff;
  stroke-width: 3.4;
  stroke-linecap: round;
  stroke-dasharray: 4 96;
  filter: url(#vcSoft);
  opacity: 0.9;
  animation: vc-pulse 2.9s linear infinite;
}

@keyframes vc-pulse {
  from { stroke-dashoffset: 100; }
  to   { stroke-dashoffset: 0; }
}

/* Prolongement anticipé. Les tirets défilent : le besoin est encore à venir. */
.vc-proj {
  fill: none;
  stroke: var(--cyan);
  stroke-width: 2;
  stroke-dasharray: 7 8;
  stroke-linecap: round;
  opacity: var(--proj);
  animation: march 1.2s linear infinite;
}

@keyframes march { to { stroke-dashoffset: -30; } }

.vc-horizon {
  fill: var(--cyan);
  font-family: var(--font);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 2.4px;
  opacity: var(--proj);
}

/* Confluence des deux intrants vers l'origine du ruban.
   Discontinus et fins, à dessein : ce ne sont pas de la valeur acquise mais ce
   qui l'alimente. Sans cela, l'apport venu d'en haut se lit comme une courbe
   qui chute. Les tirets remontent vers l'origine — le sens de l'alimentation. */
.vc-stream {
  fill: none;
  stroke: color-mix(in srgb, var(--violet) 46%, transparent);
  stroke-width: 1.2;
  stroke-linecap: round;
  stroke-dasharray: 3 6;
  animation: march 1.6s linear infinite;
}

.vc-origin-glow {
  fill: url(#vcOrigin);
  animation: vc-breathe 4.2s ease-in-out infinite;
}

@keyframes vc-breathe {
  0%, 100% { opacity: 0.55; transform: scale(0.9); transform-origin: 500px 460px; }
  50%      { opacity: 1;    transform: scale(1.1); transform-origin: 500px 460px; }
}

.vc-origin {
  fill: var(--violet);
  filter: url(#vcSoft);
}

/* Particules sur les apports : elles ne s'arrêtent jamais. */
.vc-flow {
  fill: var(--violet);
  filter: url(#vcSoft);
  offset-rotate: 0deg;
  animation: flow 3.4s linear var(--f, 0s) infinite;
}

@keyframes flow {
  0%   { offset-distance: 0%;   opacity: 0; }
  14%  { opacity: 1; }
  82%  { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}

/* --- Jalons ---------------------------------------------------------------- */
/* L'attache fine qui monte vers le libellé. */
.vc-tie {
  stroke: color-mix(in srgb, var(--blue-bright) 55%, transparent);
  stroke-width: 1;
  opacity: calc(var(--lit) * 0.9);
}

/* La bille : elle surgit, s'élargit au passage, puis se pose. */
.vc-bead {
  fill: var(--bg);
  stroke: var(--blue-bright);
  stroke-width: 2.6;
  r: calc(4px + var(--lit) * 5.5px);
  opacity: min(1, calc(var(--lit) * 4));
}

.vc-bead[data-end] { stroke: var(--green); }

/* La valeur atteinte, chiffrée au pied du jalon : l'apport devient lisible
   étape par étape, et pas seulement en total. */
.vc-val {
  fill: var(--blue-bright);
  font-family: var(--font);
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  opacity: min(1, calc(var(--lit) * 3));
}

.vc-val[data-end] { fill: var(--green); }

.vc-val-plus { fill: var(--cyan); fill-opacity: var(--proj); }

/* Axe du temps. */
.vc-axis { stroke: var(--border); stroke-width: 1; }

.vc-axis-label {
  fill: var(--faint);
  font-family: var(--font);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
}

/* --- Libellés, sans cadre ---------------------------------------------------
   Plus de cartouche : ni bordure, ni fond, ni flou d'arrière-plan. La
   hiérarchie est portée par la seule typographie et par la lumière. */
.vc-node {
  position: absolute;
  width: 15cqw;
  min-width: 8rem;
  text-align: left;
  /* Le plancher reste franchement lisible : un temps « pas encore atteint » se
     met en retrait, il ne disparaît pas. */
  opacity: calc(0.46 + var(--lit) * 0.54);
}

/* Les intrants s'alignent à gauche de leur point ; les étapes se centrent
   au-dessus de leur bille. */
.vc-node[data-place="left"] { translate: 0 -50%; }
.vc-node:not([data-place])  { translate: -50% -100%; }

.vc-node .n-i {
  display: flex;
  align-items: center;
  gap: 0.5em;
  margin: 0 0 0.45em;
  font-size: 1.02cqw;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--blue-bright) calc(var(--lit) * 100%), var(--faint));
}

/* Le tiret du cartouche se déploie au passage, puis reste à mi-course. */
.vc-node .n-i::before {
  content: "";
  flex: none;
  width: 1.5em;
  height: 1px;
  background: currentColor;
  transform-origin: left;
  transform: scaleX(calc(0.12 + var(--lit) * 0.88));
}

.vc-node .n-t {
  margin: 0;
  font-size: 1.42cqw;
  font-weight: 600;
  line-height: 1.22;
  letter-spacing: -0.018em;
  color: var(--text);
  /* Le halo ne s'allume qu'au temps en cours : il retombe à mesure que --lit
     redescend à 0,55. */
  text-shadow: 0 0 28px rgb(90 169 255 / calc(max(0, var(--lit) - 0.6) * 140%));
}

/* Les intrants gardent leur accent violet : ce sont des apports, pas des
   étapes. L'aboutissement passe au vert : c'est le seul nœud qui « sort ». */
.vc-node[data-kind="in"] .n-i {
  color: color-mix(in srgb, var(--violet) calc(60% + var(--lit) * 40%), var(--faint));
}

.vc-node[data-kind="in"] .n-t {
  text-shadow: 0 0 28px rgb(155 147 255 / calc(max(0, var(--lit) - 0.6) * 140%));
}

.vc-node[data-kind="out"] .n-i {
  color: color-mix(in srgb, var(--green) calc(var(--lit) * 100%), var(--faint));
}

.vc-node[data-kind="out"] .n-t {
  text-shadow: 0 0 30px rgb(111 207 135 / calc(max(0, var(--lit) - 0.6) * 140%));
}

/* Bandeau de commentaire : les cinq temps sont empilés dans la même cellule de
   grille — le bloc prend donc la hauteur du plus long, et chacun se relaie. */
.vc-detail {
  display: grid;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

/* Chaque commentaire porte sa fenêtre dans ses propres images-clés, sans
   `animation-delay` : sinon le premier repart pendant que le dernier n'a pas
   fini de s'effacer, et deux textes se superposent une fraction de seconde. */
.vc-d {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1rem;
  align-items: start;
  opacity: 0;
  animation: det-0 9s linear infinite;
}

.vc-d[data-s="1"] { animation-name: det-1; }
.vc-d[data-s="2"] { animation-name: det-2; }
.vc-d[data-s="3"] { animation-name: det-3; }
.vc-d[data-s="4"] { animation-name: det-4; }

@keyframes det-0 {
  0%             { opacity: 0; transform: translateY(0.5rem); }
  3%,  16%       { opacity: 1; transform: none; }
  19%, 100%      { opacity: 0; transform: translateY(-0.35rem); }
}
@keyframes det-1 {
  0%,  20%       { opacity: 0; transform: translateY(0.5rem); }
  23%, 36%       { opacity: 1; transform: none; }
  39%, 100%      { opacity: 0; transform: translateY(-0.35rem); }
}
@keyframes det-2 {
  0%,  40%       { opacity: 0; transform: translateY(0.5rem); }
  43%, 56%       { opacity: 1; transform: none; }
  59%, 100%      { opacity: 0; transform: translateY(-0.35rem); }
}
@keyframes det-3 {
  0%,  60%       { opacity: 0; transform: translateY(0.5rem); }
  63%, 76%       { opacity: 1; transform: none; }
  79%, 100%      { opacity: 0; transform: translateY(-0.35rem); }
}
@keyframes det-4 {
  0%,  80%       { opacity: 0; transform: translateY(0.5rem); }
  83%, 96%       { opacity: 1; transform: none; }
  99%, 100%      { opacity: 0; transform: translateY(-0.35rem); }
}

.vc-d-i {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  border-radius: 0.65rem;
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--blue-bright);
  font-variant-numeric: tabular-nums;
  flex: none;
}

.vc-d[data-kind="in"]  .vc-d-i { color: var(--violet); }
.vc-d[data-kind="out"] .vc-d-i { color: var(--green); }
.vc-d[data-kind="proj"] .vc-d-i { color: var(--cyan); }

.vc-d h4 {
  margin: 0 0 0.3rem;
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.vc-d p { margin: 0; font-size: 0.9375rem; line-height: 1.5; color: var(--text-2); }

.vc-hint {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1.15rem 0 0;
  font-size: 0.75rem;
  color: var(--faint);
}


/* --- Repli mobile : même récit, en colonne. --- */
.vc-stack { display: none; }

@media (max-width: 860px) {
  .vc { display: none; }
  .vc-stack { display: grid; gap: 0; }
}

.vs-step {
  position: relative;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: 0.9rem;
  padding-bottom: 1.5rem;
}

.vs-step:last-child { padding-bottom: 0; }

.vs-rail { position: relative; display: grid; justify-items: center; }

.vs-rail::before {
  content: "";
  position: absolute;
  top: 1.9rem; bottom: -0.5rem; left: 50%;
  width: 1px;
  background: var(--border);
  transform: translateX(-50%);
}

.vs-step:last-child .vs-rail::before { display: none; }

.vs-bead {
  display: grid;
  place-items: center;
  width: 1.9rem; height: 1.9rem;
  border-radius: 50%;
  background: var(--bg-2);
  font-size: 0.6875rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  /* Même minuterie et même `--lit` que la version large : une seule
     chorégraphie à maintenir, et aucun décalage de rebouclage. */
  border: 1px solid color-mix(in srgb, var(--blue-bright) calc(var(--lit) * 100%), var(--border));
  color: color-mix(in srgb, var(--blue-bright) calc(var(--lit) * 100%), var(--faint));
  box-shadow: 0 0 0 calc(var(--lit) * 4px) var(--ring);
}

.vs-body h4 {
  margin: 0.25rem 0 0.3rem;
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.015em;
}

.vs-body .n-i {
  font-size: 0.5938rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--blue-bright) calc(var(--lit) * 100%), var(--faint));
}

.vs-step[data-kind="in"] .n-i {
  color: color-mix(in srgb, var(--violet) calc(60% + var(--lit) * 40%), var(--faint));
}

.vs-step[data-kind="out"] .n-i {
  color: color-mix(in srgb, var(--green) calc(var(--lit) * 100%), var(--faint));
}

/* Même encodage couleur que sur la version large : violet pour un apport,
   vert pour l'aboutissement. */
.vs-step[data-kind="in"] .vs-bead {
  border-color: color-mix(in srgb, var(--violet) calc(var(--lit) * 100%), var(--border));
  color: color-mix(in srgb, var(--violet) calc(var(--lit) * 100%), var(--faint));
  box-shadow: 0 0 0 calc(var(--lit) * 4px) rgb(155 147 255 / 26%);
}

.vs-step[data-kind="out"] .vs-bead {
  border-color: color-mix(in srgb, var(--green) calc(var(--lit) * 100%), var(--border));
  color: color-mix(in srgb, var(--green) calc(var(--lit) * 100%), var(--faint));
  box-shadow: 0 0 0 calc(var(--lit) * 4px) rgb(111 207 135 / 26%);
}

.vs-body p { margin: 0; font-size: 0.875rem; line-height: 1.5; color: var(--muted); }

/* Jauge de valeur de l'étape : elle se remplit quand l'étape est atteinte. */
.vs-bar {
  margin-top: 0.55rem;
  height: 3px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}

/* La jauge se remplit dès que l'étape est atteinte et n'y redescend plus :
   `min` sature dès que --lit décolle, la valeur acquise ne recule jamais. */
.vs-bar i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--grad);
  width: calc(var(--val, 100%) * min(1, var(--lit) * 2.5));
}

/* -------- Chapitre 3 : la chaîne se déroule au défilement ------------------
   C'est la pièce la plus riche, et pourtant celle qui demande le moins de
   règles nouvelles — parce qu'elle n'en demande aucune sur la chorégraphie
   elle-même.

   Le schéma est déjà découpé en cinq temps par une boucle de 9 s : les deux
   socles, les trois étapes, la projection. C'est exactement l'ordre demandé. Au
   lieu de réécrire cette séquence en fonction de `--p`, on **met la boucle en
   pause et on déplace sa tête de lecture** : `animation-play-state: paused`
   fige l'animation, et un `animation-delay` négatif tiré de `--p` choisit
   l'instant affiché. Le défilement devient la molette de lecture du schéma.

   Trois conséquences, toutes voulues :
     — aucune image-clé n'est touchée. Hors chapitre joué — fenêtre étroite,
       mouvement réduit, vignette du panneau Design System — la boucle repart
       d'elle-même, à l'identique. Il n'y a pas un second jeu de règles ;
     — le compteur de pourcentage et le commentaire qui se relaie suivent sans
       un réglage de plus : ils sont sur la même timeline de 9 s ;
     — tout ce qui doit continuer de vivre continue : l'éclat qui court sur le
       ruban, les particules des deux apports, la respiration de l'origine et
       les tirets de la projection portent leurs propres animations, que la mise
       en pause du cadre ne touche pas. Le schéma est scruté, jamais éteint.

   On s'arrête à 8,6 s et non à 9 s : la dernière image de la boucle est celle
   du rebouclage, où la projection s'est déjà refermée. */
.val[data-live] {
  --p: 0;

  /* --- Table de tempo du chapitre, même forme qu'aux repères ---------------
     `--rail` est en hauteurs de fenêtre ; 1 / --rail est donc la part de `--p`
     qui vaut un écran de défilement. À 2,9 écrans, un écran ≈ 34,5 % de `--p`.

     `--u` est le temps du schéma, 0 → 1 : c'est lui qui déplace la tête de
     lecture de la boucle de 9 s. Sa fenêtre laisse au début de quoi retirer
     l'accroche, et à la fin de quoi tenir la planche complète sous les yeux —
     0,22 de `--p`, soit 0,64 écran de lecture posée sur le schéma complet. */
  --rail: 2.9;
  --u: clamp(0, calc((var(--p) - 0.15) / 0.63), 1);
  --entry: clamp(0, calc((var(--p) - 0.03) / 0.10), 1);

  /* Les deux bouts du monde : de la couture 2 à la couture 3. Le monde suit ici
     le temps du **schéma** et non l'avancement brut — c'est la chaîne qui se
     construit, le décor doit se construire avec elle. Et il revient vers le
     bleu : les repères avaient poussé jusqu'au violet, le circuit rend la main. */
  --env-t0: var(--mo-2);   --env-g0: var(--mg-2);
  --env-t1: var(--mo-3);   --env-g1: var(--mg-3);
  --env-w: var(--u);

  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;
}

/* --- Réglages de l'environnement de la chaîne de valeur ---------------------
   Le schéma est la pièce forte du site : son décor doit le **servir**, pas lui
   disputer le premier plan. Trois décisions en conséquence :

     — pas de sol. Un plancher en perspective croiserait le ruban et l'œil ne
       saurait plus quelle ligne lire. C'est la couche qu'on retire, et c'est
       pour cela que le mécanisme la rend optionnelle.
     — une trame **serrée** : elle arrive de la couture 2 à 1,4 rem, et à cette
       densité elle ne se lit plus comme un champ d'étoiles mais comme un
       circuit, ce qui est le registre du chapitre. Elle se desserre ensuite vers
       la couture 3, où le profil la reprend plus large.
     — une **lumière basse** : le monde évolue sans attirer le regard hors du
       plan. C'est le seul réglage de force du décor, et il suffit — inutile de
       lui donner des accents propres, qui auraient rouvert la faute de couture. */
.val[data-live] .env {
  --env-lum: 0.62;
  --env-champ-o: 0.34;
}

/* La trame du circuit se tisse : deux filets fins croisés en plus des points,
   qui se resserrent avec l'avancement. Ils sont dans le décor, donc derrière le
   ruban, et leur opacité reste basse. */
.val[data-live] .env-champ::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    repeating-linear-gradient(60deg,  transparent 0 3.4rem, var(--border) 3.4rem 3.45rem),
    repeating-linear-gradient(-60deg, transparent 0 3.4rem, var(--border) 3.4rem 3.45rem);
  /* Cette trame croisée est **propre au chapitre** : aucun voisin ne la porte.
     Elle est donc éteinte aux deux coutures — sinon elle disparaîtrait d'un coup
     à la jonction, ce qui est le défaut qu'on vient de fermer ailleurs. */
  opacity: calc((0.25 + 0.35 * var(--u)) * var(--env-relais));
}

.val[data-live] .vc-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

.val[data-live] .vc-inner {
  position: relative;
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  padding-block: 4.6rem clamp(1.5rem, 5vh, 3.5rem);
}

.val[data-live] .vc-kicker {
  position: relative;
  z-index: 4;
  align-self: start;
  margin: 0;
}

.val[data-live] .vc-intro,
.val[data-live] .vc-shell { grid-row: 2; grid-column: 1; }

.val[data-live] .vc-intro {
  max-width: 52rem;
  pointer-events: none;
  transform: translateY(calc(var(--entry) * -3.5rem)) scale(calc(1 - 0.05 * var(--entry)));
  transform-origin: 0 50%;
  opacity: calc(1 - var(--entry));
  filter: blur(calc(var(--entry) * 5px));
}

/* `html.js` en tête : sans lui, `html.js .rv[data-in]` — posé à la révélation —
   reprendrait la main sur l'opacité et le plan arriverait d'un coup. */
html.js .val[data-live] .vc-shell {
  margin: 0;
  opacity: var(--entry);
  /* Le plan ne remonte pas de 6 vh : il **se déplie sur place**. Un volet
     s'ouvre depuis l'axe du ruban et la caméra desserre légèrement son cadrage.
     C'est le même « il arrive », sans qu'un bord de fenêtre soit traversé. */
  transform: scale(calc(0.965 + 0.035 * var(--entry)));
  clip-path: inset(calc((1 - var(--entry)) * 42%) 0);
}

/* --- Tenir dans une fenêtre collée ----------------------------------------
   Le cadre entier doit tenir dans un écran sans qu'une ligne soit rognée : la
   scène est collée, il n'y a pas de rattrapage possible. Le plan est donc
   dimensionné **par sa hauteur** — on plafonne sa largeur à ce que son rapport
   autorise — et le reste du cadre se resserre. Le rapport 1240/420 est conservé
   tel quel : les libellés sont posés en pourcentage du plan, et une boîte au
   mauvais rapport les décrocherait de leurs billes. */
.val[data-live] .vc-shell { padding: 1.1rem clamp(1rem, 2vw, 1.5rem); }

.val[data-live] .vc { width: min(100%, calc(36svh * 1240 / 420)); margin-inline: auto; }

.val[data-live] .vc-head { margin-bottom: 0.75rem; }

.val[data-live] .vc-gauge b { font-size: clamp(1.6rem, 2.6vw, 2.15rem); }

.val[data-live] .vc-detail { margin-top: 0.9rem; padding-top: 0.8rem; }

/* --- La molette de lecture ------------------------------------------------- */
/* Les trois porteurs de la boucle de 9 s : le cadre (états des temps, volet de
   révélation, projection), le compteur de pourcentage, et le commentaire qui se
   relaie. Même pause, même tête de lecture. */
.val[data-live] .vc-shell,
.val[data-live] .vc-gauge b,
.val[data-live] .vc-d {
  animation-play-state: paused;
  animation-delay: calc(var(--u) * -8.6s);
}

/* La mention des 9 secondes n'est vraie que hors chapitre joué : ici c'est le
   doigt qui donne le tempo, et un repère de défilement le dit à sa place. */
.val[data-live] .vc-hint { display: none; }

.vc-cue {
  display: none;
  margin: 1.15rem 0 0;
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
}

/* Sorti du cadre et posé au bas de l'étage : dans une fenêtre collée, chaque
   ligne prise au cadre est une ligne prise au schéma. */
.val[data-live] .vc-cue {
  display: block;
  position: absolute;
  inset: auto 0 0;
  margin: 0;
  text-align: center;
  /* Il s'efface dès que la chaîne est lancée : il a fait son office. */
  opacity: clamp(0, calc(1 - 4 * var(--u)), 1);
}

/* --- Le dédoublement du fil ------------------------------------------------
   Deux ramifications quittent le tronc au même point et divergent vers le
   plan : le fil devient les deux socles. Elles s'allument avec le premier temps
   du schéma — celui des deux apports — sans avoir à lire les variables de la
   boucle, qui ne descendent que dans le cadre.

   Hors chapitre joué, la section est du flux normal et bien plus haute : les
   deux ramifications ne pointeraient plus vers rien. Elles n'existent donc que
   pendant la scène — le fil, lui, reste. */
.val .fil-socle { display: none; }

.val[data-live] .fil-socle {
  display: block;
  position: absolute;
  top: 46%;
  left: var(--fil-x);
  width: 5.5rem;
  height: 1px;
  background: linear-gradient(90deg, var(--violet), transparent);
  transform-origin: 0 50%;
  opacity: clamp(0, calc((var(--u) - 0.02) / 0.06), 1);
}

.val .fil-socle:nth-of-type(1) { transform: rotate(-24deg); }
.val .fil-socle:nth-of-type(2) { transform: rotate(24deg); }

/* Deux cas où le chapitre ne se joue pas et redevient du flux normal :
     — sous 900 px de large, c'est la pile qui s'affiche à la place du plan, et
       elle ne tient pas dans une fenêtre collée ;
     — sous 40 rem de haut, le cadre entier ne tient plus dans un écran, et on
       ne rogne pas le commentaire du client pour y arriver.
   Dans les deux cas la boucle de 9 s reprend son cours d'elle-même : plus rien
   ne la met en pause. C'est tout l'intérêt de n'avoir touché aucune image-clé. */
@media (max-width: 900px), (max-height: 40rem) {
  .val[data-live] {
    height: auto;
    padding-block: clamp(4.5rem, 11vh, 9rem);
  }

  .val[data-live] .vc-stage { position: static; height: auto; overflow: visible; }

  .val[data-live] .vc-inner {
    display: block;
    height: auto;
    padding-block: 0;
  }

  .val[data-live] .vc-intro,
  .val[data-live] .vc-shell { grid-row: auto; grid-column: auto; }

  .val[data-live] .vc-intro {
    max-width: none;
    pointer-events: auto;
    transform: none;
    opacity: 1;
    filter: none;
  }

  html.js .val[data-live] .vc-shell {
    margin-top: 2.5rem;
    opacity: 1;
    transform: none;
  }

  .val[data-live] .vc-shell,
  .val[data-live] .vc-gauge b,
  .val[data-live] .vc-d {
    animation-play-state: running;
    animation-delay: 0s;
  }

  .val[data-live] .vc-shell { padding: clamp(1rem, 2.5vw, 2rem); }
  .val[data-live] .vc { width: 100%; }
  .val[data-live] .vc-head { margin-bottom: 1.25rem; }
  .val[data-live] .vc-gauge b { font-size: clamp(1.9rem, 3.6vw, 2.9rem); }
  .val[data-live] .vc-detail { margin-top: 1.5rem; padding-top: 1.25rem; }

  .val[data-live] .vc-hint { display: flex; }
  .val[data-live] .vc-cue { display: none; }
  .val .fil-socle { display: none; }
}

/* ============================================================ 11. Profil */
.about {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

@media (max-width: 880px) { .about { grid-template-columns: 1fr; } }

.portrait {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-3);
}

.portrait img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  transform: scale(1.06);
  transition: transform 1.4s var(--ease), filter 1.4s var(--ease);
  filter: saturate(0.9) contrast(1.04);
}

.portrait:hover img { transform: scale(1.01); }

/* Voile marine + reflet bleu, pour que la photo appartienne à la même toile.
   Le voile reste sombre dans les deux thèmes : la légende posée dessus est
   blanche, un voile clair la rendrait illisible. */
.portrait::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to top, rgb(5 7 15 / 88%) 0%, rgb(5 7 15 / 30%) 34%, transparent 56%),
    linear-gradient(120deg, rgb(47 128 255 / 16%), transparent 55%);
}

.portrait figcaption {
  position: absolute;
  inset: auto 0 0;
  z-index: 1;
  padding: 1.1rem 1.25rem;
}

.portrait .p-name {
  font-family: var(--font-mark);
  font-size: 1.35rem;
  font-weight: 300;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #fff;
}

.portrait .p-role {
  margin-top: 0.2rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 68%);
}

/* Petites pastilles de fait, sous le portrait. */
.facts { display: grid; gap: 0.5rem; margin-top: 1rem; }

.fact {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.75rem;
  align-items: baseline;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: 0.875rem;
}

.fact dt {
  font-size: 0.5938rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
}

.fact dd { margin: 0; color: var(--text-2); }

.prose p { margin: 0 0 1.1rem; color: var(--text-2); }
.prose p:last-child { margin-bottom: 0; }
.prose strong { color: var(--text); font-weight: 600; }

/* La citation d'ouverture du profil, mise en exergue. */
.pull {
  margin: 0 0 1.75rem;
  padding-left: 1.25rem;
  border-left: 2px solid var(--blue);
  font-size: clamp(1.0625rem, 1.2vw + 0.8rem, 1.4rem);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.02em;
  color: var(--text);
}

/* -------- Chapitre 4 : le portrait en travelling, la prose par plans --------
   Version précédente à jeter, et pour la raison qui vaut partout ici : la
   colonne du portrait était accrochée et **la prose défilait à côté**. C'était
   donc, littéralement, du défilement mis en scène — chaque paragraphe montait à
   travers le champ sur sa hauteur d'écran.

   Ici l'étage est collé. Le portrait tient toute la hauteur du cadre et **se
   recadre lentement** pendant le chapitre, comme un travelling ; la prose ne
   traverse plus rien, elle **se relaie sur place** en plans successifs, par
   fondu et profondeur.

   Les six paragraphes se relaient **deux par deux**. Un seul paragraphe laissait
   une bande vide à côté du portrait : deux le remplissent, et cela ramène le
   chapitre de six temps à trois, donc dans le budget. */
.pro[data-live] {
  --p: 0;
  /* Le rail était écrit en dur (240 vh). Il est nommé comme aux trois premiers
     chapitres : c'est de lui que le mécanisme de couture tire la durée du
     relais — une demi-fenêtre à chaque bout — et un nombre sans nom ne pouvait
     pas la donner. */
  --rail: 2.4;
  --t0: 0.17;
  --dt: 0.26;          /* --p consommé par plan de deux paragraphes */
  --entry: clamp(0, calc((var(--p) - 0.03) / 0.10), 1);
  /* Borne haute = rang du dernier plan : le troisième tient jusqu'au bout, et
     c'est sur lui que les domaines prennent le relais. */
  --t: clamp(0, calc((var(--p) - var(--t0)) / var(--dt)), 2);

  /* --- Les deux bouts du monde -------------------------------------------
     De la couture 3 à la couture 4, et c'est la plus grande montée en teinte du
     site : +1,7 cran, soit 31° vers le violet. C'est **le chapitre chaud**, et
     il l'est parce qu'on y parle d'un homme et non d'un dispositif. Le monde
     avance par plans, comme aux repères — un cran par paire de paragraphes. */
  --env-t0: var(--mo-3);   --env-g0: var(--mg-3);
  --env-t1: var(--mo-4);   --env-g1: var(--mg-4);
  --env-w: calc(var(--t) / 2);

  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;
}

/* --- Réglages de l'environnement du profil ---------------------------------
   « Plus chaud et plus calme. » Le chaud est dans le cran de monde ; le calme
   est ici : c'est la **trame la plus effacée du site** et il n'y a pas de sol.
   Rien de géométrique derrière un visage — une grille en perspective aurait
   ramené le portrait au rang d'illustration dans un dispositif.

   La lumière est à son maximum utile — 1, et non davantage : au-delà, les deux
   couches qui la portent plafonnent leur opacité, et une valeur qui ne peut rien
   changer n'a rien à faire dans une feuille de style. Ce sont les accents qui
   font tout le relief du décor ici, puisque les deux autres couches se taisent. */
.pro[data-live] .env {
  --env-lum: 1;
  --env-champ-o: 0.18;
}

.pro[data-live] .pro-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

.pro[data-live] .pro-decor {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.pro[data-live] .pro-halo {
  position: absolute;
  inset: 0;
  background: radial-gradient(46rem 36rem at 62% 45%,
    color-mix(in srgb, var(--violet, var(--blue)) 24%, transparent), transparent 72%);
  /* Le halo se déplace d'un plan à l'autre : le décor avance avec la lecture. */
  transform: translate3d(calc(var(--t) * -6%), 0, 0) scale(calc(1 + var(--t) * 0.05));
  opacity: calc(0.45 + 0.55 * var(--entry));
}

.pro[data-live] .pro-inner {
  position: relative;
  z-index: 1;
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  padding-block: 4.6rem clamp(1.5rem, 5vh, 3.5rem);
}

.pro[data-live] .pro-kicker { align-self: start; margin: 0; }

.pro[data-live] .about {
  grid-row: 2;
  height: 100%;
  align-items: center;
  /* Le portrait prend franchement le champ : c'est lui le plan. */
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
}

.pro[data-live] .pro-side {
  /* Plus d'accrochage : l'étage entier est collé, la colonne n'a plus à l'être.
     Deux mécanismes d'accroche imbriqués se disputaient la même hauteur. */
  position: static;
  display: grid;
  align-content: center;
  gap: clamp(1rem, 2.5vh, 1.75rem);
  height: 100%;
}

/* Le travelling : le cadrage se desserre lentement d'un bout à l'autre du
   chapitre. C'est une transformation composée, pas un recadrage de mise en
   page — et elle ne se dispute rien avec l'`object-position` de la section 18,
   qui reste libre de porter la dérive. */
.pro[data-live] .portrait {
  height: min(52svh, 30rem);
  margin: 0;
}

.pro[data-live] .portrait img {
  height: 100%;
  transform: scale(calc(1.14 - 0.14 * var(--entry) + var(--p) * 0.07));
}

/* Les pastilles de fait s'inscrivent l'une après l'autre, par volets décalés.
   Le curseur est `--entry`, l'entrée du chapitre : la version précédente lisait
   `--q`, l'avancement de la section, parce qu'il n'y avait pas d'étage collé et
   que `--p` n'existait qu'une fois la section plein cadre. Maintenant que
   l'étage est collé, `--p` est disponible dès l'entrée et c'est lui qui commande
   — avec `--q`, les pastilles restaient invisibles. */
.pro[data-live] .fact {
  --on: clamp(0, calc(var(--entry) * 2.4 - var(--k, 0) * 0.35), 1);
  opacity: var(--on);
  /* Un volet horizontal : la pastille s'inscrit sur place. Elle est courte et
     tient sur une ligne, donc la découpe ne traverse aucune ligne de texte. */
  clip-path: inset(0 calc((1 - var(--on)) * 100%) 0 0);
}

.pro[data-live] .fact:nth-child(2) { --k: 1; }
.pro[data-live] .fact:nth-child(3) { --k: 2; }
.pro[data-live] .fact:nth-child(4) { --k: 3; }

/* La prose devient une pile de plans dans une seule case. Les paragraphes de
   même `--i` sont sur le même plan et arrivent donc ensemble. */
.pro[data-live] .prose {
  display: grid;
  align-content: center;
  height: 100%;
  perspective: 90rem;
  opacity: var(--entry);
}

/* C'est le **plan** qui porte la mise en scène, pas le paragraphe : les deux
   paragraphes d'un même plan doivent rester l'un sous l'autre et arriver
   ensemble. Empilés individuellement, ils se seraient recouverts. */
.pro[data-live] .pro-plan {
  grid-row: 1;
  grid-column: 1;
  align-self: center;
  display: grid;
  gap: clamp(0.9rem, 2vh, 1.5rem);

  /* Même procédé et même raison qu'aux repères et aux domaines : des seuils, pas
     une arithmétique substituée. Trois plans, trois fenêtres qui ne se
     recouvrent pas — deux paragraphes ne peuvent donc jamais se superposer. */
  animation: 1s linear paused both;
  animation-delay: calc(var(--p) * -1s);
  transition: none;
}

.pro[data-live] .pro-plan:nth-child(1) { animation-name: pro-plan-1; }
.pro[data-live] .pro-plan:nth-child(2) { animation-name: pro-plan-2; }
.pro[data-live] .pro-plan:nth-child(3) { animation-name: pro-plan-3; }

@keyframes pro-plan-1 {
  0%,  10%  { visibility: hidden;  opacity: 0; transform: perspective(90rem) translateZ(-11rem); }
  20%, 34%  { visibility: visible; opacity: 1; transform: perspective(90rem) translateZ(0); }
  39%       { visibility: visible; opacity: 0; transform: perspective(90rem) translateZ(6rem); }
  40%, 100% { visibility: hidden;  opacity: 0; transform: perspective(90rem) translateZ(6rem); }
}

@keyframes pro-plan-2 {
  0%,  39%  { visibility: hidden;  opacity: 0; transform: perspective(90rem) translateZ(-11rem); }
  49%, 63%  { visibility: visible; opacity: 1; transform: perspective(90rem) translateZ(0); }
  68%       { visibility: visible; opacity: 0; transform: perspective(90rem) translateZ(6rem); }
  69%, 100% { visibility: hidden;  opacity: 0; transform: perspective(90rem) translateZ(6rem); }
}

/* Le dernier plan tient jusqu'au bout du chapitre. */
@keyframes pro-plan-3 {
  0%,  68%  { visibility: hidden;  opacity: 0; transform: perspective(90rem) translateZ(-11rem); }
  78%, 100% { visibility: visible; opacity: 1; transform: perspective(90rem) translateZ(0); }
}

/* À l'échelle du plan, la prose est un bloc de lecture et non une colonne de
   côté : le corps monte, et la largeur avec lui. C'est ce qui remplit la moitié
   droite du cadre au lieu d'y laisser deux bandes sombres. */
/* Le ton monte d'un cran, et c'est la même correction que sur la description des
   repères (`--rep-txt`) : la prose portait la couleur des paragraphes du site,
   réglée pour la toile presque noire. Le décor du chapitre est désormais un
   violet nettement plus clair, sur lequel ce ton passe pour un gris moyen. Le
   retrait d'un plan est l'affaire de son horloge, jamais de sa couleur. */
.pro[data-live] .prose p {
  margin: 0;
  font-size: clamp(1.05rem, 0.55vw + 0.95rem, 1.35rem);
  line-height: 1.62;
  max-width: 38rem;
  color: var(--text);
}

.pro[data-live] .prose .pull {
  font-size: clamp(1.2rem, 0.9vw + 1rem, 1.7rem);
  line-height: 1.4;
}

/* Le fil se tend le long de la colonne du portrait — mais il **rend son
   épaisseur et son aura avant la jonction**. Elles étaient constantes sur tout
   le chapitre : le fil passait donc de 1 px à 2 px d'un coup à l'entrée, et
   revenait d'un coup à la sortie. Il reste dans la gouttière (`--fil-vers` nul),
   seule sa matière change. */
.pro[data-live] .fil {
  --fil-champ: var(--env-relais);
  --fil-gras: 1px;
  --fil-lueur: 0.5;
}

/* Fenêtre étroite : la grille du profil repasse à une colonne (rupture à
   880 px, section 11), le portrait ne peut plus rester accroché à côté de rien,
   et l'aération des paragraphes n'a plus lieu d'être. */
@media (max-width: 880px) {
  .pro[data-live] .about { grid-template-columns: 1fr; }
  .pro[data-live] .pro-side { position: static; }

  .pro[data-live] .prose p,
  .pro[data-live] .prose .pull {
    display: block;
    min-height: 0;
    margin-bottom: 1.1rem;
    opacity: 1;
  }

  .pro[data-live] .prose p:last-child { margin-bottom: 0; }
}

/* ======================================================= 12. Domaines */
.domains {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1rem;
  margin-top: 2.5rem;
}

.domain {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 17rem;
  padding: 1.35rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  isolation: isolate;
  transition: transform 0.6s var(--ease), border-color 0.4s var(--ease);
}

.domain:hover { transform: translateY(-5px); border-color: var(--border-strong); }

.domain img {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.08);
  transition: transform 1.2s var(--ease);
  filter: saturate(1.05);
}

.domain:hover img { transform: scale(1.16); }

.domain::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--bg) 94%, transparent) 6%,
    color-mix(in srgb, var(--bg) 60%, transparent) 46%,
    color-mix(in srgb, var(--bg) 22%, transparent)
  );
}

.domain h3 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: #fff;
}

:root[data-theme="light"] .domain h3,
:root[data-theme="light"] .domain .d-n { color: var(--text); }

.domain .d-n {
  margin-bottom: 0.45rem;
  font-size: 0.5938rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-bright);
}

/* Classe explicite : un sélecteur `.domain p` replierait aussi le numéro,
   qui est également un paragraphe. */
.domain .d-d {
  margin: 0.5rem 0 0;
  font-size: 0.875rem;
  line-height: 1.5;
  color: var(--text-2);
  transition: max-height 0.6s var(--ease), opacity 0.5s var(--ease), margin 0.6s var(--ease);
}

/* La description ne se replie que si le script tourne — c'est le même contrat
   que `.rv` juste au-dessus, et pour la même raison. Ces trois descriptions
   sont du contenu rédigé : les replier par défaut les rendait invisibles sans
   script, et sur un écran tactile étroit — où aucune scène ne s'arme, donc où
   il n'y a pas de survol — elles ne dépendaient plus que du focus clavier.
   Le repli n'est pas une version au rabais : au repos, tout est écrit. */
html.js .domain .d-d {
  max-height: 0;
  opacity: 0;
  overflow: hidden;
}

html.js .domain:hover .d-d,
html.js .domain:focus-within .d-d { max-height: 9rem; opacity: 1; }

/* -------- Chapitre 5 : les trois terrains passent au premier plan ----------
   La pellicule des prestations, avec les images : les trois vignettes montent à
   travers le champ, celle qui est au milieu occupe tout l'écran, la suivante
   affleure par le bas et celle qui vient de passer sort par le haut. On voit
   toujours d'où l'on vient et où l'on va.

   Le premier essai les empilait en profondeur, l'une devant l'autre. À rejeter :
   au croisement, deux photographies à demi transparentes se mélangeaient et il
   n'y avait plus d'image lisible du tout. Ici les vignettes sont **opaques** et
   c'est la géométrie qui les sépare — comme dans la pellicule, où deux
   paragraphes ne se lisent jamais en même temps.

   La chorégraphie descend d'un seul curseur : chaque vignette ne connaît que
   son écart à la vignette courante (`--d`), d'où se déduisent décalage,
   profondeur et clarté.

   Le clavier : les trois vignettes sont focalisables, et la description ne
   s'ouvre au survol que sur celle qui est au premier plan. Prendre le focus sur
   une vignette de l'arrière-plan la **ramène au premier plan** — plutôt que de
   la retirer de l'ordre de tabulation, on la rend lisible. On ne peut donc
   jamais atteindre au clavier quelque chose qu'on ne voit pas. */
.dom[data-live] {
  --p: 0;
  --t0: 0.16;          /* --p où la première vignette est au premier plan */
  --dt: 0.32;          /* --p consommé par vignette — lu aussi par le script */
  /* L'accroche se retire sur un cinquième du chapitre, et non sur un dixième :
     c'est l'autre moitié du geste d'entrée. */
  --entry: clamp(0, calc((var(--p) - 0.06) / 0.21), 1);
  /* La borne haute est le rang de la dernière vignette, et non le nombre de
     vignettes : au-delà, la troisième repartait vers le haut et le dernier
     tiers du chapitre se jouait sur un écran vide — la pellicule quittait
     l'image, ce qu'aucun chapitre ne doit faire. Elle reste donc au premier
     plan jusqu'au bout, et c'est sur elle que les prestations prennent le
     relais. Le plan tenu est aussi ce qui laisse le temps de lire la
     description. */
  --t: clamp(0, calc((var(--p) - var(--t0)) / var(--dt)), 2);
  /* L'écart des vignettes sur la pellicule a disparu avec la pellicule
     elle-même : les trois terrains sont désormais superposés en plein cadre et
     se relaient par volet, il n'y a plus rien à décaler. */

  /* --- Les deux bouts du monde -------------------------------------------
     De la couture 4 à la couture 5 : le profil avait poussé jusqu'au violet, les
     terrains **redescendent vers le bleu** (−2,2 crans, 40°). Le monde avance
     par vignette, comme les plans du profil et les repères. */
  --env-t0: var(--mo-4);   --env-g0: var(--mg-4);
  --env-t1: var(--mo-5);   --env-g1: var(--mg-5);
  --env-w: calc(var(--t) / 2);

  /* Le rail suit les temps : trois passages de 0,32 et un plan tenu d'un demi
     écran à la fin. Nommé, comme aux autres chapitres, parce que la durée du
     relais de couture s'en déduit. */
  --rail: 2.5;
  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;
}

/* --- Réglages de l'environnement des domaines ------------------------------
   Le seul chapitre, avec les repères, à porter un **sol** : on y parle de
   terrains, et un plancher en perspective qui s'incline le dit sans une ligne de
   texte. Il est plus discret qu'aux repères — les trois photographies occupent
   déjà le plein cadre, et le sol n'a pas à leur disputer la lecture.

   La lumière est basse pour la même raison : les vignettes sont opaques et
   pleines, le décor ne se voit qu'aux passages. */
.dom[data-live] .env {
  --env-lum: 0.66;
  --env-champ-o: 0.42;
  --env-sol-o: 0.5;
}

.dom[data-live] .dom-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* Voile aux deux bords : la pellicule entre et sort dans le noir plutôt que
   d'être tranchée net par le bord de la fenêtre. */
.dom[data-live] .dom-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  background:
    linear-gradient(180deg, var(--bg), transparent 20%),
    linear-gradient(0deg, var(--bg), transparent 12%);
  opacity: 0.92;
}

:root[data-theme="light"] .dom[data-live] .dom-stage::after { opacity: 0.78; }

.dom[data-live] .dom-inner {
  position: relative;
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  padding-block: 4.6rem clamp(1.5rem, 5vh, 3.5rem);
}

.dom[data-live] .dom-kicker {
  position: relative;
  z-index: 4;
  align-self: start;
  margin: 0;
}

.dom[data-live] .dom-intro,
.dom[data-live] .domains { grid-row: 2; grid-column: 1; }

.dom[data-live] .dom-intro {
  max-width: 46rem;
  pointer-events: none;
  transform: translateY(calc(var(--entry) * -3.5rem)) scale(calc(1 - 0.05 * var(--entry)));
  transform-origin: 0 50%;
  opacity: calc(1 - var(--entry));
  filter: blur(calc(var(--entry) * 5px));
}

.dom[data-live] .domains {
  place-items: stretch;
  gap: 0;
  margin: 0;
  height: 100%;
  perspective: 1600px;
  opacity: var(--entry);
}

/* Deuxième version à jeter, pour la même raison que les repères : les trois
   vignettes montaient à travers le champ, chacune décalée de `--gap`. Le
   mouvement dominant était donc une translation verticale, et le défilement se
   voyait. La vignette occupait par ailleurs une carte de 46 rem au milieu d'un
   écran par ailleurs vide.

   Ici le terrain est **plein cadre** : la photographie remplit l'étage, voilée,
   et le texte est posé dessus. On passe d'un terrain au suivant par un **volet
   qui s'ouvre depuis le centre** doublé d'un **recadrage** — la caméra desserre
   son cadrage sur celui qui arrive. Rien ne glisse, tout se révèle sur place. */
html.js .dom[data-live] .domain {
  grid-area: 1 / 1;
  /* Plein cadre : le terrain occupe l'étage entier, il n'y a plus de carte
     posée au milieu du vide. */
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: clamp(1.75rem, 4vw, 4.5rem);
  display: grid;
  align-content: end;
  border-color: transparent;
  border-radius: clamp(0.75rem, 1.5vw, 1.5rem);
  background: var(--bg-3);
  overflow: hidden;
  transition: none;

  /* Même procédé qu'aux repères, et pour la même raison : les images-clés sont
     des seuils, elles ne se substituent pas, et deux terrains ne peuvent donc
     pas se retrouver visibles ensemble. L'arithmétique précédente le
     garantissait sur le papier mais repartait invalide par moments, et trois
     photographies à demi transparentes se mêlaient alors en une bouillie.

     L'entrée est **longue** : c'est le passage du titre au premier terrain, donc
     le moment où l'on change de monde, et il se jouait presque d'un coup. Le
     terrain monte maintenant de loin sur un cinquième du chapitre, et son
     recadrage continue bien après que le titre a disparu. */
  animation: 1s linear paused both;
  animation-delay: calc(var(--p) * -1s);
}

html.js .dom[data-live] .domain:nth-child(1) { animation-name: dom-plan-1; }
html.js .dom[data-live] .domain:nth-child(2) { animation-name: dom-plan-2; }
html.js .dom[data-live] .domain:nth-child(3) { animation-name: dom-plan-3; }

/* L'entrée dans le sujet : le terrain vient de loin, s'ouvre en prenant le
   cadre, puis continue de se desserrer lentement — la caméra ne s'arrête pas
   net quand le titre s'est effacé. */
@keyframes dom-plan-1 {
  0%,  10%  { visibility: hidden;  opacity: 0; transform: scale(0.86); }
  16%       { visibility: visible; opacity: 0; transform: scale(0.88); }
  34%       { visibility: visible; opacity: 1; transform: scale(0.98); }
  52%       { visibility: visible; opacity: 1; transform: scale(1.01); }
  57%       { visibility: visible; opacity: 0; transform: scale(1.04); }
  58%, 100% { visibility: hidden;  opacity: 0; transform: scale(1.04); }
}

@keyframes dom-plan-2 {
  0%,  57%  { visibility: hidden;  opacity: 0; transform: scale(0.9); }
  67%, 76%  { visibility: visible; opacity: 1; transform: scale(1); }
  81%       { visibility: visible; opacity: 0; transform: scale(1.04); }
  82%, 100% { visibility: hidden;  opacity: 0; transform: scale(1.04); }
}

/* Le dernier terrain tient jusqu'au bout : c'est sur lui que les prestations
   prennent le relais. */
@keyframes dom-plan-3 {
  0%,  81%  { visibility: hidden;  opacity: 0; transform: scale(0.9); }
  90%, 100% { visibility: visible; opacity: 1; transform: scale(1); }
}

/* Plus de volet sur la photographie : l'ouverture du terrain est portée par les
   images-clés du plan, qui montent l'échelle de 0,86 à 1 sur un cinquième du
   chapitre. Deux mécanismes pour la même ouverture se contrariaient, et celui-ci
   dépendait d'une variable qui n'existe plus. */

/* Le voile du plein cadre. Sur une vignette de 46 rem, le dégradé d'origine
   suffisait ; sur une image qui tient tout l'écran, le titre se posait au
   milieu de la photographie et s'y perdait. Le voile monte donc plus haut et
   part de plus opaque — c'est le seul moyen de garder le contraste du texte
   sans toucher à l'image. */
.dom[data-live] .domain::after {
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--bg) 97%, transparent) 2%,
    color-mix(in srgb, var(--bg) 88%, transparent) 34%,
    color-mix(in srgb, var(--bg) 52%, transparent) 66%,
    color-mix(in srgb, var(--bg) 26%, transparent)
  );
}

/* À l'échelle du plein cadre, le titre devient un vrai titre de plan et la
   description une légende large. */
.dom[data-live] .domain h3 {
  font-size: clamp(1.6rem, 3.4vw, 3.2rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  max-width: 34rem;
}

.dom[data-live] .domain .d-d {
  font-size: clamp(1rem, 0.6vw + 0.9rem, 1.3rem);
  line-height: 1.6;
  max-width: 42rem;
}

/* La description est ouverte en permanence sur le terrain : dans la scène il n'y
   a rien à survoler pour lire, et un terrain hors cadre n'est de toute façon plus
   rendu. Le titre, la légende et le filet suivent donc la présence du plan sans
   la recalculer — c'est le plan qui la porte, une fois pour tous. */
.dom[data-live] .domain .d-d {
  /* La boîte est constante et la description est là sur les trois vignettes :
     seule son opacité change. Tirer le `max-height` du curseur remettait les
     trois vignettes en page à chaque image du défilement, pour un résultat que
     l'opacité donne à elle seule — et la hauteur de la vignette est de toute
     façon fixée au-dessus, donc rien ne dépend de ce dépliement. Le titre reste
     ainsi rigoureusement à la même place d'une vignette à l'autre. */
  max-height: 9rem;
  opacity: 1;
  transition: none;
}

.dom[data-live] .domain h3,
.dom[data-live] .domain .d-n { opacity: 1; }

/* Le filet en dégradé n'apparaît que sur celle qui est en place — c'est le même
   liseré que sur les fiches des prestations, dessiné par soustraction de deux
   masques. */
.dom[data-live] .domain::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  padding: 1px;
  border-radius: inherit;
  background: var(--grad);
  opacity: 1;
  pointer-events: none;
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
          mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}

/* Le focus clavier ramène la vignette au premier plan et ouvre sa description :
   rien de focalisable ne reste illisible. */
html.js .dom[data-live] .domain:focus-within {
  z-index: 40;
  transform: none;
  opacity: 1;
}

.dom[data-live] .domain:focus-within .d-d { max-height: 9rem; opacity: 1; transform: none; }

/* --- Les trois jalons posés sur le fil ------------------------------------- */
.dom-rail { display: none; }

.dom[data-live] .dom-rail {
  display: block;
  position: absolute;
  inset: 0 auto;
  left: calc(var(--gut) / -2);
  width: 0;
}

.dom[data-live] .dom-rail i {
  /* Même décomposition que les vignettes : un jalon ne connaît que son écart au
     premier plan, et l'écart se lit en deux moitiés plutôt qu'en valeur absolue. */
  --d: calc(var(--i) - var(--t));
  --fut: clamp(0, var(--d), 1);
  --pst: clamp(0, calc(var(--d) * -1), 1);
  --off: calc(var(--fut) + var(--pst));

  position: absolute;
  /* Les trois jalons se répartissent sur la hauteur de l'étage : le premier au
     tiers haut, le dernier au tiers bas. */
  top: calc(34% + var(--i) * 16%);
  width: 0.5rem;
  height: 0.5rem;
  margin: -0.25rem 0 0 -0.25rem;
  border-radius: 50%;
  border: 1.5px solid var(--border-strong);
  background: var(--bg-3);
  opacity: var(--entry);
  transform: scale(calc(0.8 + 0.5 * (1 - var(--off))));
  box-shadow: 0 0 calc((1 - var(--off)) * 18px) rgb(90 169 255 / 55%);
}

.dom[data-live] .dom-rail i::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--grad);
  opacity: calc(1 - var(--off));
}

/* Registre clair. Deux besoins propres, comme pour la scène des prestations :

     — une ombre portée, sinon une vignette claire sur fond clair ne se détache
       pas de celle qui est derrière ;
     — un voile bas franchement plus opaque. Dans la scène, la description de la
       vignette en place est **ouverte en permanence**, alors qu'au repos elle ne
       s'ouvre qu'au survol : le texte sombre du registre clair repose donc sur
       la photographie pendant tout le chapitre, et le voile d'origine ne suffit
       plus à le porter. */
:root[data-theme="light"] .dom[data-live] .domain {
  box-shadow: 0 34px 72px -46px rgb(9 22 48 / 45%);
}

:root[data-theme="light"] .dom[data-live] .domain::after {
  background: linear-gradient(
    to top,
    color-mix(in srgb, var(--bg-2) 96%, transparent) 0 32%,
    color-mix(in srgb, var(--bg) 74%, transparent) 54%,
    color-mix(in srgb, var(--bg) 18%, transparent)
  );
}

/* Fenêtre étroite : les vignettes reprennent la grille d'origine et le survol
   redevient le moyen de lire la description. */
@media (max-width: 900px) {
  .dom[data-live] .dom-rail { display: none; }
}

/* ===================================================== 13. Prestations
   ----------------------------------------------------------------------------
   Une scène jouée au défilement. La section occupe un rail de plusieurs
   hauteurs de fenêtre ; l'étage reste collé en haut, et les trois fiches
   passent devant une **tête de lecture fixe au milieu de l'écran** — comme
   une pellicule devant un projecteur. Rien ne quitte l'image : la colonne,
   la tête, son faisceau, le compteur géant et les trois fiches restent là
   du premier au dernier écran. Le lien visuel n'est jamais rompu, il est
   posé par la géométrie elle-même : la fiche en place est *toujours* au
   bout du faisceau, parce que les deux sont au centre.

   Tout descend d'un seul curseur, `--p` (0 → 1 sur la section), écrit par
   le script. Les trois temps s'en déduisent par calcul :

     --entry  0 → 1   l'accroche se retire, la pellicule monte en place
     --t      0 → 4   position continue de la tête sur la pellicule
     --cv     0 → 1   convergence : les trois se rassemblent en éventail

   Chaque fiche et chaque jalon ne connaissent que leur écart à la tête
   (`--d`), d'où se déduisent décalage, profondeur et clarté. Une seule
   source, aucune synchronisation à tenir à la main. `--live` (= 1 − --cv)
   désarme d'un coup toute la profondeur au moment de la convergence.

   Sans l'attribut `data-live` — script absent (vignettes du panneau Design
   System) ou mouvement réduit — rien de tout ceci ne s'applique : la
   section reste la liste numérotée d'origine, entièrement lisible.
   ========================================================================== */

/* --- État de repos : la liste numérotée, sans script ---------------------- */
.prs-figure { margin-top: 2.5rem; }
.prs-deck { display: grid; gap: 1rem; }
.prs-spine, .prs-odo, .prs-cue { display: none; }

.prs-card {
  position: relative;
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) auto;
  grid-template-areas: "n t go" "n b go";
  gap: 0.3rem clamp(1rem, 2.5vw, 2.5rem);
  align-items: start;
  padding: clamp(1.25rem, 2.5vw, 2rem);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  transition: border-color 0.45s var(--ease), background-color 0.45s var(--ease);
}

.prs-card:hover { border-color: var(--border-strong); background: var(--surface-2); }

.prs-n {
  grid-area: n;
  margin: 0;
  font-size: clamp(1.6rem, 3vw, 2.35rem);
  font-weight: 600;
  line-height: 0.9;
  letter-spacing: -0.05em;
  font-variant-numeric: tabular-nums;
  background: var(--grad);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

.prs-t {
  grid-area: t;
  margin: 0 0 0.3rem;
  font-size: clamp(1.0625rem, 1.4vw, 1.375rem);
  font-weight: 600;
  letter-spacing: -0.025em;
}

.prs-b {
  grid-area: b;
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--text-2);
}

.prs-go {
  grid-area: go;
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.6rem 1rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-2);
  white-space: nowrap;
  transition: border-color 0.4s var(--ease), background-color 0.4s var(--ease),
    color 0.4s var(--ease);
}

.prs-go:hover { border-color: var(--blue); background: var(--blue); color: #fff; }

.prs-side {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.75rem;
  margin-top: 2rem;
}

.prs-echo {
  margin: 0;
  font-size: clamp(1.05rem, 1.5vw, 1.4rem);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.03em;
}

.prs-echo span { color: var(--muted); }

@media (max-width: 760px) {
  .prs-card { grid-template-columns: 2.75rem minmax(0, 1fr); grid-template-areas: "n t" "n b" "go go"; }
  .prs-go { justify-self: start; margin-top: 0.75rem; }
}

/* --- Scène : rail, étage collé, curseur unique --------------------------- */
.prs[data-live] {
  --p: 0;              /* posé par le script : avancement dans la section */
  --v: 0;              /* vitesse du défilement, 0 → 1 : le filé du compteur */
  --t0: 0.16;          /* --p où la première fiche se cale sur la tête */
  --dt: 0.175;         /* --p consommé par fiche — lu aussi par le script */
  --gap: clamp(18rem, 40vh, 26rem);          /* écart des fiches sur la pellicule */
  --gutter: clamp(1rem, 2.2vw, 2.25rem);
  --spinew: 2.5rem;
  --beamw: calc(var(--spinew) / 2 + var(--gutter));
  /* La ligne de lecture : à cette distance du haut des fiches, donc sur le
     titre. C'est le seul axe horizontal de la scène — tête, faisceau, titre
     et compteur s'y alignent, et il ne bouge jamais. Les fiches gardent
     ainsi leur hauteur naturelle : aucune n'est étirée à la plus longue. */
  --hdr: 3.8rem;

  --entry: clamp(0, calc((var(--p) - 0.035) / 0.105), 1);
  --t: clamp(0, calc((var(--p) - var(--t0)) / var(--dt)), 4);
  --cv: clamp(0, calc((var(--p) - 0.685) / 0.175), 1);
  --live: calc(1 - var(--cv));

  /* --- Les deux bouts du monde -------------------------------------------
     De la couture 5 à la couture 6, et c'est le **seul chapitre qui passe du
     côté froid** : le cran de monde descend sous zéro (−1), soit 18° de teinte
     vers le cyan. Une salle de projection n'a pas la couleur d'un bureau. */
  --env-t0: var(--mo-5);   --env-g0: var(--mg-5);
  --env-t1: var(--mo-6);   --env-g1: var(--mg-6);

  --rail: 3.8;
  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;

  /* --- Couture avec les chapitres voisins ---------------------------------
     Le fil conducteur (section 19) court dans la gouttière du texte. Ici il
     vient se poser **sur l'axe de la colonne** de la pellicule : le fil devient
     la colonne, puis la quitte. L'écart à franchir est la demi-colonne plus la
     demi-gouttière, et il est repris du calcul, jamais réglé à l'œil.

     Ce chapitre avait sa propre fenêtre de glissement, réglée à la main. Elle
     est remplacée par le mécanisme partagé : c'est le même geste, mais mesuré en
     demi-fenêtre de rail comme aux sept autres coutures, et l'aura y devient un
     écart au tronc commun plutôt qu'une valeur absolue. Une variante de moins à
     entretenir. */
  --fil-champ: var(--env-relais);
  --fil-vers: calc(var(--spinew) / 2 + var(--gut) / 2);
  /* La colonne porte déjà son propre halo : celui du fil ferait double emploi,
     donc l'aura **diminue** dans le champ au lieu de croître. */
  --fil-lueur: -0.55;
}

/* --- Réglages de l'environnement des prestations ---------------------------
   « La scène sombre de la pellicule. » C'est le chapitre le plus bas en lumière
   du site — un tiers de la valeur du raccord —, et c'est un réglage, pas un
   décor de plus : la même matière que partout, à l'éclairage d'une salle de
   projection. La trame est presque éteinte pour la même raison. Le voile des
   deux bords (`.prs-stage::after`) tient toujours l'entrée et la sortie de la
   pellicule ; il a désormais une matière derrière lui au lieu du vide. */
.prs[data-live] .env {
  --env-lum: 0.28;
  --env-champ-o: 0.24;
}

.prs[data-live] .prs-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

/* Voile aux deux bords : la pellicule entre et sort dans le noir plutôt que
   d'être tranchée net par le bord de la fenêtre. */
.prs[data-live] .prs-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  background:
    linear-gradient(180deg, var(--bg), transparent 17%),
    linear-gradient(0deg, var(--bg), transparent 10%);
  opacity: 0.9;
}

.prs[data-live] .prs-inner {
  position: relative;
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  padding-block: 4.6rem clamp(1.5rem, 5vh, 3.5rem);
}

.prs[data-live] .prs-kicker {
  position: relative;
  z-index: 45;
  align-self: start;
  margin: 0;
}

/* L'accroche et la pellicule partagent la même case : l'une se retire au
   moment où l'autre monte, sans jamais se disputer la place. */
.prs[data-live] .prs-intro,
.prs[data-live] .prs-figure { grid-row: 2; grid-column: 1; }

.prs[data-live] .prs-intro {
  max-width: 52rem;
  pointer-events: none;
  transform: translateY(calc(var(--entry) * -3.5rem)) scale(calc(1 - 0.05 * var(--entry)));
  transform-origin: 0 50%;
  opacity: calc(1 - var(--entry));
  filter: blur(calc(var(--entry) * 5px));
}

.prs[data-live] .prs-figure {
  display: grid;
  grid-template-columns: var(--spinew) minmax(0, 1fr) minmax(0, 15rem);
  gap: var(--gutter);
  align-items: start;
  margin: 0;
  opacity: var(--entry);
  transform: translateY(calc((1 - var(--entry)) * 7vh));
}

/* --- Écart à la tête : la seule chose que fiches et jalons ont à savoir --- */
.prs[data-live] .prs-card,
.prs[data-live] .prs-node {
  --d: calc(var(--i) - var(--t));            /* < 0 déjà passé · > 0 à venir */
  --fut: clamp(0, var(--d), 1);
  --pst: clamp(0, calc(var(--d) * -1), 1);
  --off: calc(var(--fut) + var(--pst));      /* 0 en place … 1 au loin */
  --foc: clamp(0, calc(4 * (1 - var(--off)) - 2), 1);
  --y: calc(var(--d) * var(--gap) * var(--live));
}

/* --- La colonne, ses jalons, et la tête de lecture ---------------------- */
.prs[data-live] .prs-spine {
  display: block;
  position: relative;
  align-self: stretch;
  grid-row: 1;
}

.prs-shaft {
  position: absolute;
  left: 50%;
  top: var(--hdr);
  width: 2px;
  height: calc(2 * var(--gap) + 40vh);
  margin-left: -1px;
  border-radius: 2px;
  overflow: hidden;
  background: linear-gradient(180deg,
    transparent, rgb(255 255 255 / 10%) 10%, rgb(255 255 255 / 20%) 50%,
    rgb(255 255 255 / 10%) 90%, transparent);
  transform:
    translateY(-50%)
    translateY(calc((1 - var(--t)) * var(--gap) * var(--live)))
    scaleY(calc(1 - 0.88 * var(--cv)));
}

:root[data-theme="light"] .prs-shaft {
  background: linear-gradient(180deg,
    transparent, rgb(9 22 48 / 10%) 10%, rgb(9 22 48 / 22%) 50%,
    rgb(9 22 48 / 10%) 90%, transparent);
}

/* Une impulsion descend le rail en continu : la scène respire même à l'arrêt. */
.prs-pulse {
  position: absolute;
  inset: 0 0 auto;
  height: 14%;
  background: linear-gradient(180deg, transparent, var(--blue-bright), transparent);
  animation: prs-pulse 3.8s linear infinite;
}

@keyframes prs-pulse {
  0%        { transform: translateY(-120%); opacity: 0; }
  10%, 90%  { opacity: 0.85; }
  100%      { transform: translateY(740%); opacity: 0; }
}

.prs-node {
  position: absolute;
  left: 50%;
  top: var(--hdr);
  width: 0.6rem;
  height: 0.6rem;
  margin: -0.3rem 0 0 -0.3rem;
  border: 1.5px solid var(--border-strong);
  border-radius: 50%;
  background: var(--bg-3);
  transform: translateY(var(--y)) scale(calc(0.82 + 0.3 * var(--foc)));
  box-shadow: 0 0 calc(var(--foc) * 18px) rgb(90 169 255 / 55%);
}

.prs-node::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--grad);
  opacity: var(--foc);
}

/* La tête de lecture : immobile au centre, c'est le point de rendez-vous. */
.prs-read {
  position: absolute;
  left: 50%;
  top: var(--hdr);
  width: 1.7rem;
  height: 1.7rem;
  margin: -0.85rem 0 0 -0.85rem;
  border-radius: 50%;
  opacity: var(--entry);
}

.prs-read::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
}

.prs-read::after {
  content: "";
  position: absolute;
  inset: -70%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(90 169 255 / 22%), transparent 72%);
}

/* Le faisceau : de la tête au flanc de la fiche. Il ne bouge jamais — c'est
   la pellicule qui vient s'y présenter. */
.prs-beam {
  position: absolute;
  left: 50%;
  top: 50%;
  width: var(--beamw);
  height: 1px;
  background: linear-gradient(90deg, var(--blue-bright), rgb(90 169 255 / 12%));
  transform-origin: left;
  transform: scaleX(var(--entry));
}

.prs-beam span {
  position: absolute;
  top: -1px;
  left: 0;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 7px 1px rgb(90 169 255 / 90%);
  animation: prs-spark 2.1s linear infinite;
}

.prs-beam span:nth-child(2) { animation-delay: 1.05s; }

/* Le point d'accostage, au bout du faisceau : c'est là que chaque fiche
   vient se présenter. */
.prs-beam::after {
  content: "";
  position: absolute;
  top: -3px;
  right: -3px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--blue-bright);
  box-shadow: 0 0 12px 2px rgb(90 169 255 / 70%);
}

@keyframes prs-spark {
  0%   { transform: translateX(0); opacity: 0; }
  18%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateX(var(--beamw)); opacity: 0; }
}

/* --- La pellicule : les trois fiches dans une seule case ---------------- */
.prs[data-live] .prs-deck {
  grid-row: 1;
  gap: 0;
  perspective: 1500px;
}

.prs[data-live] .prs-card {
  grid-area: 1 / 1;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: clamp(1.4rem, 2.6vw, 2.25rem);
  padding-top: 2.7rem;
  border-color: transparent;
  background: var(--bg-3);
  box-shadow: 0 40px 90px -50px rgb(0 0 0 / 85%);
  transition: none;
  z-index: calc(24 - 8 * var(--off) + var(--i));
  transform:
    translate3d(
      calc(var(--fan) * 1.15rem * var(--cv)),
      calc(var(--y) + var(--fan) * 5rem * var(--cv)),
      calc(var(--off) * -170px * var(--live)))
    rotateX(calc(var(--d) * 5.5deg * var(--live)))
    rotate(calc(var(--fan) * -1.3deg * var(--cv)))
    scale(calc(1 - 0.08 * var(--off) * var(--live) - 0.07 * var(--cv)));
  opacity: calc(1 - 0.62 * var(--off) * var(--live));
}

/* Chaque fiche porte son propre accent : l'ambiance de l'étage change avec
   celle qui est en place. */
.prs[data-live] .prs-card:nth-child(1) { --ac: 47 128 255; --fan: -1; }
.prs[data-live] .prs-card:nth-child(2) { --ac: 155 147 255; --fan: 0; }
.prs[data-live] .prs-card:nth-child(3) { --ac: 71 214 229; --fan: 1; }

/* Halo débordant, allumé seulement quand la fiche est au faisceau. */
.prs[data-live] .prs-card::before {
  content: "";
  position: absolute;
  inset: -22% -16%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(var(--ac) / 30%), transparent 72%);
  filter: blur(24px);
  opacity: var(--foc);
  pointer-events: none;
}

/* Filet en dégradé sur le pourtour — un liseré d'un pixel, dessiné par
   soustraction de deux masques. */
.prs[data-live] .prs-card::after {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  background: var(--grad);
  opacity: calc(0.28 + 0.72 * var(--foc));
  pointer-events: none;
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
          mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
}

.prs[data-live] .prs-n {
  position: absolute;
  top: clamp(1.1rem, 2vw, 1.5rem);
  left: clamp(1.4rem, 2.6vw, 2.25rem);
  font-size: clamp(1.25rem, 1.8vw, 1.6rem);
  opacity: calc(0.18 + 0.82 * var(--cv));
}

.prs[data-live] .prs-t {
  margin: 0;
  font-size: clamp(1.25rem, 1vw + 0.9rem, 1.8rem);
  opacity: 0.5;
  transform: translateY(0.4rem);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

.prs[data-live] .prs-card[data-focus] .prs-t { opacity: 1; transform: none; }

.prs[data-live] .prs-b {
  max-width: 40rem;
  font-size: clamp(0.9375rem, 0.35vw + 0.85rem, 1.0625rem);
  line-height: 1.6;
  overflow: hidden;
  /* Boîte constante. Tirée du curseur, cette hauteur remettait les trois corps
     de fiche en page à **chaque image** de tout le chapitre, alors qu'elle ne
     sert qu'à une chose : replier les paragraphes au moment où les trois fiches
     se rassemblent en éventail. Or ce moment est un seuil, pas un continuum, et
     le script pose déjà l'attribut qui le désigne. Une seule mise en page au
     passage, au lieu de soixante par seconde. */
  max-height: 60rem;
  opacity: calc(var(--foc) * var(--live));
  /* Le repli est un seuil, donc il se détend de lui-même : une transition sur
     la seule hauteur, jouée une fois au passage, plutôt qu'un recalcul continu.
     C'est le seul endroit de la scène où une détente est souhaitable — ailleurs
     la position vient du doigt et traînerait derrière lui. */
  transition: max-height 0.55s var(--ease);
}

.prs[data-live][data-end] .prs-b { max-height: 0; }

/* Hors du faisceau, le lien ne doit être ni cliquable ni atteignable au
   clavier : `visibility` l'ôte de l'ordre de tabulation, et son retrait
   attend la fin du fondu. */
.prs[data-live] .prs-go {
  align-self: start;
  margin-top: 0.35rem;
  visibility: hidden;
  opacity: 0;
  transform: translateY(0.6rem);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease),
    border-color 0.4s var(--ease), background-color 0.4s var(--ease),
    color 0.4s var(--ease), visibility 0s linear 0.45s;
}

.prs[data-live] .prs-card[data-focus] .prs-go {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition-delay: 0s;
}

/* Registre clair : la fiche devient blanche et c'est son ombre qui la
   détache — un gris sur un gris ne se lit pas. */
:root[data-theme="light"] .prs[data-live] .prs-card {
  background: var(--bg-2);
  box-shadow: 0 34px 72px -46px rgb(9 22 48 / 42%);
}

:root[data-theme="light"] .prs[data-live] .prs-stage::after { opacity: 0.72; }

/* --- Colonne de droite : le compteur, puis la conclusion --------------- */
.prs[data-live] .prs-side {
  display: grid;
  grid-row: 1;
  align-self: start;
  gap: 0.9rem;
  justify-items: start;
  margin: 0;
}

.prs[data-live] .prs-odo {
  --cell: 1.08em;
  display: block;
  height: var(--cell);
  margin: 0;
  overflow: hidden;
  font-size: clamp(3.2rem, 6.6vw, 6rem);
  font-weight: 600;
  line-height: var(--cell);
  letter-spacing: -0.055em;
  font-variant-numeric: tabular-nums;
  opacity: var(--entry);
  /* Le milieu du chiffre tombe sur la ligne de lecture. */
  margin-top: calc(var(--hdr) - var(--cell) / 2);
}

/* Le rouleau : 01 → 02 → 03, puis l'esperluette du logo. Il avance d'un cran
   entier — le script pose `--n` au passage de la tête — sinon un demi-chiffre
   traînerait dans la fenêtre pendant tout le temps. Le filé, lui, suit la
   vitesse du doigt : `--v`. */
.prs-reel {
  display: block;
  transform: translateY(calc(var(--n, 0) * var(--cell) * -1));
  filter: blur(calc(var(--v, 0) * 18px));
  transition: transform 0.75s var(--ease), filter 0.4s var(--ease);
}

.prs-reel i {
  display: block;
  height: var(--cell);
  font-style: normal;
  background: var(--grad);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

.prs-amp { font-family: var(--font-mark); font-weight: 400; letter-spacing: 0; }

.prs[data-live] .prs-payoff {
  visibility: hidden;
  opacity: var(--cv);
  transform: translateY(calc((1 - var(--cv)) * 1.75rem));
}

.prs[data-live][data-end] .prs-payoff { visibility: visible; }

.prs[data-live] .prs-echo { margin: 0 0 1.15rem; }
.prs[data-live] .prs-echo span { display: block; }

/* --- L'indice de défilement, le temps de comprendre ce qui se passe ----- */
.prs[data-live] .prs-cue {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  position: absolute;
  left: 50%;
  bottom: 1.25rem;
  z-index: 41;
  margin: 0;
  transform: translateX(-50%);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--faint);
  opacity: calc(1 - var(--entry));
  pointer-events: none;
}

.prs-cue i {
  display: block;
  position: relative;
  width: 1px;
  height: 2.25rem;
  overflow: hidden;
  background: linear-gradient(var(--border-strong), transparent);
}

.prs-cue i::after {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 40%;
  background: var(--blue-bright);
  animation: cue 2.6s var(--ease) infinite;
}

/* --- Fenêtre étroite : la colonne s'amincit, le compteur passe au-dessus - */
@media (max-width: 900px) {
  .prs[data-live] {
    --gap: clamp(14rem, 32vh, 20rem);
    --spinew: 1.1rem;
  }

  .prs[data-live] .prs-figure { grid-template-columns: var(--spinew) minmax(0, 1fr); }

  .prs[data-live] .prs-odo {
    position: absolute;
    right: 0;
    bottom: calc(100% + 0.5rem);
    font-size: clamp(2.4rem, 11vw, 3.6rem);
  }

  .prs[data-live] .prs-side {
    grid-row: 2;
    grid-column: 2;
    align-self: start;
    position: relative;
    margin-top: 1.1rem;
  }

  .prs[data-live] { --hdr: 3.3rem; }
  .prs[data-live] .prs-card { padding-top: 2.4rem; }
}

/* ======================================================= 14. Témoignage */
.quote {
  position: relative;
  padding: clamp(2rem, 5vw, 4rem);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background:
    radial-gradient(ellipse 60% 100% at 82% 0%, rgb(155 147 255 / 12%), transparent 60%),
    var(--surface);
  box-shadow: var(--glow);
  overflow: hidden;
}

.quote-mark {
  position: absolute;
  top: -1.5rem; right: 1.5rem;
  font-family: Georgia, "Times New Roman", serif;
  font-size: 14rem;
  line-height: 1;
  color: var(--blue);
  opacity: 0.11;
  pointer-events: none;
  user-select: none;
}

.quote blockquote {
  margin: 0 0 1.75rem;
  max-width: 48rem;
  font-size: clamp(1.0625rem, 1.5vw + 0.7rem, 1.6rem);
  font-weight: 500;
  line-height: 1.42;
  letter-spacing: -0.024em;
  text-wrap: pretty;
}

/* Les mots se lèvent un à un quand la carte entre dans le champ. */
.quote .w { display: inline-block; }

html.js .quote .w {
  opacity: 0;
  transform: translateY(0.5em);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  transition-delay: calc(var(--w) * 26ms);
}

html.js .quote[data-in] .w { opacity: 1; transform: none; }

.quote figcaption { display: flex; align-items: center; gap: 0.9rem; }

.q-badge {
  display: grid;
  place-items: center;
  width: 2.6rem; height: 2.6rem;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-2);
  flex: none;
}

.q-who b { display: block; font-size: 0.9375rem; font-weight: 600; }
.q-who span { font-size: 0.8125rem; color: var(--muted); }

/* -------- Chapitre 7 : la citation se lève mot à mot -----------------------
   Les mots ne se lèvent plus d'un coup à l'entrée dans le champ : ils montent
   au rythme du doigt. Le script découpe déjà la citation et pose sur chaque mot
   son rang (`--w`) ; il ne reste qu'à comparer ce rang à l'avancement.

   `--nw` est le nombre de mots de la citation. Il est écrit ici et non déduit :
   rien en CSS ne sait compter les enfants d'un élément. La formule est faite
   pour le supporter — si la valeur était trop basse, les derniers mots
   finiraient simplement de se lever un peu plus tôt, et rien ne resterait
   caché. La citation du client ne changeant pas, la valeur ne bougera pas.

   `html.js` en tête du sélecteur : sans lui, la cascade d'entrée
   (`html.js .quote[data-in] .w`) reprendrait la main sur l'opacité et les mots
   se lèveraient tous ensemble. */
.tem[data-live] {
  --p: 0;
  --nw: 52;
  /* Le lever occupe la première moitié du chapitre ; la seconde laisse la
     citation entière sous les yeux, le temps de la lire. */
  --u: clamp(0, calc((var(--p) - 0.10) / 0.52), 1);

  /* --- Les deux bouts du monde -------------------------------------------
     De la couture 6 à la couture 7 : la pellicule laisse le monde au cyan, la
     parole le **ramène au bleu** (+1,3 cran). Le monde suit ici le lever de la
     citation — c'est elle le mouvement du chapitre. */
  --env-t0: var(--mo-6);   --env-g0: var(--mg-6);
  --env-t1: var(--mo-7);   --env-g1: var(--mg-7);
  --env-w: var(--u);

  --rail: 2.15;
  height: calc(var(--rail) * 100vh);
  height: calc(var(--rail) * 100svh);
  padding-block: 0;
}

/* --- Réglages de l'environnement du témoignage -----------------------------
   Le monde du témoignage est celui du **texte**, et une trame de points n'en dit
   rien. La trame commune reste — c'est elle qui raccorde aux voisins — et une
   **grille de lignes de base** s'y ajoute au cœur du chapitre : l'espace du
   texte avant le texte.

   Elle est portée par `--env-relais`, donc absente aux deux coutures : c'est la
   même règle que la trame croisée de la chaîne de valeur. Un motif propre à un
   chapitre ne doit jamais se trouver au raccord, sinon il y disparaît d'un coup.

   L'interligne suit `--env-grain`, le pas de trame chaîné : la grille se resserre
   et se desserre avec le monde, elle n'a pas de mesure à elle. */
.tem[data-live] .env { --env-champ-o: 0.46; }

.tem[data-live] .env-champ::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(180deg,
    var(--border) 0 1px, transparent 1px var(--env-grain));
  opacity: calc(0.5 * var(--env-relais));
}

.tem[data-live] .tem-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
}

.tem[data-live] .tem-inner {
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  align-items: center;
  padding-block: 4.6rem clamp(1.5rem, 5vh, 3.5rem);
}

.tem[data-live] .tem-kicker { align-self: start; margin: 0; }

.tem[data-live] .quote { align-self: center; }

html.js .tem[data-live] .quote .w {
  /* Rang du mot ramené à l'avancement. Le « + 10 » réserve une marge de fin :
     le dernier mot est levé avant que `--u` n'atteigne 1. La division par 3
     étale le lever de chaque mot sur trois rangs, sinon il claque. */
  --wp: calc((var(--u) * (var(--nw) + 10) - var(--w)) / 3);

  opacity: clamp(0.07, var(--wp), 1);
  transform: translateY(calc((1 - clamp(0, var(--wp), 1)) * 0.55em));
  transition: none;
}

/* La signature se pose quand la phrase est entière : elle ne devance pas la
   parole. `visibility` la retire de l'ordre de tabulation jusque-là — il n'y a
   pas de lien dedans aujourd'hui, mais la règle ne dépend pas de ce détail. */
.tem[data-live] .quote figcaption {
  animation: tem-sign 1s linear paused both;
  animation-delay: calc(var(--u) * -1s);
}

@keyframes tem-sign {
  0%, 78%   { visibility: hidden;  opacity: 0; transform: translateY(0.8rem); }
  92%, 100% { visibility: visible; opacity: 1; transform: none; }
}

/* Le guillemet devient le décor du chapitre : **très grand, plein cadre**, posé
   derrière la citation. La citation se levait mot à mot dans le vide ; elle se
   lève maintenant dans un plan. Le guillemet se resserre à mesure que la phrase
   se forme — la caméra se rapproche de la parole — et il déborde du cadre, ce
   qui remplit l'image au lieu de la ponctuer d'un signe dans un coin. */
.tem[data-live] .quote-mark {
  position: absolute;
  inset: auto auto -14% -6%;
  z-index: 0;
  font-size: min(74svh, 40rem);
  line-height: 0.78;
  transform: scale(calc(1.16 - 0.16 * var(--u)));
  transform-origin: 0 100%;
  opacity: calc(0.07 + 0.06 * var(--u));
}

:root[data-theme="light"] .tem[data-live] .quote-mark { opacity: calc(0.1 + 0.07 * var(--u)); }

/* L'espace se referme sur la parole : le cadre de la citation se resserre
   pendant que les mots se lèvent. C'est une échelle, pas une mise en page. */
.tem[data-live] .quote {
  position: relative;
  z-index: 1;
  align-self: center;
  transform: scale(calc(0.965 + 0.035 * var(--u)));
  /* Le cadre de la citation s'efface au profit du plan : opaque, il masquait le
     guillemet géant et la citation se retrouvait posée **sur** un décor au lieu
     d'être **dans** un plan. Il reste un fond très léger et un filet, juste ce
     qu'il faut pour tenir le contraste du texte. */
  background: color-mix(in srgb, var(--bg) 42%, transparent);
  border-color: color-mix(in srgb, var(--border) 60%, transparent);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

:root[data-theme="light"] .tem[data-live] .quote {
  background: color-mix(in srgb, #fff 62%, transparent);
}

/* Un halo qui accompagne la montée de la phrase : le fond n'est jamais inerte. */
.tem[data-live] .tem-stage::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: radial-gradient(44rem 34rem at 46% 52%,
    color-mix(in srgb, var(--blue) 22%, transparent), transparent 72%);
  transform: scale(calc(0.9 + 0.2 * var(--u)));
  opacity: calc(0.35 + 0.65 * var(--u));
  pointer-events: none;
}

/* L'aura du fil s'élargit derrière la citation, comme si la parole en sortait —
   mais elle **rentre avant la jonction**. C'était la plus forte surcharge du
   site (aura ×2,4, tronc à 2 px) et elle était constante : le fil sautait donc
   d'épaisseur et de clarté aux deux bouts du chapitre. */
.tem[data-live] .fil {
  --fil-champ: var(--env-relais);
  --fil-gras: 1px;
  --fil-lueur: 1.4;
}

/* ========================================================= 15. Contact */
.contact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.95fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
}

@media (max-width: 880px) { .contact { grid-template-columns: 1fr; } }

.coords { display: grid; gap: 0.6rem; margin-top: 2rem; }

.coord {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.95rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: border-color 0.4s var(--ease), transform 0.4s var(--ease),
    background-color 0.4s var(--ease);
}

.coord:hover { border-color: var(--border-strong); transform: translateX(4px); background: var(--surface-2); }

.coord .c-i {
  display: grid;
  place-items: center;
  width: 2.15rem; height: 2.15rem;
  border-radius: 0.6rem;
  background: color-mix(in srgb, var(--blue) 16%, transparent);
  color: var(--blue-bright);
  flex: none;
}

.coord .c-i svg { width: 1rem; height: 1rem; }

.coord dt {
  font-size: 0.5938rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

.coord dd { margin: 0.1rem 0 0; font-size: 0.9375rem; font-weight: 500; }

/* --- Formulaire --- */
.form {
  padding: clamp(1.35rem, 3vw, 2.25rem);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--glow);
}

.field { position: relative; margin-bottom: 1rem; }

.field label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.field label .req { color: var(--blue-bright); }

.field input, .field textarea {
  width: 100%;
  padding: 0.8rem 0.95rem;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--bg-3) 60%, transparent);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9375rem;
  line-height: 1.5;
  transition: border-color 0.3s var(--ease), background-color 0.3s var(--ease),
    box-shadow 0.3s var(--ease);
}

:root[data-theme="light"] .field input,
:root[data-theme="light"] .field textarea { background: var(--bg-3); }

.field textarea { min-height: 7.5rem; resize: vertical; }

.field input::placeholder, .field textarea::placeholder { color: var(--faint); }

.field input:focus, .field textarea:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--ring);
}

.field input:user-invalid, .field textarea:user-invalid { border-color: var(--amber); }

.consent {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.7rem;
  margin: 1.25rem 0;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--muted);
}

.consent input { width: 1.1rem; height: 1.1rem; margin-top: 0.15rem; accent-color: var(--blue); }

.form-note {
  margin: 1rem 0 0;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--faint);
}

/* -------- Chapitre 8 : la résolution --------------------------------------
   Pas d'étage collé ici, et c'est délibéré : le formulaire ne doit jamais être
   pris dans une fenêtre qui le rogne, et un champ de saisie qui reçoit le focus
   pendant que la page est accrochée est une mauvaise idée. Le chapitre se joue
   donc en flux normal — il respire davantage, les coordonnées se posent l'une
   après l'autre, et le fil arrive au bout.

   Le curseur est l'avancement de la **section** : la valeur neutre 0,5 place les
   trois coordonnées en place, si bien que sans script rien n'est caché. Et c'est
   `translate` — non `transform` — qui porte l'arrivée, pour laisser au survol
   de la coordonnée sa propre translation. */
/* -------- Chapitre 8 : la résolution ---------------------------------------
   Le contact n'a **pas** d'étage collé, et c'est un choix tenu : il abrite un
   formulaire, et un champ de saisie qui prend le focus dans une fenêtre
   accrochée se dérobe sous le doigt. Le chapitre reste donc en flux — mais il
   cesse d'être un bloc qui monte : il se **rassemble**.

   Le curseur est `--q`, l'avancement de la section, et non `--p` : sans étage
   collé, `--p` ne commence à courir qu'une fois la section déjà plein cadre, et
   l'arrivée serait passée. */
.ctc[data-live] {
  --p: 0;
  /* La convergence : 0 quand la section affleure, 1 quand elle est en place. */
  --conv: clamp(0, calc((var(--q, 0.5) - 0.06) / 0.30), 1);

  /* --- Les deux bouts du monde, et des coutures tirées de `--q` -----------
     Sans étage collé, `--p` reste nul : les curseurs de couture se tirent donc
     de `--q`, l'avancement de la section. Les bornes sont mesurées et non
     devinées — à 1280 × 800 la section fait 889 px pour une fenêtre de 800, si
     bien que `--q` vaut 0,474 quand son haut atteint le haut du cadre (la
     couture 7) et 0,526 quand son bas y affleure (le début de la couture 8). Le
     cœur du chapitre ne dure que quatre-vingt-neuf pixels, et c'est juste : le
     contact est court, c'est délibéré, et la résolution doit rester la matière
     commune plutôt qu'un monde de plus.

     Le cran de monde revient à zéro — le marine du héros. La boucle se ferme sur
     la teinte qui l'a ouverte. */
  --env-t0: var(--mo-7);   --env-g0: var(--mg-7);
  --env-t1: var(--mo-8);   --env-g1: var(--mg-8);
  --env-w: clamp(0, calc((var(--q, 0.5) - 0.474) / 0.526), 1);
  --env-rA: clamp(0, calc((var(--q, 0.5) - 0.474) / 0.12), 1);
  --env-rB: clamp(0, calc((var(--q, 0.5) - 0.80) / 0.20), 1);

  padding-block: clamp(6rem, 16vh, 12rem) clamp(5rem, 13vh, 10rem);

  /* C'est le seul chapitre sans étage collé, donc **le seul sans découpe** — les
     sept autres la tiennent de l'`overflow: hidden` de leur étage. Or son décor
     (`::before`, plus bas) est mis à l'échelle 1,5 avant son arrivée : un calque
     pleine largeur agrandi de moitié dépasse de 320 px à 1280, et la page se
     laissait alors **déplacer latéralement de 320 px**. Relevé à 1280 × 620 puis
     confirmé à 1280 × 800 ; `overflow-x: clip` sur le corps ne l'arrêtait pas.

     `clip` et non `hidden` : `hidden` ferait de la section un conteneur de
     défilement, ce qui n'a pas de sens ici et casserait l'ancrage. */
  overflow: clip;
}

/* --- Réglages de l'environnement du contact --------------------------------
   « La lumière se rassemble. » Elle se rassemble deux fois, et ce n'est pas une
   redite : le halo de l'environnement rentre dans le **fil**, comme aux sept
   autres coutures, tandis que le décor propre du chapitre (`::before`, plus bas)
   se referme vers le **centre du cadre**. Le premier geste appartient au récit
   du site, le second au chapitre.

   Le décor du contact **est la matière du raccord**, et rien d'autre : ni lumière
   ni trame propres. C'est le seul chapitre dans ce cas, et c'est ce qui ferme
   proprement la couture 8 — le pied, qui n'est pas un chapitre, porte la même
   matière au même pas et à la même force. Relevé à 1280 × 800 : teinte 0° des
   deux côtés, pas de trame 48 px des deux côtés, force de champ 0,40 des deux
   côtés. Un réglage propre au chapitre aurait rouvert la marche, puisque la page
   s'arrête avant que le relais ait pu redescendre — le contact fait 889 px pour
   une fenêtre de 800, et le pied qui le suit n'en fait que 318. */
.ctc[data-live] .env {
  --env-lum: var(--env-lum-r);
  --env-champ-o: var(--env-champ-r);
}

/* Le décor de la résolution : la lumière se rassemble au centre du cadre au lieu
   de s'étaler. C'est le seul chapitre où le décor se **referme** — le récit se
   boucle, et le fond le dit avant le texte. */
.ctc[data-live]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(50rem 40rem at 50% 46%,
    color-mix(in srgb, var(--blue) 26%, transparent), transparent 70%);
  transform: scale(calc(1.5 - 0.5 * var(--conv)));
  opacity: calc(0.25 + 0.75 * var(--conv));
}

.ctc[data-live] > .wrap { position: relative; z-index: 1; }

/* Le tronc portait ici un `scaleX(1 + 2 × (1 − --conv))` : il entrait donc dans
   le chapitre à **trois pixels** de large pour revenir à un, juste sous un
   témoignage resté à un pixel. C'était la troisième rupture d'épaisseur du fil,
   et la plus visible — la couture 7 est celle où la page ralentit.

   Le geste est rendu à l'**aura**, par le mécanisme partagé : elle s'élargit à
   mesure que la lumière se rassemble, et elle rentre avant les deux jonctions.
   Le tronc, lui, reste rigoureusement uniforme — c'est la doctrine du fil, et le
   resserrement final est l'affaire du pied, qui n'a aucun voisin en dessous. */
.ctc[data-live] .fil {
  /* `--env-relais` et non `--conv` : la convergence est achevée à `--q` = 0,36,
     donc **avant** la couture 7 (0,474), et l'aura y serait à pleine force au
     moment du raccord. Le relais, lui, est nul aux deux jonctions par
     construction. */
  --fil-champ: var(--env-relais);
  --fil-lueur: 0.8;
}

.ctc[data-live] .coords { margin-top: 2.75rem; gap: 0.75rem; }

/* Les coordonnées s'inscrivent l'une après l'autre par volets décalés. Elles
   glissaient d'un rem vers la gauche ; elles se découvrent maintenant sur
   place. Chaque coordonnée tient sur une ligne, la découpe ne traverse donc
   aucune ligne de texte. */
.ctc[data-live] .coord {
  --on: clamp(0, calc((var(--q, 0.5) - 0.12 - var(--k, 0) * 0.045) / 0.06), 1);
  opacity: var(--on);
  clip-path: inset(0 calc((1 - var(--on)) * 100%) 0 0);
}

.ctc[data-live] .coord:nth-child(2) { --k: 1; }
.ctc[data-live] .coord:nth-child(3) { --k: 2; }

/* =========================================================== 16. Pied */
.foot {
  /* `section { position: relative }` ne couvre pas le pied, qui est un
     `<footer>` : sans cela le segment de fil qu'il porte se référerait au bloc
     conteneur initial et couvrirait toute la page au lieu du seul pied. */
  position: relative;
  border-top: 1px solid var(--border);
  padding-block: clamp(2.5rem, 6vh, 4rem) 2rem;
  /* --- Couture 8 : le pied reçoit la matière de la jonction -----------------
     Le pied n'est **pas un chapitre** : ni rail, ni curseur, ni `data-live`, donc
     pas d'environnement. Or la huitième couture est une couture comme les sept
     autres, et le contact ne peut pas se terminer sur une arête.

     Il n'y a heureusement rien à métamorphoser ici : `--mo-8` valant zéro, le
     décor du contact arrive à la jonction **sans teinte tournée**, c'est-à-dire
     exactement au marine du haut de page. Le pied n'a donc qu'à porter la même
     matière : le fond opaque, et la trame au pas du raccord. Le fond valait
     60 % — les quarante restants laissaient passer la toile globale, et c'était
     la marche. La boucle est fermée : le décor du dernier écran est celui du
     premier.

     C'est **la moins exacte des huit coutures**, et il faut le dire : la trame du
     pied est plate là où celle d'un chapitre est en perspective, si bien que le
     pas se raccorde mais non la fuite. Le filet du haut du pied porte la
     jonction, et un filet à cet endroit se lit comme un pied de page, pas comme
     une arête. L'opacité de la trame est portée par la couleur des points plutôt
     que par une couche de plus : une seule couche n'a pas besoin d'enveloppe. */
  background:
    radial-gradient(circle,
      color-mix(in srgb, var(--border-strong) 40%, transparent) 1px, transparent 1.6px)
      0 0 / var(--mg-8) var(--mg-8),
    var(--bg-2);
}

.foot-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  margin-bottom: 2.5rem;
}

/* La marque au repos. Plus de cadre ni de coins arrondis : ce n'était utile
   qu'au JPEG, qui portait son propre fond. Le dessin vectoriel vit sur le fond
   de la page, dans les deux thèmes. */
.foot-logo { width: min(15rem, 62vw); }

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.75rem;
  font-size: 0.875rem;
  color: var(--muted);
}

.foot-links a { transition: color 0.3s var(--ease); }
.foot-links a:hover { color: var(--text); }

.foot-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
  font-size: 0.75rem;
  color: var(--faint);
}

/* Retour en haut */
.top {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 55;
  display: grid;
  place-items: center;
  width: 2.75rem; height: 2.75rem;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: color-mix(in srgb, var(--bg-2) 85%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--text-2);
  cursor: pointer;
  /* `pointer-events` écarte la souris, mais pas le tabulateur : sans
     `visibility`, ce bouton restait un arrêt de focus invisible — et comme
     `data-on` est posé par le script, l'arrêt était permanent quand le script
     ne tournait pas. Même motif que `.prs-go` : le retrait attend la fin du
     fondu, sinon le bouton disparaîtrait d'un coup au lieu de s'effacer. */
  visibility: hidden;
  opacity: 0;
  transform: translateY(0.8rem) scale(0.9);
  pointer-events: none;
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease),
    border-color 0.5s var(--ease), color 0.5s var(--ease),
    visibility 0s linear 0.5s;
}

.top[data-on] {
  visibility: visible;
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition-delay: 0s;
}
.top:hover { border-color: var(--blue); color: var(--blue-bright); transform: translateY(-2px); }

.sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ================================================== 17. Le présentateur
   ----------------------------------------------------------------------------
   Alain accompagne la lecture d'un bout à l'autre : en position fixe, jamais
   quitté des yeux, il change de côté et réagit à chaque section.

   Le matériau est un **portrait illustré** tiré de la photographie, et c'est
   ce qui rend le mouvement possible. Deux essais l'ont montré à leurs dépens :
   un portrait redessiné à la main ne ressemble pas assez, et une photographie
   découpée en morceaux qu'on fait pivoter donne un pantin de carton — un
   membre humain photographié ne tourne pas d'un bloc autour d'un point. Un
   **dessin**, si : c'est la règle de tout le dessin animé découpé. Et cette
   illustration montre les deux bras, là où la photographie s'arrêtait à
   mi-poitrine.

   Le montage tient en une règle : **chaque pixel n'appartient qu'à un seul
   calque**. Le premier essai empilait quatre copies entières et ne découpait
   que celles du dessus : dès qu'une partie tournait, la même partie restée
   immobile apparaissait en dessous — tête en double, bras fantôme. Les
   découpes sont donc **complémentaires**, et l'ordre est celui d'une
   marionnette de papier : la **tête** dessous (découpée au-dessus du col, cou
   compris), les **bras** au milieu (chacun avec la racine de son épaule), et
   le **torse par-dessus**, découpé du col vers le bas et privé des bras. C'est
   le torse qui masque le cou et les deux emmanchures : une rotation ne peut
   donc pas ouvrir de trou au joint, puisque le joint est dessous. Deux
   éléments sont dessinés en propre sur le visage : la bouche qui s'ouvre et
   les paupières qui tombent.

   Repères mesurés sur l'image (760 × 760) : tête centrée à 48 % / 24 %,
   articulation d'épaule à 28 % et 79 % de largeur pour 57 % de hauteur,
   bouche à 49 % / 35 %, yeux à 42 % et 54 % pour 25 %.

   Le script ne fait que deux choses : poser `data-pose` / `data-side` de la
   section courante et recopier son `data-say`. Le reste est ici.
   ========================================================================== */
.prez {
  position: fixed;
  z-index: 50;
  bottom: -2.75rem;
  left: clamp(0.25rem, 0.8vw, 1rem);
  width: clamp(6.5rem, 10vw, 11rem);
  pointer-events: none;

  --tt: 0deg;    /* l'appui du corps */
  --zo: 1;       /* le pas en avant quand il prend la parole */
  --bg: 0deg;    /* bras gauche a l'image */
  --bd: 0deg;    /* bras droit */
  --tg: 0deg;    /* inclinaison de la tete */

  transition: left 1.1s var(--ease), right 1.1s var(--ease);
  animation: prez-entree 1.2s var(--ease) 0.5s both;
}

/* Cote droit : il traverse la page quand la section y laisse la place — et
   l'image se retourne, pour qu'il fasse toujours face au contenu. */
.prez[data-side="right"] {
  left: auto;
  right: clamp(0.25rem, 1vw, 1.25rem);
}

.prez[data-side="right"] .prez-cadre { transform: scaleX(-1); }

@keyframes prez-entree {
  from { opacity: 0; transform: translateY(30%); }
  to   { opacity: 1; transform: none; }
}

.prez-cadre {
  position: relative;
  transform-origin: 50% 100%;
}

/* --- Les calques : meme image, quatre decoupes, quatre articulations ----- */
.prez-c {
  display: block;
  width: 100%;
  height: auto;
  /* Le bas s'efface : il emerge du bord de l'ecran plutot que d'y etre coupe
     net — et c'est aussi ce qui cache l'extremite des bras, tronquee par le
     cadre de l'illustration. C'est pourquoi leur rotation reste mesuree. */
  -webkit-mask-image: linear-gradient(180deg, #000 72%, transparent 96%);
          mask-image: linear-gradient(180deg, #000 72%, transparent 96%);
}

/* Le torse occupe le flux et donne sa taille au cadre ; les autres calques se
   superposent à lui. */
.prez-brasg, .prez-brasd, .prez-tete-g {
  position: absolute;
  inset: 0;
}

/* Le torse : du col vers le bas, **sans les bras**. C'est lui qui passe par
   -dessus et masque le cou et les deux emmanchures. */
.prez-torse {
  clip-path: polygon(31% 42%, 69% 42%, 71% 100%, 29% 100%);
  transform: rotate(var(--tt)) scale(var(--zo));
  transform-origin: 50% 96%;
  transition: transform 1.4s var(--ease);
  animation: prez-souffle 6.4s ease-in-out infinite;
}

/* Les bras, épaule comprise. Leur racine passe sous le torse : la rotation ne
   peut donc pas découvrir l'emmanchure. */
.prez-brasg {
  clip-path: polygon(0% 38%, 33% 43%, 31% 100%, 0% 100%);
  transform-origin: 27% 50%;
  transform: rotate(var(--bg));
  transition: transform 1.3s var(--ease);
  animation: prez-brasg 8.6s ease-in-out infinite;
}

.prez-brasd {
  clip-path: polygon(67% 43%, 100% 38%, 100% 100%, 69% 100%);
  transform-origin: 78% 50%;
  transform: rotate(var(--bd));
  transition: transform 1.3s var(--ease);
  animation: prez-brasd 9.8s ease-in-out infinite;
}

/* La tete et ce qui la suit : bouche et paupieres sont dans le meme groupe,
   sinon elles resteraient en arriere quand elle s'incline. */
.prez-tete-g {
  position: absolute;
  inset: 0;
  transform-origin: 48% 45%;
  transform: rotate(var(--tg));
  transition: transform 1.3s var(--ease);
  animation: prez-tangage 12.5s ease-in-out infinite;
}

/* La tête, cou compris : sa base est recouverte par le torse, donc son
   inclinaison ne laisse voir aucun raccord. */
.prez-tete {
  clip-path: polygon(24% 0%, 74% 0%, 74% 48%, 24% 48%);
}

/* --- La bouche : c'est elle qu'on attendait ------------------------------ */
/* Une ouverture sombre posee sous la moustache, fermee au repos (un simple
   trait), qui s'ouvre et se referme pendant qu'il parle. A l'echelle ou il est
   affiche, c'est le contraste qui se lit, pas la forme exacte des levres. */
.prez-bouche {
  position: absolute;
  left: 45.6%;
  top: 34.4%;
  width: 6.6%;
  height: 2.6%;
  border-radius: 46% 46% 50% 50%;
  background: radial-gradient(ellipse at 50% 32%, #4a2a21, #351d17 70%);
  box-shadow: inset 0 -0.12em 0.18em rgb(185 117 90 / 55%);
  transform-origin: 50% 18%;
  transform: scaleY(0.14);
  opacity: 0.92;
}

/* --- Les paupieres : le clignement -------------------------------------- */
.prez-cil {
  position: absolute;
  top: 22.6%;
  width: 4.6%;
  height: 2.5%;
  border-radius: 0 0 48% 48%;
  background: linear-gradient(180deg, #e6b591, #dca67f);
  transform-origin: 50% 0;
  transform: scaleY(0);
  animation: prez-clin 7.8s infinite;
}

.prez-cil-g { left: 39.7%; }
.prez-cil-d { left: 51.6%; }

/* --- Ce qui vit en permanence ------------------------------------------- */
@keyframes prez-souffle {
  0%, 100% { translate: 0 0; scale: 1; }
  30%      { translate: 0 -2.2px; scale: 1.004; }
  62%      { translate: 0 -1px; scale: 1.002; }
}

/* Les bras ne suivent pas le meme tempo : sans ce decalage, le corps entier
   semblerait battre la mesure. */
@keyframes prez-brasg {
  0%, 100% { rotate: var(--bg); }
  50%      { rotate: calc(var(--bg) - 1.4deg); }
}

@keyframes prez-brasd {
  0%, 100% { rotate: var(--bd); }
  50%      { rotate: calc(var(--bd) + 1.6deg); }
}

@keyframes prez-tangage {
  0%, 100% { rotate: calc(var(--tg) - 0.7deg); }
  50%      { rotate: calc(var(--tg) + 0.9deg); }
}

@keyframes prez-clin {
  0%, 94%, 100% { transform: scaleY(0); }
  96.5%         { transform: scaleY(1); }
}

/* La lumiere qui passe : decoupee par sa propre silhouette, elle ne tombe
   jamais a cote de lui. */
.prez-lumiere {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(104deg,
    transparent 32%, rgb(198 226 255 / 26%) 48%, rgb(255 250 240 / 14%) 54%, transparent 70%);
  background-size: 300% 100%;
  background-repeat: no-repeat;
  mix-blend-mode: screen;
  opacity: 0.45;
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  animation: prez-lueur 14s ease-in-out infinite;
  transition: opacity 0.9s var(--ease);
}

@keyframes prez-lueur {
  0%            { background-position: -70% 0; }
  50%           { background-position: 170% 0; }
  50.001%, 100% { background-position: -70% 0; }
}

.prez-halo {
  position: absolute;
  inset: 8% -10% 6%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(90 169 255 / 26%), transparent 72%);
  filter: blur(16px);
  opacity: 0.7;
  transition: opacity 0.8s var(--ease);
}

/* --- Les poses : une attitude par section -------------------------------
   Le sens compte, et il est contre-intuitif : les deux bras pendent, donc un
   bras va **vers le corps** quand son extremite basse s'en rapproche — ce qui
   demande une rotation **positive** pour le bras de droite a l'image, et
   **negative** pour celui de gauche. Dans ce sens le bras passe sous le torse
   et le raccord reste invisible, meme a trois fois l'amplitude reelle. Dans
   l'autre, il s'ecarte du torse et decouvre un coin de vide a l'emmanchure.
   D'ou la regle : `--bd` >= 0, `--bg` <= 0. */
.prez[data-pose="bonjour"]     { --tt: 0deg;    --tg: 0deg;    --bd: 5deg; }
.prez[data-pose="chiffres"]    { --tt: 1.8deg;  --tg: 1.4deg;  --bd: 4deg; --bg: -1.5deg; }
.prez[data-pose="plan"]        { --tt: 1.2deg;  --tg: 0.8deg;  --bd: 3deg; --bg: -2deg; }
.prez[data-pose="moi"]         { --tt: -1deg;   --tg: -1deg;   --zo: 1.03; --bg: -3.5deg; }
.prez[data-pose="domaines"]    { --tt: 1.8deg;  --tg: 1.6deg;  --bd: 5deg; }
.prez[data-pose="prestations"] { --tt: 1.4deg;  --tg: 1.2deg;  --bd: 4deg; }
.prez[data-pose="ecoute"]      { --tt: -1.6deg; --tg: -1.6deg; --bg: -2.5deg; }
.prez[data-pose="contact"]     { --tt: 0deg;    --tg: 0deg;    --zo: 1.04; --bd: 7deg; --bg: -3deg; }
.prez[data-pose="merci"]       { --tt: -0.8deg; --tg: -0.5deg; --bd: 6.5deg; }

/* --- Ce qu'il dit ------------------------------------------------------- */
.prez-say {
  position: absolute;
  left: 76%;
  bottom: 66%;
  z-index: 2;
  width: max-content;
  max-width: 13rem;
  margin: 0;
  padding: 0.5rem 0.8rem 0.55rem;
  border: 1px solid var(--border-strong);
  border-radius: 0.9rem 0.9rem 0.9rem 0.15rem;
  background: var(--bg-3);
  box-shadow: 0 18px 40px -22px rgb(0 0 0 / 80%);
  font-size: 0.75rem;
  line-height: 1.35;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text-2);
  text-wrap: balance;
  opacity: 0;
  transform: translateY(0.4rem) scale(0.94);
  transform-origin: 0 100%;
  transition: opacity 0.45s var(--ease), transform 0.55s var(--ease);
}

.prez[data-side="right"] .prez-say {
  left: auto;
  right: 76%;
  border-radius: 0.9rem 0.9rem 0.15rem 0.9rem;
  transform-origin: 100% 100%;
}

.prez[data-say-on] .prez-say { opacity: 1; transform: none; }

/* Quand il parle : la bouche articule, les bras accompagnent la parole — on
   parle avec les mains — la tete approuve, et la lumiere se leve sur lui. */
.prez[data-say-on] .prez-bouche { animation: prez-parle 0.34s ease-in-out 13; }
.prez[data-say-on] .prez-brasd { animation: prez-geste-d 1.5s ease-in-out 3; }
.prez[data-say-on] .prez-brasg { animation: prez-geste-g 1.9s ease-in-out 2; }
.prez[data-say-on] .prez-tete-g { animation: prez-hoche 1.3s ease-in-out 2; }
.prez[data-say-on] .prez-torse { animation: prez-avance 2.2s ease-in-out 2; }
.prez[data-say-on] .prez-lumiere { opacity: 0.85; }
.prez[data-say-on] .prez-halo { opacity: 0.95; }

/* Les voyelles ne s'ouvrent pas toutes pareil : trois amplitudes suffisent a
   faire une parole plutot qu'un battement. */
@keyframes prez-parle {
  0%, 100% { transform: scaleY(0.18); }
  30%      { transform: scaleY(0.92); }
  55%      { transform: scaleY(0.45); }
  80%      { transform: scaleY(1); }
}

@keyframes prez-geste-d {
  0%, 100% { rotate: var(--bd); }
  30%      { rotate: calc(var(--bd) + 5.5deg); }
  62%      { rotate: calc(var(--bd) + 1.5deg); }
}

@keyframes prez-geste-g {
  0%, 100% { rotate: var(--bg); }
  45%      { rotate: calc(var(--bg) - 4.5deg); }
}

@keyframes prez-hoche {
  0%, 100% { rotate: var(--tg); }
  45%      { rotate: calc(var(--tg) + 1.8deg); }
}

@keyframes prez-avance {
  0%, 100% { translate: 0 0; scale: 1; }
  25%      { translate: 0 -3px; scale: 1.012; }
}

/* Fenetre etroite : il retrecit, et il se tait — le texte occupe toute la
   largeur, la bulle passerait dessus. */
@media (max-width: 760px) {
  .prez { width: clamp(4.75rem, 17vw, 6.5rem); bottom: -1.25rem; }
  .prez-say { display: none; }
}

/* Vraiment tres etroit : le portrait generait la lecture, il se retire. */
@media (max-width: 340px) {
  .prez { display: none; }
}

:root[data-theme="light"] .prez-halo {
  background: radial-gradient(closest-side, rgb(18 87 201 / 16%), transparent 72%);
}

:root[data-theme="light"] .prez-c { filter: saturate(1.02) contrast(1.01); }

/* En registre clair, une lueur en `screen` ne se verrait pas. */
:root[data-theme="light"] .prez-lumiere {
  mix-blend-mode: soft-light;
  opacity: 0.65;
}

/* =========================================== 18. La page au défilement
   ----------------------------------------------------------------------------
   Le script mesure, la feuille de style décide. Chaque bloc narré reçoit :

     --q    0 quand il affleure le bas de la fenêtre, 1 quand il vient d'en
            sortir par le haut. Sa valeur neutre est donc 0,5 — au milieu —
            et c'est elle que portent tous les `var(--q, 0.5)` : sans script,
            ou sous mouvement réduit, la page est exactement au repos.
     --qo   0 tant que le bloc n'a pas commencé à sortir par le haut, 1 quand
            il est passé. C'est le départ : le héros s'enfonce, il ne glisse
            pas.
     --s    sur la racine, l'avancement de la page entière.

   Aucune de ces valeurs n'est écrite sous mouvement réduit : les replis
   ci-dessous suffisent à tout immobiliser, il n'y a pas une règle de plus à
   maintenir pour ce cas.
   ========================================================================== */

/* Le fond entier respire avec la lecture : les aurores descendent moins vite
   que la page, la trame de points glisse. C'est ce décalage, et non un effet
   posé sur un bloc, qui donne la profondeur. */
.aurora { transform: translateY(calc(var(--s, 0) * -7vh)); }
.grid-dots { background-position: 0 calc(var(--s, 0) * -60px); }

/* --- Le héros s'en va plutôt qu'il ne défile -----------------------------
   Uniquement hors scène : quand le chapitre se joue (section 7), c'est `--p`
   qui commande la marque et l'accroche, et deux jeux de règles se
   disputeraient la même transformation. */
.hero:not([data-live]) .logo {
  transform: translateY(calc(var(--qo, 0) * -7vh)) scale(calc(1 - 0.16 * var(--qo, 0)));
  opacity: calc(1 - 1.15 * var(--qo, 0));
}

.hero:not([data-live]) .hero-copy {
  transform: translateY(calc(var(--qo, 0) * -3.5vh));
  opacity: calc(1 - 1.3 * var(--qo, 0));
}

/* Les traits de lumière restent en arrière : ils appartiennent au fond. */
.hero:not([data-live]) .hero-swoosh { transform: translateY(calc(var(--qo, 0) * 13vh)); }

/* --- Les chiffres montent à contre-courant, en éventail ------------------
   Hors chapitre joué seulement : quand il se joue (section 9), chaque repère a
   son propre avancement et sa propre montée, et l'éventail n'a plus de sens. */
.rep:not([data-live]) .figures { transform: translateY(calc((0.5 - var(--q, 0.5)) * 3.5rem)); }

.fig:nth-child(2) dd { --k: 1; }
.fig:nth-child(3) dd { --k: 2; }
.fig:nth-child(4) dd { --k: 3; }

.rep:not([data-live]) .fig dd {
  transform: translateY(calc((0.5 - var(--q, 0.5)) * (1 + var(--k, 0)) * 0.9rem));
}

/* --- Les images se recadrent en avançant, sans jamais bouger d'un pixel --
   `object-position` plutôt qu'une transformation : le survol garde son propre
   zoom, les deux ne se disputent rien. */
.portrait img { object-position: 50% calc(50% + (0.5 - var(--q, 0.5)) * 16%); }
.domain img { object-position: 50% calc(50% + (0.5 - var(--q, 0.5)) * 22%); }

/* --- Le bandeau de repères s'allume au passage du milieu -----------------
   Le carré de l'écart au centre donne une cloche sans avoir à prendre de
   valeur absolue : 1 au milieu, 0 aux deux bords.

   La cloche est portée par la piste, et non par la section : le bandeau abrite
   un segment du fil, et une opacité posée sur la section l'emporterait avec
   elle. Le tronc y perdrait la moitié de sa clarté sur une cinquantaine de
   pixels, entre deux voisins restés pleins — soit exactement la marche que la
   section 19 s'attache à éviter. Les filets et le fond du bandeau ne bougent
   pas non plus : la couture reste posée, seul le texte respire. */
.strip {
  --mi: calc(0.5 - var(--q, 0.5));
  /* Même raison qu'aux repères : un plancher réglé pour du texte clair sur fond
     sombre laisse, en registre clair, un gris pâle sur presque blanc. */
  --sol-strip: 0.5;
}

:root[data-theme="light"] .strip { --sol-strip: 0.62; }

.strip-track {
  opacity: calc(var(--sol-strip) + (1 - var(--sol-strip)) * (1 - 4 * var(--mi) * var(--mi)));
}

/* --- Le guillemet du témoignage dérive ----------------------------------- */
.quote-mark { transform: translateY(calc((0.5 - var(--q, 0.5)) * 4.5rem)); }

/* --- Les coordonnées du contact remontent doucement ---------------------- */
.coords { transform: translateY(calc((0.5 - var(--q, 0.5)) * 2.5rem)); }

/* ============================================ 19. Le fil conducteur
   ----------------------------------------------------------------------------
   Un seul objet traverse le site du premier au dernier écran : un filament de
   lumière posé dans la gouttière gauche de la colonne de texte. Il naît sous
   le logo du héros, enfile les quatre repères, se dédouble en deux socles dans
   la chaîne de valeur, vient se confondre avec la colonne de la pellicule des
   prestations, puis se referme en un point dans le contact.

   Chaque section porte son propre segment (`.fil`), qui couvre exactement sa
   hauteur : les segments se touchent bout à bout dans le flux, et le fil est
   donc continu **par construction** — rien à synchroniser, rien à recalculer
   au défilement.

   C'est aussi la raison pour laquelle le tronc est rigoureusement uniforme sur
   toute sa longueur. Une opacité ou un dégradé tirés de `--q` prendraient une
   valeur différente d'un segment à l'autre, et une marche apparaîtrait à
   chaque jonction. Ce qui change de chapitre en chapitre, c'est la morphologie
   du fil — épaisseur, ramifications, billes, convergence — et elle est
   toujours attachée au contenu, jamais à la longueur du segment.
   ========================================================================== */

:root {
  /* Abscisse du fil : le milieu de la gouttière gauche de la colonne de texte.
     Le fil court ainsi en marge du texte sans jamais passer dessous, et il
     reste à la même place quelle que soit la largeur de la fenêtre. */
  --fil-x: calc(50% - min(50%, var(--maxw) / 2) + var(--gut) / 2);
  --fil-w: 1px;
  --fil-c: color-mix(in srgb, var(--blue-bright) 34%, transparent);
  --fil-spark: rgb(255 255 255 / 42%);
}

:root[data-theme="light"] {
  --fil-c: color-mix(in srgb, var(--blue) 34%, transparent);
  --fil-spark: rgb(31 111 224 / 55%);
}

.fil {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* --- La dérive du fil, et la règle qui la borne ----------------------------
   Un chapitre a le droit d'appeler le fil hors de la gouttière : aux repères il
   devient la colonne de lumière qui monte derrière le glyphe, aux prestations
   l'axe de la pellicule. Mais il doit le **rendre à la gouttière avant la
   jonction**, sinon le fil saute latéralement d'un chapitre au suivant. Relevé
   avant reprise, entre les repères et la chaîne de valeur : 412 px de saut
   franchis en un point, plus un passage de 2 px à 1 px d'épaisseur. Trois des
   quatre continuités que le fil doit tenir étaient rompues au même endroit.

   D'où un seul curseur, `--fil-champ` : 0 le fil est dans la gouttière, 1 il est
   sur la cible du chapitre. Les chapitres le tirent de `--env-relais`, nul aux
   deux coutures par construction — la continuité de position, d'épaisseur et de
   clarté n'est donc pas réglée à l'œil, elle est acquise.

   Trois réglages, et ils remplacent autant de cas particuliers :

     --fil-vers   l'écart de la gouttière à la cible. En pourcentage du cadre
                  (les repères) ou en longueurs reprises du calcul de la scène
                  (les prestations) : le mécanisme ne s'en mêle pas.
     --fil-gras   l'épaisseur gagnée dans le champ.
     --fil-lueur  l'aura gagnée — négative pour la pellicule, dont la colonne
                  porte déjà son propre halo.

   La part qui bouge passe en **transformation** et non dans `left` : une
   transformation ne remet pas le fil en page à chaque image. */
[data-live] .fil {
  --fil-dx: calc(var(--fil-champ, 0) * var(--fil-vers, 0px));
  --fil-w: calc(1px + var(--fil-champ, 0) * var(--fil-gras, 0px));
  --fil-aura: calc(1 + var(--fil-champ, 0) * var(--fil-lueur, 0));
  /* Le nœud de relais est **le point de la couture** : il ne brille qu'au
     passage d'un chapitre à l'autre et s'efface au cœur du chapitre, où il
     n'aurait rien à dire. C'est lui qu'on voit reprendre la lumière du
     chapitre sortant, puis la rendre au suivant. */
  --fil-node: calc(0.25 + 0.75 * var(--env-couture, 1));
  /* Au-dessus de l'étage : dans un chapitre à décor opaque, le fil est frère de
     l'étage dans le flux, il passerait donc **dessous** et disparaîtrait. Or
     c'est lui qui relie les chapitres — l'environnement peut changer, le fil
     doit traverser. C'était une règle propre aux repères ; depuis que les huit
     chapitres ont un décor opaque, elle appartient au mécanisme. */
  z-index: 2;
  transform: translate3d(var(--fil-dx), 0, 0);
}

.fil > b {
  position: absolute;
  inset: 0 auto;
  left: var(--fil-x);
  width: var(--fil-w);
  margin-left: calc(var(--fil-w) / -2);
  border-radius: 2px;
  background: var(--fil-c);
  /* Contient les éclats : ils courent dans le tronc, pas à côté. */
  overflow: hidden;
}

/* Les éclats qui descendent le fil. Un dégradé répété que l'on décale, plutôt
   qu'un élément que l'on déplace : la période est indépendante de la hauteur
   du segment, donc la même dans une section de trois écrans et dans un bandeau
   de cinquante pixels. */
.fil > b > i {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(180deg,
    transparent 0 11rem,
    var(--fil-spark) 15rem,
    transparent 19rem 30rem);
  animation: fil-run 7s linear infinite;
}

@keyframes fil-run { to { background-position: 0 30rem; } }

/* L'aura : c'est elle qui fait lire le fil comme de la lumière et non comme un
   filet de tableau. Uniforme verticalement, donc sans jonction visible. */
.fil::before {
  content: "";
  position: absolute;
  inset: 0 auto;
  left: var(--fil-x);
  width: 1.6rem;
  margin-left: -0.8rem;
  background: linear-gradient(90deg, transparent, rgb(90 169 255 / 16%), transparent);
  filter: blur(5px);
  opacity: var(--fil-aura, 1);
}

/* Le nœud de relais, posé sur la jonction haute du segment : il y a un nœud à
   chaque passage de chapitre. C'est un point — jamais une longueur — donc il
   ne peut pas introduire de marche. */
.fil::after {
  content: "";
  position: absolute;
  top: 0;
  left: var(--fil-x);
  width: 0.42rem;
  height: 0.42rem;
  margin: -0.21rem 0 0 -0.21rem;
  border-radius: 50%;
  background: var(--blue-bright);
  box-shadow: 0 0 0 3px rgb(90 169 255 / 14%), 0 0 12px 1px rgb(90 169 255 / 45%);
  opacity: var(--fil-node, 0.8);
  animation: fil-node 4.6s ease-in-out infinite;
}

@keyframes fil-node {
  0%, 100% { transform: scale(0.85); }
  50%      { transform: scale(1.3); }
}

/* --- La fermeture, dans le pied --------------------------------------------
   Le fil se referme en un point. Le tronc se dissipe sur son dernier tiers —
   c'est le seul segment qui a droit à un dégradé vers le bas, puisqu'il n'a
   aucun voisin en dessous — et un point plus large se pose à l'endroit où il
   s'éteint. Le récit est bouclé : un point est né sous le monogramme, un point
   le referme. */
.foot .fil > b {
  background: linear-gradient(180deg, var(--fil-c) 0 46%, transparent 82%);
}

.foot .fil::before { mask-image: linear-gradient(180deg, #000 40%, transparent 78%); }

.foot .fil-fin {
  position: absolute;
  top: 46%;
  left: var(--fil-x);
  width: 0.6rem;
  height: 0.6rem;
  margin: -0.3rem 0 0 -0.3rem;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 0 4px rgb(90 169 255 / 12%), 0 0 18px 2px rgb(90 169 255 / 40%);
}

/* ============================================= 20. Mouvement réduit */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  html.js .rv { opacity: 1; transform: none; }
  html.js .kicker::before { transform: none; }
  html.js .quote .w { opacity: 1; transform: none; }

  /* La marque est **déjà** à son état final : sa construction n'existe plus que
     dans l'ouverture, et l'ouverture ne se joue pas ici — le script ne la
     déclenche pas. Il n'y a donc rien à rétablir, seulement les calques qui
     n'ont de sens qu'en mouvement à éteindre. */
  .lg-out, .lg-trame, .lg-bloom, .lg-sheen { display: none; }
  .hero-swoosh { opacity: 1; }
  .spot, .aurora i, .scroll-cue i::after { display: none; }

  /* Le fil reste — il porte la continuité, pas une information. Seuls les
     éclats qui le descendent s'en vont : figés, ils se liraient comme une tache
     et non comme un passage. */
  .fil > b > i { display: none; }

  /* Le drapeau `js` n'est pas posé sous mouvement réduit, donc la description
     des domaines est déjà dépliée d'elle-même. La règle reste par sécurité, au
     cas où la préférence changerait après le chargement — le drapeau, lui,
     resterait en place. */
  html.js .domain .d-d { max-height: 9rem; opacity: 1; }

  /* Le schéma se lit alors comme une planche fixe : la courbe entière, la
     projection posée, et les cinq commentaires dépilés les uns sous les
     autres — plus rien ne se relaie, donc rien ne doit rester caché. */
  .vc-proj, .vc-horizon { opacity: 0.9; }

  /* Sans mouvement, plus de « déjà franchi » à distinguer : on force l'état
     allumé, et toutes les propriétés qui en dérivent suivent d'elles-mêmes. */
  .vc-lit { --lit: 1; }

  .vc-detail { gap: 1.1rem; }
  .vc-d { grid-area: auto; opacity: 1; transform: none; }
  .vc-hint { display: none; }

  /* Le présentateur reste présent — il ne respire plus, il ne hoche plus.
     L'appui de la section est posé d'un coup, sans mouvement. */
  /* Le présentateur reste présent — il ne bouge plus. La pose de la section
     est posée d'un coup, sans geste, et la bouche reste fermée. */
  .prez { animation: none; opacity: 1; }
  .prez-c, .prez-cadre, .prez-tete-g, .prez-bouche, .prez-cil { animation: none; transition: none; }
  .prez-cil { transform: scaleY(0); }
  .prez-bouche { transform: scaleY(0.14); }
  .prez-lumiere { animation: none; opacity: 0.3; }
  .prez-say { transition: none; }
}

/* ================================== 21. La marque : le rangement et l'ouverture
   ----------------------------------------------------------------------------
   Deux choses ici, et elles sont solidaires.

   **Le rangement.** La barre fait 4,25 rem de haut : le lockup complet, réduit
   tel quel, y donnerait une ligne de sens de 5 px. Le dessin est donc
   *condensé* — les deux lignes de texte quittent le dessous du monogramme pour
   venir se ranger à sa droite, et les trois domaines s'effacent. Le monogramme
   et son filet, eux, ne se séparent jamais : c'est le cœur reconnaissable.
   L'agencement condensé est l'**état par défaut** de `.mk--barre`, ce qui vaut
   pour la page sans script comme sous mouvement réduit.

   **L'ouverture** part de là et remonte le chemin : elle rejoue l'agencement
   complet, en grand, au centre de l'écran, puis le laisse revenir à sa place.
   Aucun échange d'objet en route — c'est la marque de la barre elle-même qui se
   construit et qui se range, et c'est pour cela qu'on la voit s'installer.

   Chaque déplacement est un `translate() scale()` en unités du dessin, et sa
   valeur est celle de l'état d'arrivée : les images-clés du rangement n'ont donc
   qu'un temps à décrire — celui du départ, l'agencement complet, identique à
   tous les paliers. Ajouter un palier ne touche à aucune animation.
   ========================================================================== */

/* --- L'emplacement dans la barre -------------------------------------------
   `--mk-w` est la largeur du dessin entier (900 unités) ; `--mk-u` en déduit
   l'unité en pixels. L'emplacement, lui, ne mesure que ce qui est **visible**
   dans l'agencement condensé (`--mk-cw` × `--mk-ch`, décalé de `--mk-cy`) :
   c'est cela, et non la boîte du dessin, qui doit décider de la place prise
   dans la barre. */
.mono {
  --mk-w: 21.25rem;
  --mk-u: calc(var(--mk-w) / 900);
  --mk-cw: 900;
  --mk-ch: 104;
  --mk-cy: 22;

  width: calc(var(--mk-cw) * var(--mk-u));
  height: calc(var(--mk-ch) * var(--mk-u));
}

.mono .mk {
  top: calc(-1 * var(--mk-cy) * var(--mk-u));
  width: var(--mk-w);
  /* Indispensable, et facile à oublier : pour un `<svg>` la boîte de référence
     des transformations est son `viewBox`, et l'origine y vaut le centre. Le vol
     de l'ouverture est calculé depuis le coin haut gauche — sans cette ligne, la
     marque part décalée d'une demi-largeur et d'une demi-hauteur. */
  transform-origin: 0 0;
}

/* --- L'agencement condensé -------------------------------------------------
   Palier large : monogramme et filet à gauche, `ALAIN MIELLE` puis
   `INNOVATION & VISION` empilés à droite, alignés à gauche.

   L'espace est la matière première de ce lockup — c'est lui qui le fait lire
   comme du haut de gamme, et non le nombre de lignes. La gouttière entre le
   monogramme et le bloc de texte fait donc 50 unités, soit près de 19 px : c'est
   large, et c'est voulu. L'interlettrage, lui, est celui de l'original à
   l'identique, puisque les groupes ne subissent qu'une homothétie — condenser ne
   resserre aucune lettre.

   L'origine des transformations doit être posée : pour un élément SVG, la boîte
   de référence est le `viewBox`, et `transform-origin` y vaudrait le centre —
   les translations calculées ci-dessous supposent l'origine du repère. */
.mk-a, .mk-b, .mk-c { transform-origin: 0 0; }

.mk--barre .mk-a { transform: translate(-83.9px, 5.9px) scale(0.536); }
.mk--barre .mk-b { transform: translate(236.4px, -166.4px) scale(0.822); }
.mk--barre .mk-c { transform: translate(132px, -273.8px) scale(1.112); }

/* Le filet mesure 3 unités d'épaisseur pour 169 de long : réduit d'un facteur
   0,54 puis affiché à 0,38 px l'unité, il tomberait sous le demi-pixel et
   s'éteindrait. On lui rend son épaisseur en barre — c'est le seul endroit du
   dessin où une proportion cède, et elle cède à la visibilité. */
.mk--barre .lg-rule { y: 218.2px; height: 5.6px; }

/* Palier étroit : plus de place à droite du monogramme pour deux lignes de
   texte. Le dessin retrouve son **empilement d'origine** — monogramme, filet,
   signature — ce qui est à la fois plus fidèle et plus étroit, les deux lignes
   se partageant la largeur au lieu de s'additionner. La ligne de sens s'en va :
   on retire, on ne tasse pas. Elle reste écrite en entier dans le héros et dans
   le pied, qui portent le lockup complet.

   Le seuil est à 720 px, et non à 900 : jusque-là le bloc de trois composants
   tient sans serrer les liens de la barre, et cette ligne est ce qui distingue
   la marque complète de la version réduite. */
@media (max-width: 719px) {
  .mono { --mk-w: 11rem; --mk-cw: 734; --mk-ch: 214; --mk-cy: 10; }
  .mk--barre .mk-a { transform: translate(18.9px, -13.2px) scale(0.774); }
  .mk--barre .mk-b { transform: translate(-195.6px, -136px) scale(1.25); }
  .mk--barre .mk-c { opacity: 0; }
}

/* Palier téléphone : la signature elle-même ne tient plus à côté des liens. Le
   monogramme et son filet suffisent — l'esperluette bleue est la marque, et le
   nom complet est écrit trois centimètres plus bas, dans le héros. */
@media (max-width: 479px) {
  .mono { --mk-w: 8.9rem; --mk-cw: 454; --mk-ch: 150; }
  .mk--barre .mk-a { transform: translate(-121.1px, -13.2px) scale(0.774); }
  .mk--barre .mk-b { opacity: 0; }
}

/* --- La marque dans le pied ------------------------------------------------
   Même dessin, au repos, avec les trois domaines posés dessous en texte courant.
   Ils sont hors du lockup mais sous lui : les mots restent, la marque respire. */
.foot-marque { display: grid; justify-items: start; gap: 0.9rem; }

/* Plus discret que la ligne de sens de la marque juste au-dessus : c'est un
   complément, pas un troisième étage du logotype. */
.foot-domaines {
  margin: 0;
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}

.foot-domaines i {
  font-style: normal;
  margin-inline: 0.7em;
  color: var(--blue);
}

/* --- L'état de repos des calques d'animation -------------------------------
   Sans ouverture, le dessin est simplement **dessiné**. Tout ce qui ne sert
   qu'à la construction est donc éteint par défaut, et les masques de révélation
   sont grands ouverts. C'est ce qui rend la page correcte sans script, sous
   mouvement réduit, et à toute navigation interne après la première. */
.lg-trame, .lg-out, .lg-bloom { opacity: 0; }

.lg-out {
  fill: none;
  stroke: var(--text);
  stroke-width: 1.15;
  stroke-linejoin: round;
}

.lg-out .amp { stroke: var(--blue-bright); }

.lg-scan { transform-origin: 450px 275px; }
.lg-scan2 { transform-origin: 450px 329px; }

/* --- L'ouverture ----------------------------------------------------------
   La toile n'existe que si le script l'a déclenchée. Sans script il n'y a ni
   toile ni verrou : la page est immédiatement lisible et défilable, et il n'y a
   pas de règle de repli à maintenir pour cela — c'est l'absence de l'attribut
   qui s'en charge.

   Toute la séquence est cadencée depuis un seul décalage, `--ouv-d`. Le script
   n'a qu'à le rendre négatif pour que la tête de lecture saute au dernier temps :
   c'est ainsi que l'ouverture se **passe** sans se couper net — la même
   timeline, avancée. */
html[data-ouverture] {
  --ouv-d: 0s;
  /* La courbe du site (`--ease`) décolle franc : c'est juste pour une révélation
     de bloc, c'est nerveux pour une marque. L'ouverture a les siennes — un départ
     retenu et une arrivée longue. Rien ici ne rebondit et rien ne détale.
     `--ouv-pose` mène le rangement, `--ouv-doux` les apparitions. */
  --ouv-pose: cubic-bezier(0.52, 0.01, 0.16, 1);
  --ouv-doux: cubic-bezier(0.33, 0, 0.2, 1);
}

/* Le verrou. Il ne tient que le temps de la construction (2,8 s), et le script
   le lève aussi au premier clic, à la première touche, au premier cran de
   molette. La gouttière rendue par la barre de défilement retirée est compensée
   pour que la page ne saute pas d'une dizaine de pixels au moment du verrou. */
html[data-ouv-verrou] {
  overflow: hidden;
  padding-right: var(--ouv-gouttiere, 0px);
}

.ouv { display: none; }

html[data-ouverture] .ouv {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 90;
  /* Le bleu nuit du logo d'origine, dégradé et plus clair vers le centre-gauche,
     comme sur la planche que le commanditaire montre. Sur le thème clair on
     reste institutionnel : le halo se contente d'exister. */
  background:
    radial-gradient(72% 58% at 42% 40%, color-mix(in srgb, var(--blue) 22%, transparent), transparent 72%),
    radial-gradient(120% 100% at 50% 120%, color-mix(in srgb, var(--blue) 9%, transparent), transparent 60%),
    var(--bg);
  animation: ouv-toile 1.05s var(--ouv-doux) calc(var(--ouv-d) + 2.8s) both;
}

/* La toile ne se contente pas de pâlir : elle se **retire vers la marque**,
   qui vient de partir vers le haut. C'est le même geste, vu de l'autre côté. */
@keyframes ouv-toile {
  0%   { opacity: 1; }
  100% { opacity: 0; visibility: hidden; }
}

/* La barre pendant l'ouverture : la marque doit passer devant la toile, mais ce
   qui l'entoure n'a rien à y faire. Les liens et la bascule reviennent quand la
   marque est en place. */
html[data-ouverture] .nav { z-index: 95; }
html[data-ouverture] .nav[data-stuck] { background: transparent; backdrop-filter: none; border-bottom-color: transparent; }
html[data-ouverture] .progress { opacity: 0; }

/* Le fondu d'arrivée doit partir de zéro : `fade-in` n'a qu'une image-clé
   d'arrivée, donc son état de départ est la valeur portée par l'élément. D'où
   la déclaration ci-dessous — sans elle, le remplissage vers l'arrière tiendrait
   les liens à pleine opacité pendant toute l'ouverture. */
/* Transparent reste cliquable : sans cela on pourrait basculer le thème sans
   voir le bouton. `pointer-events` bascule donc avec l'opacité, dans les mêmes
   images-clés — le rendre inerte pour toute la durée de l'attribut laisserait la
   barre morte une seconde après être redevenue visible. Le focus clavier, lui,
   reste possible : c'est l'un des moyens de passer l'ouverture. */
html[data-ouverture] .nav-links,
html[data-ouverture] .theme-btn {
  animation: ouv-controles 0.7s var(--ouv-doux) calc(var(--ouv-d) + 3.25s) both;
}

@keyframes ouv-controles {
  from { opacity: 0; pointer-events: none; }
  to   { opacity: 1; pointer-events: auto; }
}

/* --- 1 · la trame, de 0,1 s à 3 s ----------------------------------------
   Une grille de points s'ouvre depuis le futur emplacement de l'esperluette,
   tient le temps que le dessin se fasse, puis **se replie vers la marque** :
   l'état « donnée » avant la forme, et son retrait quand la forme est là.
   Le disque grandit par une simple mise à l'échelle de l'élément, dont le
   masque radial est fixe : une variable d'étendue dans le masque ne
   s'interpolerait pas sans être enregistrée, et sauterait d'un temps à l'autre. */
html[data-ouverture] .ouv-trame {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle, color-mix(in srgb, var(--blue-bright) 55%, transparent) 1px, transparent 1.5px);
  background-size: 26px 26px;
  background-position: 50% 44%;
  mask-image: radial-gradient(circle at 50% 44%, #000 26%, transparent 74%);
  transform-origin: 50% 44%;
  animation: ouv-trame 3.6s var(--ouv-doux) calc(var(--ouv-d) + 0.1s) both;
}

@keyframes ouv-trame {
  0%   { opacity: 0;    transform: scale(0.14); }
  14%  { opacity: 0.82; }
  34%  {                transform: scale(1); }
  68%  { opacity: 0.38; transform: scale(1.02); }
  100% { opacity: 0;    transform: scale(0.52); }
}

/* Une seule lueur dans toute l'ouverture, et c'est celle de l'esperluette
   (`.lg-bloom`, plus bas). Le halo de la toile ci-dessus est fixe. Un troisième
   foyer avait été posé au centre : deux halos qui se recouvrent ne font pas deux
   fois plus d'effet, ils font du flou. */

/* --- 2 · le vol : la marque en grand, puis à sa place --------------------
   Le script mesure et écrit trois nombres — deux décalages et un facteur ; la
   feuille de style décide de tout le reste. Le premier temps tient la marque en
   grand pendant toute la construction (70 % de 4 s = 2,8 s), le dernier la
   ramène en 1,2 s — une arrivée longue, sans à-coup ni rebond. La valeur d'arrivée n'est pas écrite : sans image-clé à 100 %, c'est
   l'état de repos de l'élément qui la fournit — donc l'agencement condensé du
   palier courant, quel qu'il soit. */
html[data-ouverture] .nav .mk--barre {
  animation: ouv-vol 4s var(--ouv-pose) var(--ouv-d) both;
}

@keyframes ouv-vol {
  0%, 70% { transform: translate(var(--ouv-tx, 0px), var(--ouv-ty, 0px)) scale(var(--ouv-s, 1)); }
}

html[data-ouverture] .nav .mk--barre .mk-a {
  animation: ouv-plein 4s var(--ouv-pose) var(--ouv-d) both;
}

/* L'agencement complet : le dessin tel qu'il est écrit dans le balisage. */
@keyframes ouv-plein {
  0%, 70% { transform: none; opacity: 1; }
}

/* Les deux lignes de texte ne peuvent pas rejoindre leur place en ligne droite :
   elles passeraient à travers le monogramme, qui rétrécit vers la gauche au même
   instant. Deux objets qui se traversent, c'est un accident, pas un mouvement.
   Elles se **dissolvent** donc pendant qu'elles se déplacent, et reparaissent
   posées — des fondus longs, jamais un clignotement.

   Le monogramme et son filet, eux, ne se dissolvent pas : ils font tout le trajet
   à vue. C'est eux que l'œil suit, et c'est ce qui donne à voir la marque *qui
   s'installe* plutôt qu'une marque qui disparaît d'un côté pour reparaître de
   l'autre. */
html[data-ouverture] .nav .mk--barre .mk-b,
html[data-ouverture] .nav .mk--barre .mk-c {
  animation: ouv-plein-txt 4s var(--ouv-pose) var(--ouv-d) both;
}

@keyframes ouv-plein-txt {
  0%, 70% { transform: none; opacity: 1; }
  82%     { opacity: 0; }
  90%     { opacity: 0; }
}

/* Le filet a deux choses à faire pendant l'ouverture : reprendre son épaisseur
   d'origine (elle est renforcée en barre, où elle tomberait sous le pixel) et
   s'ouvrir depuis le centre au bon moment. Les deux tiennent dans **une seule**
   déclaration : deux règles séparées, même à sélecteurs différents, ne se
   cumulent pas — la plus spécifique remplace entièrement l'`animation` de
   l'autre, et c'est l'ouverture du filet qui avait disparu. */
html[data-ouverture] .nav .mk--barre .lg-rule {
  transform-box: fill-box;
  transform-origin: center;
  animation:
    ouv-filet 4s var(--ouv-pose) var(--ouv-d) both,
    ouv-rule 0.8s var(--ouv-doux) calc(var(--ouv-d) + 1.72s) both;
}

@keyframes ouv-filet {
  0%, 70% { y: 219.5px; height: 3px; }
}

@keyframes ouv-rule { from { transform: scaleX(0); } }

/* --- 3 · la construction --------------------------------------------------
   Tout ce qui suit est porté par `.nav` : c'est la marque de la barre, et elle
   seule, qui se construit. Les exemplaires du héros et du pied restent au repos
   pendant l'ouverture — ce sont eux que la page découvrira, déjà dessinés. */

/* La trame dans les lettres : les points grossissent et se rejoignent — l'idée
   qui prend corps. Le glyphe glisse légèrement sur le motif, qui est en espace
   utilisateur : les points paraissent affluer. */
html[data-ouverture] .nav .lg-trame {
  transform-origin: 450px 110px;
  animation: ouv-trame-lettres 1.5s var(--ouv-doux) calc(var(--ouv-d) + 0.4s) both;
}

@keyframes ouv-trame-lettres {
  0%   { opacity: 0;    transform: scale(1.11); }
  18%  { opacity: 0.95; }
  62%  { opacity: 0.9;  transform: scale(1); }
  100% { opacity: 0;    transform: scale(1); }
}

html[data-ouverture] .nav .lg-pt { animation: ouv-pt 1.35s var(--ouv-doux) calc(var(--ouv-d) + 0.45s) both; }

@keyframes ouv-pt {
  from { r: 1.1px; }
  to   { r: 3.9px; }
}

/* Le tracé : les lettres se dessinent au trait, puis le trait s'efface sous
   l'encre. Du dessin à la réalisation — c'est le sujet du client. */
html[data-ouverture] .nav .lg-out {
  stroke-dasharray: 1400;
  stroke-dashoffset: 1400;
  animation:
    draw 1.25s linear calc(var(--ouv-d) + 0.6s) both,
    ouv-trait 1.6s var(--ouv-doux) calc(var(--ouv-d) + 0.6s) both;
}

@keyframes ouv-trait {
  0%   { opacity: 0; }
  10%  { opacity: 1; }
  62%  { opacity: 1; }
  100% { opacity: 0; }
}

/* L'esperluette s'encre **la première**, et en bleu : c'est le point de bascule
   d'un état à l'autre, le pivot de tout le dessin. Elle porte son propre
   `fill-opacity`, ce qui la rend indépendante de celui de la lettre voisine —
   une opacité de groupe les aurait emportées ensemble. */
html[data-ouverture] .nav .lg-fill { animation: ouv-encre 0.7s var(--ouv-doux) calc(var(--ouv-d) + 1.52s) both; }
html[data-ouverture] .nav .lg-fill .amp { animation: ouv-encre 0.55s var(--ouv-doux) calc(var(--ouv-d) + 1.3s) both; }

@keyframes ouv-encre { from { fill-opacity: 0; } to { fill-opacity: 1; } }

html[data-ouverture] .nav .lg-bloom {
  transform-origin: 597px 106px;
  animation: ouv-eclat 1.35s var(--ouv-doux) calc(var(--ouv-d) + 1.28s) both;
}

@keyframes ouv-eclat {
  0%   { opacity: 0;   transform: scale(0.2); }
  18%  { opacity: 0.9; }
  100% { opacity: 0;   transform: scale(1.5); }
}

/* Le filet s'ouvre depuis le centre, juste après l'esperluette : le trait bleu
   part du pivot. Sa déclaration est plus haut, avec son épaisseur — voir pourquoi
   là-bas. */

/* Le balayage découvre la signature, puis la ligne de sens, un cran plus bas :
   deux volets qui s'ouvrent depuis l'axe de l'esperluette. Ce n'est pas un
   fondu — on voit la lumière passer. */
html[data-ouverture] .nav .lg-scan  { animation: ouv-volet 0.75s var(--ouv-doux) calc(var(--ouv-d) + 1.85s) both; }
html[data-ouverture] .nav .lg-scan2 { animation: ouv-volet 0.75s var(--ouv-doux) calc(var(--ouv-d) + 2.05s) both; }

@keyframes ouv-volet { from { transform: scaleX(0); } }

/* Le balayage final : un éclat unique traverse toute la marque, découpé par les
   lettres. C'est le vernis, et le signal que la construction est finie. Il doit
   être **éteint avant le rangement** : les copies du masque vivent dans les
   `defs`, elles ne suivent pas les groupes qui se déplacent, et un balayage
   encore visible dessinerait un fantôme de la marque à sa position d'origine. */
html[data-ouverture] .nav .lg-sheen { animation: sheen 1.5s linear calc(var(--ouv-d) + 2.02s) both; }

/* --- 4 · le relais du fil ------------------------------------------------
   Quand la marque est en place, elle laisse filer une amorce vers le bas de
   l'écran, à l'abscisse du fil conducteur, puis s'éteint. **Le fil n'est pas
   construit ici** : il appartient au récit et vit dans les sections. Ceci n'est
   que le geste qui l'annonce — un raccord, pas un objet. */
.ouv-relais { display: none; }

html[data-ouverture] .ouv-relais {
  display: block;
  position: fixed;
  top: 4.25rem;
  left: var(--fil-x);
  z-index: 88;
  width: 1px;
  height: 38vh;
  margin-left: -0.5px;
  background: linear-gradient(180deg, var(--blue-bright), var(--fil-c) 38%, transparent);
  transform-origin: 50% 0;
  pointer-events: none;
  animation: ouv-relais 1.3s var(--ouv-doux) calc(var(--ouv-d) + 3.6s) both;
}

@keyframes ouv-relais {
  0%   { opacity: 0; transform: scaleY(0); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: scaleY(1); }
}

/* Fenêtre basse : l'amorce ne doit pas plonger jusqu'au bas de l'écran. */
@media (max-height: 620px) {
  html[data-ouverture] .ouv-relais { height: 24vh; }
}

/* --- Mouvement réduit ---------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* Le script ne déclenche pas l'ouverture ici, donc rien de tout ce qui
     précède ne s'applique. Ces deux lignes ne valent que pour le cas où la
     préférence changerait après le chargement, l'attribut étant déjà posé. */
  .ouv, .ouv-relais { display: none !important; }
  html[data-ouv-verrou] { overflow: visible; }
}
