/* ─────────────────────────────────────────────────────────────────────────────
   Ancestral Beat — proto lecteur.
   Reprend les tokens du portfolio : Inter, #1C1C1E sur blanc, accent #3700FF,
   nav en colonne à gauche.
   ───────────────────────────────────────────────────────────────────────────── */

/* ── Grille ───────────────────────────────────────────────────────────────
   Deux mesures gouvernent la mise en page : la colonne réservée à la nav, et
   la largeur du contenu. Tous les blocs s'y alignent, et les marges gauche et
   droite sont égales pour que l'ensemble reste centré dans la fenêtre. */
:root {
  --marge-laterale: 170px;   /* de chaque côté, la nav tient dans celle de gauche */
  --contenu: 1080px;         /* largeur de la grille de contenu */
  --gouttiere: 56px;         /* entre la colonne de texte et son visuel */
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: #fff;
  color: #1C1C1E;
  /* Inter ne contient aucun caractère chinois : sans ces trois secours, le
     texte zh tomberait sur la police système par défaut, souvent laide et
     d'une graisse qui jure avec le reste. PingFang sur macOS et iOS,
     YaHei sur Windows, Noto Sans SC ailleurs. */
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue',
               'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
  -webkit-font-smoothing: antialiased;
}

body { overflow: hidden; }

/* ── Chargement du moteur audio ──────────────────────────────────────────────
   Les 79 échantillons mettent plusieurs secondes à arriver, surtout sur un
   réseau mobile. Le moteur commence à charger dès l'ouverture du site, quelle
   que soit la page — mais on peut descendre jusqu'à Écouter plus vite que le
   réseau. Sans signe visible, le visiteur touche l'écran, rien ne se passe, et
   il en conclut que le site est cassé.

   Deux signes, aucun élément nouveau : les commandes restent en retrait et
   sourdes aux clics, et la ligne d'état respire. Tout revient à la normale
   quand player.js pose `moteur-pret` sur le corps du document.

   Portée limitée à la page Écouter : les commandes vidéo de Jouer n'ont rien à
   voir avec le moteur audio, les éteindre pendant son chargement serait faux. */

body:not(.moteur-pret) #page-listen .transport,
body:not(.moteur-pret) #page-listen .track {
  opacity: 0.3;
  pointer-events: none;
}

#page-listen .transport,
#page-listen .track { transition: opacity 0.5s ease; }

body:not(.moteur-pret) #status {
  animation: respiration 1.6s ease-in-out infinite;
}

@keyframes respiration {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}

/* ── Bouton de langue ───────────────────────────────────────────────────────
   Disque noir de 40 px en haut à droite, repris du portfolio. Il vit hors des
   pages, en `fixed`, pour rester au même endroit d'une page à l'autre pendant
   que celles-ci glissent. */

.lang-cycle {
  position: fixed;
  top: 26px;
  right: 26px;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: #1C1C1E;
  color: #fff;
  font: 600 12px/1 'Inter', 'PingFang SC', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
  letter-spacing: 0.05em;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  transition: background 0.25s ease;
  z-index: 300;
}

