/* =========================================================
   TOLSTOÏ — teaser documentaire
   Thème sépia. Le fond s'éclaircit au scroll : de la geôle
   au Sahara. Tout le pilotage couleur passe par --bg / --fg,
   mis à jour par assets/js/main.js.
   ========================================================= */

/* ---------- 1. Tokens ---------- */

:root {
  --ink: #0a0806;
  --paper: #ece5d8;
  --accent: #c8791f;
  --accent-soft: rgba(200, 121, 31, 0.5);

  /* Accent LISIBLE, pour le texte posé sur le fond mouvant du bas de page.
     Basculé par main.js entre un doré clair et un ambre profond, au même
     instant que --fg. L'orangé --accent garde une luminance moyenne : il
     convient aux traits et aux halos, jamais à du texte qui traverse
     l'éclaircissement du fond — il s'y noyait sur 43 % de la page.
     Valeur de repli = version claire : c'est celle qui convient à la page
     des mentions légales, sombre et sans JavaScript. */
  --accent-ink: #f2cf98;

  /* Pilotées au scroll par le JS (valeurs de départ = haut de page) */
  --bg: #0a0806;
  --fg: #f2ede3;

  --font-display: "Bodoni Moda", "Times New Roman", serif;
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);

  --shell: 1180px;
  --gutter: clamp(1.25rem, 4vw, 3rem);

  /* Intensité du halo orangé. Elle décroît avec --lift (piloté au scroll
     par main.js) : sur le sable clair du bas de page, un glow à pleine
     puissance vire au jaune sale — c'est là que ça ferait « cheap ». */
  --glow: calc(1 - var(--lift, 0) * 0.75);
}

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  font-weight: 400;
  color: var(--fg);
  background: var(--ink);
  /* clip plutôt que hidden : hidden fait de <body> un conteneur de
     défilement et casse l'inertie tactile sur iOS. */
  overflow-x: hidden;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

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

/* ---------- 2. Calques de fond ---------- */

#scroll-bg {
  position: fixed;
  inset: 0;
  z-index: -20;
  background-color: var(--bg);
  will-change: background-color;
}

.glow {
  position: fixed;
  inset: 0;
  z-index: -18;
  pointer-events: none;
  background:
    radial-gradient(58% 42% at 50% 28%, rgba(200, 121, 31, 0.11), transparent 72%),
    radial-gradient(48% 38% at 82% 78%, rgba(200, 121, 31, 0.07), transparent 70%);
}

.grain {
  position: fixed;
  inset: -140px;
  z-index: 40;
  pointer-events: none;
  opacity: 0.055;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  /* Grain fixe : l'animer obligeait à recomposer tout le viewport
     plusieurs fois par seconde, en plus du défilement. */
}

.vignette {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  background: radial-gradient(ellipse at center, transparent 42%, rgba(0, 0, 0, 0.42) 100%);
  opacity: var(--vignette, 1);
  /* PAS de transition ici.
     L'opacité est déjà pilotée par le défilement, en 240 paliers : la
     continuité vient du scroll lui-même. Y ajouter une transition de
     0,4 s la faisait redémarrer à chaque palier — soit toutes les ~0,3 s
     en défilement lent, donc plus vite qu'elle ne s'achevait. Le calque
     plein écran ne se stabilisait jamais et le fond battait légèrement. */
}

main {
  position: relative;
  z-index: 10;
}

/* ---------- 3. Éléments partagés ---------- */

.label {
  margin: 0 0 1.4rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  opacity: 0.62;
}

em {
  font-style: italic;
}

/* ---------- 4. Hero ---------- */

.hero {
  position: relative;
  /* Au-dessus de la chronologie. Les archives de fond dérivent beaucoup
     (parallaxe forte) et débordaient par le haut de la première étape :
     elles passaient alors DEVANT le hero, qui les précède dans le DOM. */
  z-index: 3;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 7rem var(--gutter) 8rem;
  text-align: center;
  overflow: hidden;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  /* La vidéo est légèrement zoomée (effet de dérive) : on la clippe
     ici plutôt que de compter sur le débordement du parent. */
  overflow: hidden;
  background: #0a0806;
}

.hero__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: sepia(0.62) contrast(1.08) brightness(0.62) saturate(0.75);
  animation: hero-drift 26s ease-in-out infinite alternate;
}

@keyframes hero-drift {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to { transform: scale(1.16) translate3d(-1.5%, -1.5%, 0); }
}

.hero__scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(10, 8, 6, 0.72) 0%, rgba(10, 8, 6, 0.34) 34%, rgba(10, 8, 6, 0.62) 72%, #0a0806 100%),
    radial-gradient(70% 55% at 50% 46%, transparent 20%, rgba(10, 8, 6, 0.55) 100%);
}

.hero__inner {
  position: relative;
  z-index: 1;
  max-width: 60rem;
}

.hero__kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.9rem;
  margin: 0 0 clamp(1.75rem, 4vw, 2.75rem);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.38em;
  text-transform: uppercase;
  color: #cbb9a2;
}

.hero__kicker .rule {
  width: 40px;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
}

.hero__title {
  margin: 0;
  font-family: var(--font-display);
  color: #fbf7f0;
}

.hero__given {
  display: block;
  font-style: italic;
  font-weight: 400;
  font-size: clamp(2rem, 6.4vw, 4.3rem);
  line-height: 1;
  letter-spacing: 0.02em;
  color: #d8c7ad;
  margin-bottom: 0.05em;
}

.hero__family {
  display: block;
  font-weight: 900;
  font-size: clamp(2.5rem, 10.6vw, 8.8rem);
  line-height: 0.9;
  letter-spacing: -0.005em;
  white-space: nowrap;
}

.hero__letter {
  display: inline-block;
}

.js .hero__given {
  opacity: 0;
  animation: fade-rise 1s var(--ease-out) 0.18s forwards;
}

