/* =============================================================================
   NOXEL // PIERRE LORET — système de polarité
   -----------------------------------------------------------------------------
   Un seul squelette, deux dialectes. La polarité est portée par <html data-pol>.

     data-pol="neg"  →  NOXEL          encre blanche sur noir, pixel, brut, rapide
     data-pol="pos"  →  PIERRE LORET   encre noire sur papier, éditorial, lent

   RÈGLE : ne jamais écrire une couleur en dur dans une page. Tout passe par les
   variables ci-dessous, sinon la page ne sait plus s'inverser.
   ============================================================================= */

/* =============================================================================
   TRANSITIONS DE PAGE NATIVES (View Transitions API)
   -----------------------------------------------------------------------------
   Le navigateur photographie la page sortante et l'entrante, puis anime de
   l'une à l'autre. Ça remplace le rideau #wipe, qui reste en repli.

   ⚠️ Ne marche QU'EN http(s). Ouvert en file:// l'origine est opaque, le
   navigateur refuse la transition et common.js retombe sur le rideau.
   Pour la voir : `python3 -m http.server 8080` puis localhost:8080.

   On ne nomme QUE .field. Tout le reste (nav, #prog, #rail, #hud, #cursor) est
   en mix-blend-mode:difference : un élément nommé est extrait de son contexte
   de fusion et virerait au blanc pendant la transition. Laissés dans le fond,
   ils se fondent avec eux-mêmes, donc invisiblement.
   ============================================================================= */
@view-transition { navigation: auto; }

/* le champ porte la polarité de la page : c'est lui qui morphe d'une page à l'autre */
.field { position:fixed; inset:0; z-index:0; background:var(--bg);
  view-transition-name:field; }
::view-transition-group(field) { animation-duration:.62s;
  animation-timing-function:cubic-bezier(.62,.04,.16,1); }

/* le fond prend le dialecte de la page d'ARRIVÉE : c'est son CSS qui pilote */
[data-pol="pos"]::view-transition-old(root),
[data-pol="pos"]::view-transition-new(root) {
  animation-duration:.6s; animation-timing-function:cubic-bezier(.62,.04,.16,1); }
[data-pol="neg"]::view-transition-old(root),
[data-pol="neg"]::view-transition-new(root) {
  animation-duration:.32s; animation-timing-function:steps(6); }

/* le curseur ne doit pas être photographié : il resterait figé dans l'image */
.vt-out #cursor, .vt-out #focus, .vt-out .ghost { opacity:0 !important; }

@property --seam { syntax:'<percentage>'; inherits:true; initial-value:50%; }

/* ---------- polarité négative (par défaut) ---------- */
:root,
[data-pol="neg"] {
  --bg:#0a0a0a; --bg-2:#0f0f0f;
  --panel:#141414; --panel-2:#1c1c1c;
  --line:#2b2b2b; --line-2:#3a3a3a;
  --ink:#f4f4f4; --dim:#9c9c9c; --dim-2:#c4c4c4;
  --pure:#ffffff;   /* l'encre absolue de cette polarité */
  --anti:#0a0a0a;   /* le fond absolu : texte posé sur --pure */

  --display:"Press Start 2P", monospace;
  --display-w:400; --display-ls:1px; --display-lh:1.12; --ds:.60;
  --corner:5px;     /* crans pixel : signature Noxel */

  --ease:cubic-bezier(.2,.9,.15,1);
  --dur:.26s; --dur-2:.42s;

  /* dialecte du bouton principal — voir .btn--ghost */
  --ghost-bg:var(--pure); --ghost-ink:var(--anti);
  --ghost-pad:14px 24px; --ghost-line:0; --ghost-gap:16px;
}

/* ---------- polarité positive ---------- */
[data-pol="pos"] {
  --bg:#f2f0ec; --bg-2:#eae7e1;
  --panel:#e9e6e0; --panel-2:#e1ddd5;
  --line:#d6d2ca; --line-2:#bdb8ae;
  --ink:#0a0a0a; --dim:#5f5c58; --dim-2:#3c3a37;
  --pure:#0a0a0a;
  --anti:#f2f0ec;

  --display:"Space Grotesk", system-ui, sans-serif;
  --display-w:700; --display-ls:-2.5px; --display-lh:.94; --ds:1;
  --corner:0px;     /* le pro n'est pas cranté */

  --ease:cubic-bezier(.62,.04,.16,1);
  --dur:.5s; --dur-2:.82s;

  /* le pro ne crie pas : son bouton principal se réduit à un filet */
  --ghost-bg:transparent; --ghost-ink:var(--ink);
  --ghost-pad:7px 0; --ghost-line:1px; --ghost-gap:22px;
}

:root { --maxw:1180px; --gut:clamp(18px,5vw,58px); }

/* =============================================================================
   BASE
   ============================================================================= */
*, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
html { scroll-behavior:smooth; background:var(--bg);
  /* iOS gonfle les textes en paysage sans cette ligne ; et le voile gris posé
     sur chaque lien tapé n'a rien à faire dans une interface dessinée. */
  -webkit-text-size-adjust:100%; -webkit-tap-highlight-color:transparent; }
body {
  font-family:"Space Grotesk", system-ui, sans-serif;
  background:var(--bg); color:var(--ink); line-height:1.65;
  overflow-x:hidden; -webkit-font-smoothing:antialiased; cursor:none;
  transition:background .6s var(--ease), color .6s var(--ease);
}
a { color:inherit; text-decoration:none; }
/* ⚠️ cursor:none sur body ne suffit PAS. La feuille de style du navigateur pose
   cursor:pointer sur tout lien, et elle gagne contre l'héritage : la main du
   système réapparaissait sur chaque lien, chaque carte et chaque bouton, juste
   à côté du curseur dessiné. On le coupe partout où il ressurgit. */
a, a:link, label, summary, select, input, textarea,
[role="button"], [data-hov], [data-nav], [data-cursor] { cursor:none; }
img { max-width:100%; display:block; }
/* appearance:none est INDISPENSABLE. Sans lui, macOS garde le chrome natif du
   <button> (coins arrondis, fond clair, dégradé) que background/border ne
   suppriment pas : les boutons SON et LANGUE de la nav ressortaient en pastilles
   grises au milieu d'une interface entièrement à angles droits. */
button { appearance:none; -webkit-appearance:none;
  background:none; border:0; border-radius:0; padding:0; color:inherit; font:inherit;
  letter-spacing:inherit; text-align:inherit; cursor:none; }

/* clavier : un état de focus net, jamais au survol souris */
:focus-visible { outline:2px solid var(--pure); outline-offset:3px; }
nav#nav :focus-visible, #rail :focus-visible { outline-color:#fff; }

/* l'interface native (barres, formulaires) suit la polarité de la page */
html[data-pol="neg"] { color-scheme:dark; }
html[data-pol="pos"] { color-scheme:light; }
html { scrollbar-width:thin; scrollbar-color:var(--line-2) var(--bg); }
::-webkit-scrollbar { width:11px; height:11px; }
::-webkit-scrollbar-track { background:var(--bg); }
::-webkit-scrollbar-thumb { background:var(--line-2); border:3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background:var(--dim); }
::selection { background:var(--pure); color:var(--anti); }
/* Lisible par les moteurs et les lecteurs d'écran, invisible à l'œil.
   clip-path plutôt que display:none : ce dernier retire l'élément de
   l'arbre d'accessibilité, donc Google et VoiceOver ne le verraient pas. */
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }

.mono { font-family:"DM Mono", ui-monospace, monospace; }
.pixel { font-family:"Press Start 2P", monospace; }
.dim { color:var(--dim); }

/* crans pixel : actifs en négatif, neutralisés en positif via --corner:0 */
.pix { clip-path:polygon(
  0 var(--corner), var(--corner) var(--corner), var(--corner) 0,
  calc(100% - var(--corner)) 0, calc(100% - var(--corner)) var(--corner), 100% var(--corner),
  100% calc(100% - var(--corner)), calc(100% - var(--corner)) calc(100% - var(--corner)), calc(100% - var(--corner)) 100%,
  var(--corner) 100%, var(--corner) calc(100% - var(--corner)), 0 calc(100% - var(--corner))); }

/* =============================================================================
   TEXTURE DE FOND — dense en négatif, quasi nulle en positif
   ============================================================================= */
.tex { position:fixed; inset:0; z-index:1; pointer-events:none; }
.tex .grain {
  position:absolute; inset:0;
  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='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
[data-pol="neg"] .tex .grain { opacity:.06; mix-blend-mode:screen; }
[data-pol="pos"] .tex .grain { opacity:.032; mix-blend-mode:multiply; }

.tex .weave { position:absolute; inset:0; }
[data-pol="neg"] .tex .weave {
  opacity:.5;
  background:repeating-linear-gradient(to bottom, rgba(255,255,255,.04) 0 1px, transparent 1px 3px); }
[data-pol="pos"] .tex .weave {
  opacity:1;
  background-image:repeating-linear-gradient(to right, rgba(10,10,10,.045) 0 1px, transparent 1px calc(100% / 12)); }

/* =============================================================================
   CURSEUR — carré en négatif, réticule de cadrage en positif
   ============================================================================= */
#cursor {
  position:fixed; top:0; left:0; width:13px; height:13px; background:var(--pure);
  z-index:9999; pointer-events:none; mix-blend-mode:difference;
  transition:width .16s var(--ease), height .16s var(--ease), opacity .2s; }
[data-pol="neg"] #cursor { background:#fff; }
[data-pol="pos"] #cursor { background:#fff; }
/* CREUX, jamais plein. En mix-blend-mode:difference, un carré blanc de 38 px
   posé sur une surface claire (bouton plein, tuile d'icône) devient un pavé noir
   opaque qui masque la lettre en dessous. L'anneau donne le même repère sans
   jamais cacher le contenu. */
