/* ============================================================
   NOCIF — V3 « cinématique toxique »
   Fond noir, accent vert fluo. Intro, scroll inertiel, preview au
   survol, HUD timecode mono, curseur custom, parallaxe, grain ciné.
   ============================================================ */

:root {
  --bg: #070707;
  --ink: #ece9e2;
  --accent: #c2ff00;
  --accent-soft: rgba(194, 255, 0, 0.14);
  --line: rgba(236, 233, 226, 0.14);
  --dim: rgba(236, 233, 226, 0.5);
  --pad: clamp(16px, 2.2vw, 30px);
  --tile-w: clamp(230px, 21vw, 372px);
  --tile-ratio: 3 / 2;   /* source 16:9 + object-fit cover → rogne uniquement les côtés */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --mono: "JetBrains Mono", ui-monospace, monospace;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }

body {
  background: var(--bg);
  color: var(--ink);
  font-family: "Inter Tight", "Helvetica Neue", Arial, sans-serif;
  font-size: 14px; line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
body.no-scroll, body.loading { overflow: hidden; }
body { overflow: hidden; height: 100vh; }   /* vue unique plein écran */

button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ol, ul { list-style: none; }
img { display: block; }

.noscript {
  position: fixed; inset: 0; z-index: 400; background: var(--bg);
  display: grid; place-content: center; gap: 12px; padding: var(--pad);
  text-align: center; font-family: var(--mono); font-size: 13px; line-height: 1.7;
}
.noscript a { color: var(--accent); text-decoration: underline; }

/* ============ FOCUS CLAVIER ============ */
/* Le site masque le curseur système (`cursor: none`) et n'avait AUCUN style de
   focus : en navigation clavier, rien n'indiquait où l'on se trouvait.
   `:focus-visible` ne s'affiche qu'au clavier, jamais au clic souris. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.tile:focus-visible { outline-offset: 7px; }
/* la vignette focalisée adopte l'état visuel du survol (cadre + relief) pour
   que le repère clavier soit aussi lisible que le repère souris */
.strip.is-revealed .tile:focus-visible { opacity: 1; --ty: -16px; --hs: 1.09; z-index: 5; }

.mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* Lu par les moteurs de recherche et les lecteurs d'écran, invisible à l'œil.
   `clip-path` plutôt que `display:none` : ce dernier retire aussi l'élément
   des lecteurs d'écran, ce qui annulerait tout l'intérêt. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ============ PRELOADER ============ */

.loader {
  position: fixed; inset: 0; z-index: 300;
  /* Fond PLAT (2026-08-23). C'était un dégradé radial de sept niveaux étalés sur
     tout l'écran : à cette échelle, les paliers de quantification se voient en
     anneaux — le banding signalé par le client. Un dégradé CSS ne peut pas être
     tramé ; celui du shader, si (voir TRAMAGE dans initNeuro). On laisse donc la
     profondeur au shader, qui la rend sans paliers. */
  background: #060606;
  display: grid; place-content: center; place-items: center;
  transition: opacity 0.5s ease;
  cursor: default;
}
/* Le loader passe AU-DESSUS de la couche CRT (300 contre 1) : il porte donc ses
   propres lignes de balayage, sinon l'intro serait la seule surface lisse du
   site. Rien ne les recouvre ici — il n'y a pas d'image à préserver. */
.loader::after {
  content: ""; position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.15) 0 1px,
    transparent 1px 3px
  );
}
/* CURSEUR PENDANT L'INTRO.
   Le curseur custom vit à z-index 250, le loader à 300 : pendant l'intro il est
   donc DERRIÈRE, invisible. C'est le curseur système qui doit prendre le relais
   — sauf que `body.has-cursor button { cursor: none }` (deux types + une classe)
   l'emportait sur ce qu'héritait le bouton, et le curseur disparaissait pile sur
   ENTER : le seul endroit de l'écran où l'on a besoin de le voir. Même piège que
   `body.player-open` plus haut, même remède.
   (Le curseur custom n'est volontairement PAS remonté au-dessus du loader : il est
   vert, et l'intro doit rester strictement noir & blanc.)
   Le loader n'affiche plus la main partout non plus : depuis que seul le bouton
   fait entrer, un curseur « cliquable » sur tout l'écran mentait sur l'action. */
body.loading, body.loading * { cursor: default !important; }
.loader.is-ready .loader__enter { cursor: pointer !important; }
.loader.is-done { opacity: 0; pointer-events: none; transition: opacity 0.55s ease; }

/* champ « neural noise » plein cadre, derrière le texte (voir initNeuro) */
.loader__noise {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 1; display: block; pointer-events: none;
}

/* à l'entrée, le bloc de texte s'efface en s'avançant vers l'objectif */
.loader.is-opening .loader__core {
  opacity: 0; transform: scale(1.14);
  transition: opacity 0.5s var(--ease), transform 0.9s var(--ease);
}

.loader__core {
  position: relative; z-index: 2;
  display: grid; justify-items: center; gap: 20px;
  width: min(86vw, 440px); padding: 30px;
}

.loader__brand { position: relative; }
/* la typo n'est plus la vedette : NOCIF en petit label mono discret — c'est
   l'obturateur qui porte l'entrée. */
.loader__word {
  display: block; font-family: var(--mono); font-weight: 400;
  font-size: 15px; letter-spacing: 0.62em; text-indent: 0.62em;
  text-transform: uppercase; color: rgba(236, 233, 226, 0.92);
  transition: letter-spacing 0.5s var(--ease), text-indent 0.5s var(--ease);
}
.loader.is-ready .loader__word { letter-spacing: 0.72em; text-indent: 0.72em; }

/* NOUVEAU TRAITEMENT DU MOT (2026-08-23).
   Deux essais de logotype géant ont été refusés (contour « générique », serif
   italique « cheap ») : on ne rejoue donc PAS la carte de la fonte. Le mot reste
   le petit label mono, mais il cesse d'être posé SUR l'image — il en fait partie :
     • `difference` : le mot inverse ce qui passe dessous. Sur le noir il est
       blanc ; quand un filament le traverse, la lettre s'assombrit et le fluide
       la traversée — le mot est littéralement rongé par le champ.
     • les lettres s'allument UNE À UNE au fil du chargement (voir runIntro) :
       le mot se compose au lieu d'attendre, figé. */
.loader__word { mix-blend-mode: difference; }
.loader__word .lw {
  display: inline-block;
  opacity: 0.14;
  transition: opacity 0.55s var(--ease), filter 0.55s var(--ease);
  filter: blur(1.4px);
}
.loader__word .lw.is-lit { opacity: 1; filter: none; }

/* Journal de démarrage. Les lignes s'empilent par le BAS (`bottom: 0`) : chaque
   nouvelle ligne pousse les précédentes vers le haut, où le masque les efface.
   Aucune position à calculer en JS, aucune mesure de hauteur. */
.loader__log {
  position: relative; width: 100%; height: 62px; overflow: hidden;
  font-family: var(--mono); font-size: 10px; line-height: 1.5;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: rgba(236, 233, 226, 0.34);
  -webkit-mask: linear-gradient(to bottom, transparent, #000 55%, #000);
          mask: linear-gradient(to bottom, transparent, #000 55%, #000);
}
.loader__log ul { position: absolute; left: 0; right: 0; bottom: 0; display: flex; flex-direction: column; gap: 2px; }
.loader__log li { display: flex; gap: 12px; white-space: nowrap; animation: logIn 0.42s var(--ease) both; }
.loader__log .tc { color: rgba(236, 233, 226, 0.22); font-variant-numeric: tabular-nums; }
.loader__log .ok { margin-left: auto; color: rgba(236, 233, 226, 0.5); }
@keyframes logIn { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }

/* barre de progression : hairline plein largeur, remplie en scaleX (GPU) */
.loader__bar { width: 100%; height: 1px; background: var(--line); position: relative; overflow: hidden; }
.loader__bar span {
  position: absolute; inset: 0; transform-origin: left center; transform: scaleX(0);
  background: var(--ink); box-shadow: 0 0 10px rgba(236, 233, 226, 0.35);
}

.loader__meta {
  width: 100%; display: flex; justify-content: space-between; align-items: baseline;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--dim); font-variant-numeric: tabular-nums;
}
.loader__status::before { content: "\203A  "; color: rgba(236, 233, 226, 0.55); }
.loader__pct { color: var(--ink); }

/* bouton ENTER : masqué jusqu'à la fin du chargement (.is-ready) */
.loader__enter {
  position: relative; margin-top: 6px;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.4em;
  text-transform: uppercase; color: var(--ink);
  padding: 14px 26px 14px 30px;                    /* +gauche : compense l'letter-spacing */
  border: 1px solid rgba(236, 233, 226, 0.30); background: rgba(236, 233, 226, 0.04);
  opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity 0.55s var(--ease) 0.15s, transform 0.55s var(--ease) 0.15s,
              background 0.25s, color 0.25s, border-color 0.25s;
}
.loader.is-ready .loader__enter {
  opacity: 1; transform: none; pointer-events: auto;
  animation: enterpulse 2.1s ease-in-out infinite;
}
.loader__enter:hover {
  background: var(--ink); color: var(--bg); border-color: var(--ink); animation: none;
}
/* focus clavier : liseré blanc discret — surtout PAS le vert accent global,
   et pas d'inversion (le bouton est auto-focus, il paraîtrait « enfoncé ») */
.loader__enter:focus-visible { outline: 1px solid rgba(236, 233, 226, 0.7); outline-offset: 4px; }
/* équerres de viseur aux coins (rappel des repères de cadrage du site) */
.loader__enter::before, .loader__enter::after {
  content: ""; position: absolute; width: 8px; height: 8px; pointer-events: none;
  border: 1px solid currentColor; opacity: 0.7;
}
.loader__enter::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
.loader__enter::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }
@keyframes enterpulse {
  0%, 100% { box-shadow: 0 0 0 rgba(236, 233, 226, 0); }
  50% { box-shadow: 0 0 20px rgba(236, 233, 226, 0.16); }
}

