/* =========================================================
   LUDO STUDIO — feuille de style principale
   Noir & blanc, minimaliste, typographie large.
   ========================================================= */

/* ---------- 1. Tokens ---------- */
:root {
  --black: #000000;
  --near-black: #0a0a0a;
  --ink: #101010;
  --white: #ffffff;
  --grey-90: #f2f2f2;
  --grey-60: #9a9a9a;
  --grey-40: #6a6a6a;
  --grey-20: #2a2a2a;
  --line: rgba(255, 255, 255, 0.14);
  --line-strong: rgba(255, 255, 255, 0.3);

  --bg: var(--black);
  --fg: var(--white);

  --font-sans: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;

  --pad-x: clamp(20px, 5vw, 72px);
  --gap: clamp(16px, 2.2vw, 28px);
  --max: 1680px;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);   /* sortie très douce, longue traîne */
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  /* Depart vif, arret long, aucun rebond : la courbe des panneaux iOS.
     Ecrite en clair a onze endroits du bloc Process, elle ne pouvait que
     diverger — comme le diametre du cadran et le nom d'une image l'ont
     fait aujourd'hui. */
  --ease-pose: cubic-bezier(0.32, 0.72, 0, 1);
  --dur: 0.7s;

  --glass: rgba(255, 255, 255, 0.09);
  --glass-line: rgba(255, 255, 255, 0.22);
}

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.5;
  font-weight: 400;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
h1, h2, h3, h4, p, figure, ul { margin: 0; }
ul { padding: 0; list-style: none; }

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

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

/* ---------- 3. Utilitaires ---------- */
.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}

.eyebrow {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--grey-60);
  font-weight: 500;
}

.display {
  font-weight: 600;
  line-height: 0.92;
  letter-spacing: -0.045em;
  text-transform: uppercase;
}

.h-xl { font-size: clamp(36px, 6.4vw, 100px); }
.h-lg { font-size: clamp(30px, 4.6vw, 68px); }
.h-md { font-size: clamp(22px, 2.8vw, 40px); }

.lead {
  font-size: clamp(16px, 1.4vw, 21px);
  line-height: 1.45;
  color: var(--grey-90);
  max-width: 46ch;
}

.muted { color: var(--grey-60); }

.section {
  padding-block: clamp(72px, 11vw, 168px);
}

.rule {
  height: 1px;
  background: var(--line);
  border: 0;
  margin: 0;
}

/* Lien souligné qui se dessine au survol */
.link {
  position: relative;
  display: inline-block;
  padding-bottom: 2px;
}
.link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.5s var(--ease);
}
.link:hover::after { transform: scaleX(1); transform-origin: left; }

/* Bouton */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 15px 26px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 500;
  transition: background 0.4s var(--ease), color 0.4s var(--ease), border-color 0.4s var(--ease);
}
.btn:hover { background: var(--white); color: var(--black); border-color: var(--white); }
.btn--solid { background: var(--white); color: var(--black); border-color: var(--white); }
.btn--solid:hover { background: transparent; color: var(--white); border-color: var(--line-strong); }
.btn .arrow { transition: transform 0.4s var(--ease); }
.btn:hover .arrow { transform: translateX(4px); }

/* ---------- 4. Header ---------- */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 20px var(--pad-x);
  mix-blend-mode: difference;
  transition: transform 0.5s var(--ease), opacity 0.3s linear;
}
.header--hidden { transform: translateY(-110%); }

.brand {
  font-size: 13px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  font-weight: 600;
  white-space: nowrap;
}

/* LE LOGOTYPE DE L'EN-TETE — UNE BOITE PEINTE A TRAVERS UN MASQUE.

   Le nom du studio n'est plus du texte mais un trace. Le poser en `<img>`
   le figerait en blanc ; en SVG inline, ses 23 Ko de chemins seraient
   recopies dans les cinq pages. Un masque donne les deux : UN fichier mis
   en cache, et une couleur qui reste celle du texte — donc qui suit le
   survol, le menu ouvert et le passage de l'en-tete en clair.

   ON DONNE LA HAUTEUR, LA LARGEUR EN DECOULE. Le rapport du trace est
   1189 sur 135, soit 8,81 pour 1. Ecrire la largeur ferait varier la
   hauteur d'un ecran a l'autre et le mot ne s'alignerait plus sur la
   navigation. 11px de haut : la hauteur de capitale que rendait le nom en
   texte a 13px de corps.

   Le prefixe -webkit- n'est pas decoratif : Safari ne connait `mask` sans
   prefixe que depuis 15.4, et c'est precisement le navigateur de la
   moitie des visiteurs d'un site de realisateur. */
/* L'EN-TETE EST UNE BOITE, PARCE QU'ELLE PORTE MAINTENANT DEUX OBJETS.

   Le signe et le mot. `.brand` etait un simple bloc de reglages
   typographiques ; sur l'accueil, v30.css en faisait deja une boite flex,
   mais les trois autres pages ne chargent pas v30.css et le signe s'y
   serait pose au-dessus du mot. La boite descend donc ici, dans la feuille
   que les cinq partagent.

   Les archives ont deja `display: inline-flex` sur `.brand` via leur
   propre feuille : la declaration est identique, elle ne change rien pour
   elles. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

/* L'EN-TETE EST UNE BOITE, PARCE QU'ELLE PORTE DEUX OBJETS.

   Le signe et le mot. Sur l'accueil, v30.css en faisait deja une boite
   flex ; les trois autres pages ne chargent pas v30.css et le signe s'y
   serait pose au-dessus du mot. La boite descend donc dans la feuille que
   les cinq partagent. Les archives ont deja cette declaration via leur
   propre feuille : identique, elle ne change rien pour elles. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}

/* LE SIGNE DE L'EN-TETE — la meme hauteur que le mot, et non 1,4 fois.

   Dans le hero, signe et mot font tous deux 18 px de haut : le signe EST
   a la hauteur de capitale, donc son fut vaut celui des lettres. Le meme
   raisonnement ici donne 11 px, la hauteur du mot. Ecrire un rapport —
   comme l'ancien carre qui valait 1,44 fois le nom — casserait ce calage
   et le signe redeviendrait plus gras que les lettres.

   Classe distincte de `.brand-mark` : ce nom designe encore l'ancien carre
   dans une vingtaine de pages conservees, qui chargent cette feuille.
   Reutiliser le nom aurait redimensionne leur signe. */
/* 13 px ET NON 12,5 : UNE HAUTEUR ENTIERE.

   Le rapport releve sur la maquette donne 1,14 x 11 = 12,54 px, et j'avais
   ecrit 12,5. Sur un ecran sans densite double, une boite de 12,5 px a son
   bord inferieur AU MILIEU d'un pixel : le trait du cadre s'y etale sur
   deux rangees au lieu d'une. Mesure a 1x — le haut donne 173 de
   luminosite sur une seule rangee, le bas 120 puis 76 sur deux. Rien n'est
   perdu, mais le bas et la droite paraissent plus pales que le haut et la
   gauche, et ca se lit comme une coupure.

   A 13 px les quatre bords tombent sur des pixels entiers. Le rapport
   passe de 1,14 a 1,18 : quatre centiemes, invisibles a cette taille,
   contre un defaut qui se voyait.

   `overflow: visible` en plus : le trait du cadre est dessine exactement
   sur le bord du viewBox, ou un SVG est ecrete par defaut. Il n'y a donc
   aucune marge pour l'anticrenelage. La declaration ne change pas le
   dessin, elle empeche seulement qu'on lui en retire un morceau. */
/* HAUTEUR PAIRE, ET C'EST LA VRAIE REGLE — la taille entiere ne suffisait
   pas.

   J'avais corrige 12,5 en 13 en croyant regler le probleme : un entier,
   donc des bords sur des pixels pleins. Faux. L'en-tete fait 60 px et
   centre ses enfants : un enfant de 13 px se pose a (60-13)/2 = 23,5. Sa
   hauteur est entiere, sa POSITION ne l'est pas, et le trait du cadre se
   coupe en deux rangees en haut comme en bas.

   Dans un conteneur de hauteur PAIRE avec `align-items: center`, un enfant
   tombe sur des pixels pleins si et seulement si sa propre hauteur est
   paire. L'en-tete mesure 60 px au-dessus de 560 et 58 en dessous : pair
   dans les deux cas. Le signe passe donc a 14 et le mot a 12.

   Le rapport devient 14/12 = 1,167, soit exactement celui du hero
   (21/18). Les deux echelles sont enfin les memes. */
.brand-signe {
  height: 14px;
  width: auto;
  flex: none;
  display: block;
  overflow: visible;
  opacity: 0.92;
}

/* `:empty` N'EST PAS UNE COQUETTERIE, C'EST CE QUI PROTEGE LES ARCHIVES.

   Cette feuille est chargee par une vingtaine de pages conservees —
   index21 a index29, les bancs — dont l'en-tete porte encore le nom EN
   TEXTE dans un `.brand-nom`. Sans le selecteur, `background:
   currentColor` peindrait un rectangle plein par-dessus leur nom : vingt
   pages cassees par une regle ecrite pour l'accueil, et rien pour le
   signaler puisqu'on ne les ouvre plus.

   La boite du logotype est vide par construction. `:empty` distingue donc
   exactement les deux cas, sans classe supplementaire a maintenir. */
.brand-nom:empty {
  display: block;
  height: 12px;
  width: calc(12px * 8.81);
  background: currentColor;
  -webkit-mask: url(../brand/polar-mot.svg) no-repeat center / contain;
          mask: url(../brand/polar-mot.svg) no-repeat center / contain;
}

/* Sous 560 px, on RETRECIT au lieu de cacher. L'ancienne regle effacait le
   nom et laissait le carre — il n'y a plus de carre, donc cacher le mot
   viderait l'en-tete de son identite. */
/* Sous 560 px l'en-tete descend a 58 px, pair lui aussi : les deux
   hauteurs restent paires. */
@media (max-width: 560px) {
  .brand-signe { height: 12px; }
  .brand-nom:empty { height: 10px; width: calc(10px * 8.81); }
}

.nav {
  display: flex;
  align-items: center;
  gap: clamp(18px, 2.4vw, 38px);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 500;
}
.nav a[aria-current="page"]::after { transform: scaleX(1); }

