/* ═══════════════════════════════════════════════════════════════════════════
   prelude.css — LE PREMIER ÉCRAN, ET RIEN D'AUTRE
   ═══════════════════════════════════════════════════════════════════════════

   Ce fichier ne contient qu'une chose : le PRÉLUDE, c'est-à-dire l'ouverture
   du site. Il remplace un diaporama de trois diapositives par un seul trajet
   continu que le visiteur parcourt en défilant.

   Le mot de métier : c'est un « défilement scrubbé » (*scroll-scrubbed*),
   comme on scrube une bande sur une table de montage. La position de la
   molette ne DÉCLENCHE pas une animation : elle EST la tête de lecture. On
   descend, le film avance ; on remonte, il recule exactement. Une animation
   déclenchée se joue une fois et ne se rembobine pas ; un film scrubbé est
   une fonction du défilement, donc réversible par construction.

   Pourquoi ce fichier est séparé de style.css : le prélude est la seule
   partie du site dont le CSS et le JavaScript doivent se répondre au
   CARACTÈRE près (voir les cinq portes, plus bas). Les garder dans le même
   fichier que le reste garantissait qu'une modification de mise en page
   finisse par en casser une sans que rien ne se voie.

   ┌─ CE QUI PORTE TOUT : LA VARIABLE --k ────────────────────────────────┐
   │ Chaque bande de texte reçoit un seul nombre, --k, entre 0 et 1. Le    │
   │ JavaScript le calcule à partir du défilement ; le CSS en déduit TOUT  │
   │ le reste (opacité, déplacement, échelle, densité du voile). Une seule │
   │ écriture par bande et par image, et aucune animation écrite deux fois.│
   │ C'est la séparation des responsabilités : le JavaScript possède le    │
   │ déclenchement, le CSS possède l'apparence.                            │
   └───────────────────────────────────────────────────────────────────────┘
   ═══════════════════════════════════════════════════════════════════════ */

/* @property n'est pas une coquetterie. Sans lui, --k est une simple chaîne :
   la moindre valeur mal formée rend TOUS les calc() qui la lisent invalides,
   et la bande disparaît sans une erreur dans la console. Déclaré ici, le
   navigateur garantit que --k est un nombre, ou retombe sur 0. */
