From 4fac442089ce4fcd31dbb8cfe2e52ad2d68ae265 Mon Sep 17 00:00:00 2001 From: felirami Date: Tue, 1 Sep 2026 23:11:59 -0400 Subject: [PATCH 1/4] Add motion layer and hero signal board scripts Introduce two dependency-free TypeScript modules bundled by Astro: motion.ts (scroll reveals, condensed masthead with progress line, active nav, pointer highlights, magnetic buttons, metric counters, self-drawing signal map) and signal-board.ts (the hero canvas: procedurally routed traces with travelling pulses, pointer spotlight, off-screen and hidden-tab pausing, static frame under reduced motion). Load Google Fonts through preconnect and link tags instead of a CSS @import, and add the inline html.js gate that scopes every hidden-until-animated rule so the page stays fully readable without JavaScript. --- src/layouts/BaseLayout.astro | 7 + src/scripts/motion.ts | 251 +++++++++++++++++++ src/scripts/signal-board.ts | 458 +++++++++++++++++++++++++++++++++++ 3 files changed, 716 insertions(+) create mode 100644 src/scripts/motion.ts create mode 100644 src/scripts/signal-board.ts diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 4da1b3c..0a5ba6d 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -50,7 +50,14 @@ const canonicalURL = canonicalPath === null ? null : new URL(canonicalPath, Astr {title} + + + + {structuredData && ( From 4465d5a9000f6d1ae7880ec5993f91b6967519c8 Mon Sep 17 00:00:00 2001 From: felirami Date: Tue, 1 Sep 2026 23:11:59 -0400 Subject: [PATCH 3/4] Extend site contracts and QA for the motion layer Add tests covering the progressive-enhancement gate, font loading, the reduced-motion policy, the deck links, and that the homepage metrics match the deck figures exactly. Walk the page in the Playwright QA before the screenshot and axe run so scroll-triggered reveals have fired. Document the motion layer in the README. --- README.md | 9 +++++++++ scripts/qa.mjs | 11 +++++++++++ tests/site-contract.test.mjs | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/README.md b/README.md index 2492c9f..4ba8ac1 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,12 @@ npm run preview ## Deployment Production will deploy from the tested `main` branch to the Arca Computer Cloudflare account after the company zone is attached and the first deployment is verified. The Vercel deployment remains the live rollback target until that cutover passes. + +## Motion + +The homepage is fully readable without JavaScript. A one-line inline script in `src/layouts/BaseLayout.astro` adds `html.js`, and every hidden-until-revealed rule in `public/styles.css` is scoped to that class and to `prefers-reduced-motion: no-preference`. + +- `src/scripts/motion.ts` — scroll reveals, condensed masthead + progress line, active nav, pointer highlights, magnetic buttons, metric counters, and the self-drawing signal map. +- `src/scripts/signal-board.ts` — the hero canvas (procedural traces with travelling pulses). It pauses off-screen and when the tab is hidden, and renders one static frame under reduced motion. + +To test the reduced-motion path, emulate `prefers-reduced-motion: reduce` in DevTools (Rendering panel) and reload. diff --git a/scripts/qa.mjs b/scripts/qa.mjs index 224c146..abe5016 100644 --- a/scripts/qa.mjs +++ b/scripts/qa.mjs @@ -20,6 +20,17 @@ for (const [name, viewport] of Object.entries({ }); page.on('pageerror', (error) => consoleErrors.push(error.message)); const response = await page.goto(baseURL, { waitUntil: 'networkidle' }); + // Walk the page so scroll-triggered reveals have fired before screenshots and axe run. + await page.evaluate(async () => { + const step = Math.max(240, Math.round(window.innerHeight * 0.6)); + const height = document.documentElement.scrollHeight; + for (let y = 0; y <= height; y += step) { + window.scrollTo({ top: y, behavior: 'instant' }); + await new Promise((resolve) => setTimeout(resolve, 120)); + } + window.scrollTo({ top: 0, behavior: 'instant' }); + }); + await page.waitForTimeout(1500); await page.screenshot({ path: `artifacts/${name}.png`, fullPage: true }); await page.addScriptTag({ content: AxeBuilder.source }); const axe = await page.evaluate(async () => globalThis.axe.run(document)); diff --git a/tests/site-contract.test.mjs b/tests/site-contract.test.mjs index 8cf73f0..50614c5 100644 --- a/tests/site-contract.test.mjs +++ b/tests/site-contract.test.mjs @@ -94,3 +94,39 @@ test('the company deck reflects the current August 30 product state', async () = assert.match(styles, /\.deck-header nav a\s*\{[^}]*min-height:\s*24px/s); }); + +const literal = (value) => new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + +test('the homepage motion layer is progressive and reduced-motion aware', async () => { + const layout = await read('src/layouts/BaseLayout.astro'); + const page = await read('src/pages/index.astro'); + const styles = await read('public/styles.css'); + + assert.match(layout, /documentElement\.classList\.add\('js'\)/); + assert.match(layout, /rel="preconnect" href="https:\/\/fonts\.gstatic\.com"/); + assert.match(layout, /fonts\.googleapis\.com\/css2\?family=DM\+Mono/); + assert.doesNotMatch(styles, /@import/); + assert.match(styles, /@media \(prefers-reduced-motion: no-preference\)/); + assert.match(styles, /@media \(prefers-reduced-motion: reduce\)/); + assert.match(styles, /html\.js \[data-reveal\]/); + assert.match(page, /class="signal-board" aria-hidden="true"/); + assert.match(page, /

[\s\S]*The company[\s\S]*is the computer\.[\s\S]*<\/h1>/); + assert.match(page, /import \{ initMotion \} from '\.\.\/scripts\/motion'/); + assert.match(page, /import \{ mountSignalBoard \} from '\.\.\/scripts\/signal-board'/); +}); + +test('the homepage links to the deck and repeats the deck metrics exactly', async () => { + const page = await read('src/pages/index.astro'); + const deck = await read('public/deck/index.html'); + + assert.match(page, /