.burger {
  display: none;
  width: 34px;
  height: 18px;
  position: relative;
}
.burger span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: currentColor;
  transition: transform 0.45s var(--ease), opacity 0.2s linear;
}
.burger span:nth-child(1) { top: 3px; }
.burger span:nth-child(2) { bottom: 3px; }
body.menu-open .burger span:nth-child(1) { transform: translateY(5.5px) rotate(45deg); }
body.menu-open .burger span:nth-child(2) { transform: translateY(-5.5px) rotate(-45deg); }

/* Menu plein écran (mobile) */
.menu {
  position: fixed;
  inset: 0;
  z-index: 80;
  background: var(--black);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 0 var(--pad-x);
  clip-path: inset(0 0 100% 0);
  transition: clip-path 0.8s var(--ease);
  pointer-events: none;
}
body.menu-open .menu { clip-path: inset(0 0 0 0); pointer-events: auto; }
.menu a {
  font-size: clamp(34px, 10vw, 76px);
  font-weight: 600;
  letter-spacing: -0.04em;
  text-transform: uppercase;
  line-height: 1.05;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.5s var(--ease), transform 0.6s var(--ease);
}
body.menu-open .menu a { opacity: 1; transform: none; }
body.menu-open .menu a:nth-child(1) { transition-delay: 0.18s; }
body.menu-open .menu a:nth-child(2) { transition-delay: 0.24s; }
body.menu-open .menu a:nth-child(3) { transition-delay: 0.3s; }
body.menu-open .menu a:nth-child(4) { transition-delay: 0.36s; }
body.menu-open .menu a:nth-child(5) { transition-delay: 0.42s; }
.menu .menu-foot {
  position: absolute;
  bottom: 32px;
  left: var(--pad-x);
  right: var(--pad-x);
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--grey-60);
}

/* ---------- 5. Média / placeholders ---------- */
/* Chaque .media affiche --img si le fichier existe, sinon un dégradé de secours.
   Remplace simplement le fichier dans assets/work/ pour passer en vrai visuel. */
.media {
  position: relative;
  overflow: hidden;
  background-color: #111;
  background-image: var(--img, none),
    linear-gradient(140deg, var(--g1, #2b2b2b), var(--g2, #0b0b0b) 62%);
  background-size: cover, cover;
  background-position: center, center;
  isolation: isolate;
}
.media::after {
  /* grain léger */
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  opacity: 0.16;
  mix-blend-mode: overlay;
  pointer-events: none;
}
.media video,
.media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------- 6. Hero (vidéo de fond) ---------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 var(--pad-x) clamp(32px, 4.5vw, 64px);
  overflow: hidden;
}

/* Le fond est translaté par le JS (parallaxe) : il déborde volontairement
   de 12% en hauteur pour ne jamais laisser apparaître de bord. */
.hero-bg {
  position: absolute;
  inset: -6% 0;
  z-index: -1;
  background: #080808;
  will-change: transform;
}
/* La vidéo se pose : elle entre légèrement agrandie et se stabilise. */
.hero-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.13);
  transition: transform 2.8s var(--ease) 0.1s;
}
.is-loaded .hero-video { transform: scale(1.04); }
/* Voile en trois couches. La vidéo comporte des plans très clairs :
   sans le voile de base, le texte devient illisible sur ces images-là. */
.hero-scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0.12) 26%,
                            rgba(0, 0, 0, 0.22) 46%, rgba(0, 0, 0, 0.96) 100%),
    linear-gradient(90deg, rgba(0, 0, 0, 0.72) 0%, rgba(0, 0, 0, 0.12) 62%, rgba(0, 0, 0, 0) 100%),
    rgba(0, 0, 0, 0.24);
}

/* Bloc de texte : le JS l'estompe au scroll */
.hero-inner {
  position: relative;
  will-change: transform, opacity;
}

/* Sur-titre : un filet qui se dessine, puis le texte qui apparaît.
   Moins bavard qu'une pastille clignotante. */
.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 10.5px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  margin-bottom: clamp(22px, 2.8vw, 38px);
}
.hero-eyebrow .rule {
  display: block;
  flex: none;
  width: 0;
  height: 1px;
  background: currentColor;
  transition: width 1.5s var(--ease) 0.15s;
}
.is-loaded .hero-eyebrow .rule { width: clamp(26px, 4vw, 54px); }

.hero-eyebrow-text {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 1.1s var(--ease) 0.3s, transform 1.1s var(--ease) 0.3s;
}
.is-loaded .hero-eyebrow-text { opacity: 1; transform: none; }

/* Titre : graisse légère, casse normale, deux lignes.
   L'interlettrage se resserre pendant l'apparition — c'est ce détail
   qui fait la différence entre « gros titre » et « titre posé ». */
.hero-title {
  max-width: 22ch;
  font-size: clamp(30px, 4.9vw, 74px);
  font-weight: 300;
  line-height: 1.1;
  letter-spacing: 0.012em;
  text-transform: none;
  transition: letter-spacing 2s var(--ease) 0.2s;
}
.is-loaded .hero-title { letter-spacing: -0.028em; }

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 30px;
  margin-top: clamp(26px, 3.4vw, 50px);
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 1.2s var(--ease) 0.72s, transform 1.3s var(--ease) 0.72s;
}
.is-loaded .hero-meta { opacity: 1; transform: none; }

.hero-meta p {
  max-width: 52ch;
  font-size: clamp(14px, 1.05vw, 16px);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.62);
  text-wrap: pretty;   /* évite les lignes orphelines, ignoré si non supporté */
}

.hero-controls {
  display: flex;
  align-items: center;
  gap: clamp(16px, 2vw, 28px);
  flex-wrap: wrap;
}

/* Appel à l'action : un simple lien souligné, pas un bouton plein */
.hero-cta {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding-bottom: 7px;
  font-size: 13px;
  letter-spacing: 0.02em;
  border-bottom: 1px solid rgba(255, 255, 255, 0.28);
  transition: border-color 0.6s var(--ease);
}
.hero-cta:hover { border-color: var(--white); }
.hero-cta .arrow { transition: transform 0.6s var(--ease); }
.hero-cta:hover .arrow { transform: translateX(5px); }

/* Boutons en verre dépoli sur la vidéo */
.btn--glass {
  background: var(--glass);
  border-color: var(--glass-line);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
}

/* Même registre que le lien : un filet, pas une pastille */
.sound-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding-bottom: 7px;
  font-size: 13px;
  letter-spacing: 0.02em;
  color: rgba(255, 255, 255, 0.62);
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
  transition: color 0.6s var(--ease), border-color 0.6s var(--ease);
}
.sound-btn:hover { color: var(--white); border-color: rgba(255, 255, 255, 0.5); }
.sound-btn.is-on { color: var(--white); border-color: rgba(255, 255, 255, 0.4); }

/* Petit égaliseur : barres figées quand le son est coupé */
.eq {
  display: flex;
  align-items: center;
  gap: 2px;
  height: 13px;
}
.eq i {
  width: 2px;
  height: 3px;
  border-radius: 1px;
  background: currentColor;
  transition: height 0.3s var(--ease);
}
.sound-btn.is-on .eq i { animation: eq 0.85s var(--ease-soft) infinite alternate; }
.sound-btn.is-on .eq i:nth-child(2) { animation-delay: 0.12s; }
.sound-btn.is-on .eq i:nth-child(3) { animation-delay: 0.24s; }
.sound-btn.is-on .eq i:nth-child(4) { animation-delay: 0.36s; }
@keyframes eq {
  from { height: 3px; }
  to { height: 13px; }
}

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  font-size: 10px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  opacity: 0;
}
/* Arrive en dernier, une fois le titre posé */
.is-loaded .scroll-cue {
  animation: cue-in 0.9s var(--ease) 1.5s forwards,
             bob 2.8s var(--ease-soft) 2.4s infinite;
}
@keyframes cue-in {
  to { opacity: 0.45; }
}
@keyframes bob {
  0%, 100% { transform: translate(-50%, 0); opacity: 0.45; }
  50% { transform: translate(-50%, 7px); opacity: 1; }
}

/* Fine barre de progression de lecture de la page */
.progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 100;
  background: var(--white);
  transform: scaleX(0);
  transform-origin: 0 50%;
  mix-blend-mode: difference;
  pointer-events: none;
}

/* ---------- 7. Bandeau clients ---------- */
.marquee {
  border-block: 1px solid var(--line);
  padding-block: 22px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-track {
  display: flex;
  width: max-content;
  animation: slide 38s linear infinite;
}
.marquee:hover .marquee-track { animation-play-state: paused; }
/* L'espacement passe par padding-right (et non gap) pour que le défilement
   de -50% retombe exactement sur le motif dupliqué par le JS. */
.marquee-track span {
  padding-right: clamp(38px, 6vw, 96px);
  font-size: clamp(15px, 1.7vw, 24px);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--grey-60);
  white-space: nowrap;
  transition: color 0.3s linear;
}
.marquee-track span:hover { color: var(--white); }
@keyframes slide {
  to { transform: translateX(-50%); }
}

/* ---------- 8. En-tête de section ---------- */
.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: clamp(28px, 4vw, 56px);
}

/* ---------- 9. Grille de projets ---------- */
.work-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--gap) var(--gap);
}
.work-card { display: block; }
.work-card--wide { grid-column: 1 / -1; }

.work-card .media {
  aspect-ratio: 16 / 10;
  transition: transform 0.9s var(--ease);
}
.work-card--wide .media { aspect-ratio: 21 / 9; }
.work-card .media > .zoom {
  position: absolute;
  inset: 0;
  background: inherit;
  transition: transform 1s var(--ease);
}
.work-card:hover .media { transform: scale(0.985); }
.work-card:hover .media > .zoom { transform: scale(1.07); }

.work-play {
  position: absolute;
  z-index: 2;
  inset: auto auto 16px 16px;
  width: 42px;
  height: 42px;
  border: 1px solid rgba(255, 255, 255, 0.5);
  border-radius: 50%;
  display: grid;
  place-items: center;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.4s var(--ease), transform 0.5s var(--ease);
  backdrop-filter: blur(6px);
}
.work-card:hover .work-play { opacity: 1; transform: none; }

