Chat chrome: one gutter, one radius scale, one even glass recipe

The header and composer were full-bleed bands (flush to the top/right/bottom
viewport edges, composer corners off-screen) next to a rail that floats with a
12px margin. The new --layout-gutter (12px) spaces the rail, header, empty-state
card and composer identically on every side; --radius-panel/-control/-chip
(-pill) replace the ad-hoc radii across the shared classes, so every panel is
rounded on all four corners and shared controls match in every theme. The
desktop-only ::after hairlines and the left-corner-only radius are gone.

Aero: the rail and the chat chrome (answers included) share one even recipe -
the wallpaper extra-blurred into a hue-free wash (background-glass.webp) under a
uniform ~50% white veil and a whisper of gloss. Measured within ~10 RGB units
across the rail top->bottom and the card/composer left->right; the blue->green
smear and the teal composer are gone. The composer's field is a clean even tile
with no wallpaper gradient inside it.

Chat fixes: send is a filled primary tile shaped exactly like attach (dimmed
while the input is empty), suggestion/context chips use the shared .chip token
(40/44px target, visible rim, full-contrast text, raw model id in the tooltip),
the AI note reads at 12px --text-muted (.ai-disclaimer), the selected sidebar
row carries the accent border + accent icon, the theme toggle is back in Aero
(the moon flips to Dark; Settings brings Aero back), and the empty state is
centered exactly between header and composer.

Outside CSS: the wallpaper's top-left HUD-circle decoration (it peeked through
the 12px gutters on wide windows) was healed out of background.webp and
background-blur.webp; the orbs/bubbles ride one masked .ice-floats layer that
fades them under the chrome bands and the rail strip, so none is sliced by a
panel edge (still parked under reduced motion).

Version 0.6.0, SW -> freezmo-v82. Tests: chat suite pins the new geometry (12px
gutter, 24px corners, even wash on all four panels, send-vs-attach shape,
chips/input radii); mobile pins the phone chrome and the Aero toggle; bubbles
pins the floats layer + its reduced-motion opt-out.
This commit is contained in:
Martin Dvořák 2026-10-14 21:32:50 +00:00
commit 786d6cf1ae
21 changed files with 562 additions and 290 deletions

View file

@ -75,6 +75,26 @@ async function orbAt(page, phase) {
check("orbs: own duration per pass (30-70s, 5 distinct)", orbs.every((orb) => orb.duration >= 30 && orb.duration <= 70) && new Set(orbs.map((orb) => orb.duration)).size === 5, orbs.map((orb) => orb.duration).join(","));
check("orbs: reduced opacity token applied", orbs.every((orb) => orb.opacity > 0.3 && orb.opacity < 0.45), String(orbs[0]?.opacity));
/* The floats layer: orbs + bubbles live in one masked wrapper that fades
them out before the chrome bands, so none of them is sliced by a panel
edge (see .ice-floats in globals.css). */
const floats = await page.evaluate(() => {
const layer = document.querySelector(".ice-floats");
const style = layer ? getComputedStyle(layer) : null;
return {
children: layer ? layer.children.length : 0,
orbs: document.querySelectorAll(".ice-floats .ice-orb").length,
bubbles: document.querySelectorAll(".ice-floats .ice-bubble").length,
mask: style ? style.maskImage || style.webkitMaskImage : "",
};
});
check(
"floats: orbs and bubbles ride one masked layer",
floats.children === 23 && floats.orbs === 5 && floats.bubbles === 18,
JSON.stringify(floats),
);
check("floats: the chrome bands fade the layer out", floats.mask.includes("gradient"), floats.mask.slice(0, 60));
const keyframes = await page.evaluate(() => {
for (const sheet of document.styleSheets) {
try {
@ -153,8 +173,17 @@ async function orbAt(page, phase) {
const orb = document.querySelector(".ice-orb-1");
const style = getComputedStyle(orb);
const rect = orb.getBoundingClientRect();
return { animation: style.animationName, willChange: style.willChange, opacity: Number(style.opacity), top: rect.top, left: rect.left };
const floats = getComputedStyle(document.querySelector(".ice-floats"));
return {
animation: style.animationName,
willChange: style.willChange,
opacity: Number(style.opacity),
top: rect.top,
left: rect.left,
mask: floats.maskImage || floats.webkitMaskImage,
};
});
check("reduced motion: the floats mask is off (parked orbs keep their spots)", state.mask === "none", state.mask);
check("reduced motion: rise removed, layer released", state.animation === "none" && state.willChange === "auto", JSON.stringify(state));
check("reduced motion: orb visible and faded in at a static spot", state.opacity > 0.08 && state.top > 0 && state.top < 900, JSON.stringify(state));
await page.waitForTimeout(1100);