/* MISE AU POINT À LA RÉVÉLATION : quand l'œil s'ouvre, le site apparaît d'abord
   flou et sombre puis « se cale » net — comme une caméra qui fait le point. */
.stage, .backdrop, #bg-gl { transition: filter 0.85s var(--ease); }
body.focus-pull .stage,
body.focus-pull .backdrop,
body.focus-pull #bg-gl { filter: blur(18px) brightness(0.5); }

/* ============ REPÈRES DE CADRAGE ============ */

.viewfinder { position: fixed; inset: 0; z-index: 58; pointer-events: none; }
.vf { position: absolute; width: 14px; height: 14px; opacity: 0.35; }
.vf::before, .vf::after { content: ""; position: absolute; background: var(--ink); }
.vf::before { width: 100%; height: 1px; top: 0; }
.vf::after { width: 1px; height: 100%; left: 0; }
.vf--tl { top: 16px; left: 16px; }
.vf--tr { top: 16px; right: 16px; transform: scaleX(-1); }
.vf--bl { bottom: 16px; left: 16px; transform: scaleY(-1); }
.vf--br { bottom: 16px; right: 16px; transform: scale(-1); }

/* ============ CURSEUR CUSTOM ============ */

.cursor, .cursor-dot {
  position: fixed; top: 0; left: 0; pointer-events: none; z-index: 250;
  display: none; will-change: transform;
}
body.has-cursor { cursor: none; }
body.has-cursor a, body.has-cursor button,
body.has-cursor .tile, body.has-cursor .strip { cursor: none; }
body.has-cursor .cursor, body.has-cursor .cursor-dot { display: grid; place-items: center; }

.cursor {
  width: 30px; height: 30px; margin: -15px 0 0 -15px;
  border: 1px solid var(--accent); border-radius: 50%;
  transition: width .3s var(--ease), height .3s var(--ease),
              margin .3s var(--ease), background-color .3s, opacity .25s;
}
.cursor-dot { width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; background: var(--accent); border-radius: 50%; }
.cursor.is-hover { width: 74px; height: 74px; margin: -37px 0 0 -37px; background: var(--accent-soft); }
.cursor__label {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.1em;
  color: var(--accent); opacity: 0; transition: opacity .25s; text-transform: uppercase;
}
.cursor.is-hover .cursor__label { opacity: 1; }
body.cursor-out .cursor, body.cursor-out .cursor-dot { opacity: 0; }
.cursor-dot { transition: opacity 0.25s; }

/* !important nécessaire : `body.has-cursor button{cursor:none}` (2 sélecteurs
   de type) est plus spécifique que `body.player-open *` (1 seul) et gagnait
   silencieusement sur .player__hit — le curseur disparaissait sur la vidéo. */
body.player-open, body.player-open * { cursor: auto !important; }
body.player-open .cursor, body.player-open .cursor-dot { opacity: 0; }

/* ============ FOND — « grille technique » ============ */
/* Remplace la carte topographique (2 blobs de courbes en bas, jugés trop
   décoratifs). Ici : une grille d'instrument fine, plein cadre, dans le même
   langage que le HUD / les repères de cadrage. Le masque radial l'estompe
   derrière la bande de vignettes (centre) pour ne jamais concurrencer l'image,
   et la laisse respirer dans les zones vides (haut / bas / bords).
   PUR CSS statique : dégradés composités GPU, aucun SVG, aucun JS, aucune
   animation → coût ~nul (même exigence de perf que la topo qu'elle remplace). */

/* Repli STATIQUE (WebGL indisponible ou tactile) : grille monochrome détaillée,
   4 niveaux de trame, sans distorsion. Sur desktop avec WebGL, le canvas opaque
   la recouvre. Fondu vertical pour vivre dans les bandes vides haut/bas. */
.backdrop {
  position: fixed; inset: 0; z-index: -2; pointer-events: none; overflow: hidden;
  /* Aligné sur le shader WebGL (5 niveaux, teinte froide, très assombri) :
     les deux rendus doivent se ressembler si l'un remplace l'autre. */
  --grid: 40px;
  --micro: rgba(150, 170, 195, 0.014);
  --fine:  rgba(150, 170, 195, 0.022);
  --line:  rgba(150, 170, 195, 0.036);
  --major: rgba(150, 170, 195, 0.060);
  --block: rgba(150, 170, 195, 0.082);
  background-image:
    linear-gradient(var(--block) 1px, transparent 1px),
    linear-gradient(90deg, var(--block) 1px, transparent 1px),
    linear-gradient(var(--major) 1px, transparent 1px),
    linear-gradient(90deg, var(--major) 1px, transparent 1px),
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px),
    linear-gradient(var(--fine) 1px, transparent 1px),
    linear-gradient(90deg, var(--fine) 1px, transparent 1px),
    linear-gradient(var(--micro) 1px, transparent 1px),
    linear-gradient(90deg, var(--micro) 1px, transparent 1px);
  background-size:
    calc(var(--grid) * 8) calc(var(--grid) * 8),
    calc(var(--grid) * 8) calc(var(--grid) * 8),
    calc(var(--grid) * 4) calc(var(--grid) * 4),
    calc(var(--grid) * 4) calc(var(--grid) * 4),
    var(--grid) var(--grid),
    var(--grid) var(--grid),
    calc(var(--grid) / 2) calc(var(--grid) / 2),
    calc(var(--grid) / 2) calc(var(--grid) / 2),
    calc(var(--grid) / 4) calc(var(--grid) / 4),
    calc(var(--grid) / 4) calc(var(--grid) / 4);
  -webkit-mask: linear-gradient(to bottom, #000, transparent 43%, transparent 59%, #000);
          mask: linear-gradient(to bottom, #000, transparent 43%, transparent 59%, #000);
}

/* Canvas WebGL : la grille « vivante » avec la lentille trou noir. Opaque,
   plein cadre, sous tout le contenu. Retiré par le JS si l'init WebGL échoue
   (→ le repli .backdrop redevient visible). */
.backdrop-gl {
  position: fixed; inset: 0; z-index: -1; width: 100%; height: 100%;
  pointer-events: none; display: block;
}

/* ============ GRAIN + VIGNETTE ============ */

.grain {
  /* -10% suffit : l'animation translate au max de ±3% de la taille du calque.
     L'ancien -60% créait une texture composité d'environ 220vw × 220vh (~5×
     la surface de l'écran) déplacée en continu avec un blend-mode — mémoire
     GPU et coût de compositing pour rien. */
  /* ⚠ z-index NÉGATIF, et ce n'est pas un détail. `.stage` porte `perspective`,
     ce qui crée un contexte d'empilement : la bande n'est donc PAS à 3 dans le
     contexte racine, elle y entre à z-index auto. À niveau égal (0 ou auto),
     c'est l'ordre du DOM qui tranche — et le grain, placé en fin de body, repassait
     devant les vignettes. En négatif il tombe derrière tout le contenu, tout en
     restant au-dessus du fond WebGL (même niveau, mais plus loin dans le DOM). */
  position: fixed; inset: -10%; z-index: -1; pointer-events: none;
  /* `mix-blend-mode: overlay` RETIRÉ (mesuré) : un calque plein écran en mode
     de fusion oblige le compositeur à recomposer tout l'écran à chaque image —
     86 fps et 17 % d'images saccadées AU REPOS. Sans lui : 112 fps, 1,5 %.
     Pour garder le même grain sans fusion, la texture porte désormais sa
     transparence (feColorMatrix : alpha = 0.7·bruit − 0.3) : des grains clairs
     épars sur du transparent, plutôt qu'un voile gris opaque qui délaverait
     les noirs. */
  /* Relevé le 2026-08-24 (0.12 → 0.22) et grains plus contrastés : la fréquence
     descend légèrement (0.85 → 0.78 : grain un peu plus gros, donc visible sans
     être dense) et le SEUIL monte (1.15·bruit − 0.5) : moins de grains, mais plus
     francs. À seuil bas, monter l'opacité ajoutait un voile continu qui relevait
     le noir de fond ; à seuil haut, la poussière se voit autant sans délaver.
     Opacité redescendue de 0.30 à 0.19 le 2026-08-24 : à 0.30 le grain mangeait
     le contraste général. */
  opacity: 0.19;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.78' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 1.15 0 0 0 -0.5'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain 0.7s steps(4) infinite;
}
@keyframes grain {
  0% { transform: translate(0, 0); }
  25% { transform: translate(-3%, 2%); }
  50% { transform: translate(2%, -3%); }
  75% { transform: translate(-2%, -2%); }
  100% { transform: translate(3%, 2%); }
}
.vignette {
  position: fixed; inset: 0; z-index: 55; pointer-events: none;
  background: radial-gradient(125% 125% at 50% 42%, transparent 50%, rgba(0, 0, 0, 0.62));
}

/* ============ COUCHE CRT ============ */
/* Lignes de balayage : un noir très dilué, une ligne sur trois, IMMOBILES (une
   trame qui glisse scintille et fatigue en quelques secondes).
   ⚠ Elle est posée DERRIÈRE les vignettes (z-index négatif) : le
   grain et la trame appartiennent au fond, pas aux images. Passées par-dessus,
   elles mangeaient le contraste des miniatures — c'est le travail du client
   qu'on texturait, pas le décor (corrigé le 2026-08-24).
   ⚠ Une barre de trame descendante avait été ajoutée ici puis RETIRÉE le même
   jour : « elle fait cheap ». Ne pas y revenir. */
.crt {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;   /* voir la note de .grain */
  background-image: repeating-linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0.17) 0 1px,
    transparent 1px 3px
  );
}

