Ask pill joins the glass; Aero hovers light the pane, not move it

The Ask button (and the two phone icon pills sharing its styling) was a
hand-styled bg-[var(--surface-strong)] pill that never picked up the
frosted recipe - it showed the sharp wallpaper through a flat tint while
everything around it showed the blurred one. It carries a glass-pill
class and is part of the same recipe now.

Hover feedback: a transform or filter on a pane with a viewport-fixed
backdrop re-anchors the glass, so the deck card's hover lift made the
card flash a wrong crop of the wallpaper (and filter: brightness() on
the buttons, plus the mobile :active scale foundation, would have done
the same). Aero's feedback no longer moves or filters the glass: the
pane lights up with an inner glow, a white rim and a deeper shadow over
170ms, and the pressed state shades inward.

Contrast: 0 below-minimum findings, 0 axe violations. Version 0.3.3,
SW -> freezmo-v64.
This commit is contained in:
Martin Dvořák 2026-10-13 12:11:23 +00:00
commit 70ddc5e753
5 changed files with 75 additions and 5 deletions

View file

@ -1411,6 +1411,18 @@ Things I chose while building, with the reasoning:
pre-blurred wallpaper — the scene is clearly visible through the glass, and the
pixel-contrast scan still passes.
68. **The Ask pill joins the glass, and Aero's hovers light the pane instead of moving it.** Two
leftovers from the previous pass. The Ask button (and the two phone icon pills that share its
styling) was a hand-styled `bg-[var(--surface-strong)]` pill that never picked up the frosted
recipe, so it showed the sharp wallpaper through a flat tint while everything around it showed
the blurred one — it carries a `glass-pill` class now and is part of the same recipe. And the
hover feedback: a `transform` or `filter` on a pane with a viewport-fixed backdrop re-anchors
the glass, so the deck card's hover lift made the card flash a wrong crop of the wallpaper
(and the buttons' `filter: brightness()` would have done the same, and the mobile
`:active { transform: scale(0.96) }` foundation made panes jump while pressed). Aero's
feedback never moves or filters the glass now: the pane lights up with an inner glow, a white
rim and a deeper shadow, transitioned over 170ms, and the pressed state shades inward.
67. **The Aero wallpaper URL is cache-busted, and every surface is one glass.** Two fixes from
one report. The background "didn't change" because the service worker's asset cache kept
serving the previous wallpaper bytes (stale-while-revalidate returns the cached copy first,

View file

@ -412,6 +412,7 @@
.aero .nav-row[aria-current="page"],
.aero .nav-row[data-active="true"],
.aero .glass-chip,
.aero .glass-pill,
.aero .btn-secondary,
.aero .btn-primary,
.aero .grad-ice,
@ -443,6 +444,63 @@
box-shadow: 0 -14px 34px -22px rgb(2 62 110 / 0.5);
}
/* Interactive glass feedback: a soft inner glow and a brighter rim on hover,
an inner shade while pressed. Deliberately no transform and no filter — on
a pane with a viewport-fixed backdrop either one re-anchors the glass and
it flashes a wrong crop of the wallpaper (the old hover lift did exactly
that to the deck cards). */
.aero .nav-action,
.aero .nav-row,
.aero .glass-chip,
.aero .glass-pill,
.aero .btn-secondary,
.aero .btn-primary,
.aero .segmented > button,
.aero .segmented > a,
.aero .card-hover {
transition: box-shadow 170ms ease, border-color 170ms ease, background-color 170ms ease, color 170ms ease;
}
@media (hover: hover) and (pointer: fine) {
.aero .nav-action:hover,
.aero .nav-row:hover,
.aero .glass-chip:hover,
.aero .glass-pill:hover,
.aero .btn-secondary:hover,
.aero .btn-primary:hover,
.aero .segmented > button:hover,
.aero .segmented > a:hover,
.aero .card-hover:hover {
border-color: rgb(255 255 255 / 1);
background-color: rgb(255 255 255 / 0.34);
filter: none;
box-shadow:
0 18px 38px -20px rgb(2 62 110 / 0.6),
inset 0 1px 0 rgb(255 255 255 / 1),
inset 0 0 30px rgb(255 255 255 / 0.55);
}
.aero .card-hover:hover {
transform: none;
}
}
.aero .card-hover:active,
.aero .card-hover:hover,
.aero .btn-primary:active,
.aero .segmented > button:active,
.aero .segmented > a:active,
.aero .nav-action:active,
.aero .glass-pill:active,
.aero .btn-secondary:active {
transform: none;
filter: none;
}
.aero .card-hover:active,
.aero .btn-primary:active,
.aero .nav-action:active,
.aero .glass-pill:active,
.aero .btn-secondary:active {
box-shadow: inset 0 2px 8px rgb(2 62 110 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.8);
}
/* The phone tab bar carries its labels right at the bottom of the water, so
it keeps a little more veil than the cards. */
.aero .tabbar {

View file

@ -203,7 +203,7 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
{/* Desktop keeps Import above the tabs; phones get a small icon
button in the tab row instead. */}
<div className="hidden flex-wrap items-center gap-2 sm:flex">
<Link href="/chat" className="touch-target inline-flex items-center gap-2 rounded-2xl border border-[var(--glass-border)] bg-[var(--surface-strong)] px-4 py-2.5 text-sm font-semibold text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]">
<Link href="/chat" className="glass-pill touch-target inline-flex items-center gap-2 rounded-2xl border border-[var(--glass-border)] bg-[var(--surface-strong)] px-4 py-2.5 text-sm font-semibold text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]">
<ChatIcon className="size-4" />
{t("chat.ask")}
</Link>
@ -254,7 +254,7 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
href="/chat"
aria-label={t("chat.ask")}
title={t("chat.ask")}
className="touch-target grid size-11 shrink-0 place-items-center rounded-2xl border border-[var(--glass-border)] bg-[var(--surface-strong)] text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)] sm:hidden"
className="glass-pill touch-target grid size-11 shrink-0 place-items-center rounded-2xl border border-[var(--glass-border)] bg-[var(--surface-strong)] text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)] sm:hidden"
>
<ChatIcon className="size-5" />
</Link>
@ -263,7 +263,7 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
onClick={() => setSheetOpen(true)}
aria-label={t("library.moreActions")}
title={t("library.moreActions")}
className="touch-target grid size-11 shrink-0 place-items-center rounded-2xl border border-[var(--glass-border)] bg-[var(--surface-strong)] text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)] sm:hidden"
className="glass-pill touch-target grid size-11 shrink-0 place-items-center rounded-2xl border border-[var(--glass-border)] bg-[var(--surface-strong)] text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)] sm:hidden"
>
<EllipsisIcon className="size-5" />
</button>

View file

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

View file

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