/* ═══════════════════════════════════════════════════════════════════════════
   PABLO PIGNOLY — feuille de style unique

   La page d'accueil tient dans une seule fenêtre : accordéon horizontal, le
   projet actif large et tous les autres comprimés en lamelles. Les pages
   catégorie et projet défilent verticalement, normalement.

    1. Polices    2. Jetons     3. Base        4. Primitives    5. Préchargeur
    6. Curseur    7. En-tête    8. Menu        9. Accordéon    10. Pile
   11. Barre     12. Contact   13. Page projet 14. Mouvement   15. Transitions
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. Polices (auto-hébergées : rapides, et conformes RGPD) ────────────── */

/* Arimo — grotesque libre (OFL), métriquement compatible avec Arial.
   Une seule famille sur tout le site : titres, navigation, métadonnées.
   Deux fichiers, 42 ko au total. */
@font-face {
  font-family: 'Arimo';
  src: url('/fonts/arimo-variable.woff2') format('woff2');
  font-weight: 400 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Arimo';
  src: url('/fonts/arimo-italic.woff2') format('woff2');
  font-weight: 400 700; font-style: italic; font-display: swap;
}

/* ── 2. Jetons ───────────────────────────────────────────────────────────── */

:root {
  /* --paper / --ink / --accent viennent de content/site.json */
  --ink-soft: #6B6B67;
  --ink-faint: #9E9E98;
  --rule: #E4E4E0;
  --black: #0B0B0B;

  /* Une seule famille. --display sert aux titres, --sans au reste : deux noms
     pour un même fichier, ce qui permet de séparer les deux rôles plus tard
     sans repasser sur toute la feuille. */
  --display: 'Arimo', Arial, Helvetica, sans-serif;
  --sans: 'Arimo', Arial, Helvetica, sans-serif;

  /* Échelle typographique : cinq degrés, pas un de plus. Les grands titres
     ont été ramenés d'un quart — à leur taille précédente ils écrasaient les
     images au lieu de les annoncer. --fs-num est le seul écart toléré : le
     chiffre qui accompagne un libellé, d'un cran en dessous de lui. */
  --fs-h1:    clamp(2rem, 5.6vw, 4.875rem);
  --fs-h2:    clamp(1.75rem, 4.5vw, 3.75rem);
  --fs-title: clamp(1.05rem, 1.65vw, 1.6rem);
  --fs-body:  clamp(1rem, 1.05vw, 1.125rem);
  --fs-label: clamp(0.75rem, 0.8vw, 0.8125rem);
  --fs-num:   0.85em;

  /* Interlettrages. Un seul pour les capitales, un seul pour les titres —
     l'audit en avait relevé six valeurs différentes pour un même rôle. */
  --track-label: 0.14em;
  --track-display: -0.03em;

  /* Retrait optique des grands titres. Le blanc latéral d'une capitale à 60 px
     est bien plus large que celui d'un libellé à 12 px : sans ce rattrapage,
     deux blocs au même bord de boîte ne paraissent pas alignés. */
  --optical: -0.05em;

  --gutter: clamp(1.25rem, 3.5vw, 3.25rem);
  --section: clamp(5.5rem, 13vw, 13rem);

  /* Géométrie de la fenêtre unique */
  --head-h: clamp(4.75rem, 9vh, 7rem);
  --bar-h: clamp(3.25rem, 6vh, 4.25rem);
  /* Hauteur de la bande de l'accordéon. Le reste de la fenêtre est du blanc,
     et c'est voulu : c'est lui qui donne la tenue. */
  --band-h: clamp(230px, 42vh, 520px);
  /* Sur grand écran, 42 % de la hauteur donne au projet actif un cadrage
     proche du 16/9. Sur téléphone le même réglage l'étirait en colonne : le
     projet ne fait plus que 46 % d'une fenêtre étroite, mais gardait toute la
     hauteur. On cale donc la bande sur la LARGEUR du projet actif — 61 vw
     donne un cadrage voisin du 3/4 — sans jamais dépasser un tiers de l'écran
     sur les téléphones courts. */
  --frame-gap: clamp(0.5rem, 0.9vw, 1rem);

  /* Courbe unique du site. Relevée sur la référence : elle y sert à TOUTES
     les animations de transform. C'est cette discipline — une seule courbe,
     un seul vocabulaire — qui donne la sensation de maîtrise, pas les effets. */
  --ease: cubic-bezier(0.19, 1, 0.22, 1);
  /* Les fondus d'opacité gardent une courbe neutre, comme sur la référence. */
  --ease-io: cubic-bezier(0.4, 0, 0.2, 1);
  /* Apparition des projets au défilement. Relevée telle quelle sur la
     référence : départ franchement adouci, arrivée longue. Rien à voir avec
     --ease, qui part très vite — c'est ce démarrage lent qui donne la glisse. */
  --ease-reveal: cubic-bezier(0.5, 0, 0.2, 1);
  /* Courbe du glissement entre projets : démarrage et arrivée adoucis.
     La courbe --ease part trop vite pour un déplacement de 660 px. */
  --ease-slide: cubic-bezier(0.19, 1, 0.22, 1);
  --dur-slide: 1100ms;

  /* Séquence d'arrivée sur le site. --ease-leave démarre lentement, accélère,
     puis s'éteint très longuement : c'est ce long dégagement final qui évite
     la sensation de transition CSS standard. */
  --dur-open: 1200ms;
  --dur-leave: 600ms;
  --dur-logo-in: 600ms;
  --dur-logo-move: 1400ms;
  --dur-band: 1400ms;
  /* Trajet du logotype : départ et arrivée également adoucis, aucun à-coup
     ni arrêt sec. La courbe est plus douce que --ease, volontairement. */
  --ease-logo: cubic-bezier(0.19, 1, 0.22, 1);
  --ease-open: cubic-bezier(0.19, 1, 0.22, 1);
  --ease-leave: cubic-bezier(0.19, 1, 0.22, 1);
}

/* ── 3. Base ─────────────────────────────────────────────────────────────── */

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.55;
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Les pages d'index tiennent dans la fenêtre : aucun défilement vertical. */
body.page-rail { height: 100svh; overflow: hidden; }
body.is-locked { overflow: hidden; }

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

:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
::selection { background: var(--accent); color: #fff; }

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

.skip {
  position: fixed; top: 0; left: 0; z-index: 1000;
  padding: 0.75rem 1.25rem;
  background: var(--ink); color: var(--paper);
  transform: translateY(-120%);
  transition: transform 200ms var(--ease);
}
.skip:focus-visible { transform: translateY(0); }

/* ── 4. Primitives ───────────────────────────────────────────────────────── */

.container { width: 100%; padding-inline: var(--gutter); margin-inline: auto; max-width: 2000px; }

.label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  line-height: 1.2;
}
.label--muted { color: var(--ink-soft); font-weight: 400; }

