More shinier bubbles; the chat's top edge fades instead of clipping
Bubbles: ten -> eighteen (interleaved, 18px to 96px, 10-23s runs), with a tighter specular hotspot, a reflected-light glint low on the sphere, a 1px glass edge and a brighter inner rim on both the bubbles and the drifting orbs. Bodies stay translucent with capped opacity, so wallpaper text stays readable; bubble-perf shows no added frame cost. Chat: the conversation scrolls up to the glass header, and a line clipped exactly at that edge read as a rendering bug over the busy wallpaper - the scroller now carries a 14px top mask in Aero, shorter than the column's 16px top padding so a fresh conversation shows no dimming. Contrast 0 findings, axe clean, chat 101/101, mobile 123/123 (bubble count pinned to 18), regression 44/44, i18n 13/13, native 33/33. Version 0.3.7, SW -> freezmo-v66.
This commit is contained in:
parent
5eec310e16
commit
4ba80a9bf2
7 changed files with 74 additions and 20 deletions
13
README.md
13
README.md
|
|
@ -1411,6 +1411,19 @@ 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.
|
||||||
|
|
||||||
|
72. **More, shinier bubbles.** Aero's rising bubbles went from ten to eighteen (the extra eight
|
||||||
|
interleaved between the others, 18px to 96px, 10-23s runs) with a tighter specular hotspot, a
|
||||||
|
second reflected-light glint low on the sphere, a 1px glass edge and a brighter inner rim on
|
||||||
|
both the bubbles and the drifting orbs. The bodies stay translucent and their opacities are
|
||||||
|
capped (0.75-0.9), so text sitting on the wallpaper stays readable, and the bubble-perf
|
||||||
|
diagnostic shows no added frame cost (18 bubbles, same p95 as ten).
|
||||||
|
|
||||||
|
71. **The chat's top edge fades instead of hard-clipping.** The conversation scrolls right up to
|
||||||
|
the glass header, and over the busy wallpaper a line of text clipped exactly at that edge read
|
||||||
|
as a rendering bug ("the chat looks cut off"). The scroller now carries a 14px top mask in
|
||||||
|
Aero — shorter than the column's 16px top padding, so a fresh conversation at the top shows no
|
||||||
|
dimming at all.
|
||||||
|
|
||||||
70. **Chat answers are bubbles in Aero too.** The assistant answers are deliberately bubble-less
|
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
|
(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.
|
parts of the wallpaper, so the longest text in the app ended up sitting straight on the photo.
|
||||||
|
|
|
||||||
|
|
@ -276,14 +276,21 @@
|
||||||
|
|
||||||
/* The orbs become extra bubbles over the photo. */
|
/* The orbs become extra bubbles over the photo. */
|
||||||
.aero .ice-orb {
|
.aero .ice-orb {
|
||||||
background: radial-gradient(
|
background:
|
||||||
|
radial-gradient(circle at 30% 24%, rgb(255 255 255 / 0.95) 0%, rgb(255 255 255 / 0) 16%),
|
||||||
|
radial-gradient(circle at 72% 80%, rgb(255 255 255 / 0.4) 0%, rgb(255 255 255 / 0) 20%),
|
||||||
|
radial-gradient(
|
||||||
circle at 32% 28%,
|
circle at 32% 28%,
|
||||||
rgb(255 255 255 / 0.95) 0%,
|
rgb(255 255 255 / 0.9) 0%,
|
||||||
rgb(226 248 255 / 0.4) 22%,
|
rgb(226 248 255 / 0.4) 22%,
|
||||||
rgb(125 211 252 / 0.14) 56%,
|
rgb(125 211 252 / 0.14) 56%,
|
||||||
rgb(56 189 248 / 0.38) 100%
|
rgb(56 189 248 / 0.38) 100%
|
||||||
);
|
);
|
||||||
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);
|
box-shadow:
|
||||||
|
inset -8px -12px 20px -10px rgb(3 74 124 / 0.5),
|
||||||
|
inset 8px 10px 16px -10px rgb(255 255 255 / 1),
|
||||||
|
inset 0 0 0 1px rgb(255 255 255 / 0.35),
|
||||||
|
0 12px 26px -16px rgb(3 74 124 / 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The Aero gloss for the floating surfaces (sheets, dropdowns and the sticky
|
/* The Aero gloss for the floating surfaces (sheets, dropdowns and the sticky
|
||||||
|
|
@ -313,14 +320,22 @@
|
||||||
bottom: -14vh;
|
bottom: -14vh;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
background: radial-gradient(
|
background:
|
||||||
|
radial-gradient(circle at 30% 24%, rgb(255 255 255 / 0.95) 0%, rgb(255 255 255 / 0) 16%),
|
||||||
|
radial-gradient(circle at 72% 80%, rgb(255 255 255 / 0.4) 0%, rgb(255 255 255 / 0) 20%),
|
||||||
|
radial-gradient(
|
||||||
circle at 32% 28%,
|
circle at 32% 28%,
|
||||||
rgb(255 255 255 / 0.95) 0%,
|
rgb(255 255 255 / 0.9) 0%,
|
||||||
rgb(226 248 255 / 0.38) 24%,
|
rgb(226 248 255 / 0.38) 24%,
|
||||||
rgb(125 211 252 / 0.12) 58%,
|
rgb(125 211 252 / 0.12) 58%,
|
||||||
rgb(56 189 248 / 0.34) 100%
|
rgb(56 189 248 / 0.34) 100%
|
||||||
);
|
);
|
||||||
box-shadow: inset -6px -10px 16px -8px rgb(3 74 124 / 0.45), inset 6px 8px 14px -8px rgb(255 255 255 / 0.9), 0 10px 22px -14px rgb(3 74 124 / 0.5);
|
box-shadow:
|
||||||
|
inset -6px -10px 16px -8px rgb(3 74 124 / 0.5),
|
||||||
|
inset 6px 8px 14px -8px rgb(255 255 255 / 1),
|
||||||
|
inset 0 0 0 1px rgb(255 255 255 / 0.35),
|
||||||
|
0 10px 22px -14px rgb(3 74 124 / 0.5);
|
||||||
|
border: 1px solid rgb(255 255 255 / 0.25);
|
||||||
animation: aero-bubble-rise var(--bubble-duration, 20s) linear infinite;
|
animation: aero-bubble-rise var(--bubble-duration, 20s) linear infinite;
|
||||||
animation-delay: var(--bubble-delay, 0s);
|
animation-delay: var(--bubble-delay, 0s);
|
||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
|
|
@ -347,6 +362,14 @@
|
||||||
.aero .ice-bubble-8 { width: 46px; height: 46px; left: 78%; --bubble-duration: 16s; --bubble-delay: -13s; --bubble-sway: 4vw; }
|
.aero .ice-bubble-8 { width: 46px; height: 46px; left: 78%; --bubble-duration: 16s; --bubble-delay: -13s; --bubble-sway: 4vw; }
|
||||||
.aero .ice-bubble-9 { width: 24px; height: 24px; left: 87%; --bubble-duration: 14s; --bubble-delay: -6s; --bubble-sway: -3vw; }
|
.aero .ice-bubble-9 { width: 24px; height: 24px; left: 87%; --bubble-duration: 14s; --bubble-delay: -6s; --bubble-sway: -3vw; }
|
||||||
.aero .ice-bubble-10 { width: 120px; height: 120px; left: 92%; --bubble-duration: 27s; --bubble-delay: -20s; --bubble-sway: -5vw; --bubble-opacity: 0.75; }
|
.aero .ice-bubble-10 { width: 120px; height: 120px; left: 92%; --bubble-duration: 27s; --bubble-delay: -20s; --bubble-sway: -5vw; --bubble-opacity: 0.75; }
|
||||||
|
.aero .ice-bubble-11 { width: 18px; height: 18px; left: 4%; --bubble-duration: 12s; --bubble-delay: -3s; --bubble-sway: 3vw; }
|
||||||
|
.aero .ice-bubble-12 { width: 58px; height: 58px; left: 12%; --bubble-duration: 18s; --bubble-delay: -11s; --bubble-sway: -3vw; }
|
||||||
|
.aero .ice-bubble-13 { width: 30px; height: 30px; left: 21%; --bubble-duration: 15s; --bubble-delay: -5s; --bubble-sway: -2vw; }
|
||||||
|
.aero .ice-bubble-14 { width: 96px; height: 96px; left: 34%; --bubble-duration: 23s; --bubble-delay: -19s; --bubble-sway: 4vw; --bubble-opacity: 0.8; }
|
||||||
|
.aero .ice-bubble-15 { width: 22px; height: 22px; left: 42%; --bubble-duration: 10s; --bubble-delay: -8s; --bubble-sway: -3vw; }
|
||||||
|
.aero .ice-bubble-16 { width: 44px; height: 44px; left: 54%; --bubble-duration: 16s; --bubble-delay: -2s; --bubble-sway: 2vw; }
|
||||||
|
.aero .ice-bubble-17 { width: 62px; height: 62px; left: 62%; --bubble-duration: 21s; --bubble-delay: -14s; --bubble-sway: -4vw; }
|
||||||
|
.aero .ice-bubble-18 { width: 26px; height: 26px; left: 74%; --bubble-duration: 13s; --bubble-delay: -6s; --bubble-sway: 3vw; }
|
||||||
|
|
||||||
/* Helper lines that sit straight on the wallpaper (the keyboard tip, the AI
|
/* Helper lines that sit straight on the wallpaper (the keyboard tip, the AI
|
||||||
note) need their own surface in Aero: dark text on the grass measured
|
note) need their own surface in Aero: dark text on the grass measured
|
||||||
|
|
@ -516,6 +539,16 @@
|
||||||
box-shadow: inset 0 2px 8px rgb(2 62 110 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.8);
|
box-shadow: inset 0 2px 8px rgb(2 62 110 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The conversation scrolls right up to the glass header; a hard-clipped line
|
||||||
|
of text at that edge reads as a rendering bug over the busy wallpaper, so
|
||||||
|
the top of the scroll area fades out instead (the mask is fixed to the
|
||||||
|
element, not the content — and stays inside the column's 16px top padding,
|
||||||
|
so at the very top nothing is dimmed). */
|
||||||
|
.aero .chat-scroll {
|
||||||
|
mask-image: linear-gradient(to bottom, transparent 0, black 14px);
|
||||||
|
-webkit-mask-image: linear-gradient(to bottom, transparent 0, black 14px);
|
||||||
|
}
|
||||||
|
|
||||||
/* Chat: the longest text in the app sits straight on the wallpaper. The
|
/* 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
|
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
|
user bubble's thin tint washes out over the bright parts of the scene — in
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ export function IceBackdrop() {
|
||||||
<span className="ice-bubble ice-bubble-8" />
|
<span className="ice-bubble ice-bubble-8" />
|
||||||
<span className="ice-bubble ice-bubble-9" />
|
<span className="ice-bubble ice-bubble-9" />
|
||||||
<span className="ice-bubble ice-bubble-10" />
|
<span className="ice-bubble ice-bubble-10" />
|
||||||
|
<span className="ice-bubble ice-bubble-11" />
|
||||||
|
<span className="ice-bubble ice-bubble-12" />
|
||||||
|
<span className="ice-bubble ice-bubble-13" />
|
||||||
|
<span className="ice-bubble ice-bubble-14" />
|
||||||
|
<span className="ice-bubble ice-bubble-15" />
|
||||||
|
<span className="ice-bubble ice-bubble-16" />
|
||||||
|
<span className="ice-bubble ice-bubble-17" />
|
||||||
|
<span className="ice-bubble ice-bubble-18" />
|
||||||
<span className="ice-grain" />
|
<span className="ice-grain" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -569,7 +569,7 @@ export function ChatScreen() {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="relative min-h-0 flex-1">
|
<div className="relative min-h-0 flex-1">
|
||||||
<div ref={scrollRef} onScroll={onScroll} className="h-full overflow-y-auto overscroll-contain">
|
<div ref={scrollRef} onScroll={onScroll} className="chat-scroll h-full overflow-y-auto overscroll-contain">
|
||||||
<div className="mx-auto w-full max-w-3xl space-y-4 px-4 py-4">
|
<div className="mx-auto w-full max-w-3xl space-y-4 px-4 py-4">
|
||||||
{messages.length === 0 ? (
|
{messages.length === 0 ? (
|
||||||
<div className="space-y-5 py-6">
|
<div className="space-y-5 py-6">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "freezmo",
|
"name": "freezmo",
|
||||||
"version": "0.3.5",
|
"version": "0.3.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -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-v65";
|
const CACHE_VERSION = "freezmo-v66";
|
||||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||||
const ASSET_CACHE = `${CACHE_VERSION}-assets`;
|
const ASSET_CACHE = `${CACHE_VERSION}-assets`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ const AXE = axeSource();
|
||||||
);
|
);
|
||||||
check(
|
check(
|
||||||
"aero: bubbles rise",
|
"aero: bubbles rise",
|
||||||
(await page.locator(".ice-bubble").count()) === 10 &&
|
(await page.locator(".ice-bubble").count()) === 18 &&
|
||||||
(await page.locator(".ice-bubble").first().evaluate((el) => getComputedStyle(el).animationName)) ===
|
(await page.locator(".ice-bubble").first().evaluate((el) => getComputedStyle(el).animationName)) ===
|
||||||
"aero-bubble-rise",
|
"aero-bubble-rise",
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue