/* Le rail de chapitres — voir src/rail.js.

   Un fichier à lui, comme le verre et l'onde : le rail est un couple js + css, et les
   garder ensemble est ce qui permet de le décrocher d'une ligne si le film change de
   forme. */

/* ============================================================
   1 · LA COLONNE
   ============================================================ */

/* SUR LE MONTANT, ET POSÉ AU BAS DE LA PAGE (Lucas, 2026-08-14 — le menu de la référence
   Shopify Editions, repris pour son style et pour sa place, mais sans son animation).

   L'abscisse ne bouge pas. Le rail ne flotte pas dans la marge : il PART du montant gauche
   et s'avance vers l'intérieur du cadre. C'est ce qui en fait une graduation — une règle
   graduée est posée sur son axe, pas à côté.

   L'ordonnée, elle, a changé de bord, et c'est tout le déménagement. Le rail pendait du
   filet du bandeau, avec la marque au-dessus de lui. Il est maintenant ANCRÉ EN BAS, comme
   le sommaire de la référence : la bande basse est la seule bande vraiment libre de la page
   — aucun chapitre ne s'y ancre (les ancrages en service sont `center-left`, `top-left`,
   `center-right` et `center`, voir src/chapters.js), alors que le haut porte déjà la marque
   et le bouton d'écriture.

   `bottom` et non `top` : c'est le BAS de la liste qui doit tenir sa position. Ancré par le
   haut, un septième chapitre pousserait la colonne vers le pied de page ; ancré par le bas,
   il la fait grandir vers le haut, dans le vide, et l'écart au mot « Descendre » reste le
   même quel que soit le nombre de chapitres — c'est lui qu'on lit, pas la hauteur du bloc.

   3,4rem : le mot « Descendre » vit à `13vh + 1,2rem` (style.css) et le rail se pose juste
   au-dessus de lui. Les deux se lisent alors comme une seule colonne de pied de page — le
   sommaire, puis l'affordance — exactement comme la référence range ses mentions légales
   sous son menu.

   `position: fixed` et non `absolute` : la scène est haute d'un écran et ne bouge pas, les
   deux reviendraient au même — sauf pendant le geste d'ouverture du panneau d'écriture, où
   le document est décalé. Fixe, le rail ne suit pas ce décalage. */
.rail {
  position: fixed;
  z-index: 4;
  left: calc(var(--rule-inset) * 100vw);
  bottom: calc(13vh + 3.4rem);
  display: flex;
  flex-direction: column;
  /* `max-content`, soit ici les 24 px du chiffre : les titres sont hors du flux (§ 2) et
     ne comptent pas dans la mesure. La boîte du rail est donc étroite comme une graduation,
     et les volets qui s'en échappent ne l'élargissent jamais. */
  width: max-content;
  /* AUCUNE gouttière, et ce n'est pas un oubli : le pas de la colonne est porté par
     l'interligne du chiffre (`line-height: 1.7`, § 2), qui vaut aussi la bande de survol de
     chaque entrée. Un `gap` en plus aurait ouvert entre les bandes des couloirs morts où le
     volet se referme alors que la souris descend la colonne. */
  gap: 0;
  /* Le rail entre APRÈS le rideau, comme la marque et le bouton d'écriture : il fait partie
     de l'interface, pas du film, et rien de l'interface ne doit être là avant que la
     première image le soit.
     L'opacité n'est ici qu'un INTERRUPTEUR — le geste, lui, est porté par les chiffres
     eux-mêmes (§ 2), qui se dessinent un à un. Le fondu accompagne ce tracé sur la même
     courbe et au même instant que le logo, il ne le double pas. */
  opacity: 0;
  transition: opacity 1.4s cubic-bezier(0.22, 1, 0.36, 1) 1600ms;
  pointer-events: none;
}
.stage.is-ready .rail { opacity: 1; }

/* ============================================================
   2 · LA LIGNE : SON TITRE, SON CHIFFRE
   ============================================================ */

/* L'encre et le halo viennent du même groupe que le bouton d'écriture (style.css) : le
   rail est une unité sondée, main.js l'inscrit dans la liste et le cadre lui pose
   `.on-light` quand le film passe au clair sous lui. On ne redit ici que ce qui lui est
   propre.

   Le halo est celui des autres unités, et il n'est pas décoratif : douze pixels de texte
   posés sur une huile n'ont aucun contraste garanti — le film passe du bleu de nuit au
   blanc de nuage sous la même ligne, et c'est le halo qui tient la lecture entre les deux,
   la sonde de polarité ne faisant que choisir le bon des deux régimes. */