/* Son nom russe. Bodoni Moda ne couvre pas le cyrillique :
   on passe volontairement sur Inter, en capitales espacées. */
.hero__russian {
  margin: 1.1rem 0 0;
  font-family: var(--font-body);
  font-weight: 300;
  font-size: clamp(0.72rem, 1.5vw, 0.92rem);
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  color: #a08e77;
}

.js .hero__letter {
  opacity: 0;
  transform: translateY(0.36em);
  filter: blur(14px);
  animation: letter-in 1.15s var(--ease-out) forwards;
  animation-delay: calc(0.2s + var(--i) * 0.075s);
}

@keyframes letter-in {
  to {
    opacity: 1;
    transform: translateY(0);
    filter: blur(0);
  }
}

.hero__lede {
  margin: clamp(1.75rem, 4vw, 2.5rem) 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 3.4vw, 2.15rem);
  line-height: 1.28;
  color: #ece2d2;
}

.hero__lede em {
  color: #bda88c;
}

.hero__sub {
  margin: clamp(1.5rem, 3vw, 2rem) auto 0;
  max-width: 34rem;
  font-size: 0.98rem;
  line-height: 1.75;
  color: #a99883;
}

.js .hero__lede,
.js .hero__sub,
.js .hero__kicker,
.js .hero__russian {
  opacity: 0;
  animation: fade-rise 1.1s var(--ease-out) forwards;
}

.js .hero__kicker { animation-delay: 0.1s; }
.js .hero__russian { animation-delay: 0.8s; }
.js .hero__lede { animation-delay: 0.98s; }
.js .hero__sub { animation-delay: 1.16s; }

@keyframes fade-rise {
  from { opacity: 0; transform: translateY(18px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Centré via left/right plutôt que translateX : l'animation fade-rise
   se termine sur transform:translateY(0) et écraserait un translateX. */
.hero__scroll {
  position: absolute;
  bottom: clamp(2rem, 5vh, 3.25rem);
  left: 0;
  right: 0;
  width: fit-content;
  margin-inline: auto;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: #9b8b76;
  text-decoration: none;
  transition: color 0.3s;
}

.hero__scroll:hover {
  color: var(--accent);
}

/* Compense l'espace laissé après la dernière lettre par le letter-spacing,
   sinon le texte paraît décalé vers la gauche dans sa boîte. */
.hero__scroll > span:first-child {
  margin-right: -0.34em;
}

.hero__scroll-line {
  width: 1px;
  height: 46px;
  background: linear-gradient(to bottom, currentColor, transparent);
  transform-origin: top;
  animation: scroll-pulse 2.1s ease-in-out infinite;
}

@keyframes scroll-pulse {
  0%, 100% { transform: scaleY(0.45); opacity: 0.4; }
  50% { transform: scaleY(1); opacity: 1; }
}

.js .hero__scroll {
  opacity: 0;
  animation: fade-rise 1s var(--ease-out) 1.4s forwards;
}

/* ---------- 5. Parcours ---------- */

.journey {
  position: relative;
  z-index: 1; /* sous le hero et sous l'outro */
  padding: clamp(7rem, 16vh, 12rem) 0 clamp(6rem, 12vh, 10rem);
}

.journey__intro {
  max-width: 44rem;
  margin: 0 auto clamp(5rem, 12vh, 9rem);
  padding: 0 var(--gutter);
  text-align: center;
}

.journey__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.15rem, 6vw, 4rem);
  line-height: 1.06;
  letter-spacing: -0.01em;
}

.journey__title em {
  opacity: 0.66;
}

.journey__lede {
  margin: 1.6rem auto 0;
  max-width: 30rem;
  font-size: 1rem;
  line-height: 1.7;
  opacity: 0.62;
}

.journey__track {
  position: relative;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* -- la courbe -- */

.journey__svg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 2;
}

.path-base {
  stroke: rgba(160, 145, 125, 0.34);
  stroke: color-mix(in srgb, var(--fg) 26%, transparent);
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-dasharray: 1 8;
}

/* Halo permanent du tracé. Non animé : son flou est rastérisé une seule
   fois. Appliquer un filtre au tracé PARCOURU coûterait un re-rendu de
   tout le bloc (≈ 1180 × 5000 px) à chaque image de défilement. */
.path-halo {
  stroke: var(--accent);
  stroke-width: 9;
  stroke-linecap: round;
  filter: blur(7px);
  opacity: calc(0.1 * var(--glow, 1));
}

/* Rayonnement de la partie parcourue : un trait large et translucide,
   sans filtre. Superposé au trait fin, il donne la diffusion sans le
   coût d'un flou recalculé en continu. */
.path-progress-glow {
  stroke: var(--accent);
  stroke-width: 7;
  stroke-linecap: round;
  opacity: calc(0.16 * var(--glow, 1));
}

.path-progress {
  stroke: var(--accent);
  stroke-width: 1.7;
  stroke-linecap: round;
  opacity: 0.85;
}

/* -- les stations -- */

.stop {
  fill: var(--bg);
  stroke: rgba(160, 145, 125, 0.55);
  stroke: color-mix(in srgb, var(--fg) 40%, transparent);
  stroke-width: 1.2;
  transition: fill 0.45s var(--ease-out), stroke 0.45s var(--ease-out);
}

.stop.is-passed {
  fill: var(--accent);
  stroke: var(--accent);
}

/* -- le véhicule -- */

.marker {
  color: var(--accent);
  /* Le véhicule est le seul élément repositionné à chaque frame :
     on le promeut sur sa propre couche pour éviter de repeindre
     le reste du tracé à chaque déplacement. */
  will-change: transform;
  /* Deux ombres superposées plutôt qu'un seul gros flou : une serrée qui
     dessine le contour, une large qui diffuse. C'est cette double
     décroissance qui distingue un halo travaillé d'un néon plat.
     Le marqueur ne fait qu'une centaine de pixels : le filtre y est
     négligeable, contrairement au tracé complet. */
  filter: drop-shadow(0 0 5px rgba(200, 121, 31, calc(0.5 * var(--glow, 1))))
    drop-shadow(0 0 14px rgba(200, 121, 31, calc(0.28 * var(--glow, 1))));
}

.stop.is-passed {
  filter: drop-shadow(0 0 4px rgba(200, 121, 31, calc(0.55 * var(--glow, 1))));
}

.marker__halo {
  fill: var(--accent);
  opacity: 0.18;
  transform-box: fill-box;
  transform-origin: center;
  animation: halo-pulse 2.8s ease-in-out infinite;
}

@keyframes halo-pulse {
  0%, 100% { transform: scale(0.72); opacity: 0.2; }
  50% { transform: scale(1.5); opacity: 0; }
}

.marker__disc {
  fill: var(--bg);
  stroke: var(--accent);
  stroke-width: 1;
  opacity: 0.95;
}

.marker__icon {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
  transform: scale(0.35);
  transform-box: fill-box;
  transform-origin: center;
  transition:
    opacity 0.4s var(--ease-out),
    transform 0.6s var(--ease-spring);
}

.marker__icon * {
  vector-effect: non-scaling-stroke;
}

.marker__icon.is-active {
  opacity: 1;
  transform: scale(1);
}

/* -- les étapes -- */

.steps {
  position: relative;
  z-index: 1;
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(5.5rem, 14vh, 10rem);
}

.step {
  position: relative;
}

/* -- profondeur : archives en fond, sur deux plans -- */

.depth {
  position: absolute;
  top: 0;
  bottom: 0;
  /* Le calque déborde de la colonne de contenu pour occuper TOUTE la
     largeur visible : les positions en % des archives se lisent donc sur
     l'écran entier, pas sur les 1180 px du bloc.
     `100vw` INCLUT la barre de défilement — l'employer tel quel rendait
     la page défilable de quelques pixels sur le côté. On retranche donc
     sa largeur réelle, mesurée en JS (--sbw). */
  left: calc(50% - 50vw + var(--sbw, 0px) / 2);
  width: calc(100vw - var(--sbw, 0px));
  pointer-events: none;
  /* Sous la carte, qui reste parfaitement lisible. */
  z-index: 0;
  /* Les archives débordent volontairement sur les côtés (on n'en voit
     qu'un fragment). Sans clip, ce débordement rendait la page
     défilable horizontalement sur mobile.
     `clip` et non `hidden` : hidden sur un axe force l'autre en `auto`
     et couperait les images en pleine dérive verticale. */
  overflow-x: clip;
  overflow-y: visible;
}

.depth__item {
  position: absolute;
  margin: 0;
  width: var(--w);
  /* Le <figure> ne porte QUE la translation de parallaxe (écrite par le
     JS à chaque frame) ; la rotation vit sur l'image. Séparer les deux
     évite de recomposer la rotation à chaque déplacement. */
  transform: translate3d(0, 0, 0);
}

/* Ombre portée.
   Une `drop-shadow` classique ne fonctionne pas ici : les bords des
   images sont fondus par un masque, et ce masque effacerait l'ombre en
   même temps. On dessine donc une ombre qui épouse la MÊME forme
   elliptique que le fondu, décalée et floutée derrière l'image.
   Plus l'objet est loin, plus son ombre est diffuse et pâle — c'est ce
   qui achève de séparer les deux plans. */
.depth__item::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: rotate(var(--rot)) translate(var(--sh-x), var(--sh-y));
  /* Brun chaud plutôt que noir pur : sur le sable clair du bas de page,
     une ombre noire vire au gris sale. */
  background: radial-gradient(
    ellipse 70% 70% at 50% 50%,
    rgba(44, 30, 17, 0.85) 40%,
    transparent 100%
  );
  filter: blur(var(--sh-blur));
  opacity: 0;
  transition: opacity 1.4s var(--ease-out);
}