#cursor.big, [data-pol] #cursor.big {
  width:38px; height:38px; background:none; box-shadow:inset 0 0 0 1px #fff; }

/* la rémanence se retire dès qu'on vise quelque chose : cinq carrés blancs
   traînant sur un bouton plein produisaient la même occultation */
html.hovering .ghost { opacity:0 !important; }
/* ⚠️ La croix se dessine en DEUX pseudo-éléments centrés en pourcentage, pas
   en décalages fixes. Sur la polarité positive, .aim est permanent et .big
   s'ajoute au survol ; or « [data-pol] #cursor.big » l'emporte en spécificité
   et impose 38×38. Les anciens décalages, calés en dur sur un élément de
   1×30, plaçaient alors la croix hors du carré. En 50 % / translate, elle
   reste centrée quelle que soit la taille de l'état. */
#cursor.aim { width:30px; height:30px; background:none; }
#cursor.aim::before, #cursor.aim::after {
  content:""; position:absolute; top:50%; left:50%; background:#fff; }
#cursor.aim::before { width:30px; height:1px; transform:translate(-50%,-50%); }
#cursor.aim::after  { width:1px; height:30px; transform:translate(-50%,-50%); }
/* Sur un élément cliquable, la croix et l'anneau se superposaient et les deux
   traits barraient le mot qu'on visait. Le cadre suffit à dire « c'est ici » :
   la croix se retire au survol et revient dès qu'on quitte l'élément. */
#cursor.aim.big::before, #cursor.aim.big::after { display:none; }
#cursor .lab {
  position:absolute; left:50%; top:calc(100% + 12px); transform:translateX(-50%);
  font-family:"DM Mono", monospace; font-size:10px; letter-spacing:2px; white-space:nowrap;
  color:#fff; opacity:0; transition:opacity .2s; text-transform:uppercase; }
#cursor.has-lab .lab { opacity:1; }
/* rémanence : uniquement en négatif */
.ghost { position:fixed; top:0; left:0; width:13px; height:13px; background:#fff;
  z-index:9998; pointer-events:none; mix-blend-mode:difference; opacity:0; }
[data-pol="pos"] .ghost { display:none; }
@media (hover:none) { body { cursor:auto; } #cursor, .ghost { display:none; } }

/* =============================================================================
   AMORCE (boot) + BASCULE DE POLARITÉ (transition de page)
   ============================================================================= */
#leader { position:fixed; inset:0; z-index:10000; background:var(--bg); display:grid; place-items:center;
  transition:opacity .5s steps(4), visibility .5s; }
#leader.done { opacity:0; visibility:hidden; }
.lead-ring { position:relative; width:min(230px,54vw); aspect-ratio:1; display:grid; place-items:center; }
.lead-ring .ring, .lead-ring .ring2 { position:absolute; border:1px solid var(--line-2); border-radius:50%; }
.lead-ring .ring { inset:0; } .lead-ring .ring2 { inset:20%; }
.lead-ring .h, .lead-ring .v { position:absolute; background:var(--line-2); }
.lead-ring .h { left:-26%; right:-26%; top:50%; height:1px; }
.lead-ring .v { top:-26%; bottom:-26%; left:50%; width:1px; }
.lead-ring .sweep { position:absolute; inset:0; border-radius:50%;
  background:conic-gradient(rgba(255,255,255,.12) 0deg 84deg, transparent 84deg);
  animation:sweep .84s steps(7) infinite; }
[data-pol="pos"] .lead-ring .sweep { background:conic-gradient(rgba(10,10,10,.09) 0deg 84deg, transparent 84deg); }
@keyframes sweep { to { transform:rotate(360deg); } }
#cnt { position:relative; z-index:2; font-family:"Press Start 2P", monospace;
  font-size:clamp(38px,12vw,64px); color:var(--ink); }
#leader .cap { position:absolute; bottom:15%; left:0; right:0; text-align:center;
  font-family:"DM Mono", monospace; font-size:11px; letter-spacing:6px; color:var(--dim); }

/* Porte d'entrée de l'amorce — voir le bloc PORTE D'ENTRÉE dans common.js.
   Le bouton prend la place du compteur, au centre du viseur : c'est le seul
   endroit où l'œil est déjà posé. Il ne s'affiche que si le son est armé. */
#leader.gate #cnt { display:none; }
/* le voile ENTIER est la porte : le curseur doit le dire partout, pas
   seulement au-dessus du bouton */
#leader.gate { cursor:pointer; }
#leader .lead-go { position:absolute; z-index:2; display:inline-flex; align-items:center;
  gap:11px; padding:15px 27px; background:var(--pure); color:var(--anti);
  font-family:"DM Mono", monospace; font-size:12px; letter-spacing:4px;
  text-transform:uppercase; cursor:pointer;
  animation:goIn .6s var(--ease) both; }
#leader .lead-go i { width:0; height:0; flex:none;
  border-left:8px solid currentColor; border-top:5px solid transparent;
  border-bottom:5px solid transparent; }
#leader .lead-go::after { content:""; position:absolute; inset:-9px; z-index:-1;
  box-shadow:inset 0 0 0 1px var(--line-2); animation:goHalo 1.9s var(--ease) infinite; }
@keyframes goIn { from { opacity:0; transform:scale(.94); } to { opacity:1; transform:none; } }
@keyframes goHalo { 0%,100% { opacity:.35; transform:scale(1); } 50% { opacity:1; transform:scale(1.045); } }
@media (prefers-reduced-motion:reduce) {
  #leader .lead-go { animation:none; }
  #leader .lead-go::after { animation:none; opacity:.6; }
}

#wipe { position:fixed; inset:0; z-index:9990; display:grid; place-items:center;
  opacity:0; visibility:hidden; transition:opacity .3s steps(5), visibility .3s;
  background:#0a0a0a; }
#wipe.on { opacity:1; visibility:visible; }
#wipe .w { font-family:"Press Start 2P", monospace; font-size:clamp(13px,2.4vw,18px);
  letter-spacing:1px; color:#fff; animation:blink 1s steps(2) infinite; }
#wipe[data-pol="pos"] { background:#f2f0ec; }
#wipe[data-pol="pos"] .w { color:#0a0a0a; font-family:"Space Grotesk", sans-serif;
  font-weight:700; font-size:clamp(20px,4vw,32px); letter-spacing:-1.4px; }
@keyframes blink { 50% { opacity:.25; } }

/* =============================================================================
   NAV — l'encre vient de la polarité, et un bandeau se glisse dessous au scroll
   -----------------------------------------------------------------------------
   ⚠️ La barre N'EST PLUS en mix-blend-mode:difference, sauf sur la home. Deux
   raisons, et la première est un piège de spécification :

   1. `nav#nav` est en `position:fixed` avec un `z-index` : il crée donc un
      CONTEXTE D'EMPILEMENT, et un contexte d'empilement ISOLE le mélange de
      ses descendants. Un enfant en `difference` n'y mélange plus avec la
      page, mais avec le fond (vide) de la barre : il ressort blanc pur, quoi
      qu'il y ait derrière. Sur une page papier, « PIERRE LORET » en blanc sur
      #f2f0ec, c'est illisible. Il n'existe aucun moyen de faire mélanger un
      enfant avec la page tant que le parent est positionné et empilé.
   2. Même la barre entière en différence ne suffisait pas : par-dessus une
      affiche de film ou une bande claire, le texte inversé devenait une
      bouillie. La différence sait inverser une couleur, pas séparer deux
      couches d'information qui se croisent.

   Sur une page intérieure, la barre au repos survole le hero, qui est de SA
   polarité : `var(--ink)` suffit et se lit toujours. Dès qu'on décolle du
   haut, un bandeau opaque se glisse dessous (voir `.lifted`, posé par
   common.js) et la barre cesse de traverser le contenu.

   La HOME garde la différence : elle est le seul écran où la barre traverse
   la frontière noir/papier, et elle ne défile jamais. L'exception est écrite
   dans son propre <style>, en basculant les quatre variables ci-dessous.
   ============================================================================= */
nav#nav { position:fixed; top:0; left:0; right:0; z-index:200;
  display:flex; align-items:center; gap:10px; padding:16px var(--gut);
  color:var(--nav-ink);
  --nav-ink:var(--ink); --nav-dim:var(--dim); --nav-line:var(--line-2); }

/* le bandeau : absent en haut de page, il monte se poser dès le premier scroll.
   z-index:-1 le range derrière le contenu de la barre SANS le sortir de son
   contexte d'empilement, donc il ne passe jamais derrière la page. */
nav#nav::before {
  content:""; position:absolute; inset:0; z-index:-1;
  background:var(--bg); border-bottom:1px solid var(--line);
  opacity:0; transform:translateY(-101%);
  transition:opacity .32s var(--ease), transform .42s var(--ease); }
html.lifted nav#nav::before { opacity:1; transform:none; }

nav#nav .brand { font-family:"Press Start 2P", monospace; font-size:12px; letter-spacing:1px;
  display:flex; align-items:center; gap:10px; }
nav#nav .brand .mark { width:14px; height:14px; background:var(--nav-ink);
  animation:spin 6s steps(4) infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
nav#nav .links { margin-left:auto; display:flex; gap:2px; }
nav#nav .links a { font-size:13.5px; padding:8px 13px; color:var(--nav-dim);
  font-weight:500; transition:color .22s; position:relative; }