@property --k {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}
@property --d {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

/* ─────────── LES JETONS DU FILM ───────────
   Le film ne s'invente aucune couleur : il lit ces jetons, et il en lit les
   MÊMES noms dans les deux thèmes. C'est pour cela qu'il est juste en NUIT
   comme en IVOIRE sans une ligne dupliquée dans le JavaScript.

   Les trois teintes du canvas sont écrites en triplets « r,g,b » et non en
   hexadécimal : le JavaScript les compose directement en rgba(…) avec
   l'opacité qu'il veut, sans avoir à décoder du #rrggbb à chaque image. */
:root{
  --pr-fond-haut:  #0a0908;
  --pr-fond-bas:   #16090d;
  --pr-halo:       200,162,94;
  --pr-trait-bas:  150,112,58;   /* la ligne avant la traversée du halo */
  --pr-trait-haut: 232,213,163;  /* la ligne après */
  --pr-mote:       200,162,94;
  /* ─── Les trois températures du champ « tone » ───
     La cliente choisit « or », « velours » ou « nuit » sur chaque
     diapositive, dans son tableau de bord. Ce champ existait depuis la
     première version du site et pilotait un aplat de couleur sur l'ancien
     carrousel ; en remplaçant le carrousel par le film, il est devenu un
     réglage que le tableau de bord proposait, que le guide documentait, et
     que PLUS RIEN ne lisait. Un réglage sans effet est pire qu'un réglage
     absent : la cliente le change, ne voit rien, et conclut que le site est
     cassé — ou qu'elle s'est trompée.

     Les trois valeurs sortent de la palette du site, elles ne sont pas
     inventées : l'or (--gold), le velours porté à l'incandescence (le
     #4a1220 de la charte est trop sombre pour ÉMETTRE de la lumière, mais
     c'est la même teinte montée en luminance), et l'or profond (--gold-deep)
     qui joue le clair de lune. Aucune quatrième couleur : la doctrine tient
     à une dominante et une accentuation, et un halo bleu aurait été une
     deuxième identité dans la même page. */
  --pr-ton-or:      200,162,94;
  --pr-ton-velours: 140,36,54;
  --pr-ton-nuit:    150,112,58;
  /* Le voile de lisibilité et le texte qu'il protège. Voir l'audit de la
     pire image, plus bas : ces deux valeurs sont mesurées, pas choisies. */
  --pr-voile:      10,9,8;
  --pr-voile-a:    .70;
  --pr-texte:      #f7f2e8;
  --pr-surtitre:   var(--gold-soft);
  /* ─── Les deux encres ajoutées le 25/08/2026, et pourquoi ───
     Le prélude n'avait que deux encres, --pr-texte et --pr-surtitre. Tout le
     reste du texte du premier écran était écrit EN DUR ailleurs : #f6f1e7 pour
     le nom, rgba(243,237,226,.82) pour l'accroche, var(--gold-soft) pour les
     liens et les mots en italique. Ces valeurs venaient de l'ancien héros, où
     elles étaient posées sur une photo sombre — donc justes. Le film les a
     héritées sans les rejuger, et en thème IVOIRE le nom de la chanteuse s'est
     retrouvé peint en ivoire sur de l'ivoire : 1,05:1, mesuré dans le
     navigateur. Invisible. L'accroche : 1,01:1.

     Le défaut n'est pas d'avoir choisi une mauvaise couleur, c'est d'avoir
     laissé une couleur de texte VIVRE HORS DES JETONS. L'audit de la pire
     image ne mesurait que les deux jetons connus, donc il ne pouvait rien voir
     — un audit qui lit une liste écrite à la main ne mesure jamais ce qu'on a
     oublié d'y mettre. Deux contrôles ferment maintenant la boucle : l'un
     interdit toute couleur de texte écrite en dur dans le prélude, l'autre
     récolte les encres effectivement utilisées et les mesure toutes.

     --pr-texte-doux est passé de .82 à .90 : sur la pire photo (blanche, sous
     le voile sombre) .82 ne donnait que 4,40:1. La hiérarchie ne perd rien,
     elle est portée par la taille et la graisse, pas par la pâleur. */
  --pr-texte-doux: rgba(246,241,231,.90);
  --pr-accent:     var(--gold-soft);
}
[data-theme="ivoire"]{
  --pr-fond-haut:  #f4f0e8;
  --pr-fond-bas:   #e4d9c6;
  --pr-halo:       125,92,36;
  --pr-trait-bas:  125,92,36;
  --pr-trait-haut: 150,112,58;
  --pr-mote:       125,92,36;
  /* Les mêmes trois températures, encrées : sur fond clair, un halo doit
     ASSOMBRIR pour se voir. Les teintes restent celles du thème nuit, la
     luminance seule est inversée — un ton qui changerait de couleur en
     changeant de thème ferait mentir le choix de la cliente. */
  --pr-ton-or:      125,92,36;
  --pr-ton-velours: 106,26,40;
  --pr-ton-nuit:    96,74,38;
  /* Thème clair : le voile s'éclaircit et le texte s'encre. La pire image
     n'est plus une photo blanche mais une photo NOIRE, et le calcul est
     refait pour ce cas (8,0:1). */
  --pr-voile:      244,240,232;
  --pr-voile-a:    .74;
  --pr-texte:      #14110d;
  /* #6b4e1e jusqu'au 25/08/2026, et c'était un défaut réel : le surtitre doré
     posé sur le voile clair d'une photo NOIRE ne mesurait que 3,2:1. Le
     contrôle « la pire image du prélude » l'a trouvé — à l'œil il paraissait
     élégant, parce qu'on ne regarde jamais le cas le plus défavorable. Cette
     valeur-ci mesure 4,84:1, donc le plancher du texte courant, pas celui du
     grand texte : un surtitre en petites capitales de 0,78 rem n'a pas droit
     au régime de faveur des grandes lettres. */
  --pr-surtitre:   #4c3410;
  /* Les deux mêmes encres, encrées. --pr-accent est l'or du thème clair : il
     doit tenir 4,5:1 sur une photo NOIRE vue sous le voile clair, ce qui le
     force presque aussi bas que le surtitre (4,73:1 mesuré). Un or clair ici
     donnait 1,74:1 — c'est ce que valaient les liens « Voir le projet » et les
     mots en italique des titres avant ce jour. De l'or sur du papier crème se
     lit sombre : ce n'est pas un compromis d'accessibilité, c'est ce que fait
     la dorure à chaud dans la vraie vie. */
  --pr-texte-doux: rgba(20,17,13,.86);
  --pr-accent:     #4f350d;
}

/* ═══════════════════════════════════════════════════════════════════════
   1. LA GÉOMÉTRIE — LA LONGUEUR DU FILM SUIT LE CONTENU
   ═══════════════════════════════════════════════════════════════════════
   La hauteur du prélude n'est pas un nombre choisi : c'est une formule.
     • 100dvh  : la fenêtre elle-même, épinglée pendant tout le trajet
     • 104vh   : 68 vh d'entrée (le film s'ouvre, aucun texte) + 36 vh de
                 sortie (la ligne se pose)
     • n×140vh : une bande de texte = 22 vh de rampe + 96 vh de plateau
                 + 22 vh de rampe

   Conséquence directe, et c'est le but : la cliente ajoute une quatrième
   diapositive depuis son tableau de bord, le film s'allonge de 140 vh. Il
   ne comprime jamais quatre textes dans la place de trois. Un plateau de
   96 vh, c'est à peu près une seconde de lecture tranquille à la molette :
   sous 80 vh le texte passe trop vite pour être lu, au-delà de 130 vh le
   visiteur croit que la page est bloquée.

   --pr-n est posé par js/prelude.js. La valeur de repli 3 n'est pas
   décorative : sans elle, un échec du JavaScript donnerait une hauteur
   calc() invalide, donc un prélude de hauteur nulle. */
.prelude{
  --pr-n: 3;
  position:relative;
  isolation:isolate;
  height:calc(100dvh + 104vh + var(--pr-n) * 140vh);
}

/* La scène est COLLANTE (« sticky »), pas fixée. La différence compte :
   position:fixed sort l'élément du flux, il faut alors calculer sa sortie à
   la main et la moindre erreur laisse le film par-dessus la section
   suivante. position:sticky laisse le navigateur gérer l'épinglage et le
   relâchement. Zéro ligne de JavaScript pour la mécanique la plus visible
   du site. */
.pr-stage{
  position:sticky; top:0;
  height:100dvh;
  overflow:hidden;
  display:flex; align-items:center;
  padding:calc(var(--nav-h) + 2rem) var(--gutter) clamp(6rem,12vh,9rem);
}

/* ═══════════════════════════════════════════════════════════════════════
   2. LE FILM
   ═══════════════════════════════════════════════════════════════════════
   Un canvas, dessiné par une fonction pure de la progression. Aucun fichier
   à télécharger, donc aucune requête réseau, aucun octet dans le premier
   écran, et une netteté parfaite sur un écran à 3× la densité de pixels.
   Aucun .mp4 ne réunit ces trois propriétés. */
.pr-toile{
  position:absolute; inset:0;
  display:block; width:100%; height:100%;
  pointer-events:none;
  /* Le film n'apparaît qu'une fois la première image dessinée. Sans cela on
     voit un rectangle vide pendant une image ou deux : le défaut le plus
     bon marché à éviter de tout le fichier. */
  opacity:0; transition:opacity .5s var(--ease-soft);
}
.prelude.pr-vivant .pr-toile{ opacity:1; }

/* ─── Couche de lisibilité n° 1 : le voile GLOBAL ───
   Il ne dépend d'aucune bande. Son rôle est de garantir que le film, quelle
   que soit l'image que la cliente y met, ne remonte jamais assez clair au
   centre pour avaler un texte. Il est en CSS et non dans le canvas parce
   qu'il doit exister même si le canvas échoue. */
.pr-voile{
  position:absolute; inset:0; pointer-events:none;
  background:
    radial-gradient(ellipse 82% 66% at 50% 58%,
      transparent 0%, rgba(var(--pr-voile), .30) 62%, rgba(var(--pr-voile), .58) 100%),
    linear-gradient(180deg, rgba(var(--pr-voile), .52) 0%, transparent 26%),
    linear-gradient(0deg, rgba(var(--pr-voile), .70) 0%, transparent 34%);
}

/* ═══════════════════════════════════════════════════════════════════════
   3. LES BANDES DE TEXTE
   ═══════════════════════════════════════════════════════════════════════
   Toutes empilées au même endroit, toutes présentes dans le document. Une
   seule est lisible à la fois, et c'est --k qui décide laquelle.

   Elles restent TOUTES dans l'arbre d'accessibilité, sans aria-hidden : un
   lecteur d'écran lit le prélude comme un texte suivi, du nom jusqu'au
   dernier projet. C'est la version la plus complète du site, et elle ne
   dépend pas du défilement. */
.pr-bande{
  --k: 0;
  position:absolute;
  inset:calc(var(--nav-h) + 2rem) var(--gutter) clamp(6rem,12vh,9rem);
  display:flex; align-items:center;
  pointer-events:none;
}
/* Une bande effacée ne doit pas intercepter les clics d'une bande visible.
   L'attribut data-actif est posé par le JavaScript aux seules transitions
   de bande, pas à chaque image : c'est une écriture DOM par changement de
   temps, pas soixante par seconde. */
.pr-bande[data-actif] .pr-mots{ pointer-events:auto; }

.pr-mots{
  position:relative; z-index:2;
  width:100%; max-width:var(--container); margin-inline:auto;
  padding-left:clamp(0px, 2vw, 2.5rem);
  opacity:var(--k);
  /* Aucune transition ici, et c'est délibéré : la valeur est déjà lissée par
     le JavaScript (lissage normalisé au pas de temps). Une transition CSS
     par-dessus ajouterait un second lissage, et le texte traînerait derrière
     la molette d'une demi-seconde. */
}

/* ─── La ligne de voix rentre dans le flux ───
   Dans l'ancien héros, .voix-hero était posée en absolu contre la section et
   collée à « bottom: 10vh » : elle barrait tout le bas de l'écran, loin du
   nom. Le prélude la remet DANS le texte, juste sous « Cosseron », et ce
   déplacement de quelques centimètres change ce qu'elle raconte.

   Deux lignes identiques à deux hauteurs sans rapport se lisent comme deux
   ornements. Une ligne posée sous le nom, et la même géométrie en mouvement
   derrière elle dans le canvas, se lisent comme un sujet et son écho — le
   nom, puis sa voix. C'est le même tracé, au même endroit, à deux états.

   La sélectivité fait le travail sans !important : « .prelude .voix-hero »
   (0,2,0) l'emporte sur « .voix-hero » (0,1,0) de css/effects.css, même si
   cette feuille est chargée après. Les propriétés qu'on ne réécrit pas —
   l'aplatissement en scaleY(.5), le tracé sur body.is-loaded — continuent
   d'arriver de là-bas, intactes. */
.prelude .voix-hero{
  position:relative; inset:auto;
  width:100%;
  margin:clamp(.4rem,1.2vh,1rem) 0 0;
}

/* ─── Couche de lisibilité n° 2 : le voile PAR BANDE ───
   Il monte avec --k, donc il n'existe que là où il y a du texte à protéger.
   Un voile permanent aurait terni le film pendant les 68 vh d'ouverture,
   là précisément où il n'y a rien à lire.

   L'opacité de crête : .94 × .70 = 0,66. La doctrine demande entre 0,62 et
   0,72 — assez pour tenir le plancher de contraste sur une photo blanche,
   pas assez pour que le voile se voie comme un rectangle. */
.pr-bande::before{
  content:""; position:absolute; z-index:1;
  inset:-14% -10%;
  pointer-events:none;
  background:radial-gradient(ellipse 60% 72% at 34% 50%,
    rgba(var(--pr-voile), var(--pr-voile-a)) 0%,
    rgba(var(--pr-voile), calc(var(--pr-voile-a) * .55)) 46%,
    transparent 74%);
  opacity:calc(.94 * var(--k));
}

/* ─── Couche de lisibilité n° 3 : l'ombre de texte à trois couches ───
   Une seule ombre douce ne fait rien : elle floute le contour sans changer
   la valeur du fond. Trois couches font trois métiers différents.
     1 px  : détache la lettre du grain, de très près
     10 px : creuse un halo sombre autour du mot
     36 px : abaisse la valeur de toute la zone, donc du fond lui-même
   C'est la troisième qui porte réellement le contraste. */
.pr-bande{
  --tshadow:
    0 1px 2px  rgba(var(--pr-voile), .92),
    0 2px 10px rgba(var(--pr-voile), .74),
    0 0 36px   rgba(var(--pr-voile), .56);
}

/* ─── Couche de lisibilité n° 4 : la pastille du surtitre ───
   Un surtitre est petit, en capitales et interlettré : c'est le texte le
   plus fragile de l'écran. Il reçoit son propre fond. */
.pr-chip{
  display:inline-flex; align-items:center; gap:.85rem;
  padding:.42rem .95rem .42rem .8rem;
  background:rgba(var(--pr-voile), .58);
  border:1px solid var(--line);
  border-radius:2px;
  font-family:var(--font-mono);
  font-size:.64rem; font-weight:400;
  letter-spacing:.3em; text-transform:uppercase;
  color:var(--pr-surtitre);
  text-shadow:var(--tshadow);
}
.pr-chip .pr-chip-trait{
  width:clamp(22px,4vw,44px); height:1px;
  background:currentColor; opacity:.7;
}

/* ─── Ce que le ton de la cliente touche, et ce qu'il ne touchera jamais ───
   Redéfinir --pr-halo sur la BANDE suffit : tout ce qu'elle contient suit la
   température du moment sans une seule règle supplémentaire. Le canvas, lui,
   lit le jeton sur :root — les deux ne peuvent donc pas se contredire.

   Le ton ne touche PAS la couleur du texte. C'est une frontière, pas une
   omission : --pr-surtitre et --pr-texte sont sous le verdict de l'audit de
   la pire image (4,84:1 en ivoire, mesuré). Laisser un champ du tableau de
   bord déplacer une couleur de texte reviendrait à confier le contraste à
   quelqu'un qui n'a aucun moyen de le mesurer — et un site illisible ne
   ressemble jamais à une erreur, seulement à un mauvais goût. */
.pr-bande[data-ton="velours"]{ --pr-halo:var(--pr-ton-velours); }
.pr-bande[data-ton="nuit"]{    --pr-halo:var(--pr-ton-nuit); }
.pr-bande[data-ton] .pr-chip{  border-color:rgba(var(--pr-halo), .34); }

.pr-titre{
  margin-top:1.5rem;
  font-family:var(--font-display); font-weight:300;
  font-size:clamp(2.9rem, 8.2vw, 7.4rem);
  line-height:.94; letter-spacing:-.02em;
  color:var(--pr-texte);
  text-shadow:var(--tshadow);
  max-width:19ch;
}
.pr-titre em{
  font-style:italic; font-weight:400;
  color:var(--pr-accent);
}
.pr-sous{
  margin-top:1.5rem;
  max-width:44ch;
  font-size:clamp(1rem, 1.5vw, 1.22rem);
  font-weight:300; line-height:1.62;
  color:var(--pr-texte);
  text-shadow:var(--tshadow);
}
.pr-lien{
  display:inline-flex; align-items:center; gap:.6rem;
  margin-top:2.1rem;
  font-family:var(--font-mono);
  font-size:.7rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--pr-accent);
  text-shadow:var(--tshadow);
}
.pr-lien svg{
  width:16px; height:16px; fill:none; stroke:currentColor; stroke-width:1.4;
  transition:transform .3s var(--ease);
}
.pr-lien:hover svg{ transform:translateX(6px); }
.pr-lien::after{
  content:""; position:absolute; left:0; right:0; bottom:-.4rem; height:1px;
  background:currentColor; opacity:.34;
}
.pr-lien{ position:relative; }

