Frutiger Aero theme, pixel-measured contrast fixes, sheet surfaces

Frutiger Aero (Settings -> Theme): a third theme, not a dark/light variant.
A sky gradient with a strip of grass, the glacier blobs become clouds, the
orbs soap bubbles, glass gets the classic horizon gloss and the buttons a
glossy aqua. The light/dark toggles (header, rail, chat header) hide while it
is on and the picker says why; the pre-paint script applies the class before
first paint and the PWA status bar follows (#3fa3e0). Its muted/frost text is
darker than Light's because those labels sit directly on the sky.

Contrast, measured on real pixels (tests/contrast.mjs, now covering aero):
- The light settings sheet was translucent over a dark scrim, so its muted
  text measured ~2.8:1. Sheets and dialogs now use --glass-sheet-bg (a more
  opaque surface), and Light's --text-muted is a touch darker.
- Result: 0 below-minimum findings and 0 axe violations across every screen,
  all three themes, phone and desktop.

Also: the suites now seed the cookie notice as already dismissed (they run as
a returning user); the regression suite opts out to test the notice itself —
the previous deploy failed its gate because the notice blocked a chat chip
click. The notice also hides during immersive sessions (overlay-open), so it
can never cover a study feed.

Version 0.1.8, SW -> freezmo-v54.
This commit is contained in:
Martin Dvořák 2026-10-12 22:10:16 +00:00
commit e56653dae1
21 changed files with 329 additions and 57 deletions

View file

@ -1398,6 +1398,27 @@ Things I chose while building, with the reasoning:
phase-1 suite (43 checks, including the real route mapping against a stub Ollama returning a
monthly-limit body, a two-device sync round-trip, and the generation guards below).
63. **Frutiger Aero is the third theme.** Settings → Theme offers Light, Dark and Frutiger Aero.
Aero is not a dark/light variant — it replaces the palette (a sky gradient with a strip of
grass, glossy glass with the classic horizon gloss, aqua buttons) and the scene (the glacier
blobs become clouds, the orbs soap bubbles) — so the light/dark toggles (header, rail, chat
header) hide while it is on and the picker says so. The pre-paint script applies the class
before the first paint, the PWA status bar follows it (#3fa3e0), and its muted/frost text is
darker than Light's because those labels sit directly on the sky. Covered by the axe and
pixel-contrast scans in all three themes.
62. **Accessibility: axe is clean everywhere, and contrast is measured on real pixels.** axe cannot
judge text on blurred glass — it reports those nodes as "incomplete" — so `tests/contrast.mjs`
samples the rendered pixels (dominant non-glyph colour per element) and compares it with the
computed text colour using the WCAG formula, skipping disabled controls (1.4.3 exempts them)
and content behind an open sheet. Fixes from that pass: the hidden file inputs are out of the
accessibility tree (aria-hidden + tabIndex -1; labelled buttons open them), the heading order is
right (deck/homework/smart-review cards are h2; LearningSection takes a level so it is h2 on the
library and h3 inside the dialog), the study and review feeds have landmarks, and the light
settings sheet — translucent over a dark scrim, where its muted text measured ~2.8:1 — got its
own more opaque surface (`--glass-sheet-bg`). Result: 0 axe violations and 0 below-minimum
findings across every screen, all three themes, phone and desktop.
61. **Glass that sits still doesn't blur.** The MacBook got hot just browsing: the ice scene animates
constantly, and every `backdrop-filter` surface re-runs its blur on every frame while the backdrop
moves — the whole card and rail area was re-blurring the scene ~60 times a second. Measured on