From d6f36a909c782d23e27878def966ef1cf9d52be7 Mon Sep 17 00:00:00 2001 From: Gerard Recinto Date: Fri, 30 Oct 2026 19:02:11 -0700 Subject: [PATCH 1/2] Make the runbook scroll story shorter and let it play on its own The pinned runbook needed about 3.5 screens of scrolling before all five rows were lit, and stayed on row 1 until the visitor kept scrolling. On a phone that reads as the page being stuck loading. Shorten the scroll track (intro 150vh to 120vh, pin 340vh to 250vh) and advance the rows on a timer once the stage is on screen. Scrolling still wins when the visitor is ahead of the timer. Thanks, Gerard --- demo/index.html | 13 ++++++++++--- tests/homepage.spec.ts | 9 +++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/demo/index.html b/demo/index.html index 7be1a85f8..fb8a24d96 100644 --- a/demo/index.html +++ b/demo/index.html @@ -426,9 +426,9 @@ .story-cta .primary { background: #10b981; color: #000; } .story-cta .ghost { border: 1px solid rgb(var(--c-dark-700)); color: rgb(var(--c-fg)); background: rgb(var(--c-dark-900) / 0.6); } /* Scripted mode: the pin track is tall and the stage sticks while scroll drives the steps. */ - .js-story .story-intro { display: block; height: 150vh; padding: 0; } + .js-story .story-intro { display: block; height: 120vh; padding: 0; } .js-story .story-intro > div { position: sticky; top: 0; height: 100svh; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; max-width: 58rem; margin: 0 auto; padding: 5rem 1.5rem 2rem; } - .js-story .story-pin { height: 340vh; } + .js-story .story-pin { height: 250vh; } .js-story .story-stage { position: sticky; top: 0; height: 100svh; min-height: 0; padding-top: 5.5rem; padding-bottom: 2rem; } .story-hint { display: none; } .js-story .story-hint { display: flex; align-items: center; gap: 0.5rem; margin: 2rem 0 0; font: 500 0.8rem/1 'JetBrains Mono', 'JetBrains Fallback', monospace; letter-spacing: 0.08em; text-transform: uppercase; color: rgb(var(--c-slate-400)); transition: opacity 0.4s; } @@ -3117,7 +3117,7 @@

${res.title}

var rows = story.querySelectorAll('.story-row'); var tagged = story.querySelectorAll('[data-for]'); var hint = document.getElementById('story-hint'); - var step = -1, ticking = false, timed = 0; + var step = -1, ticking = false, timed = 0, auto = -1, autoTimer = 0; // The line also lights on a timer, so a fast scroll cannot skip it and nobody has to scroll to read it. var timer = setTimeout(function tick() { timed++; @@ -3137,6 +3137,13 @@

${res.title}

var pr = pin.getBoundingClientRect(); var t = Math.min(0.9999, Math.max(0, -pr.top / (pr.height - vh))); var n = pr.top > vh * 0.4 ? -1 : Math.floor(t * 5); + // Once the stage is on screen the rows also play on a timer, so nobody has to keep scrolling to see the whole runbook. + // Scrolling still wins whenever the visitor is ahead of the timer. + if (pr.top > vh * 0.9) auto = -1; + if (pr.top <= vh * 0.4 && pr.bottom >= vh * 0.6) { + if (auto < 4 && !autoTimer) autoTimer = setTimeout(function () { autoTimer = 0; auto++; frame(); }, auto < 0 ? 700 : 1100); + n = Math.max(n, auto); + } if (n === step) return; step = n; rows.forEach(function (row, i) { row.classList.toggle('on', i <= n); }); diff --git a/tests/homepage.spec.ts b/tests/homepage.spec.ts index 5f4362799..68525b005 100644 --- a/tests/homepage.spec.ts +++ b/tests/homepage.spec.ts @@ -363,6 +363,15 @@ test.describe('Homepage', () => { await expect(page.locator('.story-verdict .v-block')).not.toHaveClass(/show/); }); + test('the runbook rows play on their own once the stage is on screen', async ({ page }) => { + await page.goto('/', { waitUntil: 'domcontentloaded' }); + const [top, vh] = await page.locator('.story-pin').evaluate((el) => [el.getBoundingClientRect().top + window.scrollY, window.innerHeight]); + // Park just inside the stage and do not scroll again. + await page.evaluate((y) => window.scrollTo(0, y), top - vh * 0.3); + await expect(page.locator('.story-row.on')).toHaveCount(5, { timeout: 8_000 }); + await expect(page.locator('.story-verdict .v-allow')).toHaveClass(/show/); + }); + test('a scroll hint shows at the top and goes away once the visitor scrolls', async ({ page }) => { await page.goto('/', { waitUntil: 'domcontentloaded' }); const hint = page.locator('#story-hint'); From 30138a8c316ec1b375472d320e1fb52e7468c625 Mon Sep 17 00:00:00 2001 From: Gerard Recinto Date: Fri, 30 Oct 2026 19:13:20 -0700 Subject: [PATCH 2/2] Drop the pending runbook tick when the stage leaves the screen Thanks, Gerard --- demo/index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/demo/index.html b/demo/index.html index fb8a24d96..159f8352e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -3143,6 +3143,10 @@

${res.title}

if (pr.top <= vh * 0.4 && pr.bottom >= vh * 0.6) { if (auto < 4 && !autoTimer) autoTimer = setTimeout(function () { autoTimer = 0; auto++; frame(); }, auto < 0 ? 700 : 1100); n = Math.max(n, auto); + } else if (autoTimer) { + // Off the stage: drop a pending tick so it cannot advance the rows while nobody is looking at them. + clearTimeout(autoTimer); + autoTimer = 0; } if (n === step) return; step = n;