/* ============ HEADER ============ */

.header {
  position: fixed; inset: 0 0 auto 0; z-index: 70;
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: start; gap: 20px; padding: 16px var(--pad);
  text-transform: uppercase; font-size: 12px; letter-spacing: 0.04em;
  pointer-events: none;
}
.header > * { pointer-events: auto; }
.header__left { display: flex; align-items: baseline; gap: 22px; }
.logo { font-weight: 700; font-size: 15px; letter-spacing: 0.14em; white-space: nowrap; }
.logo__dot { color: var(--accent); font-size: 7px; vertical-align: middle; margin-left: 4px; }
.logo__dot.is-egg { animation: eggpulse 0.7s ease-in-out infinite; }
@keyframes eggpulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }

/* (.egg-toast retiré : le toast accompagnait l'easter-egg au triple-clic,
   remplacé en V3.4 par un démarrage automatique au 1er geste.) */
.header__role { opacity: 0.55; line-height: 1.3; }
/* ============ SÉLECTEUR DE SOURCE ============ */
/* Une pile de mots en colonne, c'est une barre de navigation — n'importe quel
   portfolio en a une. Ici chaque catégorie est une VOIE de régie : numéro,
   libellé, vumètre à segments proportionnel au nombre d'œuvres, et compte.
   Le site parle déjà le langage du matériel vidéo (REC, viseur, timecode, CRT) :
   le sélecteur devait en faire partie plutôt que d'être posé par-dessus.
   Effet de bord recherché : on lit la FORME du catalogue d'un coup d'œil. */

.header__filters { justify-self: center; }
.count { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.srcsel { display: flex; align-items: stretch; gap: 12px; }
/* étiquette du module, tournée comme sur une face avant d'appareil */
.srcsel__tag {
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.24em; color: var(--accent);
  opacity: 0.75; align-self: center; writing-mode: vertical-rl; transform: rotate(180deg);
}
.filters { display: flex; flex-direction: row; gap: 2px; }

.filter {
  position: relative;
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  align-items: center; gap: 3px 7px;
  padding: 9px 13px 8px; min-width: 96px;
  color: var(--dim);
  transition: color 0.25s var(--ease), background 0.3s var(--ease);
}
.filter:hover { color: var(--ink); background: rgba(236, 233, 226, 0.03); }
.filter.is-active { color: var(--accent); }

/* équerres de viseur autour de la voie active — même langage que le cadre des
   vignettes et les repères aux quatre coins de la page */
.filter::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  --c: var(--accent); --t: 1px; --l: 7px;
  background:
    linear-gradient(var(--c), var(--c)) 0 0 / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 0 / var(--t) var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 0 / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 0 / var(--t) var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 100% / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 100% / var(--t) var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 100% / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 100% / var(--t) var(--l) no-repeat;
  opacity: 0; transform: scale(1.08);
  transition: opacity 0.35s var(--ease), transform 0.4s var(--ease);
}
.filter.is-active::before { opacity: 1; transform: none; }
/* les équerres s'esquissent au survol : sans ça, rien ne dit qu'une voie
   inactive est sélectionnable — un vumètre, ça se regarde, ça ne se clique pas */
.filter:hover::before { opacity: 0.3; transform: none; }

.filter__n {
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.08em;
  opacity: 0.5; font-variant-numeric: tabular-nums;
}
.filter__lb {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.1em;
  text-transform: uppercase; text-align: left; white-space: nowrap;
}
.filter.is-active .filter__n { opacity: 1; }

/* VUMÈTRE. Deux couches de segments identiques : la piste (atténuée, pleine
   largeur) et le remplissage (à la couleur courante, largeur = part du total).
   Les pas du dégradé sont en PIXELS, donc les segments ne s'étirent pas avec la
   largeur — ils se comptent, comme sur un vrai appareil. */
.filter__m { position: relative; grid-column: 1 / -1; height: 4px; }
.filter__m::before,
.filter__m i {
  content: ""; position: absolute; top: 0; bottom: 0; left: 0;
  background-image: repeating-linear-gradient(to right, currentColor 0 3px, transparent 3px 6px);
}
.filter__m::before { right: 0; opacity: 0.16; }
.filter__m i { width: calc(var(--f, 0) * 100%); opacity: 0.75; transition: width 0.5s var(--ease); }
.filter.is-active .filter__m i { opacity: 1; filter: drop-shadow(0 0 4px rgba(194, 255, 0, 0.55)); }

.filter__c {
  grid-column: 1 / -1; justify-self: end;
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.06em;
  opacity: 0.45; font-variant-numeric: tabular-nums;
}
.filter.is-active .filter__c { opacity: 0.9; }
.header__nav { justify-self: end; display: flex; align-items: baseline; gap: 18px; }
.navlink { text-transform: uppercase; transition: color 0.2s; }
.navlink:hover { color: var(--accent); }
/* (.navlink--sound / .sounddot / .sound-state retirés : le bouton « Son » du
   header a disparu en V3.4, le son est piloté depuis les réglages.) */

/* fondu d'entrée du header */
.header, .hud, .scrollbar, .viewfinder { opacity: 0; transition: opacity 0.5s ease; }
body.ready .header, body.ready .hud, body.ready .scrollbar, body.ready .viewfinder { opacity: 1; }

/* ============ FILMSTRIP (stage 3D) ============ */

.view--slider { height: 100vh; }
.stage { position: relative; height: 100vh; perspective: 1600px; }

.strip {
  position: absolute; top: 50%; left: 0; right: 0;
  transform: translate(var(--px, 0px), calc(-50% + var(--py, 0px)));
  display: flex; gap: 6px;
  /* Grande marge verticale VOLONTAIRE : les fiches crédits sont posées en
     absolu au-dessus et en dessous des vignettes, or `overflow-y: hidden`
     (nécessaire pour le défilement horizontal) coupe au bord de la zone de
     remplissage. Ce padding leur donne la place d'exister sans être rognées,
     et agrandit au passage la surface de glissé. */
  padding: clamp(110px, 26vh, 260px) var(--pad);
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; cursor: grab; z-index: 3;
  /* Au doigt, le navigateur fait DÉFILER la bande de lui-même (overflow-x)
     pendant que nos gestionnaires de pointeur la déplacent aussi : les deux
     systèmes s'additionnaient et la bande partait deux fois trop vite.
     `pan-y` retire le défilement horizontal au navigateur et nous le laisse. */
  touch-action: pan-y;
}
.strip::-webkit-scrollbar { display: none; }
.strip.is-dragging { cursor: grabbing; }

.tile {
  position: relative;
  flex: 0 0 var(--tile-w); text-align: left;
  --ty: 28px; --hs: 1; --rx: 0deg; --ry: 0deg; --rz: 0deg;   /* lift / scale / 3 axes */
  opacity: 0;
  /* transform pur, GPU-composité : aucun recalcul de layout ni de filtre par
     frame (l'ancien effet "focus centre" lisait offsetLeft/offsetWidth de
     chaque vignette à 60fps + recalculait un filter CSS → gros coût perf). */
  transform: perspective(760px) translateY(var(--ty))
             rotateX(var(--rx)) rotateY(var(--ry)) rotateZ(var(--rz)) scale(var(--hs));
  transition: opacity 0.45s var(--ease), transform 0.13s ease-out;
}
.strip.is-revealed .tile { opacity: 1; --ty: 0px; }

/* ============ CARTON DE FIN ============ */
/* Remplace l'amorce de pellicule 35 mm (perforations, cadre en pointillés),
   abandonnée avec cette direction artistique le 2026-09-15.
   Aucun cadre, aucun ornement : un filet vertical, de l'air, et l'adresse mail
   comme seul élément de poids. Arrivé là, le visiteur a tout vu — c'est le seul
   endroit du site où l'on peut lui parler directement. */
