diff --git a/docs/superpowers/plans/2026-06-30-accessibility-and-about-page.md b/docs/superpowers/plans/2026-06-30-accessibility-and-about-page.md new file mode 100644 index 0000000..2343d10 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-accessibility-and-about-page.md @@ -0,0 +1,885 @@ +# Accessibility Pass + About Page Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make the simulator usable away from the attended kiosk — reduced-motion, seizure safety, keyboard/screen-reader access, AA contrast — and add an English-first `about.html`. + +**Architecture:** All client-side in `simulator/static/`. One new pure helper (`flash.js`, UMD + node tests). One new page (`about.html`) mirroring `credits.html`. Behavior changes live in `app.js`; chrome/contrast in `style.css` and `index.html`. The Python static build (`tools/build_static.py`) copies the new static files. + +**Tech Stack:** Vanilla JS (no framework, UMD modules like `i18n.js`/`credits.js`), CSS, Playwright e2e (`simulator/e2e/`), `node --test` for pure helpers, FastAPI dev server for e2e fixtures. + +## Global Constraints + +- No engine/Python behavior changes — only `tools/build_static.py`'s copy list is touched. +- New JS modules follow the existing **UMD** pattern (browser global + `module.exports`), like `i18n.js` and `credits.js`. +- About page is **English-first**; i18n keys may be EN-only and fall back via `pickUiString` (returns `v.en`). +- Target **WCAG 2.1 AA** (4.5:1 text / 3:1 large) plus WCAG 2.3.1 (≤3 flashes/sec) and 2.3.3 (no autonomous animation under reduced-motion). +- Persist user toggles in `localStorage`, mirroring `DEV_KEY = "hef.devMode"` (`app.js:918`). +- Reduced-motion toggle key: `RM_KEY = "hef.reduceMotion"`. Warning-gate dismissal key: `WARN_KEY = "hef.motionWarnDismissed"`. +- `` switching already exists (`applyUiStrings`, `app.js:1325`) — do NOT re-implement. +- E2E in this env: start uvicorn by hand with the venv `python` (only `python3` exists on PATH) and run Playwright with `reuseExistingServer`. `loop-recovery.spec.ts` is known-red on a clean baseline (boots without video). +- Direction convention: descend = `+1` (cosmos→abyss), matching wheel-down (`app.js:703`). + +--- + +### Task 1: Output-panel layout — language picker below Audio, globe inline-left + +**Files:** +- Modify: `simulator/static/index.html:37-51` (Output fieldset) +- Modify: `simulator/static/style.css` (add `.lang-pick` rule) +- Test: `simulator/e2e/tests/a11y.spec.ts` (new) + +**Interfaces:** +- Produces: the `#lang-select` element ends up after `#audio` in DOM order; `.lang-pick` is a flex row. + +- [ ] **Step 1: Write the failing e2e test** + +Create `simulator/e2e/tests/a11y.spec.ts`: + +```ts +import { test, expect } from "@playwright/test"; + +test("language picker sits below the Audio control", async ({ page }) => { + await page.goto("/"); + const audio = page.locator("#audio"); + const lang = page.locator("#lang-select"); + await expect(audio).toBeVisible(); + await expect(lang).toBeVisible(); + const aBox = await audio.boundingBox(); + const lBox = await lang.boundingBox(); + expect(lBox!.y).toBeGreaterThan(aBox!.y); // lang is rendered lower than audio +}); + +test("globe icon is inline-left of the language select (same row)", async ({ page }) => { + await page.goto("/"); + const pick = page.locator(".lang-pick"); + const select = page.locator("#lang-select"); + const pBox = await pick.boundingBox(); + const sBox = await select.boundingBox(); + // select starts to the right of the label's left edge, and shares its row (height ~ one line) + expect(sBox!.x).toBeGreaterThan(pBox!.x); + expect(pBox!.height).toBeLessThan(40); +}); +``` + +- [ ] **Step 2: Run it to verify the first test fails** + +Start the dev server (separate shell, from `simulator/`): +`../.venv/bin/python -m uvicorn server.app:app --port 8000` (adjust to the project's venv/module path). +Run: `cd simulator/e2e && npx playwright test a11y.spec.ts -g "below the Audio"` +Expected: FAIL (lang currently renders above audio). + +- [ ] **Step 3: Move the language picker in `index.html`** + +In the Output `
`, delete the `` block from its current spot (above the Video toggle) and re-insert it as the LAST child of the fieldset, after the Audio `