/* ══════════════════════════════════════════════════════════════════════════
   KUBARIUM 3D — feuille de style principale
   Une interface de logiciel 3D de 1994, dessinée avec les moyens de 2026.
   Règle du jeu : biseaux fins, pas de gros plastique. La nostalgie se joue
   sur la typographie et la mise en grille, pas sur les ombres portées.
   ══════════════════════════════════════════════════════════════════════════ */

/* ───────────────────────────── 1. Jetons ───────────────────────────── */
:root {
  /* Fonds */
  --void:      #05080f;
  --chrome:    #0a1120;
  --chrome-hi: #10192c;
  --sunk:      #03060e;

  /* Traits */
  --line:      #1b263f;
  --line-soft: #141d31;
  --bevel-up:  rgba(255, 255, 255, .055);
  --bevel-dn:  rgba(0, 0, 0, .55);

  /* Texte */
  --ink:       #dde5f3;
  --ink-dim:#7f8da6;
  --ink-faint: #4d5c7a;

  /* Accents — un par face du cube */
  --a-films:   #ff3d78;
  --a-cg:      #4d95ff;
  --a-studio:  #ffd21f;
  --a-labo:    #57d98d;
  --a-about:   #f5233f;
  --a-contact: #ff7a2b;
  --accent:    var(--a-cg);

  /* L'ÉCHELLE DES PALIERS — du froid au chaud.
     Une victoire à un coup et une victoire sur un mélange complet n'ont pas la
     même valeur, et rien ne le disait : tous les noms sortaient de la même
     couleur, dans le même corps. La difficulté se lit maintenant sans légende —
     gris-bleu discret, vert, or, rose vif — et la taille suit. Le rose n'est pas
     choisi au hasard : c'est l'accent le plus chaud de la palette du cube, celui
     de la face FILMS. */
  --rang-p1:   #93a3bd;
  --rang-p3:   #57d98d;
  --rang-p6:   #ffd21f;
  --rang-p14:  #ff3d78;

  /* Typographie */
  --f-display: 'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;
  --f-mono:    'JetBrains Mono', ui-monospace, 'SFMono-Regular', Consolas, monospace;
  --f-pixel:   'Silkscreen', 'JetBrains Mono', monospace;

  /* LES ZONES SÛRES — l'îlot dynamique et l'indicateur d'accueil.
     `index.html` demande `viewport-fit=cover`, ce qui étend la page jusqu'aux
     bords physiques de l'écran. C'est ce qu'on veut : le châssis va toucher le
     verre plutôt que de flotter dans une bande blanche. Le prix est qu'il faut
     dire soi-même où sont les obstacles, sinon le système ne le fait pas.

     En PORTRAIT, sous Safari, ces quatre valeurs sont nulles : le navigateur
     garde sa propre barre au-dessus et en dessous, la page ne voit jamais
     l'îlot. C'est en PAYSAGE que ça se joue — la zone de contenu s'étale d'un
     bord à l'autre, et sur un 14 Pro Max l'îlot mange cinquante-neuf pixels du
     côté où il se trouve. Le rail d'outils, posé à huit pixels du bord,
     passait donc à moitié dessous.

     Elles sont recopiées dans des variables au lieu d'être appelées à
     l'endroit où elles servent, pour une raison purement pratique :
     `env(safe-area-inset-*)` ne se simule pas dans un navigateur de bureau,
     alors qu'une variable, si. Les mesures peuvent donc reproduire un vrai
     téléphone en les forçant à 59 px. */
  --sur-h: env(safe-area-inset-top, 0px);
  --sur-d: env(safe-area-inset-right, 0px);
  --sur-b: env(safe-area-inset-bottom, 0px);
  --sur-g: env(safe-area-inset-left, 0px);

  /* Rythme */
  --gap:  10px;
  --rail: 46px;
  /* Le bandeau du haut : barre de titre puis barre de menus, chacune avec son
     filet d'un pixel. Trois panneaux s'en servent pour se placer DESSOUS.

     Le compte était faux et personne ne l'avait vu : les panneaux se posaient à
     `2 × gap + 26px`, soit dix pixels TROP HAUT, et recouvraient le bout de la
     barre de menus — c'est-à-dire la signature « JULIA CHAIX · DIRECTOR ». On
     mesurait le mauvais repère, et la faute se recopiait dans les trois. */
  --titlebar: 30px;
  --menubar: 30px;
  --bandeau: calc(var(--titlebar) + var(--menubar) + 2px);
  --ease: cubic-bezier(.22, 1, .36, 1);

  color-scheme: dark;
}

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