.lang-cycle:hover { background: #3700FF; }
.lang-cycle:focus-visible { outline: 2px solid #3700FF; outline-offset: 3px; }

/* ── Nav gauche ───────────────────────────────────────────────────────────── */

.nav {
  position: fixed;
  left: 32px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
}

.nav a {
  display: block;
  position: relative;
  font-size: 14px;
  font-weight: 400;
  color: #1C1C1E;
  line-height: 1.6;
  text-decoration: none;
  transition: opacity 0.25s ease;
}

.nav a:hover { opacity: 0.45; }

.nav a.active { color: #3700FF; }

/* Pastille du lien actif, posée dans la marge pour ne pas décaler le texte */
.nav a.active::before {
  content: '';
  position: absolute;
  left: -12px;
  top: 50%;
  transform: translateY(-50%);
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #3700FF;
}

/* ── Colonne centrale ─────────────────────────────────────────────────────── */

/* Les pages sont empilées au même endroit et glissent verticalement.
   Pas de display:none pour masquer l'inactive : le canvas de l'herbe et la
   vidéo ont besoin d'une taille réelle pour se dimensionner correctement (le
   même piège que le hero du portfolio, où visibility:hidden cassait le viewport
   de l'iframe). On les sort du cadre par transform, elles restent rendues. */
.page {
  position: fixed;
  inset: 0;
  will-change: transform;
  /* Les cinq pages occupent le même rectangle. Sans état initial en CSS, elles
     s'empilent toutes visiblement entre l'affichage du HTML et le premier
     passage de pages.js — sur un téléphone lent, ça se voit franchement.
     Chaque fichier généré marque sa propre page avec `data-page-active`, ce qui
     règle la question avant même que le JavaScript ne démarre. */
  opacity: 0;
}

.page[data-page-active] { opacity: 1; }

/* La ligne d'état parle des knobs : elle n'a de sens que sur Écouter. Elle vit
   hors des pages, en position fixe, donc rien ne la masque d'elle-même — et au
   chargement elle s'affichait par-dessus l'accueil. */
#status { opacity: 0; }
body[data-page="listen"] #status { opacity: 1; }

/* Le message de chargement a aussi sa place sur l'accueil : c'est la page où
   l'on arrive, et donc celle pendant laquelle le moteur travaille. Le cacher
   là revenait à faire patienter sans rien dire.

   Il se pose à mi-hauteur entre le bouton « Faire défiler » et les contacts.
   Au ras du bord, il tombait exactement sur l'adresse mail : deux lignes de
   texte gris à la même hauteur, dont une seule cliquable.

   Seulement pendant le chargement. Dès que `moteur-pret` arrive, le message
   devient l'invite à tourner les boutons, qui ne concerne qu'Écouter. La règle
   s'éteint d'elle-même, sans rien à gérer en JS. */
body[data-page="accueil"]:not(.moteur-pret) #status {
  opacity: 1;
  bottom: 100px;
}

.stage {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 34px;
  padding: 40px 20px;
}

/* ── Page Jouer ───────────────────────────────────────────────────────────── */

.statement {
  margin: 0;
  /* Le texte se répartit tout seul : plus de coupure forcée après le
     deux-points, la ligne tombe où la largeur le veut. */
  max-width: var(--cover, 300px);
  /* Pas de `font-size` ici : ce paragraphe vit dans `.identity`, donc
     `.identity p` (0,1,1) l'emporte sur `.statement` (0,1,0). La taille
     déclarée ici n'a jamais rien peint. Sa taille est celle du corps commun,
     et c'est très bien ainsi : c'est le même registre de texte. */
  font-weight: 400;
  line-height: 1.5;
  color: #1C1C1E;
}

/* La vidéo déborde de son cadre : 1,3× plus grande, centrée dessus.
   Le cadre lui-même garde la taille de la pochette, donc rien ne bouge autour —
   le texte au-dessus et en dessous reste exactement à sa place, et la vidéo
   passe dessous. */
#page-jouer .cover {
  position: relative;
  overflow: visible;
}

.cover video {
  position: absolute;
  top: 0;
  left: 0;
  /* Taille et position posées en pixels ENTIERS par video.js.
     Les pourcentages donnaient des dimensions fractionnaires (147.6 % de 300 =
     442.797 px) et le compositeur du navigateur rendait ce bord partiel comme
     un liseré clair. Ni `transform`, ni `scale`, ni `background` ici : chacun
     ajoutait sa propre frange — le scale par son interpolation, le fond par le
     pixel qu'il laisse dépasser quand la vidéo ne couvre pas exactement. */
  object-fit: cover;   /* la vidéo est en 16:9, le cadre en carré */
  cursor: pointer;     /* la vidéo elle-même déclenche lecture/pause */
  z-index: 0;
}

/* Le texte et les commandes repassent devant la vidéo qui déborde. */
#page-jouer .identity,
#page-jouer .track,
#page-jouer .transport {
  position: relative;
  z-index: 1;
}

.research {
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  color: #1C1C1E;
}

.research a {
  color: #3700FF;
  text-decoration: none;
}

.research a:hover { text-decoration: underline; }

/* Disques noirs — mêmes que les knobs, pour que les commandes de la vidéo
   appartiennent à la même famille visuelle. */
.disc {
  flex-shrink: 0;
  width: 46px;
  height: 46px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: #1C1C1E;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.2s ease;
}

