Files
brain-of-reese/.agent/reports/25_background_no_motion/report.md
T

5.7 KiB
Raw Blame History

Phase 25 — Background: No Motion, Only Fading Light

Owner report (2026-08-25, chat, verbatim): "It should be smooth, fluxuating, dimming and brightening, but not moving. Different bright spots should slowly fade in and out." Story: .agent/user_stories/background-no-motion.md.

Root causes (phase-22 measurements as context)

  1. "Jitters down and to the right every second" = the grid drift. bg-grid-drift translated the 44px grid 44px per 60s (≈0.73px/s) diagonally down-right — exactly the reported direction. A 1px line moved sub-pixel by sub-pixel rasterizes with per-frame stepping, not smooth motion, so it reads as a once-per-second jitter. Phase 22 had made that drift visible (60% line alpha + widened mask; measured 1.82/765 mean pixel change over 5s in the grid zone vs 0.48 pre-phase-22) — that is precisely why it now reads as jitter. Confirmed in the before.png/before_4s.png pair below: a grid-only region far from every glow changes by 2.98/765 mean over 4s (4.7% of its pixels) — the grid itself is moving.
  2. "Slowly blinks brighter and darker" = the whole-layer breathe. bg-glow-breathe swung the entire glow layer's opacity 0.85↔1 over 14s (alternate) plus scale(1)↔scale(1.05) (a faint zoom). One synchronized pulse of the whole background reads as a blink; the before pair's uniform change across both glow corners (mean 2.19/765 over the full frame) is that single pulse.

Design change (styles.css — pure CSS, zero JS, no blur, palette

untouched; task 01)

property phase 22 phase 25
grid (body::before) bg-grid-drift 60s linear infinite (0→44px, 0.73px/s) static — animation removed, bg-grid-drift deleted (44px cells, 60% --line 1px lines, widened mask kept)
glow one whole-layer bg-glow-breathe (14s, opacity 0.85↔1 + scale 1↔1.05) three independent spot layers, opacity-only fades
spot A body::after indigo + cyan on one layer indigo rgb(109 120 242 / 0.14) 56rem @ 12%/8% — bg-glow-a 26s ease-in-out infinite, low 0.25
spot B html::before — cyan rgb(34 211 238 / 0.10) 60rem @ 88%/92% — bg-glow-b 34s ease-in-out −12s infinite, low 0.20
spot C html::after — indigo rgb(109 120 242 / 0.09) 52rem @ 14%/86% — bg-glow-c 42s ease-in-out −23s infinite, low 0.15
keyframes bg-grid-drift (background-position), bg-glow-breathe (opacity + transform) bg-glow-a/b/c — opacity only (0%/100% low → 50% 1)
reduced motion stills the two body layers stills all four layers

html::before/html::after join as background layers: <html> is the root stacking context, so their z-index: -1 pseudo-elements paint above the var(--bg) canvas and below the transparent, non-stacking <body>'s content (verified live — E2E test 6). 26/34/42s with negative delays (LCM 4641s) keep the cycles out of phase, so the composite pattern effectively never repeats within a viewing session. No LOCKED anchor changed (A11 pure CSS / zero JS / no CDN / no new assets; no filter: blur).

Before / after evidence (1280×800 headless Chromium, shots ~4s

apart; .agent/screenshots/25_background_no_motion/)

  • before.png / before_4s.png (captured pre-change, task 01): down-right grid jitter + the uniform whole-layer pulse.
  • after.png / after_4s.png (captured post-change, task 02): brightness changes; the grid is bit-identical.

Region diffs (pure-stdlib PNG decode; "changed" = pixels with per-channel Δ sum > 12/765):

region before pair: changed / mean|d| after pair: changed / mean|d|
full frame 42,316 (4.1%) / 2.190 8,357 (0.8%) / 1.754
grid-only patch (900,30)–(1250,120) 1,484 (4.7%) / 2.979 — the grid moves 0 (0.0%) / 0.000 — bit-identical
spot A center (154,64) patch 406 (4.1%) / 3.198 4,018 (40.2%) / 6.595 — mid-fade
spot C clip (0,500)–(500,800) 6,469 (4.3%) / 1.274 0 / 3.247 — gentle fade, no >12/765 steps
spot C center (179,688) patch 63 (0.6%) / 0.789 0 / 6.003 — visibly brightening
spot B clip (780,500)–(1280,800) 5,784 (3.9%) / 2.037 0 / 1.211 — slow fade at this phase

Read: after the change, the only region that moves pixel-by-pixel is the light itself (spot A's 26s cycle passes through its steepest mid-fade in the 4s window); every region that the grid drift used to scrub through is now byte-identical — no positional shift anywhere.

E2E story gate (task 02) — tests/e2e/test_background_no_motion.py

8 tests, green in isolation (see "Results"). The deterministic no-movement proof is test 3: a live Chromium walk of document.styleSheets shows the set of properties declared across every frame of every bg-* @keyframes rule is exactly {opacity}. Regression suites adapted to the new contract: tests/e2e/test_background_animation.py (grid static + bg-glow-a running + the three spot timelines advance + 4-layer contracts + 360px pin) and tests/e2e/test_dark_tech_theme.py (test_animated_background → static grid + 26/34/42s spots; test_reduced_motion_honored → all four layers stilled).

UI Structure Check (AGENTS.md rule 5)

  • Layers behind content: E2E test 6 pins position: fixed, z-index: -1, pointer-events: none, inset: 0 on all four pseudo-layers live, plus the html-canvas / body-transparent no-occlusion pair (rgb(10, 14, 23) / rgba(0, 0, 0, 0)).
  • No text/contrast impact: the change touches only background layers (no palette token, no text on the layers); the phase-08 contrast suite stays green.
  • No 360px overflow: E2E test 8 — scrollWidth <= clientWidth at 360×740 with all four fixed; inset: 0 layers live.