diff --git a/demo/index.html b/demo/index.html index 7be1a85f8..159f8352e 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,17 @@

${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); + } 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; 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');