Tout tient dans .spb-scene : position: relative, width: 100%, aspect-ratio: 16 / 10 plafonné par max-height: 340px (sa hauteur ne vient que de ces deux règles, tous ses enfants étant en absolu), overflow: hidden, isolation: isolate, fond rgb(12, 16, 34), coins de 14 px, et la pile de polices Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif déclarée une seule fois, héritée par les titres, les paragraphes et la pastille. Dedans, trois enfants en absolu. .spb-scroll (inset: 0, overflow-y: auto, padding: 22px 22px 22px 40px, scroll-behavior: smooth) porte le texte — six h4 de 16 px et six p de 13 px / 1,7 — et cache sa barre native par scrollbar-width: none doublé de ::-webkit-scrollbar { width: 0 } (mesuré : offsetWidth = clientWidth, aucune gouttière). .spb-beam, le rail : left: 14px, top: 18px, bottom: 18px, 4 px de large, blanc à 10 %, z-index: 3, pointer-events: none — il mesure donc la hauteur de la boîte moins 36 px (304 px sous le plafond). Son remplissage .spb-beam-fill a pour hauteur calc(var(--spb-p, 0) * 100%), un dégradé rgb(92, 255, 216) → rgb(109, 139, 255) et deux ombres (12 px bleu à .8, 4 px menthe à .9) ; le point .spb-beam-dot, 12 px blanc, halo 0 0 14px 3px, est posé à top: calc(var(--spb-p, 0) * 100%) et recentré par translate(-50%, -50%). .spb-pct, la pastille (right: 14px, top: 12px, 12 px, tabular-nums, noir à 40 %), affiche le pourcentage.
Le JavaScript est une IIFE ES5 qui traite chaque .spb-scene de la page (mesuré avec deux instances : 0 % et 25 %, indépendantes). Sa fonction update() calcule max = scrollHeight − clientHeight, puis p = scrollTop / max (0 si max ≤ 0), écrit --spb-p sur le rail avec toFixed(4) et le texte Math.round(p × 100) + '%' dans la pastille — si elle existe. Elle est appelée une fois à l'initialisation, à chaque scroll de la boîte ({ passive: true }) et à chaque resize de la fenêtre. Aucune transition, aucun lissage : le rail suit le défilement image par image. Mesuré dans Chromium sur une page vierge de 1 280 px, boîte de 1 264 × 340 px, course de 125 px : un cran de molette de 60 px donne 0.4800 → « 48% », 145,9 px de remplissage sur un rail de 304 px et le centre du point exactement à 145,9 px ; 0.9600 au deuxième cran, 1.0000 au troisième, « 100% », le point dépassant de 6 px sous le rail (c'est le translate) ; molette inverse, retour à 0.0000. 200 appels de update() coûtent 0,2 ms. scroll-behavior: smooth ne concerne que les défilements programmés : scrollTo(max) déclenche 21 événements scroll et atteint 1 en ≈ 170 ms — un seul événement sous prefers-reduced-motion, où la règle passe en auto.
La hauteur est la partie du code à comprendre avant d'y toucher. Tous les enfants de .spb-scene sont en absolu : sans aspect-ratio, la boîte ferait 0 px, et avec le ratio seul elle grandirait avec sa largeur — sur une page de 1 280 px, 790 px de haut pour 465 px de contenu vendu (texte, marges et padding), donc scrollHeight = clientHeight, max = 0 et un rail figé à 0 % ; c'est ce que max-height: 340px empêche. Mesuré sur une page vierge avec le texte vendu : à 1 280 px comme à 960, la boîte fait 340 px de haut, pour 125 px de course (1 280) ou 257 px (960) ; le plafond cesse d'agir sous 544 px de large, où le ratio reprend la main — 290 px de haut et 462 px de course dans une colonne de 480 px, 234 px et 606 px sur un mobile de 390 px. La règle à retenir : la course vaut scrollHeight − clientHeight, soit le texte, ses marges et les 44 px de padding moins la hauteur de la boîte, et update() renvoie 0 dès qu'elle n'est plus positive ; avec votre propre texte, plus long que six paragraphes, remplacez le plafond par la hauteur de votre mise en page. Trois autres choix du code : la font-family est déclarée une seule fois sur .spb-scene (mesuré : Inter sur les titres, les paragraphes et la pastille, sans aucun style en ligne) ; le halo du point voyage avec lui mais ses deux box-shadow sont constantes, identiques à 0 % et à 100 % — c'est la position qui bouge, pas l'intensité ; et @media (prefers-reduced-motion: reduce) passe scroll-behavior en auto, ce que le texte de la démo annonce. L'écouteur resize sur window, lui, n'est jamais retiré : si vous détruisez une scène dynamiquement, retirez-le avec.
Code ES5 (var, function), une variable CSS lue par deux calc(), un écouteur { passive: true }. Le plancher réel est aspect-ratio : sans lui, .spb-scene n'a aucune hauteur, tous ses enfants étant en absolu ; max-height ne fait que plafonner cette hauteur. scroll-behavior (Safari 15.4) et scrollbar-width (Chrome 121, Safari 18.2, relayé avant par ::-webkit-scrollbar) sont cosmétiques. Le rail est piloté par JavaScript, pas par animation-timeline : il fonctionne dans Firefox, qu'aucune version ne fait défiler en CSS natif — c'est ce qui le sépare de Scroll Progress Bar (fx-0547) et Progress Bar (fx-0555). Zéro dépendance : aucune bibliothèque, aucune image, aucune police à charger — la pile Inter, -apple-system, … retombe sur la police système si Inter n'est pas installée.