Files
brain-of-reese/.agent/phases/complete/25_background_no_motion/00_phase.md
T
ducoterra 4971e2859d
Build and Push Containers / build-and-push-app (push) Successful in 12s
Build and Push Containers / build-and-push-db (push) Successful in 10s
chore(agent): track .agent/ planning tree in git
Remove the blanket .agent/ gitignore so the phase roadmap, user
stories, reports, and PLAN.md are versioned with the code. Only
runtime artifacts (.agent/phase-sessions/, .agent/pipeline.log)
remain ignored. Update AGENTS.md git protocol rule to match.
2026-09-01 10:18:22 -04:00

10 KiB
Raw Blame History

Phase 25 — Background: No Motion, Only Fading Light

Source: Owner report (2026-08-25, chat): the background "jitters down and to the right every second and it slowly blinks brighter and darker. 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 (created by task 02) Context: frontend/assets/styles.css — the background block: body::before (44px grid, 1px lines at 60% --line alpha, widened radial mask, animation: bg-grid-drift 60s linear infinite → 0 0 → 44px 44px ≈ 0.73px/s down-right) and body::after (indigo + cyan radial glows, animation: bg-glow-breathe 14s ease-in-out infinite alternate → opacity 0.85↔1 + scale 1↔1.05). Both are position: fixed; inset: 0; z-index: -1; pointer-events: none; <html> owns the var(--bg) canvas, <body> stays transparent (no-occlusion contract). Pins to adapt: tests/unit/test_background_animation.py, tests/e2e/test_background_animation.py, tests/e2e/test_dark_tech_theme.py (test_animated_background pins 60s/14s; test_reduced_motion_honored pins the two body pseudo-layers).

Objective

Stop the background from moving entirely, and replace the uniform whole-layer "blink" with different bright spots that slowly fade in and out: the grid becomes a static texture, and three soft glow spots (phase-08 colors/positions, plus a third spot) each run their own slow, opacity-only fade cycle at a different period, so the background's brightness fluxuates smoothly and irregularly — no blink, no jitter, no motion.

Root cause (found from the code, 2026-08-25)

  1. "Jitters down and to the right every second" = bg-grid-drift: 44px/60s (≈0.73px/s) in the diagonal 44px 44px direction (exactly down-right). A 1px grid line translated sub-pixel-by-sub-pixel is rasterized with per-frame stepping/shimmer — perceived as a once-per- second jitter, not smooth drift. Phase 22 made the drift visible; that is precisely why it now reads as jitter.
  2. "Slowly blinks brighter and darker" = bg-glow-breathe: a uniform whole-layer opacity swing 0.85↔1 over 14s (alternate) plus scale(1)↔scale(1.05) — the entire background pulses in unison (the scale adds a faint zoom). One synchronized pulse reads as a blink; the owner wants independent spots instead.

