diff mrjunejune/test/theme_and_webp_test.js @ 263:ee04e4e69fed

Add functional JRPG frame and tools Add full-screen background_2 apertures, functional frame chrome, card-driven details, dual-window tools, live conversion workflows, and bounded cleanup for generated downloads. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Thu, 06 Aug 2026 11:31:30 -0700
parents 0f45474c1b1a
children 04fee26ecce0
line wrap: on
line diff
--- a/mrjunejune/test/theme_and_webp_test.js	Thu Aug 06 04:08:45 2026 -0700
+++ b/mrjunejune/test/theme_and_webp_test.js	Thu Aug 06 11:31:30 2026 -0700
@@ -183,6 +183,20 @@
     }
   });
   page.on('requestfailed', request => {
+    if (request.url().startsWith('blob:') &&
+        request.failure()?.errorText === 'net::ERR_ABORTED') {
+      return;
+    }
+    if (request.method() === 'DELETE' &&
+        request.url().includes('/api/download/') &&
+        request.failure()?.errorText === 'net::ERR_ABORTED') {
+      return;
+    }
+    if (request.method() === 'POST' &&
+        request.url().endsWith('/api/latex/render') &&
+        request.failure()?.errorText === 'net::ERR_ABORTED') {
+      return;
+    }
     errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
   });
   page.on('response', response => {
@@ -449,6 +463,20 @@
     }
   });
   page.on('requestfailed', request => {
+    if (request.url().startsWith('blob:') &&
+        request.failure()?.errorText === 'net::ERR_ABORTED') {
+      return;
+    }
+    if (request.method() === 'DELETE' &&
+        request.url().includes('/api/download/') &&
+        request.failure()?.errorText === 'net::ERR_ABORTED') {
+      return;
+    }
+    if (request.method() === 'POST' &&
+        request.url().endsWith('/api/latex/render') &&
+        request.failure()?.errorText === 'net::ERR_ABORTED') {
+      return;
+    }
     errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
   });
   page.on('response', response => {
@@ -487,10 +515,25 @@
       .getBoundingClientRect();
     const menu = document.querySelector('mjj-jrpg-menu').getBoundingClientRect();
     const character = document.querySelector('[data-character]');
+    const characterBox = character.getBoundingClientRect();
+    const sceneBarStyle = getComputedStyle(
+      document.querySelector('.jrpg-scene'),
+      '::before',
+    );
     const messageScroller = document.querySelector('zen-message-scroller')
       .getBoundingClientRect();
     const messageViewport = document.querySelector('.jrpg-message-viewport')
       .getBoundingClientRect();
+    const workspace = document.querySelector('.jrpg-workspace')
+      .getBoundingClientRect();
+    const brand = document.querySelector('.jrpg-brand').getBoundingClientRect();
+    const frameControls = document.querySelector('.jrpg-frame-controls')
+      .getBoundingClientRect();
+    const frameTelemetry = document.querySelector('.jrpg-frame-telemetry')
+      .getBoundingClientRect();
+    const telemetryValues = [
+      ...document.querySelectorAll('.jrpg-frame-telemetry > *'),
+    ].map(value => value.getBoundingClientRect());
     const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
     const main = document.querySelector('.jrpg-main').getBoundingClientRect();
     const colorProbe = document.createElement('span');
@@ -566,8 +609,53 @@
     surfaceProbe.remove();
     sunkenProbe.remove();
     panelProbe.remove();
+    const closeTo = (value, expected) => Math.abs(value - expected) < 0.01;
+    const frameAlignment = [
+      closeTo((scene.left - workspace.left) / workspace.width, 0.032),
+      closeTo((scene.top - workspace.top) / workspace.height, 0.094),
+      closeTo(scene.width / workspace.width, 0.548),
+      closeTo(scene.height / workspace.height, 0.57),
+      closeTo((utility.left - workspace.left) / workspace.width, 0.604),
+      closeTo(utility.width / workspace.width, 0.365),
+      closeTo((composer.top - workspace.top) / workspace.height, 0.72),
+      closeTo(composer.width / workspace.width, 0.682),
+      closeTo((menu.left - workspace.left) / workspace.width, 0.734),
+      closeTo(menu.width / workspace.width, 0.234),
+    ].every(Boolean);
+    const borderless = [
+      '.jrpg-shell',
+      '.jrpg-scene',
+      '.jrpg-utility',
+      'mjj-jrpg-chat',
+      'mjj-jrpg-composer',
+      'mjj-jrpg-menu',
+      '.jrpg-preview-panel',
+      '.jrpg-message',
+      '.jrpg-work-showcase a',
+      '.jrpg-composer textarea',
+      '.jrpg-composer button[type="submit"]',
+      'mjj-jrpg-menu button',
+    ].every(selector => {
+      const style = getComputedStyle(document.querySelector(selector));
+      return style.borderTopWidth === '0px' &&
+        style.borderRightWidth === '0px' &&
+        style.borderBottomWidth === '0px' &&
+        style.borderLeftWidth === '0px';
+    });
     return {
-      art: getComputedStyle(document.querySelector('.jrpg-scene')).backgroundImage,
+      art: getComputedStyle(document.querySelector('.jrpg-workspace'))
+        .backgroundImage,
+      borderless,
+      brandInSystemSlot:
+        closeTo((brand.left - workspace.left) / workspace.width, 0.054) &&
+        closeTo((brand.top - workspace.top) / workspace.height, 0.022) &&
+        document.querySelector('.jrpg-brand').parentElement.classList
+          .contains('jrpg-workspace'),
+      brandCoversSystemLabel:
+        brand.left <= workspace.left + workspace.width * 0.062 &&
+        brand.right >= workspace.left + workspace.width * 0.102 &&
+        brand.top <= workspace.top + workspace.height * 0.03 &&
+        brand.bottom >= workspace.top + workspace.height * 0.06,
       characterLoaded: character.complete && character.naturalWidth > 0,
       colors,
       composerBelowScene: composer.top >= scene.bottom,
@@ -600,6 +688,11 @@
         main.top === 0 &&
         Math.abs(main.right - innerWidth) < 1 &&
         Math.abs(main.bottom - innerHeight) < 1,
+      frameAlignment,
+      frameChromeAligned:
+        closeTo((frameControls.left - workspace.left) / workspace.width, 0.866) &&
+        closeTo((frameTelemetry.left - workspace.left) / workspace.width, 0.085) &&
+        closeTo((frameTelemetry.top - workspace.top) / workspace.height, 0.928),
       fonts: computedFonts,
       fontLoaded: [...document.fonts].some(font =>
         font.family.replaceAll('"', '') === 'Pixel Mplus' &&
@@ -614,23 +707,72 @@
       noOverflow: document.documentElement.scrollWidth <= innerWidth,
       nonPixelText,
       questLabelAbsent: !document.body.textContent.includes('QUEST 01'),
+      statsRemoved:
+        !document.querySelector('.jrpg-preview-stats') &&
+        !document.querySelector('[data-preview-type]'),
+      telemetryCovered:
+        document.querySelectorAll('.jrpg-frame-telemetry > *').length === 4 &&
+        document.querySelector('[data-frame-network]').textContent
+          .includes('ONLINE') &&
+        document.querySelector('[data-frame-uptime]').textContent
+          .match(/^\d{2}:\d{2}:\d{2}$/) &&
+        [...document.querySelectorAll('.jrpg-frame-telemetry > *')]
+          .every(item => !/TEMP:|USER:|STATUS:|UPTIME:/.test(item.textContent)),
+      telemetryValuesAligned:
+        closeTo(
+          (telemetryValues[0].left + telemetryValues[0].width / 2 -
+            frameTelemetry.left) / frameTelemetry.width,
+          0.12,
+        ) &&
+        closeTo(
+          (telemetryValues[1].left + telemetryValues[1].width / 2 -
+            frameTelemetry.left) / frameTelemetry.width,
+          0.325,
+        ) &&
+        closeTo(
+          (telemetryValues[2].left + telemetryValues[2].width / 2 -
+            frameTelemetry.left) / frameTelemetry.width,
+          0.605,
+        ) &&
+        closeTo(
+          (telemetryValues[3].left + telemetryValues[3].width / 2 -
+            frameTelemetry.left) / frameTelemetry.width,
+          0.88,
+        ),
+      shibaInFrontOfBar:
+        sceneBarStyle.backgroundImage.includes('bar-ink.webp') &&
+        Number(getComputedStyle(character.parentElement).zIndex) >
+          Number(sceneBarStyle.zIndex) &&
+        characterBox.bottom > scene.top + scene.height * 0.65,
+      terminalScrollbar: (
+        getComputedStyle(
+          document.querySelector('.jrpg-message-viewport'),
+        ).scrollbarWidth === 'thin' &&
+        getComputedStyle(
+          document.querySelector('.jrpg-message-viewport'),
+        ).scrollbarColor !== 'auto'
+      ),
       theme: document.documentElement.dataset.zenTheme,
       utilityRightOfScene: utility.left >= scene.right,
+      workspaceFillsViewport:
+        Math.abs(workspace.left) < 1 &&
+        Math.abs(workspace.top) < 1 &&
+        Math.abs(workspace.right - innerWidth) < 1 &&
+        Math.abs(workspace.bottom - innerHeight) < 1,
     };
   });
-  assert.match(desktop.art, /bar-ink\.webp/);
+  assert.match(desktop.art, /background-frame-2\.webp/);
+  assert.equal(desktop.borderless, true);
+  assert.equal(desktop.brandInSystemSlot, true);
+  assert.equal(desktop.brandCoversSystemLabel, true);
   assert.equal(desktop.characterLoaded, true);
   assert.equal(desktop.colors.page, desktop.colors.canvas);
-  assert.equal(
-    desktop.colors.conversation,
-    desktop.colors.conversationPanel,
-  );
-  assert.equal(desktop.colors.conversationBorder, desktop.colors.frame);
+  assert.equal(desktop.colors.conversation, 'rgba(0, 0, 0, 0)');
   assert.equal(desktop.colors.conversationText, desktop.colors.text);
-  assert.equal(desktop.colors.menu, desktop.colors.sunken);
+  assert.equal(desktop.colors.menu, 'rgba(0, 0, 0, 0)');
   assert.equal(desktop.colors.primaryText, desktop.colors.primary);
   assert.equal(desktop.colors.submit, desktop.colors.primary);
-  assert.equal(desktop.colors.utility, desktop.colors.surface);
+  assert.equal(desktop.colors.utility, 'rgba(0, 0, 0, 0)');
   assert.equal(desktop.composerBelowScene, true);
   assert.equal(
     desktop.composerTextareaFills,
@@ -638,6 +780,8 @@
     JSON.stringify(desktop.composerTextareaGeometry),
   );
   assert.equal(desktop.fillsViewport, true);
+  assert.equal(desktop.frameAlignment, true);
+  assert.equal(desktop.frameChromeAligned, true);
   assert.ok(desktop.fonts.every(font => font === '"Pixel Mplus"'));
   assert.equal(desktop.fontLoaded, true);
   assert.equal(desktop.menuRightOfComposer, true);
@@ -645,8 +789,14 @@
   assert.equal(desktop.noOverflow, true);
   assert.deepEqual(desktop.nonPixelText, []);
   assert.equal(desktop.questLabelAbsent, true);
+  assert.equal(desktop.statsRemoved, true);
+  assert.equal(desktop.telemetryCovered, true);
+  assert.equal(desktop.telemetryValuesAligned, true);
+  assert.equal(desktop.shibaInFrontOfBar, true);
+  assert.equal(desktop.terminalScrollbar, true);
   assert.equal(desktop.theme, 'cyberpunk');
   assert.equal(desktop.utilityRightOfScene, true);
+  assert.equal(desktop.workspaceFillsViewport, true);
   await page.locator('.jrpg-brand a').focus();
   assert.equal(
     await page.locator('.jrpg-brand a').evaluate(element =>
@@ -665,7 +815,33 @@
     await page.locator('meta[name="theme-color"]').first().getAttribute('content'),
     desktop.colors.canvas,
   );
-  assert.equal(await page.locator('[data-work-showcase] a').count(), 6);
+  assert.equal(
+    await page.locator('.jrpg-frame-controls :is(button, a)').count(),
+    3,
+  );
+  assert.equal(
+    await page.locator('.jrpg-frame-controls a').getAttribute('href'),
+    '/',
+  );
+  await page.getByRole('button', { name: 'Minimize interface' }).click();
+  assert.equal(
+    await page.locator('.jrpg-scene').evaluate(
+      scene => getComputedStyle(scene).visibility,
+    ),
+    'hidden',
+  );
+  await page.getByRole('button', { name: 'Restore interface' }).click();
+  assert.equal(
+    await page.locator('.jrpg-scene').evaluate(
+      scene => getComputedStyle(scene).visibility,
+    ),
+    'visible',
+  );
+  assert.equal(await page.locator('[data-work-showcase] a').count(), 7);
+  assert.equal(
+    await page.getByRole('button', { name: /Inspect/ }).count(),
+    0,
+  );
   assert.match(
     await page.locator('[data-work-showcase]').textContent(),
     /Copilot Tasks/,
@@ -701,13 +877,208 @@
       .getAttribute('aria-pressed'),
     'true',
   );
-  assert.equal(await page.locator('[data-work-showcase] a').count(), 3);
-
-  await page.getByRole('button', { name: /Inspect/ }).click();
-  await page.locator('.jrpg-preview-dialog dialog[open]').waitFor();
+  await page.waitForFunction(() =>
+    document.querySelectorAll('[data-latest-tool]').length === 5
+  );
+  assert.match(
+    await page.locator('[data-work-showcase]').textContent(),
+    /Online LaTeX Editor/,
+  );
+  assert.match(
+    await page.locator('[data-work-showcase]').textContent(),
+    /Personal Notes/,
+  );
+  await page.locator('[data-latest-tool]').first().click();
+  await page.locator('[data-functional-tool="markdown"]').waitFor();
   assert.equal(
     await page.locator('[data-dialog-title]').textContent(),
-    'Tools',
+    'MarkDown to HTML',
+  );
+  assert.equal(
+    await page.locator('[data-preview-link]').getAttribute('href'),
+    '/tools/markdown_to_html',
+  );
+  await page.locator('[data-markdown-source]').fill('# Modal Markdown');
+  await page.waitForFunction(() =>
+    document.querySelector('[data-markdown-output] h1')?.textContent ===
+      'Modal Markdown'
+  );
+  assert.equal(await page.locator('.jrpg-tool-pane').count(), 2);
+  const toolWorkspace = await page.evaluate(() => {
+    const dialogElement = document.querySelector(
+      '.jrpg-detail-dialog[data-detail-mode="tools"]',
+    );
+    const dialog = dialogElement.getBoundingClientRect();
+    const windows = [...document.querySelectorAll('.jrpg-tool-window')]
+      .map(window => window.getBoundingClientRect());
+    const windowShadows = [...document.querySelectorAll('.jrpg-tool-window')]
+      .map(window => getComputedStyle(window).boxShadow);
+    const rail = getComputedStyle(
+      document.querySelector('.jrpg-tool-panes'),
+      '::before',
+    );
+    return {
+      dialogHeight: dialogElement.offsetHeight,
+      dialogWidth: dialogElement.offsetWidth,
+      fillsViewport: dialogElement.offsetWidth > innerWidth * 0.9 &&
+        dialogElement.offsetHeight > innerHeight * 0.9,
+      viewportHeight: innerHeight,
+      viewportWidth: innerWidth,
+      sideBySide: windows.length === 2 &&
+        windows[0].right < windows[1].left &&
+        Math.abs(windows[0].top - windows[1].top) < 1,
+      fullPageInHeader: Boolean(
+        document.querySelector('[data-preview-link]')
+          .closest('.jrpg-dialog-heading-actions'),
+      ),
+      footerHidden: document.querySelector('[data-dialog-actions]').hidden,
+      metalRail: rail.content !== 'none' &&
+        rail.backgroundImage.includes('bar-ink.webp'),
+      pixelIcons: document.querySelectorAll('.jrpg-tool-window zen-icon').length >= 2 &&
+        [...document.querySelectorAll('.jrpg-tool-window zen-icon svg')]
+          .every(icon =>
+            getComputedStyle(icon).shapeRendering.toLowerCase() === 'crispedges'
+          ),
+      separateGlows: windowShadows.length === 2 &&
+        windowShadows.every(shadow => shadow !== 'none'),
+      wideWindows: windows.every(window => window.width > dialog.width * 0.35),
+    };
+  });
+  assert.equal(
+    toolWorkspace.fillsViewport,
+    true,
+    JSON.stringify(toolWorkspace),
+  );
+  assert.equal(toolWorkspace.sideBySide, true);
+  assert.equal(toolWorkspace.fullPageInHeader, true);
+  assert.equal(toolWorkspace.footerHidden, true);
+  assert.equal(toolWorkspace.metalRail, true);
+  assert.equal(toolWorkspace.pixelIcons, true);
+  assert.equal(toolWorkspace.separateGlows, true);
+  assert.equal(toolWorkspace.wideWindows, true);
+  await page.getByRole('button', {
+    name: 'Close destination details',
+  }).click();
+  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
+
+  await page.locator('[data-latest-tool]').first().click();
+  await page.locator('[data-functional-tool="markdown"]').waitFor();
+  await page.getByRole('button', {
+    name: 'Close destination details',
+  }).click();
+  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
+
+  await page.locator('[data-latest-tool]').nth(1).click();
+  await page.locator('[data-functional-tool="converter"]').waitFor();
+  assert.equal(
+    await page.locator('[data-converter-input]').count(),
+    2,
+  );
+  const converterFixture = path.join(
+    RUNFILES,
+    WORKSPACE,
+    'mrjunejune/test/shiba.webp',
+  );
+  assert.equal(fs.existsSync(converterFixture), true);
+  await page.locator('[data-converter-input="image"]').setInputFiles(
+    converterFixture,
+  );
+  const conversionResponse = page.waitForResponse(response =>
+    response.url().endsWith('/api/convert/image-to-webp') &&
+    response.request().method() === 'POST'
+  );
+  await page.locator('[data-convert-kind="image"]').click();
+  assert.equal((await conversionResponse).status(), 200);
+  await page.locator('[data-converter-download="image"]').waitFor();
+  const cleanupResponse = page.waitForResponse(response =>
+    response.url().includes('/api/download/') &&
+    response.request().method() === 'DELETE'
+  );
+  await page.getByRole('button', {
+    name: 'Close destination details',
+  }).click();
+  assert.equal((await cleanupResponse).status(), 204);
+  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
+
+  await page.locator('[data-latest-tool]').nth(1).click();
+  await page.locator('[data-functional-tool="converter"]').waitFor();
+  await page.locator('[data-converter-input="image"]').setInputFiles(
+    converterFixture,
+  );
+  const lateConversionResponse = page.waitForResponse(response =>
+    response.url().endsWith('/api/convert/image-to-webp') &&
+    response.request().method() === 'POST'
+  );
+  const lateCleanupResponse = page.waitForResponse(response =>
+    response.url().includes('/api/download/') &&
+    response.request().method() === 'DELETE'
+  );
+  await page.locator('[data-convert-kind="image"]').click();
+  await page.getByRole('button', {
+    name: 'Close destination details',
+  }).click();
+  assert.equal((await lateConversionResponse).status(), 200);
+  assert.equal((await lateCleanupResponse).status(), 204);
+  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
+
+  await page.locator('[data-latest-tool]').nth(2).click();
+  await page.locator('[data-functional-tool="hls"]').waitFor();
+  assert.equal(
+    await page.locator('[data-preview-link]').getAttribute('href'),
+    '/tools/hls_player',
+  );
+  await page.locator('[data-hls-sample]').click();
+  await page.waitForFunction(() =>
+    ['ready', 'error'].includes(
+      document.querySelector('[data-hls-status]')?.dataset.state,
+    )
+  );
+  assert.ok(
+    ['ready', 'error'].includes(
+      await page.locator('[data-hls-status]').getAttribute('data-state'),
+    ),
+  );
+  assert.match(
+    await page.locator('[data-hls-status]').textContent(),
+    /ready|unsupported|failed/i,
+  );
+  await page.getByRole('button', {
+    name: 'Close destination details',
+  }).click();
+  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
+
+  await page.locator('[data-latest-tool]').nth(3).click();
+  await page.locator('[data-functional-tool="latex"]').waitFor();
+  await page.locator('[data-latex-compile]').click();
+  await page.locator('[data-latex-source]').fill(
+    '\\documentclass{article}\\begin{document}Updated modal PDF\\end{document}',
+  );
+  assert.equal(await page.locator('[data-latex-download]').isHidden(), true);
+  await page.locator('[data-latex-compile]').click();
+  await page.waitForFunction(() =>
+    document.querySelector('[data-latex-status]')?.dataset.state === 'ready'
+  );
+  assert.match(
+    await page.locator('[data-latex-preview]').getAttribute('src'),
+    /^blob:/,
+  );
+  await page.getByRole('button', {
+    name: 'Close destination details',
+  }).click();
+  await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
+
+  await page.locator('[data-latest-tool]').nth(4).click();
+  await page.waitForFunction(() =>
+    document.querySelector('[data-tool-content] h1')?.textContent ===
+      'Personal Notes'
+  );
+  assert.match(
+    await page.locator('[data-tool-content]').textContent(),
+    /private browser-based writing workspace/,
+  );
+  assert.equal(
+    await page.locator('[data-preview-link]').getAttribute('href'),
+    '/notes',
   );
   await page.getByRole('button', {
     name: 'Close destination details',
@@ -715,7 +1086,7 @@
   await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
 
   await page.getByRole('button', { name: 'Resume', exact: true }).click();
-  await page.getByRole('button', { name: /Inspect/ }).click();
+  await page.locator('[data-resume-modal]').click();
   await page.locator('[data-resume-content] .sub-header').first().waitFor();
   const resumeDossier = await page.locator('[data-resume-dossier]').evaluate(
     dossier => ({
@@ -769,7 +1140,7 @@
   }).click();
   await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
 
-  await page.getByRole('button', { name: /Inspect/ }).click();
+  await page.locator('[data-blog-archive]').click();
   await page.waitForFunction(() =>
     document.querySelectorAll('[data-blog-entry]').length === 9
   );
@@ -886,7 +1257,7 @@
   const controlledReload = await page.evaluate(() => {
     const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
     return {
-      art: getComputedStyle(document.querySelector('.jrpg-scene'))
+      art: getComputedStyle(document.querySelector('.jrpg-workspace'))
         .backgroundImage,
       fillsViewport: shell.left === 0 &&
         shell.top === 0 &&
@@ -899,7 +1270,7 @@
       theme: document.documentElement.dataset.zenTheme,
     };
   });
-  assert.match(controlledReload.art, /bar-ink\.webp/);
+  assert.match(controlledReload.art, /background-frame-2\.webp/);
   assert.equal(controlledReload.fillsViewport, true);
   assert.equal(controlledReload.fontLoaded, true);
   assert.equal(controlledReload.fontFamily, '"Pixel Mplus"');
@@ -939,6 +1310,56 @@
     utilityBelowScene: true,
   });
   await mobileContext.close();
+
+  const shortContext = await browser.newContext({
+    colorScheme: 'dark',
+    viewport: { width: 900, height: 500 },
+  });
+  const shortPage = await shortContext.newPage();
+  await shortPage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
+  const shortLayout = await shortPage.evaluate(() => {
+    const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
+    const character = document.querySelector('mjj-jrpg-character')
+      .getBoundingClientRect();
+    const chat = document.querySelector('mjj-jrpg-chat')
+      .getBoundingClientRect();
+    return {
+      characterInside: character.left >= scene.left &&
+        character.right <= scene.right,
+      chatInside: chat.left >= scene.left && chat.right <= scene.right,
+      noSceneOverflow: document.querySelector('.jrpg-scene').scrollWidth <=
+        document.querySelector('.jrpg-scene').clientWidth,
+    };
+  });
+  assert.deepEqual(shortLayout, {
+    characterInside: true,
+    chatInside: true,
+    noSceneOverflow: true,
+  });
+  await shortContext.close();
+
+  const forcedColorsContext = await browser.newContext({
+    forcedColors: 'active',
+    viewport: { width: 1280, height: 900 },
+  });
+  const forcedColorsPage = await forcedColorsContext.newPage();
+  await forcedColorsPage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
+  assert.deepEqual(
+    await forcedColorsPage.evaluate(() => ({
+      sceneBar: getComputedStyle(
+        document.querySelector('.jrpg-scene'),
+        '::before',
+      ).backgroundImage,
+      workspace: getComputedStyle(
+        document.querySelector('.jrpg-workspace'),
+      ).backgroundImage,
+    })),
+    {
+      sceneBar: 'none',
+      workspace: 'none',
+    },
+  );
+  await forcedColorsContext.close();
 }
 
 async function testHlsPlayer(browser, siteRoot) {
@@ -1261,7 +1682,7 @@
     for (const source of [home, dogGame, manifest]) {
       assert.doesNotMatch(source, /\.png(?:["')]|$)/i);
     }
-    assert.match(serviceWorker, /v14-blog-browser/);
+    assert.match(serviceWorker, /v30-card-driven-details/);
     assert.match(
       await (
         await fetch(`${baseUrl}/public/pwa-register.js`)