nav#nav .links a::after { content:""; position:absolute; left:13px; right:13px; bottom:4px;
  height:1px; background:var(--nav-ink); transform:scaleX(0); transform-origin:left;
  transition:transform .35s var(--ease); }
nav#nav .links a:hover { color:var(--nav-ink); }
nav#nav .links a:hover::after, nav#nav .links a.active::after { transform:scaleX(1); }
nav#nav .links a.active { color:var(--nav-ink); }
nav#nav .tool-btn { font-family:"DM Mono", monospace; font-size:11.5px; padding:8px 12px;
  box-shadow:inset 0 0 0 1px var(--nav-line); color:var(--nav-dim);
  transition:color .22s, box-shadow .22s; }
nav#nav .tool-btn:hover, nav#nav .tool-btn.on { color:var(--nav-ink);
  box-shadow:inset 0 0 0 1px var(--nav-ink); }

/* Vumètre du bouton SON — trois barres à la place du ♪.
   -----------------------------------------------------------------------------
   Coupé, le vumètre n'existe PAS : sa largeur tombe à zéro et le bouton se
   referme dessus. Trois barres couchées se lisaient « … », c'est-à-dire un
   menu ou un chargement, pas un son éteint. Rallumé, le rectangle s'ouvre et
   les barres apparaissent dedans — le bouton dit alors ce qu'il fait avant
   même qu'on lise son libellé.

   La marge est portée par le vumètre, PAS par un `gap` sur le bouton : un gap
   subsiste autour d'un enfant de largeur nulle, et il restait un blanc de
   8 px après le pli. Elle s'anime avec la largeur, d'un seul mouvement.

   Actives, les barres respirent à trois vitesses premières entre elles pour ne
   jamais retomber en phase. La crête (.pk) les étire elles, et pas leur boîte :
   la boîte est en overflow:hidden pour le pli, elle aurait rogné l'étirement. */
nav#nav #sndBtn { display:inline-flex; align-items:center; gap:0; }
nav#nav .snd-eq { display:inline-flex; align-items:flex-end; gap:2px;
  height:15px; width:0; margin-right:0; opacity:0; overflow:hidden;
  transition:width .34s var(--ease), margin-right .34s var(--ease),
             opacity .2s var(--ease); }
nav#nav #sndBtn.on .snd-eq { width:10px; margin-right:9px; opacity:1; }
nav#nav .snd-eq i { display:block; width:2px; height:3px; background:currentColor;
  transform-origin:bottom; transition:height .2s var(--ease), transform .16s var(--ease); }
/* ⚠️ .live, PAS .on. Armé mais pas encore réveillé (aucun clic depuis
   l'arrivée sur la page), les barres restent immobiles : c'est la vérité, un
   son ne peut pas encore sortir. Voir syncLive() dans common.js. */
nav#nav #sndBtn.on.live .snd-eq i { animation:eqbar 1.1s var(--ease) infinite; }
nav#nav #sndBtn.on.live .snd-eq i:nth-child(2) { animation-duration:.79s; animation-delay:-.21s; }
nav#nav #sndBtn.on.live .snd-eq i:nth-child(3) { animation-duration:1.37s; animation-delay:-.53s; }
nav#nav #sndBtn.pk .snd-eq i { transform:scaleY(1.35); transition-duration:.05s; }
@keyframes eqbar { 0%,100% { height:3px; } 50% { height:11px; } }
@media (prefers-reduced-motion:reduce) {
  nav#nav #sndBtn.on.live .snd-eq i { animation:none; height:7px; }
  nav#nav #sndBtn.pk .snd-eq i { transform:none; }
}
nav#nav .lang b { color:var(--nav-ink); }
nav#nav :focus-visible { outline-color:var(--nav-ink); }
/* Sous 820 px, les liens quittent la barre du haut : ils deviennent la barre
   d'identité en bas d'écran (voir la couche MOBILE en fin de feuille). */
@media (max-width:820px) { nav#nav .brand { margin-right:auto; } }

/* Sur un petit écran la barre se cassait en deux lignes : « PIERRE / LORET »
   d'un côté, « SON : / OFF » de l'autre. Rien n'y a le droit de revenir à la
   ligne, et la marque s'abrège plutôt que de déborder. */
@media (max-width:560px) {
  nav#nav { gap:6px; padding:13px var(--gut); }
  nav#nav .brand, nav#nav .tool-btn { white-space:nowrap; }
  nav#nav .brand { font-size:10px; letter-spacing:.5px; gap:7px; min-width:0; overflow:hidden; }
  nav#nav .brand .mark { width:11px; height:11px; }
  nav#nav .tool-btn { font-size:10px; padding:7px 9px; }
}
/* Sous 380 px, le libellé SON disparaît : le symbole suffit, et c'est lui qui
   change d'état. Le bouton reste tapable, il ne rétrécit pas. */
@media (max-width:380px) {
  nav#nav .tool-btn.lang { padding:7px 8px; }
  nav#nav .brand { font-size:9px; }
}

/* barre de progression de lecture */
/* en mode différence la source doit être blanche : elle s'inverse seule selon le fond */
#prog { position:fixed; top:0; left:0; height:2px; width:0; background:#fff;
  z-index:210; mix-blend-mode:difference; }

/* rail d'index de section (droite) */
#rail { position:fixed; right:14px; top:50%; transform:translateY(-50%); z-index:180;
  display:flex; flex-direction:column; gap:12px; mix-blend-mode:difference; }
#rail a { display:flex; align-items:center; justify-content:flex-end; gap:8px;
  font-family:"DM Mono", monospace; font-size:10px; letter-spacing:1px; color:rgba(255,255,255,.42);
  transition:color .3s; }