.depth__item.is-ready::after {
  /* L'ombre s'efface à mesure que le fond s'éclaircit : c'est elle qui
     créait les auréoles sombres sur le sable en bas de page. */
  opacity: calc(var(--sh-o) * (1 - var(--lift, 0) * 0.82));
}

.depth__item img {
  position: relative;
  z-index: 1;
}

.depth__item--p2 {
  --sh-x: 6px;
  --sh-y: 16px;
  --sh-blur: 24px;
  --sh-o: 0.55;
}

.depth__item--p3 {
  --sh-x: 10px;
  --sh-y: 30px;
  --sh-blur: 52px;
  --sh-o: 0.34;
}

.depth__item img {
  display: block;
  width: 100%;
  height: auto;
  transform: rotate(var(--rot));
  /* grayscale AVANT sepia : les sources vont du papier jauni au tirage
     gris. Les désaturer d'abord les ramène toutes dans la même teinte,
     sinon le fond paraît bariolé — c'est ce qui fait « cheap ».
     contrast et brightness suivent --lift (0 en haut de page, 1 en bas) :
     sur fond sombre l'archive est sombre, sur fond sable elle s'éclaircit
     et s'aplatit pour rester un filigrane au lieu de faire une tache. */
  filter: grayscale(1) sepia(0.5)
    contrast(calc(0.85 - var(--lift, 0) * 0.44))
    brightness(calc(0.62 + var(--lift, 0) * 0.78))
    blur(var(--blur));
  /* Bords fondus. Beaucoup de ces scans ont un cadre noir ou un fond
     quadrillé de numérisation : sans ce dégradé, ces bordures
     apparaîtraient en rectangles nets et trahiraient le montage.
     Le fondu les efface et donne à chaque archive l'air d'affleurer
     depuis le fond plutôt que d'y être collée. */
  -webkit-mask-image: radial-gradient(
    ellipse 74% 74% at 50% 50%,
    #000 38%,
    transparent 100%
  );
  mask-image: radial-gradient(
    ellipse 74% 74% at 50% 50%,
    #000 38%,
    transparent 100%
  );
  /* Révélée seulement une fois entièrement décodée (voir main.js). */
  opacity: 0;
  transition: opacity 1.4s var(--ease-out);
}