/* LE CHIFFRE SEUL DANS LE FLUX, LE TITRE EN VOLET (Lucas, 2026-08-14).

   Les deux colonnes de la référence ont été inversées : le chiffre passe à gauche, sur le
   montant, et le titre à droite. Ce n'est pas un simple échange de places — c'est un
   échange de rôles. Chez la référence, les titres sont le menu et les chiffres les
   commentent ; ici c'est la COLONNE DE CHIFFRES qui est le menu permanent, et le titre
   n'est plus qu'une réponse à une question qu'on pose en survolant.

   D'où le retour de `position: absolute` sur le titre, qui avait été abandonné quand les
   noms étaient écrits en permanence. Il est ici indispensable, et pour deux raisons qui
   tiennent chacune toute seule :

   · LA CIBLE DE SURVOL. Lucas demande que le titre ne sorte qu'au survol DU CHIFFRE. Un
     titre resté dans le flux garderait sa place réservée dans le lien même invisible : la
     boîte du <a> ferait 170 px de large et le survol se déclencherait à 150 px du chiffre,
     sur du vide. Hors flux, le lien ne mesure plus que son chiffre — la cible EST le
     chiffre, sans qu'aucune règle n'ait à le dire.
   · LA LARGEUR DE LA COLONNE. Cinq titres de longueurs différentes qui apparaissent et
     disparaissent feraient respirer la boîte du rail à chaque survol, et le montant avec.

   Le lien ne porte plus d'opacité : elle est descendue sur le chiffre et sur le titre,
   séparément. Posée ici, elle se serait multipliée avec la leur — un titre à 0,95 dans un
   lien à 0,78 se serait affiché à 0,74, et le réglage de contraste serait devenu illisible
   à écrire autant qu'à relire. */
.rail a {
  position: relative;
  display: block;
  color: var(--type-mark, var(--film-light));
  text-decoration: none;
  pointer-events: auto;
}

/* La bascule de polarité. Elle se redit ici pour la même raison que `.scroll-hint.on-light`
   et `.contact-open.on-light` se redisent dans style.css : la règle générale est écrite
   `.type-layer .on-light`, et le rail ne vit pas dans la couche de texte — il est dans la
   marge, hors du cadre. C'est le <nav> qui porte la classe (main.js l'inscrit comme une
   unité), et les titres comme les chiffres héritent par `currentColor`. */
.rail.on-light { --type-mark: var(--ink); }

/* LE TIRET EST DÉPOSÉ. Il était le marqueur de chapitre courant tant que les noms restaient
   cachés — mais il partageait alors la ligne avec un chiffre qui n'existait pas. Maintenant
   que la colonne EST une colonne de chiffres, c'est le chiffre qui porte l'état : celui du
   chapitre courant passe à pleine encre. Un trait EN PLUS d'un chiffre qui s'allume aurait
   dit deux fois la même chose, sur une ligne haute de neuf pixels.
   La balise reste écrite par src/rail.js et n'est que masquée ici : le jour où l'on veut
   revenir au rail à graduation, c'est cette seule règle qui saute. */
.rail a i { display: none; }

/* LA COLONNE DE CHIFFRES — désormais le menu lui-même, et le seul objet du rail qui soit
   là en permanence. Une liste numérotée ne dit pas la même chose qu'une liste : elle
   annonce qu'elle a un début, une fin, et qu'on la parcourt dans l'ordre. C'est exactement
   ce qu'est la descente. Réduite à ses chiffres, elle le dit sans écrire un mot — et la
   bande basse reste vide, ce qui était toute la raison de descendre le rail ici.

   En chiffres ROMAINS parce que le site en écrit déjà : l'œilleton de chaque chapitre filmé
   porte le sien (`eyebrow`). La colonne ne fait que rendre continu ce que l'œilleton dit
   par intermittence.

   10 px et non plus 8 : le chiffre était un repère à côté d'un titre, il est maintenant la
   seule chose à viser. Il lui faut la chasse d'une cible de pointeur, pas celle d'une note
   de bas de page. La largeur fixe de 24 px donne au titre une verticale de sortie unique,
   la même pour « I » que pour « VIII » — sans elle, le volet s'ouvrirait quatre pixels plus
   loin sur les chapitres à chiffre long, et la colonne des titres serait dentelée.

   `block` et non `inline` : c'est lui qui donne sa hauteur à la ligne maintenant que le
   titre est hors du flux. */