html, body {
  height: 100%;
  margin: 0;
  background: var(--void);
  color: var(--ink);
  font-family: var(--f-mono);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

body {
  overflow: hidden;
  /* Voile de verre : un grain quasi invisible qui casse le côté « vectoriel parfait ». */
  background-image:
    radial-gradient(120% 90% at 50% 0%, #0b1428 0%, var(--void) 62%);
}

body::after {
  content: '';
  position: fixed; inset: 0;
  pointer-events: none;
  z-index: 900;
  opacity: .35;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/></filter><rect width='140' height='140' filter='url(%23n)' opacity='.5'/></svg>");
}

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: var(--accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
a:hover { border-bottom-color: var(--accent); }

:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 2px;
}

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

/* Petite étiquette système : la brique typographique de tout le châssis. */
.tag, .st, .menubar__item, .toolrail button, .hud__hint, .era__label {
  font-family: var(--f-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
}

/* ───────────────────────────── 3. Démarrage ───────────────────────────── */
.boot {
  position: fixed; inset: 0; z-index: 1000;
  display: grid; place-items: center;
  background: var(--void);
  cursor: pointer;
}
.boot__inner { width: min(460px, 86vw); }

.boot__logo { display: flex; align-items: center; gap: 14px; margin-bottom: 26px; }

.boot__glyph {
  width: 34px; height: 34px; flex: none;
  border: 1px solid var(--accent);
  position: relative;
  animation: glyphspin 3.6s var(--ease) infinite;
  transform-style: preserve-3d;
}
.boot__glyph::before, .boot__glyph::after {
  content: ''; position: absolute; inset: -1px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}
.boot__glyph::before { transform: rotateY(60deg); }
.boot__glyph::after  { transform: rotateX(60deg); }
@keyframes glyphspin { to { transform: rotateY(360deg); } }

.boot__title {
  margin: 0; font-family: var(--f-display); font-weight: 700;
  font-size: 22px; letter-spacing: .2em; line-height: 1;
}
.boot__sub {
  margin: 5px 0 0; font-size: 9.5px; letter-spacing: .18em;
  color: var(--ink-faint); text-transform: uppercase;
}

.boot__log { list-style: none; margin: 0 0 18px; padding: 0; min-height: 118px; }
.boot__log li {
  display: flex; gap: 10px; align-items: baseline;
  font-size: 11px; color: var(--ink-dim); letter-spacing: .04em;
  opacity: 0; transform: translateY(4px);
  animation: logIn .28s var(--ease) forwards;
}
.boot__log li b { margin-left: auto; color: var(--accent); font-weight: 500; }
.boot__log li i {
  flex: 1; border-bottom: 1px dotted var(--line);
  transform: translateY(-3px); font-style: normal;
}
@keyframes logIn { to { opacity: 1; transform: none; } }

.boot__bar { height: 3px; background: var(--sunk); border: 1px solid var(--line); }
.boot__bar i { display: block; height: 100%; width: 0; background: var(--accent); transition: width .3s linear; }

.boot__skip {
  margin: 14px 0 0; font-size: 9px; letter-spacing: .22em;
  color: var(--ink-faint); text-transform: uppercase; text-align: right;
  animation: blink 1.6s steps(1) infinite;
}
@keyframes blink { 50% { opacity: .25; } }

body:not(.is-booting) .boot { opacity: 0; pointer-events: none; transition: opacity .45s ease; }

/* ───────────────────────────── 4. Châssis ───────────────────────────── */
.app {
  position: fixed; inset: 0;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  background: var(--chrome);
  /* Un seul endroit tient les zones sûres pour tout le châssis : la coque est
     la grille, et le rail, le repère de face et les pastilles sont posés DANS
     la scène, qui est une de ses rangées. Les rentrer ici les rentre tous. Le
     gris du châssis reste peint sous l'îlot — c'est du remplissage, pas une
     marge — donc rien ne laisse voir une bande vide. */
  padding: var(--sur-h) var(--sur-d) var(--sur-b) var(--sur-g);
}
.app[hidden] { display: none; }

/* 4.1 Barre de titre */
.titlebar {
  display: flex; align-items: center; gap: 10px;
  padding: 0 8px 0 12px; height: var(--titlebar);
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--bevel-up);
}
.titlebar__name {
  margin: 0; font-family: var(--f-mono); font-size: 11px;
  font-weight: 500; letter-spacing: .16em; white-space: nowrap;
}
.titlebar__name b { font-weight: 700; }
.titlebar__sep { color: var(--ink-faint); margin: 0 2px; }
.titlebar__name span { color: var(--accent); }
.titlebar__grip {
  flex: 1; height: 9px; min-width: 12px;
  background: repeating-linear-gradient(
    to bottom, var(--line) 0 1px, transparent 1px 3px);
  opacity: .8;
}
.titlebar__btns { display: flex; gap: 4px; }
.wbtn {
  width: 20px; height: 17px; display: grid; place-items: center;
  border: 1px solid var(--line);
  background: var(--chrome-hi);
  box-shadow: inset 0 1px 0 var(--bevel-up), inset 0 -1px 0 var(--bevel-dn);
  font-size: 9px; color: var(--ink-dim); line-height: 1;
}
.wbtn:hover { color: var(--ink); border-color: var(--ink-faint); }
.wbtn--close:hover { color: var(--a-films); border-color: var(--a-films); }

/* 4.2 Barre de menus */
.menubar {
  display: flex; align-items: stretch; gap: 0;
  height: var(--menubar); padding: 0 7px;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
  position: relative; z-index: 60;
}
.menubar__item {
  position: relative;
  /* Un cran au-dessus de la brique typographique commune : la barre de menus se
     lit de loin et se vise à la souris, la barre d'état non. On ne touche donc
     PAS à la règle partagée, qui habille aussi le rail, le statut et l'indice. */
  font-size: 12.5px;
  padding: 0 13px;
  color: var(--ink-dim);
  display: flex; align-items: center;
  border: 1px solid transparent;
}
.menubar__item:hover { color: var(--ink); }
.menubar__item[aria-expanded="true"] {
  color: var(--ink);
  background: var(--chrome-hi);
  border-color: var(--line);
  border-bottom-color: transparent;
}
.menubar__spacer { flex: 1; }
.menubar__brand {
  display: flex; align-items: center; padding: 0 12px;
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .18em;
  color: var(--ink-faint); text-transform: uppercase;
}
.menubar__brand b { color: var(--ink-dim); font-weight: 500; }

.menu {
  position: absolute; top: 100%; left: -1px; min-width: 312px;
  background: var(--chrome-hi);
  border: 1px solid var(--line);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .65), inset 0 1px 0 var(--bevel-up);
  padding: 4px; margin: 0; list-style: none;
  z-index: 61;
}
.menu li { display: block; }
/* LES ENTRÉES SE LISENT DE LOIN. Un menu de logiciel se parcourt à la souris
   sans être lu caractère par caractère : la ligne doit être assez haute pour se
   viser sans effort et le texte assez gros pour se reconnaître d'un coup d'œil.
   On monte donc les deux ensemble — grossir le texte dans une ligne serrée
   donne un menu tassé, qui se lit MOINS bien qu'avant. */
.menu button {
  display: flex; align-items: center; gap: 12px; width: 100%;
  padding: 11px 14px; font-size: 15px; letter-spacing: .015em;
  color: var(--ink-dim); text-align: left;
}
.menu button:hover { background: var(--accent); color: #05080c; }
.menu button:hover .menu__key { color: #05080c; opacity: .7; }
.menu__dot {
  width: 10px; height: 10px; flex: none; border: 1px solid currentColor;
  opacity: .45;
}
.menu__dot--on { background: currentColor; opacity: 1; }
.menu__key { margin-left: auto; font-size: 12.5px; color: var(--ink-faint); }
.menu__rule { height: 1px; margin: 5px 2px; background: var(--line); }

/* 4.3 Scène */
.stage {
  position: relative;
  background: var(--sunk);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  touch-action: none;
}
#view { display: block; width: 100%; height: 100%; }
.stage.is-grabbing #view { cursor: grabbing; }
.stage.is-hover  #view { cursor: pointer; }
.stage.tool-orbit #view { cursor: grab; }
.stage.tool-twist #view { cursor: crosshair; }

/* 4.4 Rail d'outils

   LE COIN DE LA SCÈNE. Le rail et le repère de face étaient posés chacun en
   absolu, et le repère se plaçait par un calcul : « la marge, plus DEUX rangs
   de rail, plus quatorze pixels ». Ce calcul devait connaître d'avance le
   nombre de rangs, et il se trompait dès que le rail se repliait autrement —
   c'est-à-dire à chaque changement de format. Ils sont maintenant empilés dans
   le flux : le repère tombe sous le rail sans que personne n'ait à compter. */
.stage__coin {
  position: absolute; top: var(--gap); left: var(--gap);
  /* Hauteur CONNUE, et non pas « ce que le contenu voudra » : c'est elle qui
     donne au rail la limite sur laquelle se replier. La colonne couvre donc
     toute la scène, et ne doit par conséquent rien intercepter. */
  height: calc(100% - 2 * var(--gap));
  max-width: calc(100% - 2 * var(--gap));
  /* LE COIN SUIT LE RAIL. Rail debout → l'étiquette se met À CÔTÉ, à huit
     pixels, exactement là où le calcul la mettait avant. Rail couché → elle
     passe DESSOUS. C'est la seule chose que ce conteneur décide, et il la
     décide dans le même sens que le rail, ce qui fait qu'aucune des deux mises
     en page n'a à connaître les dimensions de l'autre. */
  display: flex; flex-direction: row; align-items: flex-start; gap: 8px;
  z-index: 20;
  pointer-events: none;
}
.toolrail {
  display: flex; flex-direction: column; gap: 1px;
  /* IL SE REPLIE PLUTÔT QUE DE SORTIR DE L'ÉCRAN. Onze outils de quarante-six
     pixels font cinq cent trente-six pixels de haut ; un téléphone couché n'a
     que trois cent quarante pixels de scène. Mesuré sur un 14 Pro Max en
     paysage : les quatre derniers — dont les notes et la bande-son — pendaient
     dans le vide sous le bord, exactement comme les deux derniers débordaient
     à droite en portrait avant la v3.39. Même faute, l'autre axe. */
  flex-wrap: wrap; max-height: 100%; max-width: 100%; flex: none;
  background: var(--line);
  border: 1px solid var(--line);
  pointer-events: auto;
}
.toolrail button {
  width: var(--rail); height: var(--rail);
  display: grid; place-items: center; gap: 3px;
  background: color-mix(in srgb, var(--chrome) 82%, transparent);
  backdrop-filter: blur(6px);
  color: var(--ink-faint);
  position: relative;
}
.toolrail button svg { width: 17px; height: 17px; }
.toolrail button i {
  font-style: normal; font-size: 7.5px; letter-spacing: .1em;
  font-family: var(--f-pixel);
}
.toolrail button:hover { color: var(--ink); background: var(--chrome-hi); }
.toolrail button[aria-pressed="true"] { color: var(--accent); background: var(--chrome-hi); }
.toolrail button[aria-pressed="true"]::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--accent);
}
.toolrail__sep { height: 8px; background: var(--chrome); }

/* 4.5 Tête haute */
.hud {
  pointer-events: none;
  max-width: min(46ch, 60vw);
  min-height: 0;
}
.hud__face em {
  display: block; font-family: var(--f-pixel); font-size: 9px;
  letter-spacing: .12em; color: var(--ink-faint); font-style: normal;
  margin-bottom: 6px;
}
.hud__face strong {
  display: block;
  font-family: var(--f-display); font-weight: 700;
  font-size: clamp(28px, 5.2vw, 54px); line-height: .92;
  letter-spacing: -.02em;
  color: var(--ink);
  text-shadow: 0 2px 30px rgba(0, 0, 0, .8);
  transition: color .5s var(--ease);
}
.hud__face span {
  display: block; margin-top: 8px; max-width: 34ch;
  font-size: 12px; color: var(--ink-dim); letter-spacing: .01em;
}
.hud__hint {
  position: absolute; left: 0; bottom: 0;
  margin: 0; color: var(--ink-faint); font-size: 9.5px;
}
.hud { display: grid; }
.hud__hint { position: static; margin-top: 22px; }

/* 4.6 Interrupteur d'époque */
.era {
  position: absolute; right: var(--gap); bottom: var(--gap);
  display: flex; align-items: center; gap: 10px;
  z-index: 20;
}
.era__label { color: var(--ink-faint); }
.era__switch {
  position: relative; width: 132px; height: 30px;
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--chrome) 85%, transparent);
  backdrop-filter: blur(6px);
  box-shadow: inset 0 1px 0 var(--bevel-up);
  display: flex; align-items: center;
}
.era__knob {
  position: absolute; top: 2px; bottom: 2px; left: 2px; width: 62px;
  background: var(--accent);
  transition: transform .5s var(--ease), background .5s var(--ease);
}
.era__switch[aria-checked="true"] .era__knob { transform: translateX(64px); }
.era__y {
  position: relative; z-index: 1; flex: 1; text-align: center;
  font-family: var(--f-pixel); font-size: 11px; letter-spacing: .04em;
  transition: color .35s var(--ease);
}
.era__y--l { color: #05080c; }
.era__y--r { color: var(--ink-faint); }
.era__switch[aria-checked="true"] .era__y--l { color: var(--ink-faint); }
.era__switch[aria-checked="true"] .era__y--r { color: #05080c; }

/* 4.7 Boussole des faces */
.facedots {
  position: absolute; right: var(--gap); top: var(--gap);
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 1px;
  background: var(--line); border: 1px solid var(--line);
  z-index: 20;
}
.facedots button {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 7px 10px 7px 8px;
  background: color-mix(in srgb, var(--chrome) 82%, transparent);
  backdrop-filter: blur(6px);
  font-family: var(--f-mono); font-size: 9.5px; letter-spacing: .13em;
  color: var(--ink-faint); text-transform: uppercase;
}
.facedots button:hover { color: var(--ink); background: var(--chrome-hi); }
.facedots button i {
  width: 8px; height: 8px; flex: none; background: currentColor;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .5);
}
.facedots li.is-active button { color: var(--ink); background: var(--chrome-hi); }

/* 4.8 Barre d'état */
.statusbar {
  display: flex; align-items: center; gap: 0;
  height: 24px; padding: 0 4px;
  background: linear-gradient(var(--chrome), var(--chrome-hi));
  border-top: 1px solid var(--line-soft);
  color: var(--ink-faint);
  overflow: hidden;
}
.st {
  display: flex; align-items: center; gap: 6px;
  padding: 0 10px; height: 100%;
  border-right: 1px solid var(--line-soft);
  white-space: nowrap;
}
.st b { color: var(--ink-dim); font-weight: 500; }
.st--spacer { flex: 1; border: 0; }
.st--coord b, .st--fps b { font-family: var(--f-pixel); font-size: 10px; color: var(--accent); }
.st--sel { color: var(--accent); border-right: 0; overflow: hidden; text-overflow: ellipsis; }
.st--btn:hover b { color: var(--accent); }
.st--mode { border-right: 0; }
.st--mode b { color: var(--accent); }

/* ───────────────────────────── 5. Fenêtres ───────────────────────────── */
/* Les fenêtres ne vivent pas dans la coque mais dans leur propre calque fixe —
   sinon la grille les compterait comme des rangées. Elles n'héritent donc pas
   du rentré de la coque, et il faut le leur redonner ici. */
.panels {
  position: fixed;
  inset: var(--sur-h) var(--sur-d) var(--sur-b) var(--sur-g);
  z-index: 200; pointer-events: none;
}

.win {
  position: absolute;
  width: min(680px, calc(100vw - 40px));
  max-height: calc(100vh - 90px);
  display: flex; flex-direction: column;
  background: var(--chrome);
  border: 1px solid var(--line);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .75), inset 0 1px 0 var(--bevel-up);
  pointer-events: auto;
  animation: winIn .34s var(--ease);
}
@keyframes winIn {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
}
.win.is-closing { animation: winOut .2s ease forwards; }
@keyframes winOut { to { opacity: 0; transform: translateY(6px) scale(.99); } }