/* ═══════════════════════════════════════════════════════════════════════
   4. LA CHORÉGRAPHIE — UNE SEULE ENTRÉE PAR TEMPS
   ═══════════════════════════════════════════════════════════════════════
   Trois entrées différentes, jamais deux fois la même dans un film. Toutes
   les trois n'emploient que transform et opacity : ce sont les deux seules
   propriétés que le navigateur sait animer sans recalculer la mise en page,
   et donc les deux seules qui tiennent 60 images par seconde pendant qu'on
   scrube. Toutes les trois sont des fonctions de --k, donc réversibles :
   remonter la molette rejoue l'entrée à l'envers, exactement.

   Les valeurs de départ sont écrites en (1 - var(--k)) : à --k = 1, tout
   vaut zéro, et l'état final est donc l'état NEUTRE. Un texte arrivé n'a
   aucune transformation résiduelle, ce qui garantit qu'il est net au pixel. */

/* ─── Temps 0 : l'identité. Elle n'ENTRE pas, elle est déjà là. ───
   Son entrée à elle, c'est la levée de rideau du chargement (body.is-loaded,
   dans effects.css). Au premier écran, rien ne doit apparaître : le visiteur
   n'a pas encore bougé, il n'y a donc aucun geste à récompenser. --k ne lui
   sert qu'à SORTIR. */