.depth__item.is-ready img {
  opacity: var(--o);
}

/* Repli : archive déjà à l'écran au moment où elle devient disponible.
   On la pose d'un coup — un fondu lent y serait bien plus visible. */
.depth__item.is-instant img,
.depth__item.is-instant::after {
  transition: none;
}

@media (min-width: 900px) {
  .depth__item--p3 {
    /* L'arrière-plan déborde volontairement du bloc : on ne voit qu'un
       fragment d'archive, ce qui creuse la perspective. */
    margin-left: -6%;
  }
}

.step__card {
  position: relative;
  /* Au-dessus des archives de fond. */
  z-index: 1;
  /* width:auto — la carte occupe la place restante à droite de la
     courbe. Un width:100% déborderait de la marge gauche. */
  width: auto;
  max-width: 27rem;
  margin-left: 4rem;
  border: 1px solid rgba(240, 232, 218, 0.11);
  border-radius: 1.15rem;
  /* Pas de backdrop-filter : derrière les cartes il n'y a qu'un aplat
     de couleur, le flou ne se verrait pas — mais il obligerait le
     navigateur à refiltrer 8 zones à chaque image de défilement. */
  background: rgba(14, 11, 8, 0.82);
  box-shadow:
    0 30px 60px -28px rgba(0, 0, 0, 0.75),
    inset 0 1px 0 rgba(255, 245, 230, 0.05);
  overflow: hidden;
  transition:
    transform 0.55s var(--ease-out),
    box-shadow 0.55s var(--ease-out),
    border-color 0.55s var(--ease-out);
}

.js .step__card {
  opacity: 0;
  transform: translateY(46px) scale(0.97);
  transition:
    opacity 0.9s var(--ease-out),
    transform 0.9s var(--ease-out),
    box-shadow 0.55s var(--ease-out),
    border-color 0.55s var(--ease-out);
}

.js .step.is-revealed .step__card {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* Toutes les cartes portent une trace d'orangé : cela relie le premier
   plan au tracé sans attirer l'œil. */
.step.is-revealed .step__card {
  border-color: rgba(200, 121, 31, calc(0.16 * var(--glow, 1)));
}

/* La carte au droit du véhicule s'allume.
   Le halo est composé de QUATRE couches à décroissance différente : un
   liseré net, puis trois diffusions de plus en plus larges et pâles.
   Un unique gros flou orange donnerait l'effet néon bon marché ; c'est
   l'étagement des retombées qui fait la profondeur. */
.step.is-active .step__card {
  border-color: rgba(214, 138, 52, calc(0.55 * var(--glow, 1)));
  box-shadow:
    0 34px 70px -26px rgba(0, 0, 0, 0.8),
    0 0 0 1px rgba(200, 121, 31, calc(0.22 * var(--glow, 1))),
    0 0 18px -6px rgba(200, 121, 31, calc(0.34 * var(--glow, 1))),
    0 0 46px -10px rgba(200, 121, 31, calc(0.24 * var(--glow, 1))),
    0 0 110px -20px rgba(200, 121, 31, calc(0.16 * var(--glow, 1))),
    inset 0 1px 0 rgba(255, 245, 230, 0.06);
}

/* Les archives sont montrées EN ENTIER : jamais de recadrage.
   La hauteur est bornée, l'image se centre dans sa plaque. */
.step__media {
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.9rem;
  background: rgba(0, 0, 0, 0.3);
  border-bottom: 1px solid rgba(240, 232, 218, 0.07);
}

.step__media img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 24rem;
  border-radius: 0.3rem;
  box-shadow: 0 8px 22px -10px rgba(0, 0, 0, 0.8);
  filter: sepia(0.38) contrast(1.04) brightness(0.93) saturate(0.82);
  transition: filter 0.7s var(--ease-out);
}

.step.is-active .step__media img {
  filter: sepia(0.16) contrast(1.06) brightness(1.02) saturate(0.92);
}

.step__body {
  padding: 1.6rem 1.7rem 1.8rem;
}

.step__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin: 0 0 0.9rem;
  font-family: var(--font-mono);
  font-size: 0.63rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #a2907a;
}

.step__no {
  color: var(--accent);
}

.step__sep {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.6;
}

.step__title {
  margin: 0 0 0.7rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.35rem, 2.6vw, 1.75rem);
  line-height: 1.14;
  color: #f7f1e6;
}

.step__teaser {
  margin: 0;
  font-size: 0.965rem;
  line-height: 1.72;
  color: #c3b5a2;
}

/* Tablettes : on garde la colonne unique, mais on resserre le bloc
   pour qu'il reste centré au lieu de coller au bord gauche. */
@media (min-width: 640px) and (max-width: 899px) {
  .journey__track {
    max-width: 36rem;
  }
}

/* -- alternance gauche / droite -- */

@media (min-width: 900px) {
  /* La colonne centrale est la « voie » réservée à la courbe :
     le véhicule y serpente sans jamais chevaucher une carte. */
  .step {
    display: grid;
    grid-template-columns: 1fr clamp(200px, 24vw, 340px) 1fr;
    align-items: center;
  }

  .step__card {
    width: 100%;
    max-width: 26rem;
    margin-left: 0;
  }

  .step--left .step__card {
    grid-column: 1;
    justify-self: end;
  }

  .step--right .step__card {
    grid-column: 3;
    justify-self: start;
  }

  .js .step--left .step__card {
    transform: translateY(46px) translateX(-26px) scale(0.97);
  }

  .js .step--right .step__card {
    transform: translateY(46px) translateX(26px) scale(0.97);
  }

  .js .step.is-revealed .step__card {
    transform: translateY(0) translateX(0) scale(1);
  }

  .step--left .step__body {
    text-align: right;
  }

  .step--left .step__meta {
    justify-content: flex-end;
  }
}