.win__bar {
  display: flex; align-items: center; gap: 10px;
  padding: 0 6px 0 11px; height: 29px; flex: none;
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
  cursor: grab; user-select: none;
}
.win__bar:active { cursor: grabbing; }
.win__bar h2 {
  margin: 0; font-family: var(--f-mono); font-size: 10.5px;
  font-weight: 500; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.win__bar .win__grip {
  flex: 1; height: 9px;
  background: repeating-linear-gradient(to bottom, var(--line) 0 1px, transparent 1px 3px);
}
.win__body {
  overflow-y: auto; overscroll-behavior: contain;
  padding: 0 0 26px;
}

/* Bandeau visuel de la fiche */
.win__hero {
  position: relative; aspect-ratio: 16 / 7;
  background: var(--sunk); overflow: hidden;
  border-bottom: 1px solid var(--line);
  /* Ce qui se pose dans le bandeau (logo, portrait) se mesure en largeur de
     bandeau (cqw) : il garde ses proportions de l'écran large au téléphone. */
  container-type: inline-size;
}
.win__hero > canvas, .win__hero > img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Plus de voile sur le bandeau : c'est une image choisie et cadrée pour lui,
   on la montre entière. Le surtitre porte son propre fond, une étiquette
   d'interface, lisible sur un rendu clair comme sur une nuit. */
.win__hero--zoom { cursor: zoom-in; }
.win__hero--zoom:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.win__kicker {
  position: absolute; left: 14px; bottom: 12px; z-index: 1;
  padding: 5px 8px 4px;
  font-family: var(--f-pixel); font-size: 9px; letter-spacing: .16em; line-height: 1;
  color: var(--accent);
  background: color-mix(in srgb, var(--void) 74%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  backdrop-filter: blur(6px);
}

.win__head { padding: 22px 22px 0; }
.win__title {
  margin: 0; font-family: var(--f-display); font-weight: 700;
  font-size: clamp(24px, 4.4vw, 38px); line-height: 1; letter-spacing: -.02em;
}
.win__year {
  margin: 8px 0 0; font-family: var(--f-mono); font-size: 10.5px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-faint);
}

/* Le logo de STUDIO dans son bandeau, à la place du grand titre : assemblage
   horizontal aux cotes de la charte (capitales à 0,42 fois la hauteur du
   symbole, écart de 0,22). --k, la hauteur du K posé, vaut 30 % de la hauteur
   du bandeau (13,125 cqw). Le cadre de l'animation est plus grand que le K
   (1,149 fois) pour laisser tourner la face : sa marge de gauche vaut 0,155
   de --k, celle du haut 0,074, celle de droite 0,151, et le nom porte 0,049
   dans son dessin. D'où le bord gauche du K à 9 % de la largeur, aligné sur
   l'accroche, son haut à 23,5 % de la hauteur, et 0,021 de --k à ajouter
   pour l'écart. */
.win__bandeau-logo {
  --k: 13.125cqw;
  position: absolute; z-index: 1;
  left: calc(9cqw - var(--k) * .1551);
  top: 9.31cqw;
  height: calc(var(--k) * 1.1486);
  display: flex; align-items: center;
}
.win__bandeau-logo .win__k {
  flex: none; height: 100%; width: auto;
  aspect-ratio: 220 / 224;
}
.win__bandeau-logo .win__nom {
  flex: none; height: calc(var(--k) * .5226); width: auto;
  aspect-ratio: 491 / 85.6;
  margin-left: calc(var(--k) * .0206);
}

.win__meta {
  margin: 22px 22px 0; padding: 0;
  border-top: 1px solid var(--line-soft);
}
.win__meta div {
  display: grid; grid-template-columns: 13ch 1fr; gap: 14px;
  padding: 7px 0; border-bottom: 1px solid var(--line-soft);
  font-size: 12px;
}
.win__meta dt {
  margin: 0; font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-faint);
}
.win__meta dd { margin: 0; color: var(--ink); }

.win__text { padding: 20px 22px 0; max-width: 62ch; }
.win__text p { margin: 0 0 14px; color: var(--ink-dim); font-size: 13.5px; line-height: 1.7; }
.win__text p b { color: var(--ink); font-weight: 600; }

/* Le portrait de la page À PROPOS, posé dans le bandeau à droite comme une
   photo glissée dans le dossier, à l'intérieur des coins du cadre. */
.win__portrait {
  position: absolute; z-index: 1;
  right: 8cqw; top: 50%; transform: translateY(-50%);
  height: 78%; aspect-ratio: 3 / 4;
  margin: 0;
  border-radius: 4px; overflow: hidden;
  background: var(--sunk);
  border: 1px solid rgba(242, 246, 250, .28);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .45);
}
.win__portrait img { display: block; width: 100%; height: 100%; object-fit: cover; }
.win__text p:first-child { color: var(--ink); }
.win__text ul { margin: 0 0 14px; padding-left: 18px; color: var(--ink-dim); font-size: 13.5px; }

.win__links { display: flex; flex-wrap: wrap; gap: 8px; padding: 16px 22px 0; }
.win__links a, .btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px; border: 1px solid var(--line);
  background: var(--chrome-hi);
  box-shadow: inset 0 1px 0 var(--bevel-up), inset 0 -1px 0 var(--bevel-dn);
  font-family: var(--f-mono); font-size: 10.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-dim);
}
.win__links a:hover, .btn:hover { color: var(--accent); border-color: var(--accent); }

.win__note {
  margin: 20px 22px 0; padding: 14px 16px;
  border: 1px dashed var(--line);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
  font-size: 12px; color: var(--ink-dim);
}
.win__note b {
  display: block; margin-bottom: 5px; color: var(--accent);
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
}

.win__foot {
  display: flex; align-items: center; gap: 8px;
  padding: 6px; flex: none;
  border-top: 1px solid var(--line);
  background: var(--chrome-hi);
}
.win__foot .btn { padding: 6px 12px; }
.win__foot .win__spacer { flex: 1; }

/* ───────────────────────────── 6. Repli texte ───────────────────────────── */
.flat {
  position: fixed; inset: 0; z-index: 300; overflow-y: auto;
  background: var(--void); padding: 40px 22px 80px;
}
.flat[hidden] { display: none; }
.flat__inner { max-width: 68ch; margin: 0 auto; }
.flat h1 {
  font-family: var(--f-display); font-size: 32px; letter-spacing: -.02em; margin: 0 0 6px;
}
.flat h2 {
  font-family: var(--f-display); font-size: 20px; margin: 40px 0 4px;
  padding-top: 14px; border-top: 1px solid var(--line);
}
.flat h3 { font-size: 14px; margin: 22px 0 4px; letter-spacing: .04em; }
.flat p { color: var(--ink-dim); font-size: 14px; }
.flat .tag { color: var(--ink-faint); }

/* ───────────────────────────── 7. Adaptation écran ───────────────────────────── */
/* ══════════════════════════════════════════════════════════════════════════
   L'ÉTROIT — et une leçon apprise en le mesurant
   ──────────────────────────────────────────────────────────────────────────
   Agrandir la barre de menus de 11,5 à 12,5 px a suffi à CASSER la mise en page
   d'un téléphone. Les cinq intitulés passaient de 364 à 394 px de large, et
   comme rien n'autorisait la barre à rétrécir, elle poussait toute la coque à
   408 px dans une fenêtre de 390. Résultat : la barre de titre, la scène, le
   rail et la barre d'état dépassaient tous par la droite, et le bouton de
   fermeture comme les deux dernières facettes tombaient hors de l'écran.

   Deux verrous, donc. `min-width: 0` sur les enfants de la coque, sans quoi un
   enfant refuse de descendre sous sa largeur de contenu et étire son parent —
   c'est le défaut par défaut de flex, et il ne pardonne pas. Et la barre de
   menus défile latéralement plutôt que de pousser : mieux vaut un menu qu'on
   fait glisser du doigt qu'un site qui déborde. */