.pr-bande-0 .pr-mots{
  transform:translateY(calc((1 - var(--k)) * -2.4rem))
            scale(calc(1 - (1 - var(--k)) * .022));
}

/* ─── Chorégraphie 1 : la montée mot à mot ───
   Chaque mot reçoit --d, son retard. La formule donne à chaque mot sa propre
   progression --kw, décalée de .055 et étalée sur .62 : les mots se lèvent
   en cascade au lieu de se lever en bloc. C'est le décalage en cascade
   (« stagger »), l'effet le plus rentable du métier. */
[data-choreo="1"] .pr-mot{
  display:inline-block;
  --kw: clamp(0, calc((var(--k) - var(--d)) / .62), 1);
  transform:translateY(calc((1 - var(--kw)) * .9em));
  opacity:var(--kw);
}
[data-choreo="1"] .pr-titre{ overflow:hidden; padding-bottom:.08em; }

/* ─── Chorégraphie 2 : l'approche depuis la profondeur ───
   Le texte vient de loin. Pas un flou (un filtre coûte un rendu hors écran
   à chaque image), mais une échelle : l'œil lit « ça avance vers moi »
   exactement pareil, pour zéro coût de composition. */
[data-choreo="2"] .pr-mots{
  transform:scale(calc(1 + (1 - var(--k)) * .16))
            translateY(calc((1 - var(--k)) * .8rem));
  transform-origin:34% 50%;
}