#rail a i { display:block; width:16px; height:1px; background:currentColor; transition:width .35s var(--ease); }
#rail a.on { color:#fff; } #rail a.on i { width:30px; }
#rail a span { opacity:0; transition:opacity .3s; }
#rail a:hover span, #rail a.on span { opacity:1; }
/* 1180 px de contenu + marges : en dessous de 1240 le rail mordrait dessus */
@media (max-width:1240px) { #rail { display:none; } }

/* =============================================================================
   MISE EN PAGE
   ============================================================================= */
.wrap { max-width:var(--maxw); margin:0 auto; }
.sec { position:relative; z-index:2; padding:clamp(76px,13vh,150px) var(--gut); }
.sec--tight { padding-top:0; }
/* svh après vh : sur iOS, vh compte la barre d'adresse repliée et le premier
   écran saute quand elle se déplie. svh mesure l'écran réellement visible. */
.hero { position:relative; z-index:2; min-height:88vh; min-height:88svh;
  display:flex; flex-direction:column;
  justify-content:center; padding:clamp(118px,17vh,190px) var(--gut) clamp(50px,8vh,80px); overflow:hidden; }
/* ⚠️ width:100% est INDISPENSABLE ici. .wrap porte margin:0 auto, et dans un
   conteneur flex une marge automatique sur l'axe transversal l'emporte sur
   l'étirement : le bloc se réduisait à la largeur de son contenu puis se
   centrait. Le hero faisait 778 px au lieu de 1180 et ne s'alignait plus sur
   les sections en dessous, qui ne sont pas des conteneurs flex. */
.hero > .wrap { width:100%; }

.eyebrow { display:inline-flex; align-items:center; gap:9px; font-family:"DM Mono", monospace;
  font-size:11.5px; letter-spacing:2.5px; text-transform:uppercase; color:var(--ink); margin-bottom:16px; }
/* --corner réduit : sur un carré de 8px, le cran par défaut de 5px détruirait la forme */
.eyebrow i { width:8px; height:8px; background:var(--pure); display:block; --corner:2px; }

/* le titre change de police avec la polarité */
.display { font-family:var(--display); font-weight:var(--display-w);
  letter-spacing:var(--display-ls); line-height:var(--display-lh);
  font-size:calc(clamp(30px,6.4vw,80px) * var(--ds)); }
.display--xl { font-size:calc(clamp(34px,10vw,142px) * var(--ds)); }
/* Un mot insécable ne se coupe plus : sur un écran étroit il doit donc TENIR.
   On resserre le corps et l'interlettrage plutôt que de laisser déborder. */
@media (max-width:560px) {
  .display--xl { font-size:calc(clamp(30px,8.6vw,60px) * var(--ds)); }
  [data-pol="pos"] .display { letter-spacing:-1.4px; }
  .display--sm { font-size:calc(clamp(20px,5.4vw,30px) * var(--ds)); }
}
.display--sm { font-size:calc(clamp(22px,3.6vw,42px) * var(--ds)); }
[data-pol="pos"] .display--sm { letter-spacing:-1.2px; }

.lead { color:var(--dim); font-size:clamp(15px,2vw,19px); max-width:600px; margin-top:18px; }
.body { font-size:clamp(15.5px,1.7vw,18px); line-height:1.9; }
.body p + p { margin-top:15px; }

.sec-head { display:flex; align-items:flex-end; justify-content:space-between;
  gap:24px; margin-bottom:42px; flex-wrap:wrap; }
.sec-head .idx { font-family:"DM Mono", monospace; font-size:11px; letter-spacing:3px; color:var(--dim); }
.split { display:grid; grid-template-columns:1fr 1fr; gap:clamp(30px,5vw,60px); align-items:center; }
@media (max-width:880px) { .split { grid-template-columns:1fr; } }

/* =============================================================================
   BOUTONS
   ============================================================================= */
.btn { display:inline-flex; align-items:center; gap:10px; width:max-content;
  font-family:"DM Mono", monospace; font-size:13.5px; letter-spacing:.5px;
  padding:14px 24px; transition:transform .14s var(--ease), gap .3s var(--ease); }
.btn--solid { background:var(--pure); color:var(--anti); }
.btn--line { box-shadow:inset 0 0 0 1px var(--line-2); color:var(--ink); }
.btn--line:hover { box-shadow:inset 0 0 0 1px var(--pure); }
.btn:hover { gap:16px; }
/* ⚠️ Le dialecte du bouton principal passe par des VARIABLES, jamais par un
   sélecteur « [data-pol="…"] .btn--ghost ». Sur la home les deux polarités
   cohabitent : html est en négatif, le panneau de droite en positif, et les
   DEUX sélecteurs visaient donc son bouton, à spécificité égale. C'est l'ordre
   d'écriture qui tranchait : le panneau clair héritait du bouton plein du
   négatif tout en gardant le padding du positif, d'où un pavé noir sans marge
   horizontale, texte collé aux bords. Une variable, elle, vient toujours de
   l'ancêtre le PLUS PROCHE : la bonne polarité l'emporte, sans exception. */
.btn--ghost { padding:var(--ghost-pad); background:var(--ghost-bg); color:var(--ghost-ink);
  border-bottom:var(--ghost-line) solid var(--pure); }
.btn--ghost:hover { gap:var(--ghost-gap); }

/* ⚠️ Le lien de retour occupe SA ligne. En inline-flex il partageait la ligne
   de l'.eyebrow qui le suit, lui aussi en inline-flex : le nom de la page se
   collait au bout du lien (« ← Retour aux projets pro ■ OUTILS ») au lieu de
   se poser dessous. width:max-content garde la cible de survol serrée sur le
   texte, sinon la boîte de cadrage ferait toute la largeur du bloc. */
.backlink { display:flex; width:max-content; align-items:center; gap:8px;
  font-family:"DM Mono", monospace;
  font-size:12.5px; color:var(--dim); margin-bottom:24px; transition:color .25s, gap .25s; }
.backlink:hover { color:var(--ink); gap:14px; }

/* =============================================================================
   RÉVÉLATIONS AU SCROLL
   ============================================================================= */
.rev { opacity:0; transform:translateY(28px);
  transition:opacity .75s var(--ease), transform .75s var(--ease); }
.rev.in { opacity:1; transform:none; }
[data-rev="left"] { transform:translateX(-30px); }
[data-rev="clip"] { opacity:1; transform:none; clip-path:inset(0 100% 0 0); transition:clip-path .95s var(--ease); }
[data-rev="clip"].in { clip-path:inset(0 0 0 0); }
[data-rev="line"] { opacity:1; transform:none; }
[data-rev="line"]::after { content:""; display:block; height:1px; background:var(--line-2);
  transform:scaleX(0); transform-origin:left; transition:transform 1s var(--ease); }
[data-rev="line"].in::after { transform:scaleX(1); }

/* découpe caractère par caractère (titres)
   Le padding/margin compensé agrandit la boîte de rognage sans déplacer la ligne :
   sans lui, un line-height serré rogne les accents (É, à, ê) et les jambages. */
/* ⚠️ Le mot est INSÉCABLE. Chaque lettre découpée est un inline-block, donc
   sans ce regroupement le navigateur coupe où bon lui semble, au milieu d'un
   mot. C'est ce qui donnait « crédi / bles » dès que la ligne serrait. */
.wd { display:inline-block; white-space:nowrap; }

.ch { display:inline-block; overflow:hidden; vertical-align:bottom;
  padding:.16em 0 .1em; margin:-.16em 0 -.1em; }
.ch > i { display:inline-block; font-style:normal; transform:translateY(115%);
  transition:transform .7s var(--ease); }
.ch.in > i { transform:none; }

@media (prefers-reduced-motion:reduce) {
  * { animation-duration:.01ms !important; animation-iteration-count:1 !important;
      transition-duration:.01ms !important; scroll-behavior:auto !important; }
  .rev, .ch > i { opacity:1 !important; transform:none !important; clip-path:none !important; }
}

/* =============================================================================
   COMPOSANTS PARTAGÉS
   ============================================================================= */
/* liste-index : piliers, films, étapes */
.index-list { border-top:1px solid var(--line); }
.row { position:relative; display:grid; grid-template-columns:auto 1fr auto; gap:clamp(16px,3vw,40px);
  align-items:center; padding:clamp(20px,3vw,32px) 0; border-bottom:1px solid var(--line);
  transition:padding-left .45s var(--ease); overflow:hidden; }
.row:hover { padding-left:clamp(10px,2vw,26px); }
.row::before { content:""; position:absolute; inset:0; background:var(--panel);
  transform:scaleY(0); transform-origin:bottom; transition:transform .45s var(--ease); z-index:0; }
.row:hover::before { transform:scaleY(1); }
.row > * { position:relative; z-index:1; }
.row .no { font-family:"DM Mono", monospace; font-size:12px; letter-spacing:2px; color:var(--dim);
  transition:color .3s; min-width:26px; }
.row:hover .no { color:var(--ink); }
.row .rt { font-size:clamp(18px,2.6vw,30px); font-weight:600; letter-spacing:-.4px;
  display:flex; align-items:center; gap:12px; flex-wrap:wrap; }
[data-pol="neg"] .row .rt { font-family:"Press Start 2P", monospace; font-weight:400;
  font-size:clamp(13px,1.8vw,19px); letter-spacing:0; }
.row .rd { color:var(--dim); font-size:14px; margin-top:6px; }
.row .rm { font-family:"DM Mono", monospace; font-size:12.5px; color:var(--dim);
  white-space:nowrap; text-align:right; transition:color .3s;
  display:flex; align-items:center; justify-content:flex-end; gap:7px; }
.row .rm .i { font-size:15px; }
.row:hover .rm { color:var(--ink); }
.tag { font-family:"DM Mono", monospace; font-size:10.5px; letter-spacing:1.5px; padding:4px 9px;
  box-shadow:inset 0 0 0 1px var(--line-2); color:var(--dim); }
@media (max-width:680px) { .row { grid-template-columns:auto 1fr; } .row .rm { display:none; } }

/* cartes */
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(248px,1fr)); gap:14px; }
.card { position:relative; padding:26px; background:var(--panel); overflow:hidden;
  display:flex; flex-direction:column; gap:13px; min-height:190px;
  transition:transform .35s var(--ease), background .35s var(--ease); }
.card::before { content:""; position:absolute; inset:0; box-shadow:inset 0 0 0 1px var(--line);
  transition:box-shadow .35s; pointer-events:none; z-index:2; }
.card:hover { transform:translateY(-5px); background:var(--panel-2); }
.card:hover::before { box-shadow:inset 0 0 0 1px var(--pure); }
.card .ic { width:46px; height:46px; display:grid; place-items:center; font-size:20px;
  background:var(--pure); color:var(--anti);
  background-image:radial-gradient(rgba(128,128,128,.22) 1px, transparent 1.4px); background-size:7px 7px; }
.card b { font-size:17px; letter-spacing:-.2px; }
.card p { color:var(--dim); font-size:13.5px; flex:1; }
/* margin-top:auto plutôt que de compter sur le flex:1 du <p> : une carte sans
   paragraphe garde son appel à l'action collé en bas, comme les autres. */
.card .go { font-family:"DM Mono", monospace; font-size:12px; color:var(--dim);
  margin-top:auto; display:flex; align-items:center; gap:6px;
  transition:gap .3s var(--ease), color .3s; }
.card:hover .go { gap:13px; color:var(--ink); }
[data-pol="pos"] .card:hover { transform:translateY(-2px); }

/* grille collée (fonctionnalités) */
.mesh { display:grid; grid-template-columns:repeat(auto-fill,minmax(250px,1fr));
  gap:1px; background:var(--line); box-shadow:0 0 0 1px var(--line); }
.mesh > div { background:var(--bg); padding:28px 24px; min-height:180px;
  display:flex; flex-direction:column; gap:11px; transition:background .3s var(--ease); }
.mesh > div:hover { background:var(--panel); }
.mesh .fi { font-size:24px; color:var(--ink); }
.mesh b { font-size:17px; } .mesh p { color:var(--dim); font-size:13.5px; }

/* figure animée */
.figbox { position:relative; aspect-ratio:16/11; background:var(--panel);
  box-shadow:inset 0 0 0 1px var(--line); overflow:hidden; display:grid; place-items:center; }
.figbox canvas { position:absolute; inset:0; }
.figbox .lbl { position:relative; z-index:2; font-family:"Press Start 2P", monospace;
  font-size:clamp(30px,6vw,60px); color:#fff; mix-blend-mode:difference; }

/* chiffres clés */
.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:1px; background:var(--line); box-shadow:0 0 0 1px var(--line); }
.stats > div { background:var(--bg); padding:30px 20px; text-align:center; }
.stats b { display:block; font-family:var(--display); font-weight:var(--display-w);
  letter-spacing:var(--display-ls); font-size:calc(clamp(24px,4.4vw,40px) * var(--ds)); }
[data-pol="pos"] .stats b { letter-spacing:-1.5px; }
.stats span { font-family:"DM Mono", monospace; font-size:11px; letter-spacing:1.5px; color:var(--dim); }

/* étiquettes / mots-clés */
.chips { display:flex; gap:9px; flex-wrap:wrap; margin-top:22px; }
.chips span { font-family:"DM Mono", monospace; font-size:12.5px; padding:8px 14px;
  box-shadow:inset 0 0 0 1px var(--line-2); color:var(--dim); }

/* bandeau défilant */
.marquee { overflow:hidden; border-top:1px solid var(--line); border-bottom:1px solid var(--line);
  background:var(--bg-2); padding:16px 0; position:relative; z-index:2; }