.work-info {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding-top: 14px;
}
.work-info h3 {
  font-size: clamp(15px, 1.5vw, 20px);
  font-weight: 500;
  letter-spacing: -0.02em;
}
.work-info .tag {
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--grey-60);
  white-space: nowrap;
}

/* ---------- 10. Cartes promo ---------- */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
}
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: clamp(320px, 34vw, 480px);
  padding: 26px;
  border: 1px solid var(--line);
  overflow: hidden;
  transition: border-color 0.4s var(--ease);
}
.card:hover { border-color: var(--line-strong); }
.card .media {
  position: absolute;
  inset: 0;
  z-index: -1;
  transition: transform 1s var(--ease);
}
.card:hover .media { transform: scale(1.06); }
.card .media::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.85));
  z-index: 1;
}
.card h3 {
  font-size: clamp(22px, 2.2vw, 32px);
  font-weight: 600;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin-block: 10px 8px;
}
.card p { color: var(--grey-90); font-size: 15px; max-width: 34ch; }
.card .card-cta {
  margin-top: 18px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* ---------- 11. Bloc "à propos" / texte large ---------- */
.statement {
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: clamp(24px, 5vw, 80px);
  align-items: start;
}
.statement p + p { margin-top: 1.1em; }

.facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap);
  margin-top: clamp(40px, 6vw, 88px);
  border-top: 1px solid var(--line);
  padding-top: 28px;
}
.fact strong {
  display: block;
  font-size: clamp(30px, 4vw, 56px);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
}
.fact span {
  display: block;
  margin-top: 8px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--grey-60);
}

/* Portrait + texte */
.studio {
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: clamp(24px, 5vw, 80px);
  align-items: end;
}
.portrait {
  position: relative;
  aspect-ratio: 4 / 5;
  --g1: #3a3a3a;
  --g2: #0a0a0a;
}
.portrait img {
  filter: grayscale(1) contrast(1.06);
  transition: filter 1.2s var(--ease), transform 1.4s var(--ease);
}
.portrait:hover img { filter: grayscale(0) contrast(1); transform: scale(1.03); }
.portrait figcaption {
  margin-top: 14px;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--grey-60);
  display: flex;
  justify-content: space-between;
  gap: 12px;
}

/* ---------- 12. Services ---------- */
.services { border-top: 1px solid var(--line); }
.service {
  display: grid;
  grid-template-columns: 80px 1fr 1.2fr;
  gap: clamp(16px, 3vw, 48px);
  align-items: start;
  padding-block: clamp(24px, 3vw, 40px);
  border-bottom: 1px solid var(--line);
  transition: padding-left 0.5s var(--ease), background 0.5s var(--ease);
}
.service:hover { padding-left: 14px; background: rgba(255, 255, 255, 0.03); }
.service .num { font-size: 11px; letter-spacing: 0.16em; color: var(--grey-40); padding-top: 8px; }
.service h3 {
  font-size: clamp(22px, 2.6vw, 38px);
  font-weight: 500;
  letter-spacing: -0.03em;
}
.service p { color: var(--grey-60); max-width: 52ch; padding-top: 6px; }

/* ---------- 13. CTA de bas de page ---------- */
.cta {
  text-align: center;
  padding-block: clamp(90px, 14vw, 200px);
  border-top: 1px solid var(--line);
}
.cta a { display: inline-block; }
.cta a:hover .h-lg { opacity: 0.6; }
.cta .h-lg { transition: opacity 0.4s var(--ease); }

/* ---------- 14. Footer ---------- */
.footer {
  border-top: 1px solid var(--line);
  padding-block: clamp(40px, 6vw, 72px) 28px;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.6fr repeat(3, 1fr);
  gap: clamp(24px, 4vw, 56px);
}
.footer h4 {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--grey-60);
  font-weight: 500;
  margin-bottom: 14px;
}
.footer li + li { margin-top: 8px; }
.footer li a { font-size: 14px; }

.newsletter {
  display: flex;
  gap: 10px;
  margin-top: 16px;
  max-width: 380px;
}
.newsletter input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line-strong);
  color: var(--white);
  padding: 10px 2px;
  font: inherit;
  font-size: 14px;
}
.newsletter input::placeholder { color: var(--grey-40); }
.newsletter input:focus { outline: none; border-color: var(--white); }
.newsletter button {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--line-strong);
  padding: 10px 0;
}
.newsletter button:hover { border-color: var(--white); }
.form-note { font-size: 12px; color: var(--grey-40); margin-top: 10px; min-height: 1.2em; }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 12px;
  margin-top: clamp(40px, 6vw, 80px);
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--grey-40);
}

/* ---------- 15. Pages intérieures ---------- */
.page-head {
  padding-top: clamp(110px, 16vw, 200px);
  padding-bottom: clamp(30px, 5vw, 64px);
}
.page-head .lead { margin-top: 20px; }

.prose { max-width: 68ch; }
.prose h2 {
  font-size: clamp(20px, 2vw, 28px);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin-top: 2.2em;
  margin-bottom: 0.5em;
}
.prose p, .prose li { color: var(--grey-90); font-size: 15px; line-height: 1.65; }
.prose p + p { margin-top: 1em; }
.prose ul { list-style: disc; padding-left: 20px; margin-top: 0.8em; }
.prose li + li { margin-top: 6px; }

/* Store */
.products {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(24px, 4vw, 56px);
}
.product .media { aspect-ratio: 4 / 3; }
.product-body { padding-top: 18px; }
.product-body h3 {
  font-size: clamp(22px, 2.4vw, 34px);
  font-weight: 600;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}
.product-body .price {
  font-size: 15px;
  color: var(--grey-60);
  margin-top: 6px;
}
.product-body p { color: var(--grey-90); margin-top: 12px; max-width: 46ch; }
.product-body .btn { margin-top: 20px; }
.spec {
  margin-top: 18px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
  font-size: 13px;
  color: var(--grey-60);
}
.spec li { padding: 6px 0; border-bottom: 1px solid var(--line); }

/* Contact */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(28px, 6vw, 96px);
  align-items: start;
}
.field { margin-bottom: 22px; }
.field label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--grey-60);
  margin-bottom: 8px;
}
.field input,
.field select,
.field textarea {
  width: 100%;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line-strong);
  color: var(--white);
  font: inherit;
  font-size: 15px;
  padding: 10px 2px;
  border-radius: 0;
}
.field select { appearance: none; }
.field select option { background: var(--black); }
.field textarea { resize: vertical; min-height: 110px; }
.field input:focus,
.field select:focus,
.field textarea:focus { outline: none; border-color: var(--white); }

.contact-list li {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.contact-list li span:first-child {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--grey-60);
}

/* Projet */
.project-hero {
  height: clamp(360px, 68vh, 760px);
  margin-top: 72px;
}
.project-meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--gap);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding-block: 26px;
}
.project-meta span {
  display: block;
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--grey-60);
  margin-bottom: 8px;
}
.project-meta strong { font-weight: 500; font-size: 15px; }
.stills {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--gap);
  margin-top: clamp(30px, 5vw, 64px);
}
.stills .media { aspect-ratio: 16 / 10; }
.stills .media.full { grid-column: 1 / -1; aspect-ratio: 21 / 9; }

/* ---------- 16. Animations d'apparition ---------- */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.9s var(--ease), transform 1s var(--ease);
  transition-delay: var(--delay, 0s);
}
.reveal.is-in { opacity: 1; transform: none; }

/* Titre hero : révélation ligne par ligne */
/* padding/margin compensés : laisse respirer les accents malgré overflow:hidden */
.line-mask {
  display: block;
  overflow: hidden;
  padding-top: 0.1em;
  margin-top: -0.1em;
}
.line-mask > span {
  display: block;
  transform: translateY(108%);
  opacity: 0;
  transition: transform 1.5s var(--ease), opacity 0.9s var(--ease);
  transition-delay: var(--delay, 0s);
}
.is-loaded .line-mask > span,
.in-view .line-mask > span { opacity: 1; }
.is-loaded .line-mask > span { transform: none; }

/* ---------- 17. Responsive ---------- */
@media (max-width: 1024px) {
  .cards { grid-template-columns: 1fr; }
  .statement { grid-template-columns: 1fr; }
  .studio { grid-template-columns: 1fr; align-items: start; }
  .portrait { aspect-ratio: 16 / 11; }
  .facts { grid-template-columns: repeat(2, 1fr); row-gap: 28px; }
  .footer-grid { grid-template-columns: 1fr 1fr; }
  .service { grid-template-columns: 40px 1fr; }
  .service p { grid-column: 2; }
  .project-meta { grid-template-columns: repeat(2, 1fr); row-gap: 22px; }
}