/* ─── Chorégraphie 3 : les moitiés qui se rejoignent ───
   Le surtitre arrive par la gauche, le titre par la droite, et ils se
   posent ensemble. C'est la dernière bande du film : elle se referme au
   lieu de simplement apparaître. */
[data-choreo="3"] .pr-chip{
  transform:translateX(calc((1 - var(--k)) * -3.4rem));
}
[data-choreo="3"] .pr-titre{
  transform:translateX(calc((1 - var(--k)) * 3.4rem));
}
[data-choreo="3"] .pr-sous,
[data-choreo="3"] .pr-lien{
  transform:translateY(calc((1 - var(--k)) * 1.1rem));
}

/* ═══════════════════════════════════════════════════════════════════════
   5. L'INVITATION À DESCENDRE
   ═══════════════════════════════════════════════════════════════════════
   Elle n'appartient à aucune bande : elle appartient au trajet. Elle
   s'efface dès que le film a commencé, parce qu'un « descendez » affiché
   pendant qu'on descend est un bruit. --pr-p est la progression brute,
   posée par le JavaScript sur la section. */
.prelude{ --pr-p: 0; }
.pr-invite{
  position:absolute; z-index:4;
  right:calc(var(--gutter) - .35rem); top:36%;
  writing-mode:vertical-rl;
  display:inline-flex; align-items:center; gap:.9rem;
  font-family:var(--font-mono);
  font-size:.6rem; letter-spacing:.26em; text-transform:uppercase;
  /* rgba(243,237,226,.52) jusqu'au 25/08/2026, avec une reprise en dur pour le
     thème ivoire juste en dessous. Deux couleurs écrites à la main pour un même
     élément, donc deux occasions de se tromper — et sur la pire photo, .52 ne
     valait que 2,77:1. Sa discrétion vient maintenant de sa taille, de son
     écriture verticale et du fait qu'elle s'efface dès que le film démarre :
     trois moyens qui ne coûtent pas de lisibilité. */
  color:var(--pr-accent);
  text-shadow:var(--tshadow);
  opacity:0;
  transition:opacity .9s var(--ease) 1.3s, color .3s;
}
/* Aucune reprise « [data-theme="ivoire"] .pr-invite » ici, et c'est volontaire :
   --pr-accent change déjà de valeur avec le thème. Une reprise par thème est
   précisément le mécanisme qui a rendu le nom invisible — deux endroits à tenir
   d'accord, donc un endroit qu'on oublie. */