@media (max-width: 860px) {
  :root { --gap: 8px; --rail: 42px; }
  .titlebar, .menubar, .stage, .statusbar { min-width: 0; }
  .menubar {
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .menubar::-webkit-scrollbar { display: none; }
  .menubar__item { flex: none; }
  /* LE RAIL PASSE À LA LIGNE. Dix outils à 42 px font 449 px de large : sur un
     téléphone de 390, les deux derniers — les notes et la bande-son — étaient
     purement hors de l'écran, donc inatteignables. Il s'enroule maintenant sur
     deux rangs, ce qui les rend visibles au lieu de les cacher. Le repliement
     lui-même est passé dans la règle de base, avec la borne de hauteur : c'est
     la même faute sur les deux axes, elle méritait un seul remède.
     Le repère de face n'a plus besoin d'être déplacé ici — il est empilé sous
     le rail dans le coin, donc il suit, un rang ou deux. */
  .toolrail { flex-direction: row; }
  .stage__coin { flex-direction: column; gap: 14px; }
  /* Les séparateurs sautent : à 390 px de large, huit pixels de vide poussent
     un outil au rang suivant, et un rang de plus coûte plus cher qu'un filet. */
  .toolrail__sep { display: none; }
  .facedots { top: auto; bottom: calc(var(--gap) + 42px); right: var(--gap); }
  .facedots button { padding: 6px 8px; font-size: 8.5px; }
  .menubar__brand { display: none; }
  .st--nodes, .st--obj, .st--coord { display: none; }
  /* Plein écran, mais mesuré sur le calque et non sur la fenêtre du navigateur :
     `100vw` ignore les zones sûres, et une fenêtre large de tout l'écran dans un
     calque rentré de cinquante-neuf pixels dépasse par la droite d'exactement
     ça. `100 %` d'un parent déjà rentré tombe juste par construction. */
  .win { width: 100%; max-width: none; max-height: 100%; inset: 0 !important; border: 0; }
  .win__body { padding-bottom: 40px; }
}

/* KUBARIUM 3D est plus long que l'ancien nom : sur les plus petits téléphones,
   la barre de titre resserre ses lettres, puis garde le seul nom du logiciel,
   pour que les trois boutons restent dans l'écran. */
@media (max-width: 380px) {
  .titlebar__name { letter-spacing: .08em; }
}
@media (max-width: 340px) {
  .titlebar__sep, #doc-name { display: none; }
}

@media (max-width: 560px) {
  .facedots { grid-template-columns: repeat(3, 1fr); }
  .facedots button i { width: 7px; height: 7px; }
  .hud__face span { display: none; }
  .era__switch { width: 112px; height: 27px; }
  .era__knob { width: 52px; }
  .era__switch[aria-checked="true"] .era__knob { transform: translateX(54px); }
}

/* ── ÉCRAN COURT ET LARGE ───────────────────────────────────────────────────
   Un téléphone couché, ou une fenêtre de bureau écrasée en hauteur. Le rail
   debout réclame cinq cent cinquante-six pixels de scène ; en dessous il se
   replie en deux colonnes — ce qui ne perd plus aucun bouton, mais mange de la
   largeur et pousse l'étiquette de face sous le bord. Couché, il tient sur un
   seul rang et rend la hauteur à la scène.

   Ce bloc ne touche QUE le rail. On pourrait croire qu'un écran court mérite le
   même traitement qu'un téléphone étroit ; ce serait faux — le bloc des 860 px
   met aussi les fenêtres en plein écran, ce qui est juste sur un téléphone et
   absurde sur un portable de 1366 × 650. */
@media (min-width: 861px) and (max-height: 660px) {
  .toolrail { flex-direction: row; }
  .stage__coin { flex-direction: column; gap: 14px; }
  .toolrail__sep { height: auto; width: 8px; }
}

/* ── AU DOIGT ──────────────────────────────────────────────────────────────
   Ce bloc ne parle pas de largeur mais de POINTEUR : une tablette de mille
   pixels de large se touche aussi au doigt, et une petite fenêtre de bureau ne
   se touche pas du tout. `pointer: coarse` désigne exactement les écrans où le
   doigt est l'outil.

   LE PIÈGE DES SEIZE PIXELS. Safari sur iOS grossit la page dès qu'on prend le
   focus d'un champ dont le texte fait moins de seize pixels — il croit rendre
   service. Nos champs sont à onze et douze, donc chaque nom tapé dans le
   panneau des soutiens déclenchait un zoom ; et comme le châssis est en
   position fixe, la page ne redescend pas toute seule après : elle reste
   agrandie, décalée, et il faut pincer pour s'en sortir. Seize pixels tout
   rond, et Safari ne touche plus à rien. C'est la seule parade qui ne coûte pas
   la possibilité de zoomer volontairement.

   Les blocs JSON en lecture seule y passent aussi. Ils seraient plus jolis
   petits, mais je n'ai pas pu vérifier si Safari épargne les champs en lecture
   seule, et un doute ne vaut pas la peine d'être servi à un vrai téléphone. */
@media (pointer: coarse) {
  /* `!important` assumé, et une seule fois dans toute la feuille. Ce n'est pas
     un choix de style qu'une règle plus précise aurait le droit de contredire,
     c'est une contrainte de la plateforme : chaque champ porte une classe
     (`.soutien__champ`, `.notes__aire`…) qui bat en précision un sélecteur de
     balise, quelle que soit la requête de média — mesuré, les champs restaient
     à onze pixels. L'autre parade serait d'énumérer les classes ici, c'est-à-dire
     de tenir une liste qui pourrira au premier champ ajouté. */
  input, textarea, select { font-size: 16px !important; }
  /* Pas de rectangle gris arrondi sur chaque bouton touché : le châssis de 1994
     n'a aucun coin rond, ce serait le seul de la page. */
  html { -webkit-tap-highlight-color: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001s !important;
    transition-duration: .001s !important;
  }
}

/* ───────────────────────────── 8. Vue en quadrants ────────────────────── */
/* Le moteur dessine quatre caméras dans la même image ; la croix de
   séparation et les étiquettes restent en DOM, plus nettes qu'en WebGL. */
.stage.is-quad::after {
  content: '';
  position: absolute; inset: 0; pointer-events: none; z-index: 10;
  background:
    linear-gradient(to right, transparent calc(50% - 1px), var(--line) calc(50% - 1px), var(--line) calc(50% + 0px), transparent calc(50% + 0px)),
    linear-gradient(to bottom, transparent calc(50% - 1px), var(--line) calc(50% - 1px), var(--line) calc(50% + 0px), transparent calc(50% + 0px));
}
.stage.is-quad::before {
  content: 'DESSUS        FACE        PROFIL        PERSPECTIVE';
  position: absolute; inset: 0; pointer-events: none; z-index: 11;
  font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em;
  color: var(--ink-faint);
  /* Chaque étiquette est repositionnée par les variables ci-dessous. */
  display: none;
}
.stage.is-quad .hud, .stage.is-quad .facedots { opacity: .35; }

/* Étiquettes de quadrant */
.quadlabels { position: absolute; inset: 0; pointer-events: none; z-index: 12; display: none; }
.stage.is-quad .quadlabels { display: block; }
.quadlabels b {
  position: absolute;
  font-family: var(--f-pixel); font-size: 9px; letter-spacing: .12em;
  color: var(--ink-faint); font-weight: 400;
  padding: 4px 7px;
  background: color-mix(in srgb, var(--void) 55%, transparent);
}
.quadlabels b:nth-child(1) { left: 8px;   top: 8px; }
.quadlabels b:nth-child(2) { left: 50.6%; top: 8px; }
.quadlabels b:nth-child(3) { left: 8px;   top: 50.6%; }
.quadlabels b:nth-child(4) { left: 50.6%; top: 50.6%; color: var(--accent); }

/* L'index de contenu reste dans le DOM en permanence : masqué à l'œil, mais
   lisible par un lecteur d'écran et par un moteur de recherche. Deux classes
   pour passer devant la règle .flat ci-dessus. */
.flat.sr-only {
  position: absolute; inset: auto;
  width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ───────────────────────────── 9. Médias ──────────────────────────────── */
/* Vidéo et planches d'images dans les fiches projet. Rien n'est chargé
   depuis un serveur tiers tant que le visiteur n'a pas cliqué. */

.win__media { padding: 22px 22px 0; }
.win__mediatitle {
  margin: 0 0 10px;
  font-family: var(--f-mono); font-size: 10px; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-faint);
}

.media {
  position: relative; overflow: hidden;
  aspect-ratio: 16 / 9; background: var(--sunk);
  border: 1px solid var(--line);
}
.media > canvas, .media > img { display: block; width: 100%; height: 100%; object-fit: cover; }
.media > iframe, .media > video { display: block; width: 100%; height: 100%; border: 0; }
.media.is-playing { border-color: var(--accent); }

.media__play {
  position: absolute; inset: 0;
  display: grid; place-content: center; gap: 12px; justify-items: center;
  /* Juste assez d'ombre en bas pour la légende : l'affiche reste lisible. */
  background: linear-gradient(rgba(5, 8, 12, 0) 45%, rgba(5, 8, 12, .38));
  transition: background .3s var(--ease);
}
.media__play:hover { background: linear-gradient(rgba(5, 8, 12, 0) 45%, rgba(5, 8, 12, .2)); }
.media__play i {
  width: 54px; height: 54px;
  border: 1px solid var(--accent);
  background: color-mix(in srgb, var(--void) 55%, transparent);
  backdrop-filter: blur(4px);
  position: relative;
  transition: transform .3s var(--ease), background .3s var(--ease);
}
.media__play i::after {
  content: ''; position: absolute; top: 50%; left: 52%;
  transform: translate(-50%, -50%);
  border-left: 13px solid var(--accent);
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
}
.media__play:hover i { transform: scale(1.08); background: color-mix(in srgb, var(--void) 30%, transparent); }
.media__play span {
  font-family: var(--f-mono); font-size: 10px; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; color: var(--ink);
  /* La même étiquette que le surtitre du bandeau : lisible sur une affiche claire. */
  padding: 4px 8px 3px;
  background: color-mix(in srgb, var(--void) 62%, transparent);
  backdrop-filter: blur(4px);
}

/* Planches : des faces de Rubik's cube. Corps sombre, pastilles carrées
   3 × 3. Une image qui couvre plusieurs cases (span) est posée derrière la
   grille : chaque case en montre son morceau et les interstices la coupent
   comme les arêtes du cube coupent le titre d'une face. */
.gallery__faces { display: grid; gap: 14px; }
.gallery {
  --gap: 6px;
  display: grid; gap: var(--gap);
  grid-template-columns: repeat(3, minmax(0, 1fr));
  padding: var(--gap);
  background: #010204;
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), 0 12px 30px rgba(0, 0, 0, .35);
}
.gallery__cell, .gallery__blank {
  position: relative; display: block; min-width: 0;
  aspect-ratio: 1;
  padding: 0; border: 0; border-radius: 3px;
  overflow: hidden;
  background: var(--sunk);
}
.gallery__cell { cursor: zoom-in; }
.gallery__cell img {
  position: absolute; max-width: none;
  /* L'image couvre tout l'élément, interstices compris ; la case n'en
     laisse voir que sa part. */
  width: calc(var(--w, 1) * 100% + (var(--w, 1) - 1) * var(--gap));
  height: calc(var(--h, 1) * 100% + (var(--h, 1) - 1) * var(--gap));
  left: calc(var(--x, 0) * (-100% - var(--gap)));
  top: calc(var(--y, 0) * (-100% - var(--gap)));
  object-fit: cover;
  opacity: .92;
  transition: transform .5s var(--ease), opacity .3s ease;
}
/* Reflet de pastille et liseré, au-dessus de l'image. */
.gallery__cell::after, .gallery__blank::after {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none;
  background: linear-gradient(150deg, rgba(255, 255, 255, .07), transparent 34%);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .05);
  transition: box-shadow .25s ease;
}
/* :hover pour la case visée, is-hot (posé en JS) pour les autres morceaux
   de la même image. */
.gallery__cell:hover img, .gallery__cell.is-hot img { transform: scale(1.035); opacity: 1; }
.gallery__cell:hover::after, .gallery__cell.is-hot::after { box-shadow: inset 0 0 0 1px var(--accent); }
.gallery__cell:focus-visible { outline: none; }
.gallery__cell:focus-visible::after { box-shadow: inset 0 0 0 2px var(--accent); }
.gallery__blank { background: color-mix(in srgb, var(--accent) 22%, var(--sunk)); }

/* Visionneuse plein écran */
.lightbox {
  position: fixed; inset: 0; z-index: 400;
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  background: color-mix(in srgb, var(--void) 94%, transparent);
  backdrop-filter: blur(10px);
  pointer-events: auto;
  animation: winIn .25s var(--ease);
}
.lightbox__bar {
  display: flex; align-items: center; gap: 10px;
  padding: 0 6px 0 14px; height: 30px;
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
}
.lightbox__count {
  font-family: var(--f-pixel); font-size: 10px; letter-spacing: .1em;
  color: var(--accent); font-weight: 400;
}
/* Une hauteur en pourcentage a besoin d'un parent de hauteur définie, et une
   piste de grille ne l'est jamais tout à fait. On sort donc l'image du flux :
   positionnée sur les quatre côtés, « contain » fait le reste — quelle que
   soit la proportion du visuel, il tient toujours dans le cadre. */
.lightbox__fig {
  margin: 0; position: relative;
  min-height: 0; overflow: hidden;
}
.lightbox__fig img {
  position: absolute; inset: 22px;
  width: calc(100% - 44px); height: calc(100% - 44px);
  object-fit: contain;
}
.lightbox__cap {
  padding: 0 22px 20px; text-align: center; min-height: 1.5em;
  font-size: 12px; color: var(--ink-dim);
}
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 46px; height: 76px;
  display: grid; place-items: center;
  font-size: 30px; line-height: 1; color: var(--ink-dim);
  background: color-mix(in srgb, var(--chrome) 70%, transparent);
  border: 1px solid var(--line);
}
.lightbox__nav:hover { color: var(--accent); border-color: var(--accent); }
.lightbox__nav--prev { left: 10px; }
.lightbox__nav--next { right: 10px; }

/* La vidéo en grand : la même visionneuse, un cadre 16:9 aussi grand que
   l'écran le permet (barre de 30 px et marges de 22 px déduites). */
.lightbox--video { grid-template-rows: auto minmax(0, 1fr); }
.lightbox__stage {
  position: relative; min-height: 0;
  display: grid; place-items: center;
  padding: 22px;
}
.lightbox__video {
  width: min(100%, calc((100vh - 74px) * 16 / 9));
  width: min(100%, calc((100dvh - 74px) * 16 / 9));
  aspect-ratio: 16 / 9;
  background: #000;
  border: 1px solid var(--line);
  box-shadow: 0 24px 80px rgba(0, 0, 0, .55);
}
.lightbox__video iframe, .lightbox__video video { display: block; width: 100%; height: 100%; border: 0; }
/* Le site est en `color-scheme: dark` et l'iframe en hérite. Le lecteur Vimeo,
   lui, ne se déclare pas sombre : devant ce désaccord, le navigateur peint le
   fond de l'iframe en blanc opaque, et l'arrondi des pixels (écran à 175 %)
   en laisse voir une colonne à gauche de la vidéo. En `normal`, le fond de
   l'iframe reste transparent et ce liseré tombe sur le noir du cadre. */
.lightbox__video iframe { color-scheme: normal; }