.marquee .row-m { display:flex; gap:36px; width:max-content; will-change:transform; }
.marquee .row-m span { font-family:"Press Start 2P", monospace; font-size:14px; color:var(--dim);
  display:flex; align-items:center; gap:36px; white-space:nowrap; }
.marquee .row-m span::after { content:"◆"; color:var(--ink); font-size:11px; }
[data-pol="pos"] .marquee .row-m span { font-family:"Space Grotesk", sans-serif; font-weight:700;
  font-size:22px; letter-spacing:-.8px; }

/* chronologie */
.timeline { border-left:1px solid var(--line); margin-left:7px; padding-left:30px; display:grid; gap:34px; }
.tl { position:relative; }
.tl::before { content:""; position:absolute; left:-37px; top:6px; width:13px; height:13px;
  background:var(--pure); transform:scale(0); transition:transform .5s var(--ease); }
.tl.in::before { transform:scale(1); }
.tl .yr { font-family:"DM Mono", monospace; font-size:12px; letter-spacing:3px; color:var(--dim); }
.tl b { display:block; font-size:18px; margin-top:6px; }
.tl p { color:var(--dim); font-size:14.5px; margin-top:3px; }

/* =============================================================================
   FRISE CHRONOLOGIQUE (page Cinéma & TV)
   -----------------------------------------------------------------------------
   Anti-chronologique : l'année en cours en haut, on remonte le temps en
   descendant. Le repère d'année est COLLANT — il tient la colonne de gauche
   pendant que ses films défilent, puis l'année suivante vient le pousser hors
   de l'écran. C'est ce qui donne la sensation de remonter une bobine.

   ⚠️ Les classes sont préfixées « reel- » : .yr est déjà pris par la petite
   chronologie de communities.html (.tl .yr).
   ============================================================================= */
.reel { position:relative; }

.reel-yr { display:grid; grid-template-columns:clamp(96px,13vw,190px) 1fr;
  gap:clamp(20px,4vw,54px); padding:clamp(34px,5vw,60px) 0; }
.reel-yr:first-child { padding-top:0; }

/* le repère : collant, aligné à droite contre l'axe */
.reel-mark { position:sticky; top:clamp(96px,15vh,132px); align-self:start; text-align:right; }
.reel-n { display:block; font-family:"Space Grotesk", sans-serif; font-weight:700;
  font-size:clamp(34px,5.4vw,78px); letter-spacing:-3px; line-height:.88;
  color:transparent; -webkit-text-stroke:1px var(--line-2);
  transition:color .7s var(--ease), -webkit-text-stroke-color .7s var(--ease); }
/* l'année se remplit quand ses films sont à l'écran : on sait où l'on est */
.reel-yr.here .reel-n { color:var(--ink); -webkit-text-stroke-color:transparent; }
.reel-c { display:block; margin-top:10px; font-family:"DM Mono", monospace;
  font-size:11px; letter-spacing:2.5px; text-transform:uppercase; color:var(--dim); }

/* l'axe : une seule bordure, mais les blocs se touchent, donc le trait est continu */
.reel-films { --reel-pad:clamp(22px,3.5vw,50px);
  position:relative; border-left:1px solid var(--line); padding-left:var(--reel-pad);
  display:grid; gap:clamp(30px,4.5vw,64px); }

/* ------------------------------------------------------------- carte de film */
.fc { position:relative; display:grid; grid-template-columns:minmax(0,44%) 1fr;
  gap:clamp(18px,2.8vw,40px); align-items:center; color:inherit; }
.fc::before { content:""; position:absolute; left:calc(var(--reel-pad) * -1 - 5px); top:calc(50% - 5px);
  width:11px; height:11px; background:var(--pure); transform:scale(0);
  transition:transform .55s var(--ease); }
.fc.in::before, .rev.in .fc::before { transform:scale(1); }

/* aspect-ratio est posé en ligne par le moteur, d'après les dimensions réelles
   du fichier : une affiche ne se laisse pas rogner dans un cadre paysage. */
.fc-vis { position:relative; aspect-ratio:16/10; overflow:hidden; background:var(--panel);
  box-shadow:inset 0 0 0 1px var(--line); display:grid; place-items:center; }
/* une affiche à 44 % de la colonne serait démesurément haute */
.fc--portrait { grid-template-columns:minmax(0,31%) 1fr; }
.fc--square   { grid-template-columns:minmax(0,38%) 1fr; }
/* Noir et blanc au repos : la frise se lit d'un bloc, sans qu'une affiche crie
   plus fort que sa voisine. La couleur revient au survol, seule entorse
   consentie au monochrome, et elle récompense le geste au lieu de le décorer. */
.fc-vis img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  filter:grayscale(1) contrast(1.06);
  transition:transform 1.1s var(--ease), filter .5s var(--ease); }
a.fc:hover .fc-vis img { transform:scale(1.055); }
/* @media (hover:hover) : sur un écran tactile, :hover reste collé après le
   doigt. Une affiche s'y colorerait au premier effleurement et n'en
   reviendrait plus, au hasard des cartes touchées. */
@media (hover:hover) {
  .fc:hover .fc-vis img { filter:grayscale(0) contrast(1); }
}

/* sans visuel, l'année en creux plutôt qu'un trou : la frise reste lisible */
.fc-vis.empty::before, .fc-vis.empty::after { content:""; position:absolute; width:14px; height:14px;
  border:1px solid var(--line-2); }
.fc-vis.empty::before { left:12px; top:12px; border-right:0; border-bottom:0; }
.fc-vis.empty::after { right:12px; bottom:12px; border-left:0; border-top:0; }
.fc-vis .ghost { font-family:"Space Grotesk", sans-serif; font-weight:700;
  font-size:clamp(30px,4.5vw,60px); letter-spacing:-2.4px;
  color:transparent; -webkit-text-stroke:1px var(--line-2); }

.fc-fmt { display:flex; align-items:center; gap:9px; flex-wrap:wrap;
  font-family:"DM Mono", monospace; font-size:10.5px; letter-spacing:3px;
  text-transform:uppercase; color:var(--dim); }
.fc-fmt .sep { font-style:normal; color:var(--line-2); }
.fc-t { font-family:"Space Grotesk", sans-serif; font-weight:700; letter-spacing:-1.5px;
  font-size:clamp(21px,3vw,38px); line-height:1.04; margin-top:11px; }
/* le filet sous le titre balaie au survol : le seul mouvement autorisé côté pro */
.fc-t::after { content:""; display:block; height:1px; background:var(--pure); margin-top:9px;
  transform:scaleX(0); transform-origin:left; transition:transform .6s var(--ease); }
a.fc:hover .fc-t::after { transform:scaleX(1); }
.fc-r { margin-top:13px; font-size:15px; }
.fc-p { margin-top:5px; font-family:"DM Mono", monospace; font-size:12.5px; color:var(--dim); }
.fc-go { display:inline-flex; align-items:center; gap:7px; margin-top:16px;
  font-family:"DM Mono", monospace; font-size:11.5px; letter-spacing:2px; color:var(--dim);
  transition:gap .35s var(--ease), color .35s; }
a.fc:hover .fc-go { gap:14px; color:var(--ink); }

@media (max-width:900px) {
  .reel-yr { grid-template-columns:1fr; gap:14px; padding:clamp(30px,6vw,48px) 0; }
  /* L'année reste COLLANTE : c'est la signature de la page (« remonter une
     bobine ») et le seul repère d'orientation une fois la colonne perdue.
     Elle devient un bandeau opaque plein largeur qui coiffe ses films, et le
     script .here continue de la remplir quand on la traverse : l'année passe
     du contour au plein, comme au bureau. */
  .reel-mark { position:sticky; top:0; z-index:5; text-align:left;
    display:flex; align-items:baseline; gap:14px;
    background:var(--bg); padding:10px 0 8px; border-bottom:1px solid var(--line); }
  .reel-n { font-size:clamp(30px,9vw,44px); }
  .reel-c { margin-top:0; }
  .fc { grid-template-columns:1fr; gap:16px; }
  .fc::before { top:-5px; }
  /* une affiche portrait en pleine largeur mangeait un écran et demi : elle
     revient à une largeur d'affiche, le texte passe dessous */
  .fc--portrait .fc-vis { width:min(62%, 230px); }
  .fc--square .fc-vis { width:min(72%, 260px); }
}

/* =============================================================================
   PLANCHE-CONTACT (page Autoentreprise)
   -----------------------------------------------------------------------------
   L'opposé de la frise : dense et horizontale là où elle est aérée et
   verticale. Une bande de vignettes numérotées, perforations comprises, qu'on
   annote au crayon gras.

   Les perforations sont dessinées par un dégradé répété sur chaque vignette,
   PAS sur une bande englobante : la grille se réagence selon la largeur, et
   des perforations posées sur une rangée fixe se décaleraient. Comme chaque
   vignette porte les siennes, les rangées restent alignées à toute taille.
   ============================================================================= */
.sheet { display:grid; grid-template-columns:repeat(auto-fill, minmax(240px, 1fr));
  gap:0; background:var(--panel); box-shadow:inset 0 0 0 1px var(--line); }

.cs-f { position:relative; display:block; padding:26px 16px 20px;
  color:inherit; box-shadow:inset 0 0 0 1px var(--line);
  transition:background .4s var(--ease); }
.cs-f:hover { background:var(--panel-2); }

/* perforations, en haut et en bas de chaque vignette */
.cs-f::before, .cs-f::after { content:""; position:absolute; left:0; right:0; height:9px;
  background-image:repeating-linear-gradient(to right,
    var(--line-2) 0 7px, transparent 7px 15px);
  opacity:.75; }