.display { font-family: var(--display); font-weight: 400; }

.link-underline { position: relative; display: inline-block; padding-bottom: 0.2em; }
.link-underline::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform 520ms var(--ease);
}
.link-underline:hover::after,
.link-underline:focus-visible::after { transform: scaleX(1); transform-origin: left; }

.link-quiet { color: var(--ink-soft); transition: color 260ms var(--ease); }
.link-quiet:hover { color: var(--accent); }

/* ── 5. Préchargeur ──────────────────────────────────────────────────────── */
/* Séquence : le nom s'ouvre, puis il quitte l'écran, puis l'accordéon arrive.
   Aucun temps mort — la sortie du titre et l'arrivée du site se chevauchent
   de quelques dizaines de millisecondes.

   L'interlettrage part de -6 % et s'écarte jusqu'à 45 %. La marge négative
   compense l'espace que l'interlettrage ajoute après la dernière lettre :
   sans elle le mot dériverait vers la gauche pendant toute l'animation. */

.loader {
  position: fixed; inset: 0; z-index: 950;
  display: grid; place-content: center;
  padding-inline: var(--gutter);
  background: var(--paper);
  opacity: 1;
  transition: opacity 420ms var(--ease-io) 180ms;
}
html.no-js .loader { display: none; }
.loader.is-leaving { opacity: 0; pointer-events: none; }

.loader__title {
  display: block;
  font-family: var(--display);
  font-size: clamp(0.7rem, 1.5vw, 1.15rem);
  font-weight: 500;
  text-transform: uppercase;
  white-space: nowrap;
  letter-spacing: -0.06em;
  margin-right: 0.06em;
  opacity: 0;
  will-change: letter-spacing, transform, opacity;
  animation: loader-open var(--dur-open) var(--ease-open) forwards;
}

/* Ouverture : lent au départ, très progressif, sans à-coup. */
@keyframes loader-open {
  0%   { letter-spacing: -0.06em; margin-right:  0.06em; opacity: 0; transform: translateY(0); }
  12%  { opacity: 1; }
  100% { letter-spacing:  0.45em; margin-right: -0.45em; opacity: 1; transform: translateY(0); }
}

/* Sortie : le geste ne s'arrête pas puis repart — l'interlettrage continue de
   s'ouvrir pendant que le titre monte, ce qui en fait un seul mouvement. */
.loader.is-leaving .loader__title {
  animation: loader-leave var(--dur-leave) var(--ease-leave) forwards;
}

@keyframes loader-leave {
  0%   { letter-spacing: 0.45em; margin-right: -0.45em; opacity: 1; transform: translateY(0); }
  100% { letter-spacing: 0.64em; margin-right: -0.64em; opacity: 0; transform: translateY(-1.7em); }
}

/* --- Intro : le logotype arrive, puis le bandeau s'ouvre ---------------- */
/* On déplace le vrai logotype de l'en-tête plutôt qu'un double : il arrive
   donc exactement à sa place, au pixel près, quelle que soit la fenêtre.
   Le décalage est mesuré en JS et posé dans --wm-x / --wm-y. */

body.is-intro .wordmark {
  opacity: 0;
  transform: translate3d(var(--wm-x, 0px), var(--wm-y, 0px), 0) scale(var(--wm-s, 1));
  will-change: transform, opacity;
}
body.is-intro.wm-visible .wordmark {
  opacity: 1;
  transition: opacity var(--dur-logo-in) var(--ease-io);
}
body.is-intro.wm-home .wordmark {
  transform: none;
  transition: opacity var(--dur-logo-in) var(--ease-io),
              transform var(--dur-logo-move) var(--ease-logo);
}

/* Le reste de l'en-tête et la barre attendent que le logotype soit posé. */
body.is-intro .nav,
body.is-intro .menu-btn,
body.is-intro .bar { opacity: 0; }
body.is-intro.chrome-in .nav,
body.is-intro.chrome-in .menu-btn,
body.is-intro.chrome-in .bar { opacity: 1; transition: opacity 620ms var(--ease-io); }

/* Animations réduites : le trajet du logotype et l'ouverture du bandeau SONT
   ce que la séquence raconte. Les supprimer revenait à supprimer la demande.
   On les raccourcit de moitié ; le reste des animations décoratives demeure
   coupé par le bloc global de la section 14. */
/* ── 6. Curseur ──────────────────────────────────────────────────────────── */
/* Une souris rouge, partout. Le pointeur système est masqué et remplacé par
   une flèche rouge qui suit sans latence ; au survol d'un élément cliquable
   elle s'efface au profit d'un disque.

   Deux couches par pièce : l'enveloppe porte la position (pilotée en JS),
   l'élément intérieur porte l'échelle des états (pilotée en CSS). Sans cette
   séparation les deux transform s'écraseraient l'un l'autre. */

.cursor { display: none; }

/* On ne masque le pointeur système QU'UNE FOIS la souris localisée. Avant le
   premier mouvement — à chaque chargement, donc à chaque changement de page —
   sa position est inconnue : masquer l'un sans pouvoir dessiner l'autre laissait
   la page sans aucun pointeur. */
body.has-cursor.is-tracking,
body.has-cursor.is-tracking * { cursor: none; }

body.has-cursor .cursor {
  display: block;
  position: fixed; top: 0; left: 0;
  /* Au-dessus du préchargeur (950) et du voile de transition (940) : en dessous,
     le curseur disparaissait pendant toute l'arrivée sur le site et à chaque
     passage d'une page à l'autre. */
  z-index: 960;
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms var(--ease);
}

/* Visibilité entièrement pilotée par des classes — jamais par un style en ligne
   que le script devrait ensuite disputer à un `!important`. L'ordre ci-dessous
   est la règle : on montre, puis on masque dans les deux cas qui l'exigent.
   Les trois sélecteurs ont la même spécificité, c'est donc l'ordre qui tranche. */
body.has-cursor.is-tracking .cursor { opacity: 1; }
body.has-cursor .cursor.is-native,
body.has-cursor .cursor.is-out { opacity: 0; }

.cursor__arrow,
.cursor__disc {
  position: absolute; top: 0; left: 0;
  will-change: transform;
}

.cursor__arrow svg {
  display: block;
  fill: var(--accent);
  transform-origin: 12% 8%;
  transition: transform 420ms var(--ease), opacity 260ms var(--ease);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.18));
}