@media (max-width: 560px) {
  .lightbox__stage { padding: 10px; }
  .gallery { --gap: 4px; }
  .lightbox__nav { width: 38px; height: 60px; font-size: 24px; }
  .win__media { padding: 18px 16px 0; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LE JEU — fenêtre d'outil, pas boîte de dialogue
   ──────────────────────────────────────────────────────────────────────────
   Elle reste ouverte pendant qu'on joue et ne couvre pas le cube : à droite,
   étroite, et elle se replie sous le cube sur un écran de téléphone. Une modale
   par-dessus un objet qu'il faut manipuler serait un contresens.
   ══════════════════════════════════════════════════════════════════════════ */
.jeu {
  position: absolute; z-index: 240;
  /* `.panels` laisse passer le pointeur — c'est ce qui permet au fond d'une
     visionneuse de rester cliquable. Une fenêtre qui vit dedans doit donc le
     RÉCUPÉRER, sinon elle s'affiche parfaitement et ne répond à rien : le
     canevas en dessous avale tous les clics. Éprouvé — le harnais a passé
     trente secondes à cliquer sur un bouton qui ne pouvait pas l'entendre. */
  pointer-events: auto;
  top: calc(var(--bandeau) + var(--gap)); right: var(--gap);
  width: 336px; max-height: calc(100% - var(--gap) * 4);
  display: flex; flex-direction: column;
  overflow: auto;
  background: color-mix(in srgb, var(--chrome) 94%, transparent);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--bevel-up), 0 18px 46px rgba(0, 0, 0, .6);
  backdrop-filter: blur(3px);
  /* Les fenêtres se lisent, elles ne se déchiffrent pas : le corps de base est
     monté d'un cran et demi, et tout ce qui en dépend suit sans autre réglage. */
  font-size: 12.5px;
}
.jeu__bar {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 8px;
  padding: 5px 6px 5px 9px;
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
}
.jeu__nom { font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-dim); }
.jeu__haut { padding: 12px 12px 8px; text-align: center; }
.jeu__chrono {
  display: block; font-family: var(--f-mono); font-size: 30px; font-weight: 500;
  letter-spacing: .02em; color: var(--ink);
  font-variant-numeric: tabular-nums;   /* les chiffres ne doivent pas danser */
}
.jeu__chrono.is-court { color: var(--a-studio); }
.jeu__etat { margin: 2px 0 0; font-size: 10px; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .06em; }

.jeu__paliers { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; padding: 0 10px 10px; }
.jeu__palier {
  display: grid; gap: 1px; padding: 7px 8px; text-align: left;
  background: var(--sunk); border: 1px solid var(--line-soft); color: var(--ink);
  font-family: var(--f-mono); font-size: 11px;
}
.jeu__palier:last-child { grid-column: 1 / -1; }
.jeu__palier:hover:not(:disabled) { border-color: var(--accent); }
.jeu__palier.is-on { border-color: var(--a-studio); background: color-mix(in srgb, var(--a-studio) 9%, var(--sunk)); }
.jeu__palier:disabled { opacity: .34; }
.jeu__palier em { font-style: normal; font-weight: 500; }
.jeu__coups, .jeu__record { font-size: 9px; color: var(--ink-faint); letter-spacing: .04em; }
.jeu__record { color: var(--ink-dim); font-variant-numeric: tabular-nums; }

.jeu__corps { padding: 0 12px 10px; }
.jeu__dit { margin: 0 0 8px; color: var(--ink-dim); line-height: 1.5; }
.jeu__abandon, .jeu__ok, .jeu__lien {
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .05em;
  padding: 6px 10px; background: var(--sunk);
  border: 1px solid var(--line); color: var(--ink-dim); text-transform: uppercase;
}
.jeu__ok { border-color: var(--a-labo); color: var(--a-labo); }
.jeu__abandon:hover, .jeu__lien:hover { border-color: var(--accent); color: var(--accent); }
.jeu__ok:hover { background: color-mix(in srgb, var(--a-labo) 14%, var(--sunk)); }

.jeu__gagne { display: grid; gap: 9px; }
/* Le chat prend sa propre ligne, au-dessus du texte : c'est une animation, pas
   une icône, et la coincer dans une colonne de cinquante-huit pixels à côté du
   score aurait rendu neuf images de travail illisibles. */
.jeu__fete { display: grid; justify-items: center; gap: 4px; text-align: center; }

/* LE CHAT CONTENT — la vraie séquence, neuf images de 216 × 232 sur une bande
   unique. Le défilé se fait en pixels entiers et non en pourcentages : avec
   `background-size: 900%`, `steps(9)` de 0 % à 100 % tombe sur des onzièmes
   alors que les images sont à des huitièmes de la course — les cadres seraient
   décalés d'un tiers d'image, et un chat coupé en deux se remarque. En pixels,
   neuf pas de 108 px, ça tombe juste par construction.

   L'IMAGE elle-même est posée par le JS : dans la version « fichier unique » la
   feuille de style est intégrée à la page et un chemin relatif vers assets/ ne
   mène plus nulle part — mesuré, ERR_FILE_NOT_FOUND. */
.jeu__chat {
  width: 108px; height: 116px;
  background-repeat: no-repeat;
  background-size: 972px 116px;
  background-position: 0 0;
  animation: jeuSaute .95s steps(9) infinite;
}
@keyframes jeuSaute { to { background-position-x: -972px; } }
/* En mouvement réduit, la règle globale ramène toute durée à un millième de
   seconde : une boucle de neuf images y deviendrait un stroboscope. On fige donc
   sur la cinquième — le chat en l'air, pattes levées, étincelles autour. C'est
   l'image de la séquence qui dit le mieux ce qu'elle raconte. */
@media (prefers-reduced-motion: reduce) {
  .jeu__chat { animation: none; background-position-x: -432px; }
}
.jeu__bravo { font-family: var(--f-display); font-size: 17px; color: var(--a-labo); }
.jeu__bravo[data-palier="p1"]  { color: var(--rang-p1); }
.jeu__bravo[data-palier="p3"]  { color: var(--rang-p3); }
.jeu__bravo[data-palier="p6"]  { color: var(--rang-p6);  font-size: 20px; text-shadow: 0 0 14px rgba(255, 210, 31, .40); }
.jeu__bravo[data-palier="p14"] { color: var(--rang-p14); font-size: 23px; text-shadow: 0 0 16px rgba(255, 61, 120, .50); }
.jeu__score { margin: 1px 0 0; font-variant-numeric: tabular-nums; color: var(--ink); }
.jeu__note { margin: 0; font-size: 10px; color: var(--ink-faint); line-height: 1.5; }
.jeu__forme { display: flex; gap: 1px; }
.jeu__champ {
  flex: 1; min-width: 0; padding: 6px 8px;
  background: var(--sunk); border: 1px solid var(--line); color: var(--ink);
  font-family: var(--f-mono); font-size: 12px;
}
.jeu__champ:focus { outline: none; border-color: var(--a-studio); }
.jeu__json {
  width: 100%; box-sizing: border-box; resize: vertical;
  background: var(--sunk); border: 1px solid var(--line); color: var(--ink-dim);
  font-family: var(--f-mono); font-size: 9px; line-height: 1.45; padding: 6px;
}

.jeu__pied { display: flex; gap: 1px; padding: 0 10px 10px; }
.jeu__pied .jeu__lien { flex: 1; text-align: center; }

.jeu__podium { padding: 0 12px 14px; border-top: 1px solid var(--line-soft); }
.jeu__h { margin: 10px 0 6px; font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-faint); }
.jeu__onglets { display: flex; gap: 1px; margin-bottom: 6px; }
.jeu__onglet {
  flex: 1; padding: 4px 2px; font-family: var(--f-mono); font-size: 9px;
  background: var(--sunk); border: 1px solid var(--line-soft); color: var(--ink-faint);
}
.jeu__onglet.is-on { border-color: var(--a-studio); color: var(--a-studio); }
.jeu__vide { margin: 0; font-size: 10px; color: var(--ink-faint); font-style: italic; }
.jeu__table { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.jeu__table li {
  /* Le nom prend ce qui reste et se tronque ; les quatre autres colonnes ont une
     largeur propre. Sans `minmax(0, 1fr)`, un nom de dix-huit caractères pousse
     la ligne au-delà du panneau et le bouton de rejeu passe à la ligne — vu, et
     corrigé ici plutôt qu'en raccourcissant les noms. */
  display: grid; align-items: center; gap: 5px;
  grid-template-columns: 16px minmax(0, 1fr) auto auto auto 20px;
  padding: 4px 5px; background: var(--sunk); border: 1px solid var(--line-soft);
}
.jeu__table li:first-child { border-color: color-mix(in srgb, var(--a-studio) 44%, var(--line)); }
.jeu__rang { font-style: normal; font-size: 9px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.jeu__joueur {
  font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1.15;
}
/* La colonne du nom est en `minmax(0, 1fr)` : un corps plus gros tronque plus
   tôt, il ne pousse pas le bouton de rejeu hors du panneau. C'est justement
   pourquoi cette contrainte était là. */
.jeu__joueur[data-palier="p1"]  { color: var(--rang-p1);  font-size: 11.5px; }
.jeu__joueur[data-palier="p3"]  { color: var(--rang-p3);  font-size: 13px; }
.jeu__joueur[data-palier="p6"]  { color: var(--rang-p6);  font-size: 15px;
  text-shadow: 0 0 10px rgba(255, 210, 31, .32); }
.jeu__joueur[data-palier="p14"] { color: var(--rang-p14); font-size: 17.5px; font-weight: 700;
  text-shadow: 0 0 12px rgba(255, 61, 120, .45); }
.jeu__etoile { color: var(--a-studio); }
.jeu__temps { font-variant-numeric: tabular-nums; color: var(--ink); }
.jeu__nb { font-size: 9px; color: var(--ink-faint); }
.jeu__rejeu {
  width: 20px; height: 20px; display: grid; place-items: center;
  background: transparent; border: 1px solid var(--line); color: var(--ink-dim); font-size: 8px;
}
.jeu__rejeu:hover:not(:disabled) { border-color: var(--a-cg); color: var(--a-cg); }
.jeu__rejeu:disabled { opacity: .4; }

/* ── Les noms d'étoiles ───────────────────────────────────────────────────
   En DOM et non peints dans la scène : en mode 1994 l'image est rendue au
   quart de sa définition, et un nom de visiteur y serait illisible. Ils ne
   captent jamais le pointeur — le ciel doit rester saisissable à travers. */
.starnames { position: absolute; inset: 0; pointer-events: none; z-index: 6; overflow: hidden; }
.starnames[hidden] { display: none; }
/* UNE PLAQUETTE SOUS LE NOM, ET PAS SEULEMENT UNE OMBRE.
   L'ombre portée suffisait tant que tous les noms étaient dorés sur du ciel.
   Avec la teinte par palier, un nom rose se pose un jour sur la face FILMS —
   qui est rose — et devient illisible. Mesuré, texte contre fond réellement
   rendu : 2,13:1 sans plaquette, contre un plancher de 4,5 dans la norme. Avec
   la plaquette à 74 % : 4,82:1, et les trois autres paliers passent de 4,2 / 7,2
   / 5,6 à 7,2 / 10,5 / 12,2. Pas d'arrondi : le châssis de 1994 n'en a nulle
   part. */
.starname {
  position: absolute; transform: translate(9px, -50%);
  padding: 1px 4px 2px;
  background: rgba(3, 6, 14, .74);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07);
  font-family: var(--f-pixel); font-size: 8px; letter-spacing: .08em;
  color: var(--a-studio); text-shadow: 0 0 8px rgba(0, 0, 0, .95), 0 0 3px rgba(0, 0, 0, 1);
  white-space: nowrap;
}
/* C'EST ICI QUE LE CODE DE COULEUR PAIE. Le palmarès est rangé par onglets, donc
   une seule difficulté à la fois ; la constellation, elle, les mélange toutes.
   Sans marque, les noms au ciel se ressemblaient tous. La teinte par défaut —
   l'or — reste celle des étoiles offertes par un soutien, qui n'ont pas de
   palier : elles se distinguent ainsi de celles qui ont été gagnées. */
.starname[data-palier="p1"]  { color: var(--rang-p1);  font-size: 7px; }
.starname[data-palier="p3"]  { color: var(--rang-p3);  font-size: 8px; }
.starname[data-palier="p6"]  { color: var(--rang-p6);  font-size: 10px; }
.starname[data-palier="p14"] { color: var(--rang-p14); font-size: 12px;
  text-shadow: 0 0 8px rgba(0, 0, 0, .95), 0 0 3px rgba(0, 0, 0, 1), 0 0 14px rgba(255, 61, 120, .55); }
/* En visée, le ciel devient la cible : le curseur le dit, et le cube s'efface
   un peu pour qu'on regarde le fond. */
body.is-viseetoile #view { cursor: crosshair; }
body.is-viseetoile .toolrail, body.is-viseetoile .facedots { opacity: .3; }

@media (max-width: 720px) {
  .jeu {
    top: auto; right: var(--gap); left: var(--gap); bottom: var(--gap);
    width: auto; max-height: 58%;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   LA PLATINE — « The Grand Tour »
   ──────────────────────────────────────────────────────────────────────────
   La pochette n'est pas une jaquette : c'est un vinyle, planètes gravées autour
   d'un label solaire, avec son bras de lecture. Une pochette pareille ne
   demande pas un cadre, elle demande une platine — le disque tourne quand ça
   joue, s'arrête à la pause, et ne se recale JAMAIS à midi entre deux pistes.
   La rotation est donc pilotée en JS, par petits pas cumulés : une animation
   CSS repartirait de zéro à chaque changement de piste, et ce sursaut est
   exactement ce qui trahit une fausse platine.
   ══════════════════════════════════════════════════════════════════════════ */
.disque {
  position: absolute; z-index: 236; pointer-events: auto;
  top: calc(var(--bandeau) + var(--gap)); right: var(--gap);
  width: 318px; max-height: calc(100% - var(--gap) * 4);
  display: flex; flex-direction: column; overflow: auto;
  background: color-mix(in srgb, var(--chrome) 94%, transparent);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--bevel-up), 0 18px 46px rgba(0, 0, 0, .6);
  backdrop-filter: blur(3px);
  font-size: 12.5px;
}
.disque__bar {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 8px;
  padding: 5px 6px 5px 9px;
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
}
.disque__nom { font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-dim); }

.disque__haut { display: flex; gap: 11px; align-items: center; padding: 12px 12px 8px; }
.disque__platine { position: relative; flex: none; width: 106px; height: 106px; }
.disque__galette {
  position: absolute; inset: 0;
  background-size: cover; background-position: 50% 50%;
  border-radius: 50%;                    /* le seul rond du site, et il le mérite */
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .8), 0 6px 18px rgba(0, 0, 0, .55),
    inset 0 0 22px rgba(0, 0, 0, .45);
}
/* Le bras : posé au repos, engagé quand ça joue. Il n'est pas dans la pochette
   — celle de Julia Chaix en a déjà un, dessiné — donc c'est un reflet qu'on
   ajoute, pas un deuxième bras : un liseré clair qui balaie le disque. */