.endcard {
  flex: 0 0 var(--tile-w); align-self: center;
  display: flex; flex-direction: column; align-items: flex-start;
  padding: 6px 0 6px 22px;
  border-left: 1px solid var(--line);
  text-align: left;
  opacity: 0; transform: translateY(28px);
  transition: opacity 0.45s var(--ease), transform 0.35s var(--ease),
              border-color 0.4s var(--ease);
}
.strip.is-revealed .endcard { opacity: 1; transform: none; }
.endcard:hover { border-left-color: var(--accent); }

.endcard__kicker {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--accent);
}
.endcard__count {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--dim); margin-top: 9px;
  font-variant-numeric: tabular-nums;
}
.endcard__ask { font-size: 13px; color: var(--dim); margin-top: clamp(26px, 5vh, 46px); }

/* l'adresse : le seul texte du site en grand corps hors logo et titres */
.endcard__mail {
  font-size: clamp(15px, 1.15vw, 20px); letter-spacing: -0.01em; line-height: 1.25;
  color: var(--ink); margin-top: 6px;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat; background-position: 0 100%; background-size: 0 1px;
  transition: color 0.3s var(--ease), background-size 0.45s var(--ease);
}
.endcard__mail:hover { color: var(--accent); background-size: 100% 1px; }

.endcard__back {
  display: flex; align-items: center; gap: 9px;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--dim);
  margin-top: clamp(24px, 4.5vh, 40px); padding: 6px 0;
  transition: color 0.25s var(--ease);
}
.endcard__back:hover, .endcard__back:focus-visible { color: var(--accent); }
.endcard__arrow { transition: transform 0.4s var(--ease); }
.endcard__back:hover .endcard__arrow { transform: translateX(-5px); }

/* pendant le rembobinage, la bande se resserre légèrement — en transform pur,
   donc composé par le GPU (aucun filtre à repeindre par image) */
.strip.is-rewinding .tile { --hs: 0.985; }
.strip.is-rewinding { cursor: default; }

/* VIGNETTE ACTIVE : voisines assombries, active relevée + nettement agrandie.
   ⚠️ `.is-hot` et NON `:hover`. Ces règles déplacent la vignette (échelle 1,09,
   soulèvement de 16 px) : si c'était le survol qui les déclenchait, la vignette
   se retirerait de sous le curseur et le survol se perdrait tout seul — c'est
   précisément le bug de décrochage. La classe est posée par bindNav à partir de
   la boîte de mise en page, qui ne bouge jamais. NE PAS revenir à `:hover` ici. */
.strip.is-revealed:has(.tile.is-hot) .tile { opacity: 0.72; }
.strip.is-revealed .tile.is-hot { opacity: 1; --ty: -16px; --hs: 1.09; z-index: 5; }
/* mode clavier : la sélection porte le liseré vert, comme un cadre de visée */
.strip.is-keynav .tile.is-hot { outline: 2px solid var(--accent); outline-offset: 7px; }
/* fiche ouverte : l'image reste en avant-plan et à plat */
.tile.is-crew { opacity: 1 !important; --ty: -16px; --hs: 1.09; z-index: 6; }

.tile__cover {
  display: block; position: relative; width: 100%;
  aspect-ratio: var(--tile-ratio); overflow: hidden; background: #131313;
  box-shadow: 0 0 0 rgba(0, 0, 0, 0);
  transition: box-shadow 0.45s var(--ease);
}
/* ombre verte discrète — volontairement courte : la bande a overflow hidden en
   vertical (scroll horizontal), une ombre longue était tranchée net en bas. */
.tile.is-hot .tile__cover { box-shadow: 0 6px 16px -10px rgba(194, 255, 0, 0.28); }

/* Cadre de mise au point.
   L'ancien liseré clignotait (animation par paliers « signal parasité ») —
   remplacé par un cadrage de viseur : quatre équerres qui se posent aux coins
   en même temps qu'un trait continu très discret. Rien ne clignote, tout est
   en opacity/transform (composité GPU), et ça reprend le langage des repères
   de cadrage aux quatre coins de la page. */
.tile__frame {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border: 1px solid rgba(194, 255, 0, 0.14);
  opacity: 0; transform: scale(1.012);
  transition: opacity 0.4s var(--ease), transform 0.4s var(--ease);
}
.tile__frame::before {
  content: ""; position: absolute; inset: 5px;
  --c: rgba(194, 255, 0, 0.85); --t: 1px; --l: 15px;
  background:
    linear-gradient(var(--c), var(--c)) 0 0 / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 0 / var(--t) var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 0 / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 0 / var(--t) var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 100% / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 100% / var(--t) var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 100% / var(--l) var(--t) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 100% / var(--t) var(--l) no-repeat;
  opacity: 0; transform: scale(1.05);
  transition: opacity 0.45s var(--ease), transform 0.55s var(--ease);
}
.tile.is-hot .tile__frame, .tile.is-crew .tile__frame,
.tile:focus-visible .tile__frame { opacity: 1; transform: none; }
.tile.is-hot .tile__frame::before, .tile.is-crew .tile__frame::before,
.tile:focus-visible .tile__frame::before { opacity: 1; transform: none; }

.tile__cover .cover__img {
  position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; object-fit: cover;
  filter: brightness(0.8) saturate(0.92);
  transform: scale(calc(var(--imgz, 1) * 1.001));
  transition: filter 0.35s var(--ease), transform 0.6s var(--ease), opacity 0.4s;
  -webkit-user-drag: none; user-select: none;   /* empêche le drag natif de l'image (cassait le glisser) */
}
.tile.is-hot .cover__img, .tile:focus-visible .cover__img { filter: brightness(1.04) saturate(1.1); transform: scale(calc(var(--imgz, 1) * 1.06)); }
/* miniature 4:3 letterboxée → zoom pour couper les bandes noires */
.cover__img.is-boxed { --imgz: 1.22; }

/* Balayage WebGL : posé par-dessus la miniature pendant ~1,1 s au survol.
   Mêmes réglages de rendu que la miniature survolée pour que le passage de
   l'un à l'autre soit invisible. */
.tile__scan {
  position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%;
  display: block; pointer-events: none;
  filter: brightness(1.04) saturate(1.1);
}
.tile.has-preview .tile__scan { opacity: 0 !important; }

/* Preview au survol.
   ⚠️ PAS d'`opacity:0` ici : Chrome refuse de démarrer la lecture d'une iframe
   rendue totalement transparente (considérée non visible) → la preview ne
   démarrait jamais. Elle joue donc normalement, simplement CACHÉE DERRIÈRE la
   miniature (z-index) ; on révèle en faisant disparaître la miniature.
   Dimensionnement "cover" EXACT d'un 16:9 dans une case 3:2 → 118.6% × 100% :
   rogne UNIQUEMENT les côtés, toute la hauteur d'image est conservée.
   (l'ancien 154%×130% zoomait trop ; le bandeau YouTube est maintenant masqué
   par le voile haut ci-dessous plutôt que par du recadrage) */
.tile__preview {
  position: absolute; top: 50%; left: 50%; border: 0; z-index: 1;
  width: 118.6%; height: 100%; transform: translate(-50%, -50%);
  pointer-events: none;
}
.tile__preview iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }
.tile.has-preview .cover__img { opacity: 0; }

/* EXTRAIT LOCAL (assets/previews/<slug>.mp4).
   Rien à surdimensionner ici : le fichier est à nous, sans bande noire
   incrustée ni habillage à repousser hors champ. La boîte épouse donc
   exactement la case, et `object-fit: cover` cadre l'extrait comme la
   miniature qu'il remplace — quel que soit le format d'export. */
.tile__preview--file { width: 100%; height: 100%; }
.tile__preview--file video {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* même étalonnage que la miniature survolée : le passage de l'image fixe à
     l'extrait ne doit pas se voir comme un changement de contraste */
  filter: brightness(1.04) saturate(1.1);
}

/* RETIRÉ (V4.7) — voile noir qui couvrait les 24 % supérieurs pendant l'aperçu
   pour masquer le bandeau titre de YouTube. Le client le voyait, à juste titre,
   comme « une barre noire en haut des vignettes ». Ce qui masque désormais ce
   bandeau : l'aperçu n'est révélé qu'après 1,3 s (le temps que le bandeau
   s'efface, derrière la miniature qui, elle, ne bouge pas) et le cadre de
   l'aperçu déborde de la case, ce qui repousse l'habillage hors champ. */

/* ============ FICHE CRÉDITS ============ */
/* Langage « relevé technique » : mono, capitales, valeurs alignées à droite
   reliées par une ligne de pointillés, entêtes en vert. Tout est posé en
   ABSOLU autour de la vignette → aucun décalage de mise en page, les voisines
   ne bougent pas d'un pixel quand la fiche s'ouvre. */

.tile__crewbtn {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 7;
  display: flex; flex-direction: column; align-items: center;
  padding-top: 6px; opacity: 0; pointer-events: none;
  transition: opacity 0.3s var(--ease);
}
/* la flèche n'apparaît qu'au survol de la vignette, et reste cliquable/
   survolable tant que la fiche est ouverte */