.cursor__ring {
  position: absolute;
  display: grid; place-content: center;
  width: 68px; height: 68px; margin: -34px 0 0 -34px;
  border: 1px solid var(--accent);
  border-radius: 50%;
  opacity: 0; transform: scale(0.28);
  transition: opacity 300ms var(--ease), transform 520ms var(--ease),
              background-color 320ms var(--ease);
}
.cursor__label {
  font-family: var(--sans);
  font-size: 0.6875rem; font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: #fff; opacity: 0; white-space: nowrap;
  transition: opacity 200ms var(--ease);
}

/* Lien, navigation : la flèche se réduit, un anneau l'accompagne. */
.cursor.is-link .cursor__arrow svg { transform: scale(0.58) rotate(-8deg); opacity: 0.45; }
.cursor.is-link .cursor__ring { opacity: 1; transform: scale(0.36); }

/* Sur un lecteur vidéo, le curseur dessiné s'efface (voir `is-native` plus
   haut) et celui du système reprend la main, seul capable de viser les
   commandes. La zone se limite au lecteur — pas à la scène qui l'entoure, sans
   quoi le rouge disparaîtrait bien avant d'atteindre le film. */
body.has-cursor .player,
body.has-cursor .player * { cursor: auto; }

/* Contact : anneau intermédiaire, sans étiquette. */
.cursor.is-mail .cursor__arrow svg { transform: scale(0.55); opacity: 0.4; }
.cursor.is-mail .cursor__ring { opacity: 1; transform: scale(0.55); }

/* Clic : tout se resserre brièvement. */
.cursor.is-down .cursor__arrow svg { transform: scale(0.76); }
.cursor.is-down .cursor__ring { transform: scale(0.9); }

/* ── 7. En-tête ──────────────────────────────────────────────────────────── */

.header {
  position: fixed; inset-inline: 0; top: 0; z-index: 800;
  display: flex; align-items: center; justify-content: space-between;
  gap: 2rem;
  height: var(--head-h);
  padding: 0 var(--gutter);
}

/* Sur les pages qui défilent, l'en-tête est fixe et transparent : sans ce voile
   les titres lui passent au travers. Aplat et coupure franche — catégories comme
   projets : le dégradé laissait traîner un halo sous le bandeau. */
.page-project .header::before,
.page-stack .header::before {
  content: ''; position: absolute; inset: -1px 0 0; z-index: -1;
  background: var(--paper);
  pointer-events: none;
}

/* Pages projet : le logotype se réduit au nom. Sous un bandeau en aplat, la
   ligne « Director » alourdissait le bloc au lieu de le poser. */
.page-project .wordmark__role { display: none; }

/* Le bandeau n'ayant plus que deux lignes à porter, il s'arrête plus haut.
   Sa hauteur se calcule sur ce qu'il contient — les deux lignes du logotype
   (interligne 0,96 × 2) plus une respiration égale au-dessus et au-dessous —
   au lieu d'être une fraction de la hauteur de fenêtre. L'écart reste donc le
   même sur tous les écrans, et le film démarre d'autant plus haut :
   `--head-h` cale aussi le haut du lecteur. */
.page-project {
  --head-h: calc(1.92 * clamp(1.2rem, 1.5vw, 1.6rem) + 2 * 0.95rem);
}

.wordmark {
  display: block;
  font-family: var(--display);
  font-size: clamp(1.2rem, 1.5vw, 1.6rem);
  line-height: 0.96;
  letter-spacing: -0.01em;
}
.wordmark span { display: block; }
.wordmark__logo { width: auto; height: clamp(1.6rem, 2.4vw, 2.4rem); }
.wordmark__role {
  margin-top: 0.4em;
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 400; letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
}

/* Les quatre catégories, énumérées, toujours visibles. */
.nav { display: flex; align-items: flex-start; gap: clamp(1.5rem, 3vw, 3.5rem); }
.nav__list { display: flex; align-items: flex-start; gap: clamp(1.25rem, 2.4vw, 2.75rem); }

.nav__item a {
  display: flex; align-items: baseline; gap: 0.5em;
  font-size: var(--fs-label); font-weight: 400;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
  padding-bottom: 0.4em;
  position: relative;
  transition: color 300ms var(--ease);
}
/* Le chiffre est d'un cran sous le nom et d'un gris plus clair : la hiérarchie
   se lit sans qu'il ait besoin d'une autre casse ni d'un autre interlettrage. */
.nav__count { color: var(--ink-faint); font-variant-numeric: tabular-nums; font-size: var(--fs-num); }
.nav__item a::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0;
  height: 1px; background: var(--accent);
  transform: scaleX(0); transform-origin: right;
  transition: transform 460ms var(--ease);
}
.nav__item a:hover, .nav__item a:focus-visible { color: var(--ink); }
.nav__item a:hover::after, .nav__item a:focus-visible::after { transform: scaleX(1); transform-origin: left; }
.nav__item.is-active a { color: var(--ink); font-weight: 500; }
.nav__item.is-active a::after { transform: scaleX(1); transform-origin: left; }
.nav__item.is-active .nav__count { color: var(--accent); }

.nav__contact {
  font-size: var(--fs-label); font-weight: 400;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink);
  padding-bottom: 0.4em;
  position: relative;
}
.nav__contact::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0;
  height: 1px; background: var(--accent);
  transform: scaleX(0); transform-origin: right;
  transition: transform 460ms var(--ease);
}
.nav__contact:hover::after, .nav__contact:focus-visible::after { transform: scaleX(1); transform-origin: left; }

/* Sélecteur de langue : discret, à droite de Contact. Même corps que la
   navigation, la langue active en noir, l'autre dans le gris des libellés. */