.cs-f::before { top:7px; }
.cs-f::after  { bottom:7px; }

/* la mention imprimée en bord de pellicule */
.cs-edge { position:absolute; top:19px; left:16px;
  font-family:"DM Mono", monospace; font-size:8.5px; letter-spacing:2px;
  text-transform:uppercase; color:var(--dim-2); }
.cs-n { position:absolute; top:17px; right:16px;
  font-family:"DM Mono", monospace; font-size:10px; letter-spacing:1px; color:var(--dim); }

.cs-img { display:block; position:relative; margin-top:16px; aspect-ratio:3/2;
  overflow:hidden; background:var(--bg-2); box-shadow:inset 0 0 0 1px var(--line);
  display:grid; place-items:center; }
.cs-img img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  filter:grayscale(1) contrast(1.06); transition:transform 1s var(--ease), filter .5s var(--ease); }
.cs-f:hover .cs-img img { transform:scale(1.05); }
@media (hover:hover) { .cs-f:hover .cs-img img { filter:grayscale(0) contrast(1); } }
.cs-empty { font-family:"DM Mono", monospace; font-size:10.5px; letter-spacing:3px;
  color:var(--line-2); }

.cs-meta { display:block; margin-top:13px; }
.cs-meta b { display:block; font-family:"Space Grotesk", sans-serif; font-weight:700;
  font-size:16px; letter-spacing:-.5px; line-height:1.15; }
.cs-meta i { display:block; font-style:normal; margin-top:3px;
  font-size:12.5px; color:var(--dim); }
.cs-meta .cs-cli { font-family:"DM Mono", monospace; font-size:11px; letter-spacing:1px;
  text-transform:uppercase; }

/* le rond au crayon gras : il se TRACE au survol, il n'apparaît pas d'un bloc.
   pathLength=1 rend la longueur du tracé indépendante de la taille réelle de
   l'ellipse, sinon le tiret devrait être recalculé à chaque largeur. */
.cs-mark { position:absolute; inset:10px 8px; width:calc(100% - 16px); height:calc(100% - 20px);
  fill:none; stroke:var(--pure); stroke-width:2.5; stroke-linecap:round;
  stroke-dasharray:1; stroke-dashoffset:1; opacity:0;
  transform:rotate(-1.4deg); pointer-events:none;
  transition:stroke-dashoffset .75s var(--ease), opacity .25s; }
.cs-f:hover .cs-mark { opacity:.85; stroke-dashoffset:0; }
@media (prefers-reduced-motion:reduce) {
  .cs-mark { transition:opacity .2s; }
  .cs-f:hover .cs-mark { stroke-dashoffset:0; }
}

/* encadré de note */
.note { margin-top:60px; padding:20px 22px; box-shadow:inset 0 0 0 1px var(--line);
  background:var(--panel); font-family:"DM Mono", monospace; font-size:12.5px; color:var(--dim); }

/* état vide des catégories — émis par render.js pour la frise ET la planche :
   la règle vit ici, pas dans une page (cinema.html l'affichait sans style) */
.cat-intro { color:var(--dim); font-size:14.5px; max-width:560px; margin-bottom:26px; }

/* =============================================================================
   LECTEUR SOUNDCLOUD (page Noxel)
   ============================================================================= */
.music-grid { display:grid; grid-template-columns:1.05fr 1fr; gap:20px; align-items:start; }
@media (max-width:880px) { .music-grid { grid-template-columns:1fr; } }
.player-card { position:relative; padding:28px; background:var(--panel); box-shadow:inset 0 0 0 1px var(--line); }
.now { display:flex; align-items:center; gap:18px; }
/* ⚠️ min-width:0 est obligatoire sur les enfants de flex qui portent du texte
   venu de SoundCloud : un titre d'un seul tenant refuse sinon de rétrécir et
   pousse la carte en défilement horizontal. */
.now-meta { min-width:0; }
.now-meta b { overflow-wrap:anywhere; }
.cover { width:86px; height:86px; flex:none; display:grid; place-items:center; font-size:34px;
  background:var(--dim-2); color:var(--anti); background-size:cover; background-position:center;
  overflow:hidden; transition:transform .3s; }
.player-card.playing .cover { animation:pulse 1.15s steps(2) infinite; }
@keyframes pulse { 50% { transform:scale(.94); } }
.now-meta b { font-size:19px; display:block; line-height:1.25; }
.now-meta small { color:var(--dim); font-family:"DM Mono", monospace; }
/* ⚠️ touch-action:none n'est pas optionnel : sans lui, iOS prend le glissement
   horizontal sur la barre pour un défilement de page et confisque le geste au
   deuxième pixel. On ne peut alors plus se déplacer dans le morceau au doigt. */
.prog-bar { position:relative; height:13px; margin:22px 0 13px; background:var(--panel-2);
  box-shadow:inset 0 0 0 1px var(--line); touch-action:none; }
.prog-bar i { display:block; height:100%; width:0; background:var(--dim-2);
  background-image:radial-gradient(rgba(128,128,128,.25) 1px, transparent 1.4px); background-size:6px 6px;
  transition:width .15s linear; }
/* pendant le glissement la largeur suit le doigt à la frame : la transition
   de .15s la ferait traîner derrière lui */
.prog-bar.scrub i { transition:none; }
.prog-bar.scrub { box-shadow:inset 0 0 0 1px var(--pure); }
.controls { display:flex; align-items:center; gap:15px; color:var(--dim);
  font-family:"DM Mono", monospace; font-size:12.5px; }
.ctl { font-size:16px; color:var(--dim); transition:color .2s, transform .14s; }
.ctl:hover { color:var(--ink); transform:scale(1.16); }
.play { width:44px; height:44px; background:var(--pure); color:var(--anti);
  display:grid; place-items:center; font-size:15px; transition:transform .14s; }
.play:hover { transform:scale(1.09); }
.time { margin-left:auto; }
/* ⚠️ Ne PAS agrandir cette hauteur en croyant bien faire. Passée à 430 px, la
   liste ne débordait presque plus : la course utile tombait à quelques dizaines
   de pixels, on l'épuisait en deux crans de molette et la page repartait. Une
   liste défilante a besoin d'une vraie course pour être agréable.
   overscroll-behavior:contain empêche la propagation à la page ; la confiscation
   de la molette elle-même est en JS dans noxelmusic.html, la CSS n'y suffisait
   pas. */
.tracks { background:var(--panel); box-shadow:inset 0 0 0 1px var(--line);
  max-height:min(340px, 52svh); overflow-y:auto; overscroll-behavior:contain; }
.tracks::-webkit-scrollbar { width:7px; }
.tracks::-webkit-scrollbar-thumb { background:var(--line-2); }
.track { display:flex; align-items:center; gap:15px; padding:16px 21px;
  border-bottom:1px dashed var(--line); transition:background .2s, padding-left .25s; }
.track:last-child { border-bottom:none; }
.track.active, .track:hover { background:var(--panel-2); padding-left:26px; }
.track .n { font-family:"DM Mono", monospace; color:var(--dim); width:22px; }
.track.active .n, .track:hover .n { color:var(--ink); }
.track .t { min-width:0; }
.track .t b { display:block; font-size:14.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.track .t small { color:var(--dim); font-size:12px; }
.track .dur { margin-left:auto; font-family:"DM Mono", monospace; color:var(--dim); font-size:12.5px; }
.track-load { padding:22px; color:var(--dim); font-size:13px; font-family:"DM Mono", monospace; }

/* équaliseur décoratif */
.eq { position:absolute; inset:auto 0 0 0; height:40vh; height:40svh; display:flex; align-items:flex-end;
  gap:4px; padding:0 var(--gut); z-index:0; opacity:.16; pointer-events:none; }
.eq span { flex:1; background:var(--pure); height:6%; }

/* =============================================================================
   PIED DE PAGE
   ============================================================================= */
footer { position:relative; z-index:2; padding:70px var(--gut) 46px;
  border-top:1px solid var(--line); margin-top:30px; }
.foot { max-width:var(--maxw); margin:0 auto; display:flex; justify-content:space-between;
  gap:28px; flex-wrap:wrap; align-items:center; }
.foot .big { font-family:var(--display); font-weight:var(--display-w);
  letter-spacing:var(--display-ls); line-height:1.4;
  font-size:calc(clamp(22px,4.6vw,44px) * var(--ds)); }
.socials { display:flex; gap:9px; flex-wrap:wrap; }
.socials a { display:inline-flex; align-items:center; gap:9px;
  font-family:"DM Mono", monospace; font-size:12.5px; padding:11px 16px;
  box-shadow:inset 0 0 0 1px var(--line); color:var(--dim); transition:color .25s, box-shadow .25s; }
.socials a:hover { color:var(--ink); box-shadow:inset 0 0 0 1px var(--pure); }

/* =============================================================================
   ICÔNES
   Un seul sprite externe, mis en cache une fois pour tout le site :
   <svg class="i"><use href="/icons.svg#spotify"></use></svg>
   fill:currentColor → le glyphe prend l'encre de la polarité courante, y
   compris à l'intérieur d'un bloc plein (.card .ic) où l'encre s'inverse.
   ============================================================================= */
.i { width:1em; height:1em; display:block; flex:none; fill:currentColor;
  pointer-events:none; }
.socials .i { font-size:15px; opacity:.85; transition:opacity .25s; }
.socials a:hover .i { opacity:1; }
.card .ic .i { font-size:21px; }
.btn .i { font-size:15px; }
.copyright { max-width:var(--maxw); margin:38px auto 0; font-family:"DM Mono", monospace;
  font-size:11.5px; color:var(--dim); display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; }

/* =============================================================================
   COUCHE 2 — animation avancée
   -----------------------------------------------------------------------------
   Chaque effet existe des DEUX côtés, avec deux dialectes opposés :
     négatif → stepped, brutal, pixel, court
     positif → continu, optique, lent
   C'est le même mécanisme qui parle deux langues. Ne jamais donner à l'un
   la courbe de l'autre : c'est ce qui tient la distinction pro / perso.
   ============================================================================= */

/* ---------- boîte de cadrage : verrouille l'élément survolé ---------- */
#focus { position:fixed; left:0; top:0; z-index:9997; pointer-events:none;
  mix-blend-mode:difference; opacity:0; }