/* La zone apparaît avec la vignette active. Son test de collision n'a plus
   d'importance pour l'ouverture de la fiche : bindNav décide « le curseur est
   sous la case » en géométrie de mise en page. `pointer-events` ne sert donc
   plus qu'au clic (tactile, et clavier via le focus). */
.tile.is-hot .tile__crewbtn, .tile.is-crew .tile__crewbtn { opacity: 1; pointer-events: auto; }
.tile__crewstem { width: 1px; height: 12px; background: var(--line); transition: background 0.25s, height 0.3s var(--ease); }
/* Le mot « crédits » à côté de la flèche : seule la flèche ne disait pas ce
   qu'elle ouvrait. Volontairement minuscule et gris — c'est une indication,
   pas un bouton d'appel. Le tout forme UNE seule cible de survol. */
.tile__crewcta {
  display: flex; align-items: center; gap: 6px; padding: 5px 10px;
}
.tile__crewword {
  font-family: var(--mono); font-size: 8px; line-height: 1;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--dim);
  transition: color 0.25s;
}
.tile__crewarrow {
  font-size: 7px; line-height: 1; color: var(--dim);
  transition: color 0.25s, transform 0.3s var(--ease);
}
.tile.is-crew .tile__crewstem { background: var(--accent); height: 16px; }
.tile.is-crew .tile__crewword, .tile.is-crew .tile__crewarrow { color: var(--accent); }
.tile.is-crew .tile__crewarrow { transform: rotate(180deg); }

.crew {
  position: absolute; left: 0; right: 0; z-index: 6;
  display: flex; flex-direction: column; gap: 2px;
  font-family: var(--mono); font-size: 9px; line-height: 1.3; letter-spacing: 0.05em;
  text-transform: uppercase; text-align: left;
  opacity: 0; pointer-events: none;
  transition: opacity 0.35s var(--ease), transform 0.45s var(--ease);
}
.crew__list { display: flex; flex-direction: column; gap: 2px; }

/* Deux colonnes pour les grosses équipes (voir crewBot).
   En colonne étroite, la mise en page « poste ····· nom » ne tient pas : les
   deux textes sont insécables, la ligne dépassait de sa colonne et venait
   s'écrire PAR-DESSUS la colonne voisine. On abandonne donc la ligne de
   pointillés dans ce mode au profit d'un texte courant qui peut revenir à la
   ligne : ça ne peut plus jamais se chevaucher. */
.crew--bot.is-2col .crew__list { display: block; columns: 2; column-gap: 18px; }
.crew--bot.is-2col .crew__line { display: block; break-inside: avoid; margin-bottom: 3px; }
.crew--bot.is-2col .crew__dots { display: none; }
.crew--bot.is-2col .crew__k,
.crew--bot.is-2col .crew__v { display: inline; white-space: normal; }
.crew--bot.is-2col .crew__k::after { content: " · "; opacity: 0.5; }
/* FICHE CRÉDITS EN COUCHE FIXE (voir CrewSheet dans main.js).
   Elle remplace le générique défilant, rejeté : le client veut tout voir d'un
   coup, sans mouvement. Posée en fixe, la fiche échappe au rognage de la bande
   et peut se répartir en colonnes. Fond presque opaque : élargie, elle passe
   au-dessus des vignettes voisines et le texte doit rester net (pas de flou de
   fond : le glassmorphism a été écarté de la DA). */
.crewlayer {
  position: fixed; z-index: 66; pointer-events: none;
  opacity: 0; transform: translateY(-6px);
  transition: opacity 0.26s var(--ease), transform 0.3s var(--ease);
}
.crewlayer.is-open { opacity: 1; transform: none; pointer-events: auto; }
.crewsheet {
  font-family: var(--mono); font-size: 9px; line-height: 1.3; letter-spacing: 0.05em;
  text-transform: uppercase; text-align: left;
  padding: 12px 14px 13px;
  background: rgba(5, 5, 5, 0.88);
  box-shadow: 0 0 0 1px rgba(236, 233, 226, 0.05), 0 20px 50px -30px rgba(0, 0, 0, 0.9);
}
/* multi-colonnes : `display: block` obligatoire (le module de colonnes ne
   s'applique pas à un conteneur flex), et chaque ligne reste inssable */
.crewsheet__list { display: block; }
.crewsheet__list .crew__line { display: flex; break-inside: avoid; margin-bottom: 3px; }

/* ============ FICHE DU LECTEUR : DEUX COLONNES ALIGNÉES ============ */
/* Uniquement dans le tiroir du lecteur, qui est un panneau VERTICAL et étroit :
   le filet « POSTE ····· Nom » y serait écrasé, et il n'y a qu'une colonne donc
   aucun ordre de lecture à brouiller.
   ⚠️ La fiche au SURVOL, elle, garde sa mise en page d'origine (filet + colonnes
   CSS). Une version de cette grille y avait été appliquée : elle étalait
   M LE MAUDIT et ses 25 postes sur toute la largeur de l'écran, avec un ordre de
   lecture qui saute de gauche à droite. Retiré le 2026-09-16, ne pas y revenir. */
.player__credlist {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 16px; row-gap: 6px;
}
/* `display: contents` sort la ligne du flux : ses deux enfants deviennent les
   cases de la grille, et les postes restent alignés d'une ligne à l'autre. */
.player__credlist .crew__line { display: contents; }
.player__credlist .crew__dots { display: none; }
.player__credlist .crew__k { align-self: start; }
/* à gauche et non à droite : sur plusieurs lignes, un pavé aligné à droite donne
   un bord gauche en dents de scie, illisible en capitales mono */
.player__credlist .crew__v { text-align: left; line-height: 1.5; }

.crew--top { bottom: calc(100% + 14px); transform: translateY(8px); }
.crew--bot { top: calc(100% + 34px); transform: translateY(-8px); }
.tile.is-crew .crew { opacity: 1; pointer-events: auto; transform: none; }

.crew__head {
  color: var(--accent); opacity: 0.75; letter-spacing: 0.18em; font-size: 9px;
  padding-bottom: 5px; margin-bottom: 3px; border-bottom: 1px solid var(--line);
}
.crew__line { display: flex; align-items: baseline; gap: 7px; }

/* ============ CRÉDIT EN BLOC (poste au-dessus, noms en dessous) ============ */
/* Un filet de pointillés est calculé sur la PREMIÈRE ligne : dès que la valeur se
   replie, il part en fragments et les lignes suivantes s'alignent à droite en
   dents de scie. Quand un poste est tenu par assez de monde pour déborder la
   colonne, on abandonne donc le filet : le poste passe au-dessus, les noms
   dessous, alignés à gauche. C'est la forme d'un vrai générique, et il n'y a plus
   rien qui puisse se casser au repli.
   Sélecteurs dédoublés : `.crewsheet__list .crew__line` impose `display: flex`
   avec une spécificité supérieure, il faut la reprendre à son niveau. */
.crew__line--block,
.crewsheet__list .crew__line--block,
.crew--bot .crew__line--block {
  display: block; break-inside: avoid; margin-bottom: 9px;
}
.crew__line--block .crew__k {
  display: block; margin-bottom: 3px; opacity: 0.75;
}
.crew__line--block .crew__v {
  display: block; text-align: left; line-height: 1.5;
}
/* le poste ne se coupe jamais : c'est la valeur qui cède la place */
.crew__k { color: var(--dim); white-space: nowrap; flex: none; }
/* ligne de conduite pointillée entre le rôle et le nom — repère de lecture
   des fiches techniques, et ça remplit joliment les largeurs variables */
.crew__dots {
  flex: 1; height: 1px; align-self: center; min-width: 8px;
  background-image: radial-gradient(circle, rgba(236, 233, 226, 0.32) 0.5px, transparent 0.5px);
  background-size: 4px 1px; background-repeat: repeat-x;
}
/* ⚠️ La valeur était en `white-space: nowrap` : un poste tenu par huit
   personnes (le cadre du Grünt Festival) sortait donc de la fiche par la
   droite, en travers des vignettes voisines. Elle revient maintenant à la
   ligne, alignée à droite, et les pointillés se rétractent à leur minimum.
   `min-width: 0` est indispensable : sans lui, un élément flex refuse de
   rétrécir sous la largeur de son contenu et rien ne se replierait.
   `overflow-wrap: anywhere` traite le cas du pseudo insécable trop long
   (shootme.alberto_ et compagnie), qu'aucune espace ne permet de couper. */
.crew__v {
  /* Couleur à alpha plutôt qu'`opacity` : `opacity` sur un parent PLAFONNE ses
     enfants, le vert de .crew__me ne pourrait donc jamais être à pleine
     intensité à l'intérieur d'une valeur atténuée. */
  color: rgba(236, 233, 226, 0.82);
  text-align: right; min-width: 0; overflow-wrap: anywhere;
}
/* Le nom de NOCIF, et lui seul, à l'intérieur d'une valeur qui peut en compter
   huit (voir markMine). Avant, c'était la ligne entière qui virait au vert dès
   qu'elle contenait « nocif » : on lui attribuait l'équipe complète. */