.lang {
  display: flex; align-items: baseline; gap: 0.45em;
  margin: 0;
  font-size: var(--fs-label); font-weight: 400;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.lang__on { color: var(--ink); }
.lang__sep { color: var(--ink-faint); }
.lang__off { color: var(--ink-faint); transition: color 300ms var(--ease); }
.lang__off:hover, .lang__off:focus-visible { color: var(--accent); }

.menu-btn { display: none; position: relative; }
.menu-btn__label {
  display: block;
  font-size: var(--fs-label); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  transition: opacity 260ms var(--ease), transform 320ms var(--ease);
}
.menu-btn__label[data-close] { position: absolute; inset: 0; opacity: 0; transform: translateY(45%); }
.menu-btn[aria-expanded='true'] .menu-btn__label[data-open] { opacity: 0; transform: translateY(-45%); }
.menu-btn[aria-expanded='true'] .menu-btn__label[data-close] { opacity: 1; transform: translateY(0); }

@media (max-width: 1000px) {
  /* La liste des catégories passe dans le menu, mais le choix de la langue
     reste visible : c'est un réglage, pas une destination. */
  .nav { display: flex; gap: 0; }
  .nav__list, .nav__contact { display: none; }
  .menu-btn { display: block; order: 2; margin-left: clamp(1rem, 4vw, 2rem); }
  .lang { order: 1; }
}

/* ── 8. Menu plein écran ─────────────────────────────────────────────────── */

.menu {
  position: fixed; inset: 0; z-index: 790;
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(5rem, 16vh, 9rem) var(--gutter) clamp(2rem, 6vh, 3.5rem);
  background: var(--paper);
  opacity: 0;
  transition: opacity 420ms var(--ease);
}
.menu[hidden] { display: none; }
.menu.is-open { opacity: 1; }

.menu__list { border-top: 1px solid var(--rule); }
.menu__item > a, .menu__item > button {
  display: flex; align-items: baseline; gap: 1rem;
  width: 100%;
  padding: clamp(0.9rem, 2.6vh, 1.5rem) 0;
  border-bottom: 1px solid var(--rule);
  text-align: left;
  opacity: 0; transform: translateY(14px);
}
.menu.is-open .menu__item > a,
.menu.is-open .menu__item > button {
  animation: menu-in 620ms var(--ease) forwards;
  animation-delay: calc(var(--i, 0) * 55ms + 90ms);
}
@keyframes menu-in { to { opacity: 1; transform: translateY(0); } }

.menu__n {
  font-size: var(--fs-label); color: var(--ink-faint);
  font-variant-numeric: tabular-nums; min-width: 2.5em;
}
.menu__item.is-active .menu__n { color: var(--accent); }
.menu__label {
  flex: 1;
  font-family: var(--display);
  font-size: clamp(2rem, 8vw, 3.5rem);
  line-height: 1;
}
.menu__count {
  font-size: var(--fs-label); font-weight: 500;
  letter-spacing: var(--track-label); color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* ── 9. Accordéon horizontal (page d'accueil) ────────────────────────────── */
/* Le projet actif occupe une large part de la fenêtre ; tous les autres sont
   comprimés en lamelles verticales, d'autant plus étroites qu'elles sont
   loin du centre. Les largeurs sont calculées en JS puis normalisées pour
   remplir exactement 100 % — jamais de trou, jamais de débordement. */

.strip {
  position: fixed;
  inset: var(--head-h) 0 var(--bar-h);
  display: flex; align-items: center;
  outline-offset: -4px;
}

.strip__track {
  display: flex;
  width: 100%;
  height: var(--band-h);
  margin-inline: auto;
  /* Indispensable : la piste est un élément flex, et la taille minimale
     automatique d'un élément flex se cale sur son contenu. Sans ce zéro, elle
     ne peut pas descendre à 4 % et la bande n'a plus rien à dérouler. */
  min-width: 0;
  transition: width var(--dur-band) var(--ease-slide);
}

.strip__empty {
  position: fixed; inset: 0;
  display: grid; place-content: center;
  font-size: var(--fs-label); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-faint);
}
.strip__empty[hidden] { display: none; }

/* --- Une lamelle ------------------------------------------------------- */

.slot {
  position: relative;
  /* Largeur pilotée image par image par le ressort en JS : pas de transition
     CSS ici, les deux se combattraient. Le repli sert au rendu sans JS. */
  flex: 0 0 var(--w, calc(100% / var(--n, 13)));
  height: 100%;
  min-width: 0;
  /* `layout` seul, surtout pas `paint` : la légende est posée sous la lamelle
     (top: 100 %), et le confinement de peinture la rognerait purement et
     simplement. Elle resterait dans le DOM, mesurable, mais invisible. */
  contain: layout;
}
.slot__link { display: block; height: 100%; }

/* L'image garde une largeur constante et c'est la lamelle qui la rogne :
   comprimer ne l'écrase pas, cela n'en montre qu'une tranche centrale. */
.slot__media {
  position: relative; display: block;
  height: var(--enter, 100%);
  overflow: hidden;
  background: #EFEFEC;
  filter: grayscale(1) contrast(1.04);
  opacity: 1;
  transition: filter 600ms var(--ease-slide);
}
.slot.is-active .slot__media { filter: none; }

.slot__img {
  position: absolute; top: 0; left: 50%;
  width: var(--media-w, 46vw);
  max-width: none;
  height: 100%;
  object-fit: cover;
  /* Deux rôles dans un seul transform : le -50 % recadre l'image au centre de
     la lamelle, --rise la fait monter depuis le bas sous le masque. C'est ce
     glissement, décalé du centre vers les bords, qui dessine l'arc. */
  transform: translate3d(-50%, var(--rise, 0%), 0) scale(var(--img-s, 1.001));
  opacity: 0;
  transition: opacity 900ms var(--ease-io),
              transform var(--dur-band) var(--ease) var(--reveal, 0ms);
}
.slot__media.is-loaded .slot__img { opacity: 1; }
html.no-js .slot__img { opacity: 1; }
.slot.is-active .slot__link:hover .slot__img { --img-s: 1.03; }

/* Survol d'une lamelle latérale : la couleur revient et l'image respire à
   peine. Volontairement bien en deçà du 1.03 du projet central — il doit
   rester le seul point d'attention. */
.slot:not(.is-active) .slot__link:hover .slot__media { filter: grayscale(0) contrast(1); }
.slot:not(.is-active) .slot__link:hover .slot__img { --img-s: 1.012; }

.slot__media--empty {
  background: repeating-linear-gradient(-45deg, #F5F5F2 0 10px, #EFEFEB 10px 20px);
  display: grid; place-content: center;
}
.slot__label {
  font-size: var(--fs-label); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-faint);
}

.slot__flag {
  position: absolute; top: 0; left: 0;
  transform: translateY(-140%);
  font-size: var(--fs-label); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--accent);
  opacity: 0; transition: opacity 400ms var(--ease);
  white-space: nowrap;
}
.slot.is-active .slot__flag { opacity: 1; }

/* --- Légende, ancrée sous la lamelle active ---------------------------- */

.slot__cap {
  position: absolute; top: 100%; left: 0;
  display: flex; flex-direction: column; gap: 0.4rem;
  /* 42 % de la fenêtre suffit sur un grand écran ; sur un téléphone cela ne
     fait que 164 px et la ligne « 08 / 15 · COMMERCIAL » se brisait en deux.
     On lui garantit donc une largeur lisible, sans jamais dépasser la page. */
  width: max-content;
  max-width: min(calc(100vw - 2 * var(--gutter)), max(42vw, 260px));
  margin-top: clamp(0.9rem, 1.8vw, 1.6rem);
  padding-left: clamp(0.75rem, 3vw, 3.5rem);
  opacity: 0; transform: translateY(9px);
  pointer-events: none;
  transition: opacity 420ms var(--ease), transform 620ms var(--ease-slide);
}
.slot.is-active .slot__cap {
  opacity: 1; transform: none;
  transition-delay: 340ms;
}

/* La legende est transparente aux clics : sa boite, plus large que la lamelle,
   volerait ceux des voisines. Sur le projet actif on la rend cliquable a
   nouveau ligne par ligne — le titre ouvre le projet comme la vignette. */
.slot.is-active .slot__cap > * { pointer-events: auto; }

.slot__meta {
  display: flex; align-items: baseline; gap: clamp(0.75rem, 1.6vw, 1.5rem);
  font-size: var(--fs-label);
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.slot__n { color: var(--accent); font-variant-numeric: tabular-nums; }
.slot__of { color: var(--ink-faint); font-size: var(--fs-num); }
.slot__info { color: var(--ink-soft); }

.slot__title {
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--fs-title);
  line-height: 1.04;
  letter-spacing: var(--track-display);
  position: relative; display: inline-block;
}
.slot__title::after {
  content: ''; position: absolute; inset-inline: 0; bottom: -0.1em;
  height: 1px; background: var(--accent);
  transform: scaleX(0); transform-origin: right;
  transition: transform 560ms var(--ease);
}
.slot.is-active .slot__link:hover .slot__title::after,
.slot.is-active .slot__link:focus-visible .slot__title::after {
  transform: scaleX(1); transform-origin: left;
}

/* --- Arrivée : les lamelles tombent en arc, puis la bande s'ouvre ------- */

/* Formation du bandeau : il naît étroit en son centre et s'ouvre
   simultanément vers la gauche et vers la droite — la piste est centrée par
   `margin-inline: auto`, donc animer sa largeur suffit. Le contenu se révèle
   pendant l'expansion, du centre vers les bords : chaque lamelle porte un
   retard proportionnel à sa distance au centre, posé en JS dans --reveal. */
.strip.is-entering .strip__track { width: 4%; }

/* Navigation interne vers l'accueil : l'état d'ouverture est neutralisé avant
   la première image, la bande est là d'emblée. La classe est posée dans le
   <head>, donc rien ne clignote. */
html.pp-direct .strip.is-entering .strip__track { width: 100%; }
html.pp-direct .strip.is-entering .slot__img { --rise: 0%; }
html.pp-direct .strip.is-entering .slot__cap { opacity: 1; transform: none; }
.strip.is-entering .slot__img { --rise: 108%; }
.strip.is-entering .slot__cap { opacity: 0; transform: translateY(9px); transition-delay: 0ms; }

/* --- Écran étroit : l'accordéon devient un simple défilé ---------------- */
/* Treize lamelles sur 375 px donneraient des bandes de 14 px : inexploitable.
   On repasse à un projet à la fois, avec le défilement natif et son calage. */

/* Sur téléphone, le bandeau reste l'accordéon — pas un défilement natif. La
   page d'accueil ne défile pas verticalement, le doigt n'a donc rien d'autre à
   faire : on lui donne le geste horizontal en entier. Sans `touch-action`, le
   navigateur préempterait le glissement et le rail ne bougerait pas. */
html.is-touch .strip { touch-action: none; }

@media (max-width: 760px) {
  /* Le projet actif occupe 46 % de la largeur ; pour un cadrage de 0,85 il faut
     donc une bande de 46 / 0,85 = 54 % de cette même largeur. */
  :root { --band-h: min(54vw, 34vh); }
}

/* ── 10. Pile verticale (pages catégorie) ────────────────────────────────── */

/* --- Le triptyque et sa légende ---------------------------------------- */

.panel__link { display: block; }

.frame {
  position: relative; display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: #F2F2EF;
}
.frame__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.001);
  filter: grayscale(0.18) contrast(1.02);
  transition: opacity 900ms var(--ease), transform 1200ms var(--ease), filter 700ms var(--ease);
}
.frame.is-loaded .frame__img { opacity: 1; }
html.no-js .frame__img { opacity: 1; }