body.is-loaded .pr-invite{ opacity:clamp(0, calc(1 - var(--pr-p) * 9), 1); }
/* Le survol va vers l'encre principale, donc il ÉCLAIRCIT en nuit et ASSOMBRIT
   en ivoire. Dans les deux cas le geste se lit « ça se renforce » — ce qu'un
   retour de survol doit dire. Un or fixe disait « ça change de couleur ». */
.pr-invite:hover{ color:var(--pr-texte); }
.pr-invite-trait{
  width:1px; height:64px; position:relative; overflow:hidden;
  background:rgba(var(--pr-halo), .38);
}
.pr-invite-trait::after{
  content:""; position:absolute; inset:0; background:rgb(var(--pr-halo));
  animation:prInvite 2.4s var(--ease-soft) infinite;
}
@keyframes prInvite{
  0%{ transform:translateY(-100%); }
  60%,100%{ transform:translateY(100%); }
}

/* La jauge du trajet : trois traits qui se remplissent l'un après l'autre,
   un par tiers de film. Elle répond à la seule question qu'un visiteur se
   pose devant un défilement long : est-ce que ça finit ?

   Elle ne lit que --pr-p, la progression brute déjà posée sur la section.
   Zéro écriture DOM supplémentaire par image : les trois tiers sont
   découpés en CSS, ce qui coûte exactement rien. */