/* ---------- 6. Outro / CTA ---------- */

.outro {
  position: relative;
  /* Même raison que pour le hero : les archives de la dernière étape
     dérivent vers le bas et ne doivent pas passer devant le texte. */
  z-index: 2;
  padding: clamp(7rem, 16vh, 12rem) var(--gutter) 0;
}

.outro__inner {
  max-width: 46rem;
  margin: 0 auto;
  text-align: center;
}

.outro__crest {
  width: 74px;
  height: auto;
  margin: 0 auto 2.25rem;
  opacity: 0.42;
  filter: grayscale(1);
  mix-blend-mode: multiply;
}

.outro__title {
  margin: 0 0 2.5rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.9rem, 5vw, 3.1rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.outro__title em {
  opacity: 0.62;
}

.milestones {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(2rem, 7vw, 5rem);
  margin: 0 0 clamp(2.5rem, 6vw, 3.5rem);
  padding: 1.6rem 0;
  border-top: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  border-color: color-mix(in srgb, var(--fg) 18%, transparent);
}

.milestone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
}

.milestone__year {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  line-height: 1;
  color: var(--accent-ink);
}

.milestone__label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  opacity: 0.62;
}

.outro__text strong {
  font-weight: 600;
  color: var(--accent-ink);
}

.outro__text p {
  margin: 0 0 1.4rem;
  font-size: clamp(1rem, 1.5vw, 1.09rem);
  line-height: 1.8;
  opacity: 0.78;
}

.outro__text p:last-child {
  margin-bottom: 0;
}

/* ---------- L'appel à l'action ----------
   Il vit sur le sable clair du bas de page. Une pastille noire y était le
   seul élément vraiment noir du site : elle se posait dessus comme un
   corps étranger, sans lien avec le langage orangé de la chronologie.
   D'où une matière d'ambre — c'est l'action principale de la page, elle a
   le droit d'en être l'élément le plus lumineux.

   Le texte est sombre, et non blanc : sur cet ambre, du blanc plafonne à
   2,9:1 de contraste. Le brun profond atteint 5:1 même sur la partie la
   plus foncée du dégradé. */

.cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 1rem;
  margin-top: clamp(2.75rem, 6vw, 4rem);
  padding: 1.15rem 2.5rem;
  border-radius: 999px;
  overflow: hidden;
  isolation: isolate;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  color: #1e1408;
  /* Dégradé très resserré, centré sur --accent : de quoi éviter l'aplat
     mort, pas de quoi faire un bouton verni. Un écart marqué et un liseré
     de lumière franc donnaient un rendu plastique, à contre-emploi sur un
     site d'archives. */
  background: linear-gradient(168deg, #d38428 0%, #bc6e11 100%);
  /* Quatre retombées plutôt qu'une seule grosse ombre — même principe que
     pour le halo de la chronologie. Un liseré net, un contact rapproché,
     une diffusion large, et une lumière ténue sur l'arête supérieure qui
     donne son épaisseur à la pastille. Toutes sont CHAUDES : une ombre
     noire sur du sable fait une salissure grise. */
  box-shadow:
    inset 0 1px 0 rgba(255, 238, 212, 0.26),
    0 0 0 1px rgba(116, 62, 6, 0.28),
    0 2px 4px -2px rgba(80, 42, 4, 0.4),
    0 12px 26px -14px rgba(150, 82, 10, 0.55);
  transition:
    transform 0.45s var(--ease-spring),
    box-shadow 0.45s var(--ease-out);
}

/* Voile clair qui se lève au survol. Il remplace l'ancien remplissage
   glissant : un aplat qui monte par le bas se lit comme un chargement,
   alors qu'un éclaircissement se lit comme une réponse au geste. */
.cta__fill {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(168deg, #e59a35 0%, #cd7d18 100%);
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
}

.cta:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 1px 0 rgba(255, 242, 220, 0.34),
    0 0 0 1px rgba(116, 62, 6, 0.3),
    0 3px 7px -3px rgba(80, 42, 4, 0.38),
    0 18px 38px -16px rgba(190, 112, 22, 0.7);
}

.cta:hover .cta__fill {
  opacity: 1;
}

.cta:active {
  transform: translateY(0);
  box-shadow:
    inset 0 2px 5px rgba(88, 46, 4, 0.35),
    0 0 0 1px rgba(122, 66, 8, 0.3),
    0 2px 6px -3px rgba(88, 46, 4, 0.4);
}

.cta:focus-visible {
  outline: 2px solid #7a4208;
  outline-offset: 3px;
}

.cta__arrow {
  display: inline-flex;
  width: 17px;
  transition: transform 0.45s var(--ease-spring);
}

.cta__arrow svg {
  width: 100%;
  height: auto;
}

.cta:hover .cta__arrow {
  transform: translateX(5px);
}

@media (prefers-reduced-motion: reduce) {
  .cta,
  .cta__arrow,
  .cta__fill {
    transition-duration: 0.01ms;
  }

  .cta:hover {
    transform: none;
  }
}

.outro__note {
  margin: 2.25rem 0 0;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.45;
}

/* ---------- Formulaire de soutien ----------
   Rien n'est écrit en couleur fixe : tout dérive de --fg, qui bascule du
   crème au brun foncé au fil du scroll. Le formulaire se trouvant en bas
   de page, il est lu sur le sable clair — mais il reste juste si le
   visiteur arrive directement par l'ancre, avant que le fond n'ait viré. */

.soutien {
  margin-top: clamp(5rem, 12vh, 8rem);
  padding-top: clamp(3rem, 8vh, 5rem);
  border-top: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
  text-align: center;
}

.soutien__title {
  margin: 0.9rem 0 1.1rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  line-height: 1.15;
}

