/* ═══════════════════════════════════════════════════════════════════
   GUYLAINE COSSERON — « Velours & Or »
   effects.css (3/3) — écran de chargement, curseur, grain, révélations
   au défilement, barre d'écoute, visionneuse, retour en haut,
   respect de « prefers-reduced-motion », impression.
   ═══════════════════════════════════════════════════════════════════ */

/* ═══════════════ ÉCRAN DE CHARGEMENT ═══════════════ */
.preloader{
  position:fixed; inset:0; z-index:300;
  display:grid; place-items:center;
  background:var(--ink);
  transition:opacity .8s var(--ease), visibility .8s;
}
.preloader.is-done{ opacity:0; visibility:hidden; }
/* Le voile se relève comme un rideau de scène */
.preloader-veil{
  position:absolute; inset:0;
  background:linear-gradient(180deg,#0e0b07,#060505);
  transform-origin:top;
  transition:transform 1.05s var(--ease);
}
.preloader.is-done .preloader-veil{ transform:scaleY(0); }

.preloader-inner{
  position:relative; z-index:1;
  display:flex; flex-direction:column; align-items:center; gap:1.3rem;
  transition:opacity .5s var(--ease), transform .7s var(--ease);
}
.preloader.is-done .preloader-inner{ opacity:0; transform:translateY(-18px); }

.preloader-mark{ width:104px; height:104px; overflow:visible; }
.pm-ring{
  fill:none; stroke:#c8a25e; stroke-width:1; opacity:.55;
  stroke-dasharray:327; stroke-dashoffset:327;
  animation:ringDraw 1.6s var(--ease) forwards;
}
@keyframes ringDraw{ to{ stroke-dashoffset:0; } }
.pm-text{
  fill:#c8a25e; font-family:var(--font-display); font-weight:600; font-size:34px;
  opacity:0; animation:markIn 1s var(--ease) .3s forwards;
}
@keyframes markIn{ to{ opacity:1; } }
.preloader-name{
  font-family:var(--font-display); font-size:1.1rem; letter-spacing:.24em;
  text-transform:uppercase; color:rgba(243,237,226,.7);
  opacity:0; animation:markIn 1s var(--ease) .55s forwards;
}
.preloader-name em{ font-style:italic; color:#c8a25e; text-transform:none; letter-spacing:.06em; }
.preloader-bar{ width:190px; height:1px; background:rgba(200,162,94,.22); overflow:hidden; }
.preloader-bar span{
  display:block; height:100%; width:0%;
  background:linear-gradient(90deg,#96703a,#e8d5a3);
  transition:width .35s var(--ease-soft);
}
.preloader-count{
  font-size:.66rem; letter-spacing:.3em; color:rgba(243,237,226,.45);
  font-variant-numeric:tabular-nums;
}

/* ═══════════════ BARRE DE PROGRESSION DE LECTURE ═══════════════ */
.scroll-progress{
  position:fixed; top:0; left:0; right:0; z-index:80;
  height:2px; transform:scaleX(0); transform-origin:left;
  background:linear-gradient(90deg,var(--gold-deep),var(--gold),var(--gold-soft));
  box-shadow:0 0 14px rgba(200,162,94,.55);
}

/* ═══════════════ MATIÈRE : GRAIN + VIGNETTAGE ═══════════════ */
.grain{
  position:fixed; inset:-50%; z-index:200; pointer-events:none;
  opacity:.045; mix-blend-mode:overlay;
  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='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grainShift 8s steps(6) infinite;
}
@keyframes grainShift{
  0%{ transform:translate(0,0); }
  20%{ transform:translate(-4%,2%); }
  40%{ transform:translate(3%,-3%); }
  60%{ transform:translate(-2%,-2%); }
  80%{ transform:translate(2%,3%); }
  100%{ transform:translate(0,0); }
}
.vignette{
  position:fixed; inset:0; z-index:199; pointer-events:none;
  background:radial-gradient(120% 100% at 50% 50%, transparent 55%, rgba(0,0,0,.45) 100%);
  opacity:.7;
}
[data-theme="ivoire"] .vignette{ opacity:.25; }
[data-theme="ivoire"] .grain{ mix-blend-mode:multiply; opacity:.05; }

/* ═══════════════ LA SALLE : UNE SEULE AMBIANCE DERRIÈRE TOUT ═══════════════
   Le grain est une MATIÈRE, pas un lieu : il vibre sur place en 8 s. Ce qui
   manquait au site est autre chose — la sensation de traverser un endroit au
   lieu de défiler devant des sections empilées. D'où cette couche unique,
   fixe, derrière tout le reste : deux halos chauds qui dérivent lentement,
   comme deux projecteurs de salle qu'on aurait laissés allumés.

   Trois nombres portent l'effet, et chacun a sa raison :
   · 96 s de cycle — au-delà d'une minute, l'œil ne perçoit plus un mouvement
     mais un changement d'air. En dessous, il repère la boucle et l'ornement
     devient un tic.
   · opacité 1 sur des halos déjà à 5 % et 3,5 % — l'ambiance doit se voir
     seulement quand on la cherche. Un décor qu'on remarque vole l'attention
     que la chanteuse a payée.
   · un délai négatif (-38 s) — la page ne s'ouvre pas au début de la boucle,
     elle s'ouvre au milieu. Sans lui, les deux halos partent du même point à
     chaque visite et l'on voit le mécanisme.
   Seul « transform » est animé : aucun recalcul de mise en page, la couche
   vit sur le processeur graphique et ne coûte rien au défilement. */
.ambiance{
  position:fixed; inset:-25%; z-index:198; pointer-events:none;
  background:
    radial-gradient(38% 30% at 22% 28%, rgba(200,162,94,.050), transparent 70%),
    radial-gradient(42% 34% at 78% 72%, rgba(74,18,32,.035), transparent 72%);
  animation:ambianceDerive 96s var(--ease-soft,ease-in-out) -38s infinite alternate;
  will-change:transform;
}
@keyframes ambianceDerive{
  0%{   transform:translate3d(-2.5%,-1.5%,0) scale(1.00); }
  50%{  transform:translate3d(2%,2.5%,0)     scale(1.06); }
  100%{ transform:translate3d(3%,-2%,0)      scale(1.02); }
}
/* Sur le thème clair, la même lumière posée sur de l'ivoire donnerait un
   voile sale : on l'assombrit au lieu de l'éclairer. */
[data-theme="ivoire"] .ambiance{
  background:
    radial-gradient(38% 30% at 22% 28%, rgba(150,112,58,.055), transparent 70%),
    radial-gradient(42% 34% at 78% 72%, rgba(74,18,32,.030), transparent 72%);
}

/* ═══════════════ ONGLET CACHÉ : PLUS RIEN NE TOURNE ═══════════════
   Les boucles JavaScript s'arrêtaient déjà quand l'onglet passe à l'arrière-
   plan. Les animations CSS, elles, continuaient : le grain, le bandeau
   défilant, les halos, les pulsations. Un site qui fait chauffer un
   téléphone rangé dans une poche n'est pas un site cher, quelle que soit son
   apparence.

   Le sélecteur est volontairement brutal, et c'est nécessaire :
   « animation-play-state » N'EST PAS une propriété héritée. La poser sur
   <body> ne descend ni dans les enfants ni dans les ::before — la règle
   paraîtrait écrite et n'arrêterait rien. */
body.est-en-pause *,
body.est-en-pause *::before,
body.est-en-pause *::after{ animation-play-state:paused !important; }

/* ═══════════════ MOUVEMENT RÉDUIT ACTIVÉ EN COURS DE ROUTE ═══════════════
   Le bloc @media plus bas suffit à tout ce que le CSS gouverne, et il est
   vivant par nature : une requête média se réévalue seule dès que le
   réglage change. Ce qui ne l'est pas, ce sont les transformations que le
   JavaScript écrit EN LIGNE — parallaxe, inclinaison des cartes, aimantation
   des boutons, curseur dessiné, inclinaison du bandeau. Un style en ligne
   bat une feuille de style ; seul « !important » le bat en retour.

   D'où cette classe, posée par animations.js sur l'événement « change ». Les
   boucles ne sont pas démontées : elles continuent d'écrire dans le vide, et
   elles ne coûtent rien puisqu'elles ne tournent que pendant un mouvement.
   Le jour où le visiteur remet les animations, il suffit d'ôter la classe et
   tout repart — aucun état à défaire, donc aucun oubli possible. */
body.mvt-reduit [data-parallax],
body.mvt-reduit [data-tilt],
body.mvt-reduit [data-magnetic],
body.mvt-reduit .marquee-track,
body.mvt-reduit .marquee{ transform:none !important; }
/* Le prélude n'a pas de ligne ici, et ce n'est pas un oubli : il se replie
   tout seul, des deux côtés à la fois. Sa porte est une requête média
   (@media prefers-reduced-motion, dans css/prelude.css), donc le navigateur
   l'applique à la seconde où le réglage change, sans qu'aucune classe n'ait
   besoin d'être posée ; et son moteur écoute le même « change » pour libérer
   sa boucle et figer les états finaux. Écrire une troisième vérité ici, ce
   serait s'exposer à ce qu'elle diverge des deux autres. */
body.mvt-reduit .ambiance{ animation:none !important; }
/* Le geste survit, sa corde ne bouge plus : voir le commentaire du bloc
   @media (prefers-reduced-motion) plus bas. Les deux chemins doivent dire la
   même chose, sinon le réglage changé EN COURS DE VISITE donnerait un autre
   résultat que le même réglage au chargement. */
body.mvt-reduit .note .voix-svg{ transform:none !important; }

/* ═══════════════ DOIGTS : 44 px DE CIBLE, SANS BOUGER LA MISE EN PAGE ═══════
   Un pouce ne vise pas comme une souris : les référentiels d'accessibilité
   demandent 44 px de côté. Plusieurs commandes du site (flèches de la
   visionneuse, points de navigation, minuscules liens de pied de page)
   étaient dessinées pour un curseur.

   Le piège évité ici : « display:inline-flex » sans redéclarer l'alignement
   recadre le libellé à gauche et fait rétrécir un bouton pleine largeur à la
   taille de son texte. L'alignement est donc réécrit dans la même règle. */
@media (pointer:coarse){
  .btn,.btn-solid,.btn-ghost,.lb-prev,.lb-next,.lb-close,.to-top,
  .dock-handle,.play-btn,.nav-toggle,.theme-toggle,.mm-close{
    min-height:44px; min-width:44px;
    display:inline-flex; align-items:center; justify-content:center;
  }
  /* Les repères de section sont un rail de traits de 1 px : leur agrandir le
     TRAIT le détruirait. C'est le lien qui l'entoure qu'on agrandit, et son
     alignement d'origine (à droite, en ligne) est reconduit tel quel. */
  .dots-nav a{
    min-height:44px;
    display:flex; align-items:center; justify-content:flex-end;
  }
}

/* ═══════════════ RÉVÉLATIONS AU DÉFILEMENT ═══════════════ */
[data-reveal],.reveal{
  opacity:0; transform:translateY(34px);
  transition:opacity .95s var(--ease), transform .95s var(--ease), filter .95s var(--ease);
  will-change:opacity,transform;
}
[data-reveal="left"]{ transform:translateX(-40px); }
[data-reveal="right"]{ transform:translateX(40px); }
[data-reveal="zoom"]{ transform:scale(.94); }
[data-reveal="blur"]{ transform:translateY(20px); filter:blur(10px); }
[data-reveal].is-revealed,.reveal.is-revealed{
  opacity:1; transform:none; filter:none;
}
/* Titres découpés en mots : chaque mot monte derrière un voile */
.is-split{ overflow:hidden; }
.split-word{
  display:inline-block; overflow:hidden;
  padding-bottom:.06em; margin-bottom:-.06em;
  vertical-align:bottom;
}
.split-inner{
  display:inline-block;
  transform:translateY(105%);
  transition:transform 1.05s var(--ease);
}
.is-in-view .split-inner{ transform:none; }
.split-word:nth-child(1) .split-inner{ transition-delay:.02s; }
.split-word:nth-child(2) .split-inner{ transition-delay:.09s; }
.split-word:nth-child(3) .split-inner{ transition-delay:.16s; }
.split-word:nth-child(4) .split-inner{ transition-delay:.23s; }
.split-word:nth-child(5) .split-inner{ transition-delay:.30s; }
.split-word:nth-child(6) .split-inner{ transition-delay:.37s; }

/* ═══════════════ LA LIGNE DE VOIX ═══════════════
   Le parti pris du site tient en une phrase : *une seule ligne continue
   traverse tout le site, et cette ligne est la voix*. Elle est déjà gravée
   sur les 19 plaques dessinées (tools/make-placeholders.mjs) ; ici, elle
   passe D'UNE SECTION À L'AUTRE, et elle se trace au défilement.

   Pourquoi ce mécanisme et pas `animation-timeline: view()`, que le plan
   proposait : le site a DÉJÀ une vérité pour « cet élément est entré dans
   l'écran » — la classe .is-revealed, posée par ScrollTrigger et, si GSAP
   manque, par l'IntersectionObserver de repli. Ajouter une chronologie
   native créerait une seconde vérité, qui divergerait en silence le jour
   où l'une des deux change de seuil. Une règle de l'architecture : le CSS
   décide de l'apparence, le JavaScript ne décide que du déclenchement.
   Bonus concret : `animation-timeline` n'existe pas dans Safari avant 26 ;
   .is-revealed marche partout, aujourd'hui.

   Le tracé lui-même est le procédé de la signature manuscrite (.sig-path) :
   un pointillé aussi long que le trait, décalé hors du trait, puis ramené
   à zéro. C'est le même outil pour le même effet — pas un second.

   Et la géométrie n'est écrite QU'UNE FOIS dans la page, dans un <defs> ;
   les six séparateurs la réclament par <use>. Ce n'est pas une économie
   d'octets, c'est la fidélité au parti pris : il y a littéralement un seul
   tracé dans le document. Six copies collées auraient divergé au premier
   ajustement, et « une seule ligne » serait redevenue une figure de style.
   Le <use> reçoit les propriétés de trait (stroke, dasharray, dashoffset) et
   les transmet au clone : elles sont héritables en SVG, c'est ce qui permet
   de peindre deux fois la même forme — un halo large, un trait fin. */
.voix-source{ position:absolute; width:0; height:0; overflow:hidden; }
.voix{
  /* Le voile de [data-reveal] est neutralisé ici : la ligne n'a pas à
     monter en fondu EN PLUS de se dessiner. Deux mouvements simultanés sur
     le même objet se lisent comme une hésitation, jamais comme un geste.
     On garde l'attribut parce qu'il apporte .is-revealed ; on lui retire
     seulement son apparence. */
  opacity:1; transform:none;
  width:100%; max-width:var(--container); margin-inline:auto;
  padding-inline:var(--gutter);
  line-height:0;                 /* pas d'espace fantôme sous le SVG en ligne */
}
.voix-svg{
  /* Pas de `preserveAspectRatio="none"` ni de hauteur imposée : l'échelle
     reste uniforme, donc l'onde garde sa forme et le trait son épaisseur
     apparente. Une hauteur figée avec un viewBox large obligerait à
     déformer le repère — la ligne s'aplatirait sur grand écran et se
     hérisserait sur téléphone, et `stroke-dasharray` ne mesurerait plus la
     même longueur que le tracé. */
  display:block; width:100%; height:auto;
  overflow:visible;
}
.voix-trait{
  fill:none; stroke-linecap:round;
  /* 1203 = longueur mesurée du tracé dans le repère du viewBox ; on prend
     un peu plus pour que l'extrémité soit franchement hors champ au repos. */
  stroke-dasharray:1220; stroke-dashoffset:1220;
  transition:stroke-dashoffset 2.4s var(--ease-soft) .15s;
}
.voix-halo{ stroke:var(--gold); stroke-opacity:.14; stroke-width:7; }
.voix-fin{ stroke:var(--gold-soft); stroke-opacity:.5; stroke-width:1.4; }
.voix.is-revealed .voix-trait{ stroke-dashoffset:0; }
/* Un segment sur deux repart dans l'autre sens : la ligne ne se répète pas
   à l'identique d'une section à la suivante. Le miroir est horizontal, donc
   les deux extrémités restent à la MÊME hauteur — c'est cette hauteur
   commune qui fait lire six segments comme une seule ligne reprise, et non
   comme six ornements posés côte à côte. */
.voix[data-voix="retour"] .voix-svg{ transform:scaleX(-1); }

/* ── La ligne au premier écran ──────────────────────────────────────────
   Le parti pris promettait qu'elle « se tend en portée à l'accueil ». Elle y
   manquait : les six séparateurs la faisaient traverser la page, mais le
   premier écran — le seul que tout le monde voit — ne la montrait pas. Un
   motif qui n'apparaît qu'après le premier défilement n'est pas le motif du
   site, c'est sa décoration interne.

   Trois décisions, et aucune n'ajoute un second motif :

     · même géométrie, réclamée par le même <use href="#voix-trace">. Il n'y
       a toujours qu'UN seul tracé dans le document ;

     · elle est APLATIE, pas redessinée. `scaleY` autour du centre du repère
       laisse les deux extrémités exactement à y=28 — cette hauteur commune
       est ce qui fait lire tous les segments comme une seule ligne reprise.
       Une ligne tendue n'est pas une autre ligne : c'est la même, sous
       tension. Le mot du parti pris, rendu littéral ;

     · elle se trace sur `body.is-loaded`, pas sur `.is-revealed`. Un élément
       déjà visible au chargement n'entre JAMAIS dans l'écran : un
       ScrollTrigger posé là ne partirait qu'au premier défilement, et la
       ligne surgirait en retard, comme un défaut. Le site a déjà une vérité
       pour « le rideau est levé » — celle qui prend les mesures du film.
       On s'en sert au lieu d'en inventer une.

   `z-index:1` et non -1 : le prélude empile derrière elle un canvas et deux
   voiles de lisibilité, et le voile par bande monte jusqu'à 0,66 d'opacité.
   Dessous, la ligne serait effacée par ce qui est censé la protéger.

   Le positionnement ci-dessous est celui de l'ANCIEN héros, où la ligne
   barrait le bas de l'écran. css/prelude.css la remet dans le flux, sous le
   nom (voir « .prelude .voix-hero »). Les deux règles cohabitent sans
   !important, par sélectivité, et celle-ci reste utile telle quelle : c'est
   elle qui vaut sur 404.html, où il n'y a pas de prélude. */
.voix-hero{
  position:absolute; z-index:1;
  left:0; right:0; width:auto;
  bottom:clamp(4.4rem,10vh,6.2rem);
  pointer-events:none;
}
.voix-hero .voix-svg{ transform:scaleY(.5); }
.voix-hero .voix-trait{
  /* Plus lente et plus tardive que le nom, et volontairement en retard sur
     lui : on lit « le nom, puis sa voix », et non deux mouvements qui se
     disputent le premier écran. */
  transition:stroke-dashoffset 3.2s var(--ease-soft) 1.05s;
}
/* Discrète par principe : au premier écran, le nom doit rester la seule
   chose qu'on regarde. La ligne se remarque quand on cesse de lire.

   L'épaisseur est CORRIGÉE, et ce n'est pas un réglage d'esthète : aplatir
   au `scaleY` écrase aussi le trait. Un trait presque horizontal ne montre
   que son épaisseur verticale — à `scaleY(.5)`, 1,4 px n'en font plus que
   0,7, et le trait fin disparaît des écrans ordinaires. On double donc les
   deux épaisseurs pour retrouver exactement l'apparence des séparateurs. */
.voix-hero .voix-halo{ stroke-width:14; stroke-opacity:.08; }
.voix-hero .voix-fin{ stroke-width:2.8; stroke-opacity:.34; }
body.is-loaded .voix-hero .voix-trait{ stroke-dashoffset:0; }

/* ═══════════════ « TENIR LA NOTE » ═══════════════
   Le seul geste du site. Tout passe par UNE variable, --p, que le
   JavaScript pousse de 0 à 1 image par image et que le CSS lit partout :
   la tension de la ligne, l'anneau, le halo. Le JavaScript ne décide de
   rien d'autre que « où en est la pression » — l'apparence reste
   entièrement ici, comme le reste du site.

   Pourquoi --p et pas une classe par palier : un geste analogique n'a pas
   de paliers. Une classe .est-a-moitie mentirait sur ce qui se passe, et
   le retour en douceur au relâchement deviendrait impossible à écrire. */
.note{
  --p:0;
  position:relative;
  max-width:820px; margin:clamp(1.6rem,4vw,2.8rem) auto clamp(2rem,5vw,3.2rem);
  text-align:center;
}
/* La ligne s'APLATIT à mesure qu'on tient : c'est le même vocabulaire que
   le premier écran, où « tendue » veut déjà dire « aplatie ».
   Les deux épaisseurs grossissent en même temps, et ce n'est pas un effet :
   un scaleY écrase aussi le trait. À p=1 l'échelle vaut .38 ; 19 × .38 ≈ 7 et
   3,6 × .38 ≈ 1,4, soit exactement les épaisseurs du repos. La ligne se tend
   donc SANS maigrir — sinon on lirait « elle s'efface » au lieu de
   « elle se tend ». */
.note .voix-svg{ transform:scaleY(calc(1 - .62 * var(--p))); transform-origin:50% 50%; }
.note .voix-halo{ stroke-width:calc(7px + 12px * var(--p)); stroke-opacity:calc(.14 + .34 * var(--p)); }
.note .voix-fin{ stroke-width:calc(1.4px + 2.2px * var(--p)); stroke-opacity:calc(.5 + .5 * var(--p)); }
.note.is-revealed .voix-trait{ stroke-dashoffset:0; }

.note-lueur{
  position:absolute; left:50%; top:50%; z-index:0;
  width:min(72%,520px); aspect-ratio:2 / 1;
  transform:translate(-50%,-50%) scale(calc(.84 + .34 * var(--p)));
  background:radial-gradient(50% 50% at 50% 50%, rgba(200,162,94,.34), transparent 70%);
  opacity:calc(var(--p) * .95);
  filter:blur(16px); pointer-events:none;
}
.note-btn{
  position:relative; z-index:2;
  display:inline-flex; flex-direction:column; align-items:center; gap:.7rem;
  /* Le bouton chevauche la ligne : il n'est pas posé À CÔTÉ de l'instrument,
     il est SUR la corde. Sans ce décalage, on lirait deux objets. */
  margin-top:clamp(-2.4rem,-4vw,-1.4rem);
  padding:.6rem 1.2rem;
  background:none; border:0; color:inherit; cursor:pointer;
  /* Sur écran tactile, maintenir un doigt veut dire « fais défiler » pour le
     navigateur, et « ouvre le menu » au bout d'une seconde. Les deux
     annuleraient le geste avant qu'il commence. */
  touch-action:none; -webkit-touch-callout:none;
  user-select:none; -webkit-user-select:none;
}
.note-btn:focus-visible{ outline:1px solid var(--gold); outline-offset:8px; }
.note-anneau{
  width:66px; height:66px; border-radius:50%;
  /* L'anneau EST la progression : un dégradé conique dont l'angle vaut --p.
     Aucune image, aucun SVG de plus, et le masque le réduit à un filet. */
  background:conic-gradient(from -90deg,
    var(--gold) calc(var(--p) * 360deg), rgba(200,162,94,.18) 0);
  mask:radial-gradient(closest-side, transparent 70%, #000 71%);
  -webkit-mask:radial-gradient(closest-side, transparent 70%, #000 71%);
  transform:scale(calc(1 + .07 * var(--p)));
}
.note-libelle{
  font-family:var(--font-mono); font-size:.6rem; letter-spacing:.18em;
  text-transform:uppercase; color:var(--gold);
}
.note-aide{ margin:.9rem 0 0; font-size:.74rem; color:var(--faint); }
.note-etat{ margin:.5rem 0 0; min-height:1.2em; font-size:.74rem; color:var(--gold); }
.note.est-tenue .note-btn{ cursor:default; }
.note.est-tenue .note-aide{ opacity:0; transition:opacity .6s var(--ease-soft); }

/* La récompense : la section s'allume dans l'ordre où on la lit. Une vague de
   lumière qui descend, pas quatre éléments qui clignotent ensemble — c'est le
   décalage qui fait « on allume une salle » plutôt que « un interrupteur ».
   Rien n'est CONDITIONNÉ au geste : la presse se lit entièrement sans avoir
   tenu la note. Un moment offert ne doit jamais devenir un péage. */
#presse .press-bg{ opacity:.72; transition:opacity 1.8s var(--ease-soft), transform 2.4s var(--ease-soft); }
#presse.est-eclairee .press-bg{ opacity:1; transform:scale(1.12); }
#presse.est-eclairee .kicker{ animation:noteEclat 1.5s var(--ease-soft) both; }
#presse.est-eclairee .section-title{ animation:noteEclat 1.5s var(--ease-soft) .18s both; }
#presse.est-eclairee .press-quote blockquote{ animation:noteEclat 1.5s var(--ease-soft) .36s both; }
#presse.est-eclairee .press-author{ animation:noteEclat 1.5s var(--ease-soft) .54s both; }
@keyframes noteEclat{
  0%{   transform:none; }
  32%{  transform:translateY(-4px); }
  100%{ transform:none; }
}

/* ═══════════════ BARRE D'ÉCOUTE ═══════════════ */
.dock{
  position:fixed; left:0; right:0; bottom:0; z-index:85;
  pointer-events:none;
  visibility:hidden;
}
.dock.is-ready{ visibility:visible; }
.dock > *{ pointer-events:auto; }

.dock-inner{
  display:grid;
  grid-template-columns:auto minmax(110px,1.1fr) minmax(140px,2.4fr) auto auto;
  align-items:center; gap:clamp(.7rem,1.6vw,1.5rem);
  padding:.85rem clamp(1rem,3vw,2.2rem);
  background:rgba(10,9,8,.82);
  backdrop-filter:blur(22px) saturate(140%);
  -webkit-backdrop-filter:blur(22px) saturate(140%);
  border-top:1px solid var(--line);
  transform:translateY(105%);
  transition:transform .8s var(--ease);
}
[data-theme="ivoire"] .dock-inner{ background:rgba(235,229,217,.9); }
.dock.is-open .dock-inner{ transform:none; }

/* Poignée « Écouter », visible quand la barre est repliée */
.dock-handle{
  position:absolute; bottom:0; right:clamp(1rem,3vw,2.2rem);
  display:inline-flex; align-items:center; gap:.6rem;
  padding:.55rem 1.1rem;
  background:rgba(10,9,8,.82);
  backdrop-filter:blur(18px); -webkit-backdrop-filter:blur(18px);
  border:1px solid var(--line); border-bottom:0;
  font-family:var(--font-mono);
  font-size:.62rem; letter-spacing:.16em; text-transform:uppercase; color:var(--gold-ink);
  transform:translateY(0);
  transition:opacity .5s var(--ease), transform .6s var(--ease), background .3s;
}
[data-theme="ivoire"] .dock-handle{ background:rgba(235,229,217,.9); }
.dock.is-open .dock-handle{ opacity:0; transform:translateY(100%); pointer-events:none; }
.dock-handle:hover{ color:var(--gold); }
.dh-eq{ display:inline-flex; align-items:flex-end; gap:2px; height:10px; }
.dh-eq i{ width:2px; height:100%; background:currentColor; transform-origin:bottom;
  animation:eqBar 1.2s ease-in-out infinite; }
.dh-eq i:nth-child(2){ animation-delay:.2s; }
.dh-eq i:nth-child(3){ animation-delay:.4s; }
/* Bouton lecture : disque doré avec anneau qui respire */
.dock-play{
  position:relative; width:44px; height:44px; border-radius:50%;
  display:grid; place-items:center;
  background:linear-gradient(150deg,var(--gold-soft),var(--gold-deep));
  color:#120e09; flex:none;
  transition:transform .4s var(--ease), box-shadow .4s var(--ease);
}
.dock-play:hover{ transform:scale(1.07); box-shadow:0 0 26px rgba(200,162,94,.4); }
.dock-play svg{ width:20px; height:20px; fill:currentColor; }
.dp-play,.dp-pause{ grid-area:1/1; transition:opacity .25s var(--ease), transform .35s var(--ease); }
.dp-pause{ opacity:0; transform:scale(.7); }
.dock.is-playing .dp-play{ opacity:0; transform:scale(.7); }
.dock.is-playing .dp-pause{ opacity:1; transform:none; }
/* L'anneau extérieur ne pulse que pendant la lecture */
.dp-ring{
  position:absolute; inset:-7px; width:auto; height:auto;
  fill:none; stroke:var(--gold); stroke-width:1; opacity:0;
}
.dock.is-playing .dp-ring{ animation:dockPulse 2.4s ease-out infinite; }
@keyframes dockPulse{
  0%{ opacity:.55; transform:scale(.86); }
  100%{ opacity:0; transform:scale(1.22); }
}

/* Titre et album en cours */
.dock-meta{ display:flex; flex-direction:column; gap:.15rem; min-width:0; }
.dm-title{
  font-family:var(--font-display); font-size:.98rem; color:var(--text);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.dm-album{
  font-family:var(--font-mono);
  font-size:.58rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
/* Ligne d'écoute : forme d'onde dessinée au canvas + remplissage doré */
.dock-seek{
  position:relative; height:34px; cursor:pointer;
  border-radius:2px; overflow:hidden;
}
.dock-wave{
  position:absolute; inset:0; width:100%; height:100%;
  opacity:.5; transition:opacity .3s var(--ease);
}
.dock-seek:hover .dock-wave{ opacity:.8; }
.ds-fill{
  position:absolute; top:0; bottom:0; left:0; width:0%;
  background:linear-gradient(90deg,rgba(150,112,58,.28),rgba(200,162,94,.34));
  border-right:1px solid var(--gold-soft);
  pointer-events:none;
  transition:width .12s linear;
}
.dock-seek:focus-visible{ outline:1px solid var(--gold); outline-offset:3px; }

.dock-time{
  /* Le seul endroit du site où un chiffre change dix fois par seconde : la
     largeur fixe du mono rend la ligne parfaitement immobile. */
  font-family:var(--font-mono);
  font-size:.66rem; letter-spacing:.04em; color:var(--muted);
  font-variant-numeric:tabular-nums; white-space:nowrap;
}
.dock-time b{ color:var(--gold-ink); font-weight:400; }

.dock-controls{ display:inline-flex; align-items:center; gap:.35rem; }
.dock-controls button{
  width:34px; height:34px; border-radius:50%;
  display:grid; place-items:center; color:var(--muted);
  transition:color .3s var(--ease), background .3s var(--ease);
}
.dock-controls button svg{ width:16px; height:16px; fill:currentColor; }
.dock-controls button:hover{ color:var(--gold); background:rgba(200,162,94,.1); }
.dock-close svg{ fill:none !important; stroke:currentColor; stroke-width:1.6; }

/* Message honnête quand aucun extrait n'est encore déposé */
.dock-note{
  position:absolute; bottom:100%; left:0; right:0;
  padding:.5rem clamp(1rem,3vw,2.2rem);
  background:rgba(10,9,8,.9);
  backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  border-top:1px solid var(--line);
  font-family:var(--font-mono);
  font-size:.64rem; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); text-align:center;
  opacity:0; transform:translateY(10px);
  transition:opacity .5s var(--ease), transform .5s var(--ease);
}
[data-theme="ivoire"] .dock-note{ background:rgba(235,229,217,.94); }
.dock.is-open .dock-note{ opacity:1; transform:none; }
.dock-note[hidden]{ display:none; }
/* ═══════════════ VISIONNEUSE (photos & vidéos) ═══════════════ */
.lightbox{
  position:fixed; inset:0; z-index:250;
  display:grid; place-items:center;
  padding:clamp(1rem,5vw,4rem);
  opacity:0; visibility:hidden;
  transition:opacity .55s var(--ease), visibility .55s;
}
.lightbox.is-open{ opacity:1; visibility:visible; }
.lb-backdrop{
  position:absolute; inset:0;
  background:rgba(6,5,4,.92);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
}
.lb-figure{
  position:relative; z-index:1; margin:0;
  max-width:min(1180px,100%);
  display:flex; flex-direction:column; gap:1rem; align-items:center;
  transform:scale(.94) translateY(18px); opacity:0;
  transition:transform .75s var(--ease), opacity .6s var(--ease);
}
.lightbox.is-open .lb-figure{ transform:none; opacity:1; }
.lb-media{
  position:relative; width:100%;
  max-height:min(76vh,900px);
  display:grid; place-items:center;
  border:1px solid rgba(200,162,94,.22);
  background:var(--placeholder);
  overflow:hidden;
}
.lb-media img,.lb-media video{
  display:block; width:100%; max-height:min(76vh,900px);
  object-fit:contain; background:#080706;
}
.lb-media iframe{ display:block; width:100%; aspect-ratio:16/9; border:0; }
.lb-media.is-empty{ width:100%; aspect-ratio:16/9; }
.lb-media.is-empty::after{
  content:"GC"; font-family:var(--font-display); font-size:clamp(3rem,9vw,7rem);
  color:transparent; -webkit-text-stroke:1px rgba(200,162,94,.4);
  letter-spacing:.08em;
}
.lb-caption{
  font-family:var(--font-mono);
  font-size:.68rem; letter-spacing:.16em; text-transform:uppercase;
  color:rgba(243,237,226,.62); text-align:center;
}
/* Commandes de la visionneuse */
.lb-close,.lb-arrow{
  position:absolute; z-index:2;
  display:grid; place-items:center;
  color:rgba(243,237,226,.75);
  border:1px solid rgba(200,162,94,.28);
  background:rgba(12,10,8,.5);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  transition:color .35s var(--ease), border-color .35s var(--ease),
             background .35s var(--ease), transform .35s var(--ease);
}
.lb-close svg,.lb-arrow svg{
  width:20px; height:20px; fill:none; stroke:currentColor;
  stroke-width:1.4; stroke-linecap:round;
}
.lb-close{ top:clamp(.8rem,2.4vw,1.8rem); right:clamp(.8rem,2.4vw,1.8rem); width:46px; height:46px; border-radius:50%; }
.lb-arrow{ top:50%; margin-top:-27px; width:54px; height:54px; border-radius:50%; }
.lb-prev{ left:clamp(.5rem,2vw,1.8rem); }
.lb-next{ right:clamp(.5rem,2vw,1.8rem); }
.lb-close:hover,.lb-arrow:hover{
  color:var(--gold-soft); border-color:var(--gold);
  background:rgba(200,162,94,.14);
}
.lb-prev:hover{ transform:translateX(-4px); }
.lb-next:hover{ transform:translateX(4px); }

/* ═══════════════ RETOUR EN HAUT ═══════════════ */
.to-top{
  position:fixed; right:clamp(1rem,2.4vw,2rem); bottom:clamp(5.2rem,8vw,6.4rem);
  z-index:84; width:52px; height:52px; border-radius:50%;
  display:grid; place-items:center;
  /* --gold-ink et non --gold-soft : sur le thème ivoire, cette pastille
     devient claire (voir la règle [data-theme="ivoire"] plus bas) et un or
     clair sur fond clair donne 2,1:1 — une flèche qu'on devine au lieu de
     la voir. Le jeton suit le thème ; la couleur n'est écrite qu'une fois. */
  color:var(--gold-ink);
  background:rgba(10,9,8,.7);
  backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
  border:1px solid var(--line);
  opacity:0; visibility:hidden; transform:translateY(14px) scale(.9);
  transition:opacity .5s var(--ease), transform .6s var(--ease),
             visibility .5s, background .3s var(--ease), color .3s var(--ease);
}
[data-theme="ivoire"] .to-top{ background:rgba(235,229,217,.8); }
.to-top.is-visible{ opacity:1; visibility:visible; transform:none; }
.to-top:hover{ color:var(--gold); background:rgba(200,162,94,.14); transform:translateY(-3px); }
.to-top > svg:first-of-type{
  width:18px; height:18px; fill:none; stroke:currentColor;
  stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round;
}
/* Anneau de progression : la circonférence se remplit avec le défilement */
.tt-ring{
  position:absolute; inset:-1px; width:auto; height:auto;
  fill:none; stroke:var(--gold); stroke-width:1.4;
  transform:rotate(-90deg);
  stroke-dasharray:113; stroke-dashoffset:113;
  transition:stroke-dashoffset .18s linear;
}
/* ═══════════════ ADAPTATION AUX PETITS ÉCRANS ═══════════════ */
@media (max-width:900px){
  .dock-inner{ grid-template-columns:auto minmax(90px,1fr) minmax(110px,2fr) auto; }
  .dock-time{ display:none; }
}
@media (max-width:640px){
  /* Deux rangées : infos + commandes en haut, ligne d'écoute en dessous */
  .dock-inner{
    grid-template-columns:auto 1fr auto;
    grid-template-areas:"play meta ctrl" "seek seek seek";
    gap:.5rem .8rem; padding:.7rem 1rem .8rem;
  }
  .dock-play{ grid-area:play; width:40px; height:40px; }
  .dock-play svg{ width:18px; height:18px; }
  .dock-meta{ grid-area:meta; }
  .dock-controls{ grid-area:ctrl; }
  .dock-seek{ grid-area:seek; height:26px; }
  .to-top{ width:46px; height:46px; bottom:clamp(4.6rem,14vw,5.6rem); }
  .lb-arrow{ width:44px; height:44px; margin-top:-22px; }
  .lb-close{ width:40px; height:40px; }
  .preloader-mark{ width:84px; height:84px; }
  .preloader-bar{ width:150px; }
}

/* ═══════════════ MOUVEMENT RÉDUIT (accessibilité) ═══════════════
   Certains utilisateurs demandent au système de limiter les animations
   (vertiges, migraines). On respecte ce choix : tout reste lisible,
   seulement plus rien ne bouge. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
  [data-reveal],.reveal,.split-inner{ opacity:1 !important; transform:none !important; filter:none !important; }
  .ht-word .ltr{ transform:none !important; }
  .grain{ display:none; }
  /* L'ambiance dérive : c'est exactement ce qu'on demande d'arrêter. La
     lumière reste, seul le mouvement part — on retire l'animation, pas la
     couche, sinon le site perd son atmosphère alors qu'on n'a demandé qu'à
     ne rien voir bouger. */
  .ambiance{ animation:none !important; transform:none !important; }
  .preloader{ display:none; }
  .dp-ring{ display:none; }
  /* La ligne de voix ne se dessine plus : elle est déjà là. Le trait n'est
     pas un ornement animé, c'est le motif du site — le retirer priverait
     l'utilisateur du parti pris, alors qu'il n'a demandé qu'à ne pas voir
     bouger. Même raisonnement pour la signature manuscrite. */
  .voix-trait,.sig-path{ stroke-dashoffset:0 !important; }
  /* « Tenir la note » reste JOUABLE : l'anneau se remplit, la section
     s'allume. Seule la corde ne bouge plus — c'est la seule partie du geste
     qui déplace des pixels, et c'est exactement ce qu'on a demandé d'arrêter.
     On ne retire pas le moment, on retire son mouvement. */
  .note .voix-svg{ transform:none !important; }
  .note-lueur{ filter:none; }
}
/* ═══════════════ IMPRESSION ═══════════════
   Un programmateur qui imprime la page doit obtenir une fiche propre :
   fond blanc, texte noir, aucun décor, aucune interface. */
@media print{
  :root{ --text:#000; --muted:#333; --line:#bbb; }
  html,body{ background:#fff !important; color:#000 !important; }
  .preloader,.grain,.vignette,.ambiance,.scroll-progress,
  .dock,.to-top,.lightbox,.nav,.mobile-menu,.dots-nav,
  .pr-toile,.pr-voile,.pr-invite,.pr-jauge,.newsletter{ display:none !important; }
  [data-reveal],.reveal,.split-inner{ opacity:1 !important; transform:none !important; filter:none !important; }
  /* Le prélude s'aplatit lui-même à l'impression (voir la fin de
     css/prelude.css) : le film disparaît, les bandes se dépilent et le
     premier écran redevient une fiche d'identité en haut de page. La règle
     vit là-bas parce que c'est là que vivent ses cinq autres portes — une
     seule liste à relire quand on veut savoir quand le film n'existe pas. */
  /* Sur papier, la ligne de voix ne sépare plus rien d'utile et mange une
     bande de toner à chaque section. La signature, elle, reste : c'est une
     information. */
  .voix{ display:none !important; }
  /* Un geste ne s'imprime pas : sur papier, « tenir la note » serait un rond
     doré sans mode d'emploi au milieu de la revue de presse. */
  .note{ display:none !important; }
  .sig-path{ stroke-dashoffset:0 !important; stroke:#000 !important; }
  section{ padding:1.2rem 0 !important; break-inside:avoid; }
  .show-card,.event-row,.disc-card,.press-quote{ break-inside:avoid; border-color:#ccc !important; }
  a{ color:#000 !important; text-decoration:underline; }
  /* On imprime la destination des liens : utile sur papier */
  a[href^="http"]::after{ content:" (" attr(href) ")"; font-size:.7em; color:#555; }
}


/* ═══════════════ JAVASCRIPT COUPÉ ═══════════════
   Le défaut que ce bloc répare, dit franchement : sans JavaScript, le site
   était une page NOIRE ET VIDE. Deux causes qui s'additionnent —
     • [data-reveal] et .reveal partent à opacity:0, et c'est le script qui
       leur ajoute .is-revealed au défilement ;
     • .preloader ne disparaît que sur .is-done, ajoutée par le script aussi.
   Résultat : un rideau opaque devant du contenu invisible.

   La correction habituelle, <noscript><style>…</style></noscript>, est
   IMPOSSIBLE ici : la CSP de index.html impose style-src 'self', qui bloque
   toute feuille écrite dans la page. C'est le prix d'une CSP sans
   'unsafe-inline' — un prix qu'on assume, mais qu'il faut payer autrement.

   « scripting: none » est l'autre chemin : une requête de média, donc une
   règle dans un fichier .css, donc autorisée. Le navigateur la retient
   quand les scripts sont désactivés. Le contenu est intégralement rendu par
   js/main.js, donc le texte n'apparaîtra pas pour autant — mais la page
   affiche sa structure, ses titres, ses liens et son pied de page au lieu
   d'un écran noir. Un moteur de recherche qui n'exécute pas le JavaScript
   voit la même chose.

   Cas non couvert, et il faut le savoir : un navigateur où le JavaScript
   est bloqué par une extension déclare quand même « scripting: enabled ».
   Ce bloc traite le réglage du navigateur, pas le blocage par extension. */
@media (scripting: none){
  [data-reveal],.reveal,.split-inner{ opacity:1 !important; transform:none !important; filter:none !important; }
  .ht-word .ltr{ transform:none !important; opacity:1 !important; }
  .preloader{ display:none !important; }
  .scroll-progress{ display:none !important; }
  /* Le prélude a sa propre porte « scripting: none » dans css/prelude.css,
     écrite au même endroit que les quatre autres. Ici, il n'y a donc rien à
     faire pour lui : le film n'est jamais construit sans script, et le
     premier écran est déjà complet dans index.html. */
  /* Les tracés attendent .is-revealed, que personne ne viendra poser : sans
     cette ligne, la ligne de voix et la signature restent des pointillés
     décalés hors du trait, donc invisibles. Le défaut serait silencieux —
     rien ne manque à l'écran, il manque juste le motif du site. */
  .voix-trait,.sig-path{ stroke-dashoffset:0 !important; }
  /* Un bouton qui ne répond plus est pire que pas de bouton : il donne au
     visiteur l'impression que le site est cassé alors que c'est son
     navigateur qui a désactivé les scripts. On retire le geste, on garde la
     ligne — elle a sa place dans la section de toute façon. */
  .note-btn,.note-aide,.note-etat,.note-lueur{ display:none !important; }
}

/* ═══════════════ DERNIER MOT : « hidden » CACHE ═══════════════
   Dernière feuille chargée, dernière règle du fichier : un élément marqué
   hidden disparaît, même si une règle de mise en page pose un display sur
   le même élément. Sans cela, [hidden] (spécificité 0,1,0) perd contre une
   classe écrite plus loin — et un bloc censé être masqué reste à l'écran. */
[hidden]{ display:none !important; }