.disc svg { width: 26px; height: 26px; display: block; }
.disc:hover  { opacity: 0.75; }
.disc:active { opacity: 0.55; }
.disc:focus-visible { outline: 2px solid #3700FF; outline-offset: 3px; }

/* Le disque de lecture fait exactement la taille d'un knob de Composer : les
   deux pages partagent la même famille de commandes rondes. */

/* Reculer / avancer de 10 s : pas de pastille, juste le trait noir sur le fond
   de la page, et plus grands que les disques pour rester lisibles sans leur
   contraste. */
.seek {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.2s ease;
}

.seek svg { width: 38px; height: 38px; display: block; }
.seek:hover  { opacity: 0.45; }
.seek:active { opacity: 0.25; }
.seek:focus-visible { outline: 2px solid #3700FF; outline-offset: 3px; border-radius: 6px; }

.transport-video {
  align-items: center;
  justify-content: center;
  gap: 54px;
}

.identity { text-align: left; width: var(--cover, 300px); }

.identity h1 {
  margin: 0;
  font-size: 20px;
  font-weight: 500;
  letter-spacing: -0.01em;
}

.identity p {
  margin: 2px 0 0;
  font-size: 15px;
  font-weight: 400;
}

/* ── Pochette ─────────────────────────────────────────────────────────────── */

.cover {
  width: var(--cover, 300px);
  height: var(--cover, 300px);
  flex-shrink: 0;
  will-change: transform;
}

/* ── Carrousel des morceaux ───────────────────────────────────────────────── */
/* La carte courante vit dans le flux ; ses voisines sont clonées en position
   absolue de part et d'autre, hors du cadre. Le conteneur masque le débordement
   pour qu'on ne voie arriver la suivante qu'au moment du geste. */
.carousel {
  position: relative;
  /* Plus large que la carte pour laisser voir un bout des voisines — mais les
     bords s'effacent en dégradé, si bien qu'elles se devinent en arrivant au
     lieu de surgir d'un coup. La carte courante, elle, tient dans la zone
     pleinement opaque du milieu. */
  width: calc(var(--cover, 300px) * 2.1);
  display: flex;
  justify-content: center;
  /* Coupure franche et serrée : la carte courante occupe 25 %–75 % du conteneur,
     et la bascule vers le transparent se fait sur 5 % à peine de chaque côté
     (~36 px). La voisine reste donc invisible jusqu'au dernier moment, puis
     apparaît d'un coup au lieu de se deviner sur toute la largeur. */
  -webkit-mask-image: linear-gradient(90deg,
      transparent 20%, #000 25%, #000 75%, transparent 80%);
          mask-image: linear-gradient(90deg,
      transparent 20%, #000 25%, #000 75%, transparent 80%);
}

.slide {
  display: flex;
  flex-direction: column;
  gap: 34px;
  width: var(--cover, 300px);
  will-change: transform;
}

/* Les clones se superposent au flux sans le pousser. */
.slide.voisine {
  position: absolute;
  top: 0;
  left: 50%;
  margin-left: calc(var(--cover, 300px) / -2);   /* centrée comme la carte réelle */
  pointer-events: none;
}

/* La carte de Composer se saisit à la souris pour changer de morceau. */
#page-listen .slide { cursor: grab; }
#page-listen .slide:active { cursor: grabbing; }

.cover canvas { display: block; }

/* ── Bloc morceau ─────────────────────────────────────────────────────────── */

.track { width: var(--cover, 300px); }

.track-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.track-title {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.track-pos {
  font-size: 12px;
  font-weight: 400;
  color: #8a8a8e;
  font-variant-numeric: tabular-nums;
}

/* Crédit défilant, façon afficheur de gare : le texte fait le tour en continu
   sur la largeur de la pochette. Deux copies identiques se suivent et la piste
   glisse de -50 % : quand la première copie sort, la seconde est exactement à sa
   place, donc la boucle ne se voit pas. */
.track-credit {
  margin: 3px 0 0;
  width: var(--cover, 300px);
  overflow: hidden;
  white-space: nowrap;
  font-size: 14.5px;
  font-weight: 400;
  color: #1C1C1E;
  /* masque les bords : le texte s'efface au lieu d'être tranché net.
     Fondu court (2 %, soit ~6 px) : juste de quoi adoucir la coupe sans manger
     une lettre entière à chaque bout. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 2%, #000 98%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 2%, #000 98%, transparent);
}

.marquee-track {
  display: inline-flex;
  will-change: transform;
  animation: marquee var(--marquee-dur, 18s) linear infinite;
}

.marquee-item { padding-right: 3.5em; }

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

/* Un texte qui bouge en permanence gêne certaines personnes — et le défilement
   n'apporte rien si tout tient déjà dans la largeur. */
@media (prefers-reduced-motion: reduce) {
  /* La respiration s'arrête : c'est le texte qui porte l'information, pas le
     mouvement. */
  body:not(.moteur-pret) #status { animation: none; opacity: 1; }

  .marquee-track { animation: none; }
  .track-credit { mask-image: none; -webkit-mask-image: none; text-overflow: ellipsis; }
}

/* ── Transport : prev — knobs — next ──────────────────────────────────────── */

.transport {
  width: var(--cover, 300px);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  margin-top: 4px;
  min-height: 68px;
}

.skip {
  flex-shrink: 0;
  margin-top: 11px;          /* aligne la flèche sur le centre des disques */
  padding: 0;
  border: none;
  background: none;
  color: #1C1C1E;
  cursor: pointer;
  line-height: 0;
  transition: opacity 0.2s ease;
}

.skip:hover  { opacity: 0.45; }
.skip:active { opacity: 0.25; }

#knob-row {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 22px;
  flex: 1;
}

/* ── Knobs ────────────────────────────────────────────────────────────────── */

.knob {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  width: 46px;      /* la largeur du disque : pas de label à loger */
}

/* Affiché seulement si SHOW_LABELS est activé dans js/knobs.js */
.knob-label {
  font-size: 11px;
  line-height: 1.25;
  text-align: center;
  color: #1C1C1E;
}

.knob-canvas {
  display: block;
  border-radius: 50%;
  cursor: grab;
  touch-action: none;
}

.knob-canvas:active { cursor: grabbing; }
.knob-canvas:focus-visible { outline: 2px solid #3700FF; outline-offset: 3px; }

.knob-pct {
  font-size: 11px;
  color: #8a8a8e;
  font-variant-numeric: tabular-nums;
}

/* ── Accueil ──────────────────────────────────────────────────────────────── */
/* Le portrait garde le format de la photo (620 × 808) au lieu du carré de la
   pochette : un cadre carré rognerait le visage ou laisserait du vide. */
#page-accueil .cover {
  width: auto;
  height: min(52vh, 420px);
  aspect-ratio: 508 / 662;
}

#page-accueil .cover canvas { display: block; }

/* Bouton « Scroll down » : même disque noir que le CTA de Composer. */
#btn-descendre { margin-top: 4px; }

/* Contacts de l'accueil : détachés du bloc central, posés en bas de l'écran.
   Même traitement que ceux de Composer et que les liens de Jouer. */
.accueil-contact {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 30px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 28px;
  font-size: 15px;
}

.accueil-contact a {
  color: #3700FF;
  text-decoration: none;
}

.accueil-contact a:hover { text-decoration: underline; }

/* ── Statut (chargement / invite au clic) ─────────────────────────────────── */

#status {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  font-size: 14px;
  color: #8a8a8e;
  transition: opacity 0.4s ease;
  pointer-events: none;
}

/* ── Format ───────────────────────────────────────────────────────────────── */

/* La colonne centrale d'Accueil, Écouter et Jouer tient dans une seule
   variable : la pochette, le carrousel, le bloc de texte et la rangée de knobs
   s'y accrochent tous. L'élargir les élargit ensemble, donc la colonne reste
   d'un seul tenant quoi qu'il arrive.

   Elle suit la HAUTEUR de la fenêtre, pas sa largeur : c'est le vertical qui
   contraint, puisque tout doit tenir sans défilement. Les 380 px retranchés
   sont ce que prennent le nom, le bloc morceau, le transport, les deux
   gouttières et les marges. La mesure est prise sur JOUER, la plus chargée des
   trois : 373 px hors pochette, contre 335 pour Écouter, parce qu'elle porte un
   paragraphe de plus. Caler la réserve sur Écouter ferait déborder Jouer sur un
   écran court, et les trois pages doivent garder la même pochette — sinon le
   carré changerait de taille en passant de l'une à l'autre.

   Les deux bornes disent le reste. 420 px en haut : au-delà, la pochette
   écrase le texte qui l'accompagne. 300 px en bas, et surtout pas moins :
   rétrécir la colonne rétrécit aussi les blocs de texte, qui se replient alors
   sur plus de lignes. Mesuré sur Jouer, passer de 300 à 260 px rend 40 px de
   pochette et en reprend 117 en texte. La colonne se retourne contre elle-même,
   donc on s'arrête avant. */
:root { --cover: clamp(300px, calc(100vh - 440px), 420px); }



/* ── Fenêtre courte ───────────────────────────────────────────────────────────
   Une fenêtre qu'on n'a pas maximisée fait vite 640 px de haut, et Jouer n'y
   tenait pas : 711 px de contenu pour 640 px de cadre, le texte du haut passait
   sous le bord. Le défaut est antérieur aux tailles de corps, il vient de la
   page elle-même, qui porte un paragraphe, une pochette, des liens et une barre
   de transport.

   On reprend sur les blancs, et sur eux seuls : contrairement à la colonne,
   ils ne changent pas la longueur du texte. Les gouttières passent de 34 à
   20 px et les marges hautes et basses de 40 à 24 px, soit 74 px récupérés.

   Le seuil est à 820 px parce que c'est là que Jouer commence à manquer d'air,
   pas à 700. Et la règle vaut pour les trois pages, pas pour la seule qui en a
   besoin : à hauteur égale elles doivent garder le même rythme, sinon l'écart
   se voit en passant de l'une à l'autre.

   Ça ne sauve pas tout. Sous ~710 px de fenêtre, Jouer déborde encore : elle
   porte un paragraphe, une vidéo, un bloc de liens et une barre de transport,
   et la colonne est déjà à sa borne basse. Le défaut est antérieur — avant ces
   réglages elle débordait sous ~711 px — mais il n'est pas réglé pour autant. */
@media (max-height: 820px) and (pointer: fine) {
  .stage {
    gap: 20px;
    padding: 24px 20px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Page Composer — Tension Engine

   Voir PAGE_ENGINE.md. Rien de nouveau côté identité : Inter, #1C1C1E sur
   blanc, accent #3700FF, disques noirs. Une seule liberté, décidée ensemble :
   le titre « Tension Engine », plus grand que tout le reste du site.
   Pas de filet noir entre les sections — l'espace fait la séparation.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Cette page défile sur elle-même, contrairement à Listen et Jouer qui
   tiennent dans un écran. */
.page-longue { overflow: hidden; }

.doc {
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: none;
  /* La nav est en position fixe à gauche : sur les autres pages le contenu est
     centré dans la fenêtre et passe à côté, mais ici il occupe toute la
     largeur et passerait dessous. On lui réserve sa colonne. */
  /* Marges identiques des deux côtés : le contenu reste centré dans la
     fenêtre, et la nav, en position fixe, tient dans la marge de gauche. */
  padding: 0 var(--marge-laterale) 140px;
}

.doc::-webkit-scrollbar { display: none; }

/* ── Titre ──────────────────────────────────────────────────────────────── */

.eng-tete {
  /* Largeur propre, plus étroite que la grille : titre et disque se serrent
     l'un contre l'autre au centre, au lieu d'être poussés aux extrémités. */
  max-width: 760px;
  margin: 0 auto 130px;
  min-height: 70vh;
  display: grid;
  grid-template-columns: auto auto;
  justify-content: center;
  gap: 48px;
  align-items: center;
}

/* Disque d'herbe, calé sur la hauteur du titre sur deux lignes. */
.eng-rond {
  width: clamp(200px, 26vw, 320px);
  aspect-ratio: 1;
}

.eng-rond canvas { display: block; }

.eng-titre {
  margin: 0;
  font-size: clamp(44px, 7vw, 86px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 0.98;
  color: #1C1C1E;
}

.eng-accroche {
  margin: 12px 0 0;
  max-width: 300px;
  font-size: clamp(17px, 1.8vw, 21px);
  font-weight: 400;
  color: #1C1C1E;
}

.eng-cta {
  margin-top: 26px;
  padding: 11px 26px;
  border: none;
  border-radius: 22px;
  background: #1C1C1E;
  color: #fff;
  font-family: inherit;
  font-size: 15.5px;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.eng-cta:hover { opacity: 0.75; }
.eng-cta:focus-visible { outline: 2px solid #3700FF; outline-offset: 3px; }

/* ── Citation ───────────────────────────────────────────────────────────── */

.eng-citation {
  max-width: 620px;
  margin: 0 auto 150px;
  text-align: center;
}

.eng-citation p {
  margin: 0;
  font-size: clamp(19px, 2.6vw, 27px);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.eng-citation cite {
  display: block;
  margin-top: 20px;
  font-size: 15px;
  font-style: normal;
  color: #8a8a8e;
}

/* La signature est le logo d'Asakan plutôt que son nom écrit. Il reste petit :
   c'est la phrase qu'on doit lire, la signature ne fait que la créditer. Le
   fichier fourni ne mesure que 75 x 55 px, donc 27 px de haut le laisse à peu
   près au double de sa taille d'affichage, et il tient sur un écran fin. */
.eng-citation cite a {
  display: inline-block;
  line-height: 0;   /* pas d'interligne parasite sous l'image */
  transition: opacity 0.2s ease;
}

.eng-citation cite a:hover { opacity: 0.55; }
.eng-citation cite a:focus-visible { outline: 2px solid #3700FF; outline-offset: 4px; }

.eng-citation cite img {
  display: inline-block;
  height: 27px;
  width: auto;
}

/* ── Bandes : texte d'un côté, visuel de l'autre ────────────────────────── */

.eng-bande {
  max-width: var(--contenu);
  margin: 0 auto 150px;
  display: grid;
  /* La colonne de texte reste plus étroite que son visuel : une ligne trop
     longue se lit mal.

     Les proportions vont de pair avec la gouttière. Elle est passée de 96 à
     56 px, et les quarante pixels récupérés sont allés entièrement au texte :
     0,72/1,28 devient 0,77/1,23, ce qui laisse le visuel à sa taille exacte
     (630 px) et fait passer le texte de 354 à 394 px. Réduire la gouttière
     sans toucher aux proportions aurait élargi les deux colonnes à parts
     égales, donc n'aurait presque rien donné au texte. */
  grid-template-columns: 0.77fr 1.23fr;
  gap: var(--gouttiere);
  align-items: center;
}

/* Une bande sur deux inverse l'ordre : c'est l'alternance qui donne le rythme
   à la page. `order` plutôt qu'un second jeu de colonnes, pour que l'ordre de
   lecture du HTML reste celui du sens.
   Les proportions s'inversent aussi, sinon le visuel se retrouverait dans la
   colonne étroite et paraîtrait deux fois plus petit d'une section à l'autre. */
.eng-inverse             { grid-template-columns: 1.23fr 0.77fr; }
.eng-inverse .eng-mots   { order: 2; }
.eng-inverse .eng-visuel { order: 1; }

.eng-mots h2 {
  margin: 0 0 12px;
  font-size: clamp(21px, 2.4vw, 27px);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.015em;
}

.eng-mots p {
  margin: 0;
  font-size: 16.5px;
  line-height: 1.6;
  color: #1C1C1E;
}

.eng-visuel {
  margin: 0;
  border-radius: 10px;
  overflow: hidden;
  background: #f2f2f4;   /* le temps que le média arrive */
}

.eng-visuel img,
.eng-visuel video {
  display: block;
  width: 100%;
  height: auto;
}

/* ── Compatibilité ──────────────────────────────────────────────────────── */

.eng-compat {
  max-width: 660px;
  margin: 0 auto 120px;
  text-align: center;
}

.eng-compat h2 {
  margin: 0 0 10px;
  font-size: clamp(19px, 2.2vw, 24px);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.eng-compat p {
  margin: 0 0 28px;
  font-size: 16px;
  color: #1C1C1E;
}

.eng-logos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 26px 52px;
}

/* Les fichiers sont rognés au ras de l'encre : la hauteur ci-dessous est donc
   la hauteur réelle du logo, pas celle d'un cadre. Reste que les cinq marques
   n'ont pas la même construction — un logo empilé sur deux étages et un
   simple mot posé sur une ligne ne pèsent pas pareil à hauteur égale. D'où un
   réglage par logo, fait à l'œil.

   Deux familles : les logos empilés (symbole au-dessus du nom) montent haut,
   les logos d'une seule ligne restent bas. */
.eng-logos img {
  display: block;
  height: 42px;   /* Wwise : symbole au-dessus du mot, sert de référence */
  width: auto;
}

/* Unreal empile lui aussi, mais son nom est écrit en petites capitales sous le
   symbole : il lui faut la hauteur la plus grande pour rester lisible. */
.eng-logos .logo-haut { height: 62px; }

/* Godot : symbole et nom empilés, mais plus ramassés que ceux d'Unreal. */
.eng-logos .logo-carre { height: 44px; }

/* Les deux logos d'une seule ligne. Leur nom occupe toute la hauteur, donc une
   valeur basse suffit à leur donner le même poids optique que les empilés. */
.eng-logos .logo-ligne { height: 26px; }

/* FMOD est le plus étiré des cinq : à 26 px il mesurait 95 px de large contre
   71 px pour Unity, et c'est cette largeur qu'on lit, pas la hauteur. On le
   cale donc sur la largeur d'Unity plutôt que sur sa hauteur. */
.eng-logos .logo-fmod { height: 20px; }

/* ── Inscription ────────────────────────────────────────────────────────── */

.eng-inscription {
  max-width: 520px;
  margin: 0 auto 90px;
  text-align: center;
}

.eng-inscription h2 {
  margin: 0 0 10px;
  font-size: clamp(19px, 2.2vw, 24px);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.eng-inscription > p {
  margin: 0 0 24px;
  font-size: 16px;
}

#form-newsletter {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

#form-newsletter input {
  flex: 1 1 180px;
  padding: 11px 15px;
  border: 1px solid #d8d8dc;
  border-radius: 22px;
  font-family: inherit;
  font-size: 15.5px;
  color: #1C1C1E;
  background: #fff;
}

#form-newsletter input:focus {
  outline: none;
  border-color: #1C1C1E;
}

#form-newsletter button {
  padding: 11px 26px;
  border: none;
  border-radius: 22px;
  background: #1C1C1E;
  color: #fff;
  font-family: inherit;
  font-size: 15.5px;
  cursor: pointer;
  transition: opacity 0.2s ease;
}

#form-newsletter button:hover { opacity: 0.75; }

#nl-reponse {
  margin: 14px 0 0;
  min-height: 20px;
  font-size: 15px;
  color: #8a8a8e;
}

#nl-reponse.ok { color: #3700FF; }

/* ── Contacts ───────────────────────────────────────────────────────────── */
/* Même traitement que les liens de la page Jouer : bleu, flèche, sans filet. */

.eng-contact {
  max-width: 660px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 28px;
  font-size: 15px;
}

.eng-contact a {
  color: #3700FF;
  text-decoration: none;
}

.eng-contact a:hover { text-decoration: underline; }

/* ── Format ─────────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .eng-tete {
    grid-template-columns: 1fr;
    gap: 34px;
    min-height: 0;
    padding-top: 40px;
  }
  .eng-rond { width: min(60vw, 260px); }
  .eng-bande {
    grid-template-columns: 1fr;
    gap: 26px;
    margin-bottom: 90px;
  }
  /* En une seule colonne, l'alternance n'a plus de sens : le texte passe
     toujours avant son visuel. */
  .eng-inverse .eng-mots   { order: 0; }
  .eng-inverse .eng-visuel { order: 0; }
  .eng-tete { min-height: 58vh; margin-bottom: 76px; }
  .eng-citation { margin-bottom: 84px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Page Look — les travaux de design graphique

   Page cachée : elle n'est pas annoncée dans le menu, on y arrive par lien ou
   en continuant de descendre après Composer. Pas de texte de présentation, pas
   de portrait : un titre par travail, et les images.
   ═══════════════════════════════════════════════════════════════════════════ */

/* L'entrée de menu ne paraît que lorsqu'on est sur la page. Sans ça, une
   rubrique qu'on ne veut pas annoncer figurerait en permanence dans le menu. */
.nav a.nav-cachee { display: none; }
body[data-page="look"] .nav a.nav-cachee { display: block; }

/* Colonne un peu plus étroite que le texte de Composer : une image se regarde
   d'un coup d'œil, elle n'a pas besoin de toute la largeur, et les marges la
   posent. */
.doc-look { padding-bottom: 120px; }

.look-oeuvre {
  max-width: 860px;
  margin: 0 auto 150px;
}

/* Titre de la page, le seul grand caractère de Look. Calé sur la même colonne
   que les images, pour que tout parte de la même verticale. */
.look-entete {
  max-width: 860px;
  margin: 0 auto;
  padding-top: 120px;
  font-size: clamp(30px, 4vw, 46px);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.05;
  color: #1C1C1E;
}

.look-oeuvre:first-of-type { margin-top: 90px; }

/* Les noms des travaux sont des étiquettes, pas des titres : c'est l'image
   qu'on regarde. En gros caractères ils entraient en concurrence avec elle et
   avec le titre de la page. */
/* Le nom du travail et son « i » sur une même ligne de base. */
.look-tete {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}

.look-titre {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  letter-spacing: 0;
  color: #1C1C1E;
}

/* Même disque noir que les knobs, la vidéo et le bouton de langue, en plus
   petit : c'est le vocabulaire du site, on n'invente pas une forme de plus. */
.look-info {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: #1C1C1E;
  /* Le libellé lisible par un lecteur d'écran vit dans l'attribut aria-label ;
     à l'écran on ne veut que la lettre, dessinée ci-dessous. */
  font-size: 0;
  cursor: pointer;
  position: relative;
  transition: background 0.25s ease;
}

.look-info::after {
  content: 'i';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 600 11px/1 'Inter', sans-serif;
  color: #fff;
}

.look-info:hover,
.look-info.ouvert { background: #3700FF; }
.look-info:focus-visible { outline: 2px solid #3700FF; outline-offset: 3px; }

/* La notice se lit comme un texte de fond : colonne plus étroite que l'image,
   parce qu'une ligne longue se lit mal, et posée sous le titre pour qu'on
   sache de quoi elle parle. */
.look-notice {
  max-width: 620px;
  margin: 0 0 22px;
  font-size: 15.5px;
  line-height: 1.65;
  color: #1C1C1E;
}

/* Les images d'un même travail respirent entre elles, sans filet ni cadre :
   c'est l'espace qui les sépare, comme partout ailleurs sur le site. */
.look-oeuvre img,
.look-oeuvre picture {
  display: block;
  width: 100%;
  height: auto;
}

.look-oeuvre img + img,
.look-oeuvre picture + img,
.look-oeuvre img + picture { margin-top: 70px; }

/* Les canvas démarrent transparents : collage.js les révèle une fois les deux
   textures chargées, pour ne pas laisser voir un rectangle vide. */
/* Les vidéos de Composer sont des illustrations, pas des médias à consulter :
   ni commandes, ni bouton de lecture superposé par Safari. */
.eng-visuel video::-webkit-media-controls,
.eng-visuel video::-webkit-media-controls-start-playback-button {
  display: none !important;
  -webkit-appearance: none;
}

.eng-visuel video { pointer-events: none; }

.look-collage {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  transition: opacity 0.6s ease;
}

.look-collage + .look-collage { margin-top: 70px; }

/* ── Repères mobiles ─────────────────────────────────────────────────────────
   Masqués par défaut : ils ne servent que quand la nav de gauche s'efface. */

#mobile-page-name { display: none; }

/* ── Téléphone ───────────────────────────────────────────────────────────────
   Même critère que le portfolio : pointeur grossier ET écran plus haut que
   large. On vise l'appareil, pas la largeur — une fenêtre de bureau rétrécie
   garde la version bureau, qu'elle sait afficher, et un iPad en paysage aussi.
   ────────────────────────────────────────────────────────────────────────── */
@media (pointer: coarse) and (max-aspect-ratio: 1/1) {

  /* Les pages sont transformées pendant le geste : on prévient le compositeur. */
  .page { will-change: transform; }

  /* ── Nav ────────────────────────────────────────────────────────────────
     La colonne de liens supposait une largeur qu'un téléphone n'a pas. Elle
     se peignait par-dessus le titre : `position: static` annulait son
     `z-index`, et les pages, en `position: fixed`, passaient au même endroit.
     On ne la répare pas, on la remplace. */
  .nav { display: none !important; }

  /* Le nom de la page et sa position dans la série tiennent lieu de menu.
     `difference` les garde lisibles sur le blanc comme sur la vidéo de
     Jouer, et `pointer-events: none` les empêche d'intercepter le geste. */
  #mobile-page-name {
    display: flex;
    position: fixed;
    top: 22px;
    left: 0;
    right: 0;
    padding: 0 24px;
    align-items: center;
    z-index: 200;
    font-size: 13px;
    line-height: 1.6;
    color: #fff;
    mix-blend-mode: difference;
    -webkit-font-smoothing: antialiased;
    pointer-events: none;
  }

  /* Composer est la seule page qui défile sous le libellé. Dès qu'on descend,
     le repère s'efface : il dit où l'on est, ce qui compte en arrivant, pas
     pendant la lecture — et à mi-page il tombait en plein sur un titre. Il
     revient tout seul en haut. */
  body[data-defile] #mobile-page-name {
    opacity: 0;
    transition: opacity 0.25s ease;
  }

  #mobile-page-name { transition: opacity 0.25s ease; }

  /* Aligné sur le nom de page plutôt que dans le coin : les deux repères
     forment une seule barre en haut de l'écran. Un peu plus petit, la place
     est comptée. */
  .lang-cycle {
    top: 14px;
    right: 18px;
    width: 34px;
    height: 34px;
    font-size: 11px;
  }

  /* Le compteur reste centré sur l'écran entier ; on réserve la place du
     bouton à droite pour que le nom de page ne passe jamais dessous. */
  #mobile-page-name { padding-right: 64px; }

  #mobile-page-counter {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
  }

  /* ── Pages d'un écran ───────────────────────────────────────────────────
     Le haut laisse passer le nom de page, le bas l'indice de geste. */
  .stage {
    gap: 22px;
    padding: 64px 24px 56px;
  }

  :root { --cover: min(76vw, 320px); }

  .accueil-contact { bottom: 34px; }

  /* ── Composer ───────────────────────────────────────────────────────────
     Sur bureau, `.doc` réserve 170 px de chaque côté pour que la nav fixe
     tienne dans la marge de gauche. Sans nav, cette réserve n'a plus d'objet,
     et sur 375 px elle laissait une colonne de texte de 122 px : deux mots par
     ligne. On repasse à une marge de page ordinaire. */
  .doc { padding: 0 24px 96px; }

  /* En une colonne, le titre et le disque se posent l'un sous l'autre : centrés,
     ils forment un axe, alors qu'alignés à gauche le disque pendait dans le
     vide à droite. C'est la seule page où l'on centre, et c'est pour ça. */
  .eng-tete {
    min-height: 0;
    padding-top: 72px;
    margin-bottom: 64px;
    gap: 26px;
    justify-items: center;
    text-align: center;
  }

  .eng-rond  { width: min(52vw, 210px); }

  /* « Tension Engine » d'un seul tenant. Le retour à la ligne du HTML sert le
     bureau, où le titre se cale à côté du disque sur deux lignes ; ici il
     coupait un nom de produit en deux pour rien. La taille est en `vw` pour
     que la ligne remplisse la largeur quel que soit le téléphone — 8,9 vw est
     la valeur mesurée qui la fait tenir entre les marges sur un 375. */
  .eng-titre    { font-size: 12vw; line-height: 1.05; }
  .eng-titre br { display: none; }

  /* `max-width: 300px` bridait la ligne sur bureau pour qu'elle passe sous le
     titre ; centrée sur toute la largeur du téléphone, elle tient d'elle-même. */
  .eng-accroche { max-width: none; font-size: 16px; }

  .eng-citation { margin-bottom: 76px; }
  .eng-citation p { font-size: 20px; }

  .eng-bande { gap: 22px; margin-bottom: 72px; }

  .eng-logos { gap: 22px 34px; }

  .eng-inscription { margin-bottom: 64px; }

  /* Look sur téléphone : l'image prend toute la largeur utile, les écarts se
     resserrent pour que le défilement ne s'éternise pas entre deux travaux. */
  .look-oeuvre { margin-bottom: 76px; }
  .look-oeuvre:first-of-type { margin-top: 54px; }
  .look-entete { padding-top: 72px; font-size: clamp(26px, 8vw, 38px); }
  .look-oeuvre img + img,
  .look-oeuvre picture + img,
  .look-oeuvre img + picture,
  .look-collage + .look-collage { margin-top: 34px; }
  .look-tete { margin-bottom: 14px; }
  .look-notice { font-size: 13px; margin-bottom: 18px; }

  /* Le formulaire s'empile. À 375 px, `flex: 1 1 180px` laissait le nom seul
     sur sa ligne puis serrait l'e-mail contre le bouton : trois éléments de
     largeurs différentes, sans alignement. Empilés, ils font une colonne, et
     chaque champ est assez large pour qu'on voie ce qu'on tape. */
  #form-newsletter { flex-direction: column; }
  #form-newsletter input,
  #form-newsletter button { width: 100%; flex: none; }

  /* ── Gestes ─────────────────────────────────────────────────────────────
     Le doigt navigue entre les pages et tourne les knobs : le navigateur ne
     doit pas interpréter les mêmes mouvements de son côté. Seule Composer
     garde le défilement natif, qui est le sien. */
  body { overscroll-behavior: none; }
  .stage { touch-action: none; }
  .doc   { touch-action: pan-y; }

  /* Pas d'état de survol sur un écran tactile : il resterait collé après le
     relâchement du doigt. */
  .nav a:hover,
  .eng-cta:hover { opacity: 1; }
}
