Aero uses the real wallpaper + liquid glass buttons
The wallpaper is the one from the mockup: "The S Aesthetic That You Definitely Recognize Frutiger Aero" via WallpaperSafari (https://wallpapersafari.com/w/IlWuz7) — the URL in the request pointed at a different (teal abstract) wallpaper, this is the grass/sky/water one that matches the screenshot. Re-encoded to a 237 kB WebP in public/aero/, used as the scene in aero only (light/dark never download it), and credited in Settings next to the theme picker. The drawn CSS scene (rays, reeds, skyline, caustics) is off over the photo; the ten rising bubbles stay, and the orbs became extra bubbles. Buttons and segments are liquid glass now — translucent, glossed top half, bright rim, backdrop blur — instead of the solid aqua fill. Helper lines that sit straight on the wallpaper (the keyboard tip, the AI note) get a small glass chip: dark text on grass measured 2.5:1. Also carries: the underwater scene + bubbles work, the floating cookie notice, the Czech title (Tvůj mrazák) and the review-suite fix that made the last deploy fail its gate. Contrast: 0 below-minimum findings, 0 axe violations across all three themes. Version 0.2.2, SW -> freezmo-v56.
This commit is contained in:
parent
67e2cefcfe
commit
f1c4a89adc
10 changed files with 83 additions and 78 deletions
22
README.md
22
README.md
|
|
@ -1399,17 +1399,17 @@ Things I chose while building, with the reasoning:
|
|||
monthly-limit body, a two-device sync round-trip, and the generation guards below).
|
||||
|
||||
63. **Frutiger Aero is the third theme.** Settings → Theme offers Light, Dark and Frutiger Aero.
|
||||
Aero is not a dark/light variant — it replaces the palette (a sky gradient with a strip of
|
||||
grass, glossy glass with the classic horizon gloss, aqua buttons) and the scene (the glacier
|
||||
blobs become clouds, the orbs soap bubbles) — so the light/dark toggles (header, rail, chat
|
||||
header) hide while it is on and the picker says so. The pre-paint script applies the class
|
||||
before the first paint and the PWA status bar follows it (#2fa7e6). The scene is an underwater
|
||||
one: sun shafts, caustic patches, reeds on the left, a city silhouette on the right and ten
|
||||
rising bubbles (`.ice-bubble` — `display: none` outside aero, so they cost nothing in the other
|
||||
themes, and they sit still under reduced motion). Its glass is a pale blue at 0.8 opacity:
|
||||
transparent enough to read as glass, opaque enough that the dark navy text clears 4.5:1 over the
|
||||
water — and the phone tab bar mixes the sheet glass rather than the page, which in aero *is* the
|
||||
water. Covered by the axe and pixel-contrast scans in all three themes.
|
||||
Aero is not a dark/light variant — it replaces the palette and the scene: the wallpaper is
|
||||
*“The S Aesthetic That You Definitely Recognize Frutiger Aero”* via WallpaperSafari
|
||||
(<https://wallpapersafari.com/w/IlWuz7>, re-encoded to a 237 kB WebP in `public/aero/` and
|
||||
credited in Settings), with ten drawn bubbles rising over it (`.ice-bubble` — `display: none`
|
||||
outside aero so they cost nothing elsewhere, still under reduced motion). Buttons and segments
|
||||
are **liquid glass** (translucent, glossed top half, bright rim, backdrop blur), the cards are
|
||||
pale blue glass, and the helper lines that sit straight on the wallpaper (the keyboard tip, the
|
||||
AI note) get their own small glass chip — dark text on grass measured 2.5:1. The light/dark
|
||||
toggles (header, rail, chat header) hide while it is on and the picker says why; the PWA status
|
||||
bar follows the wallpaper's sky (#2fa7e6). Covered by the axe and pixel-contrast scans in all
|
||||
three themes.
|
||||
|
||||
62. **Accessibility: axe is clean everywhere, and contrast is measured on real pixels.** axe cannot
|
||||
judge text on blurred glass — it reports those nodes as "incomplete" — so `tests/contrast.mjs`
|
||||
|
|
|
|||
110
app/globals.css
110
app/globals.css
|
|
@ -257,27 +257,25 @@
|
|||
--ice-grain-opacity: 0.02;
|
||||
}
|
||||
|
||||
/* The water: sun shafts through a bright surface, deepening below. */
|
||||
/* The scene is the wallpaper itself: Frutiger Aero 4k Wallpaper (1920x1200)
|
||||
via WallpaperSafari — https://wallpapersafari.com/w/sgI2Dq — credited in
|
||||
Settings and the README. Only the moving bubbles are drawn on top; the
|
||||
photo already has grass, sun, water and clouds. */
|
||||
.aero .ice-scene {
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
102deg,
|
||||
rgb(255 255 255 / 0) 0px,
|
||||
rgb(255 255 255 / 0.13) 46px,
|
||||
rgb(255 255 255 / 0) 104px
|
||||
),
|
||||
linear-gradient(180deg, #45b3ec 0%, #93daf9 26%, #d8f2ff 34%, #58b6e8 37%, #3f9ad4 66%, #2b86c4 100%);
|
||||
background-image: url("/aero/background.webp");
|
||||
background-size: cover;
|
||||
background-position: center 42%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* The blobs are caustic light patches here, not glacier gradients. */
|
||||
.aero .ice-blob-a,
|
||||
.aero .ice-blob-b,
|
||||
.aero .ice-blob-c,
|
||||
.aero .ice-blob-d {
|
||||
background: radial-gradient(circle, rgb(255 255 255 / 0.85) 0%, rgb(191 240 255 / 0.5) 45%, transparent 74%);
|
||||
/* The drawn extras belong to the old CSS scene: off over the photo. */
|
||||
.aero .ice-scene::before,
|
||||
.aero .ice-scene::after,
|
||||
.aero .ice-blob {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* The orbs are the big soap bubbles: a glassy rim with a bright specular. */
|
||||
/* The orbs become extra bubbles over the photo. */
|
||||
.aero .ice-orb {
|
||||
background: radial-gradient(
|
||||
circle at 32% 28%,
|
||||
|
|
@ -289,48 +287,6 @@
|
|||
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);
|
||||
}
|
||||
|
||||
/* Reeds on the left, drawn on the scene's own pseudo-elements (the grain
|
||||
layer is at 2% opacity, so anything drawn on it is invisible). */
|
||||
.aero .ice-scene::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -6vw;
|
||||
bottom: -4vh;
|
||||
width: 40vw;
|
||||
height: 52vh;
|
||||
opacity: 0.75;
|
||||
background:
|
||||
linear-gradient(74deg, transparent 40%, rgb(34 150 84 / 0.85) 40% 41.6%, transparent 41.6%),
|
||||
linear-gradient(84deg, transparent 58%, rgb(74 222 128 / 0.7) 58% 59.4%, transparent 59.4%),
|
||||
linear-gradient(64deg, transparent 22%, rgb(20 130 74 / 0.8) 22% 23.6%, transparent 23.6%),
|
||||
linear-gradient(92deg, transparent 70%, rgb(56 189 116 / 0.6) 70% 71.2%, transparent 71.2%),
|
||||
linear-gradient(52deg, transparent 8%, rgb(15 110 64 / 0.7) 8% 9.8%, transparent 9.8%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* A city silhouette on the right: a row of blocks of different heights,
|
||||
anchored to the water line and fading upward. */
|
||||
.aero .ice-scene::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 3vw;
|
||||
bottom: 33vh;
|
||||
width: 36vw;
|
||||
height: 17vh;
|
||||
opacity: 0.38;
|
||||
background:
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 0% 100% / 9% 64% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 12% 100% / 12% 100% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 27% 100% / 8% 52% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 38% 100% / 11% 78% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 52% 100% / 7% 44% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 62% 100% / 13% 88% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 78% 100% / 9% 58% no-repeat,
|
||||
linear-gradient(rgb(7 58 104 / 0.95), rgb(7 58 104 / 0.95)) 90% 100% / 10% 72% no-repeat;
|
||||
mask-image: linear-gradient(180deg, rgb(0 0 0 / 0.9) 0%, rgb(0 0 0 / 0.45) 75%, transparent 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* The Aero gloss: the classic white top half with a horizon line. */
|
||||
.aero .card,
|
||||
.aero .glass-panel,
|
||||
|
|
@ -394,13 +350,45 @@
|
|||
.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; }
|
||||
|
||||
/* Glossy aqua primary buttons and segments. */
|
||||
/* 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
|
||||
2.5:1. A small glass chip fixes it without hiding the wallpaper. */
|
||||
.aero .hint-line {
|
||||
display: inline-block;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
backdrop-filter: blur(8px) saturate(1.4);
|
||||
-webkit-backdrop-filter: blur(8px) saturate(1.4);
|
||||
}
|
||||
|
||||
/* Liquid glass buttons: translucent, glossed top half, bright rim — the
|
||||
Vista/Aero button, not a solid fill. */
|
||||
.aero .grad-ice,
|
||||
.aero .btn-primary,
|
||||
.aero .segmented > button[aria-pressed="true"],
|
||||
.aero .segmented > a[aria-current="page"] {
|
||||
background-image: linear-gradient(180deg, #a5e4ff 0%, #38bdf8 52%, #0ea5e9 53%, #0284c7 100%);
|
||||
color: #052437;
|
||||
background-image: linear-gradient(
|
||||
180deg,
|
||||
rgb(255 255 255 / 0.9) 0%,
|
||||
rgb(226 245 255 / 0.62) 48%,
|
||||
rgb(168 224 255 / 0.55) 52%,
|
||||
rgb(219 242 255 / 0.7) 100%
|
||||
);
|
||||
border: 1px solid rgb(255 255 255 / 0.9);
|
||||
color: #05243d;
|
||||
box-shadow: 0 10px 24px -14px rgb(2 62 110 / 0.6), inset 0 1px 0 rgb(255 255 255 / 0.98), inset 0 -8px 14px -10px rgb(255 255 255 / 0.7);
|
||||
backdrop-filter: blur(10px) saturate(1.5);
|
||||
-webkit-backdrop-filter: blur(10px) saturate(1.5);
|
||||
}
|
||||
|
||||
/* The glass itself, a touch more transparent so the wallpaper reads through
|
||||
it like liquid, still opaque enough for the dark text (contrast scanned). */
|
||||
.aero {
|
||||
--glass-bg: rgb(222 244 255 / 0.72);
|
||||
--glass-solid-bg: rgb(228 246 255 / 0.82);
|
||||
--glass-sheet-bg: rgb(230 246 255 / 0.92);
|
||||
}
|
||||
|
||||
html,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ import { cn } from "@/lib/utils";
|
|||
export function AiDisclaimer({ className }: { className?: string }) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<p className={cn("text-center text-[11.5px] leading-snug text-[var(--text-subtle)]", className)}>
|
||||
// hint-line: Aero gives this its own glass chip (it sits on the wallpaper).
|
||||
<p className={cn("hint-line text-center text-[11.5px] leading-snug text-[var(--text-subtle)]", className)}>
|
||||
{t("common.aiDisclaimer")}
|
||||
</p>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,20 @@ export function ThemeSection() {
|
|||
))}
|
||||
</div>
|
||||
{theme === "aero" ? (
|
||||
<>
|
||||
<p className="text-[11px] leading-snug text-[var(--text-subtle)]">{t("theme.aeroNote")}</p>
|
||||
<p className="text-[11px] leading-snug text-[var(--text-subtle)]">
|
||||
{t("theme.aeroCredit")}{" "}
|
||||
<a
|
||||
href="https://wallpapersafari.com/w/IlWuz7"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline decoration-dotted"
|
||||
>
|
||||
wallpapersafari.com/w/IlWuz7
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -225,7 +225,8 @@ export function FeedbackPanel({
|
|||
export function Hint({ children }: { children: React.ReactNode }) {
|
||||
const noHints = useNoHints();
|
||||
if (noHints) return null;
|
||||
return <p className="text-sm text-frost-600 dark:text-frost-300">{children}</p>;
|
||||
// hint-line: Aero gives this its own glass chip (it sits on the wallpaper).
|
||||
return <p className="hint-line text-sm text-frost-600 dark:text-frost-300">{children}</p>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1004,6 +1004,7 @@ const en: Record<string, string> = {
|
|||
"settings.theme": "Theme",
|
||||
"theme.aero": "Frutiger Aero",
|
||||
"theme.aeroNote": "Aero has no light/dark switch — pick Light or Dark to get it back.",
|
||||
"theme.aeroCredit": "Background: “The S Aesthetic That You Definitely Recognize Frutiger Aero” via WallpaperSafari.",
|
||||
"settings.legal": "Legal",
|
||||
"consent.title": "About cookies",
|
||||
"consent.body": "Freezmo uses no cookies and no trackers — your decks, homework and settings stay in this browser.",
|
||||
|
|
@ -1959,6 +1960,7 @@ const cs: Record<string, string> = {
|
|||
"settings.theme": "Téma",
|
||||
"theme.aero": "Frutiger Aero",
|
||||
"theme.aeroNote": "Aero nemá přepínač světlý/tmavý — vyber Světlý nebo Tmavý, pokud ho chceš zpět.",
|
||||
"theme.aeroCredit": "Pozadí: „The S Aesthetic That You Definitely Recognize Frutiger Aero“ přes WallpaperSafari.",
|
||||
"settings.legal": "Právní informace",
|
||||
"consent.title": "O cookies",
|
||||
"consent.body": "Freezmo nepoužívá cookies ani sledování — sady, úkoly a nastavení zůstávají v tomto prohlížeči.",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "freezmo",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
|
|
|
|||
BIN
public/aero/background.webp
Normal file
BIN
public/aero/background.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
|
|
@ -9,7 +9,7 @@
|
|||
*
|
||||
* Bumping CACHE_VERSION is how a new deploy invalidates old assets.
|
||||
*/
|
||||
const CACHE_VERSION = "freezmo-v55";
|
||||
const CACHE_VERSION = "freezmo-v56";
|
||||
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
|
||||
const ASSET_CACHE = `${CACHE_VERSION}-assets`;
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ async function makeReviewPage(locale = "en") {
|
|||
check("cs: results slide after answering", await page.getByText("100%").isVisible());
|
||||
await page.locator('[data-testid="review-feed"]').getByRole("button", { name: "Zpět do knihovny" }).click();
|
||||
await page.waitForTimeout(900);
|
||||
check("cs: back in the library", await page.getByRole("heading", { name: "Tvoje mraznička" }).isVisible());
|
||||
check("cs: back in the library", await page.getByRole("heading", { name: "Tvůj mrazák" }).isVisible());
|
||||
await context.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue