Thin frosted Aero glass, boot-theme fix, latest background revision

The white surfaces were reading as a milky white panel (a card top was
~71% white). The frosted recipes now use a 14% base veil plus the gloss
over the pre-blurred wallpaper, small chrome (rows, chips, segments,
buttons) keeps a touch more veil, and nothing nested inside a sheet or
dialog uses the viewport-aligned layer (it showed the raw dark water
under the settings theme labels at 1.5:1 — nested chrome falls back to
the flat fills over the genuinely blurred sheet).

The first-load text bug was the boot script: its dark test was
`s && !a ? s === 'dark' : matchMedia(...)`, which for a stored aero fell
through to the OS preference, so a dark-preferring machine loaded with
both `dark` and `aero` classes and every `.dark` rule painted over the
wallpaper until a theme toggle re-applied the classes. The stored value
now decides alone; a dark-colorScheme check pins it in the mobile suite
(123 checks).

Background: the user's latest Gemini scene (1280x714) re-encoded to
public/aero/background.webp (206 kB) with a fresh pre-blurred sibling.

Contrast: 0 below-minimum findings, 0 axe violations, all three themes.
Version 0.3.1, SW -> freezmo-v62.
This commit is contained in:
Martin Dvořák 2026-10-13 09:38:29 +00:00
commit c88ad2865a
9 changed files with 122 additions and 38 deletions

View file

@ -81,8 +81,12 @@ export const viewport: Viewport = {
* Applies the saved theme before paint to avoid a flash, and keeps the
* `theme-color` meta in step with the *app* theme (the SSR metas follow the
* OS preference, which is wrong once the user has picked a theme here).
* The stored theme decides alone: aero is not dark, and the old ternary added
* `dark` alongside `aero` whenever the OS preferred dark — the dark palette
* on the bright wallpaper is what made the first load look washed out until
* a theme toggle re-applied the classes.
*/
const themeScript = `(function(){try{var DEV=${devInstanceJs};var s=localStorage.getItem('freezmo.theme');var a=s==='aero';var d=s&&!a?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches;if(d)document.documentElement.classList.add('dark');if(a)document.documentElement.classList.add('aero');var l=localStorage.getItem('freezmo.locale.v1');if(l==='cs'||l==='en')document.documentElement.lang=l;var m=document.querySelector('meta[name="theme-color"][data-app]');if(!m){m=document.createElement('meta');m.setAttribute('name','theme-color');m.setAttribute('data-app','1');document.head.insertBefore(m,document.head.firstChild);}m.setAttribute('content',DEV?'#b45309':(a?'#2fa7e6':(d?'#0e1424':'#f4f7fd')));}catch(e){}})();`;
const themeScript = `(function(){try{var DEV=${devInstanceJs};var s=localStorage.getItem('freezmo.theme');var a=s==='aero';var d=a?false:(s?s==='dark':window.matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.classList.toggle('dark',!!d);if(a)document.documentElement.classList.add('aero');var l=localStorage.getItem('freezmo.locale.v1');if(l==='cs'||l==='en')document.documentElement.lang=l;var m=document.querySelector('meta[name="theme-color"][data-app]');if(!m){m=document.createElement('meta');m.setAttribute('name','theme-color');m.setAttribute('data-app','1');document.head.insertBefore(m,document.head.firstChild);}m.setAttribute('content',DEV?'#b45309':(a?'#2fa7e6':(d?'#0e1424':'#f4f7fd')));}catch(e){}})();`;
/**
* Captures Chrome's `beforeinstallprompt` (it can fire before React hydrates)