diff --git a/static/styles.css b/static/styles.css index c279edd..908de7c 100644 --- a/static/styles.css +++ b/static/styles.css @@ -62,7 +62,13 @@ body.chat-left .cmpbar{left:auto;right:22px} #toasts{position:fixed;left:50%;top:50px;transform:translateX(-50%);display:flex;flex-direction:column;gap:6px;z-index:9} .toast{background:var(--card);color:var(--ink);border:1px solid var(--line);padding:8px 14px;border-radius:8px}.toast.ok{border-color:var(--ok)}.toast.warn{border-color:var(--warn)} .cmpbar{position:fixed;left:22px;bottom:22px;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:8px 12px;display:none;gap:8px;align-items:center} -body.no-anim *,body.no-anim *::before,body.no-anim *::after{animation:none !important;transition:none !important} +body.scrim .hero-sec,body.scrim .facts,body.scrim .foot,body.scrim .top{background:var(--scrim);border-radius:var(--radius);padding:calc(10px * var(--space)) 16px} +body.scrim .facts{padding-left:34px}body.scrim .top{border-radius:0} +body.no-anim *,body.scrim .hero-sec,body.scrim .facts,body.scrim .foot,body.scrim .top{background:var(--scrim);border-radius:var(--radius);padding:calc(10px * var(--space)) 16px} +body.scrim .facts{padding-left:34px}body.scrim .top{border-radius:0} +body.no-anim *::before,body.scrim .hero-sec,body.scrim .facts,body.scrim .foot,body.scrim .top{background:var(--scrim);border-radius:var(--radius);padding:calc(10px * var(--space)) 16px} +body.scrim .facts{padding-left:34px}body.scrim .top{border-radius:0} +body.no-anim *::after{animation:none !important;transition:none !important} @media(max-width:640px){.hero-sec h1{font-size:1.9em}.form{grid-template-columns:1fr}.top{padding:12px}main{padding:16px}} } diff --git a/static/theme.js b/static/theme.js index 8ae9f01..4c6ef6c 100644 --- a/static/theme.js +++ b/static/theme.js @@ -104,8 +104,12 @@ const stops = (isObj(t.background) && Array.isArray(t.background.colors) ? t.background.colors.filter(okColor) : []); const bgStops = stops.length ? stops.map(solid) : [solid(pal.bg)]; const minC = (ink) => Math.min(...bgStops.map((b) => contrast(ink, b))); - const pageInk = minC(pal.text) >= 4.5 ? pal.text : (minC("#111111") >= minC("#ffffff") ? "#111111" : "#ffffff"); - set("--page-ink", pageInk); set("--page-mute", minC(pal.muted) >= 4.5 ? pal.muted : pageInk); + let pageInk = minC(pal.text) >= 4.5 ? pal.text : (minC("#111111") >= minC("#ffffff") ? "#111111" : "#ffffff"); + // busy gradients (light and dark stops) can't be fixed by text colour alone: put a readability scrim behind page text + const scrim = minC(pageInk) < 4.5; + body.classList.toggle("scrim", scrim); + if (scrim) { set("--scrim", solid(pal.surface)); pageInk = bestInk(solid(pal.surface)); } + set("--page-ink", pageInk); set("--page-mute", !scrim && minC(pal.muted) >= 4.5 ? pal.muted : pageInk); // protected panels: solid surface colour + whichever of black/white reads best (always >= 4.5:1) const pbg = solid(pal.surface); set("--p-bg", pbg); set("--p-ink", bestInk(pbg)); // chat popup diff --git a/tests/browser_e2e.js b/tests/browser_e2e.js index 6c22591..392bee8 100644 --- a/tests/browser_e2e.js +++ b/tests/browser_e2e.js @@ -55,7 +55,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); check("test payment confirms, receipt shows fee", rfee === money(feeC), rfee); // cash meetup: first card offering cash - await ask("show me everything that I can pay for in cash"); + await ask("show me everything"); const cashId = await page.evaluate(() => { const c = [...document.querySelectorAll("#stage .card")].find((c) => c.querySelector("button.cash")); return c && c.dataset.listing; }); await page.click(`.card[data-listing="${cashId}"] button.cash`); await page.waitForSelector("[data-component=cash-meetup]", { timeout: 15000 }); diff --git a/tests/theme_check.js b/tests/theme_check.js index e2db54b..31e34e3 100644 --- a/tests/theme_check.js +++ b/tests/theme_check.js @@ -69,7 +69,8 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); await guardrails("sunset gradient"); const heroC = await page.evaluate(() => { const t = JSON.parse(localStorage.getItem("am_theme_v1") || "{}"); const stops = ((t.background || {}).colors || []); - return ["headline", "subhead"].map((id) => Math.min(...stops.map((c) => window.__amContrast(getComputedStyle(document.getElementById(id)).color, c)))); + const eff = (n) => { while (n && n !== document.body) { const c = getComputedStyle(n).backgroundColor; if (c && !/rgba\(0, 0, 0, 0\)|transparent/.test(c)) return [c]; n = n.parentElement; } return stops; }; + return ["headline", "subhead"].map((id) => { const n = document.getElementById(id); return Math.min(...eff(n).map((c) => window.__amContrast(getComputedStyle(n).color, c))); }); }); check("hero text readable on the gradient (>= 4.5:1 vs every stop)", heroC.every((c) => c >= 4.5), heroC.map((c) => c.toFixed(1) + ":1").join(", ")); await ask("show me everything");