From 5eec310e1683df941d2f8e675e392a60835b1921 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Tue, 13 Oct 2026 12:43:46 +0000 Subject: [PATCH] Thinner glass everywhere, chat answers as bubbles in Aero Two asks. "Slightly more transparent": the veils drop another notch (recipe 0.15 -> 0.12, tokens ~0.03 less, tab bar / wizard bar / gloss likewise). "Some buttons aren't themed": the recipe now catches the flows' hand-rolled frost by attribute selectors - the study activities' bg-white/xx chips (multiple choice, matching, ordering, fill blank), the exam rows that used bg-[var(--surface)] (an opaque white in Aero), and everything on the surface tokens (chat chips, homework chips, inputs, dropzones). Inside sheets the same shapes stay flat; the dropzones lost their press-scale (a transform re-anchors the fixed backdrop) and shade inward instead. And the chat: the assistant answers are bubble-less by design and the user bubble's thin tint washed out over the bright wallpaper, so the longest text in the app sat straight on the photo. In Aero both roles get a real pane now - chat-answer wraps the markdown in a bubble, the user turn carries chat-bubble, with a stronger veil than the shared glass for comfortable reading. Light and dark keep the bubble-less assistant design (styled only under .aero). Contrast: 0 below-minimum findings, 0 axe violations. Chat 101/101, mobile 123/123, regression 44/44, i18n 13/13, native 33/33. Version 0.3.5, SW -> freezmo-v65. --- README.md | 20 +++++++ app/globals.css | 97 ++++++++++++++++++++++++++-------- components/chat/MessageRow.tsx | 6 ++- package.json | 2 +- public/sw.js | 2 +- 5 files changed, 102 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 22e31e0..81e2d36 100644 --- a/README.md +++ b/README.md @@ -1411,6 +1411,26 @@ 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. +70. **Chat answers are bubbles in Aero too.** The assistant answers are deliberately bubble-less + (full-width text on the flat themes) and the user bubble's thin tint washed out over the bright + parts of the wallpaper, so the longest text in the app ended up sitting straight on the photo. + In Aero both get a real pane: `.chat-answer` wraps the markdown in a bubble (plus a + `chat-bubble` class on the user turn), with a stronger veil than the shared glass so a wall of + text still reads comfortably over the same blurred scene, and the bright rim. Light and dark + keep the bubble-less assistant design — these classes are styled only under `.aero`. + +69. **The glass got thinner and reaches the whole app.** Two asks in one pass: "slightly more + transparent" and "some buttons aren't themed" while doing decks/homework. The veils drop + another notch (recipe 0.15 -> 0.12, the tokens ~0.03 less, the tab bar / wizard bar / gloss + likewise), and the recipe now catches the flows' hand-rolled frost: the study activities' + `bg-white/xx` chips (multiple-choice options, matching terms, ordering cards, fill-in-blank + chips), the exam deck rows and pickers that used `bg-[var(--surface)]` — an *opaque* white in + Aero — and everything using the surface tokens (chat action chips, homework chips, inputs, + dropzones); attribute selectors match them, so no component needed a new class. The same shapes + inside sheets stay flat (the exclusion list covers the new selectors — there the sheet itself is + the glass), and the dropzones lost their press-scale (a transform on a fixed backdrop + re-anchors it) and shade inward instead. `tests/contrast.mjs`: 0 findings. + 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 diff --git a/app/globals.css b/app/globals.css index 4565aa3..d9730a1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -295,10 +295,10 @@ .aero .glass { background-image: linear-gradient( 180deg, - rgb(255 255 255 / 0.32) 0%, - rgb(255 255 255 / 0.1) 46%, + rgb(255 255 255 / 0.28) 0%, + rgb(255 255 255 / 0.08) 46%, rgb(255 255 255 / 0.03) 47%, - rgb(255 255 255 / 0.1) 100% + rgb(255 255 255 / 0.08) 100% ); } @@ -386,12 +386,12 @@ stay thin too. Contrast is pixel-scanned in all three themes, so the tints cannot drop below what the text needs. */ .aero { - --glass-bg: rgb(228 246 255 / 0.26); - --glass-solid-bg: rgb(232 248 255 / 0.34); - --glass-sheet-bg: rgb(232 248 255 / 0.68); - --surface-strong: rgb(255 255 255 / 0.26); - --surface-sunken: rgb(255 255 255 / 0.2); - --nav-active-bg: rgb(255 255 255 / 0.3); + --glass-bg: rgb(228 246 255 / 0.22); + --glass-solid-bg: rgb(232 248 255 / 0.3); + --glass-sheet-bg: rgb(232 248 255 / 0.64); + --surface-strong: rgb(255 255 255 / 0.23); + --surface-sunken: rgb(255 255 255 / 0.17); + --nav-active-bg: rgb(255 255 255 / 0.27); } /* Liquid glass — one recipe for every surface, so the rail, the cards, the @@ -418,16 +418,29 @@ .aero .grad-ice, .aero .segmented, .aero .segmented > button[aria-pressed="true"], -.aero .segmented > a[aria-current="page"] { +.aero .segmented > a[aria-current="page"], +/* Hand-rolled frost fills in the study, exam, chat and homework flows: + white/xx utilities and the surface tokens — without this they render as + flat white chips next to the glass. */ +.aero [class*="bg-white/35"], +.aero [class*="bg-white/40"], +.aero [class*="bg-white/45"], +.aero [class*="bg-white/50"], +.aero [class*="bg-white/55"], +.aero [class*="bg-white/60"], +.aero [class*="bg-white/70"], +.aero [class*="bg-[var(--surface)]"], +.aero [class*="bg-[var(--surface-strong)]"], +.aero [class*="bg-frost-50"] { background-image: linear-gradient( 180deg, - rgb(255 255 255 / 0.16) 0%, - rgb(255 255 255 / 0.05) 46%, - rgb(255 255 255 / 0.04) 53%, - rgb(255 255 255 / 0.09) 100% + rgb(255 255 255 / 0.13) 0%, + rgb(255 255 255 / 0.04) 46%, + rgb(255 255 255 / 0.03) 53%, + rgb(255 255 255 / 0.07) 100% ), - linear-gradient(rgb(255 255 255 / 0.15), rgb(255 255 255 / 0.15)), + linear-gradient(rgb(255 255 255 / 0.12), rgb(255 255 255 / 0.12)), url("/aero/background-blur.webp?v=3"); background-size: 100% 100%, 100% 100%, cover; background-position: 0 0, 0 0, center 42%; @@ -439,7 +452,7 @@ blue, which read as a heavy slab over the photo — give it the same thin glass as everything else (it already has a real backdrop blur). */ .aero [data-wizard-bar] { - background: rgb(255 255 255 / 0.24); + background: rgb(255 255 255 / 0.2); border-top-color: rgb(255 255 255 / 0.6); box-shadow: 0 -14px 34px -22px rgb(2 62 110 / 0.5); } @@ -484,6 +497,7 @@ } .aero .card-hover:active, .aero .card-hover:hover, +.aero .press:active, .aero .btn-primary:active, .aero .segmented > button:active, .aero .segmented > a:active, @@ -494,6 +508,7 @@ filter: none; } .aero .card-hover:active, +.aero .press:active, .aero .btn-primary:active, .aero .nav-action:active, .aero .glass-pill:active, @@ -501,16 +516,46 @@ box-shadow: inset 0 2px 8px rgb(2 62 110 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.8); } +/* Chat: the longest text in the app sits straight on the wallpaper. The + assistant answers are deliberately bubble-less in the flat themes, and the + user bubble's thin tint washes out over the bright parts of the scene — in + Aero both get a real pane (a stronger veil than the shared glass, so an + answer reads as its own bubble). */ +.aero .chat-bubble, +.aero .chat-answer { + border-color: rgb(255 255 255 / 0.7); + background-image: + linear-gradient( + 180deg, + rgb(255 255 255 / 0.6) 0%, + rgb(255 255 255 / 0.44) 46%, + rgb(255 255 255 / 0.4) 53%, + rgb(255 255 255 / 0.52) 100% + ), + url("/aero/background-blur.webp?v=3"); + background-size: 100% 100%, cover; + background-position: 0 0, center 42%; + background-attachment: scroll, fixed; + background-repeat: no-repeat, no-repeat; + box-shadow: 0 16px 34px -22px rgb(2 62 110 / 0.45), inset 0 1px 0 rgb(255 255 255 / 0.9); +} +.aero .chat-answer { + border: 1px solid rgb(255 255 255 / 0.7); + border-radius: 1.25rem; + border-bottom-left-radius: 0.6rem; + padding: 0.7rem 0.95rem; +} + /* 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 { background-image: linear-gradient( 180deg, - rgb(255 255 255 / 0.26) 0%, - rgb(255 255 255 / 0.12) 46%, - rgb(255 255 255 / 0.1) 53%, - rgb(255 255 255 / 0.16) 100% + rgb(255 255 255 / 0.22) 0%, + rgb(255 255 255 / 0.1) 46%, + rgb(255 255 255 / 0.08) 53%, + rgb(255 255 255 / 0.13) 100% ), url("/aero/background-blur.webp?v=3"); background-size: 100% 100%, cover; @@ -533,7 +578,17 @@ .aero .glass-sheet .segmented > a[aria-current="page"], .aero .glass-sheet .btn-secondary, .aero .glass-sheet .btn-primary, -.aero .glass-sheet .grad-ice { +.aero .glass-sheet .grad-ice, +.aero .glass-sheet [class*="bg-white/35"], +.aero .glass-sheet [class*="bg-white/40"], +.aero .glass-sheet [class*="bg-white/45"], +.aero .glass-sheet [class*="bg-white/50"], +.aero .glass-sheet [class*="bg-white/55"], +.aero .glass-sheet [class*="bg-white/60"], +.aero .glass-sheet [class*="bg-white/70"], +.aero .glass-sheet [class*="bg-[var(--surface)]"], +.aero .glass-sheet [class*="bg-[var(--surface-strong)]"], +.aero .glass-sheet [class*="bg-frost-50"] { background-image: none; } diff --git a/components/chat/MessageRow.tsx b/components/chat/MessageRow.tsx index c466060..80ac578 100644 --- a/components/chat/MessageRow.tsx +++ b/components/chat/MessageRow.tsx @@ -43,7 +43,7 @@ export function MessageRow({ if (message.role === "user") { return (
-
+

{message.content}

{message.attachments && message.attachments.length > 0 ? (

@@ -69,7 +69,9 @@ export function MessageRow({

{message.thinking ? : null} {message.content ? ( - +
+ +
) : streaming ? ( {[0, 1, 2].map((dot) => ( diff --git a/package.json b/package.json index 9e5145c..29e162a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freezmo", - "version": "0.3.3", + "version": "0.3.5", "private": true, "license": "GPL-3.0-only", "scripts": { diff --git a/public/sw.js b/public/sw.js index 262eba6..815ca68 100644 --- a/public/sw.js +++ b/public/sw.js @@ -9,7 +9,7 @@ * * Bumping CACHE_VERSION is how a new deploy invalidates old assets. */ -const CACHE_VERSION = "freezmo-v64"; +const CACHE_VERSION = "freezmo-v65"; const SHELL_CACHE = `${CACHE_VERSION}-shell`; const ASSET_CACHE = `${CACHE_VERSION}-assets`;