.panel__link:hover .frame__img { transform: scale(1.035); filter: grayscale(0) contrast(1); }

.frame--empty {
  background: repeating-linear-gradient(-45deg, #F5F5F2 0 10px, #EFEFEB 10px 20px);
  display: grid; place-content: center;
}
.frame__label {
  font-size: var(--fs-label); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-faint);
}

.caption {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.5rem;
  margin-top: clamp(1.1rem, 2.2vw, 2rem);
  text-align: center;
}
.caption__title {
  display: inline-block; position: relative;
  font-family: var(--display); font-weight: 500;
  font-size: var(--fs-title); line-height: 1.1;
  letter-spacing: var(--track-display);
}
.caption__title::after {
  content: ''; position: absolute; inset-inline: 0; bottom: -0.14em;
  height: 1px; background: var(--accent);
  transform: scaleX(0); transform-origin: right;
  transition: transform 560ms var(--ease);
}
.panel__link:hover .caption__title::after,
.panel__link:focus-visible .caption__title::after { transform: scaleX(1); transform-origin: left; }

.caption__meta {
  font-size: var(--fs-label);
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
}

/* --- Page catégorie : projets empilés, trois visuels chacun ------------- */

.stack { padding-top: var(--head-h); }

/* Le titre garde exactement la largeur d'un projet — il est simplement rentre
   d'une gouttiere par son propre padding. Sa boite se calcule donc comme celle
   des projets, mais depuis un conteneur plus large de deux gouttieres : d'ou le
   `- 2 x gouttiere` et le `- 1,6 x gouttiere` (0,8 x 2), qui ramenent les deux
   au meme nombre de pixels. */
.stack__head {
  /* Aucun retrait latéral : le bloc de tête occupe exactement la boîte d'un
     projet, donc le titre et la phrase démarrent sur le bord gauche du premier
     visuel. Le retrait d'une gouttière qui existait ici était la cause du
     décalage relevé à l'audit. */
  padding: clamp(2.5rem, 6vw, 5rem) 0 clamp(2rem, 4vw, 3.5rem);
  width: min(
    calc(100% - 2 * var(--gutter)),
    max(680px, calc(80% - 1.6 * var(--gutter)))
  );
  max-width: 1224px; margin-inline: auto;
}
.stack__title {
  font-family: var(--display); font-weight: 500;
  font-size: var(--fs-h2); line-height: 0.94;
  letter-spacing: var(--track-display);
  /* Retrait optique : cale le bord réel de la lettre sur celui des visuels. */
  margin-left: var(--optical);
}
/* Le compteur ne suit plus la taille du titre — en `em` il tombait à 5,7 px sur
   téléphone. Taille de libellé, fixe, alignée en haut de la capitale. */