Phase 22 served the phase-08 design intent (grid drift + whole-layer breathe). The owner now supersedes that design intent — this is an owner revision of a design comment, not of any LOCKED anchor: A1–A17 are untouched, and the phase-08 anchors this phase must still honor are pure CSS / zero JS / no CDN / no new assets / no filter: blur (A11 + the block's perf note).

Owner direction (2026-08-25, verbatim)

"It should be smooth, fluxuating, dimming and brightening, but not moving. Different bright spots should slowly fade in and out."

  1. No movement — no grid drift, no scale/transform, no background-position animation, anywhere in the background.
  2. Fluxuating brightness — overall page brightness varies smoothly and irregularly (not one synchronized pulse).
  3. Different bright spots — multiple glow spots, each fading in and out on its own slow cycle.
  4. The static grid stays — the owner rejected the grid's motion, not the grid; it remains as a still texture. (If the owner later wants the grid gone, that is a follow-up, not this phase.)

Design (pure CSS, zero JS — A11 anchor)

  • body::before — grid: remove the animation; delete @keyframes bg-grid-drift. Keep the 44px cells, 60% --line alpha lines, and widened radial mask (the static texture).

  • Three glow-spot layers, one soft radial gradient each, opacity-only keyframes (0%,100% low → 50% 1, ease-in-out, infinite), with different durations + negative delays so the cycles are out of phase (periods 26/34/42s → LCM 4641s, the composite pattern effectively never repeats within a viewing session):

    layer spot (gradient) keyframes cycle
    body::after indigo rgb(109 120 242 / 0.14), circle 56rem at 12% 8% (phase-08) bg-glow-a 26s, low opacity 0.25
    html::before cyan rgb(34 211 238 / 0.10), circle 60rem at 88% 92% (phase-08) bg-glow-b 34s, delay −12s, low 0.20
    html::after indigo rgb(109 120 242 / 0.09), circle 52rem at 14% 86% bg-glow-c 42s, delay −23s, low 0.15
  • html::before / html::after join body::after as background layers: <html> is the root stacking context — its z-index: -1 pseudo-elements paint above the var(--bg) canvas and below the transparent, non-stacking <body>'s content, so the no-occlusion contract holds unchanged (verify in the E2E, not just assume).

  • All four layers keep: content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;

  • prefers-reduced-motion: reduce stills all four layers (animation: none).

  • No filter (phase-08 no-blur perf anchor), no JS, no new assets; the WCAG palette and every text contrast pair are untouched (the layers carry no text). Opacity-only keyframes stay compositor-friendly.

Dependencies

  • 08_story_dark_tech_theme (complete) — the layers, the palette, the pure-CSS / no-blur / no-CDN anchors, and the 60s/14s pins in tests/e2e/test_dark_tech_theme.py this phase adapts.
  • 22_background_animation (complete) — the current implementation and the unit/E2E pins this phase supersedes.
  • 07_story_responsive_polish (complete) — the 360px overflow pin (the layers are fixed; inset: 0 — they must add no width).

Tasks

  1. 01_still_background_css.md — the CSS redesign (static grid + three opacity-only glow fades) + unit source pins (new tests/unit/test_background_no_motion.py; the phase-22 pins in tests/unit/test_background_animation.py adapted).
  2. 02_e2e_story_suite_commit.md — tests/e2e/test_background_no_motion.py (the story gate, isolated), the regression suites adapted (test_background_animation.py, test_dark_tech_theme.py), the story file, the phase report + screenshots, final validation, the single atomic commit, phase move to complete/.

Locked decisions

  • A11 untouched — vanilla HTML/CSS/JS in git, no CDN, zero JS, system fonts; the whole change is CSS. No filter/blur (phase-08 perf anchor). No new assets. No anchor changed — the superseded spec is the phase-08 design intent (grid drift + whole-layer breathe), not a LOCKED decision; the owner's 2026-08-25 direction is recorded above as the revision. A16 honored — one new story E2E suite + adapted regressions. A17 honored — one atomic --no-gpg-sign commit.

Testing & Quality

  • Unit (source-level, repo source-pin pattern):
    • New tests/unit/test_background_no_motion.py — pins the full new contract (task 01, step 6).
    • Adapted tests/unit/test_background_animation.py — the phase-22 drift/breathe pins flip to the new contract; the generic layer-plumbing and no-blur/no-JS sections stay (task 01, step 7).
  • Integration: none (no app/ changes).
  • Coverage: frontend-only; the >90% app/ gate is unaffected (TOTAL must stay ≥ the pre-change number).
  • E2E: tests/e2e/test_background_no_motion.py (task 02), green in isolation (prereq podman compose up -d db); regressions green in isolation after adaptation: test_background_animation.py, test_dark_tech_theme.py, test_responsive_polish.py.
  • Lint/types: uv run ruff check . && uv run pyright clean.

Completion Criteria

  • No movement: the grid is static (bg-grid-drift gone, no animation on body::before) and no bg-* keyframe animates anything but opacity — audited in a real Chromium via document.styleSheets (E2E test 3).
  • Three distinct bright spots (body::after, html::before, html::after) run distinct slow opacity fades (26s/34s/42s, out of phase); the timelines advance; the layer opacity and a clipped screenshot of the glow region measurably change within a few seconds (E2E tests 2, 4, 5).
  • Contracts hold: all four layers fixed; inset: 0; z-index: -1; pointer-events: none; <html> keeps the var(--bg) canvas and <body> stays transparent (no occlusion); prefers-reduced-motion stills all four; no horizontal overflow at 360px (E2E tests 6–8).
  • .agent/reports/25_background_no_motion/ documents the two root causes with before/after screenshot pairs (.agent/screenshots/25_background_no_motion/).
  • uv run pytest green; uv run pytest --cov=app --cov-report=term-missing TOTAL ≥ pre-change number (gate >90%).
  • uv run pytest tests/e2e/test_background_no_motion.py -v --no-cov green in isolation; test_background_animation.py, test_dark_tech_theme.py, test_responsive_polish.py green in isolation after adaptation.
  • uv run ruff check . && uv run pyright clean.
  • UI Structure Check (AGENTS.md rule 5): layers stay behind content, no text/contrast impact, no 360px overflow.
  • .agent/user_stories/background-no-motion.md exists; the old background-animation.md story carries a supersession note.
  • One --no-gpg-sign commit staging only this phase's files (the unrelated dirty TODO.md must NOT be staged); .agent/phases/todo/25_background_no_motion/ moved to .agent/phases/complete/.

Commit

git add -f .agent/phases/complete/25_background_no_motion \
           .agent/user_stories/background-no-motion.md \
           .agent/user_stories/background-animation.md \
           .agent/reports/25_background_no_motion \
           .agent/screenshots/25_background_no_motion
git add frontend/assets/styles.css tests/unit tests/e2e
git commit --no-gpg-sign -m "fix(ui): background no longer moves — static grid, three glow spots fading in and out on their own slow cycles (owner 2026-08-25)"