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 pre-blurred wallpaper — the scene is clearly visible through the glass, and the
pixel-contrast scan still passes. 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 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 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, 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[aria-current="page"],
.aero .nav-row[data-active="true"], .aero .nav-row[data-active="true"],
.aero .glass-chip, .aero .glass-chip,
.aero .glass-pill,
.aero .btn-secondary, .aero .btn-secondary,
.aero .btn-primary, .aero .btn-primary,
.aero .grad-ice, .aero .grad-ice,
@ -443,6 +444,63 @@
box-shadow: 0 -14px 34px -22px rgb(2 62 110 / 0.5); 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 /* The phone tab bar carries its labels right at the bottom of the water, so
it keeps a little more veil than the cards. */ it keeps a little more veil than the cards. */
.aero .tabbar { .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 {/* Desktop keeps Import above the tabs; phones get a small icon
button in the tab row instead. */} button in the tab row instead. */}
<div className="hidden flex-wrap items-center gap-2 sm:flex"> <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" /> <ChatIcon className="size-4" />
{t("chat.ask")} {t("chat.ask")}
</Link> </Link>
@ -254,7 +254,7 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
href="/chat" href="/chat"
aria-label={t("chat.ask")} aria-label={t("chat.ask")}
title={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" /> <ChatIcon className="size-5" />
</Link> </Link>
@ -263,7 +263,7 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
onClick={() => setSheetOpen(true)} onClick={() => setSheetOpen(true)}
aria-label={t("library.moreActions")} aria-label={t("library.moreActions")}
title={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" /> <EllipsisIcon className="size-5" />
</button> </button>

View file

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

View file

@ -9,7 +9,7 @@
* *
* Bumping CACHE_VERSION is how a new deploy invalidates old assets. * 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 SHELL_CACHE = `${CACHE_VERSION}-shell`;
const ASSET_CACHE = `${CACHE_VERSION}-assets`; const ASSET_CACHE = `${CACHE_VERSION}-assets`;