.stack__count {
  font-family: var(--sans);
  font-size: var(--fs-label); font-weight: 400;
  letter-spacing: var(--track-label);
  vertical-align: top; color: var(--accent); margin-left: 0.75em;
}
.stack__blurb {
  margin-top: clamp(0.75rem, 1.6vw, 1.25rem);
  max-width: 34ch; color: var(--ink-soft);
}

/* Écart entre projets. Il valait 130 px là où la référence en tient 100 entre
   le bas d'une image et le haut de la suivante, légende comprise — soit plus du
   double une fois la légende ajoutée. Resserré pour approcher cette densité
   sans la copier : notre légende reste plus grande que ses 15 px. */
.stack__list {
  display: flex; flex-direction: column;
  gap: clamp(2.75rem, 4.8vw, 5.5rem);
  padding: 0 var(--gutter) var(--section);
}
/* Réduction de 20 % : mêmes proportions (les visuels sont en 16/9, ils suivent),
   même grille, mêmes écarts — simplement plus de blanc autour de chaque projet.

   La réduction est relative, pas seulement plafonnée : un simple `max-width` ne
   change rien tant que la fenêtre est plus étroite que le plafond, et sur un
   écran de 1440 ou 1512 points l'ancien plafond de 1530 px ne mordait jamais.
   `80 %` garantit les −20 % sur toutes les largeurs, le plafond 1224 px (1530 ×
   0,8) les garantit sur les très grands écrans, et le plancher 680 px empêche
   les petites fenêtres de rétrécir jusqu'à l'absurde. */
.stack__item {
  width: min(100%, max(680px, 80%));
  max-width: 1224px; margin-inline: auto;
}

.frames {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--frame-gap);
}
/* Trois de front à toutes les largeurs : l'alignement horizontal des projets
   fait partie de la composition, l'empiler en changeait la lecture. Seul
   l'écart se resserre. */
@media (max-width: 760px) {
  .frames { gap: 0.35rem; }
}

/* ── 11. Barre basse ─────────────────────────────────────────────────────── */

.bar {
  position: fixed; inset-inline: 0; bottom: 0; z-index: 700;
  display: flex; align-items: center;
  gap: clamp(1rem, 2.5vw, 2.5rem);
  height: var(--bar-h);
  padding-inline: var(--gutter);
  font-size: var(--fs-label);
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
}
.bar__count { font-variant-numeric: tabular-nums; flex: none; }
.bar__count b { font-weight: 500; color: var(--ink); }
.bar__sep { padding-inline: 0.5em; color: var(--ink-faint); }

.bar__track { flex: 1; height: 1px; background: var(--rule); overflow: hidden; }
.bar__track i {
  display: block; height: 100%; width: 100%;
  background: var(--accent);
  transform: scaleX(0); transform-origin: left;
}

.bar__next {
  flex: none;
  display: inline-flex; align-items: center; gap: 0.6em;
  letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
  transition: color 300ms var(--ease);
}
.bar__next:hover { color: var(--ink); }
.bar__arrow { transition: transform 460ms var(--ease); }
.bar__next:hover .bar__arrow { transform: translateX(0.35em); }
.bar__next[disabled] { opacity: 0.35; pointer-events: none; }

@media (max-width: 620px) { .bar__next span:first-child { display: none; } }

/* ── 12. Contact ─────────────────────────────────────────────────────────── */

.sheet {
  position: fixed; inset: 0; z-index: 850;
  display: flex; flex-direction: column; justify-content: center;
  padding: var(--head-h) var(--gutter) var(--bar-h);
  background: var(--paper);
  opacity: 0;
  transition: opacity 420ms var(--ease);
}
.sheet[hidden] { display: none; }
.sheet.is-open { opacity: 1; }

.sheet__close {
  position: absolute; top: clamp(1.1rem, 2.2vw, 2rem); right: var(--gutter);
  font-size: var(--fs-label); font-weight: 500;
  letter-spacing: var(--track-label); text-transform: uppercase;
}
.sheet__close:hover { color: var(--accent); }

.sheet__inner { max-width: 1400px; margin-inline: auto; width: 100%; }

.sheet__mail {
  display: block;
  font-family: var(--display); font-weight: 400;
  font-size: var(--fs-h2);
  line-height: 1.02; letter-spacing: var(--track-display);
  margin-block: clamp(1rem, 2.5vw, 2rem) clamp(2rem, 5vw, 4rem);
  word-break: break-word;
  transition: color 420ms var(--ease);
}
.sheet__mail:hover { color: var(--accent); }

.sheet__row {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 1rem 2rem;
  padding-top: clamp(1.25rem, 2.5vw, 2rem);
  border-top: 1px solid var(--rule);
}
.sheet__social { display: flex; flex-wrap: wrap; gap: clamp(1rem, 2vw, 2rem); }
.sheet__social a {
  font-size: var(--fs-label); letter-spacing: var(--track-label); text-transform: uppercase;
  color: var(--ink-soft);
  position: relative; padding-bottom: 0.25em;
  transition: color 300ms var(--ease);
}
.sheet__social a::after {
  content: ''; position: absolute; inset-inline: 0; bottom: 0; height: 1px;
  background: var(--accent);
  transform: scaleX(0); transform-origin: right;
  transition: transform 440ms var(--ease);
}
.sheet__social a:hover { color: var(--ink); }
.sheet__social a:hover::after { transform: scaleX(1); transform-origin: left; }

/* ── 13. Page projet (défilement vertical normal) ────────────────────────── */

.page-project { scroll-behavior: smooth; }
/* Le film ne prend plus toute la largeur : il garde des bandes de part et
   d'autre et, surtout, il tient ENTIÈREMENT dans la fenêtre — plus besoin de
   faire défiler pour en voir le bas.

   La largeur est le plus petit de trois nombres : la place disponible, la
   largeur qui fait tenir la hauteur sous l'en-tête (d'où `--ar`, le rapport
   du film écrit en nombre par le build), et un plafond qui garantit les
   bandes latérales même sur un écran bas et large. */
.project__stage {
  padding: calc(var(--head-h) + clamp(1rem, 2.5vh, 2rem)) var(--gutter) 0;
}
.stage-one, .stage-pair {
  --stage-max: 1280px;
  width: min(
    100%,
    calc((100svh - var(--head-h) - clamp(4rem, 9vh, 7rem)) * var(--ar, 1.7778)),
    var(--stage-max)
  );
  margin-inline: auto;
}

/* Deux films côte à côte : même page, même rythme, seul le haut change.
   --stage-ratio suit la source — 4/5 pour du vertical, 16/9 sinon. */