@media (max-width: 760px) {
  .nav { display: none; }
  .burger { display: block; }
  .work-grid { grid-template-columns: 1fr; }
  .products { grid-template-columns: 1fr; }
  .contact-grid { grid-template-columns: 1fr; }
  .stills { grid-template-columns: 1fr; }
  .stills .media.full { aspect-ratio: 16 / 10; }
  .work-card--wide .media { aspect-ratio: 16 / 10; }
  .footer-grid { grid-template-columns: 1fr; }
  .hero-meta { flex-direction: column; align-items: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  .line-mask > span { transform: none; opacity: 1; }
  .hero-bg, .hero-inner { transform: none !important; opacity: 1 !important; }
  .hero-meta, .hero-eyebrow-text { opacity: 1; transform: none; }
  .hero-eyebrow .rule { width: clamp(26px, 4vw, 54px); }
  .hero-video { transform: scale(1.04); }
  .scroll-cue { opacity: 0.45; }
}


/* =========================================================
   LA PORTE D'ENTRÉE

   Elle existe pour une raison technique — le clic de ses boutons est
   le geste sans lequel aucun navigateur n'accorde le son.

   Mais elle ne cache plus la page : elle la voile. Le film du hero
   joue derrière, flouté, et c'est lui qui justifie la question posée.
   Un rideau noir avec une phrase demandait au visiteur de croire
   qu'il y avait un film ; un voile le lui montre.

   Posée dans style.css et non dans une feuille de version : n'importe
   quelle page peut l'obtenir en ajoutant son markup.
   ========================================================= */
.porte {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: var(--pad-x, 40px);
  background: rgba(0, 0, 0, 0.42);
  -webkit-backdrop-filter: blur(26px) saturate(1.15);
  backdrop-filter: blur(26px) saturate(1.15);
  /* La sortie anime AUSSI le flou : la page revient au net en même temps
     que le voile s'efface, au lieu d'apparaître d'un coup derrière lui.
     C'est ce qui rend l'ouverture douce plutôt que brusque. */
  transition: opacity 0.85s var(--ease), -webkit-backdrop-filter 0.85s var(--ease),
              backdrop-filter 0.85s var(--ease);
}
.porte.is-partie {
  opacity: 0;
  -webkit-backdrop-filter: blur(0px) saturate(1);
  backdrop-filter: blur(0px) saturate(1);
  pointer-events: none;
}

/* Le défilement est bloqué, et la navigation s'efface : la porte doit
   être la seule chose à l'écran. Sans ça, le menu et la barre de
   progression transparaissent à travers le voile. */
body.porte-ouverte { overflow: hidden; }
body.porte-ouverte .header,
body.porte-ouverte .progress,
body.porte-ouverte .scroll-cue {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.5s var(--ease);
}

.porte-bloc {
  outline: none;
  text-align: center;
}

.porte-choix {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

/* Les deux boutons montent l'un après l'autre. Le décalage est court —
   80 ms — mais c'est lui qui distingue une arrivée d'une apparition. */
.porte-btn {
  padding: 15px 26px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.24);
  background: rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  color: rgba(255, 255, 255, 0.82);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
  opacity: 0;
  transform: translateY(12px);
  animation: porte-btn-entre 0.9s var(--ease) both;
  transition: background 0.3s var(--ease), border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.porte-btn:nth-child(1) { animation-delay: 0.25s; }
.porte-btn:nth-child(2) { animation-delay: 0.33s; }
@keyframes porte-btn-entre {
  to { opacity: 1; transform: none; }
}

.porte-btn:hover { border-color: rgba(255, 255, 255, 0.55); color: #fff; background: rgba(255, 255, 255, 0.12); }
.porte-btn--son {
  background: #fff;
  border-color: #fff;
  color: #000;
}
.porte-btn--son:hover { background: rgba(255, 255, 255, 0.86); color: #000; }
.porte-btn span {
  display: inline-block;
  margin-left: 7px;
  transition: transform 0.3s var(--ease);
}
.porte-btn--son:hover span { transform: translateX(4px); }

@media (prefers-reduced-motion: reduce) {
  .porte { transition: opacity 0.001ms; }
  .porte-btn { animation: none; opacity: 1; transform: none; }
}


/* =========================================================
   LA PAGE DU PARCOURS
   ========================================================= */

/* UNE COLONNE ETROITE, ET C'EST LE SEUL REGLAGE QUI COMPTE POUR UN TEXTE
   LONG.

   Au-dela d'environ soixante-quinze signes par ligne, l'oeil perd le debut
   de la ligne suivante en revenant a la marge, et la lecture ralentit sans
   qu'on sache dire pourquoi. La mesure est en `ch` et non en pixels :
   l'unite vaut la largeur du zero de la police en cours, donc la contrainte
   reste de soixante-huit signes quel que soit le corps ou le dessin. Ecrite
   en pixels, elle serait juste pour une police et fausse pour la suivante.

   L'interligne monte a 1,75 : plus haut que celui d'un paragraphe isole,
   parce qu'une colonne etroite empile beaucoup de lignes courtes et que
   l'oeil a besoin de plus d'air pour ne pas sauter d'une a l'autre. */
.recit {
  max-width: 68ch;
  /* `.wrap` centre son contenu ; une colonne plus etroite s'y retrouve donc
     au milieu, decalee vers la droite par rapport au titre qui, lui, va
     jusqu'au bord. Deux alignements differents sur la meme page se lisent
     comme une erreur de gabarit. On la rappelle a gauche. */
  margin-left: 0;
  margin-right: auto;
  padding-bottom: 6vh;
}
.recit p {
  margin: 0 0 1.5em;
  font-size: 17px;
  line-height: 1.75;
  color: var(--muted, rgba(255, 255, 255, 0.66));
}

/* Le dernier paragraphe est la chute : il revient au blanc plein pendant
   que les autres restent en gris. C'est la seule hierarchie du bloc, et
   elle suffit — la souligner davantage ferait deux textes. */
.recit p.accent {
  color: var(--white, #fff);
}

/* LA FORMULE DE PRESSE — un bloc qui ne ressemble a aucun autre.

   Elle dit en une phrase ce que la page raconte en cinq paragraphes :
   c'est le texte qu'on recopie dans un dossier ou un generique. Le filet a
   gauche est le meme que celui du hero, a la meme epaisseur et a la meme
   opacite : la page n'invente pas un signe, elle en reprend un.

   Pas de guillemets ni d'italique : ce n'est pas une citation, personne ne
   l'a dite. C'est une definition. */
.boilerplate {
  margin: 0 auto 8vh 0;
  max-width: 60ch;
  padding-left: 30px;
  border-left: 1px solid rgba(255, 255, 255, 0.2);
  font-size: 19px;
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.9);
}

@media (max-width: 720px) {
  .recit p { font-size: 16px; }
  .boilerplate { font-size: 17px; padding-left: 20px; }
}


/* =========================================================
   LA PAGE DU DEROULEMENT
   ========================================================= */

/* UNE GRILLE A DEUX COLONNES : le numero a gauche, l'etape a droite.

   C'est la respiration du verrou du hero — un signe court a gauche, la
   matiere a droite — et ca donne a l'oeil un rail vertical a suivre le long
   des six etapes. La colonne du numero est FIXE et non proportionnelle :
   proportionnelle, elle s'elargirait avec la fenetre et l'ecart entre le
   numero et son titre grandirait sans raison. */
.etapes {
  padding-bottom: 4vh;
  max-width: 900px;
  margin-left: 0;
  margin-right: auto;
}

.etape {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 0 24px;
  padding: 42px 0;
  border-top: 1px solid var(--line, rgba(255, 255, 255, 0.12));
}
.etape:first-child { border-top: 0; padding-top: 0; }

/* LE NUMERO EST UN REPERE, PAS UN TITRE. Il reste en gris et en petit
   corps : lu aussi fort que le nom de l'etape, on lirait douze choses au
   lieu de six. Il est aligne sur la premiere ligne du titre, d'ou le
   `padding-top` — la police du titre a une hauteur de capitale plus basse
   que sa boite. */
.etape-n {
  font-size: 12px;
  letter-spacing: 0.18em;
  color: var(--grey-60, rgba(255, 255, 255, 0.42));
  padding-top: 6px;
  font-variant-numeric: tabular-nums;
}

.etape-t {
  margin: 0;
  font-size: clamp(20px, 2.2vw, 27px);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.etape-c {
  margin: 14px 0 0;
  font-size: 15px;
  color: var(--grey-60, rgba(255, 255, 255, 0.5));
}

.etape-p {
  margin: 16px 0 0;
  max-width: 56ch;
  font-size: 16px;
  line-height: 1.7;
  color: var(--muted, rgba(255, 255, 255, 0.66));
}

/* LES ITEMS SONT SANS PUCE ET SUR PLUSIEURS COLONNES.

   Sans puce : ce sont des elements d'une meme categorie, pas des points
   d'un argumentaire — un tiret ou un rond devant chacun ajouterait six
   signes pour ne rien dire de plus. C'est l'indentation et le gris qui
   font la liste.

   En colonnes : sept items en une seule colonne font une bande etroite et
   haute qui deseequilibre l'etape. `auto-fill` avec un minimum de 190 px
   en met deux ou trois selon la place, et une seule sur telephone. */
.etape-l {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 8px 24px;
  max-width: 640px;
}
.etape-l li {
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted, rgba(255, 255, 255, 0.66));
}

@media (max-width: 640px) {
  .etape { grid-template-columns: 1fr; gap: 10px; padding: 32px 0; }
  .etape-n { padding-top: 0; }
  .etape-l { grid-template-columns: 1fr; }
}


/* =========================================================
   LA BOUTIQUE EN ATTENTE
   ========================================================= */

/* LA LISTE DE CE QUI VIENDRA N'EST PAS UN CATALOGUE.

   Six mots, en colonnes, sans puce et sans prix : le lecteur doit
   comprendre en un coup d'oeil la NATURE des produits, pas croire qu'il
   peut les acheter. Le corps reste celui d'une liste ordinaire — le
   grossir en ferait une offre. */
.attente {
  max-width: 720px;
  margin-left: 0;
  margin-right: auto;
  padding-bottom: 4vh;
}
.attente-l {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 10px 24px;
  max-width: 560px;
}
.attente-l li {
  font-size: 16px;
  line-height: 1.5;
  color: var(--muted, rgba(255, 255, 255, 0.66));
}

@media (max-width: 640px) {
  .attente-l { grid-template-columns: 1fr; }
}


/* =========================================================
   À PROPOS — MOITIÉ-MOITIÉ
   =========================================================
   Photo à gauche, texte à droite, globe et formule en pied.

   `align-items: start` N'EST PAS UN DÉTAIL. Un enfant de grille étiré à
   la hauteur de la rangée n'a plus aucune marge pour coller : `sticky`
   ne s'y déclenche jamais, et la photo défile avec le texte. */
.apropos {
  display: grid;
  grid-template-columns: 44fr 56fr;
  align-items: start;
}

/* Collée à la hauteur de la fenêtre, sous l'en-tête — qui est en
   `mix-blend-mode: difference` et reste donc lisible sur le mur clair de
   la photo comme sur la veste sombre. */
.apropos-photo {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  margin: 0;
  overflow: hidden;
  background: var(--grey-20);
}
/* 70 % 40 % : le visage est aux trois quarts de la largeur. Centrer
   l'image le coupait dès que la colonne rétrécit ; ancré là, on garde
   le visage, le gobelet et l'appareil. */
.apropos-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 70% 40%;
  filter: grayscale(0.12) contrast(1.04);
}

.apropos-texte {
  min-width: 0;
  padding: clamp(110px, 12vw, 180px) var(--pad-x) clamp(64px, 9vw, 120px) clamp(32px, 4.4vw, 72px);
}
/* « LUDO — RÉALISATEUR » sur une ligne dans la demi-colonne : à 4,2vw le
   titre fait environ 11 em, soit 46 % de la largeur — la colonne en offre
   48. Au-dessus, il se coupe au tiret. */
.apropos-titre {
  margin-top: 14px;
  font-size: clamp(30px, 4.2vw, 64px);
}
.apropos-lead { margin-top: clamp(22px, 2.6vw, 36px); }
.apropos-recit {
  max-width: 62ch;
  margin-top: clamp(26px, 3vw, 44px);
  padding-bottom: 0;
}

.apropos-pied {
  display: grid;
  grid-template-columns: minmax(188px, 20vw) minmax(0, 1fr);
  gap: clamp(20px, 2.6vw, 40px);
  align-items: center;
  margin-top: clamp(44px, 5.4vw, 80px);
  padding-top: clamp(28px, 3vw, 44px);
  border-top: 1px solid var(--line);
}
/* UN CANVAS PLUS LARGE QUE HAUT, POUR LES NOMS — PAS POUR LA SPHÈRE.
   Le globe prend son rayon sur la PLUS PETITE moitié du canvas (× 0,82) et
   se centre : la largeur en plus ne le grossit pas, elle donne de la place
   aux étiquettes. Carré, le canvas coupait « INDONÉSIE » et « AUSTRALIE »
   au bord droit — à 9 px, un nom s'étend jusqu'à 76 px à droite de son
   point, et le limbe touchait presque le bord.
   Pour 230 px de haut : rayon 94 px, il faut 94 + 76 = 170 px de demi-
   largeur, soit un rapport de 1,48. On prend 3:2.

   Le décalage d'un sixième à gauche garde le bord de la sphère aligné sur
   la colonne de texte : les noms partent toujours vers la droite, la marge
   de gauche ne sert à rien et on la rend. */
.apropos-sphere {
  position: relative;
  width: 120%;              /* 120 % d'une colonne de 20vw : le canvas garde ses 24vw, */
  aspect-ratio: 3 / 2;
  margin-left: -20%;        /* et finit au bord de sa colonne au lieu d'y laisser du vide */
  touch-action: none;
}
.apropos-sphere canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}
.apropos-presse blockquote {
  margin: 12px 0 0;
  max-width: 52ch;
  font-size: clamp(15px, 1.1vw, 17px);
  line-height: 1.6;
  color: rgba(255, 255, 255, 0.86);
}

/* EN COLONNE UNIQUE, la photo passe au-dessus et cesse de coller : collée
   sur un téléphone, elle couvrirait l'écran pendant toute la lecture. On la
   borne en hauteur pour que le nom arrive avant la fin du premier écran. */
@media (max-width: 960px) {
  .apropos { grid-template-columns: 1fr; }
  /* `width: 100%` EST OBLIGATOIRE. Avec une largeur automatique, le format
     4:5 et la hauteur maximale se négocient, et c'est la LARGEUR qui cède :
     mesuré à 500 px de fenêtre, la photo tombait à 472 px et laissait une
     bande vide à droite. Une largeur imposée laisse la hauteur seule
     céder, et `object-fit: cover` recadre. */
  .apropos-photo {
    position: relative;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 5;
    max-height: 78svh;
  }
  .apropos-texte { padding: clamp(40px, 8vw, 64px) var(--pad-x) 72px; }
  .apropos-titre { font-size: clamp(30px, 8.4vw, 56px); }
}
@media (max-width: 560px) {
  .apropos-pied { grid-template-columns: 1fr; justify-items: start; }
  .apropos-sphere { width: min(100%, 420px); margin-left: -16.667%; }
}

/* Sous « moins d'animation », pas de globe : c'est déjà la règle de
   l'accueil. La formule reste, seule, sur toute la largeur. */
@media (prefers-reduced-motion: reduce) {
  .apropos-sphere { display: none; }
  .apropos-pied { grid-template-columns: 1fr; }
}


/* ---------------------------------------------------------
   À PROPOS — TOUTE LA COLONNE DANS L'ÉCRAN, SUR ORDINATEUR
   ---------------------------------------------------------
   Tout ce qui suit est en `em`, rapporté à un corps de base de
   16 px × --k. Le script de la page règle --k pour que le contenu tienne
   dans la hauteur de la fenêtre : un seul nombre, et toutes les
   proportions suivent — titre, texte, marges, globe.

   La colonne est centrée verticalement dans la hauteur de l'écran. Quand
   le texte tient, la section fait exactement un écran, et la photo avec. */
@media (min-width: 961px) {
  .apropos-texte {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-height: 100vh;
    min-height: 100svh;
    padding-top: max(84px, 10vh);    /* l'en-tête fixe, plus une respiration */
    padding-bottom: max(28px, 4vh);
    font-size: calc(16px * var(--k, 1));
  }
  .apropos-contenu { width: 100%; }

  .apropos-titre { margin-top: 0.45em; font-size: 2.75em; }
  .apropos-lead {
    max-width: 46em;
    margin-top: 0.9em;
    font-size: 1.1em;
    line-height: 1.42;
  }
  .apropos-recit { max-width: 50em; margin-top: 1.1em; }
  .apropos-recit p { margin: 0 0 0.75em; font-size: 0.92em; line-height: 1.56; }
  .apropos-recit p.accent { margin: 0; font-size: 1em; line-height: 1.45; }

  /* LE CANVAS NE DESCEND PAS SOUS 270 PX DE LARGE. Ses noms gardent 9 px
     quelle que soit sa taille : un globe plus petit que ses étiquettes les
     coupe au bord. En 2:1, le rayon vaut 0,205 fois la largeur, et il reste
     0,295 fois la largeur à droite du limbe : 80 px à 270, de quoi loger
     « NLLE-ZÉLANDE ».

     PLUS PROCHE DE LA FORMULE, SANS RIEN RECOUPER. Le canvas fait 125 % de
     sa colonne et recule de 25 % : le bord de la sphère reste aligné sur le
     texte au-dessus, et le canvas finit PILE au bord de sa colonne. Avant,
     il reculait dans une colonne de sa propre largeur et laissait 20 % de
     vide à droite, soit 54 px ajoutés à l'écart pour rien. On garde en
     revanche les 80 px de place des noms : les rogner, c'est laisser
     « AUSTRALIE » mordre sur la formule quand le globe tourne. */
  .apropos-pied {
    grid-template-columns: max(216px, 13.2em) minmax(0, 1fr);
    gap: 1em;
    margin-top: 1.9em;
    padding-top: 0;
  }
  .apropos-sphere { width: 125%; aspect-ratio: 2 / 1; margin-left: -25%; }
  .apropos-presse blockquote {
    max-width: 40em;
    margin-top: 0.5em;
    font-size: 0.86em;
    line-height: 1.5;
  }
  .apropos-cta { margin: 1em 0 0; }
}


/* =========================================================
   À PROPOS — L'ENTRÉE
   =========================================================
   Les gestes sont ceux du hero de l'accueil, pas des nouveaux : le volet
   (heroVoletD) et la lettre qui se précise (heroLettreFine). Une page qui
   emprunte le vocabulaire de l'accueil a l'air du même site ; une page
   qui invente le sien a l'air d'un autre.

   LA SÉQUENCE, SUR ORDINATEUR (0 = mise en page définitive) :
       0 ms     volet sur la photo, l'image recule en s'éclairant
     380 ms     surtitre
     480 ms     titre, qui monte dans son masque en se précisant
     820 ms     paragraphe d'ouverture
    1 060 ms    les quatre paragraphes, un tous les 120 ms
    2 550 ms    le globe sort du noir
    2 800 ms    « Polar Production », la formule, puis le bouton

   PAS DE FILET AU-DESSUS DU GLOBE. Il y en a eu un, tiré puis éteint ;
   retiré à la demande. Sans trait pour séparer le pied du texte, c'est le
   SILENCE qui le fait : le globe part quand les paragraphes se sont posés,
   et ce temps mort le fait lire comme une seconde partie, pas comme la
   suite du texte.

   Tout est masqué SOUS `.apropos-anim` (posée dans le <head>) et joué
   sous `.apropos-go` (posée quand les polices sont là). Le remplissage
   `both` tient l'état de départ pendant le délai, puis l'état d'arrivée
   pour toujours : l'animation l'emporte sur les déclarations qui
   masquent. */

@keyframes aproposVolet {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}
@keyframes aproposPlan {
  from { transform: scale(1.14); filter: grayscale(0.12) contrast(1.04) brightness(0.55); }
  to   { transform: none;        filter: grayscale(0.12) contrast(1.04) brightness(1); }
}
@keyframes aproposLigne {
  from { opacity: 0; transform: translateY(108%); filter: blur(8px); }
  to   { opacity: 1; transform: none;             filter: blur(0); }
}
@keyframes aproposFine {
  from { opacity: 0; transform: translateY(14px); filter: blur(5px); }
  to   { opacity: 1; transform: none;             filter: blur(0); }
}
@keyframes aproposSphere {
  from { opacity: 0; transform: scale(0.86); filter: blur(10px); }
  to   { opacity: 1; transform: none;        filter: blur(0); }
}

/* Le pied ne porte plus de trait : ni bordure, ni l'espace qui l'accompagnait. */
.apropos-pied { border-top: 0; padding-top: 0; }

/* `transform` ne s'applique pas à un élément en ligne : les surtitres
   passent en bloc en ligne pour pouvoir glisser. */
.apropos-contenu > .eyebrow,
.apropos-presse > .eyebrow { display: inline-block; }

/* ---- états de départ ---- */
.apropos-anim .apropos-photo { clip-path: inset(0 100% 0 0); }
.apropos-anim .apropos-contenu > .eyebrow,
.apropos-anim .apropos-lead,
.apropos-anim .apropos-recit p,
.apropos-anim .apropos-sphere,
.apropos-anim .apropos-presse > * { opacity: 0; }
/* Plus spécifique que `.is-loaded .line-mask > span` : sans ça, main.js
   ferait monter le titre au chargement, avant la séquence. */
.apropos-anim .apropos-titre .line-mask > span {
  opacity: 0;
  transform: translateY(108%);
  transition: none;
}

/* ---- la séquence : photo et texte ---- */
.apropos-go .apropos-photo     { animation: aproposVolet 1500ms var(--ease) 0ms both; }
.apropos-go .apropos-photo img { animation: aproposPlan 2600ms var(--ease) 0ms both; }
.apropos-go .apropos-contenu > .eyebrow        { animation: aproposFine 1100ms var(--ease) 380ms both; }
.apropos-go .apropos-titre .line-mask > span   { animation: aproposLigne 1400ms var(--ease) 480ms both; }
.apropos-go .apropos-lead                      { animation: aproposFine 1200ms var(--ease) 820ms both; }
.apropos-go .apropos-recit p:nth-child(1)      { animation: aproposFine 1150ms var(--ease) 1060ms both; }
.apropos-go .apropos-recit p:nth-child(2)      { animation: aproposFine 1150ms var(--ease) 1180ms both; }
.apropos-go .apropos-recit p:nth-child(3)      { animation: aproposFine 1150ms var(--ease) 1300ms both; }
.apropos-go .apropos-recit p:nth-child(4)      { animation: aproposFine 1150ms var(--ease) 1420ms both; }

/* ---- la séquence : le pied, en dernier ---- */
@media (min-width: 961px) {
  .apropos-go .apropos-sphere                           { animation: aproposSphere 1700ms var(--ease) 2550ms both; }
  .apropos-go .apropos-presse > :nth-child(1)           { animation: aproposFine 1100ms var(--ease) 2800ms both; }
  .apropos-go .apropos-presse > :nth-child(2)           { animation: aproposFine 1100ms var(--ease) 2920ms both; }
  .apropos-go .apropos-presse > :nth-child(3)           { animation: aproposFine 1100ms var(--ease) 3100ms both; }
}
/* Sur téléphone, le même ordre, mais compté depuis le moment où le pied
   entre dans l'écran (`.vu`, posée par la page). */
@media (max-width: 960px) {
  .apropos-go .apropos-pied.vu .apropos-sphere          { animation: aproposSphere 1700ms var(--ease) 350ms both; }
  .apropos-go .apropos-pied.vu .apropos-presse > :nth-child(1) { animation: aproposFine 1100ms var(--ease) 600ms both; }
  .apropos-go .apropos-pied.vu .apropos-presse > :nth-child(2) { animation: aproposFine 1100ms var(--ease) 720ms both; }
  .apropos-go .apropos-pied.vu .apropos-presse > :nth-child(3) { animation: aproposFine 1100ms var(--ease) 900ms both; }
}


/* =========================================================
   PROCESS — LA BANDE ET LE TIRAGE
   =========================================================
   Le titre, six vignettes a gauche, le tirage de l'etape au centre avec son
   dessin, le cadran a droite qui tourne au defilement. Le tout
   tient dans UNE hauteur d'ecran : la scene est une grille, pas un empilement
   de positions calculees.

   POURQUOI UNE GRILLE. Le tirage etait pose a une distance du haut, le texte
   ancre au bas, la bande entre les deux : trois reglages independants qui se
   chevauchaient des que l'ecran raccourcissait. Une grille partage ce qu'elle
   a, et ne peut pas deborder.

   LA SCENE NE S'AFFICHE QUE SI LE SCRIPT L'A MONTEE. `proc-vif` est posee sur
   la page par le script, apres verification de la largeur et du reglage
   d'animation : sans lui, la scene reste invisible et la liste des six etapes
   garde la page. */
/* LA BARRE DE NAVIGATION RESTE EN HAUT SUR CETTE PAGE. Le script commun pose
   `header--hidden` des qu'on defile vers le bas ; ici, tout se joue au
   defilement, donc la barre disparaitrait exactement quand elle sert. On
   neutralise le deplacement plutot que de modifier le script commun : la
   regle ne vaut que sous `proc-vif`, donc que sur cette page et seulement
   quand la scene est montee. Les autres pages gardent leur barre escamotable. */
.proc-vif .header--hidden { transform: none; }

/* L'AIMANT A ETE RETIRE. Le magnetisme de proximite du navigateur bloquait
   le defilement a la deuxieme etape : au pave tactile, chaque petite
   impulsion compte comme un geste termine, et le navigateur ramene au point
   d'accroche le plus proche. On pousse, il rend, on ne passe plus.
   Un aimant doit donc attendre l'ARRET reel du defilement, ce que le
   magnetisme natif ne sait pas distinguer ici. Ne pas le remettre en l'etat. */

.proc-piste { display: none; }
.proc-vif .proc-piste { display: block; position: relative; }
.proc-vif .etapes { display: none; }

.proc-scene {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: clip;
  background: var(--black);
  display: grid;
  /* PREMIERE COLONNE ELARGIE : 84 px etait trop etroit pour lire des photos.
     Elle appartient desormais a la seule bande, qui monte jusqu'en haut. */
  grid-template-columns: min(9.5vw, 128px) minmax(0, min(40vw, 540px)) minmax(0, 1fr);
  /* LE TEXTE SUIT LE TIRAGE, IL N'EST PAS ANCRE EN BAS. Avec une rangee
     elastique au milieu, l'espace rendu par le titre compacte se coincait
     entre le tirage et son texte : un trou de 150 px. Les deux rangees du
     milieu prennent donc leur hauteur naturelle, et c'est la derniere,
     elastique, qui absorbe ce qui reste, en bas de scene. */
  /* C'EST LE TIRAGE QUI CEDE, PAS LE TEXTE. Titre, texte et appel prennent
     ce qu'il leur faut ; le tirage occupe le reste. Regler sa hauteur en
     fraction d'ecran ne marchait qu'a une taille d'ecran donnee : sur un
     ecran court le bouton n'avait plus de place. */
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  column-gap: 26px;
  row-gap: clamp(12px, 2.2vh, 26px);
  padding: clamp(76px, 9.5vh, 100px) var(--pad-x) clamp(22px, 4vh, 44px);
  --tour: 0deg;
}

/* ---- le titre, entre dans la scene par le script ----
   DANS LA SCENE, LE TITRE N'EST PLUS UN TITRE DE PAGE : c'est une ligne
   d'en-tete. Surtitre et titre partagent la meme ligne de base, le titre tient
   sur une seule ligne, le chapeau passe dessous en petit. Les deux lignes du
   `h1` sont gardees mais mises bout a bout : leur masque doit rester un bloc
   pour clipper l'animation, d'ou `inline-block` plutot que `inline`. */
.proc-vif .proc-tete {
  /* A PARTIR DE LA COLONNE 2 : la premiere revient a la bande, qui remonte
     a hauteur du titre. En s'etendant sur les deux, le titre lui barrait le
     haut et les deux se disputaient la meme cellule.
     LA LARGEUR EST BORNEE EN `vw` AUSSI : sans cela le chapeau courrait
     jusque sous la boussole, a droite. */
  grid-column: 2 / -1;
  grid-row: 1;
  /* CALE A GAUCHE. Le bloc porte la classe `wrap`, qui centre par marges
     automatiques ; tant qu'il s'etendait sur deux colonnes ca ne se voyait
     pas, mais confine dans une cellule plus large que lui il s'est mis a
     flotter au milieu. On annule ces marges et on l'ancre au bord. */
  justify-self: start;
  margin: 0;
  padding: 0;
  max-width: min(70ch, 50vw);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 14px;
}
.proc-vif .proc-tete .eyebrow { font-size: 10px; }
.proc-vif .proc-tete .h-xl {
  /* le gabarit pose la marge en style en ligne, sur le `h1` lui-meme */
  margin: 0 !important;
  font-size: clamp(17px, 1.75vw, 26px);
  line-height: 1.1;
  letter-spacing: -0.025em;
}
.proc-vif .proc-tete .h-xl .line-mask { display: inline-block; }
.proc-vif .proc-tete .h-xl .line-mask + .line-mask::before {
  content: "\00a0";
}
.proc-vif .proc-tete .lead {
  flex-basis: 100%;
  margin-top: 5px;
  font-size: clamp(11.5px, 0.88vw, 13px);
  line-height: 1.5;
  max-width: 76ch;
}

/* ---- la bande : six vignettes, une seule allumee ---- */
.proc-bande {
  grid-column: 1;
  /* DE LA PREMIERE RANGEE A L'AVANT-DERNIERE. Elle part donc a hauteur du
     surtitre « Process », a gauche du titre, et non plus sous lui.
     PAS JUSQU'A LA DERNIERE : l'interrupteur de son occupe celle-la, dans
     cette meme colonne. En allant jusqu'au bout, la bande partageait sa
     cellule avec lui, et le bouton — pose plus haut dans l'empilement — se
     serait affiche PAR-DESSUS la sixieme vignette. */
  grid-row: 1 / -2;
  display: grid;
  grid-template-rows: repeat(6, 1fr);
  gap: 8px;
  z-index: 3;
}
.proc-vig {
  position: relative;
  padding: 0;
  border: 1px solid transparent;
  /* LE DESSIN DE L'ETAPE, ARRETE, remplit la case : process-dessins.js le
     met a l'echelle pour couvrir et le recadre au centre. `overflow` le
     decoupe au rayon de la vignette. */
  overflow: hidden;
  /* 7 px contre 14 sur le tirage : le meme rapport a la largeur. Un rayon
     identique sur les deux aurait donne une vignette presque ronde et un
     tirage a peine adouci. */
  border-radius: 7px;
  background: #111;
  cursor: pointer;
  opacity: 0.55;
  filter: brightness(0.62);
  /* `scale` ET NON `transform` : les vignettes ont deja une animation
     d'entree qui occupe `transform`, les deux se battraient. */
  scale: 1;
  transition: opacity 0.5s var(--ease-pose),
              filter 0.5s var(--ease-pose),
              scale 0.5s var(--ease-pose),
              border-color 0.4s var(--ease-pose);
}
/* ELLE SE PRESENTE AU LIEU DE CLIGNOTER. Deux pour cent suffisent : au-dela
   la bande se met a respirer et l'oeil quitte le tirage. */
.proc-vig.la {
  opacity: 1;
  filter: none;
  border-color: rgba(255, 255, 255, 0.55);
  scale: 1.02;
  z-index: 2;
}

/* LE CARTOUCHE QUI SUIT LE CURSEUR.
   UN SEUL ELEMENT POUR LES SIX VIGNETTES, et non un pseudo-element par
   vignette : il glisse de l'une a l'autre au lieu de disparaitre et de
   reapparaitre, et c'est ce glissement qui fait la difference.

   IL EST POSE PAR LE SCRIPT, jamais par le CSS : sa position vient du
   curseur. `position: fixed` evite de recalculer les decalages dans une
   scene collee ; aucun ancetre ne porte de transformation, donc le repere
   reste bien celui de la fenetre.

   SON EMPILEMENT LUI EST PROPRE : au-dessus de tout le contenu de la scene,
   qui monte jusqu'a 5, mais SOUS la barre de navigation (90), qui doit
   rester atteignable. */
.proc-etiq {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 60;
  padding: 5px 10px;
  /* FOND SOMBRE, PAS BLANC. Sur une page noire, un cartouche blanc claque
     comme une alerte au lieu d'accompagner le curseur. Le filet clair suffit
     a le detacher des images. */
  background: rgba(10, 10, 10, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #fff;
  font-family: var(--mono, ui-monospace, Menlo, monospace);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-999px, -999px, 0);
  transition: opacity 0.22s var(--ease);
  will-change: transform;
}
.proc-etiq.la { opacity: 1; }
/* La vignette survolee sort de sa penombre : l'etiquette dit laquelle, le
   changement de matiere dit qu'elle repond. */
/* `:not(.la)` EST NECESSAIRE : cette regle vient APRES celle de la vignette
   active, a priorite egale, donc elle l'emporterait — survoler l'etape en
   cours l'aurait fait RETRECIR au lieu de ne rien faire. */
.proc-vig:not(.la):hover { opacity: 0.85; filter: brightness(0.85); scale: 1.01; }
.proc-vig:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ---- le tirage et ses calques ---- */
.proc-tirage {
  grid-column: 2;
  grid-row: 2;
  position: relative;
  /* CALE EN BAS, PAS EN HAUT. Le plafond 16/9 empeche le tirage de remplir
     sa rangee elastique ; le surplus doit tomber AU-DESSUS de lui, sous le
     titre, et non entre l'image et son texte ou il se lit comme un trou. */
  align-self: end;
  justify-self: start;
  aspect-ratio: 16 / 9;
  /* La hauteur vient de la rangee, la largeur suit le 16/9. Le plafond
     empeche le tirage de depasser la largeur de sa colonne : au-dela, c'est
     `max-width` qui bloquerait et le format serait fausse. */
  height: 100%;
  max-height: calc(min(40vw, 540px) * 9 / 16);
  max-width: 100%;
  overflow: hidden;
  /* LE RAYON FAIT BASCULER LE REGISTRE. Un rectangle net cerne d'un filet est
     une maquette ; la meme surface adoucie devient un objet.
     `overflow: hidden` decoupe le dessin et le calque sortant au rayon, sans
     rien avoir a leur ajouter. */
  border-radius: 14px;
  outline: 1px solid rgba(255, 255, 255, 0.28);
  z-index: 3;
}
/* UN CALQUE PAR ETAPE. Le dessin se met a l'echelle sur lui, et c'est lui
   qui porte le fondu et l'echelle du croisement : le dessin, dedans, garde
   sa propre transformation sans que les deux se battent.
   LE SORTANT PASSE DEVANT : il s'efface par-dessus le nouveau, deja en place
   dessous. */
.proc-calque {
  position: absolute;
  inset: 0;
  overflow: hidden;
}
.proc-calque.proc-sortie { z-index: 1; pointer-events: none; }

/* LE VOLET D'ETALONNAGE A ETE RETIRE avec les photos : les deux couches
   brute et etalonnee, les six reglages de couleur, la poignee et son
   invitation. Le tirage montre maintenant un dessin de l'etape, et il n'y a
   plus deux images a comparer. Regles supprimees plutot que laissees a
   vide. */

/* ---- le texte, sous le tirage ---- */
.proc-texte {
  grid-column: 2;
  grid-row: 3;
  align-self: start;
  z-index: 3;
}
/* ---- LE PASSAGE D'UNE ETAPE A L'AUTRE ----
   LE TEXTE N'A PAS BESOIN DE SORTIR. Son contenu est remplace d'un bloc ;
   ce sont les nouvelles lignes qui montent, decalees les unes des autres.
   Le decalage suffit a faire lire un enchainement plutot qu'une
   substitution, et il evite la demi-seconde morte d'un fondu sortant.

   COURT, PARCE QUE CA SE REJOUE SIX FOIS PAR DEFILEMENT. Une transition
   admirable au premier passage devient penible au troisieme : 280 ms ici,
   quand l'entree de la scene, qui n'arrive qu'une fois, s'etire. */
@keyframes procLigne {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
.proc-texte.entre > * {
  animation: procLigne 280ms var(--ease-pose) both;
}
.proc-texte.entre > *:nth-child(2) { animation-delay: 45ms; }
.proc-texte.entre > *:nth-child(3) { animation-delay: 90ms; }
.proc-texte.entre > *:nth-child(4) { animation-delay: 130ms; }

/* LE GRAND CHIFFRE DU CADRAN. Passer de 03 a 04 doit se voir : le nouveau
   monte depuis le bas, le compteur suit avec un leger retard. Un peu plus
   ample que les lignes de texte, parce que c'est le repere de position dans
   la page et qu'on doit remarquer qu'il a change. */
@keyframes procChiffre {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.proc-lecture.entre .proc-chiffre {
  animation: procChiffre 340ms var(--ease-pose) both;
}
.proc-lecture.entre .proc-compte {
  animation: procChiffre 340ms var(--ease-pose) 70ms both;
}

.proc-titre {
  margin: 4px 0 6px;
  font-size: clamp(19px, 2vw, 30px);
  font-weight: 600;
  letter-spacing: -0.03em;
}
.proc-intro { margin: 0 0 6px; font-size: 14px; color: var(--grey-90); }
.proc-texte .etape-l { margin: 0; }
.proc-texte .etape-l li { font-size: 13.5px; line-height: 1.65; }

/* ---- L'ENTREE DE LA SCENE ----
   MEME GRAMMAIRE QUE LA PAGE A PROPOS, pas une seconde inventee ici : le
   volet qui decouvre, le plan qui se detend, la ligne qui monte sous son
   masque, le fin qui monte en se defloutant, la sphere qui grandit. Les
   enchainements sont ceux d'A propos, reutilises tels quels.

   TROIS REGLES QUI TIENNENT TOUT :

   1. LE CADRAN N'ENTRE PAS PAR SA ROTATION. Le script ecrit `--tour` sur les
      reperes a chaque image ; une animation qui toucherait la meme
      transformation se battrait avec lui. Il entre donc par l'opacite et
      l'echelle du CADRAN, pendant que les reperes continuent de tourner.

   2. TOUT VIT SOUS `proc-vif`, posee par le script apres verification de la
      largeur et du reglage d'animation. Sans elle, la version simple — celle
      des telephones — serait masquee sans jamais etre revelee.

   3. UNE SEQUENCE COURTE, environ 1,8 s contre 3,5 sur A propos. Ici tout le
      propos est de defiler : un visiteur qui lance la molette pendant
      l'entree verrait deux mouvements se superposer. */

/* ---- etats de depart ---- */
/* `:not(.proc-go)` PARTOUT DANS LES ETATS DE DEPART. Une fois la scene
   revelee, c'est le remplissage `both` des animations qui tient l'etat
   d'avant pendant leur retard ; ces regles n'ont donc plus rien a faire, et
   les garder empechait de retirer les animations apres coup. */
.proc-vif:not(.proc-go) .proc-tirage { clip-path: inset(0 100% 0 0); }
.proc-vif:not(.proc-go) .proc-tete .eyebrow,
.proc-vif:not(.proc-go) .proc-tete .lead,
.proc-vif:not(.proc-go) .proc-vig,
.proc-vif:not(.proc-go) .proc-texte,
.proc-vif:not(.proc-go) .proc-appel,
/* La jauge n'est PAS masquee ici : plus rien ne la revele depuis que son
   animation est retiree, elle resterait invisible pour de bon. */
.proc-vif:not(.proc-go) .proc-cadran { opacity: 0; }
.proc-vif .proc-tete .h-xl .line-mask > span {
  opacity: 0;
  transform: translateY(108%);
  transition: none;
}

/* ---- la sequence ---- */
.proc-go .proc-tete .eyebrow      { animation: aproposFine 900ms var(--ease) 60ms both; }
.proc-go .proc-tete .h-xl .line-mask > span {
  animation: aproposLigne 1000ms var(--ease) 140ms both;
}
.proc-go .proc-tete .lead         { animation: aproposFine 900ms var(--ease) 340ms both; }

/* LA BANDE A SON PROPRE ENCHAINEMENT, et non `aproposFine`. Celui d'A propos
   arrive sur `opacity: 1` et `blur(0)` ; avec `both`, ces deux valeurs sont
   FIGEES POUR TOUJOURS. Or une vignette eteinte vit a 0,55 d'opacite et
   0,62 de luminosite : c'est ce qui distingue l'etape en cours des cinq
   autres. Reutiliser l'enchainement tel quel aurait laisse les six vignettes
   allumees, et la bande aurait cesse de dire ou l'on en est.
   Il s'arrete donc sur les valeurs d'une vignette ETEINTE ; `.proc-vig.la`,
   plus loin dans la feuille, rallume celle de l'etape. */
/* UNE SEULE ANIMATION POUR LES SIX, dont la valeur d'arrivee est une
   VARIABLE. Il y en avait deux — l'une finissant a 0,55 pour les vignettes
   eteintes, l'autre a 1 pour l'allumee — et changer d'etape faisait passer
   de l'une a l'autre. Or changer le nom d'une animation la RELANCE : avec un
   retard de 460 a 810 ms et un remplissage `both`, les deux vignettes
   concernees s'effacaient pendant ce retard. Au passage de la premiere a la
   deuxieme etape, defile avant la fin de l'entree, elles devenaient noires.
   Ici, allumer une vignette change la VARIABLE, pas l'animation. Rien ne se
   relance, quel que soit le moment ou l'on defile. */
@keyframes procVignette {
  from { opacity: 0;              transform: translateY(14px); }
  to   { opacity: var(--vig, 0.55); transform: none; }
}
.proc-go .proc-vig:nth-child(1)   { animation: procVignette 760ms var(--ease) 460ms both; }
.proc-go .proc-vig:nth-child(2)   { animation: procVignette 760ms var(--ease) 530ms both; }
.proc-go .proc-vig:nth-child(3)   { animation: procVignette 760ms var(--ease) 600ms both; }
.proc-go .proc-vig:nth-child(4)   { animation: procVignette 760ms var(--ease) 670ms both; }
.proc-go .proc-vig:nth-child(5)   { animation: procVignette 760ms var(--ease) 740ms both; }
.proc-go .proc-vig:nth-child(6)   { animation: procVignette 760ms var(--ease) 810ms both; }
/* La vignette allumee ne change PAS d'animation — elle change la valeur
   d'arrivee de la seule qui existe. C'est toute la difference entre un
   reglage et une relance. */
.proc-vig.la { --vig: 1; }

/* le tirage se decouvre, son image se detend derriere */
.proc-go .proc-tirage             { animation: aproposVolet 1100ms var(--ease) 560ms both; }
/* SEUL LE PREMIER CALQUE SE DETEND. Les suivants arrivent par le croisement ;
   leur donner cette animation la relancerait a chaque etape, retard compris. */
@keyframes procPlan {
  from { transform: scale(1.12); }
  to   { transform: none; }
}
.proc-go .proc-tirage .proc-calque.proc-premier {
  animation: procPlan 1700ms var(--ease) 560ms both;
}

.proc-go .proc-texte              { animation: aproposFine 900ms var(--ease) 980ms both; }
.proc-go .proc-appel              { animation: aproposFine 900ms var(--ease) 1140ms both; }

/* LE CADRAN EN DERNIER, comme le globe sur A propos : le silence qui le
   precede le fait lire comme un second temps, pas comme la suite.
   SON ENCHAINEMENT LUI EST PROPRE : `aproposSphere` arrive sur `opacity: 1`
   et l'aurait fige la, alors que le cadran de la piste M vit a 0,6. Il
   arrive donc sur SA valeur, sinon l'entree annulerait le reglage. */
@keyframes procCadran {
  from { opacity: 0;   transform: scale(0.86); filter: blur(10px); }
  to   { opacity: 0.6; transform: none;        filter: blur(0); }
}
.proc-go .proc-cadran             { animation: procCadran 1300ms var(--ease) 1300ms both; }
/* LA JAUGE N'ENTRE PAS EN ANIMATION. Elle s'efface en fin de course via
   `.fini` ; une animation figee par `both` sur `opacity: 1` l'emporterait
   sur cette regle et la jauge ne disparaitrait plus jamais. Un trait de deux
   pixels ne vaut pas de casser ca. */

/* ---- l'appel, au ras du bas, au-dessus de la jauge ----
   POURQUOI LA COLONNE 3 ET PAS SOUS LE TEXTE : sous la colonne de texte il
   ne reste rien sur un ecran court (le texte finit a 620 pour 633 de scene).
   A droite, l'arc du cadran remonte en s'approchant du bas et degage une
   bande d'environ 310 px de large, libre a toutes les etapes. */
.proc-appel {
  grid-column: 2;
  grid-row: -2 / -1;
  align-self: start;
  justify-self: start;
  z-index: 3;
  max-width: none;
  /* l'espace au texte se pose ICI : un `margin` raccourci pose plus bas
     effacerait un `margin-top` ecrit au-dessus */
  margin: clamp(10px, 1.8vh, 20px) 0 0;
  padding: 0;
}
.proc-appel p { margin: 0; }

/* ---- l'interrupteur de son, discret, en bas de colonne ---- */
.proc-son {
  grid-column: 1;
  grid-row: -2 / -1;
  align-self: end;
  justify-self: start;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  padding: 5px 0 0;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--mono, ui-monospace, Menlo, monospace);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #6a6a6a;
  transition: color 0.3s var(--ease);
}
.proc-son:hover,
.proc-son[aria-pressed="true"] { color: #fff; }
.proc-son-eq { display: flex; align-items: flex-end; gap: 2px; height: 9px; }
.proc-son-eq i { width: 2px; height: 3px; background: currentColor; }
/* Les barres ne s'agitent QUE si le son est allume : un egaliseur qui danse
   sur un bouton muet raconte le contraire de son etat. */
.proc-son[aria-pressed="true"] .proc-son-eq i {
  animation: procEq 0.85s var(--ease-soft, ease-in-out) infinite alternate;
}
.proc-son[aria-pressed="true"] .proc-son-eq i:nth-child(2) { animation-delay: 0.18s; }
.proc-son[aria-pressed="true"] .proc-son-eq i:nth-child(3) { animation-delay: 0.36s; }
@keyframes procEq {
  from { height: 3px; }
  to   { height: 9px; }
}

/* ---- le cadran, a droite, qui deborde ---- */
.proc-cadran {
  position: absolute;
  right: -8vw;
  /* POSE FRANCHEMENT BAS, ET IL SORT PAR LE BAS. Arrive ici, descendre
     imposait de choisir : rétrécir le cercle une troisieme fois, ou le
     laisser deborder. Il deborde deja a droite ; qu'il s'enfonce aussi sous
     le bord inferieur le fait lire comme un instrument trop grand pour la
     fenetre, ce qui est le parti pris de la page.
     LE HAUT DU CERCLE RESTE VISIBLE : c'est la condition. Rogne en haut ET
     en bas, il cesserait d'etre un rond pour devenir une bande courbe. */
  top: calc(50% + 12vh);
  translate: 0 -50%;
  /* LA BORNE EN `vh` TIENT LE CERCLE ENTIER DANS L'ECRAN : au-dela il serait
     rogne en haut ET en bas ET a droite, et ne se lirait plus comme un rond.
     Il ne deborde donc que d'un cote, celui de la fenetre.
     LE DIAMETRE EST POSE UNE SEULE FOIS : l'origine de rotation des reperes
     s'en deduit. Ecrit deux fois, il derive — c'est ce qui faisait tourner
     les six numeros autour d'un point 43 px au-dessus du centre. */
  /* 87 vh et non 92 : les cinq points cedes libèrent la marge qui permet de
     DESCENDRE le cadran sans le faire rogner en bas. Entier, il se lit comme
     un rond ; rogne en haut et en bas, il redevient une bande courbe. */
  --d: min(85vh, 64vw, 980px);
  width: var(--d);
  aspect-ratio: 1;
  z-index: 1;
  /* 0,6 COMME DANS LA PISTE M, PAS 0,92. Le cadran de M est une trame qui
     passe derriere le travail, pas un instrument qu'on lit : trois objets se
     disputent l'ecran (la colonne, le tirage, le cadran) et c'est lui qui
     doit s'effacer. Le monter rendait la page plus chargee, pas plus proche
     du modele. */
  opacity: 0.6;
  pointer-events: none;
}
.proc-cercle {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.3);
}
.proc-cercle::after {
  content: "";
  position: absolute;
  inset: 11%;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.16);
}
/* LA COURONNE ET LE VISEUR NE SONT PAS DANS LA PISTE M. Son cadran est
   presque vide : deux cercles fins et six numeros sur le pourtour. La
   couronne crantee et le triangle le faisaient lire comme un instrument
   grave, ce qui est la piste N, pas la M. On les retire plutot que de les
   supprimer du balisage : la structure reste disponible si tu veux revenir
   a l'instrument. */
.proc-couronne,
.proc-viseur {
  display: none;
}
.proc-grad { position: absolute; inset: 0; transform: rotate(var(--tour)); }
.proc-grad span {
  position: absolute;
  left: 50%;
  top: 3%;
  font-family: var(--mono, ui-monospace, Menlo, monospace);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: #6a6a6a;
  /* LE CENTRE, PAS UNE VALEUR APPROCHANTE. Le repere est pose a 3 % du haut
     du cercle ; son centre de rotation est donc a 50 % - 3 % = 47 % du
     diametre, mesure depuis son propre bord haut. Toute autre valeur lui
     fait decrire une orbite qui n'est pas celle du cadran. */
  transform-origin: 50% calc(var(--d) * 0.47);
  transition: color 0.3s;
}
.proc-grad span.la { color: #fff; }
.proc-viseur {
  position: absolute;
  left: 50%;
  top: -9px;
  translate: -50% 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-top: 10px solid #fff;
}
/* LE CHIFFRE EST EN BAS A GAUCHE DU CADRAN, COMME DANS M (bas 4 cqw, gauche
   6 cqw sur un cadran de 58, soit 7 % et 10 %).
   J'avais d'abord remonte ce bloc a 22 % en croyant que l'appel « Parlons de
   votre film » occupait la zone : c'etait faux. L'appel est en colonne 2 et
   finit a 450 px, le cadran commence a 807. Ils ne se rencontrent jamais. */
.proc-lecture {
  position: absolute;
  left: 10%;
  bottom: 7%;
  z-index: 2;
  text-align: left;
}
.proc-chiffre {
  display: block;
  font-family: var(--mono, ui-monospace, Menlo, monospace);
  font-weight: 400;
  letter-spacing: -0.02em;
  /* 7,6 % du diametre, comme les 4,4 cqw de M sur son cadran de 58 */
  font-size: calc(var(--d) * 0.076);
  color: rgba(255, 255, 255, 0.9);
}
.proc-compte {
  display: block;
  margin-top: 6px;
  font-family: var(--mono, ui-monospace, Menlo, monospace);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: #9a9a9a;
}
.proc-jauge {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: rgba(255, 255, 255, 0.14);
  z-index: 4;
}
.proc-jauge i { display: block; height: 100%; width: 0; background: #fff; }
/* PLEINE, ELLE N'A PLUS RIEN A DIRE. Une jauge de progression arrivee au
   bout n'est qu'un trait blanc en travers de l'ecran, et la scene se decolle
   juste apres : le trait se retrouverait a flotter entre la derniere etape
   et l'appel. Elle s'efface donc en fin de course, et revient si l'on
   remonte. */
.proc-jauge { transition: opacity 0.45s var(--ease); }
.proc-jauge.fini { opacity: 0; }
