training/dashboard: strip every stacking-context-creating prop from bg-canvas

The 'rendering over presentation elements' artifacts were from
piling stacking-context-creating properties on bg-canvas:
  - filter: blur(0px) — even at 0px creates a stacking context
    AND a 3D-flattening grouping property
  - transform: translateZ(0) — stacking context + 3D context
  - earlier: isolation, contain — stacking context + flattening

Each of these on its own can cause neighboring element artifacts
in Chromium (cutout-shaped opacity transition leaks, or in extreme
cases content rendering at the wrong z-order).

Strip everything. The bg-canvas is now just position:fixed with
overflow:hidden — that's enough for the bg layers it contains,
and the browser will GPU-promote it automatically when there's
real animation inside. The filter is now conditional: JS sets
--bg-filter to blur(Npx) only when the slider is non-zero, and
removes the custom property at zero so the rule falls through to
filter: none and no stacking context is created.
This commit is contained in:
Max Gorog 2026-05-08 00:02:18 -05:00
parent 841dcead6a
commit cba10e27a5
3 changed files with 20 additions and 8 deletions

View file

@ -48,15 +48,19 @@ html, body {
/* Animation timings respect --anim-speed (multiplier; 1 = nominal,
2 = twice as fast). Whole canvas can also be blurred via
--bg-blur. Both are set by the theme panel sliders. */
/* Minimal positioning, no layer-promotion tricks. Every stacking-
context-creating property here (filter, transform, isolation,
contain) caused some downstream compositor artifact in earlier
iterations. The browser will GPU-promote on its own when needed.
`filter` is only applied when the bg-blur slider is non-zero
the JS sets `--bg-filter` to `blur(Npx)` then, and removes the
custom property when blur is 0 so the rule falls through to
`none`. */
.bg-canvas {
position: fixed; inset: 0; z-index: 0;
pointer-events: none; overflow: hidden;
background: var(--bg);
filter: blur(var(--bg-blur, 0px));
/* Layer promotion via transform alone is enough to give bg-canvas
its own compositor layer. isolation/contain were over-aggressive
and contributed to compositor artifacts on neighboring layers. */
transform: translateZ(0);
filter: var(--bg-filter, none);
overflow-anchor: none;
}

View file

@ -420,7 +420,15 @@
root.setProperty('--theme-c', state.C);
root.setProperty('--theme-h', state.H);
root.setProperty('--anim-speed', state.animSpeed);
root.setProperty('--bg-blur', `${state.bgBlur}px`);
// Only apply the filter when there's something to blur — at
// zero, applying `filter: blur(0px)` still creates a stacking
// context and forces a compositor layer that can flatten 3D
// children and produce artifacts on neighboring elements.
if (state.bgBlur > 0) {
root.setProperty('--bg-filter', `blur(${state.bgBlur}px)`);
} else {
root.removeProperty('--bg-filter');
}
root.setProperty('--tint-strength', state.tint);
document.body.dataset.theme = state.background;
applyThemeSpecificVars();

View file

@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CIS490 — live</title>
<link rel="stylesheet" href="/static/dashboard.css?v=646f533a">
<link rel="stylesheet" href="/static/dashboard.css?v=7fae1ca0">
</head>
<body>
<!-- SVG filter defs for the lava-lamp goo effect. Width/height 0
@ -479,6 +479,6 @@
</article>
</div>
<script src="/static/dashboard.js?v=c0e132ce"></script>
<script src="/static/dashboard.js?v=17a27369"></script>
</body>
</html>