Stop blurring the animated scene on static glass (2x frame time)

The MacBook ran hot browsing the app: the ice scene animates constantly, so
every backdrop-filter surface re-ran its blur on every frame. Measured on
desktop (2x CPU throttle, software renderer): p50 frame 66.6ms baseline,
33.3ms with the static surfaces not filtering, 16.7ms with the scene's
animation also off; frames over 34ms: 128 -> 14.

The glass recipe is split: floating layers (sheets, dropdowns, dialogs, the
sticky header/chrome/tab bar) keep the blur (backdrop-blur-lg, what phones
already used); static layers (cards, the sidebar rail, the gate, the study
prompt card) keep the identical tint, rim, highlight and shadow without the
filter. The study prompt card moves from glass-panel to card + glass-solid,
which is the same surface recipe.

Quality: a deterministic pixel diff (scene motion frozen, both themes,
desktop + phone) shows a ~1% colour shift in the card backdrops — invisible.
The mobile axe contrast audit and the bubbles suite stay green.
tests/bubble-perf.mjs now also reports the glass cost. README: decision 61.
SW -> freezmo-v49.
This commit is contained in:
Martin Dvořák 2026-10-12 19:47:36 +00:00
commit 1f2e702b4e
5 changed files with 63 additions and 18 deletions

View file

@ -404,8 +404,20 @@ body:has(.overlay-open) :is(.app-header, .segmented, .tabbar) {
@layer components {
/* Frosted ice glass ------------------------------------------------------
One shared recipe for the large surfaces only: translucent tint, 24px
blur, a bright rim, an inner top highlight and a blue-tinted shadow.
Two recipes now:
- **Floating glass** (sheets, dropdowns, dialogs and the sticky chrome):
the tint below plus a backdrop blur — the blur is what reads as glass
when content scrolls underneath (the app header, the study chrome, the
tab bar, a sheet over the page).
- **Static glass** (cards, the rail, the gate, the study prompt card): the
exact same tint, rim, highlight and shadow, but **no** backdrop-filter.
On a smooth gradient the blur is invisible — and because the ice scene
animates, every blurred surface re-runs the filter on every frame.
Measured on desktop (2x throttle, software renderer): p50 frame time
66ms while the static surfaces blurred the scene, 33ms without them,
17ms with the scene's animation also stopped.
Nested glass (chips, inputs) never adds a second blur layer. Tailwind's
backdrop utilities emit the -webkit- prefix too (hand-written
backdrop-filter gets minified to -webkit- only, which paints nothing). */
@ -416,11 +428,18 @@ body:has(.overlay-open) :is(.app-header, .segmented, .tabbar) {
.tabbar,
.glass,
.gate-card {
@apply backdrop-blur-xl backdrop-saturate-[1.4];
background: var(--glass-bg);
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-highlight);
}
/* Only the layers that can have content behind them blur. */
.glass-panel,
.study-chrome,
.tabbar,
.glass {
@apply backdrop-blur-lg backdrop-saturate-[1.4];
}
.glass-panel {
border: 1px solid var(--glass-border);
border-radius: 1.5rem;
@ -460,28 +479,23 @@ body:has(.overlay-open) :is(.app-header, .segmented, .tabbar) {
border-radius: 1.75rem; /* 28px */
}
/* Blur is expensive on phones: halve it, and thin out the ice scene. */
/* Blur is expensive: halve it on phones (floating glass only). */
@media (max-width: 1023px) {
.glass-panel,
.card,
.sidebar-surface,
.study-chrome,
.tabbar,
.glass,
.gate-card {
@apply backdrop-blur-lg;
.glass {
@apply backdrop-blur-md;
}
}
/* No backdrop-filter support: fall back to a more opaque surface. */
/* No backdrop-filter support: fall back to a more opaque surface (floating
glass only — the static surfaces already look right without a filter). */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.glass-panel,
.card,
.sidebar-surface,
.study-chrome,
.tabbar,
.glass,
.gate-card {
.glass {
background: var(--glass-fallback);
}
}