#focus.on { opacity:1; }
#focus i { position:absolute; width:9px; height:9px; border:1px solid #fff; }
#focus i:nth-child(1) { left:0;  top:0;    border-right:0; border-bottom:0; }
#focus i:nth-child(2) { right:0; top:0;    border-left:0;  border-bottom:0; }
#focus i:nth-child(3) { left:0;  bottom:0; border-right:0; border-top:0; }
#focus i:nth-child(4) { right:0; bottom:0; border-left:0;  border-top:0; }
/* le pro fait un point au ralenti, comme une bague de mise au point */
[data-pol="pos"] #focus { transition:opacity .3s, transform .42s cubic-bezier(.62,.04,.16,1),
  width .42s cubic-bezier(.62,.04,.16,1), height .42s cubic-bezier(.62,.04,.16,1); }
/* Noxel claque dessus en quatre crans */
[data-pol="neg"] #focus { transition:opacity .1s, transform .16s steps(4),
  width .16s steps(4), height .16s steps(4); }
@media (hover:none) { #focus { display:none; } }

/* ---------- HUD : compteur de métrage (positif) / niveau (négatif) ---------- */
#hud { position:fixed; right:14px; bottom:16px; z-index:180; mix-blend-mode:difference;
  font-family:"DM Mono", monospace; font-size:10.5px; letter-spacing:2px;
  color:rgba(255,255,255,.5); pointer-events:none; }
@media (max-width:980px) { #hud { display:none; } }

/* ---------- révélations : deux façons d'arriver à l'image ---------- */
/* positif : la mise au point se fait, comme au tirage */
[data-rev="focus"] { opacity:0; filter:blur(10px); transform:translateY(16px) scale(1.012);
  transition:opacity .9s var(--ease), filter 1.15s var(--ease), transform 1.15s var(--ease); }
[data-rev="focus"].in { opacity:1; filter:blur(0); transform:none; }

/* dither et iris passent par un RIDEAU, pas par clip-path : la classe .pix
   utilise déjà clip-path pour les crans, et la révélation l'effacerait. */

/* négatif : le rideau se retire par paliers, comme un rafraîchissement d'écran */
[data-rev="dither"] { opacity:1; transform:none; position:relative; }
[data-rev="dither"]::after { content:""; position:absolute; inset:0; z-index:6;
  background:var(--bg); transform-origin:right; transition:transform .6s steps(9); }
[data-rev="dither"].in::after { transform:scaleX(0); }

/* iris : l'image s'ouvre par le bas */
[data-rev="iris"] { opacity:1; transform:none; position:relative; }
[data-rev="iris"]::after { content:""; position:absolute; inset:0; z-index:6;
  background:var(--bg); transform-origin:top; transition:transform 1s var(--ease); }
[data-rev="iris"].in::after { transform:scaleY(0); }
[data-rev="iris"] canvas { transform:scale(1.12); transition:transform 1.5s var(--ease); }
[data-rev="iris"].in canvas { transform:none; }
[data-pol="neg"] [data-rev="iris"]::after { transition:transform .55s steps(7); }

/* ---------- boutons : le plein balaie le contour ---------- */
.btn--line { position:relative; overflow:hidden; z-index:0; }
.btn--line::before { content:""; position:absolute; inset:0; background:var(--pure);
  transform:scaleX(0); transform-origin:left; z-index:-1; }
[data-pol="neg"] .btn--line::before { transition:transform .3s steps(5); }
[data-pol="pos"] .btn--line::before { transition:transform .6s var(--ease); }
.btn--line:hover::before { transform:scaleX(1); }
.btn--line:hover { color:var(--anti); }

/* le remplissage des lignes suit le même écart de dialecte */
[data-pol="neg"] .row::before { transition:transform .3s steps(6); }
[data-pol="neg"] .row { transition:padding-left .2s steps(4); }

/* ---------- chronologie : le trait se dessine avec le scroll ---------- */
.timeline { position:relative; }
.timeline::after { content:""; position:absolute; left:-1px; top:0; width:1px;
  height:var(--tl,0%); background:var(--pure); transition:height .12s linear; }

/* ---------- balayage de trame, en fond, côté Noxel seulement ---------- */
[data-pol="neg"] .tex::after { content:""; position:absolute; left:0; right:0; height:130px;
  background:linear-gradient(rgba(255,255,255,0), rgba(255,255,255,.05), rgba(255,255,255,0));
  animation:sweepdown 7.5s linear infinite; }
@keyframes sweepdown { from { top:-150px; } to { top:100%; } }

/* ---------- entrée chorégraphiée après l'amorce ---------- */
[data-enter] { opacity:0; transform:translateY(14px);
  transition:opacity .8s var(--ease), transform .8s var(--ease); }
[data-pol="neg"] [data-enter] { transition:opacity .3s steps(4), transform .3s steps(4); }
[data-enter].in { opacity:1; transform:none; }

@media (prefers-reduced-motion:reduce) {
  [data-rev="focus"], [data-rev="dither"], [data-rev="iris"], [data-enter] {
    opacity:1 !important; filter:none !important; transform:none !important; clip-path:none !important; }
  [data-pol="neg"] .tex::after { display:none; }
}

/* aide clavier */
#keys { position:fixed; left:var(--gut); bottom:18px; z-index:180; mix-blend-mode:difference;
  font-family:"DM Mono", monospace; font-size:10.5px; letter-spacing:1.5px;
  color:rgba(255,255,255,.4); display:flex; gap:14px; transition:color .3s; }
