From 0c5d0ffb1161d06c7b2f70b865022d14f3fede2e Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Tue, 13 Oct 2026 11:02:32 +0000 Subject: [PATCH] One Ask-style glass everywhere, cache-busted Aero wallpaper 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, and an old worker can outlive a deploy). Both aero images carry a ?v=3 query now, so a stale cache cannot answer for them. "Everything should look like the Ask button" is literal: the rail, the cards, the chips, the segments and the buttons share one recipe - the pre-blurred wallpaper under a ~15% veil with the bright rim. The blur bake is regenerated at 640x400 with a shadow lift (gamma 0.86) and a saturation nudge, so the scene's colours stay vivid instead of washing to a milky film. The wizard's phone action bar loses Aero's heavy water-blue slab for the same glass, and nested chrome inside sheets keeps its flat fills (the sheet itself is the real glass there). Contrast: 0 below-minimum findings on every screen and theme, 0 axe violations. Version 0.3.2, SW -> freezmo-v63. --- README.md | 21 +++++-- app/globals.css | 101 +++++++++++++++---------------- package.json | 2 +- public/aero/background-blur.webp | Bin 3892 -> 8890 bytes public/sw.js | 2 +- 5 files changed, 68 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 3937d57..abc2bb2 100644 --- a/README.md +++ b/README.md @@ -1407,10 +1407,23 @@ Things I chose while building, with the reasoning: painted the dark palette over the bright wallpaper, until a theme toggle re-applied the classes. The stored value now decides alone and the class is toggled explicitly; `tests/mobile-test.mjs` pins it with a dark-`colorScheme` browser context. And the surfaces - drop their thick white veils (a card top measured ~71% white) for a 14% base veil plus the - gloss over the pre-blurred wallpaper — the scene is clearly visible through the glass, small - chrome (rows, chips, segments, buttons) keeps slightly more veil, and the pixel-contrast scan - that re-runs in the deploy e2e gate still passes. + drop their thick white veils (a card top measured ~71% white) for thin veils over the + pre-blurred wallpaper — the scene is clearly visible through the glass, and the + pixel-contrast scan still passes. + +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, + and the old worker can outlive a deploy): both aero images now carry a `?v=3` query, so a + stale cache cannot answer for them, and future wallpaper swaps get a new query too. And + "everything should look like the Ask button" is now literal: the rail, the cards, the chips, + the segments and the buttons all share one recipe — the pre-blurred wallpaper under a ~15% + veil (the minimum the pixel-contrast scan needs) with the bright rim. The blur bake is + shadow-lifted (gamma 0.86) and slightly saturated so the scene's colours stay vivid instead + of washing out to a milky film, the wizard's phone action bar (Aero's `--page` token was the + saturated water blue, a heavy slab over the photo) joins in, and nested chrome inside sheets + keeps its flat fills (the sheet itself is the real glass there). `tests/contrast.mjs` is back + to 0 findings across every screen and theme. 65. **Slide content never flex-shrinks, and the AI note appears once.** A long typed answer in a short-answer activity shrank the textarea inside the slide's flex column (58px instead of its diff --git a/app/globals.css b/app/globals.css index 3c1f0da..82ec22e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -208,7 +208,7 @@ --surface: #ffffff; --surface-raised: #ffffff; --surface-strong: rgb(255 255 255 / 0.72); - --card-hover-bg: rgb(255 255 255 / 0.9); + --card-hover-bg: rgb(255 255 255 / 0.5); --surface-sunken: rgb(255 255 255 / 0.5); --hairline: rgb(255 255 255 / 0.75); --card-border: var(--glass-border); @@ -222,7 +222,7 @@ wizard's step help): darker here, because they sit on the water. */ --color-frost-600: #16324a; --color-frost-700: #102a41; - --hover: rgb(255 255 255 / 0.45); + --hover: rgb(255 255 255 / 0.32); --focus: #0369a1; /* Pale blue glass, like the mock: bright enough for dark navy text. */ @@ -261,7 +261,7 @@ in public/aero/). Only the moving bubbles are drawn on top; the image already has grass, sun, water and clouds. */ .aero .ice-scene { - background-image: url("/aero/background.webp"); + background-image: url("/aero/background.webp?v=3"); background-size: cover; background-position: center 42%; background-repeat: no-repeat; @@ -288,17 +288,17 @@ /* The Aero gloss for the floating surfaces (sheets, dropdowns and the sticky chrome keep a real backdrop-filter — they sit over scrolling content). - Thin like the card glass: the old 75% top read as a white lid. */ + Thin like the card glass: a whisper of sheen, not a white lid. */ .aero .glass-panel, .aero .study-chrome, .aero .tabbar, .aero .glass { background-image: linear-gradient( 180deg, - rgb(255 255 255 / 0.5) 0%, - rgb(255 255 255 / 0.18) 46%, - rgb(255 255 255 / 0.05) 47%, - rgb(255 255 255 / 0.14) 100% + rgb(255 255 255 / 0.32) 0%, + rgb(255 255 255 / 0.1) 46%, + rgb(255 255 255 / 0.03) 47%, + rgb(255 255 255 / 0.1) 100% ); } @@ -361,10 +361,8 @@ @apply backdrop-blur-md backdrop-saturate-150; } -/* The thin glass the Ask / Import JSON buttons use: a 34% white tint over the - sharp wallpaper with a bright rim — no pre-blurred layer, so it reads as - glass rather than as a white chip. Rail rows, the active nav item and the - active segment all match it. */ +/* Bright white rims on the study/homework segments (the frosted fill itself + comes from the shared glass recipe below). */ .aero .segmented > button, .aero .segmented > a { border-color: rgb(255 255 255 / 0.5); @@ -388,44 +386,28 @@ 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.3); - --glass-solid-bg: rgb(232 248 255 / 0.42); - --glass-sheet-bg: rgb(232 248 255 / 0.74); - --surface-strong: rgb(255 255 255 / 0.32); - --surface-sunken: rgb(255 255 255 / 0.24); - --nav-active-bg: rgb(255 255 255 / 0.38); + --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); } -/* Liquid glass without the per-frame cost: every surface shows a pre-blurred - copy of the wallpaper, glued to the viewport (`background-attachment: - fixed`), with the tint and the gloss layered on top. Blurring the live - scene instead measured 66ms per frame at 2x CPU throttle — the bubbles move - behind every surface, so a real backdrop-filter re-runs ~60 times a second - (vs 17ms with nothing blurring). The look is the same; the cost is zero. */ +/* Liquid glass — one recipe for every surface, so the rail, the cards, the + chips, the segments and the buttons all read exactly like the Ask pill: a + pre-blurred, slightly lifted copy of the wallpaper (glued to the viewport, + same cover/42% geometry as the scene) under a whisper-thin veil and a + bright rim. Real backdrop-filter is skipped on purpose: the bubbles move + behind every surface, so it re-runs ~60 times a second (measured 66ms per + frame at 2x CPU throttle vs 17ms with nothing blurring). The veils are the + minimum the pixel-contrast scan needs to hold 4.5:1 for the small muted + text — anything more and the glass reads as a white panel. */ .aero .card, .aero .glass-solid, .aero .sidebar-surface, .aero .gate-card, -.aero .list-group { - background-image: - linear-gradient( - 180deg, - rgb(255 255 255 / 0.3) 0%, - rgb(255 255 255 / 0.1) 46%, - rgb(255 255 255 / 0.04) 53%, - rgb(255 255 255 / 0.14) 100% - ), - linear-gradient(rgb(255 255 255 / 0.14), rgb(255 255 255 / 0.14)), - url("/aero/background-blur.webp"); - background-size: 100% 100%, 100% 100%, cover; - background-position: 0 0, 0 0, center 42%; - background-attachment: scroll, scroll, fixed; - background-repeat: no-repeat, no-repeat, no-repeat; -} - -/* The small chrome — rail rows, the active nav item, chips, segments and the - buttons — gets the same frosted glass with a touch more veil, so its small - text pops over the busy wallpaper. */ +.aero .list-group, .aero .nav-action, .aero .nav-row[aria-current="page"], .aero .nav-row[data-active="true"], @@ -439,25 +421,40 @@ background-image: linear-gradient( 180deg, - rgb(255 255 255 / 0.42) 0%, - rgb(255 255 255 / 0.16) 46%, - rgb(255 255 255 / 0.08) 53%, - rgb(255 255 255 / 0.2) 100% + 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% ), - linear-gradient(rgb(255 255 255 / 0.12), rgb(255 255 255 / 0.12)), - url("/aero/background-blur.webp"); + linear-gradient(rgb(255 255 255 / 0.15), rgb(255 255 255 / 0.15)), + url("/aero/background-blur.webp?v=3"); background-size: 100% 100%, 100% 100%, cover; background-position: 0 0, 0 0, center 42%; background-attachment: scroll, scroll, fixed; background-repeat: no-repeat, no-repeat, no-repeat; } +/* The wizard's phone action bar: Aero's page token is the saturated water + 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); + border-top-color: rgb(255 255 255 / 0.6); + box-shadow: 0 -14px 34px -22px rgb(2 62 110 / 0.5); +} + /* 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(rgb(255 255 255 / 0.48), rgb(255 255 255 / 0.48)), - url("/aero/background-blur.webp"); + 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% + ), + url("/aero/background-blur.webp?v=3"); background-size: 100% 100%, cover; background-position: 0 0, center 42%; background-attachment: scroll, fixed; diff --git a/package.json b/package.json index 3493a3b..cc30079 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freezmo", - "version": "0.3.1", + "version": "0.3.2", "private": true, "license": "GPL-3.0-only", "scripts": { diff --git a/public/aero/background-blur.webp b/public/aero/background-blur.webp index 5d004fa96a10c7ebfc28fa4a7a6251bbd01b4b00..fd2b22dc17b61ac13d7213ea638ce13ac73e468c 100644 GIT binary patch delta 8496 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 E5B6r8Q2+n{ delta 3458 zcmV-|4Sn*uMYJ9qQb|TeEDrzxS4BclR!}%t3IG5AFaQ7m-vIzCu^iR`fB6Fd00Fr& zMD#yELALF-jRYv{x%aMs0$#vZ=hxjch?oG_lB|S|NS2KVdLE|GoBY9-UDI`4qrO3$ z>hgLGT!*;&VQ7Y%rfY_#8@i?$x`9PNUtY$vR3N&6BvL_oB77N$s5#uBPEix`avKmc z1Q1e52;9WX#4{Z1K)R6*e-fhG0kjf0OoWB$fD)r~LmMESkBa=9GNd>m3=wWFH@BT) zoMa$HqAEA1pE}nSeApzU_QY*73`)ywJ4{T2k7==PUg9_0#rCaPjU9m2-5?L6G+&0M ztJP2VkNQe>yLfg06V=`<9jro^Ltja27fXvnNEo~7P)DN)Sbho8epFw$ya`zA_@THoO7TV_U_=ltP+FN1-;eQe}wOyBe^*LYLq|wuMmb z1;gxRrF$ci*Jcue>CZ=}4XBhq*pt$PG z7?mv4=kBk45+rs?d{vy%J1Ejy^P^k&*9;|ghcCgD);3@5e`z|rZN9IL5M3+zxA^sc zT;b!t9slk4Z^wT-{@d~2j{kQ2x8uJZ|Lyp1$A3Hi+wtFy|91Sh?u$ z{}tHr--iGGhW{;YC0z-klDgzN!q@H>$!%j{vi%~-lifCpuy1ooh73+dNqD;y=-IXl zyJULZzi=Gne|Aa~`av0>t&FJ3KP9GcKrl}U-p{uwl!w|NpTZ1DWHgsyxH|RLRY*#! zi+sQh|DS>)eeQNJy-g{Asazjjx$yMH8lwDO)Pck)$^M!_De?YX6|nJjAj+~GNH4Tm z$GI_<2}M-snJ$}+?QEL zQ3{6mu&o&JdM^#9xaDIT2sC?)UM5XK(p1K^*$oUkof7s!gy}6l#DzMK(o?rK0BA;0 zcx#yMFoV3_3kzV-NLg$jEHgkz+r^V;(H%1@x3ugKV#n)kGvZ5_Fv&^(1Oe+E=P%Ae z(@L?qe>f*%vkf~w^{Jwa`bj(+ennbfctqnmPRK69LS#)+-KQZhGrRzv&N|2#I+Ky! zz$Pr5z|s>STT{5^=~QCQ%_&2QlZGjelx64e!BE75g_*!KpU{@F@Z1I<|)O8}h#D&PSu z0X}UqmPt4>F{B}}x`O}>iDz!$I7#Vry&u(`Y7zIbm$t{N(sgwDdwOg)cX991Z@qv2 z{dr<5pG42#ZaO`+b;Bxn9l#AC?=_{zP?jlk1Lhf6LCpnYuqr(XpABJaPst$+Xj``{UH4aF>iY(FrJ%R%)B#maqB|8#$QYo+&d$h?kLbZc0#biJD2>)&B>hZ?j@)NhuX`HKMp6Fn))HN=D(f99WB|4;w_|NsB~jjUkeHGij5STy^bQdFCDu9--5 z@a`w_=jvneVFE3ch~soaT6z3!4vbZ?*(bak6f{dMAzjPpwCZH|Dt6EQ86yw%kAKBn zU)Mxw#Mxj;5~lgYg*^b?5_CGUvl^KXz(36z9<+rl+?~7(uy2z zt`n#-d=+45z38(Hy~8k0Tph+KS*NgwEIo&L^KlkhX2+`vkrrBJ$Eot$!@nkFyoY^- z#~3cuIBxzelPQSnIrP#pe}>Vt4wp#FX0GFvBah)!?yE(4EN*;E$mceAPShaL)ZlE> z7yb2P4@u}}o8WHDzc)!ELmZ42@aQw;aYIKV=YlQ2_Cj;ottE($un4%cl*dI{nV^lg zEQ)n~l6D|NL(*?%?Cjcxzmmxz(hs~x;4~e#yXtT+k4ZbS6iojpyC^+nctZ zs7$C&e^@3&od}zlAiB)#&_jDpOOi;{MMMprxc9BN;_*X#f3e`28vMdMXwrAItSYpy z2wZ@#OQ8Xpwl%bc->jbaT+jbnA8MMWD+VTs?j=9)iZUSh7PnZARk=%mnLk)XW{j&b z8t3yvG?%cw#a_X?`>Mp$HZ-ovXmm$Jl(dV0>dmxmY-swx+dFppLCcoDiJ4kOQ*Ug$ zCqwm#fD2=lf2@r$*b`!L!5OO)BtRMLIQF*WUyZUHi441ddj?aF&lYQ?F6u8V0-+lK zb46Fg>`l8Kb~y=Qwd{c7#Na?ity;uLhkxzI#Ze9yrvrzq{QYM2m*_A9c8EO!sU;^W z#AV1r&g=>;c;w&J8~XBIsD{u1Zjt_S5=%!DWKxkNey0B*d8#l4&P~|r?K)d zWrBP0;VmaFu;bi->mQ)v4H1wmTl|fD@d(jA3hAe;KfX?IU_G&C|PVDyYB?l#3)^{;f|FcB1|3#XVKTVcbn__$99f)wsaI+IRV5 zQWRHG8^d^R+G6(5l~__Yn7xne5Hjre*({upm+-huPw+`mhB{mAL-AK^?P$o!)5_** zRoHho9RaM0V`dXr17m@E*X_4;a%bger}n%~e_#Egr80bIWmwYEd3+Od!7i1}!xG>~ zi$a_)l;^xw7J$z~UYrxC%q*#s44yhXssmD2yd|g+@Ca71fa)cOx9OR{?@fa4-2Ska zWg^~DoqVV=>>}HjDL(pzqe*A&ySJb$qF*LZ`&xo1Lx_@NM}X4F_kHalRitKss!aHA ze-qieRDnnbgrbla1%-W|+~rny{wa!Z3AU+sOSNV#*;D20q)7MzF(C8tVAav&f~6z@ zx*Mt9i5gRy*-QF_e1x;}auI2&3S;6pfltkBRnMn*Tk7*9pOuVoMGuhGm%#(`K$s=L zAo#^gozMT<$IVkp+MdQiY8rm_zRN0m$WxknHA3oO-Vojl(~P zX}<}iN$(&0EwDgHG-9abThoSNA=Ru^D_rcM(AIE9xS2XJ<-8iuHH$9nRPSpYukuF0 zn@=)9;S#7~)b1t%Sd9;t>vJ(?TAmrJm~#&%YDO$t{ew)Ao=pKgci;_C_9D?xe?o2D zKI31-3k?N5sh+car)5c1!9I``4J!sIs+2)+H>J&QfhbcAT-`JN;Yf@if7I*k$x{z+ z8Q|FhgX`kvLXe$LiHdc_o)#AxL4T{fC++)xLnctRS=k~A+SEa^D3igKo^Zr;P-3kn z&ATP5DBWOUU1Kr(XdB%>7LaZe+8oDZu9Kt#f@#enom6jF8|^A>+@LT;S7+^M_s2A8n0EJA