.disque__bras {
  position: absolute; inset: 0; border-radius: 50%;
  background: linear-gradient(118deg,
    rgba(255, 255, 255, 0) 34%, rgba(255, 255, 255, .13) 47%,
    rgba(255, 255, 255, 0) 60%);
  pointer-events: none;
}
.disque.is-joue .disque__galette { box-shadow: 0 0 0 1px rgba(0, 0, 0, .8),
  0 6px 22px rgba(0, 0, 0, .6), inset 0 0 22px rgba(0, 0, 0, .45),
  0 0 26px rgba(255, 170, 40, .28); }

.disque__idc { display: grid; gap: 2px; min-width: 0; }
.disque__album {
  font-family: var(--f-display); font-size: 15px; line-height: 1.15; color: var(--ink);
}
.disque__artiste { color: var(--ink-dim); }
.disque__compte { font-size: 9px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .08em; }

.disque__corps { padding: 0 12px 10px; display: grid; gap: 7px; }
.disque__piste {
  font-family: var(--f-mono); font-size: 12px; color: var(--a-studio);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.disque__ligne-temps { display: grid; grid-template-columns: auto 1fr auto; gap: 7px; align-items: center; }
.disque__temps { font-family: var(--f-mono); font-size: 9px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.disque__barre {
  height: 5px; padding: 0; border: 1px solid var(--line);
  background: var(--sunk); position: relative; cursor: pointer;
}
.disque__jauge {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--a-studio) 55%, transparent), var(--a-studio));
}
.disque__commandes { display: flex; align-items: center; gap: 5px; }
.disque__bt {
  width: 26px; height: 24px; display: grid; place-items: center;
  background: var(--chrome-hi); border: 1px solid var(--line); color: var(--ink-dim);
  font-size: 10px; cursor: pointer;
}
.disque__bt:hover { color: var(--ink); border-color: var(--a-cg); }
.disque__bt--gros { width: 36px; color: var(--ink); }
.disque__volboite { margin-left: auto; display: flex; align-items: center; gap: 5px; }
.disque__hp { font-style: normal; color: var(--ink-faint); font-size: 10px; }
.disque__vol { width: 74px; accent-color: var(--a-studio); }
.disque__dit { margin: 0; font-size: 10px; color: var(--ink-faint); line-height: 1.45; }
.disque__dit.is-avert { color: var(--a-about); }

