Aero glass is transparent now, blurred without the per-frame cost

The white-ish fills are gone: every surface is a thin translucent glass with
the gloss and a bright rim, and the wallpaper reads through all of it.

The blur is a pre-blurred copy of the wallpaper (public/aero/
background-blur.webp, 3 kB) glued to the viewport with
background-attachment: fixed, layered under the tint and the gloss, instead
of a live backdrop-filter. Blurring the live scene measured 66ms per frame
at 2x CPU throttle (115 frames over 34ms) because the bubbles move behind
every surface; with the pre-blurred layer the same scene runs at 33ms and 8
slow frames — the same as the other themes, so the MacBook stays cool.

Sticky chrome, sheets and dropdowns keep a real backdrop-filter (they sit
over scrolling content, and their area is small).

Contrast: 0 below-minimum findings, 0 axe violations, all three themes.
Version 0.2.5, SW -> freezmo-v58.
This commit is contained in:
Martin Dvořák 2026-10-13 07:45:22 +00:00
commit 13b3a80e1c
5 changed files with 59 additions and 29 deletions

View file

@ -1419,9 +1419,14 @@ Things I chose while building, with the reasoning:
(<https://wallpapersafari.com/w/IlWuz7>, re-encoded to a 237 kB WebP in `public/aero/` and
credited in Settings), with ten drawn bubbles rising over it (`.ice-bubble` — `display: none`
outside aero so they cost nothing elsewhere, still under reduced motion). Buttons and segments
are **liquid glass** (translucent, glossed top half, bright rim, backdrop blur), the cards are
pale blue glass, and the helper lines that sit straight on the wallpaper (the keyboard tip, the
AI note) get their own small glass chip — dark text on grass measured 2.5:1. The light/dark
are **liquid glass** — transparent, glossed, with a bright rim — and every surface shows a
**pre-blurred copy of the wallpaper** (`public/aero/background-blur.webp`, 3 kB, glued to the
viewport with `background-attachment: fixed`) instead of blurring the live scene: the bubbles
move behind every surface, so a real `backdrop-filter` re-runs ~60 times a second (measured
66ms/frame at 2x throttle vs 17ms with nothing blurring). The tint and the gloss ride on top of
that layer, so the look is identical at zero per-frame cost. The helper lines that sit straight
on the wallpaper (the keyboard tip, the AI note) get a small glass chip — dark text on grass
measured 2.5:1. The light/dark
toggles (header, rail, chat header) hide while it is on and the picker says why; the PWA status
bar follows the wallpaper's sky (#2fa7e6). Covered by the axe and pixel-contrast scans in all
three themes.

View file

@ -287,14 +287,12 @@
box-shadow: inset -8px -12px 20px -10px rgb(3 74 124 / 0.5), inset 8px 10px 16px -10px rgb(255 255 255 / 0.95), 0 12px 26px -16px rgb(3 74 124 / 0.55);
}
/* The Aero gloss: the classic white top half with a horizon line. */
.aero .card,
/* The Aero gloss for the floating surfaces (sheets, dropdowns and the sticky
chrome keep a real backdrop-filter — they sit over scrolling content). */
.aero .glass-panel,
.aero .sidebar-surface,
.aero .study-chrome,
.aero .tabbar,
.aero .glass,
.aero .gate-card {
.aero .glass {
background-image: linear-gradient(
180deg,
rgb(255 255 255 / 0.75) 0%,
@ -369,28 +367,55 @@
.aero .btn-primary,
.aero .segmented > button[aria-pressed="true"],
.aero .segmented > a[aria-current="page"] {
background-image: linear-gradient(
180deg,
rgb(255 255 255 / 0.9) 0%,
rgb(226 245 255 / 0.62) 48%,
rgb(168 224 255 / 0.55) 52%,
rgb(219 242 255 / 0.7) 100%
);
border: 1px solid rgb(255 255 255 / 0.9);
color: #05243d;
box-shadow: 0 10px 24px -14px rgb(2 62 110 / 0.6), inset 0 1px 0 rgb(255 255 255 / 0.98), inset 0 -8px 14px -10px rgb(255 255 255 / 0.7);
/* @apply, not a hand-written backdrop-filter: the minifier keeps only the
-webkit- prefix of a hand-written one, which paints nothing (see decision
19). This is what makes the buttons read as liquid glass. */
@apply backdrop-blur-lg backdrop-saturate-150;
border: 1px solid rgb(255 255 255 / 0.6);
color: #04223c;
box-shadow: 0 12px 26px -16px rgb(2 62 110 / 0.55), inset 0 1px 0 rgb(255 255 255 / 0.9);
/* The glass itself comes from the pre-blurred wallpaper rule above. */
}
/* The glass itself, a touch more transparent so the wallpaper reads through
it like liquid, still opaque enough for the dark text (contrast scanned). */
/* Liquid glass: far more transparent than the other themes, with a strong
backdrop blur doing the legibility work — the wallpaper stays visible
through every surface. Contrast is pixel-scanned in all three themes, so
the tint cannot drop below what the text needs. */
.aero {
--glass-bg: rgb(222 244 255 / 0.72);
--glass-solid-bg: rgb(228 246 255 / 0.82);
--glass-sheet-bg: rgb(230 246 255 / 0.92);
--glass-bg: rgb(228 246 255 / 0.34);
--glass-solid-bg: rgb(232 248 255 / 0.46);
--glass-sheet-bg: rgb(232 248 255 / 0.8);
--surface-strong: rgb(255 255 255 / 0.34);
--surface-sunken: rgb(255 255 255 / 0.26);
--nav-active-bg: rgb(255 255 255 / 0.4);
}
/* Liquid glass without the per-frame cost: every surface shows a pre-blurred
copy of the wallpaper, glued to the viewport (`background-attachment:
fixed`), with the tint and the gloss layered on top. Blurring the live
scene instead measured 66ms per frame at 2x CPU throttle — the bubbles move
behind every surface, so a real backdrop-filter re-runs ~60 times a second
(vs 17ms with nothing blurring). The look is the same; the cost is zero. */
.aero .card,
.aero .glass-solid,
.aero .sidebar-surface,
.aero .gate-card,
.aero .nav-action,
.aero .btn-primary,
.aero .grad-ice,
.aero .segmented > button[aria-pressed="true"],
.aero .segmented > a[aria-current="page"],
.aero .hint-line {
background-image:
linear-gradient(
180deg,
rgb(255 255 255 / 0.5) 0%,
rgb(255 255 255 / 0.2) 47%,
rgb(255 255 255 / 0.07) 53%,
rgb(255 255 255 / 0.24) 100%
),
linear-gradient(rgb(230 247 255 / 0.42), rgb(230 247 255 / 0.42)),
url("/aero/background-blur.webp");
background-size: 100% 100%, 100% 100%, cover;
background-position: 0 0, 0 0, center 42%;
background-attachment: scroll, scroll, fixed;
background-repeat: no-repeat, no-repeat, no-repeat;
}
html,

View file

@ -1,6 +1,6 @@
{
"name": "freezmo",
"version": "0.2.4",
"version": "0.2.5",
"private": true,
"license": "GPL-3.0-only",
"scripts": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -9,7 +9,7 @@
*
* Bumping CACHE_VERSION is how a new deploy invalidates old assets.
*/
const CACHE_VERSION = "freezmo-v57";
const CACHE_VERSION = "freezmo-v58";
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
const ASSET_CACHE = `${CACHE_VERSION}-assets`;