.pr-jauge{
  position:absolute; z-index:4;
  left:var(--gutter); bottom:clamp(2.4rem,6vh,3.4rem);
  display:flex; gap:6px;
  opacity:0; transition:opacity .8s var(--ease) 1.5s;
}
body.is-loaded .pr-jauge{ opacity:1; }
.pr-jauge i{
  display:block; width:clamp(26px,4vw,52px); height:2px;
  background:rgba(var(--pr-halo), .22);
  overflow:hidden;
}
.pr-jauge i::after{
  content:""; display:block; height:100%;
  background:rgb(var(--pr-halo));
  transform-origin:0 50%;
}
.pr-jauge i:nth-child(1)::after{ transform:scaleX(clamp(0, calc(var(--pr-p) * 3), 1)); }
.pr-jauge i:nth-child(2)::after{ transform:scaleX(clamp(0, calc(var(--pr-p) * 3 - 1), 1)); }
.pr-jauge i:nth-child(3)::after{ transform:scaleX(clamp(0, calc(var(--pr-p) * 3 - 2), 1)); }

/* ═══════════════════════════════════════════════════════════════════════
   6. LES CINQ PORTES DU HÉROS STATIQUE
   ═══════════════════════════════════════════════════════════════════════
   Un film scrubbé n'est pas toujours le bon choix. Dans cinq situations, le
   prélude redevient un héros normal d'une hauteur d'écran :

     1. mouvement réduit demandé par le système
     2. téléphone (une main, un pouce, une barre d'adresse qui bouge)
     3. écran bas (paysage sur téléphone, fenêtre écrasée)
     4. JavaScript coupé
     5. canvas indisponible

   Les trois premières sont des requêtes média. Elles sont écrites ICI et
   dans js/prelude.js, et elles doivent être identiques AU CARACTÈRE PRÈS.
   C'est la seule façon d'éviter le pire défaut possible de ce fichier : un
   CSS qui croit le film arrêté pendant que le JavaScript continue de le
   scruber, ou l'inverse. Un contrôle automatique compare les deux fichiers.

   Ce que le héros statique n'est PAS : une version dégradée. Il porte la
   même information, le nom, la même invitation, et les mêmes textes de
   projet en pile calme. C'est la règle « complet sans le film ». */

.prelude.pr-fixe{ height:auto; min-height:100svh; }
/* LE DÉFAUT CORRIGÉ LE 25/08/2026 — mesuré à 375 px, pas supposé.
   La scène gardait son « display:flex » de base. Or en mode fixe les bandes
   redeviennent statiques : elles devenaient donc des éléments flexibles d'une
   même RANGÉE et se partageaient les 338 px utiles. Le nom de la chanteuse
   tenait dans 178 px et se cassait au milieu — « Guylain / e ». Le commentaire
   des ruptures, dix lignes plus bas, affirmait déjà « une colonne unique, et
   c'est la maquette principale » : le fichier promettait ce que le code ne
   tenait pas. Un « display:block » suffit, et il doit figurer dans CHAQUE
   variante qui éteint le film — d'où le contrôle qui les récolte toutes. */
.prelude.pr-fixe .pr-stage{ position:relative; display:block; height:auto; min-height:100svh; }
.prelude.pr-fixe .pr-toile,
.prelude.pr-fixe .pr-invite,
.prelude.pr-fixe .pr-jauge{ display:none; }
/* En mode fixe, les bandes ne sont plus empilées : elles se rangent. La
   bande 0 garde sa place, les autres deviennent une liste sous elle. */
.prelude.pr-fixe .pr-bande{
  position:static; inset:auto;
  display:block;
  pointer-events:auto;
}
.prelude.pr-fixe .pr-bande::before{ display:none; }
.prelude.pr-fixe .pr-mots{
  opacity:1; transform:none;
}
.prelude.pr-fixe .pr-bande-0{ margin-bottom:clamp(3rem,8vh,5.5rem); }
.prelude.pr-fixe .pr-bandes{
  display:grid; gap:clamp(1.6rem,4vw,2.4rem);
  border-top:1px solid var(--line);
  padding-top:clamp(1.8rem,5vw,2.8rem);
}
.prelude.pr-fixe .pr-titre{
  font-size:clamp(1.7rem,5.6vw,2.5rem);
  margin-top:.9rem; max-width:none;
}
.prelude.pr-fixe .pr-sous{ margin-top:.7rem; }
.prelude.pr-fixe .pr-lien{ margin-top:1.1rem; }
.prelude.pr-fixe .pr-mot{ transform:none; opacity:1; }
/* Le fond du mode fixe : le même dessin que la première image du film, mais
   en CSS. Sans lui, couper le canvas donnerait un aplat. */
