"""Unit: the phase-78 static-background contract (source pins). Owner direction (TODO.md L4, recorded per AGENTS.md rule 3): "Remove the animated css background, it's too resource intensive" — the three opacity-fading glow spots (``body::after`` / ``html::before`` / ``html::after``), their glow keyframes, and the ``prefers-reduced-motion`` rule whose only job was stilling those layers are deleted from ``styles.css``. The 44px grid texture on ``body::before`` STAYS — it is static (zero animation cost). Supersedes the phase-25 fading-glow contract (superseded chain 08 → 25 → 78); the phase-25 unit source-pin suite (``tests/unit/test_background_animation.py``) is deleted with its premise (three fading glows on named keyframe cycles). Story: n/a (TODO-derived). Browser behavior (no background animation in a real viewport, the grid still painted, no occlusion, no overflow) is E2E-covered by ``tests/e2e/test_background_no_motion.py`` (task 02). """ from __future__ import annotations import re from pathlib import Path STYLES_CSS = ( Path(__file__).resolve().parents[2] / "frontend" / "assets" / "styles.css" ) GRID_LAYER = "body::before" # the static 44px grid — STAYS # The phase-25 glow layers — all three deleted in phase 78 (they were # the ONLY animated part of the background). GLOW_LAYERS = ("body::after", "html::before", "html::after") def _css() -> str: return STYLES_CSS.read_text(encoding="utf-8") def _css_no_comments() -> str: """styles.css with /* … */ comments stripped — for functional rule checks that must not trip on explanatory prose.""" return re.sub(r"/\*[\s\S]*?\*/", "", _css()) def _find_rule(css: str, selector: str) -> re.Match[str] | None: """The first top-level ``selector { ... }`` rule, or None (the deleted glow layers must be ABSENT, so this may not assert).""" return re.search(r"(?m)^" + re.escape(selector) + r"\s*\{([\s\S]*?)\n\}", css) def _rule_block(css: str, selector: str) -> str: """Body of the first ``selector { ... }`` rule (top-level, no nesting).""" rule = _find_rule(css, selector) assert rule, f"styles.css must define a {selector} rule" return rule.group(1) # -------------------------------------------------------------------------- # The animated part is gone — no glow layers, no glow keyframes # -------------------------------------------------------------------------- def test_no_bg_glow_keyframes_remain() -> None: """The three glow @keyframes blocks are deleted — the names must not appear anywhere in the file (no declaration, no keyframe block, no stale comment). Pinned by the ``bg-`` prefix: no token carrying the background keyframe namespace may survive (the prefix is a plain literal, so this pin itself carries none of the deleted names).""" assert "bg-" not in _css(), ( "no token in the background keyframe namespace (bg-*) may remain " "in styles.css — the glow keyframes and their declarations are deleted" ) assert re.search(r"@keyframes bg-", _css_no_comments()) is None, ( "no background @keyframes may remain in styles.css" ) def test_glow_layers_are_deleted() -> None: """body::after / html::before / html::after no longer exist as CSS rules — the layers (their background-images, their animations, their fixed/z-index:-1 boxes) are gone from the page entirely.""" rules = _css_no_comments() for sel in GLOW_LAYERS: assert _find_rule(rules, sel) is None, ( f"{sel} must be deleted (the phase-78 static contract)" ) def test_no_background_layer_declares_animation() -> None: """None of the four background pseudo-element selectors carries an ``animation:`` declaration — the three glow selectors are ABSENT, and the surviving grid layer is animation-free.""" rules = _css_no_comments() for sel in (GRID_LAYER, *GLOW_LAYERS): rule = _find_rule(rules, sel) if rule is None: continue # deleted layer — nothing to animate assert "animation" not in rule.group(0), ( f"{sel} must not animate (static background contract)" ) # -------------------------------------------------------------------------- # The static grid STAYS — byte-identical texture, no animation # -------------------------------------------------------------------------- def test_grid_layer_is_static_and_unchanged() -> None: """The owner removed the animated part, not the grid: body::before keeps 44px cells, 1px lines at 60% of the grid line color, and the widened radial mask (both the -webkit- and standard mask properties) — and carries NO animation. Phase 92 (task 02): the line color is the 9th identity variable --grid-line at 60% (the built-in #4a2626 reproduces the old warm tone exactly — and the tab's Grid lines picker now repaints this texture).""" block = _rule_block(_css(), GRID_LAYER) grid = "color-mix(in srgb, var(--grid-line) 60%, transparent)" assert "background-size: 44px 44px" in block assert f"linear-gradient(to right, {grid} 1px, transparent 1px)" in block, ( "grid must keep horizontal 1px lines at 60% --grid-line" ) assert f"linear-gradient(to bottom, {grid} 1px, transparent 1px)" in block, ( "grid must keep vertical 1px lines at 60% --grid-line" ) mask = "radial-gradient(140% 110% at 50% 0%, black 40%, transparent 90%)" assert f"-webkit-mask-image: {mask};" in block assert f"mask-image: {mask};" in block assert "animation" not in block, "the grid must stay static (no animation)" def test_grid_layer_plumbing() -> None: """The surviving grid layer stays behind the content and can never intercept input: fixed, full-viewport, z-index -1, pointer-events none, with pseudo content (UI Structure Check: the fixed; inset: 0 layer adds no width — no 360px overflow).""" block = _rule_block(_css(), GRID_LAYER) assert "position: fixed" in block assert "inset: 0" in block assert "z-index: -1" in block assert "pointer-events: none" in block assert 'content: ""' in block def test_html_owns_canvas_and_body_stays_transparent() -> None: """The no-occlusion contract survives the deletion: keeps the var(--bg) canvas; stays transparent and non-stacking — or the z-index:-1 grid layer would be painted over.""" html_block = _rule_block(_css(), "html") assert "background: var(--bg)" in html_block, ( "html must keep background: var(--bg) (the page canvas)" ) body_block = _rule_block(_css(), "body") assert "background: transparent" in body_block, ( "body must keep background: transparent so the grid shows" ) for prop in ("z-index", "transform", "opacity", "filter"): assert prop + ":" not in body_block, ( f"body must not create a stacking context (found {prop})" ) # -------------------------------------------------------------------------- # Reduced motion + phase-08 anchors # -------------------------------------------------------------------------- def test_background_reduced_motion_block_is_deleted() -> None: """The prefers-reduced-motion rule whose only job was stilling the background layers (body::before, body::after, html::before, html::after → animation: none) is deleted WITH the layers. The unrelated reduced-motion blocks (typing dots, spinner, toasts, nav slide, …) stay untouched.""" blocks = re.findall( r"@media \(prefers-reduced-motion: reduce\)\s*\{([\s\S]*?)\n\}", _css() ) assert blocks, "the unrelated reduced-motion blocks must survive" for i, block in enumerate(blocks): for sel in (GRID_LAYER, *GLOW_LAYERS): assert sel not in block, ( f"reduced-motion block {i} still references background layer {sel}" ) def test_no_filter_no_blur() -> None: """The phase-08 performance anchor survives the deletion: no ``filter`` in the grid rule and no ``blur`` anywhere in styles.css (comments stripped).""" assert "filter" not in _rule_block(_css(), GRID_LAYER), ( "the grid layer must not use any filter" ) assert "blur" not in _css_no_comments(), ( "no filter: blur anywhere in styles.css (phase-08 perf anchor)" )