.soutien__lede {
  max-width: 34rem;
  margin: 0 auto clamp(2.25rem, 5vw, 3rem);
  font-size: clamp(0.97rem, 1.4vw, 1.05rem);
  line-height: 1.75;
  opacity: 0.78;
}

.soutien__lede strong {
  font-weight: 600;
  color: var(--accent-ink);
}

.soutien__form {
  max-width: 34rem;
  margin: 0 auto;
  text-align: left;
}

/* Pot de miel : hors du champ visuel ET du lecteur d'écran, mais bien
   présent dans le DOM pour les robots. `display: none` ne suffit pas —
   certains scripts l'ignorent justement parce que c'est trop évident. */
.soutien__pot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.soutien__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 1.1rem;
}

@media (max-width: 33rem) {
  .soutien__grid {
    grid-template-columns: 1fr;
  }
}

.field {
  margin: 0 0 1.35rem;
}

.field__label {
  display: block;
  margin-bottom: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  /* 0.78 et non 0.62 comme les libellés décoratifs de la page : à cette
     taille, sur le sable clair du bas de page, 0.62 tombait sous le
     rapport de contraste de 4,5:1. Un intitulé de champ doit se lire. */
  opacity: 0.78;
}

.field__input {
  width: 100%;
  /* 48px : au-dessus de la cible tactile minimale de 44px. */
  min-height: 48px;
  padding: 0.85rem 1.05rem;
  border: 1px solid color-mix(in srgb, var(--fg) 22%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 1rem; /* < 16px déclencherait un zoom automatique sur iOS */
  line-height: 1.5;
  transition:
    border-color 0.35s var(--ease-out),
    background 0.35s var(--ease-out),
    box-shadow 0.35s var(--ease-out);
}

.field__input::placeholder {
  color: color-mix(in srgb, var(--fg) 45%, transparent);
}

.field__input:hover {
  border-color: color-mix(in srgb, var(--fg) 34%, transparent);
}

.field__input:focus {
  outline: none;
  border-color: var(--accent);
  background: color-mix(in srgb, var(--fg) 8%, transparent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

.field__input.is-error {
  border-color: #c0442e;
}

.field__input--area {
  min-height: 9rem;
  resize: vertical;
}

/* Le chevron du <select> : la flèche native ne se met pas à la couleur du
   texte et jure sur le fond sombre du haut de page. */
.field__select {
  position: relative;
  display: block;
}

.field__select select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 3rem;
  cursor: pointer;
}

.field__chevron {
  position: absolute;
  top: 50%;
  right: 1.05rem;
  width: 18px;
  transform: translateY(-50%);
  opacity: 0.55;
  pointer-events: none;
}

.field__chevron svg {
  display: block;
  width: 100%;
  height: auto;
}

.field__error {
  display: block;
  min-height: 1.1rem;
  margin-top: 0.4rem;
  font-size: 0.82rem;
  line-height: 1.35;
  color: #d0563c;
}

/* Vide la plupart du temps : sans cela chaque message d'erreur
   déplacerait tout ce qui suit au moment où il apparaît. */
.field__error:empty {
  min-height: 0;
  margin-top: 0;
}

.field--check {
  margin-top: 0.2rem;
}

.check {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  cursor: pointer;
}

.check input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.check__box {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-top: 0.1rem;
  border: 1px solid color-mix(in srgb, var(--fg) 30%, transparent);
  border-radius: 6px;
  background: color-mix(in srgb, var(--fg) 5%, transparent);
  transition:
    background 0.3s var(--ease-out),
    border-color 0.3s var(--ease-out);
}

.check__box svg {
  width: 13px;
  height: 13px;
  color: var(--ink);
  opacity: 0;
  transform: scale(0.6);
  transition:
    opacity 0.25s var(--ease-out),
    transform 0.3s var(--ease-spring);
}

.check input:checked + .check__box {
  border-color: var(--accent);
  background: var(--accent);
}

.check input:checked + .check__box svg {
  opacity: 1;
  transform: scale(1);
}

.check input:focus-visible + .check__box {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.check__text {
  font-size: 0.86rem;
  line-height: 1.6;
  opacity: 0.72;
}

.soutien__alerte {
  margin: 0 0 1.4rem;
  padding: 0.95rem 1.15rem;
  border-radius: 10px;
  border: 1px solid;
  font-size: 0.92rem;
  line-height: 1.55;
}

.soutien__alerte.is-hidden {
  display: none;
}

.soutien__alerte--ok {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--fg);
}

.soutien__alerte--ko {
  border-color: rgba(192, 68, 46, 0.5);
  background: rgba(192, 68, 46, 0.1);
  color: #d0563c;
}

.cta--submit {
  width: 100%;
  justify-content: center;
  margin-top: 1.4rem;
  border: 0;
  cursor: pointer;
  font-family: var(--font-mono);
}

.cta--submit[disabled],
.cta--submit[disabled]:hover {
  cursor: progress;
  transform: none;
  /* Désaturé plutôt que transparent : à travers une pastille translucide,
     le texte du formulaire réapparaîtrait derrière l'ambre. */
  background: linear-gradient(175deg, #c9ac82 0%, #b39463 100%);
  color: #3a2e1c;
  box-shadow:
    inset 0 1px 0 rgba(255, 240, 214, 0.35),
    0 0 0 1px rgba(122, 90, 40, 0.2),
    0 2px 5px -2px rgba(88, 66, 24, 0.3);
}

.cta--submit[disabled] .cta__fill {
  opacity: 0;
}

/* Sur les écrans les plus étroits, l'interlettrage faisait passer le
   libellé à la ligne à l'intérieur de la pastille. La règle vaut pour
   TOUS les boutons : « Soutenir le projet » est presque aussi long
   qu'« Envoyer le message ». */
@media (max-width: 23rem) {
  .cta {
    gap: 0.6rem;
    padding-left: 1.2rem;
    padding-right: 1.2rem;
    font-size: 0.66rem;
    letter-spacing: 0.14em;
  }
}

.soutien__direct {
  margin: 1.6rem 0 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.72;
}

.soutien__direct a {
  /* Pas de capitales sur l'adresse : une adresse e-mail se recopie, et
     « CONTACT@NLION.FR » se recopie mal. Le reste de la ligne les garde. */
  text-transform: none;
  letter-spacing: 0.04em;
  font-size: 0.74rem;
  color: var(--accent-ink);
  text-underline-offset: 3px;
}

.site-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.7rem;
  margin-top: clamp(6rem, 14vh, 9rem);
  padding: 2.25rem 0 3rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  /* L'atténuation passe par la couleur, et non par `opacity` : une
     opacité sur le bloc s'appliquerait aussi au lien des mentions
     légales, qu'aucun enfant ne peut ensuite rendre plus lisible. */
  color: color-mix(in srgb, var(--fg) 42%, transparent);
}

.site-footer a {
  color: color-mix(in srgb, var(--fg) 78%, transparent);
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  transition: color 0.3s var(--ease-out);
}

.site-footer a:hover,
.site-footer a:focus-visible {
  color: var(--accent);
}

/* ---------- Mentions légales ----------
   Page statique, sans JavaScript : --bg et --fg gardent leurs valeurs de
   départ, donc le sépia sombre du haut du site. Une page de texte long
   n'a besoin ni de l'inertie, ni de la chronologie, ni de la parallaxe —
   d'où un gabarit à part plutôt qu'une section de plus. */

.legal {
  max-width: 42rem;
  margin: 0 auto;
  padding: clamp(4rem, 12vh, 7rem) var(--gutter) 0;
}

.legal__head {
  margin-bottom: clamp(3rem, 8vw, 4.5rem);
  padding-bottom: clamp(2rem, 5vw, 2.75rem);
  border-bottom: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
  text-align: center;
}

.legal__title {
  margin: 0.9rem 0 1.2rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.9rem, 5vw, 3rem);
  line-height: 1.12;
  letter-spacing: -0.01em;
}

.legal__title em {
  opacity: 0.62;
}

.legal__maj {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.45;
}

.legal__bloc {
  margin-bottom: clamp(2.75rem, 7vw, 3.75rem);
}

.legal__bloc h2 {
  margin: 0 0 1.2rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.25rem, 2.6vw, 1.6rem);
  line-height: 1.25;
}