#keys:hover { color:#fff; }
#keys kbd { box-shadow:inset 0 0 0 1px rgba(255,255,255,.35); padding:2px 5px; font-family:inherit; }
@media (max-width:980px) { #keys { display:none; } }

/* =============================================================================
   COUCHE MOBILE — le site pensé pour le téléphone, pas rétréci pour lui
   -----------------------------------------------------------------------------
   Deux familles de règles, à ne pas confondre :
     @media (max-width:820px)  → la MISE EN PAGE téléphone (barre d'identité,
                                 pieds de page empilés, grilles resserrées)
     @media (hover:none)       → le TOUCHER (états pressés, aucun état hover)
   Un iPad Pro est large ET tactile : il prend la seconde sans la première.

   ⚠️ La barre d'identité remplace les deux liens de nav. En haut d'écran ils
   ne tenaient pas et disparaissaient : le mobile n'avait PLUS AUCUNE
   navigation entre les deux mondes. En bas, sous le pouce, chaque cellule
   parle son dialecte : NOXEL en pixel, PIERRE LORET en éditorial. C'est la
   home réduite à une barre.
   ============================================================================= */
@media (max-width:820px) {

  /* ---------- barre d'identité (bas d'écran) ----------
     Elle est OPAQUE et dans la polarité de sa page : elle passe au-dessus des
     affiches de la frise, où toute transparence la rendrait illisible. */
  nav#nav .links {
    display:flex; position:fixed; left:0; right:0; top:auto; bottom:0; margin:0; gap:0;
    background:var(--bg); border-top:1px solid var(--line);
    padding-bottom:env(safe-area-inset-bottom);
  }
  /* installée en application (manifeste), la barre du haut passe sous
     l'encoche : le bandeau la suit puisqu'il est en inset:0.
     ⚠️ Padding écrit en entier : le bloc 560 px plus haut pose un raccourci
     `padding`, et un `padding-top` seul ici laisserait un bas asymétrique. */
  nav#nav { padding:calc(13px + env(safe-area-inset-top)) var(--gut) 13px; }
  nav#nav .links a { flex:1; display:flex; align-items:center; justify-content:center;
    height:56px; padding:0; color:var(--dim);
    /* le libellé long reste pour les lecteurs d'écran ; le ::before porte
       le nom court. innerHTML est réécrit par applyLang, un pseudo-élément
       ne l'est jamais : le nom survit au changement de langue. */
    font-size:0; }
  nav#nav .links a[href="/noxelmusic"]::before { content:"NOXEL";
    font-family:"Press Start 2P", monospace; font-size:9.5px; letter-spacing:1px; }
  nav#nav .links a[href="/pro"]::before { content:"PIERRE LORET";
    font-family:"Space Grotesk", sans-serif; font-weight:700; font-size:13px; letter-spacing:-.2px; }
  nav#nav .links a + a { border-left:1px solid var(--line); }
  /* le soulignement du bureau devient un carré témoin à côté du nom */
  nav#nav .links a::after { content:""; position:static; width:5px; height:5px;
    background:var(--pure); margin-left:9px; transform:none; opacity:0; transition:opacity .3s; }
  nav#nav .links a.active { color:var(--ink); }
  nav#nav .links a.active::after { opacity:1; }

  /* la barre recouvre le bas : le contenu doit pouvoir défiler au-dessus */
  body { padding-bottom:calc(56px + env(safe-area-inset-bottom)); }

  /* SON et FR·EN : seuls réglages restés en haut, ils passent au minimum
     tapable sans grossir visuellement (le trait reste fin, la boîte grandit) */
  nav#nav .tool-btn { min-height:42px; padding:8px 12px; }

  /* le lien retour est la première cible de la page : sa boîte de frappe
     s'étend sans bouger d'un pixel à l'écran */
  .backlink { padding:12px 0; margin:-12px 0 12px; }

  /* ---------- héros ----------
     min-height en svh : 88vh compte la barre d'adresse iOS repliée, et le
     premier écran débordait toujours d'un demi-titre. Les pages qui posent
     min-height:auto en ligne gardent la main (l'inline gagne). */
  .hero { min-height:min(78svh, 640px);
    padding:calc(clamp(92px,13svh,128px) + env(safe-area-inset-top))
            var(--gut) clamp(36px,6svh,56px); }
  .backlink { margin-bottom:20px; }

  /* ---------- sections ---------- */
  .sec { padding:clamp(58px,9vh,96px) var(--gut); }
  .sec--tight { padding-top:0; }
  .sec-head { margin-bottom:30px; }
  .sec-head .idx { display:none; }

  /* ---------- chiffres : toujours deux colonnes, l'orphelin en pleine largeur */
  .stats { grid-template-columns:1fr 1fr; }
  .stats > div { padding:24px 14px; }
  .stats > div:last-child:nth-child(odd) { grid-column:1 / -1; }

  /* ---------- cartes plateformes : des tuiles, pas une liste ----------
     Icône et nom suffisent sous le pouce ; la phrase et l'appel à l'action
     sont du bruit à cette taille. C'est un choix, pas un oubli. */
  .grid { grid-template-columns:1fr 1fr; gap:9px; }
  .card { min-height:0; padding:18px 15px; gap:9px; }
  .card .ic { width:38px; height:38px; }
  .card .ic .i { font-size:17px; }
  .card b { font-size:14.5px; }
  .card p, .card .go { display:none; }

  /* ---------- grille de fonctionnalités ---------- */
  .mesh > div { min-height:0; padding:20px 18px; }

  /* ---------- planche-contact : deux vignettes de front, comme une vraie */
  .sheet { grid-template-columns:1fr 1fr; }
  .cs-f { padding:22px 12px 16px; }
  .cs-edge { display:none; }
  .cs-meta b { font-size:13.5px; }

  /* ---------- lecteur ---------- */
  .player-card { padding:20px; }
  .cover { width:68px; height:68px; font-size:26px; }
  .now-meta b { font-size:16px; }
  /* précédent / suivant étaient des glyphes nus de 16 px : intapables */
  .ctl { padding:12px 10px; font-size:18px; }
  .controls { gap:6px; }
  .prog-bar { height:18px; }
  /* plus de défilement dans le défilement : la liste suit la page, plafonnée
     à un peu plus d'un demi-écran si elle s'allonge vraiment */
  .tracks { max-height:56svh; }

  /* ---------- petite chronologie : le rail fixe mangeait la colonne ---------- */
  .timeline { margin-left:0; padding-left:24px; }
  .tl::before { left:-30px; width:11px; height:11px; }

  /* ---------- l'appel à l'action du positif ----------
     Au bureau il se réduit à un filet sous le texte : le pro ne crie pas.
     Au doigt, ce filet ne disait plus « bouton » du tout, et sa boîte de
     frappe faisait 34 px. Il se ferme donc en cadre : c'est toujours un
     trait de 1 px et rien d'autre, mais il redevient une cible. */
  [data-pol="pos"] .btn--ghost {
    --ghost-pad:15px 22px; --ghost-line:0;
    box-shadow:inset 0 0 0 1px var(--pure);
    justify-content:center; }

  /* ---------- pied de page ---------- */
  footer { padding:52px var(--gut) 40px; }
  .foot { flex-direction:column; align-items:flex-start; gap:26px; }
  .socials { width:100%; display:grid; grid-template-columns:1fr 1fr; gap:8px; }
  .socials a { padding:14px; }
  .copyright { margin-top:30px; flex-direction:column; align-items:flex-start; gap:6px; }

  /* ---------- frise : l'année moins monumentale, les cartes plus serrées */
  .reel-yr { padding:26px 0; }
  .fc-t { font-size:clamp(19px,5.6vw,26px); }
}

/* Les corps de texte informatifs remontent au-dessus du seuil de lecture
   téléphone, et le dialecte négatif retrouve une taille de titre : --ds:.6
   ramenait les minimums de clamp() à 18 px pour un h2 et 13 px pour le pied
   de page, des tailles de légende, pas de titre. */
@media (max-width:560px) {
  [data-pol="neg"] .display { font-size:calc(clamp(40px,9.4vw,80px) * var(--ds)); }
  [data-pol="neg"] .foot .big { font-size:calc(clamp(30px,7.5vw,44px) * var(--ds)); }
  .fc-fmt, .reel-c { font-size:12px; letter-spacing:2px; }
  .fc-p, .fc-go { font-size:13px; }
  .cs-n, .cs-empty { font-size:11px; }
  .stats span { font-size:11.5px; letter-spacing:1px; }
  .tag { font-size:11px; }
  .m-tagline { letter-spacing:2px; gap:10px; }
  .m-tagline::before { width:20px; }
}

/* très petits écrans : on resserre encore d'un cran */
@media (max-width:390px) {
  nav#nav .links a[href="/noxelmusic"]::before { font-size:8.5px; }
  nav#nav .links a[href="/pro"]::before { font-size:12px; }
  .stats b { font-size:calc(clamp(20px,7vw,28px) * var(--ds)); }
}

/* le libellé du bouton SON s'efface sous 380 px, le symbole ♪ et le filet
   suffisent à dire l'état — refreshSnd() enveloppe le texte dans .snd-lab */
@media (max-width:380px) { nav#nav .snd-lab { display:none; } }

/* =============================================================================
   TOUCHER — le doigt n'a pas de survol, il a une pression
   -----------------------------------------------------------------------------
   Tous les retours du site vivent sur :hover : au doigt, rien ne répondait.
   Chaque surface tapable rend maintenant sa pression, dans le dialecte de sa
   polarité (la transition est GLOBALEMENT raccourcie à l'appui : un état
   pressé qui arrive en 6 dixièmes arrive après le doigt).
   ============================================================================= */
@media (hover:none) {
  /* ---------- 1. neutraliser les survols collants ----------
     Sur un écran tactile, :hover se pose au premier toucher et ne part plus :
     une carte restait soulevée, une ligne restait remplie, un bouton restait
     inversé. Chaque état hover est ramené à son repos AVANT les états
     pressés ci-dessous : pendant l'appui les deux s'appliquent, et c'est la
     dernière règle qui gagne — l'ordre de ce bloc n'est pas décoratif. */
  .card:hover { transform:none; background:var(--panel); }
  .card:hover::before { box-shadow:inset 0 0 0 1px var(--line); }
  .card:hover .go { gap:6px; color:var(--dim); }
  .row:hover { padding-left:0; }
  .row:hover::before { transform:scaleY(0); }
  .row:hover .no, .row:hover .rm { color:var(--dim); }
  .btn:hover { gap:10px; }
  .btn--line:hover { color:var(--ink); }
  .btn--line:hover::before { transform:scaleX(0); }
  .socials a:hover { color:var(--dim); box-shadow:inset 0 0 0 1px var(--line); }
  .tool:hover { background:transparent; }
  .step:hover { background:var(--bg); }
  .mesh > div:hover { background:var(--bg); }
  .cs-f:hover { background:transparent; }
  .track:hover { background:transparent; padding-left:21px; }
  .track:hover .n { color:var(--dim); }
  .track.active { background:var(--panel-2); padding-left:26px; }
  .track.active .n { color:var(--ink); }
  .ctl:hover { color:var(--dim); transform:none; }
  .play:hover { transform:none; }
  a.fc:hover .fc-vis img { transform:none; }
  a.fc:hover .fc-t::after { transform:scaleX(0); }
  a.fc:hover .fc-go { gap:7px; color:var(--dim); }
  .cs-f:hover .cs-img img { transform:none; }
  .cs-f:hover .cs-mark { opacity:0; }

  /* le rond au crayon reste posé sur les vignettes LIÉES : au doigt, rien ne
     disait qu'une vignette s'ouvre et pas sa voisine */
  a.cs-f .cs-mark { opacity:.4; stroke-dashoffset:0; }

  /* ---------- 2. la pression répond ---------- */
  .btn:active { transform:scale(.97); transition-duration:.08s; }
  .btn--line:active::before { transform:scaleX(1); transition-duration:.08s; }
  .btn--line:active { color:var(--anti); }
  [data-pol="pos"] .btn--ghost:active { transform:none; opacity:.55; }
  .card:active { transform:scale(.975); background:var(--panel-2); transition-duration:.1s; }
  .card:active::before { box-shadow:inset 0 0 0 1px var(--pure); }
  .row:active::before { transform:scaleY(1); transition-duration:.12s; }
  .tool:active { background:var(--panel); }
  .step:active { background:var(--panel); }
  .cs-f:active { background:var(--panel-2); }
  .socials a:active, nav#nav .tool-btn:active { color:var(--ink); transition-duration:.08s; }
  nav#nav .links a:active { color:var(--ink); }
  .track:active { background:var(--panel-2); }
  .ctl:active { color:var(--ink); transform:scale(1.16); }
  .play:active { transform:scale(.92); }
  /* la couleur reste la récompense du GESTE, jamais un état par défaut : au
     doigt c'est l'appui qui la fait revenir, et le monochrome tient debout */
  a.fc:active .fc-vis img { filter:grayscale(0) contrast(1); }
  a.cs-f:active .cs-img img { filter:grayscale(0) contrast(1); }
}