.stage-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(6px, 0.7vw, 12px);
}
/* Les deux films restent côte à côte à toutes les largeurs : les empiler
   changeait la lecture d'un diptyque en celle de deux plans successifs. */

/* Le cadre suit le format déclaré du film. Il était figé à 16/9 pour un film
   seul — `--stage-ratio` n'agissait que sur les paires —, ce qui ajoutait des
   bandes noires à tout ce qui n'est pas exactement 16/9. */
.player { position: relative; aspect-ratio: var(--stage-ratio, 16 / 9); background: var(--black); overflow: hidden; }
.player__poster { width: 100%; height: 100%; object-fit: cover; }
.player__poster--empty {
  display: block; width: 100%; height: 100%;
  background: repeating-linear-gradient(-45deg, #101010 0 12px, #0B0B0B 12px 24px);
}
.player iframe, .player video.player__file {
  position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
}

.player__flag {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  font-size: var(--fs-label); letter-spacing: var(--track-label); text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
  border: 1px dashed rgba(255, 255, 255, 0.3);
  padding: 0.9rem 1.4rem; text-align: center;
}

.project__head { padding-block: clamp(2.5rem, 6vw, 5rem) clamp(3rem, 7vw, 6rem); }

/* La catégorie annonce le projet : c'est le seul rouge de la page, et il tient
   dans la taille d'un libellé. Un accent, pas un titre. */
.project__cat { color: var(--accent); font-weight: 400; }
.project__cat a { transition: opacity 260ms var(--ease); }
.project__cat a:hover, .project__cat a:focus-visible { opacity: 0.6; }
.project__title {
  font-family: var(--display); font-weight: 500;
  font-size: var(--fs-h1); line-height: 0.94;
  letter-spacing: var(--track-display);
  margin-block: clamp(0.75rem, 1.6vw, 1.25rem) clamp(2.5rem, 6vw, 4.5rem);
  margin-left: var(--optical);
  max-width: 20ch;
}
.project__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  border-top: 1px solid var(--rule);
  padding-top: clamp(1.5rem, 3vw, 2.25rem);
}
.fact dt { margin-bottom: 0.5rem; }
.fact__value { font-family: var(--display); font-size: clamp(1rem, 1.3vw, 1.25rem); line-height: 1.3; }

/* Mosaïque : rangée de deux, rangée de trois, et on répète. Les visuels
   s'emboîtent au lieu de s'étaler, écarts serrés.

   Aucun recadrage : chaque photo garde son format d'origine, porté par --r.
   Dans une rangée les largeurs sont proportionnelles aux rapports (flex-grow
   = --r, base 0), donc toutes les hauteurs tombent d'elles-mêmes sur la même
   valeur et la rangée remplit exactement la largeur. Un projet vertical reste
   vertical, un 4/3 reste un 4/3, et les projets tournés en 16/9 — la grande
   majorité — rendent exactement comme avant. */
.project__gallery {
  display: flex; flex-direction: column;
  gap: clamp(6px, 0.7vw, 12px);
  padding-bottom: var(--section);
}
.still-row {
  display: flex; align-items: flex-start;
  gap: clamp(6px, 0.7vw, 12px);
}
.still {
  margin: 0;
  flex: var(--r, 1.778) 1 0; min-width: 0;
  position: relative;
  aspect-ratio: var(--r, 1.778);
  overflow: hidden;
  background: #F2F2EF;
}
.still img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* La mosaïque garde ses rangées de deux et de trois quelle que soit la largeur,
   pour la même raison que le triptyque : c'est l'emboîtement qui fait la page. */
.still-placeholder {
  padding: clamp(3rem, 9vw, 6rem); text-align: center;
  border: 1px dashed var(--rule); color: var(--ink-faint);
  font-size: var(--fs-label); letter-spacing: 0.1em; text-transform: uppercase;
}
.still-placeholder code { font-family: var(--sans); color: var(--ink-soft); }

/* Bas de page projet : une seule proposition, sur toute la largeur.

   L'ancienne version tenait sur deux colonnes, une par voisin. En n'en gardant
   qu'un, il restait une demi-colonne vide, un filet vertical qui ne séparait
   plus rien, et un titre calé à droite sans raison. Le bloc est donc refait :
   texte à gauche sur la grille de la page — même bord que la catégorie, le
   titre et les photos au-dessus — et visuel à droite, visible d'emblée. */