.legal__bloc h3 {
  margin: 2rem 0 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

.legal__bloc p,
.legal__bloc li {
  font-size: clamp(0.95rem, 1.4vw, 1.02rem);
  line-height: 1.8;
  opacity: 0.82;
}

.legal__bloc p {
  margin: 0 0 1.1rem;
}

.legal__bloc ul {
  margin: 0 0 1.1rem;
  padding-left: 1.1rem;
}

.legal__bloc li {
  margin-bottom: 0.5rem;
  padding-left: 0.35rem;
}

.legal__bloc li::marker {
  color: var(--accent);
}

.legal__bloc strong {
  font-weight: 500;
  opacity: 1;
}

.legal a {
  color: var(--accent);
  text-underline-offset: 3px;
}

.legal a:hover {
  text-decoration-thickness: 2px;
}

/* Deux colonnes tant que la place le permet : un intitulé court en
   petites capitales, la valeur en face. Sous 30rem, tout s'empile. */
.legal__liste {
  display: grid;
  grid-template-columns: minmax(9rem, auto) 1fr;
  gap: 0.55rem 1.6rem;
  margin: 0;
}

.legal__liste dt {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  line-height: 1.9;
  opacity: 0.55;
}

.legal__liste dd {
  margin: 0;
  font-size: clamp(0.95rem, 1.4vw, 1.02rem);
  line-height: 1.75;
  opacity: 0.9;
}

@media (max-width: 30rem) {
  .legal__liste {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }

  .legal__liste dd {
    margin-bottom: 1rem;
  }
}

.legal__retour {
  margin: 0;
  text-align: center;
}

/* Variante discrète, pour le retour depuis les mentions légales : une
   page de texte n'a pas à être dominée par un bouton d'ambre.
   La couleur est posée ici explicitement — sans cela elle dépendrait de
   `.legal a`, qui l'emporte par spécificité, et un simple déplacement de
   la règle changerait le rendu sans qu'on comprenne pourquoi.
   Ambre sur le sépia sombre : 5,9:1 de contraste. */
.legal .cta--ghost,
.cta--ghost {
  margin-top: 0;
  color: var(--accent);
  background: transparent;
  text-decoration: none;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 38%, transparent);
}

.cta--ghost .cta__fill {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}

.legal .cta--ghost:hover,
.cta--ghost:hover {
  color: var(--accent);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 72%, transparent),
    0 16px 32px -18px rgba(200, 121, 31, 0.5);
}

.cta--ghost:active {
  transform: translateY(1px);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 60%, transparent);
}

/* ---------- Retour en haut ----------
   Réservée au bureau, à la demande. Sur téléphone, une pastille flottante
   mange une part notable d'un petit écran et recouvre le formulaire.

   Fond et texte dérivent de --bg et --fg : la pastille s'inverse donc
   d'elle-même quand la page passe du sépia au sable, sans réglage. */

.to-top {
  display: none;
}

