import { clickNavigationLink, expect, test } from '../helpers/serve-dist'; import { installSceneInstrumentation, readSceneInstrumentation, waitForOneAnimationFrame, } from '../helpers/scene-instrumentation'; const sections = [ { id: 'start', label: 'Start' }, { id: 'profile', label: 'Profile' }, { id: 'experience', label: 'Experience' }, { id: 'infrastructure', label: 'Infrastructure' }, { id: 'selected-builds', label: 'Selected builds' }, { id: 'additional-work', label: 'Additional work' }, { id: 'toolkit', label: 'Toolkit' }, { id: 'contact', label: 'Contact' }, ] as const; test.beforeEach(async ({ page }) => installSceneInstrumentation(page)); test('keeps navigation, HUD, progress, chapter, and arrival on the same next-frame section snapshot', async ({ page, distUrl, }) => { const graphicsErrors: string[] = []; page.on('console', (message) => { if ( message.type() === 'error' && /THREE\.WebGLProgram|Shader Error|VALIDATE_STATUS/iu.test(message.text()) ) graphicsErrors.push(message.text()); }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', /running|failed|static/u); const chapterColors: string[] = []; for (const section of sections) { await page.evaluate((id) => { const heading = document.getElementById(`${id}-heading`); if (!(heading instanceof HTMLElement)) throw new Error(`Missing heading: ${id}`); const top = heading.getBoundingClientRect().top + window.scrollY; const viewportHeight = window.visualViewport?.height ?? window.innerHeight; const destination = id === 'contact' ? document.documentElement.scrollHeight : Math.max(0, top - viewportHeight * 0.35 + 1); window.scrollTo({ top: destination, behavior: 'instant' }); }, section.id); await waitForOneAnimationFrame(page); await expect(page.locator(`nav a[href="#${section.id}"]`)).toHaveAttribute('aria-current', 'page'); await expect(page.locator('.journey-hud__section')).toHaveText(section.label); const routeText = await page.locator('.journey-hud__route').textContent(); expect(routeText).toMatch(/^ROUTE (?:100|[0-9]{1,2})%$/u); if (section.id === 'contact') { await expect(page.locator('.journey-hud__arrival')).toHaveText('ARRIVED'); await expect(page.locator('.journey-hud__route')).toHaveText('ROUTE 100%'); } else { await expect(page.locator('.journey-hud__arrival')).toHaveCount(0); } // The section snapshot is emitted in the scroll RAF above; the scene's // already-running loop must consume it no later than its next RAF. await waitForOneAnimationFrame(page); const clearColors = (await readSceneInstrumentation(page)).clearColors.filter((color) => color !== '#000000'); const latestChapterColor = clearColors.at(-1); if (latestChapterColor === undefined) throw new Error(`Missing rendered chapter color for ${section.id}`); chapterColors.push(latestChapterColor); } const instrumentation = await readSceneInstrumentation(page); expect(instrumentation.maximumCanvasCount).toBeLessThanOrEqual(1); const chapterEnergy = chapterColors.map(colorEnergy); for (let index = 1; index < chapterEnergy.length; index += 1) { expect(chapterEnergy[index]).toBeGreaterThanOrEqual(chapterEnergy[index - 1] ?? 0); } expect(chapterEnergy.at(-1)).toBeGreaterThan(chapterEnergy[0] ?? 0); expect(new Set(chapterColors).size).toBeGreaterThanOrEqual(4); expect(graphicsErrors).toEqual([]); }); test('draws only the independently eligible next roadside sign during passive forward travel', async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const viewport = page.viewportSize(); if (viewport === null) throw new Error('Missing browser viewport'); const oracle = await page.evaluate((candidateDistanceVh) => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; const headingTop = profile.getBoundingClientRect().top + window.scrollY; const destination = Math.max( 1, headingTop - viewportHeight * (0.35 + candidateDistanceVh), ); return { destination, viewportHeight, expectedLabel: 'Profile' }; }, triggerProbeDistanceVh(viewport.width, viewport.height)); await page.evaluate(() => window.dispatchEvent(new WheelEvent('wheel', { deltaY: 1 }))); for (let y = 1; y < oracle.destination; y += oracle.viewportHeight * 0.5) { await page.evaluate((nextY) => window.scrollTo(0, nextY), y); await waitForOneAnimationFrame(page); } await page.evaluate((destination) => window.scrollTo(0, destination), oracle.destination); await waitForOneAnimationFrame(page); await expect.poll(async () => (await readSceneInstrumentation(page)).routeLabelsDrawn).toContain(oracle.expectedLabel); const labels = (await readSceneInstrumentation(page)).routeLabelsDrawn; expect([...new Set(labels)]).toEqual([oracle.expectedLabel]); }); test('keeps a readable sign immutable through reversal, resize, and a multi-section jump', async ({ page, distUrl, }) => { await page.setViewportSize({ width: 1440, height: 900 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const oracle = await page.evaluate(() => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; const headingTop = profile.getBoundingClientRect().top + window.scrollY; return { destination: Math.max(1, headingTop - viewportHeight * (0.35 + 0.45)), viewportHeight, }; }); await page.mouse.wheel(0, 1); for (let y = 1; y < oracle.destination; y += oracle.viewportHeight * 0.5) { await page.evaluate((nextY) => window.scrollTo(0, nextY), y); await waitForOneAnimationFrame(page); } await page.evaluate((destination) => window.scrollTo(0, destination), oracle.destination); await expect.poll(async () => (await readSceneInstrumentation(page)).routeLabelsDrawn) .toContain('Profile'); await page.waitForTimeout(1_250); await page.mouse.wheel(0, -120); await page.setViewportSize({ width: 844, height: 390 }); await page.waitForTimeout(500); const resizedFrame = (await readSceneInstrumentation(page)).signFrames.findLast((frame) => ( frame.targetId === 'profile' && frame.viewport.width === 844 && frame.viewport.height === 390 )); if (resizedFrame === undefined) throw new Error('Missing post-resize Profile frame'); expect(resizedFrame.phase).toBe('dwell'); expect(resizedFrame.viewportProfile).toBe('short'); expect(resizedFrame.bounds.left).toBeGreaterThanOrEqual(8); expect(resizedFrame.bounds.right).toBeLessThanOrEqual(288); expect(countSignPresentations( (await readSceneInstrumentation(page)).signFrames, 'profile', )).toBe(1); await page.evaluate(() => { const target = document.getElementById('selected-builds-heading'); if (!(target instanceof HTMLElement)) throw new Error('Missing selected builds heading'); window.scrollTo(0, target.getBoundingClientRect().top + window.scrollY); }); await page.waitForTimeout(450); const labels = (await readSceneInstrumentation(page)).routeLabelsDrawn; expect([...new Set(labels)]).toEqual(['Profile']); expect((await readSceneInstrumentation(page)).maximumCanvasCount).toBeLessThanOrEqual(1); }); test('carries a departed sign past the car before its restrained edge fade', async ({ page, distUrl, }) => { await page.setViewportSize({ width: 1_440, height: 900 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const viewport = page.viewportSize(); if (viewport === null) throw new Error('Missing browser viewport'); const destination = await page.evaluate((candidateDistanceVh) => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, profile.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + candidateDistanceVh), ); }, triggerProbeDistanceVh(viewport.width, viewport.height)); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell')).toBe(true); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => ( frame.targetId === 'profile' && frame.phase === 'depart' && frame.progress >= 0.52 )), { intervals: [16], timeout: 5_000 }).toBe(true); const frames = (await readSceneInstrumentation(page)).signFrames; const dwell = frames.findLast((frame) => frame.targetId === 'profile' && frame.phase === 'dwell'); const passing = frames.findLast((frame) => ( frame.targetId === 'profile' && frame.phase === 'depart' && frame.progress >= 0.3 && frame.opacity >= 0.98 )); if (dwell === undefined || passing === undefined) { throw new Error('Missing comparable Profile dwell and pass-by frames'); } expect(passing.opacity).toBeGreaterThanOrEqual(0.98); expect(passing.bounds.width).toBeGreaterThan(dwell.bounds.width * 1.18); expect(passing.bounds.left).toBeLessThan(dwell.bounds.left - 24); expect(passing.targetId).toBe(dwell.targetId); }); test('shows exactly one destination sign for a navigator click and suppresses history restoration', async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await clickNavigationLink(page, '#selected-builds'); await expect(page.locator('#selected-builds-heading')).toBeFocused(); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.findLast((frame) => ( frame.targetId === 'selected-builds' ))?.phase).toBe('dwell'); await clickNavigationLink(page, '#selected-builds'); await expect(page.locator('#selected-builds-heading')).toBeFocused(); await page.waitForTimeout(2_400); const afterRepeat = await readSceneInstrumentation(page); expect(afterRepeat.routeLabelsDrawn).toEqual(['Selected builds']); expect(countSignPresentations(afterRepeat.signFrames, 'selected-builds')).toBe(1); await page.goBack({ waitUntil: 'domcontentloaded' }); await page.waitForTimeout(400); expect((await readSceneInstrumentation(page)).routeLabelsDrawn).toEqual(['Selected builds']); }); test('keeps the non-navigator Explore work section jump sign-suppressed', async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await page.locator('.hero-journey__action').click(); await expect(page.locator('#selected-builds-heading')).toBeFocused(); await page.waitForTimeout(400); expect((await readSceneInstrumentation(page)).routeLabelsDrawn).toEqual([]); const viewport = page.viewportSize(); if (viewport === null) throw new Error('Missing browser viewport'); const destination = await page.evaluate((candidateDistanceVh) => { const heading = document.getElementById('additional-work-heading'); if (!(heading instanceof HTMLElement)) throw new Error('Missing Additional work heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, heading.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + candidateDistanceVh), ); }, triggerProbeDistanceVh(viewport.width, viewport.height)); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.findLast((frame) => frame.targetId === 'additional-work')?.phase).toBe('dwell'); expect((await readSceneInstrumentation(page)).routeLabelsDrawn).toEqual(['Additional work']); }); test('keeps a navigator-triggered Profile sign fully beside its visible destination panel', async ({ page, distUrl, }) => { await page.setViewportSize({ width: 780, height: 493 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await clickNavigationLink(page, '#profile'); await expect(page.locator('#profile-heading')).toBeFocused(); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => ( frame.targetId === 'profile' && frame.phase === 'dwell' ))).toBe(true); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'profile' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error('Missing navigator-triggered Profile dwell frame'); const panel = await page.locator('#profile').evaluate((element) => { const bounds = element.getBoundingClientRect(); return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom }; }); expect(frame.bounds.right).toBeLessThanOrEqual(panel.left - 16); expect(frame.bounds.left).toBeGreaterThanOrEqual(8); }); test('keeps a portrait navigator sign clear of its full-width destination panel', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact portrait composition is sufficient.'); await page.setViewportSize({ width: 390, height: 844 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await clickNavigationLink(page, '#profile'); await expect(page.locator('#profile-heading')).toBeFocused(); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell')).toBe(true); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'profile' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error('Missing portrait navigator Profile dwell frame'); const panels = await page.locator('#profile, #experience').evaluateAll((elements) => ( elements.map((element) => { const bounds = element.getBoundingClientRect(); return { id: element.id, left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom, }; }) )); for (const panel of panels) { expect( rectanglesOverlap(frame.bounds, panel, 16), JSON.stringify({ frame: frame.bounds, panel }), ).toBe(false); } }); test('scales a medium portrait navigator sign without changing the phone composition', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact portrait breakpoint comparison is sufficient.'); const widths: number[] = []; for (const viewport of [ { width: 390, height: 844, minimumWidth: 85, maximumWidth: 110 }, { width: 692, height: 793, minimumWidth: 145, maximumWidth: 210 }, ] as const) { await page.setViewportSize(viewport); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await clickNavigationLink(page, '#profile'); await expect(page.locator('#profile-heading')).toBeFocused(); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell')).toBe(true); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'profile' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error(`Missing navigator Profile dwell at ${viewport.width}x${viewport.height}`); expect(frame.navigationArrival).toBe(true); expect(frame.viewportProfile).toBe('tall'); expect(frame.bounds.width).toBeGreaterThanOrEqual(viewport.minimumWidth); expect(frame.bounds.width).toBeLessThanOrEqual(viewport.maximumWidth); expect(frame.bounds.left).toBeGreaterThanOrEqual(8); expect(frame.bounds.right).toBeLessThanOrEqual(viewport.width - 8); const occupied = await page.locator('#profile, #experience').evaluateAll((elements) => ( elements.map((element) => { const bounds = element.getBoundingClientRect(); return { id: element.id, left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom }; }).filter((bounds) => bounds.bottom > 0 && bounds.top < window.innerHeight) )); for (const panel of occupied) { expect(rectanglesOverlap(frame.bounds, panel, 16), JSON.stringify({ viewport, frame: frame.bounds, panel })) .toBe(false); } widths.push(frame.bounds.width); } expect(widths[1]).toBeGreaterThanOrEqual((widths[0] ?? 0) * 1.45); }); test('moves an already-readable portrait sign into navigator-safe placement without replaying it', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact portrait composition is sufficient.'); await page.setViewportSize({ width: 390, height: 844 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const destination = await page.evaluate(() => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing Profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, profile.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + 0.22), ); }); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.findLast((frame) => frame.targetId === 'profile')?.phase).toBe('dwell'); await clickNavigationLink(page, '#profile'); await expect(page.locator('#profile-heading')).toBeFocused(); const panel = await page.locator('#profile').evaluate((element) => { const bounds = element.getBoundingClientRect(); return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom }; }); await expect.poll(async () => { const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'profile' && candidate.phase === 'dwell' && candidate.navigationArrival === true )); return frame === undefined ? true : rectanglesOverlap(frame.bounds, panel, 16); }, { intervals: [16], timeout: 5_000 }).toBe(false); const instrumentation = await readSceneInstrumentation(page); expect(countSignPresentations(instrumentation.signFrames, 'profile')).toBe(1); expect(instrumentation.routeLabelsDrawn).toEqual(['Profile']); }); test('re-arms passive signage after a navigator round trip to Start', async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await clickNavigationLink(page, '#profile'); await expect(page.locator('#profile-heading')).toBeFocused(); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell')).toBe(true); await page.waitForTimeout(2_500); await clickNavigationLink(page, '#start'); await expect(page.locator('#start-heading')).toBeFocused(); const viewport = page.viewportSize(); if (viewport === null) throw new Error('Missing browser viewport'); const destination = await page.evaluate((candidateDistanceVh) => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, profile.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + candidateDistanceVh), ); }, triggerProbeDistanceVh(viewport.width, viewport.height)); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => countSignPresentations( (await readSceneInstrumentation(page)).signFrames, 'profile', )).toBe(2); }); test('reconciles an idle rapid jump to the latest active route without flashing intermediates', async ({ page, distUrl, }) => { await page.setViewportSize({ width: 1_440, height: 900 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await page.mouse.wheel(0, 1); await page.evaluate(() => { const target = document.getElementById('selected-builds-heading'); if (!(target instanceof HTMLElement)) throw new Error('Missing selected builds heading'); const top = target.getBoundingClientRect().top + window.scrollY; const viewportHeight = window.visualViewport?.height ?? window.innerHeight; window.scrollTo(0, Math.max(1, top - viewportHeight * 0.35 + 1)); }); await expect.poll(async () => (await readSceneInstrumentation(page)).routeLabelsDrawn) .toContain('Selected builds'); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.findLast((frame) => ( frame.targetId === 'selected-builds' ))?.phase, { intervals: [16], timeout: 5_000 }).toBe('dwell'); const instrumentation = await readSceneInstrumentation(page); expect([...new Set(instrumentation.routeLabelsDrawn)]) .toEqual(['Selected builds']); const dwell = instrumentation.signFrames.findLast((frame) => ( frame.targetId === 'selected-builds' && frame.phase === 'dwell' )); if (dwell === undefined) throw new Error('Missing reconciled Selected builds dwell frame'); expect(dwell.bounds.left).toBeGreaterThanOrEqual(8); expect(dwell.bounds.right).toBeLessThanOrEqual(1_432); }); test('keeps the readable Profile face outside its destination panel with verified texel density', async ({ page, distUrl, }) => { await page.setViewportSize({ width: 1440, height: 900 }); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const destination = await page.evaluate(() => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, profile.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + 0.45), ); }); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.some((frame) => frame.targetId === 'profile' && frame.phase === 'dwell')).toBe(true); const instrumentation = await readSceneInstrumentation(page); const frame = instrumentation.signFrames.findLast((candidate) => ( candidate.targetId === 'profile' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error('Missing readable Profile sign frame'); const panel = await page.locator('#profile').evaluate((element) => { const bounds = element.getBoundingClientRect(); return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom }; }); expect(frame.bounds.right).toBeLessThanOrEqual(panel.left - 16); expect(frame.texture).toMatchObject({ width: 2_048, height: 512, fontReadyWhenDrawn: true, }); expect(frame.texture.width / (frame.bounds.width * frame.rendererPixelRatio)) .toBeGreaterThanOrEqual(4); }); test('keeps a fresh readable sign inside responsive world-safe corridors', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact responsive matrix is sufficient.'); for (const viewport of [ { width: 390, height: 844, profile: 'tall', minimumFaceWidth: 120 }, { width: 780, height: 493, profile: 'short', minimumFaceWidth: 120 }, { width: 844, height: 390, profile: 'short', minimumFaceWidth: 130 }, { width: 768, height: 1_024, profile: 'tall', minimumFaceWidth: 150 }, { width: 1_440, height: 900, profile: 'wide', minimumFaceWidth: 180 }, { width: 2_560, height: 1_440, profile: 'wide', minimumFaceWidth: 240 }, ] as const) { await page.setViewportSize(viewport); await page.goto(distUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); const destination = await page.evaluate((candidateDistanceVh) => { const profile = document.getElementById('profile-heading'); if (!(profile instanceof HTMLElement)) throw new Error('Missing profile heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, profile.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + candidateDistanceVh), ); }, triggerProbeDistanceVh(viewport.width, viewport.height)); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.at(-1)?.phase, { intervals: [16], timeout: 5_000 }).toBe('dwell'); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'profile' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error(`Missing Profile dwell at ${viewport.width}x${viewport.height}`); const geometry = await page.evaluate(() => { const boundsFor = (selector: string) => { const element = document.querySelector(selector); if (!(element instanceof HTMLElement)) return null; const bounds = element.getBoundingClientRect(); return { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom }; }; return { panel: boundsFor('#profile'), adjacentPanels: [boundsFor('.hero-journey__content')].filter((bounds) => bounds !== null), fixedZones: [ boundsFor('.site-navigation'), boundsFor('.drive-controls'), boundsFor('.journey-hud'), ].filter((bounds) => bounds !== null), }; }); if (geometry.panel === null) throw new Error('Missing Profile panel bounds'); expect(frame.viewportProfile).toBe(viewport.profile); expect(frame.bounds.left).toBeGreaterThanOrEqual(8); expect(frame.bounds.right).toBeLessThanOrEqual(viewport.width - 8); expect(frame.bounds.top).toBeGreaterThanOrEqual(8); expect(frame.bounds.bottom).toBeLessThanOrEqual(viewport.height - 8); expect(frame.bounds.width).toBeGreaterThanOrEqual(viewport.minimumFaceWidth); expect(rectanglesOverlap(frame.bounds, geometry.panel, 16)).toBe(false); for (const adjacentPanel of geometry.adjacentPanels) { expect(rectanglesOverlap(frame.bounds, adjacentPanel, 16)).toBe(false); } for (const fixedZone of geometry.fixedZones) { expect(rectanglesOverlap(frame.bounds, fixedZone, 8)).toBe(false); } expect(frame.texture.fontReadyWhenDrawn).toBe(true); expect(frame.texture.width / (frame.bounds.width * frame.rendererPixelRatio)) .toBeGreaterThanOrEqual(4); } }); test('keeps the upcoming Experience sign clear of both outgoing and incoming panels', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact desktop composition is sufficient.'); await page.setViewportSize({ width: 1_440, height: 900 }); await page.goto(`${distUrl}/#profile`, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await expect(page.locator('nav a[href="#profile"]')).toHaveAttribute('aria-current', 'page'); const destination = await page.evaluate(() => { const heading = document.getElementById('experience-heading'); if (!(heading instanceof HTMLElement)) throw new Error('Missing Experience heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, heading.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + 0.45), ); }); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.at(-1)?.phase, { intervals: [16], timeout: 5_000 }).toBe('dwell'); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'experience' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error('Missing readable Experience sign frame'); expect(frame.corridor).toBe('right'); expect(frame.bounds.left).toBeGreaterThan(720); const panels = await page.locator('#profile, #experience').evaluateAll((elements) => ( elements.map((element) => { const bounds = element.getBoundingClientRect(); return { id: element.id, left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom, }; }).filter((bounds) => bounds.bottom > 0 && bounds.top < window.innerHeight) )); expect(panels.map(({ id }) => id)).toEqual(['profile', 'experience']); for (const panel of panels) { expect(rectanglesOverlap(frame.bounds, panel, 16), panel.id).toBe(false); } }); test('keeps the short-landscape Experience sign in the right roadside gap', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact short-landscape composition is sufficient.'); await page.setViewportSize({ width: 844, height: 390 }); await page.goto(`${distUrl}/#profile`, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await expect(page.locator('nav a[href="#profile"]')).toHaveAttribute('aria-current', 'page'); const destination = await page.evaluate(() => { const heading = document.getElementById('experience-heading'); if (!(heading instanceof HTMLElement)) throw new Error('Missing Experience heading'); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, heading.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + 0.38), ); }); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.findLast((frame) => frame.targetId === 'experience')?.phase, { intervals: [16], timeout: 5_000, }).toBe('dwell'); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === 'experience' && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error('Missing readable short-landscape Experience sign frame'); expect(frame.corridor).toBe('right'); expect(frame.viewportProfile).toBe('short'); expect(frame.bounds.left).toBeGreaterThan(422); expect(frame.bounds.right).toBeLessThanOrEqual(836); expect(frame.bounds.top).toBeGreaterThanOrEqual(210); expect(frame.bounds.bottom).toBeLessThanOrEqual(300); expect(frame.bounds.width).toBeGreaterThanOrEqual(120); const occupied = await page.locator('#profile, #experience, .site-navigation, .drive-controls, .journey-hud') .evaluateAll((elements) => elements.map((element) => { const bounds = element.getBoundingClientRect(); return { id: element.id || element.className, fixed: element.matches('.site-navigation, .drive-controls, .journey-hud'), visible: bounds.bottom > 0 && bounds.top < window.innerHeight, bounds: { left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom, }, }; }).filter(({ visible }) => visible)); for (const surface of occupied) { expect( rectanglesOverlap(frame.bounds, surface.bounds, surface.fixed ? 8 : 16), String(surface.id), ).toBe(false); } expect(frame.texture.fontReadyWhenDrawn).toBe(true); }); test('keeps every passive transition sign clear of visible opaque panels and fixed controls', async ({ page, distUrl, }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'One exact responsive transition matrix is sufficient.'); test.setTimeout(180_000); for (const viewport of [ { width: 1_440, height: 900 }, { width: 390, height: 844 }, { width: 844, height: 390 }, ] as const) { await page.setViewportSize(viewport); for (let index = 1; index < sections.length; index += 1) { const previous = sections[index - 1]; const next = sections[index]; if (previous === undefined || next === undefined) throw new Error('Incomplete route transition'); const startUrl = previous.id === 'start' ? distUrl : `${distUrl}/#${previous.id}`; await page.goto(startUrl, { waitUntil: 'domcontentloaded' }); await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-state', 'running'); await expect(page.locator(`nav a[href="#${previous.id}"]`)).toHaveAttribute('aria-current', 'page'); const transition = `${viewport.width}x${viewport.height} ${previous.id} -> ${next.id}`; const destination = await page.evaluate(({ targetId, candidateDistanceVh }) => { const heading = document.getElementById(`${targetId}-heading`); if (!(heading instanceof HTMLElement)) throw new Error(`Missing ${targetId} heading`); const viewportHeight = window.visualViewport?.height ?? window.innerHeight; return Math.max( 1, heading.getBoundingClientRect().top + window.scrollY - viewportHeight * (0.35 + candidateDistanceVh), ); }, { targetId: next.id, candidateDistanceVh: viewport.width < viewport.height ? 0.225 : viewport.height < 560 ? 0.38 : 0.45, }); await page.mouse.wheel(0, 1); await page.evaluate((scrollY) => window.scrollTo(0, scrollY), destination); await expect.poll(async () => ( await readSceneInstrumentation(page) ).signFrames.findLast((frame) => frame.targetId === next.id)?.phase, { intervals: [16], message: `${transition} should reach readable dwell`, timeout: 5_000, }).toBe('dwell'); const frame = (await readSceneInstrumentation(page)).signFrames.findLast((candidate) => ( candidate.targetId === next.id && candidate.phase === 'dwell' )); if (frame === undefined) throw new Error(`Missing readable ${next.id} sign frame`); const occupied = await page.evaluate(() => { const alpha = (color: string): number => { const match = color.match(/rgba?\([^)]*(?:,|\s\/\s)([\d.]+)\)$/u); return match?.[1] === undefined ? 1 : Number.parseFloat(match[1]); }; const boundsFor = (element: Element) => { const bounds = element.getBoundingClientRect(); return { id: element.id || element.closest('[id]')?.id || element.className, left: bounds.left, top: bounds.top, right: bounds.right, bottom: bounds.bottom, }; }; const panels = [...document.querySelectorAll( [ '.hero-journey__content', '.portfolio-section:not([data-layout="full-diagram"]):not([data-layout="project-stage"])', '.chapter-body', '.portfolio-section[data-layout="full-diagram"] > h2', '.infrastructure-case > h3', '.infrastructure-case > p', '.infrastructure-flow-figure', '.portfolio-section[data-layout="project-stage"] > h2', '.portfolio-section[data-layout="project-stage"] > .section-eyebrow', '.project-case-study', '.project-card', ].join(', '), )].filter((element) => { const bounds = element.getBoundingClientRect(); const style = getComputedStyle(element); const painted = style.backgroundImage !== 'none' || alpha(style.backgroundColor) > 0.08; return painted && bounds.bottom > 0 && bounds.top < window.innerHeight; }).map(boundsFor); const fixed = [...document.querySelectorAll( '.site-navigation, .drive-controls, .journey-hud', )].map(boundsFor); return { panels, fixed }; }); const panelOverlaps = occupied.panels .filter((panel) => rectanglesOverlap(frame.bounds, panel, 16)) .map((panel) => ({ panel, sign: frame.bounds })); const fixedOverlaps = occupied.fixed .filter((zone) => rectanglesOverlap(frame.bounds, zone, 8)); expect(frame.bounds.left, `${next.id} left edge`).toBeGreaterThanOrEqual(8); expect(frame.bounds.right, `${next.id} right edge`).toBeLessThanOrEqual(viewport.width - 8); expect(frame.bounds.top, `${next.id} top edge`).toBeGreaterThanOrEqual(8); expect(frame.bounds.bottom, `${next.id} bottom edge`).toBeLessThanOrEqual(viewport.height - 8); expect(panelOverlaps, transition).toEqual([]); expect(fixedOverlaps, transition).toEqual([]); } } }); function colorEnergy(color: string): number { const value = Number.parseInt(color.slice(1), 16); return ((value >> 16) & 0xff) + ((value >> 8) & 0xff) + (value & 0xff); } function rectanglesOverlap( first: { left: number; top: number; right: number; bottom: number }, second: { left: number; top: number; right: number; bottom: number }, gap: number, ): boolean { return !( first.right <= second.left - gap || first.left >= second.right + gap || first.bottom <= second.top - gap || first.top >= second.bottom + gap ); } function countSignPresentations( frames: ReadonlyArray<{ targetId: string; phase: string }>, targetId: string, ): number { let presentations = 0; let previousPhase: string | null = null; for (const frame of frames) { if (frame.targetId !== targetId) continue; if (frame.phase === 'anticipate' && previousPhase !== 'anticipate') presentations += 1; previousPhase = frame.phase; } return presentations; } function triggerProbeDistanceVh(width: number, height: number): number { const aspect = width / height; if (height < 560 && aspect >= 1) return 0.37; if (aspect < 1.05) return 0.22; return 0.45; }