.crew__me { color: var(--accent); }

.tile__meta {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; gap: 8px; padding: 34px 12px 10px;
  background: linear-gradient(to top, rgba(6, 6, 6, 0.92), rgba(6, 6, 6, 0.4) 55%, transparent);
  opacity: 0.9; transition: opacity 0.3s;
}
.tile.is-hot .tile__meta { opacity: 1; }
.tile__idx { font-family: var(--mono); font-size: 11px; color: var(--accent); }
/* année : posée en haut à droite de la carte, face à l'index (même mono) —
   donne au filmstrip son poids éditorial (un corpus daté) sans surcharge */
.tile__year { font-family: var(--mono); font-size: 11px; opacity: 0.5; margin-left: auto; align-self: flex-start; letter-spacing: 0.04em; }
.tile__txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tile__artist { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.7; }
.tile__title {
  font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.02em;
  line-height: 1.05; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ============ NOM (contour + révélation) ============ */

/* (le grand logotype du bas a été retiré — direction en attente) */

/* ============ HUD TIMECODE ============ */

.hud {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; justify-content: space-between; gap: 16px;
  padding: 0 calc(var(--pad) + 46px) 16px var(--pad);   /* marge droite pour la roue */
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--dim); pointer-events: none;
}
/* Centré sur l'ÉCRAN, pas sur la place restante. La barre est en
   `space-between` et sa marge droite est plus large (elle dégage la roue des
   réglages) : le segment du milieu tombait 59 px trop à gauche.
   `text-indent` rattrape le demi-espacement de lettres qui traîne après le
   dernier caractère et décale le centre optique. */
.hud__c {
  position: absolute; left: 50%; transform: translateX(-50%);
  text-indent: 0.04em; opacity: 0.55; white-space: nowrap;
}
.rec { color: var(--accent); animation: blink 2s steps(1) infinite; }
@keyframes blink { 0%, 60% { opacity: 1; } 61%, 100% { opacity: 0.15; } }

.scrollbar {
  position: fixed; left: var(--pad); right: calc(var(--pad) + 46px); bottom: 40px; z-index: 40;
  height: 1px; background: var(--line); pointer-events: none;
}
/* remplissage en scaleX (composité GPU) : animer width forçait un layout+paint
   à chaque frame de scroll ; le lerp JS lisse déjà, pas besoin de transition. */
.scrollbar span {
  display: block; height: 100%; width: 100%; background: var(--accent);
  transform: scaleX(0); transform-origin: left center;
}

.hint {
  position: fixed; left: 50%; bottom: 52px; z-index: 41;
  transform: translateX(-50%);
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  text-indent: 0.07em;   /* même correction optique que .hud__c */
  color: var(--dim); pointer-events: none;
  opacity: 0.6; transition: opacity 0.5s ease;
  animation: hintpulse 2.6s ease-in-out infinite;
}
.hint.is-gone { opacity: 0; }
@keyframes hintpulse { 0%, 100% { opacity: 0.35; } 50% { opacity: 0.7; } }

/* ============ RACCORD « CUT » VIGNETTE ⇄ LECTEUR ============ */
/* Le navigateur photographie l'écran avant/après et interpole. On règle ici
   la manière : l'image se déplace et change de format, le reste de la page
   (fond noir du lecteur, bande, HUD) se fait en fondu court derrière. */

::view-transition-group(nocif-hero) {
  animation-duration: 0.62s;
  animation-timing-function: var(--ease);
}
::view-transition-old(nocif-hero), ::view-transition-new(nocif-hero) {
  animation-duration: 0.62s;
  animation-timing-function: var(--ease);
  /* sans ceci les deux photos sont ÉTIRÉES au format de la boîte pendant tout
     le trajet (3/2 → 16/9) : l'image se déforme visiblement. `cover` la fait
     se recadrer proprement, comme un vrai changement de cadre. */
  height: 100%; width: 100%; object-fit: cover;
}
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.4s; }

/* Pendant le raccord, le lecteur doit être à son état final immédiatement :
   ses transitions propres se joueraient en double et la photo d'arrivée
   serait prise alors qu'il est encore transparent. */
body.vt .player { transition: none; }
body.vt .player__stage { transition: none; transform: none; }
body.vt .player__veil { transition: none; }

/* Mode de repli : l'API YouTube est inaccessible (bloqueur de publicités,
   réseau). On affiche une intégration simple avec les contrôles de YouTube —
   nos contrôles maison ne pourraient rien piloter, et le calque qui capture
   les clics empêcherait d'utiliser ceux de YouTube. */
.player.is-fallback .pctl,
.player.is-fallback .player__hit,
.player.is-fallback .player__big,
.player.is-fallback .player__veil,
.player.is-fallback .player__spin { display: none; }

/* ============ PLAYER ============ */

.player {
  position: fixed; inset: 0; z-index: 100; background: rgba(3, 3, 3, 0.98);
  display: grid; place-items: center; opacity: 0; pointer-events: none;
  transition: opacity 0.4s;
}
.player.is-open { opacity: 1; pointer-events: auto; }
.player__stage {
  /* l'œuvre prend le maximum, limité par la hauteur ; --vrn = ratio numérique
     de l'œuvre, posé en JS (applyRatio) → fonctionne aussi en vertical */
  width: min(94vw, calc((88vh - 62px) * var(--vrn, 1.7778)));
  transform: scale(0.97); transition: transform 0.5s var(--ease);
}
.player.is-open .player__stage { transform: scale(1); }
/* aspect-ratio piloté en JS (applyRatio) selon le ratio réel de l'œuvre */
.player__screen { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; overflow: hidden; }
/* l'iframe déborde pour COUVRIR la boîte → aucune bande noire */
.player__frame {
  position: absolute; top: 50%; left: 50%; width: 100%; height: 100%;
  transform: translate(-50%, -50%);
}
/* `pointer-events: none` : ceinture ET bretelles avec .player__hit. Le
   moindre survol qui atteindrait l'iframe ferait apparaître le bandeau titre
   de YouTube — rogné par le débordement du cadre. Rien ne doit y arriver. */
.player__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; pointer-events: none; }
/* SAUF en mode de repli : là, nos contrôles sont masqués et ce sont ceux de
   YouTube qui servent — les rendre insensibles au clic rendrait la vidéo
   impossible à lancer. */
.player.is-fallback .player__frame iframe { pointer-events: auto; }

/* VOILE DE PAUSE / CHARGEMENT.
   YouTube réaffiche titre, chaîne, « Plus de vidéos » et logo dès que la
   lecture n'est pas en cours. Comme le cadre déborde pour rogner les bandes
   noires, cet habillage apparaît COUPÉ — d'où le voile, qui recouvre toute
   l'image à ce moment-là (voir setPlayState).

   FONDU ASYMÉTRIQUE, et c'est le point important : il monte INSTANTANÉMENT
   (l'habillage YouTube, lui, est dessiné dans l'image qui suit la pause : un
   fondu de 0,3 s le laissait voir en clair), et ne redescend qu'en douceur,
   pour que la reprise de lecture reste un fondu et non un à-coup. */
.player__veil {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background: #050505; overflow: hidden;
  opacity: 0; transition: opacity 0.32s var(--ease);
}
.player.is-paused .player__veil { opacity: 1; transition: none; }
/* poster = miniature du projet, plutôt qu'un écran noir vide au chargement */
.player__veil img {
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0.4; filter: blur(3px) saturate(0.75);
  transform: scale(1.06);
}

/* indicateur de chargement (état buffering) */
.player__spin {
  position: absolute; top: 50%; left: 50%; z-index: 6;
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border: 1.5px solid rgba(236, 233, 226, 0.18);
  border-top-color: var(--accent); border-radius: 50%;
  opacity: 0; pointer-events: none; transition: opacity 0.2s;
}
.player.is-buffering .player__spin { opacity: 1; animation: pspin 0.8s linear infinite; }
@keyframes pspin { to { transform: rotate(360deg); } }

/* capte tous les clics : aucune interaction ne part vers YouTube */
.player__hit { position: absolute; inset: 0; z-index: 2; background: transparent; }

/* gros bouton lecture quand en pause */
.player__big {
  position: absolute; inset: 0; z-index: 5; display: grid; place-items: center;
  pointer-events: none; opacity: 0; transition: opacity 0.3s var(--ease);
}
.player__big svg {
  width: 74px; height: 74px; padding: 22px; box-sizing: border-box;
  color: var(--ink); background: rgba(0, 0, 0, 0.45); border-radius: 50%;
  border: 1px solid rgba(236, 233, 226, 0.25);
  transform: scale(0.9); transition: transform 0.3s var(--ease);
}
.player.is-paused .player__big { opacity: 1; }
.player.is-paused .player__big svg { transform: scale(1); }

/* ============ CONTRÔLES MAISON ============ */

.pctl {
  display: flex; align-items: center; gap: 13px; padding: 14px 2px 0;
  transition: opacity 0.35s ease;
}
.player.is-idle .pctl { opacity: 0; }

