fix: synchronize responsive navigation and soundtrack cue

This commit is contained in:
2026-09-02 18:33:52 +03:00
parent b4728ceef0
commit 2ea3cab142
12 changed files with 661 additions and 232 deletions

View File

@@ -1,26 +1,41 @@
import { expect, test } from '../helpers/serve-dist';
import { expect, test } from "../helpers/serve-dist";
test('keeps the decorative world separate from the semantic reading path', async ({ page, distUrl }) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
test("keeps the decorative world separate from the semantic reading path", async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
await expect(page.locator('#journey-scene-description')).toContainText('night');
await expect(page.locator('.journey-world')).toHaveAttribute('aria-hidden', 'true');
await expect(page.locator('.journey-poster')).toHaveAttribute('alt', '');
await expect(page.locator('.journey-poster')).toHaveAttribute('width', '1600');
await expect(page.locator('main#portfolio-content')).toBeVisible();
await expect(page.locator("#journey-scene-description")).toContainText(
"night",
);
await expect(page.locator(".journey-world")).toHaveAttribute(
"aria-hidden",
"true",
);
await expect(page.locator(".journey-poster")).toHaveAttribute("alt", "");
await expect(page.locator(".journey-poster")).toHaveAttribute(
"width",
"1600",
);
await expect(page.locator("main#portfolio-content")).toBeVisible();
});
test('keeps the hero and navigation in paint layers above the world', async ({ page, distUrl }) => {
test("keeps the hero and navigation in paint layers above the world", async ({
page,
distUrl,
}) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
const layers = await page.evaluate(() => {
const world = document.querySelector<HTMLElement>('.journey-world');
const shell = document.querySelector<HTMLElement>('.portfolio-shell');
const hero = document.querySelector<HTMLElement>('.hero-journey__content');
const heading = document.querySelector<HTMLElement>('.hero-journey h1');
const navigation = document.querySelector<HTMLElement>('.site-navigation');
if (!world || !shell || !hero || !heading || !navigation) throw new Error('Missing top-screen structure');
const world = document.querySelector<HTMLElement>(".journey-world");
const shell = document.querySelector<HTMLElement>(".portfolio-shell");
const hero = document.querySelector<HTMLElement>(".hero-journey__content");
const heading = document.querySelector<HTMLElement>(".hero-journey h1");
const navigation = document.querySelector<HTMLElement>(".site-navigation");
if (!world || !shell || !hero || !heading || !navigation)
throw new Error("Missing top-screen structure");
const worldStyle = getComputedStyle(world);
const heroStyle = getComputedStyle(hero);
const navigationStyle = getComputedStyle(navigation);
@@ -29,14 +44,20 @@ test('keeps the hero and navigation in paint layers above the world', async ({ p
shellZ: Number(getComputedStyle(shell).zIndex),
navigationZ: Number(navigationStyle.zIndex),
navigationPosition: navigationStyle.position,
headingVisible: heading.getBoundingClientRect().width > 0 && heading.getBoundingClientRect().height > 0,
navigationVisible: navigation.getBoundingClientRect().width > 0 && navigation.getBoundingClientRect().height > 0,
heroOpacity: Number.parseFloat(heroStyle.backgroundColor.match(/[\d.]+(?=\))/gu)?.at(-1) ?? '1'),
headingVisible:
heading.getBoundingClientRect().width > 0 &&
heading.getBoundingClientRect().height > 0,
navigationVisible:
navigation.getBoundingClientRect().width > 0 &&
navigation.getBoundingClientRect().height > 0,
heroOpacity: Number.parseFloat(
heroStyle.backgroundColor.match(/[\d.]+(?=\))/gu)?.at(-1) ?? "1",
),
};
});
expect(layers).toMatchObject({
navigationPosition: 'fixed',
navigationPosition: "fixed",
headingVisible: true,
navigationVisible: true,
});
@@ -45,21 +66,52 @@ test('keeps the hero and navigation in paint layers above the world', async ({ p
expect(layers.heroOpacity).toBeGreaterThanOrEqual(0.94);
});
test('keeps a scrolled content panel in a geometry-safe paint layer above the world', async ({ page, distUrl }) => {
test("resets the navigation disclosure when crossing the compact breakpoint", async ({
page,
distUrl,
}) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
const disclosure = page.locator(".site-navigation details");
const summary = page.locator(".site-navigation summary");
const startLink = page.locator('.site-navigation a[href="#start"]');
await expect(disclosure).toHaveAttribute("open", "");
await expect(startLink).toBeVisible();
await page.setViewportSize({ width: 630, height: 900 });
await expect(summary).toBeVisible();
await expect(disclosure).not.toHaveAttribute("open", "");
await expect(startLink).toBeHidden();
await summary.click();
await expect(disclosure).toHaveAttribute("open", "");
await page.setViewportSize({ width: 1440, height: 900 });
await expect(summary).toBeHidden();
await expect(disclosure).toHaveAttribute("open", "");
await expect(startLink).toBeVisible();
});
test("keeps a scrolled content panel in a geometry-safe paint layer above the world", async ({
page,
distUrl,
}) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
const profileLink = page.locator('nav a[href="#profile"]');
if (!(await profileLink.isVisible())) await page.locator('.site-navigation summary').click();
if (!(await profileLink.isVisible()))
await page.locator(".site-navigation summary").click();
await profileLink.click();
await expect(page).toHaveURL(`${distUrl}/#profile`);
await expect(page.locator('#profile-heading')).toBeInViewport();
await expect(page.locator("#profile-heading")).toBeInViewport();
const layers = await page.evaluate(() => {
const world = document.querySelector<HTMLElement>('.journey-world');
const main = document.querySelector<HTMLElement>('.portfolio-main');
const panel = document.querySelector<HTMLElement>('#profile');
const heading = document.querySelector<HTMLElement>('#profile-heading');
if (!world || !main || !panel || !heading) throw new Error('Missing scrolled panel structure');
const world = document.querySelector<HTMLElement>(".journey-world");
const main = document.querySelector<HTMLElement>(".portfolio-main");
const panel = document.querySelector<HTMLElement>("#profile");
const heading = document.querySelector<HTMLElement>("#profile-heading");
if (!world || !main || !panel || !heading)
throw new Error("Missing scrolled panel structure");
const mainStyle = getComputedStyle(main);
const panelStyle = getComputedStyle(panel);
const headingRect = heading.getBoundingClientRect();
@@ -68,73 +120,107 @@ test('keeps a scrolled content panel in a geometry-safe paint layer above the wo
mainIsolation: mainStyle.isolation,
mainPosition: mainStyle.position,
headingVisible:
headingRect.width > 0
&& headingRect.height > 0
&& headingRect.bottom > 0
&& headingRect.top < window.innerHeight,
panelOpacity: Number.parseFloat(panelStyle.backgroundColor.match(/[\d.]+(?=\))/gu)?.at(-1) ?? '1'),
headingRect.width > 0 &&
headingRect.height > 0 &&
headingRect.bottom > 0 &&
headingRect.top < window.innerHeight,
panelOpacity: Number.parseFloat(
panelStyle.backgroundColor.match(/[\d.]+(?=\))/gu)?.at(-1) ?? "1",
),
};
});
expect(layers).toMatchObject({
worldZ: 0,
mainIsolation: 'isolate',
mainPosition: 'static',
mainIsolation: "isolate",
mainPosition: "static",
headingVisible: true,
});
expect(layers.panelOpacity).toBeGreaterThanOrEqual(0.94);
});
test('provides native motion and quality controls with visible state', async ({ page, distUrl }) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
test("provides native motion and quality controls with visible state", async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
await page.getByRole('button', { name: 'Drive settings' }).click();
const quality = page.getByLabel('Quality control');
await page.getByRole("button", { name: "Drive settings" }).click();
const quality = page.getByLabel("Quality control");
await expect(quality).toBeVisible();
await expect(page.getByRole('status')).toHaveCount(0);
await page.keyboard.press('Escape');
await page.getByRole('button', { name: 'Start the drive' }).click();
await expect(page.getByRole('button', { name: 'Pause motion' })).toBeVisible();
const canvas = page.locator('.journey-canvas');
await expect(page.getByRole("status")).toHaveCount(0);
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "Play soundtrack" }).click();
await expect(
page.getByRole("button", { name: "Pause motion" }),
).toBeVisible();
const canvas = page.locator(".journey-canvas");
const originalCanvas = await canvas.elementHandle();
const originalGeneration = await page.locator('.journey-world').getAttribute('data-scene-generation');
const originalGeneration = await page
.locator(".journey-world")
.getAttribute("data-scene-generation");
await page.getByRole('button', { name: 'Pause motion' }).click();
await expect(page.getByRole('button', { name: 'Resume motion' })).toBeVisible();
await page.getByRole("button", { name: "Pause motion" }).click();
await expect(
page.getByRole("button", { name: "Resume motion" }),
).toBeVisible();
await expect(canvas).toHaveCount(1);
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-generation', originalGeneration ?? '');
expect(await originalCanvas?.evaluate((element) => element.isConnected)).toBe(true);
await expect(page.locator(".journey-world")).toHaveAttribute(
"data-scene-generation",
originalGeneration ?? "",
);
expect(await originalCanvas?.evaluate((element) => element.isConnected)).toBe(
true,
);
await page.getByRole('button', { name: 'Resume motion' }).click();
await expect(page.getByRole('button', { name: 'Pause motion' })).toBeVisible();
await page.getByRole("button", { name: "Resume motion" }).click();
await expect(
page.getByRole("button", { name: "Pause motion" }),
).toBeVisible();
await expect(canvas).toHaveCount(1);
await expect(page.locator('.journey-world')).toHaveAttribute('data-scene-generation', originalGeneration ?? '');
expect(await originalCanvas?.evaluate((element) => element.isConnected)).toBe(true);
await expect(page.locator(".journey-world")).toHaveAttribute(
"data-scene-generation",
originalGeneration ?? "",
);
expect(await originalCanvas?.evaluate((element) => element.isConnected)).toBe(
true,
);
await page.getByRole('button', { name: 'Drive settings' }).click();
await page.getByLabel('Quality control').selectOption('static');
await expect(page.getByRole('button', { name: 'Enable motion' })).toBeVisible();
await expect(page.locator('.journey-canvas')).toHaveCount(0);
await expect(page.getByRole('status')).toContainText('Motion: static');
await page.getByRole("button", { name: "Drive settings" }).click();
await page.getByLabel("Quality control").selectOption("static");
await expect(
page.getByRole("button", { name: "Enable motion" }),
).toBeVisible();
await expect(page.locator(".journey-canvas")).toHaveCount(0);
await expect(page.getByRole("status")).toContainText("Motion: static");
});
test('never creates a canvas when the system requests reduced motion', async ({ page, distUrl }) => {
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
test("never creates a canvas when the system requests reduced motion", async ({
page,
distUrl,
}) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
await expect(page.locator('.journey-canvas')).toHaveCount(0);
await page.getByRole('button', { name: 'Drive settings' }).click();
await expect(page.getByRole('status')).toContainText('reduced motion requested');
await expect(page.getByLabel('Quality control')).toBeVisible();
await expect(page.locator(".journey-canvas")).toHaveCount(0);
await page.getByRole("button", { name: "Drive settings" }).click();
await expect(page.getByRole("status")).toContainText(
"reduced motion requested",
);
await expect(page.getByLabel("Quality control")).toBeVisible();
});
test('shows ARRIVED from the authoritative section snapshot at Contact', async ({ page, distUrl }) => {
await page.goto(distUrl, { waitUntil: 'domcontentloaded' });
test("shows ARRIVED from the authoritative section snapshot at Contact", async ({
page,
distUrl,
}) => {
await page.goto(distUrl, { waitUntil: "domcontentloaded" });
const contactLink = page.locator('nav a[href="#contact"]');
if (!(await contactLink.isVisible())) await page.locator('.site-navigation summary').click();
if (!(await contactLink.isVisible()))
await page.locator(".site-navigation summary").click();
await contactLink.click();
await expect(page.locator('.journey-hud')).toContainText('Contact');
await expect(page.locator('.journey-hud')).toContainText('ARRIVED');
await expect(page.locator('.journey-hud')).toContainText('100%');
await expect(page.locator(".journey-hud")).toContainText("Contact");
await expect(page.locator(".journey-hud")).toContainText("ARRIVED");
await expect(page.locator(".journey-hud")).toContainText("100%");
});