fix(ui): background no longer moves — static grid, three glow spots fading in and out on their own slow cycles (owner 2026-08-25)
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# Task 01 — Still Background: Static Grid + Three Opacity-Only Glow Fades
|
||||
|
||||
**Phase:** `25_background_no_motion` · **Story:** `.agent/user_stories/background-no-motion.md`
|
||||
|
||||
## Objective
|
||||
Redesign the background block of `frontend/assets/styles.css` to the
|
||||
owner's spec (no movement; different bright spots slowly fading in and
|
||||
out), and pin the new contract at source level.
|
||||
|
||||
## Work
|
||||
0. **Capture the "before" evidence FIRST** (pre-change): with the DB up
|
||||
(`podman compose up -d db`), boot the app the same way
|
||||
`tests/e2e/conftest.py`'s `app_server` fixture does (uvicorn
|
||||
`app.main:app` on a free port, wait for it to answer), and with a small
|
||||
throwaway Playwright script screenshot the `/` page at 1280×800 →
|
||||
`.agent/screenshots/25_background_no_motion/before.png`, then again
|
||||
~4s later → `before_4s.png` (the pair shows the down-right jitter +
|
||||
the uniform brightness pulse). Kill the server when done.
|
||||
1. `frontend/assets/styles.css` — **grid layer `body::before`:** remove
|
||||
the `animation: bg-grid-drift 60s linear infinite;` declaration and
|
||||
delete the whole `@keyframes bg-grid-drift { … }` block. Keep the 44px
|
||||
cells, the 60% `--line` alpha 1px lines, and the widened radial mask —
|
||||
the grid remains as a **static texture**. Update the block's comment:
|
||||
drift removed (owner 2026-08-25) — the 0.73px/s sub-pixel drift
|
||||
rasterizes as a once-per-second down-right jitter; the owner wants no
|
||||
movement.
|
||||
2. `frontend/assets/styles.css` — **glow layers.** Replace the
|
||||
whole-layer breathe with three independent spot layers:
|
||||
- `body::after` — keep the phase-08 indigo spot exactly:
|
||||
`background-image: radial-gradient(circle 56rem at 12% 8%,
|
||||
rgb(109 120 242 / 0.14), transparent 62%);` and
|
||||
`animation: bg-glow-a 26s ease-in-out infinite;`
|
||||
- **new `html::before`** — the phase-08 cyan spot:
|
||||
`background-image: radial-gradient(circle 60rem at 88% 92%,
|
||||
rgb(34 211 238 / 0.10), transparent 62%);` and
|
||||
`animation: bg-glow-b 34s ease-in-out -12s infinite;`
|
||||
- **new `html::after`** — a third indigo spot:
|
||||
`background-image: radial-gradient(circle 52rem at 14% 86%,
|
||||
rgb(109 120 242 / 0.09), transparent 62%);` and
|
||||
`animation: bg-glow-c 42s ease-in-out -23s infinite;`
|
||||
- All three layers (and the grid layer) must declare:
|
||||
`content: ""; position: fixed; inset: 0; z-index: -1;
|
||||
pointer-events: none;` — no `transform`, no `background-position`,
|
||||
no `filter` on any of them.
|
||||
- Delete `@keyframes bg-glow-breathe { … }` and add the three
|
||||
**opacity-only** keyframe blocks (nothing else may appear in any
|
||||
`bg-*` keyframe):
|
||||
```css
|
||||
@keyframes bg-glow-a { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
|
||||
@keyframes bg-glow-b { 0%, 100% { opacity: 0.20; } 50% { opacity: 1; } }
|
||||
@keyframes bg-glow-c { 0%, 100% { opacity: 0.15; } 50% { opacity: 1; } }
|
||||
```
|
||||
3. `frontend/assets/styles.css` — **reduced motion:** in the existing
|
||||
`@media (prefers-reduced-motion: reduce)` block that stills the
|
||||
background (currently `body::before, body::after { animation: none; }`,
|
||||
right after the spinner block), extend the selector list to all four
|
||||
layers: `body::before, body::after, html::before, html::after
|
||||
{ animation: none; }`. Do not touch the typing/spinner/thinking
|
||||
reduced-motion blocks.
|
||||
4. `frontend/assets/styles.css` — **section comment:** rewrite the
|
||||
"Animated background" comment to document the new spec: no movement
|
||||
(owner 2026-08-25); three independent soft spots, opacity-only fades
|
||||
at 26/34/42s with negative delays → out of phase, so the total light
|
||||
fluxuates smoothly and irregularly; `html::before`/`html::after` are
|
||||
background layers (root stacking context: they paint above the
|
||||
`var(--bg)` canvas and below the transparent, non-stacking `<body>`'s
|
||||
content — the no-occlusion contract is unchanged).
|
||||
5. **New `tests/unit/test_background_no_motion.py`** (repo source-pin
|
||||
pattern — reuse the helpers from `tests/unit/test_background_animation.py`
|
||||
(`_css`, `_css_no_comments`, `_rule_block`; note `_rule_block` matches
|
||||
top-level `selector { … }`, which works for `html::before`/`html::after`
|
||||
as written in step 2). Pin, at minimum:
|
||||
- `body::before` carries **no `animation`** declaration;
|
||||
`@keyframes bg-grid-drift` is absent from the file; the grid keeps
|
||||
its static texture (`background-size: 44px 44px`, the two 60%-alpha
|
||||
1px line gradients, the widened mask, both mask properties).
|
||||
- `body::after` runs `bg-glow-a 26s ease-in-out infinite`;
|
||||
`html::before` runs `bg-glow-b 34s ease-in-out -12s infinite`;
|
||||
`html::after` runs `bg-glow-c 42s ease-in-out -23s infinite`;
|
||||
each glow layer's `background-image` is exactly the single radial
|
||||
gradient from step 2 (color, radius, position, 62% transparent stop).
|
||||
- **No movement:** parse every `@keyframes bg-*` rule in the file —
|
||||
the set of declared property names across all keyframe frames is
|
||||
exactly `{opacity}` (no `transform`, `scale`, `background-position`,
|
||||
…); none of the three glow layers declares `transform` or
|
||||
`background-position`.
|
||||
- The three glow durations are distinct and each ≥ 20s (slow).
|
||||
- All four pseudo-layers: `position: fixed`, `inset: 0`,
|
||||
`z-index: -1`, `pointer-events: none`, `content: ""`.
|
||||
- Plumbing: `html` keeps `background: var(--bg)`; `body` keeps
|
||||
`background: transparent` and declares none of `z-index`,
|
||||
`transform`, `opacity`, `filter`.
|
||||
- The reduced-motion block stills all four layers (all four selectors
|
||||
present together with `animation: none`).
|
||||
- No `filter` in any background layer block; no `blur` anywhere in
|
||||
the file (comments stripped).
|
||||
6. **Adapt `tests/unit/test_background_animation.py`** (the phase-22
|
||||
source pins) to the new contract so the whole unit suite is green:
|
||||
replace the grid-drift and breathe tests with their new-contract
|
||||
equivalents (where a check is already covered by the new file, keep the
|
||||
file self-contained rather than importing from it — the repo pattern is
|
||||
local pins); keep the generic layer-plumbing tests
|
||||
(`test_both_layers_are_fixed_zminus1_noninteractive` — extend it to
|
||||
cover `html::before`/`html::after` — and
|
||||
`test_html_owns_bg_and_body_stays_transparent`) and the no-blur/no-JS
|
||||
anchor test; update the module docstring to describe the phase-25
|
||||
design and point at `.agent/user_stories/background-no-motion.md`.
|
||||
|
||||
## Testing & Quality
|
||||
- `uv run pytest tests/unit -v` green (new + adapted pins).
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` TOTAL ≥ the
|
||||
pre-change number (`app/` is untouched — the >90% gate holds).
|
||||
- `uv run ruff check . && uv run pyright` clean.
|
||||
- Coverage **>90%** on new/modified code: the functional change is CSS;
|
||||
the new/modified Python is pytest source pins, exercised in full.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `styles.css`: no `bg-grid-drift`, no `bg-glow-breathe`, no
|
||||
`animation` on `body::before`; exactly three opacity-only
|
||||
`bg-glow-a/b/c` cycles on `body::after`, `html::before`,
|
||||
`html::after` (26s/34s/42s, delays 0/−12s/−23s); reduced-motion
|
||||
stills all four layers; comments document the owner's no-movement
|
||||
spec.
|
||||
- [ ] `uv run pytest tests/unit` green with
|
||||
`tests/unit/test_background_no_motion.py` present and passing.
|
||||
- [ ] `uv run pytest --cov=app` TOTAL ≥ pre-change; ruff + pyright clean.
|
||||
- [ ] `.agent/screenshots/25_background_no_motion/before.png` and
|
||||
`before_4s.png` captured **before** the CSS change.
|
||||
Reference in New Issue
Block a user