.disque__liste { list-style: none; margin: 0; padding: 0 12px 12px; display: grid; gap: 1px; }
.disque__ligne {
  width: 100%; display: grid; grid-template-columns: 18px minmax(0, 1fr) auto;
  gap: 7px; align-items: center; text-align: left;
  padding: 4px 6px; background: var(--sunk); border: 1px solid var(--line-soft);
  color: var(--ink-dim); font-size: 11px; cursor: pointer;
}
.disque__ligne em { font-style: normal; font-size: 9px; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.disque__ligne b { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.disque__ligne span { font-family: var(--f-mono); font-size: 9px; color: var(--ink-faint); }
.disque__ligne:hover { border-color: var(--line); color: var(--ink); }
.disque__ligne.is-on {
  border-color: color-mix(in srgb, var(--a-studio) 50%, var(--line));
  color: var(--a-studio);
}
.disque__ligne.is-on em { color: var(--a-studio); }
/* Ce qui joue, dans la barre d'état. */
.st--musique { color: var(--a-studio); max-width: 190px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.st--musique[hidden] { display: none; }

@media (max-width: 720px) {
  .disque { top: auto; right: var(--gap); left: var(--gap); bottom: var(--gap);
    width: auto; max-height: 62%; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LES NOTES DE VISIONNAGE — même châssis que le jeu, à gauche
   ══════════════════════════════════════════════════════════════════════════ */
.notes {
  position: absolute; z-index: 238; pointer-events: auto;
  top: calc(var(--bandeau) + var(--gap)); left: calc(var(--rail) + var(--gap));
  width: 360px; max-height: calc(100% - var(--gap) * 4);
  display: flex; flex-direction: column; overflow: auto;
  background: color-mix(in srgb, var(--chrome) 94%, transparent);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--bevel-up), 0 18px 46px rgba(0, 0, 0, .6);
  backdrop-filter: blur(3px);
  font-size: 12.5px;
}
.notes__bar {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 8px; padding: 5px 6px 5px 9px;
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
}
.notes__nom { font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-dim); }
.notes__onglets { display: flex; gap: 1px; padding: 8px 10px 0; }
.notes__onglet {
  flex: 1; padding: 5px 4px; font-family: var(--f-mono); font-size: 9px;
  letter-spacing: .05em; text-transform: uppercase;
  background: var(--sunk); border: 1px solid var(--line-soft); color: var(--ink-faint);
}
.notes__onglet.is-on { border-color: var(--a-cg); color: var(--a-cg); }
.notes__corps { padding: 10px; display: grid; gap: 8px; }
.notes__dit { margin: 0; color: var(--ink-dim); line-height: 1.5; font-size: 10px; }
.notes__vide { margin: 0; font-size: 10px; color: var(--ink-faint); font-style: italic; }
.notes__merci { margin: 0; font-size: 10px; color: var(--a-labo); }
.notes__liste { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.notes__note { padding: 7px 8px; background: var(--sunk); border: 1px solid var(--line-soft); }
.notes__meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; font-size: 9px; color: var(--ink-faint); }
.notes__meta b { font-size: 11px; color: var(--ink); font-weight: 500; }
.notes__cible { font-style: normal; color: var(--a-cg); }
.notes__cuite {
  padding: 0 4px; border: 1px solid var(--line);
  color: var(--ink-faint); text-transform: uppercase; letter-spacing: .06em;
}
/* `pre-wrap` : les retours à la ligne du visiteur sont conservés, et le texte
   passe par textContent — donc jamais interprété comme du HTML. */
.notes__texte { margin: 4px 0 0; color: var(--ink-dim); line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
.notes__actes { display: flex; flex-wrap: wrap; gap: 1px; margin-top: 6px; align-items: center; }
.notes__champ, .notes__aire, .notes__json {
  width: 100%; box-sizing: border-box; padding: 6px 8px;
  background: var(--sunk); border: 1px solid var(--line); color: var(--ink);
  font-family: var(--f-mono); font-size: 11px; line-height: 1.5;
}
.notes__aire { resize: vertical; }
.notes__json { font-size: 9px; color: var(--ink-dim); }
.notes__champ:focus, .notes__aire:focus { outline: none; border-color: var(--a-cg); }
.notes__attache { display: flex; gap: 6px; align-items: center; font-size: 10px; color: var(--ink-dim); }
.notes__ok, .notes__non, .notes__lien {
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  padding: 6px 10px; background: var(--sunk); border: 1px solid var(--line); color: var(--ink-dim);
}
.notes__ok { border-color: var(--a-labo); color: var(--a-labo); }
.notes__non { border-color: var(--a-about); color: var(--a-about); }
.notes__lien:hover { border-color: var(--accent); color: var(--accent); }
.notes__ok:hover { background: color-mix(in srgb, var(--a-labo) 14%, var(--sunk)); }
.notes__non:hover { background: color-mix(in srgb, var(--a-about) 12%, var(--sunk)); }

/* Les notes d'un projet, dans sa fiche */
.win__notes { padding: 26px 22px 18px; }
.win__notes h3 { margin: 0 0 8px; font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-faint); }

@media (max-width: 720px) {
  .notes {
    top: auto; left: var(--gap); right: var(--gap); bottom: var(--gap);
    width: auto; max-height: 62%;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   SOUTENIR — et le générique
   ══════════════════════════════════════════════════════════════════════════ */
.soutien {
  position: absolute; z-index: 239; pointer-events: auto;
  top: calc(var(--bandeau) + var(--gap)); right: var(--gap);
  width: 360px; max-height: calc(100% - var(--gap) * 4);
  display: flex; flex-direction: column; overflow: auto;
  background: color-mix(in srgb, var(--chrome) 94%, transparent);
  border: 1px solid var(--line);
  box-shadow: inset 0 1px 0 var(--bevel-up), 0 18px 46px rgba(0, 0, 0, .6);
  backdrop-filter: blur(3px);
  font-size: 12.5px;
}
.soutien__bar {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 8px; padding: 5px 6px 5px 9px;
  background: linear-gradient(var(--chrome-hi), var(--chrome));
  border-bottom: 1px solid var(--line);
}
.soutien__nom { font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-dim); }
.soutien__onglets { display: flex; gap: 1px; padding: 8px 10px 0; }
.soutien__onglet {
  flex: 1; padding: 5px 4px; font-family: var(--f-mono); font-size: 9px;
  letter-spacing: .05em; text-transform: uppercase;
  background: var(--sunk); border: 1px solid var(--line-soft); color: var(--ink-faint);
}
.soutien__onglet.is-on { border-color: var(--a-studio); color: var(--a-studio); }
.soutien__corps { padding: 10px; display: grid; gap: 8px; }
.soutien__dit { margin: 0; color: var(--ink-dim); line-height: 1.55; }
.soutien__mini { margin: 0; font-size: 9px; color: var(--ink-faint); line-height: 1.5; }
.soutien__vide { margin: 0; font-size: 10px; color: var(--ink-faint); font-style: italic; }
.soutien__merci { margin: 0; font-size: 10px; color: var(--a-labo); }
/* L'avertissement d'aperçu et celui de relecture : ils s'adressent à Julia Chaix,
   pas au visiteur, et doivent se distinguer du reste au premier regard. */
.soutien__avert {
  margin: 0; padding: 6px 8px; font-size: 9px; line-height: 1.5;
  color: var(--a-studio); background: color-mix(in srgb, var(--a-studio) 8%, var(--sunk));
  border: 1px solid color-mix(in srgb, var(--a-studio) 34%, var(--line));
}

.soutien__campagne { display: grid; gap: 4px; }
.soutien__chiffres { display: flex; gap: 6px; align-items: baseline; font-variant-numeric: tabular-nums; }
.soutien__chiffres b { font-size: 17px; color: var(--a-studio); }
.soutien__chiffres span { color: var(--ink-dim); }
.soutien__chiffres em { margin-left: auto; font-style: normal; font-size: 9px; color: var(--ink-faint); text-transform: uppercase; }
.soutien__barre { height: 5px; background: var(--sunk); border: 1px solid var(--line); }
.soutien__barre i { display: block; height: 100%; background: var(--a-studio); }

.soutien__paliers { display: grid; gap: 1px; }
.soutien__palier {
  display: grid; gap: 2px; padding: 7px 8px; text-align: left;
  background: var(--sunk); border: 1px solid var(--line-soft); color: var(--ink);
  font-family: var(--f-mono); font-size: 11px; position: relative;
}
button.soutien__palier:hover { border-color: var(--a-studio); }
.soutien__palier.is-on { border-color: var(--a-studio); background: color-mix(in srgb, var(--a-studio) 9%, var(--sunk)); }
.soutien__palier b { color: var(--a-studio); font-weight: 500; }
.soutien__palier em { font-style: normal; font-weight: 500; }
.soutien__palier p { margin: 0; font-size: 9px; color: var(--ink-faint); line-height: 1.5; }
.soutien__dons { position: absolute; top: 6px; right: 8px; color: var(--a-studio); font-size: 10px; }

.soutien__donner {
  display: block; padding: 9px 12px; text-align: center;
  font-family: var(--f-mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--void); background: var(--a-studio);
  border: 1px solid var(--a-studio); border-bottom: 1px solid var(--a-studio);
}
.soutien__donner:hover { background: color-mix(in srgb, var(--a-studio) 82%, #fff); border-bottom-color: var(--a-studio); }

.soutien__h { margin: 2px 0 0; font-family: var(--f-pixel); font-size: 9px; letter-spacing: .1em; color: var(--ink-faint); }
/* Le générique : des blocs par palier, comme un générique de fin qu'on lirait
   de haut en bas. Pas de défilement automatique — on n'impose pas un rythme de
   lecture à quelqu'un qui cherche son propre nom. */
.soutien__roule { display: grid; gap: 10px; }
.soutien__bloc em {
  display: block; font-style: normal; font-size: 9px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--a-studio); margin-bottom: 3px;
}
.soutien__bloc ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.soutien__bloc li { display: flex; gap: 6px; align-items: baseline; }
.soutien__bloc li b { font-weight: 500; }
.soutien__bloc li i { font-style: italic; font-size: 9px; color: var(--ink-faint); }
.soutien__etoile { color: var(--a-studio); }

.soutien__bravo { font-family: var(--f-display); font-size: 18px; color: var(--a-labo); }
.soutien__champ, .soutien__json {
  width: 100%; box-sizing: border-box; padding: 6px 8px;
  background: var(--sunk); border: 1px solid var(--line); color: var(--ink);
  font-family: var(--f-mono); font-size: 11px;
}
.soutien__json { font-size: 9px; color: var(--ink-dim); resize: vertical; line-height: 1.45; }
.soutien__champ:focus { outline: none; border-color: var(--a-studio); }
.soutien__ok, .soutien__non, .soutien__lien {
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase;
  padding: 6px 10px; background: var(--sunk); border: 1px solid var(--line); color: var(--ink-dim);
}
.soutien__ok { border-color: var(--a-labo); color: var(--a-labo); }
.soutien__non { border-color: var(--a-about); color: var(--a-about); }
.soutien__lien:hover { border-color: var(--accent); color: var(--accent); }
.soutien__file { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.soutien__file li { padding: 7px 8px; background: var(--sunk); border: 1px solid var(--line-soft); }
.soutien__meta { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; font-size: 9px; color: var(--ink-faint); }
.soutien__meta b { font-size: 11px; color: var(--ink); font-weight: 500; }
.soutien__texte { margin: 4px 0 0; color: var(--ink-dim); line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.soutien__actes { display: flex; gap: 1px; margin-top: 6px; }

@media (max-width: 720px) {
  .soutien {
    top: auto; right: var(--gap); left: var(--gap); bottom: var(--gap);
    width: auto; max-height: 62%;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   LE GÉNÉRIQUE DÉROULANT
   ──────────────────────────────────────────────────────────────────────────
   Le fond est TRANSPARENT, à peine assombri : ce qu'on voit derrière n'est pas
   une image de ciel, c'est le vrai champ d'étoiles du moteur, qui continue de
   tourner pendant que le texte s'enfonce. Une couleur opaque aurait tué la
   seule chose qui rendait l'effet possible ici.
   ══════════════════════════════════════════════════════════════════════════ */
.crawl {
  position: fixed; inset: 0; z-index: 300; pointer-events: auto;
  background: transparent;
  cursor: pointer; overflow: hidden;
  display: grid; grid-template-rows: 1fr auto;
}
/* LA NUIT TOMBE, MAIS PAR LE BAS.
   Le voile ne s'abat pas d'un bloc : il monte en quatorze secondes, opaque au
   pied de l'écran, nul en haut. C'est là toute la ruse — un voile plein aurait
   assombri le fond ET les étoiles, or les étoiles sont la seule chose qui donne
   au défilé sa profondeur. On noircit donc l'endroit où il n'y a rien à perdre.
   Les deux couches sont ABSOLUES, donc hors flux : elles ne comptent pas comme
   pistes de la grille et n'ajoutent aucune rangée. */
.crawl::before,
.crawl::after {
  content: ''; position: absolute; inset: 0;
  pointer-events: none; z-index: 0;
}
.crawl::before {
  background:
    radial-gradient(120% 88% at 50% 106%,
      rgba(2, 4, 10, .88) 0%, rgba(2, 4, 10, .52) 44%, rgba(2, 4, 10, 0) 80%),
    linear-gradient(to top,
      rgba(2, 4, 10, .80) 0%, rgba(2, 4, 10, .34) 30%, rgba(2, 4, 10, .06) 58%, rgba(2, 4, 10, 0) 78%);
  opacity: 0; animation: crawlNuit 14s ease forwards;
}
/* LES PROJECTEURS. Deux faisceaux montent du point de fuite, un doré et un
   bleu, décalés — la lumière qui manquait à un texte qui, sinon, flotterait
   sans source. Ils restent volontairement sous le seuil où ils laveraient le
   ciel : c'est un halo de salle, pas un éclairage de parking. */
.crawl::after {
  background:
    conic-gradient(from 209deg at 50% 122%,
      rgba(255, 210, 31, 0) 0deg,  rgba(255, 216, 90, .085) 13deg,
      rgba(255, 210, 31, 0) 27deg, rgba(77, 149, 255, 0) 100deg,
      rgba(120, 180, 255, .07) 113deg, rgba(77, 149, 255, 0) 126deg),
    radial-gradient(26% 13% at 50% 101%, rgba(255, 214, 60, .34), rgba(255, 210, 31, 0) 72%);
  opacity: 0; animation: crawlPhare 6s 1.2s ease forwards, crawlSouffle 11s 7.2s ease-in-out infinite;
}
@keyframes crawlNuit  { from { opacity: 0; } to { opacity: 1; } }
@keyframes crawlPhare { from { opacity: 0; } to { opacity: 1; } }
/* Le halo du point de fuite respire — une salle de projection n'est jamais
   parfaitement stable. L'amplitude est faible exprès : au-delà, ça clignote. */
@keyframes crawlSouffle { 0%, 100% { opacity: 1; } 50% { opacity: .72; } }

/* La perspective vit sur le PARENT, l'inclinaison sur l'enfant : c'est la
   seule répartition qui donne un vrai point de fuite. Les deux sur le même
   élément et le texte s'incline sans s'enfoncer.
   Le `z-index: 1` n'est pas décoratif : sans lui, les deux pseudo-éléments —
   absolus, donc peints au-dessus d'un frère resté statique — passeraient PAR
   DESSUS le texte, et le voile de nuit assombrirait les noms qu'il est censé
   faire ressortir. */
/* LA PERSPECTIVE N'EST PLUS UNE CONSTANTE.
   Elle était figée à 340px, et c'était un bug patient : le plan de caméra CSS
   se trouve à `perspective / sin(inclinaison)`, soit 431px de texte local. Tout
   ce qui passait au-delà — c'est-à-dire TOUT le générique après l'introduction
   dès qu'il y avait plus de deux noms — traversait ce plan et s'étalait en
   lettres géantes et floues sur tout l'écran, puis disparaissait. Le défilé
   finissait par vingt secondes de ciel vide. Personne ne l'avait vu parce que
   les captures étaient prises dans les premières secondes.
   Le JS mesure donc la hauteur réelle du bloc et calcule la perspective qui
   plafonne l'agrandissement de la ligne la plus profonde. Effet secondaire
   heureux : l'évasement du trapèze ne dépend plus du nombre de soutiens — deux
   noms ou deux cents, le générique a la même allure. */
.crawl__scene {
  /* LE POINT DE FUITE EST DANS LE CADRE, pas sur son bord.
     Il était à 0 %, c'est-à-dire tout en haut : le texte convergeait vers un
     point qu'on ne voyait jamais, et n'atteignait jamais sa petite taille. En le
     descendant à un sixième de la hauteur, les lignes rentrent VRAIMENT dans
     l'horizon — elles rapetissent jusqu'à disparaître, au lieu de sortir du
     cadre à leur taille de départ. */
  perspective: var(--crawl-p, 500px); perspective-origin: 50% var(--crawl-o, 16%);
  overflow: hidden; position: relative; z-index: 1;
  /* Le texte s'efface en approchant de l'horizon. Le dégradé du masque fait
     ce travail bien mieux qu'une bande de couleur posée par-dessus, qui aurait
     masqué les étoiles en même temps que les noms. */
  mask-image: linear-gradient(to bottom, transparent 4%, #000 30%, #000 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 4%, #000 30%, #000 100%);
}
/* `preserve-3d` ICI est la clé de toute l'extrusion, et son absence est un
   piège silencieux : sans lui, le titre serait aplati dans le plan du texte
   AVANT que la perspective de la scène ne s'applique, les seize tranches
   n'auraient plus aucun raccourci, et `translateZ` ne produirait exactement
   rien de visible. Le volume ne se voit que si la chaîne entière — scène en
   perspective, texte, titre — reste en trois dimensions. */
.crawl__texte {
  position: absolute; left: 50%; width: min(620px, 78vw);
  transform-origin: 50% 0%;
  transform-style: preserve-3d;
  text-align: center; color: var(--a-studio);
  font-family: var(--f-display); font-weight: 700;
  text-shadow: 0 0 24px rgba(255, 210, 31, .28);
  /* LE TEXTE GLISSE LE LONG DE SON PROPRE PLAN.
     Il coulissait avant en `top`, c'est-à-dire à l'écran, APRÈS la projection :
     chaque ligne gardait alors sa taille du début à la fin, et le défilé n'était
     qu'une image inclinée qui remonte. Le `translateY` est écrit APRÈS le
     `rotateX`, donc il s'applique AVANT lui : la ligne se déplace dans le plan
     incliné, sa profondeur change, et elle rétrécit en s'éloignant. C'est toute
     la différence entre un plan qui monte et un texte qui s'en va.
     La valeur est posée par le JS à chaque image — il faut de toute façon une
     boucle pour masquer les lignes qui passent derrière le plan de caméra. */
  top: var(--crawl-o, 16%);
  transform: translateX(-50%) rotateX(52deg) translateY(var(--crawl-d, 0px));
}
.crawl--plat .crawl__scene { perspective: none; mask-image: none; -webkit-mask-image: none; }
.crawl--plat .crawl__texte {
  position: static; transform: none; transform-style: flat;
  width: min(620px, 84vw); margin: 0 auto;
  animation: none; overflow: auto; max-height: 100%; padding: 24px 0;
}
.crawl__episode {
  margin: 0 0 6px; font-family: var(--f-mono); font-weight: 500;
  font-size: 15px; letter-spacing: .28em; color: var(--a-cg);
  text-shadow: 0 0 10px rgba(77, 149, 255, .70), 0 0 28px rgba(77, 149, 255, .30);
}

/* ── LE TITRE EXTRUDÉ ──────────────────────────────────────────────────────
   Les seize tranches sont ABSOLUES et se superposent exactement ; la face,
   elle, reste dans le flux et c'est elle qui donne sa hauteur au titre. Faire
   l'inverse — dix-sept copies empilées dans le flux — aurait donné un titre
   dix-sept fois trop haut, ce qui est le genre de détail qu'on ne voit qu'une
   fois la page ouverte. */
.crawl__titre {
  position: relative; margin: 0 0 30px;
  font-size: clamp(34px, 6vw, 68px); letter-spacing: .04em;
  transform-style: preserve-3d;
}
.crawl__tranche, .crawl__face { display: block; }
.crawl__tranche {
  position: absolute; left: 0; right: 0; top: 0;
  color: #8a5a0c; text-shadow: none;
}
/* LA FACE. Un dégradé de chrome doré découpé aux lettres (`background-clip`),
   surmonté d'une bande blanche oblique qui balaie le mot toutes les cinq
   secondes et demie — le reflet, pas une animation de couleur.
   Le halo passe par `drop-shadow` et non par `text-shadow` : sur un texte dont
   le remplissage est transparent, `text-shadow` se peint DERRIÈRE la lettre et
   se voit donc à travers, ce qui empâte le dégradé ; `drop-shadow` suit le
   canal alpha du rendu, c'est-à-dire la silhouette réelle des lettres.
   Le `translateZ` minuscule met la face franchement devant sa propre épaisseur,
   au lieu de laisser le tri de profondeur trancher une égalité à zéro. */
.crawl__face {
  position: relative; transform: translateZ(.6px);
  background-image:
    linear-gradient(102deg,
      rgba(255, 255, 255, 0) 34%, rgba(255, 255, 255, .92) 46%, rgba(255, 255, 255, 0) 58%),
    linear-gradient(180deg,
      #fff7d4 0%, #ffe488 24%, #ffd21f 46%, #b8780a 60%, #ffe796 76%, #fffaea 100%);
  background-size: 320% 100%, 100% 100%;
  background-position: 0% 0, 0 0;
  background-repeat: no-repeat;
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  text-shadow: none;
  /* DEUX OMBRES, PAS QUATRE. Mesuré : la chaîne de quatre `drop-shadow`, dont
     deux à trente-huit et soixante-quatre pixels de flou, coûtait un bon tiers
     du temps d'image (0,93 contre 1,32 i/s au rastériseur logiciel, tout le
     reste égal). Les deux ombres serrées en rendent 1,29 — presque tout le
     bénéfice pour presque rien. Le grand halo coloré, lui, n'a aucun besoin de
     suivre le contour des lettres : il est passé en dégradé derrière le titre,
     où il ne coûte pas un flou par image. */
  filter:
    drop-shadow(0 0 2px rgba(255, 246, 210, .95))
    drop-shadow(0 0 14px rgba(255, 210, 31, .78));
  animation: crawlBrillance 5.5s 1s ease-in-out infinite;
}
/* LA FLAQUE DE LUMIÈRE. Un dégradé radial posé DERRIÈRE l'épaisseur du titre —
   doré au centre, rose sur les bords, éteint avant les bords de la boîte. Ce
   n'est pas un halo de lettres, c'est une source : le titre a l'air éclairé au
   lieu d'avoir l'air phosphorescent, et ça ne coûte rien par image. */
.crawl__titre::before {
  content: ''; position: absolute; left: -18%; right: -18%; top: -46%; bottom: -46%;
  transform: translateZ(-.5em); pointer-events: none;
  background: radial-gradient(closest-side at 50% 50%,
    rgba(255, 214, 60, .30) 0%, rgba(255, 120, 140, .15) 46%, rgba(255, 61, 120, 0) 76%);
}
/* Le reflet reste au repos les deux tiers du temps, puis traverse : un chrome
   qui scintille sans arrêt ressemble à une bannière de publicité. */
@keyframes crawlBrillance {
  0%, 58% { background-position: 0% 0, 0 0; }
  100%    { background-position: 100% 0, 0 0; }
}
/* En mouvement réduit, les seize tranches n'ont plus de perspective pour les
   faire mordre : elles se superposeraient à l'identique, seize fois le même
   mot au même endroit, pour rien. On les retire — mais on ne rend pas le titre
   plat pour autant : une pile d'ombres décalées rend l'épaisseur, en fixe. On
   a demandé qu'on arrête de bouger, pas qu'on arrête d'avoir du relief.
   L'ombre ne traverse pas la lettre : le dégradé qui la remplit est opaque, et
   `text-shadow` se peint derrière — il ne dépasse donc qu'en bas à droite. */
.crawl--plat .crawl__titre { transform-style: flat; }
.crawl--plat .crawl__tranche { display: none; }
.crawl--plat .crawl__face {
  animation: none; background-position: 100% 0, 0 0; transform: none;
  text-shadow:
    1px 1px 0 #a06a12, 2px 2px 0 #96630f, 3px 3px 0 #8a5a0c, 4px 4px 0 #7d500a,
    5px 5px 0 #6f4608, 6px 6px 0 #613c06, 7px 7px 0 #533205, 8px 9px 14px rgba(0, 0, 0, .6);
}
.crawl--plat::before, .crawl--plat::after { animation: none; opacity: 1; }
/* À plat le générique s'affiche en haut de l'écran : sans cette réserve, la
   ligne d'épisode se poserait pile sur la barre de menus. */
.crawl--plat .crawl__scene { padding-top: calc(var(--bandeau) + 10px); }

.crawl__intro {
  margin: 0 0 40px; font-family: var(--f-mono); font-weight: 400;
  font-size: clamp(15px, 2vw, 21px); line-height: 1.65; text-align: justify;
}
/* En colonne étroite, la justification creuse des rivières de blanc entre les
   mots — mesuré sur un écran de 390 px, cinq lignes sur six en portaient. Un
   texte centré ne fait pas ça, et le défilé est déjà tout en symétrie. */
@media (max-width: 720px) {
  .crawl__intro { text-align: center; }
}
.crawl__bloc { margin: 0 0 34px; }
.crawl__bloc h3 {
  margin: 0 0 12px; font-family: var(--f-mono); font-weight: 500;
  font-size: clamp(12px, 1.5vw, 16px); letter-spacing: .22em; color: var(--a-cg);
  text-shadow: 0 0 10px rgba(77, 149, 255, .75), 0 0 26px rgba(77, 149, 255, .35);
}
/* Le filet sous l'intitulé : un néon qui s'éteint aux deux bouts. */
.crawl__bloc h3::after {
  content: ''; display: block; width: 46%; height: 1px; margin: 9px auto 0;
  background: linear-gradient(90deg,
    rgba(77, 149, 255, 0), rgba(140, 190, 255, .9), rgba(77, 149, 255, 0));
  box-shadow: 0 0 9px rgba(77, 149, 255, .65);
}
.crawl__bloc p {
  margin: 0 0 5px; font-size: clamp(17px, 2.4vw, 26px); letter-spacing: .02em;
  text-shadow: 0 0 12px rgba(255, 210, 31, .48), 0 0 30px rgba(255, 61, 120, .20);
}
.crawl__sortie {
  position: relative; z-index: 1;
  margin: 0; padding: 10px; text-align: center;
  font-family: var(--f-mono); font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-faint);
}
/* Le mouvement réduit passe par la classe posée en JS, mais la garde media est
   là pour le cas où le réglage change APRÈS l'ouverture du générique : sans
   elle, la règle globale qui ramène toute animation à un millième de seconde
   transformerait le reflet — qui, lui, boucle — en stroboscope. */
@media (prefers-reduced-motion: reduce) {
  .crawl__face { animation: none; }
  .crawl::before, .crawl::after { animation: none; opacity: 1; }
}

/* Pendant le générique, le châssis s'efface : les fenêtres disparaissent, le
   reste s'estompe sans bloquer le clic — qui, lui, ferme le générique. */
body.is-crawl .soutien,
body.is-crawl .jeu,
body.is-crawl .notes,
body.is-crawl .disque,
body.is-crawl .win { display: none; }
body.is-crawl .toolrail,
body.is-crawl .facedots,
body.is-crawl .hud,
body.is-crawl .era { opacity: .07; transition: opacity .8s ease; }