@media (min-width: 900px) and (pointer: fine) {
  .to-top {
    position: fixed;
    right: clamp(1.5rem, 3vw, 2.75rem);
    bottom: clamp(1.5rem, 3vw, 2.75rem);
    /* Au-dessus du grain (40) et de la vignette (30), qui couvrent tout
       l'écran. Les deux laissent passer les clics, mais pas les pixels. */
    z-index: 45;
    display: grid;
    place-items: center;
    /* 48 px : au-dessus de la cible minimale de 44 px. */
    width: 48px;
    height: 48px;
    border-radius: 999px;
    color: var(--fg);
    background: var(--bg);
    box-shadow:
      inset 0 0 0 1px color-mix(in srgb, var(--fg) 24%, transparent),
      0 10px 26px -14px rgba(0, 0, 0, 0.55);
    opacity: 0;
    /* `visibility` retire la pastille du parcours de tabulation tant
       qu'elle est cachée — une simple opacité à 0 la laisserait
       focusable, et le clavier s'arrêterait sur un lien invisible. */
    visibility: hidden;
    transform: translateY(10px);
    transition:
      opacity 0.45s var(--ease-out),
      transform 0.45s var(--ease-out),
      box-shadow 0.35s var(--ease-out),
      color 0.35s var(--ease-out),
      visibility 0s linear 0.45s;
  }

  /* `.page-legal` : cette page ne charge pas main.js, personne n'y
     ajoutera jamais `is-visible`. La pastille y reste donc affichée. */
  .to-top.is-visible,
  .page-legal .to-top {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0s;
  }

  .to-top svg {
    width: 19px;
    height: 19px;
  }

  .to-top:hover {
    color: var(--accent-ink);
    transform: translateY(-3px);
    box-shadow:
      inset 0 0 0 1px color-mix(in srgb, var(--accent) 62%, transparent),
      0 16px 32px -16px rgba(200, 121, 31, 0.5);
  }

  .to-top:active {
    transform: translateY(0);
  }

  .to-top:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .to-top {
    transition-duration: 0.01ms;
    transform: none;
  }

  .to-top:hover {
    transform: none;
  }
}

/* -- révélation générique au scroll -- */

.js .reveal {
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity 0.95s var(--ease-out),
    transform 0.95s var(--ease-out);
}

.js .reveal.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 599px) {
  .hero__kicker {
    gap: 0;
    letter-spacing: 0.28em;
  }

  .hero__kicker .rule {
    display: none;
  }
}

/* ---------- 7. Allègements mobile / écrans tactiles ----------
   Le grain en mix-blend-mode oblige à recomposer tout le viewport à
   chaque image : sur téléphone, cela suffit à saccader le défilement.
   On le retire, l'ambiance étant portée par le fond. */

@media (max-width: 899px) {
  /* Sur une colonne, les archives de fond sont réduites pour rester
     une texture et ne jamais concurrencer le texte. */
  .depth__item {
    width: calc(var(--w) * 0.5);
  }

  .depth__item--p3 {
    margin-left: 0;
  }
}

/* Accroche aux étapes sur tactile.
   On utilise le scroll-snap NATIF plutôt que du JavaScript : intercepter
   le scroll au doigt casse l'élan géré par le système. `proximity` (et
   non `mandatory`) n'accroche que si l'on s'arrête déjà près d'une étape,
   donc un balayage rapide traverse la page sans jamais être retenu. */
@media (hover: none) and (pointer: coarse) {
  html {
    scroll-snap-type: y proximity;
  }

  .step {
    scroll-snap-align: center;
  }
}

@media (hover: none) and (pointer: coarse) {
  /* Un seul plan sur tactile : moins de surfaces à composer pendant
     le défilement, et une lecture plus calme sur petit écran. */
  .depth__item--p3 {
    display: none;
  }

  /* ---- Ce qui coûte cher pendant le défilement ----

     Le tracé occupe toute la hauteur du bloc, soit ~3 600 px sur
     téléphone. Un `filter: blur()` sur un objet de cette taille oblige le
     navigateur à fabriquer une texture hors écran de la hauteur entière,
     puis à la recomposer dès que l'opacité change — et elle change au
     défilement, `--glow` suivant l'éclaircissement du fond. On remplace
     donc le flou par un trait large et translucide : même diffusion à
     l'œil, sans texture intermédiaire. */
  .path-halo {
    stroke-width: 7;
    filter: none;
    opacity: calc(0.14 * var(--glow, 1));
  }

  /* Le rayonnement de la portion parcourue est un SECOND trait dont le
     `stroke-dashoffset` change à chaque image : le navigateur re-rastérise
     alors deux traits au lieu d'un. Sur un écran de téléphone, où la
     courbe ne fait qu'une trentaine de pixels de large, le gain visuel ne
     justifie pas d'en doubler le coût. Le halo permanent ci-dessus suffit
     à porter la lueur. */
  .path-progress-glow {
    display: none;
  }

  /* Une seule retombée au lieu de deux. Le véhicule est le seul élément
     repositionné à chaque image : son filtre est recalculé à chaque fois,
     et deux ombres, c'est deux passes de flou. */
  .marker {
    filter: drop-shadow(0 0 6px rgba(200, 121, 31, calc(0.55 * var(--glow, 1))));
  }

  /* Les ombres reposent sur un flou de 24 à 52 px : autant de surfaces
     à rastériser. Sur téléphone le gain visuel ne vaut pas le coût. */
  .depth__item::after {
    display: none;
  }
}

@media (max-width: 899px), (hover: none) and (pointer: coarse) {
  .grain {
    display: none;
  }

  .step__card {
    background: rgba(16, 12, 9, 0.94);
  }

  .hero__video,
  .marker__halo {
    animation: none;
  }

  .marker__halo {
    opacity: 0.14;
  }

  #scroll-bg {
    will-change: auto;
  }
}

/* ---------- 8. Accessibilité / mouvement réduit ---------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .grain,
  .hero__video,
  .marker__halo,
  .hero__scroll-line {
    animation: none !important;
  }

  .js .hero__letter,
  .js .hero__given,
  .js .hero__russian,
  .js .hero__lede,
  .js .hero__sub,
  .js .hero__kicker,
  .js .hero__scroll,
  .js .step__card,
  .js .reveal {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    animation: none !important;
    transition: none !important;
  }

  .marker__icon {
    transition: none;
  }
}