.pctl__btn { color: var(--dim); display: grid; place-items: center; padding: 3px; transition: color 0.2s; }
.pctl__btn:hover { color: var(--accent); }
.pctl__btn .i-pause, .pctl__btn.is-playing .i-play { display: none; }
.pctl__btn.is-playing .i-pause { display: block; }
.pctl__btn .i-muted, .pctl__btn.is-muted .i-vol { display: none; }
.pctl__btn.is-muted .i-muted { display: block; }

.pctl__time {
  font-family: var(--mono); font-size: 11px; color: var(--dim);
  font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
}
.pctl__time--dur { opacity: 0.6; }

.pctl__bar {
  position: relative; flex: 1; height: 3px; cursor: pointer;
  background: rgba(236, 233, 226, 0.14);
}
.pctl__bar::before { content: ""; position: absolute; inset: -9px 0; }  /* zone de clic confortable */
.pctl__buf { position: absolute; top: 0; bottom: 0; left: 0; width: 0; background: rgba(236, 233, 226, 0.22); }
.pctl__fill { position: absolute; top: 0; bottom: 0; left: 0; width: 0; background: var(--accent); }
.pctl__head {
  position: absolute; top: 50%; left: 0; width: 10px; height: 10px;
  margin: -5px 0 0 -5px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 12px rgba(194, 255, 0, 0.7);
  transform: scale(0); transition: transform 0.2s var(--ease);
}
.pctl__bar:hover .pctl__head, .pctl__bar.is-scrub .pctl__head { transform: scale(1); }

/* bulle de temps au survol de la barre : on sait où on va avant de cliquer */
.pctl__tip {
  position: absolute; bottom: 16px; left: 0; transform: translateX(-50%);
  font-family: var(--mono); font-size: 10px; color: var(--ink);
  background: rgba(10, 10, 10, 0.92); border: 1px solid var(--line);
  padding: 3px 7px; white-space: nowrap; pointer-events: none;
  opacity: 0; transition: opacity 0.2s;
}
.pctl__bar:hover .pctl__tip, .pctl__bar.is-scrub .pctl__tip { opacity: 1; }

/* volume : barre qui se déplie au survol de l'icône */
.pctl__vol { display: flex; align-items: center; gap: 6px; }
.pctl__volbar {
  position: relative; width: 0; height: 3px; overflow: hidden;
  background: rgba(236, 233, 226, 0.16); cursor: pointer;
  transition: width 0.28s var(--ease);
}
.pctl__vol:hover .pctl__volbar, .pctl__volbar.is-scrub { width: 58px; }
.pctl__volfill { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; background: var(--accent); }
.player__bar {
  display: flex; align-items: baseline; gap: 14px; padding-top: 12px;
  text-transform: uppercase; font-size: 11px; letter-spacing: 0.05em;
}
.player__idx { font-family: var(--mono); color: var(--accent); }
.player__meta { display: flex; gap: 12px; align-items: baseline; flex: 1; }
.player__artist { opacity: 0.6; }
.player__title { font-weight: 600; }
.player__year { font-family: var(--mono); opacity: 0.55; letter-spacing: 0.06em; color: var(--accent); }
.player__type { font-family: var(--mono); opacity: 0.5; letter-spacing: 0.1em; }
.player__close { position: absolute; top: 20px; right: var(--pad); font-size: 18px; padding: 10px; opacity: 0.7; transition: opacity 0.2s, color 0.2s; }
.player__close:hover { opacity: 1; color: var(--accent); }
.player__nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  font-size: 22px; padding: 20px; opacity: 0.4; transition: opacity 0.2s, color 0.2s;
}
.player__nav:hover { opacity: 1; color: var(--accent); }
.player__nav--prev { left: max(8px, calc((100vw - min(88vw, 1280px)) / 2 - 60px)); }
.player__nav--next { right: max(8px, calc((100vw - min(88vw, 1280px)) / 2 - 60px)); }

/* ============ LECTEUR : FLÈCHES NOMMÉES ============ */
/* « précédent / suivant » ne dit rien. Le titre de destination apparaît au
   survol : sur un portfolio, savoir ce qu'on va voir décide du clic. */
.player__nav { display: flex; align-items: center; gap: 12px; }
.player__navarrow { font-size: 22px; line-height: 1; }
.player__navlabel {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.14em;
  text-transform: uppercase; max-width: 0; overflow: hidden; white-space: nowrap;
  opacity: 0; transition: max-width 0.45s var(--ease), opacity 0.3s var(--ease);
}
.player__nav:hover .player__navlabel, .player__nav:focus-visible .player__navlabel { max-width: 30ch; opacity: 0.75; }
.player__nav--next { flex-direction: row-reverse; }

/* ============ LECTEUR : CARTON DE FIN ============ */
/* La lecture se figeait sur le voile de pause, à charge du visiteur de deviner
   quoi faire. On lui propose la suite : c'est le moment où il est le plus
   disposé à regarder autre chose. */
.player__end {
  position: absolute; inset: 0; z-index: 7;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px; padding: var(--pad);
  opacity: 0; pointer-events: none; transition: opacity 0.4s var(--ease);
}
.player.is-ended .player__end { opacity: 1; pointer-events: auto; }
.player.is-ended .player__big { opacity: 0; }
.player__endk {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.3em;
  text-transform: uppercase; color: var(--accent);
}
.player__endrow { display: flex; align-items: center; gap: clamp(16px, 3vw, 44px); flex-wrap: wrap; justify-content: center; }
.player__endbtn {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--dim); padding: 12px 18px; border: 1px solid var(--line);
  transition: color 0.25s, border-color 0.25s;
}
.player__endbtn:hover, .player__endbtn:focus-visible { color: var(--accent); border-color: var(--accent); }
.player__endnext {
  display: flex; align-items: center; gap: 14px; padding: 8px; text-align: left;
  border: 1px solid transparent; transition: border-color 0.3s var(--ease);
}
.player__endnext:hover, .player__endnext:focus-visible { border-color: var(--accent); }
.player__endnext img { width: clamp(96px, 12vw, 168px); aspect-ratio: 3 / 2; object-fit: cover; }
.player__endmeta { display: flex; flex-direction: column; gap: 3px; }
.player__endlabel {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--accent); opacity: 0.85;
}
.player__endartist { font-size: 11px; text-transform: uppercase; opacity: 0.6; letter-spacing: 0.05em; }
.player__endtitle { font-size: 15px; font-weight: 600; text-transform: uppercase; }

/* ============ LECTEUR : TIROIR DES CRÉDITS ============ */
.player__credbtn {
  display: flex; align-items: center; gap: 7px;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--dim); padding: 4px 0;
  transition: color 0.25s;
}
.player__credbtn:hover, .player.is-credits .player__credbtn { color: var(--accent); }
.player__credkey {
  font-size: 8px; padding: 2px 5px; border: 1px solid var(--line);
  letter-spacing: 0.06em; opacity: 0.7;
}
.player__keys {
  font-family: var(--mono); font-size: 8px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--dim); opacity: 0.45;
  padding-top: 8px; transition: opacity 0.35s ease;
}
.player.is-idle .player__keys { opacity: 0; }

.player__credits {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 8;
  width: min(420px, 88vw); padding: clamp(60px, 9vh, 96px) clamp(20px, 2.2vw, 34px) 30px;
  background: rgba(5, 5, 5, 0.97);
  border-left: 1px solid var(--line);
  overflow-y: auto; overscroll-behavior: contain;
  transform: translateX(100%); transition: transform 0.45s var(--ease);
}
.player.is-credits .player__credits { transform: none; }
/* l'image recule d'un cran pendant la lecture de la fiche, sans jamais bouger
   de place : décaler la scène fausserait le calcul de format (voir applyRatio) */
.player.is-credits .player__stage { opacity: 0.45; transition: opacity 0.4s var(--ease); }
.player__credhead {
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--accent); opacity: 0.8;
}
.player__credtitle {
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em;
  margin: 8px 0 16px; padding-bottom: 14px; border-bottom: 1px solid var(--line);
}
.player__credlist {
  font-family: var(--mono); font-size: 9px; line-height: 1.3; letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* ============ À PROPOS ============ */

.about {
  position: fixed; inset: 0; z-index: 90; background: var(--bg);
  transform: translateY(100%); transition: transform 0.6s var(--ease); overflow-y: auto;
}
.about.is-open { transform: translateY(0); }
.about__close { position: absolute; top: 20px; right: var(--pad); font-size: 18px; padding: 10px; opacity: 0.7; }
.about__close:hover { opacity: 1; color: var(--accent); }
.about__inner { padding: 12vh var(--pad) 8vh; max-width: 1100px; }
.about__kicker { font-family: var(--mono); font-size: 11px; letter-spacing: 0.15em; color: var(--accent); opacity: 0.8; margin-bottom: 18px; }
.about__name { font-size: clamp(64px, 14vw, 200px); font-weight: 600; line-height: 0.9; letter-spacing: 0.01em; margin-bottom: 6vh; }
.about__cols { display: grid; grid-template-columns: 2fr 1fr; gap: clamp(24px, 5vw, 80px); }
.about__bio p { font-size: clamp(16px, 1.6vw, 21px); line-height: 1.5; margin-bottom: 1em; max-width: 34em; }
.about__placeholder { opacity: 0.4; font-style: italic; }
.about__side h3 { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.12em; opacity: 0.45; margin: 24px 0 8px; }
.about__side h3:first-child { margin-top: 0; }
.about__mail { font-size: 15px; }
.about__mail:hover, .about__socials a:hover { color: var(--accent); }
.about__socials li { margin-bottom: 4px; }
.about__copy { font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; opacity: 0.35; margin-top: 28px; }

/* métiers : trois lignes sèches sous la bio */
.about__what { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-top: 26px; }
.about__what li {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--dim);
}
.about__what li::before { content: "—  "; color: var(--accent); opacity: 0.7; }