.rail a .rail-num {
  display: block;
  width: 24px;
  font-family: var(--mono);
  font-size: 10px;
  font-style: normal;
  letter-spacing: 0.1em;
  /* 1,7 : l'interligne de la colonne est ici, et nulle part ailleurs. Il vaut 17 px, soit
     la bande de survol de chaque entrée — assez pour que deux chiffres ne se touchent pas,
     assez pour qu'on en vise un à la souris sans viser son voisin. */
  line-height: 1.7;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: 0 1px 2px var(--mark-halo-near), 0 1px 6px var(--mark-halo-far);
  /* 0,62 au repos. La leçon de « DESCENDRE » (style.css) vaut encore : sous ~0,5 les
     jambages d'un glyphe se mélangent au film. On reste au-dessus, et l'écart au chapitre
     courant (pleine encre) suffit à le désigner. */
  opacity: 0.62;
  /* LA COLONNE SE DESSINE COMME LE LOGO SE DESSINE (Lucas, 2026-08-14).

     Le signe de la marque s'ouvre par `stroke-dashoffset` en 1,4 s sur
     `cubic-bezier(0.22, 1, 0.36, 1)`, après 1600 ms d'attente (style.css, et c'est l'easing
     de tout l'intro de pear.no). On ne pouvait pas reprendre le tracé — un chiffre n'a pas
     de chemin SVG à dérouler — mais on peut reprendre le GESTE, qui est de découvrir une
     forme au lieu de la faire apparaître. D'où le rognage : chaque chiffre est masqué
     depuis son bord bas et se découvre vers le bas. Mêmes durée, même courbe, même départ
     que le logo : les deux se dessinent dans le même souffle, et le rail cesse d'arriver
     1,2 s avant lui comme il le faisait avec son ancien fondu de 800 ms.

     Le décalage de 90 ms par entrée (`--i`, posé par src/rail.js) fait descendre le tracé
     le long de la colonne au lieu d'allumer les cinq chiffres ensemble : une graduation se
     trace, elle ne s'imprime pas.

     LES INSETS NÉGATIFS NE SONT PAS UNE MARGE DE CONFORT, ils sont obligatoires : le halo
     du chiffre est un `text-shadow` de 6 px, et un rognage posé à ras du bord de boîte le
     couperait — le chiffre arriverait sans le halo qui le rend lisible sur l'huile, puis le
     récupérerait d'un coup à la fin de la transition. */
  clip-path: inset(-6px -6px 100% -6px);
  transition:
    opacity 320ms linear,
    clip-path 1.4s cubic-bezier(0.22, 1, 0.36, 1) calc(1600ms + var(--i, 0) * 90ms);
}
.stage.is-ready .rail a .rail-num { clip-path: inset(-6px); }

/* Le chapitre courant, et celui qu'on vise. */
.rail a.on .rail-num,
.rail a:hover .rail-num,
.rail a:focus-visible .rail-num { opacity: 1; }

/* LE TITRE EST UN VOLET QUI SE DÉPLIE DEPUIS LE CHIFFRE.

   Fermé, il n'est pas seulement transparent : il est CONTRACTÉ SUR SA GAUCHE. C'est ce que
   fait `clip-path: inset(0 100% 0 0)` — la boîte est rognée depuis son bord droit jusqu'à
   sa gauche, donc le texte n'a plus aucune largeur, et il en reprend en s'ouvrant vers la
   droite, à partir du chiffre. Le mot sort du chiffre au lieu d'apparaître à côté de lui,
   et c'est toute la différence entre un dépliage et un fondu.

   `clip-path` et non `width` ou `scaleX` :
   · une largeur animée relance la mise en page à chaque image, et le texte se recomposerait
     en se dépliant — les mots sauteraient de ligne sous le rognage ;
   · `scaleX` étirerait les lettres, qui arriveraient écrasées puis se détendraient. Le
     rognage, lui, ne déforme rien : il découvre des glyphes déjà à leur taille définitive.

   Les 6 px de `translateX` en plus du rognage sont ce qui fait le geste : sans eux le texte
   se découvre sur place, avec eux il avance en se découvrant.

   LE VOLET EST NERVEUX, ET C'EST L'INVERSE DU TRACÉ D'ENTRÉE (Lucas, 2026-08-14). Les deux
   gestes ne répondent pas à la même chose : l'entrée est une mise en place, que personne
   n'a demandée et qui a le droit de prendre 1,4 s ; le survol est une RÉPONSE, et une
   réponse en retard se lit comme une hésitation de la page. 190 ms sur
   `cubic-bezier(0.16, 1, 0.3, 1)` — une courbe qui a parcouru 80 % de sa distance au tiers
   du temps : le mot est déjà là quand l'œil arrive dessus, il ne fait que finir de se
   poser. Le fondu, lui, tombe à 110 ms : plus court que le dépliage, pour que le mot soit
   lisible avant d'être entièrement sorti — sinon on lit un texte qui finit de s'ouvrir, ce
   qui se remarque.

   38 px : les 24 px du chiffre plus 14 de gouttière — la même gouttière que celle qui
   séparait les deux colonnes avant l'inversion. */
