The sold HTML is a single block. A .st-progress-scroll frame wraps everything: it carries the #0a0a0f background, the text colour and the font (Inter, then the system font), it is position: relative so the absolutely positioned label has a reference, and it is a flex column with max-height: 100% — your colours and your font go on that single rule. Inside, .scroll-container#progress-scroll (width: 100%, height: 280px, overflow: hidden auto — no horizontal scrolling, automatic vertical scrolling —, position: relative, scroll-behavior: smooth) first holds the track .st-progress-bar-track (4 px tall, white at 8%, position: sticky; top: 0, z-index: 20) and its fill .st-progress-bar-fill#progress-fill (width: 0%, gradient #6366f1 → #a855f7 → #ec4899, right corners rounded by 2 px, transition: width 0.05s linear), then the body .progress-inner (padding: 24px 20px): five 1 rem white h3, five 0.8 rem p in white at 50% with a 1.7 line height, four 60 × 2 px .section-divider. Their margins are written in full (margin: 0 0 12px for headings, 0 0 16px for paragraphs): the rendering does not depend on a reset in the stylesheet that hosts the block. Because the track sits inside the element that scrolls, sticky keeps it glued to the top edge while the text passes underneath — measured: the track stays at y = 0 of the container at the end of the run. After the container, still inside the frame, a span.scroll-hint-label reading “Scrollez pour explorer” in 0.65 rem uppercase, white at 30%, anchored 8 px from the bottom of the frame, pulses from 0.3 to 0.7 opacity every 2 s (@keyframes pulseHint). WebKit scrollbars are narrowed to 4 px (::-webkit-scrollbar).
The script does one thing. Wrapped in an immediately invoked function — progressScroll and progressFill do not exist in window and cannot collide with your own variables —, it fetches the two elements by id and, if both exist, attaches a single scroll listener to the container — no requestAnimationFrame, no loop: nothing runs at rest. On each event, scrollHeight − clientHeight gives the total run in pixels (215 px for the sold content at 1,264 px wide, 454 px at 374 px, as paragraphs wrap), scrollTop / run × 100 gives the percentage, written as is into style.width. The 50 ms transition smooths the step between two events. Measured in Chromium on a blank page, one 60 px wheel notch: scrollTop goes to 60, the inline width to 27.907%, the rendered width to 352.7 px on a 1,264 px track — the exact proportion, settled within 150 ms. End of run: 215 / 215, i.e. 100%; back to top: 0%. Nothing is computed on load: until a scroll event has fired, style.width stays empty and the bar at 0%.
The percentage only exists as a width. The progress variable does hold 0 to 100, but the code never writes it anywhere as text: the reader sees a bar, not “47%”. Displaying it takes one line after the existing one — label.textContent = Math.round(progress) + '%' — and that is what the three scenes below do. Three design choices, checked on a blank page. The .st-progress-scroll frame ships its own background: pasted as is onto a white page, the block stays a dark rectangle where headings, text and track can be read (measured: no text block matching the background colour, on a white page as on a dark one) — without that background, white text would only exist on an already dark site. The same frame is the label's reference: an element in position: absolute anchors to its nearest positioned ancestor, and the frame is one; the label lands 8 px from the bottom of the block, over the last visible line (measured: y = 268 in a 280 px frame). Finally the frame is a flex column with max-height: 100%: placed in a fixed-height box shorter than 280 px, the container shrinks to the box instead of overflowing (measured: in a 250 px box, container at 250 px, track and label visible); on a page with no height constraint, that max-height has no effect. The code does, however, target two fixed ids: a second copy of the block on the same page has a bar that never moves (measured: empty style.width on the second one after 100 px of scrolling).
Three properties set the floor: the two-value syntax overflow: hidden auto (Chrome 68, Firefox 61, Safari 13.1 — earlier, the declaration is ignored and the container does not scroll), position: sticky (Chrome 56, Firefox 32, Safari 13) and, for programmatic scrolling only, scroll-behavior (Safari 15.4). The JavaScript is ES2015 (const, arrow function): Chrome 49, Firefox 45, Safari 10. Zero dependencies. Progress is computed in JavaScript on the scroll event, not with animation-timeline: it works in Firefox, which renders native CSS scroll-driven animations in no version at all.
Without JavaScript, the container scrolls normally and the track stays empty: a 4 px grey line, no error. Without the two-value overflow syntax (Safari before 13.1), the container does not scroll: the text overflows the 280 px and the bar never moves — write overflow-x: hidden; overflow-y: auto as two declarations to cover those versions.