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:
@@ -13,12 +13,14 @@ Test → story mapping (Playwright Mapping Rule):
|
||||
(same contrast helper as Phase 07).
|
||||
2. ``test_no_emoji_in_chrome`` — neither page's ``innerText`` nor raw
|
||||
``outerHTML`` contains any emoji code point.
|
||||
3. ``test_animated_background`` — ``body::before``/``::after`` carry
|
||||
background images AND run their animations by default (fixed,
|
||||
pointer-events none).
|
||||
3. ``test_animated_background`` — the background layers (fixed,
|
||||
pointer-events none, carrying images): the grid (``body::before``)
|
||||
is STATIC (phase 25, owner 2026-08-25: no movement) and the three
|
||||
glow spots run their opacity-only fades ``bg-glow-a/b/c`` at
|
||||
26s/34s/42s (``body::after`` + the two ``html`` pseudo-layers).
|
||||
4. ``test_reduced_motion_honored`` — a context with
|
||||
``reduced_motion="reduce"`` → ``animation-name: none`` on both layers
|
||||
(the static grid + glows remain).
|
||||
``reduced_motion="reduce"`` → ``animation-name: none`` on all four
|
||||
layers (the static grid + spot images remain).
|
||||
5. ``test_behavior_unchanged_smoke`` — on-topic question streams an answer
|
||||
+ a source chip + the send button recovers (state machine intact under
|
||||
the new skin).
|
||||
@@ -258,14 +260,19 @@ def test_no_emoji_in_chrome(page: Page, app_url: str, db_ready: None) -> None:
|
||||
|
||||
def test_animated_background(page: Page, app_url: str, db_ready: None) -> None:
|
||||
"""AC3: the background is subtly animated, pure CSS, zero JS, and can
|
||||
never block or dim content: both body pseudo-layers are fixed,
|
||||
pointer-events:none, carry a background image and run their animation
|
||||
by default (60s grid drift + 14s glow breathing)."""
|
||||
never block or dim content. Phase-25 contract (owner 2026-08-25:
|
||||
"not moving. Different bright spots should slowly fade in and out")
|
||||
supersedes the phase-08 recipe: the grid (``body::before``) is a
|
||||
STATIC texture (``animationName: none``, image still painted) and
|
||||
the three glow spots each run their own opacity-only fade —
|
||||
``body::after`` → ``bg-glow-a`` 26s, ``html::before`` →
|
||||
``bg-glow-b`` 34s, ``html::after`` → ``bg-glow-c`` 42s — all fixed,
|
||||
pointer-events:none, carrying an image."""
|
||||
page.goto(app_url)
|
||||
report = page.evaluate(
|
||||
"""() => {
|
||||
const pick = (pseudo) => {
|
||||
const cs = getComputedStyle(document.body, pseudo);
|
||||
const pick = (el, pseudo) => {
|
||||
const cs = getComputedStyle(el, pseudo);
|
||||
return {
|
||||
image: cs.backgroundImage,
|
||||
anim: cs.animationName,
|
||||
@@ -274,27 +281,42 @@ def test_animated_background(page: Page, app_url: str, db_ready: None) -> None:
|
||||
pointerEvents: cs.pointerEvents,
|
||||
};
|
||||
};
|
||||
return { before: pick("::before"), after: pick("::after") };
|
||||
return {
|
||||
grid: pick(document.body, "::before"),
|
||||
glowA: pick(document.body, "::after"),
|
||||
glowB: pick(document.documentElement, "::before"),
|
||||
glowC: pick(document.documentElement, "::after"),
|
||||
};
|
||||
}"""
|
||||
)
|
||||
for layer in ("before", "after"):
|
||||
info = report[layer]
|
||||
assert info["image"] != "none", f"body::{layer} must carry a background image"
|
||||
assert info["anim"] not in ("", "none"), (
|
||||
f"body::{layer} must animate by default (got {info['anim']!r})"
|
||||
for key in ("grid", "glowA", "glowB", "glowC"):
|
||||
info = report[key]
|
||||
assert info["image"] != "none", f"{key} must carry a background image"
|
||||
assert info["position"] == "fixed", f"{key} must be position:fixed"
|
||||
assert info["pointerEvents"] == "none", f"{key} must not intercept input"
|
||||
# The phase-25 recipe: a static grid + three out-of-phase spot fades.
|
||||
assert report["grid"]["anim"] == "none", (
|
||||
f"the grid must be static (got {report['grid']['anim']!r})"
|
||||
)
|
||||
for key, name, seconds in (
|
||||
("glowA", "bg-glow-a", 26.0),
|
||||
("glowB", "bg-glow-b", 34.0),
|
||||
("glowC", "bg-glow-c", 42.0),
|
||||
):
|
||||
info = report[key]
|
||||
assert info["anim"] == name, f"{key} must run {name} (got {info['anim']!r})"
|
||||
assert _seconds(info["duration"]) == pytest.approx(seconds), (
|
||||
f"{key} must run its {seconds:.0f}s fade (got {info['duration']!r})"
|
||||
)
|
||||
assert info["position"] == "fixed", f"body::{layer} must be position:fixed"
|
||||
assert info["pointerEvents"] == "none", f"body::{layer} must not intercept input"
|
||||
# The recipe: 60s seamless grid drift, 14s breathing glows.
|
||||
assert _seconds(report["before"]["duration"]) == pytest.approx(60.0)
|
||||
assert _seconds(report["after"]["duration"]) == pytest.approx(14.0)
|
||||
|
||||
|
||||
def test_reduced_motion_honored(
|
||||
browser: Browser, app_url: str, db_ready: None
|
||||
) -> None:
|
||||
"""AC4: with prefers-reduced-motion: reduce both background layers stop
|
||||
animating (animation-name: none) — the static grid + glows remain."""
|
||||
"""AC4: with prefers-reduced-motion: reduce ALL FOUR background
|
||||
layers stop animating (animation-name: none) — the static grid +
|
||||
spot images remain (phase 25 stills the two html pseudo-layers as
|
||||
well as the body pair)."""
|
||||
context = browser.new_context(
|
||||
reduced_motion="reduce", viewport={"width": 1280, "height": 800}
|
||||
)
|
||||
@@ -303,19 +325,24 @@ def test_reduced_motion_honored(
|
||||
rpage.goto(app_url)
|
||||
report = rpage.evaluate(
|
||||
"""() => {
|
||||
const pick = (pseudo) => {
|
||||
const cs = getComputedStyle(document.body, pseudo);
|
||||
const pick = (el, pseudo) => {
|
||||
const cs = getComputedStyle(el, pseudo);
|
||||
return { anim: cs.animationName, image: cs.backgroundImage };
|
||||
};
|
||||
return { before: pick("::before"), after: pick("::after") };
|
||||
return {
|
||||
grid: pick(document.body, "::before"),
|
||||
glowA: pick(document.body, "::after"),
|
||||
glowB: pick(document.documentElement, "::before"),
|
||||
glowC: pick(document.documentElement, "::after"),
|
||||
};
|
||||
}"""
|
||||
)
|
||||
for layer in ("before", "after"):
|
||||
assert report[layer]["anim"] == "none", (
|
||||
f"body::{layer} must not animate under reduced motion"
|
||||
for key in ("grid", "glowA", "glowB", "glowC"):
|
||||
assert report[key]["anim"] == "none", (
|
||||
f"{key} must not animate under reduced motion (got {report[key]['anim']!r})"
|
||||
)
|
||||
assert report[layer]["image"] != "none", (
|
||||
f"body::{layer}: the static background must remain visible"
|
||||
assert report[key]["image"] != "none", (
|
||||
f"{key}: the static background image must remain visible"
|
||||
)
|
||||
finally:
|
||||
context.close()
|
||||
|
||||
Reference in New Issue
Block a user