/* ============ BANDEAU CLIENTS ============ */
/* Deux bandes en sens inverse. Le défilement est une animation CSS pure (donc
   composée par le GPU, aucun JS par image) : la piste contient deux fois la
   même liste et glisse de la moitié de sa largeur, si bien que la fin coïncide
   pixel pour pixel avec le début — la boucle ne se voit pas. */
.clients { margin-top: clamp(48px, 9vh, 110px); }
.about__side-h {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.12em; opacity: 0.45; margin-bottom: 18px;
}
.clients__band {
  display: grid; gap: 10px;
  /* les noms n'entrent ni ne sortent brutalement : ils naissent et meurent
     dans le noir, comme le reste du site */
  -webkit-mask: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
          mask: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}
.clients__row { overflow: hidden; }
.clients__track {
  display: flex; align-items: center; gap: 26px; width: max-content;
  animation: clientsSlide 46s linear infinite;
}
.clients__row--rev .clients__track { animation-duration: 61s; animation-direction: reverse; }
/* au survol, la bande s'arrête : on peut lire un nom précis */
.clients:hover .clients__track { animation-play-state: paused; }
.clients__i {
  display: flex; align-items: center;
  opacity: 0.6; white-space: nowrap;
  transition: opacity 0.25s var(--ease), color 0.25s var(--ease);
}
.clients__i:hover { opacity: 1; }
/* Logo. `brightness(0)` écrase toute la couleur en noir, `invert(1)` la retourne
   en blanc : n'importe quel fichier (couleur, noir, dégradé) ressort en blanc pur
   sur le fond noir, sans qu'il faille préparer une version blanche de chacun.
   La hauteur est commune à tous, la largeur suit : c'est l'alignement optique
   qui fait tenir une bande de logos hétéroclites. */
.clients__i img {
  height: clamp(22px, 2.5vw, 34px); width: auto; max-width: 210px;
  object-fit: contain; display: block;
  filter: brightness(0) invert(1);
}
.clients__i:hover img { filter: brightness(0) invert(1) drop-shadow(0 0 14px rgba(194, 255, 0, 0.45)); }
/* repli quand aucun fichier n'existe : le nom, dans la fonte du site */
.clients__i--text {
  font-size: clamp(19px, 2.4vw, 34px); font-weight: 600; letter-spacing: 0.01em;
  color: var(--ink);
}
.clients__i--text:hover { color: var(--accent); }
.clients__sep { color: var(--accent); opacity: 0.35; font-size: 9px; }
@keyframes clientsSlide { to { transform: translate3d(-50%, 0, 0); } }


/* ============ RÉGLAGES (roue + panneau) ============ */

.settings { position: fixed; right: 14px; bottom: 12px; z-index: 80; }

.settings__gear {
  display: grid; place-items: center; width: 34px; height: 34px;
  color: var(--dim); border: 1px solid var(--line); border-radius: 50%;
  background: rgba(10, 10, 10, 0.6);
  transition: color 0.25s, border-color 0.25s, transform 0.5s var(--ease);
}
.settings__gear:hover { color: var(--accent); border-color: rgba(194, 255, 0, 0.4); }
.settings.is-open .settings__gear { transform: rotate(90deg); color: var(--accent); border-color: rgba(194, 255, 0, 0.4); }

.settings__panel {
  position: absolute; right: 0; bottom: 46px; width: 260px;
  background: rgba(11, 11, 11, 0.96); border: 1px solid var(--line);
  padding: 16px; display: grid; gap: 12px;
  opacity: 0; transform: translateY(8px) scale(0.98); transform-origin: bottom right;
  pointer-events: none; transition: opacity 0.3s, transform 0.3s var(--ease);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
}
.settings.is-open .settings__panel { opacity: 1; transform: none; pointer-events: auto; }

.settings__title {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--accent); opacity: 0.8;
  padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.settings__row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
}

/* segment (langue) */
.seg { display: flex; border: 1px solid var(--line); border-radius: 99px; overflow: hidden; }
.seg button {
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em;
  padding: 4px 10px; opacity: 0.5; transition: opacity 0.2s, color 0.2s, background 0.2s;
}
.seg button:hover { opacity: 1; }
.seg button.is-active { opacity: 1; color: var(--bg); background: var(--accent); }

/* interrupteur */
.toggle {
  width: 34px; height: 18px; border-radius: 99px; position: relative;
  border: 1px solid var(--line); background: rgba(255, 255, 255, 0.04);
  transition: background 0.25s, border-color 0.25s;
}
.toggle__dot {
  position: absolute; top: 50%; left: 2px; transform: translateY(-50%);
  width: 12px; height: 12px; border-radius: 50%; background: var(--dim);
  transition: transform 0.25s var(--ease), background 0.25s;
}
.toggle.is-on { background: var(--accent-soft); border-color: rgba(194, 255, 0, 0.5); }
.toggle.is-on .toggle__dot { transform: translateY(-50%) translateX(16px); background: var(--accent); }

/* ============ RESPONSIVE ============ */

@media (max-width: 720px) {
  :root { --tile-w: 74vw; }
  .header { grid-template-columns: 1fr auto; grid-auto-rows: auto; row-gap: 12px; }
  .header__filters { grid-column: 1 / -1; justify-self: start; width: 100%; }
  .header__role { display: none; }
  /* La régie passe sur toute la largeur et défile latéralement plutôt que de se
     comprimer : des vumètres écrasés ne veulent plus rien dire. */
  .srcsel { width: 100%; }
  .filters { overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
  .filters::-webkit-scrollbar { display: none; }
  .filter { min-width: 88px; padding: 8px 10px 7px; }
  .hud__c { display: none; }
  .player__nav { display: none; }

  /* la bio tenait dans une colonne de 178 px sur un écran de 375 : illisible */
  .about__cols { grid-template-columns: 1fr; }
  .about__inner { padding-top: 14vh; }

  /* la barre de contrôles dépassait de sa boîte (353 px pour 342 disponibles) :
     le volume se règle avec les boutons de l'appareil, on le retire ici */
  .pctl { gap: 9px; }
  .pctl__vol { display: none; }
  .player__bar { flex-wrap: wrap; row-gap: 4px; }

  /* plus de place au-dessus/en dessous des vignettes pour les fiches crédits */
  .strip { padding: clamp(150px, 34vh, 300px) var(--pad); }
  /* une colonne étroite ne supporte pas deux colonnes de crédits */
  .crew--bot.is-2col .crew__list { columns: 1; }
  /* Les grosses équipes (25 postes) ne tiennent pas sur un écran de téléphone.
     Plutôt que de tronquer les crédits — c'est le contenu qui compte ici — la
     fiche devient défilable. La bande étant en `touch-action: pan-y`, le
     défilement vertical du doigt est bien transmis à la fiche. */
  .crew--bot { max-height: 26vh; overflow-y: auto; overscroll-behavior: contain; }
  .crew--bot::-webkit-scrollbar { width: 2px; }
  .crew--bot::-webkit-scrollbar-thumb { background: var(--accent); opacity: 0.5; }
}

/* ============ APPAREILS SANS SURVOL (tactile) ============ */
/* Toute la fiche crédits reposait sur le survol : au doigt, la flèche ▼ ne
   s'affichait jamais et les crédits étaient tout simplement inaccessibles.
   Ici elle est visible en permanence et s'ouvre à l'appui (voir bindCrew). */
@media (hover: none) {
  .tile__crewbtn { opacity: 1; pointer-events: auto; }
  .tile__crewcta { padding: 10px 16px; }   /* cible d'appui confortable */
  .tile__frame { opacity: 1; }               /* le cadre ne peut pas être « survolé » */
}

/* reduced-motion : ce poste l'a activé en permanence → on adoucit (JS),
   on n'éteint que le grain animé. */
@media (prefers-reduced-motion: reduce) {
  .grain { animation: none; }
  .loader.is-done { transform: none; }   /* sortie en fondu plutôt qu'en glissement */
  .loader__enter { animation: none; }    /* pas de pulsation du bouton ENTER */
  .loader__log li { animation: none; }   /* les lignes s'affichent sans glisser */
  /* bandeau clients : plus de défilement, la liste se replie et reste lisible */
  .clients__track { animation: none; flex-wrap: wrap; width: auto; }
  .clients__row { overflow: visible; }
  .strip.is-rewinding .tile { --hs: 1; }   /* pas de resserrement au rembobinage */
}