.prelude.pr-fixe .pr-voile{
  background:
    radial-gradient(64% 48% at 62% 30%, rgba(var(--pr-halo), .17), transparent 68%),
    radial-gradient(90% 66% at 16% 92%, rgba(var(--pr-halo), .11), transparent 62%),
    linear-gradient(168deg, var(--pr-fond-haut) 0%, var(--pr-fond-bas) 64%, var(--pr-fond-haut) 100%),
    linear-gradient(0deg, rgba(var(--pr-voile), .40) 0%, transparent 38%);
}

/* Porte 1, 2 et 3 : les trois requêtes média. Le CSS les applique de
   lui-même, sans attendre le JavaScript — donc même pendant le chargement,
   un téléphone n'affiche jamais un prélude de 524 vh. */
@media (prefers-reduced-motion: reduce){
  .prelude{ height:auto; min-height:100svh; }
  .prelude .pr-stage{ position:relative; display:block; height:auto; min-height:100svh; }
  .prelude .pr-toile,
  .prelude .pr-invite,
  .prelude .pr-jauge{ display:none; }
  .prelude .pr-bande{ position:static; inset:auto; display:block; pointer-events:auto; }
  .prelude .pr-bande::before{ display:none; }
  .prelude .pr-mots{ opacity:1; transform:none; }
  .prelude .pr-mot{ transform:none; opacity:1; }
}
@media (pointer: coarse) and (max-width: 640px){
  .prelude{ height:auto; min-height:100svh; }
  .prelude .pr-stage{ position:relative; display:block; height:auto; min-height:100svh; }
  .prelude .pr-toile,
  .prelude .pr-invite,
  .prelude .pr-jauge{ display:none; }
  .prelude .pr-bande{ position:static; inset:auto; display:block; pointer-events:auto; }
  .prelude .pr-bande::before{ display:none; }
  .prelude .pr-mots{ opacity:1; transform:none; }
  .prelude .pr-mot{ transform:none; opacity:1; }
}
@media (max-height: 520px){
  .prelude{ height:auto; min-height:100svh; }
  .prelude .pr-stage{ position:relative; display:block; height:auto; min-height:100svh; }
  .prelude .pr-toile,
  .prelude .pr-invite,
  .prelude .pr-jauge{ display:none; }
  .prelude .pr-bande{ position:static; inset:auto; display:block; pointer-events:auto; }
  .prelude .pr-bande::before{ display:none; }
  .prelude .pr-mots{ opacity:1; transform:none; }
  .prelude .pr-mot{ transform:none; opacity:1; }
}

/* Porte 4 : JavaScript coupé. Le site entier est piloté par data.js, donc
   les bandes 1 à n n'existent même pas — il ne reste que la bande 0, qui
   est écrite dans index.html. Elle suffit : nom, rôle, phrase, deux
   actions. On retire le film et la jauge, on garde l'écran. */
@media (scripting: none){
  .prelude{ height:auto; min-height:100svh; }
  .prelude .pr-stage{ position:relative; display:block; height:auto; min-height:100svh; }
  .prelude .pr-toile,
  .prelude .pr-invite,
  .prelude .pr-jauge{ display:none; }
  .prelude .pr-bande{ position:static; inset:auto; display:block; }
  .prelude .pr-bande::before{ display:none; }
  .prelude .pr-mots{ opacity:1; transform:none; }
  .prelude .pr-mot{ transform:none; opacity:1; }
}

/* ─────────── Ruptures ───────────
   375 px d'abord : le prélude en mode fixe y est une colonne unique, et
   c'est la maquette principale, pas une adaptation. Ces règles ne font que
   rendre la version large. */
@media (max-width:1024px){
  .pr-invite{ display:none; }
  .pr-titre{ max-width:24ch; }
}
@media (max-width:640px){
  .pr-jauge{ display:none; }
  .pr-chip{ letter-spacing:.24em; font-size:.6rem; }
}

/* ─────────── Impression ───────────
   Sur papier, un film n'existe pas. On imprime les mots, tous, à plat. */
@media print{
  .prelude{ height:auto; min-height:0; }
  .prelude .pr-stage{ position:static; display:block; height:auto; min-height:0; padding:0; }
  .prelude .pr-toile,
  .prelude .pr-voile,
  .prelude .pr-invite,
  .prelude .pr-jauge{ display:none; }
  .prelude .pr-bande{ position:static; inset:auto; display:block; }
  .prelude .pr-bande::before{ display:none; }
  .prelude .pr-mots{ opacity:1; transform:none; }
  .prelude .pr-mot{ transform:none; opacity:1; }
}