.rail a em {
  position: absolute;
  left: 38px;
  top: 0;
  font-family: var(--display);
  font-size: 12px;
  font-weight: 600;
  font-style: normal;
  letter-spacing: 0.005em;
  /* Le même 1,7 que le chiffre, sur le même corps de référence : c'est ce qui pose le titre
     sur la ligne d'écriture de son numéro, sans avoir à décaler l'un ou l'autre à la main. */
  line-height: 1.7;
  white-space: nowrap;
  text-shadow: 0 1px 2px var(--mark-halo-near), 0 1px 6px var(--mark-halo-far);
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  transform: translateX(-6px);
  transition:
    opacity 110ms linear,
    clip-path 190ms cubic-bezier(0.16, 1, 0.3, 1),
    transform 190ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* AU SURVOL DU CHIFFRE — et le sélecteur dit bien ce qu'il dit, puisque le lien ne mesure
   que son chiffre (voir plus haut) : `.rail a:hover`, c'est le survol du chiffre.
   Le focus clavier ouvre le même volet : sans lui, un visiteur qui tabule dans le rail
   passerait de chiffre en chiffre sans jamais savoir où il va. */
.rail a:hover em,
.rail a:focus-visible em {
  opacity: 0.95;
  clip-path: inset(0 0 0 0);
  transform: none;
}

/* L'ÉTAT DE CHAPITRE COURANT NE TOUCHE QUE LE CHIFFRE (`.rail a.on .rail-num`, § 2), et
   surtout pas le titre : `.on` est écrit par src/rail.js au fil du scroll, alors que le
   titre ne s'ouvre que sur un geste. Les faire coïncider ferait sortir un volet tout seul
   à chaque changement de chapitre, sans que personne ne l'ait demandé. */

/* ============================================================
   3 · CE QUI EST TOMBÉ AVEC LE DÉMÉNAGEMENT
   ============================================================ */

/* LE BOUTON DE GRILLE N'EST PLUS (2026-08-14), et son sommaire non plus.

   Les quatre points en carré, sous la marque, existaient pour une seule raison : DÉPLIER
   les noms que le rail gardait pour lui. Le rail les écrit maintenant en permanence — le
   bouton n'avait plus rien à ouvrir, et le retrait du texte (`.is-summary`) plus rien à
   protéger, puisque la liste ne croise plus la colonne de texte : elle est descendue dans
   la bande basse, qui est vide.

   Retiré de index.html, débranché de src/main.js. `Rail.bindToggle` reste écrit dans
   src/rail.js : c'est le point de rebranchement si l'on referme un jour la liste.

   Une seule ligne survit ici, et elle n'appartenait pas au bouton : la transition de la
   couche de texte. Elle sert à toute bascule d'opacité de la couche, pas seulement au
   sommaire disparu. */
.type-layer { transition: opacity 380ms var(--ease); }

/* ============================================================
   4 · CE QUI L'ÉTEINT
   ============================================================ */

/* RIEN N'EST ÉCRIT ICI POUR LE PANNEAU D'ÉCRITURE, et c'est un choix : le panneau est à
   z-index 25 avec son voile plein écran, le rail à 4. Il est donc déjà couvert, et une
   règle qui l'éteindrait en plus ne ferait que dupliquer — mal, puisqu'elle aurait besoin
   d'un drapeau global que contact.js ne pose pas (il ne marque que son propre panneau).

   Sous 900 px, la marge ne fait plus que 49 px : la liste, qui est maintenant écrite en
   toutes lettres, déborderait sur la colonne de texte au lieu de longer le montant. Le rail
   disparaît entièrement plutôt que de se replier — sur un téléphone, le pouce a déjà
   l'affordance du geste, et l'indice de scroll dit le reste. */
@media (max-width: 900px) {
  .rail { display: none; }
}

/* En reduced-motion on garde le rail ET son volet — le titre reste la seule façon de savoir
   où mène un chiffre, le supprimer coûterait l'information. Ce qui tombe, c'est le geste :
   plus de fondu d'entrée, et le volet s'ouvre d'un coup au lieu de se dérouler. */
@media (prefers-reduced-motion: reduce) {
  .rail { transition: none; }
  .rail a .rail-num, .rail a em { transition: none; }
  .rail a em { transform: none; }
  /* Le tracé n'a pas lieu : les chiffres sont posés entiers, halo compris. */
  .rail a .rail-num { clip-path: inset(-6px); }
}