.pager { border-top: 1px solid var(--rule); }
.pager__link {
  display: flex; align-items: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  padding: clamp(2rem, 4vw, 3.25rem) var(--gutter);
  transition: background-color 500ms var(--ease);
}
.pager__link:hover { background: #FAFAF8; }

.pager__text { flex: 1; display: flex; flex-direction: column; gap: 0.7rem; min-width: 0; }
/* `flex-wrap` plutôt qu'une seule ligne rigide : sur une colonne étroite, le
   groupe passe à la ligne ENTRE les deux libellés au lieu de couper
   « Next project » en deux. */
.pager__label { display: flex; align-items: baseline; gap: 0.6em; flex-wrap: wrap; }
.pager__label .label { white-space: nowrap; }
.pager__sep { color: var(--ink-faint); }

/* Le titre et la flèche forment un seul groupe : la flèche annonce le clic là
   où l'oeil lit, au lieu de partir seule à l'autre bout de la ligne. */
.pager__line { display: flex; align-items: center; gap: 1.1rem; }

.pager__title {
  font-family: var(--display); font-weight: 500;
  font-size: var(--fs-title); line-height: 1.02;
  letter-spacing: var(--track-display);
  /* Même rattrapage optique que les autres titres : l'encre tombe sur la même
     ligne que le libellé au-dessus, et non le bord de la boîte. */
  margin-left: var(--optical);
  transition: transform 620ms var(--ease), color 400ms var(--ease);
}
.pager__link:hover .pager__title { transform: translateX(0.25em); color: var(--accent); }

/* Le visuel ne se mérite plus : il est là dès l'arrivée, et c'est la couleur
   qui revient au survol — comme partout ailleurs sur le site. */
.pager__thumb { flex: none; display: block; width: min(300px, 26vw); }
.pager__link:hover .media__img { filter: grayscale(0) contrast(1); transform: scale(1.035); }

.pager__go {
  flex: none; display: flex; align-items: center;
  color: var(--ink-faint);
  transition: color 400ms var(--ease), transform 620ms var(--ease);
}
@media (max-width: 680px) { .pager__line { gap: 0.9rem; } }
.pager__link:hover .pager__go { color: var(--accent); transform: translateX(0.4em); }

.media { position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden; background: #F2F2EF; }
.media__img, .media__preview {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(0.2) contrast(1.02);
  transition: transform 1100ms var(--ease), filter 700ms var(--ease), opacity 500ms var(--ease);
}
.media__preview { opacity: 0; }
.media__placeholder {
  position: absolute; inset: 0; display: grid; place-content: center;
  background: repeating-linear-gradient(-45deg, #F5F5F2 0 10px, #EFEFEB 10px 20px);
}
.media__placeholder-label {
  font-size: var(--fs-label); letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-faint);
}
.pager__link:hover .media__preview.is-playing { opacity: 1; }

/* Le bloc reste une ligne sur téléphone : texte à gauche sur la grille,
   visuel à droite. La vignette suit la largeur de la fenêtre (26 %), elle n'a
   donc pas besoin de passer au-dessus pour tenir. */

/* ── 404 ─────────────────────────────────────────────────────────────────── */

.notfound {
  min-height: 70svh;
  display: flex; flex-direction: column; justify-content: center; gap: 1.25rem;
  padding: var(--section) var(--gutter);
}
.notfound .display { font-size: var(--fs-h1); line-height: 0.95; }

/* ── 14. Mouvement ───────────────────────────────────────────────────────── */

/* Apparition au défilement, calquée sur la référence : une longue montée, et
   un fondu encore plus long que le mouvement. C'est ce décalage — l'opacité
   met 1,5 s quand la translation en met 0,95 — qui fait que le bloc semble se
   poser plutôt qu'arriver. Notre réglage précédent faisait l'inverse (fondu
   plus court que le mouvement) sur 18 px seulement : le bloc apparaissait au
   lieu de glisser.

   La distance se règle par bloc : un projet de page catégorie vient de loin,
   une ligne d'information de beaucoup moins près. */
[data-reveal] {
  opacity: 0; transform: translateY(var(--rise, 28px));
  transition: opacity 1500ms var(--ease-reveal), transform 950ms var(--ease-reveal);
  transition-delay: var(--delay, 0ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }
html.no-js [data-reveal] { opacity: 1; transform: none; }

/* Arrivée sur la page : ce qui est déjà dans le cadre quand le voile se lève.
   La courbe de la référence démarre très lentement — parfait pour un bloc qui
   monte depuis le bas de l'écran, désastreux pour un bloc déjà en place : on
   restait une demi-seconde devant une page blanche, puis le contenu montait si
   mollement qu'il paraissait terne. Ici, décélération pure : le contenu part à
   pleine vitesse, il est lisible avant même que le voile ait fini de partir. */
[data-reveal].is-arrival {
  transition: opacity 620ms cubic-bezier(0, 0, 0.2, 1),
              transform 820ms cubic-bezier(0, 0, 0.2, 1);
  transition-delay: 0ms;
}

/* La référence monte de 100 px. Sur nos blocs — plus hauts, puisqu'ils portent
   trois visuels — le geste paraissait trop ample : 64 px suffisent à donner la
   glisse sans que le projet ait l'air de tomber dans la page. */
.stack__item { --rise: 64px; }
.still { --rise: 44px; }

/* Montée du texte, mot par mot : une seule courbe, une seule direction.
   La version précédente passait par 44 %, retombait à 16 %, remontait à 76 %,
   retombait à 48 % — deux rechutes en une seconde, avec un flou animé. L'effet
   se lisait comme un défaut d'affichage plutôt que comme une intention.

   Le déplacement vertical est plus ample qu'avant (0,28 em contre 0,16) pour
   compenser la disparition du clignotement, et le décalage entre les mots est
   conservé : le rythme est le même, seul le battement a disparu.

   L'attribut garde son nom d'origine — il sert de point d'accroche au script
   qui découpe le texte en mots, il n'a jamais décrit l'animation.

   Plus de `filter` : opacité et translation se composent sur le GPU sans rien
   recalculer, ce qui rend inutile la variante allégée des appareils modestes. */
.word { display: inline-block; white-space: pre; }
[data-ignite] .word {
  opacity: 0; transform: translateY(0.28em);
}
[data-ignite].is-lit .word {
  animation: rise 900ms var(--ease) forwards;
  animation-delay: calc(var(--i) * 70ms + var(--delay, 0ms));
}
@keyframes rise {
  from { opacity: 0; transform: translateY(0.28em); }
  to   { opacity: 1; transform: translateY(0); }
}
html.no-js [data-ignite] .word { opacity: 1; transform: none; }

html.is-lowpower .frame__img { transition-duration: 400ms; }

/* ── 15. Transitions de page ─────────────────────────────────────────────── */

@view-transition { navigation: auto; }

/* Fondu enchaîné entre deux pages : la sortie et l'entrée se recouvrent. Ces
   durées sont celles d'origine, et elles sont volontairement restaurées telles
   quelles — un enchaînement séquentiel supprime bien la superposition, mais il
   ouvre un trou pendant lequel PLUS RIEN n'est peint : page entièrement blanche,
   en-tête compris. Constaté à l'image sur un enregistrement.

   La superposition des photos entre deux pages catégorie ne se règle donc pas
   ici, mais en amont : voir `stripNames()` dans main.js et `pp-from-work` dans
   base.html, qui empêchent les visuels de former des groupes indépendants
   quand les deux pages ont la même mise en page. */
::view-transition-old(root) { animation: vt-out 260ms var(--ease-io) both; }
::view-transition-new(root) { animation: vt-in 520ms var(--ease) both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(12px); } }
::view-transition-group(*) { animation-duration: 640ms; animation-timing-function: var(--ease); }

/* Deux pages catégorie ont la même mise en page : leurs visuels occupent les
   mêmes rectangles. S'ils portent chacun un nom de transition, ils forment
   treize groupes indépendants qui se croisent sur 640 ms, et l'on voit les
   photos de l'une à travers celles de l'autre. On retire donc les noms de part
   et d'autre pour ce seul trajet : les visuels rentrent dans l'instantané de la
   page et suivent le fondu d'ensemble, comme partout ailleurs.

   Les autres trajets — accueil vers catégorie, catégorie vers projet — gardent
   leurs noms et leur transformation d'un visuel à l'autre.

   `!important` est nécessaire : le nom est écrit en style en ligne par le
   build, et seule une déclaration importante peut le neutraliser. */
html.pp-from-work .frames { view-transition-name: none !important; }

/* Repli des navigateurs sans transition entre documents. Mêmes temps que la
   version native, pour que le site se comporte pareil partout : on couvre en
   220 ms — la durée de sortie —, puis on découvre en 460 ms, celle d'entrée. */
.transition {
  position: fixed; inset: 0; z-index: 940;
  background: var(--paper);
  opacity: 0; pointer-events: none;
  transition: opacity 460ms var(--ease);
}
.transition.is-active { opacity: 1; transition-duration: 220ms; transition-timing-function: var(--ease-io); }
