diff --git a/styles/base/global.css b/styles/base/global.css index c430a4c..7d9c450 100644 --- a/styles/base/global.css +++ b/styles/base/global.css @@ -81,11 +81,16 @@ main#maincontent { * backgroundColor is updated at runtime by * static/js/modules/ChromeTint.js. * + * visibility: hidden keeps the elements invisible to the user (so + * they don't appear as dark bars over the page content) while iOS + * Safari still samples their backgroundColor. The WebKit source + * explicitly notes that visibility: hidden elements ARE sampled. + * * The elements are kept as small as possible (12px tall) so they - * stay well within the iOS safe-area insets and don't intrude into - * the page content. They have no background by default — they only - * become opaque when the matching element (header / footer) is in - * view. pointer-events: none ensures they never intercept clicks. + * stay well within the iOS safe-area insets. They have no background + * by default — they only get a backgroundColor when the matching + * element (header / footer) is in view. pointer-events: none ensures + * they never intercept clicks even if made visible. * * (iOS 26 ignores ; this technique is * what replaced it.) @@ -93,6 +98,7 @@ main#maincontent { .chrome-tint { pointer-events: none; position: fixed; + visibility: hidden; z-index: 9999; } diff --git a/tests/chrome-tint.spec.js b/tests/chrome-tint.spec.js index 335eefa..4a6206f 100644 --- a/tests/chrome-tint.spec.js +++ b/tests/chrome-tint.spec.js @@ -61,6 +61,7 @@ test.describe("chrome tint", () => { width: Math.round(topR.width), widthPct: Math.round((topR.width / winW) * 100), position: topCs.position, + visibility: topCs.visibility, }, bottom: { bottomFromViewport: Math.round(winH - bottomR.bottom), @@ -68,20 +69,55 @@ test.describe("chrome tint", () => { width: Math.round(bottomR.width), widthPct: Math.round((bottomR.width / winW) * 100), position: bottomCs.position, + visibility: bottomCs.visibility, }, }; }); - // Top element: position fixed, top ≤ 4px, width >= 80%, height >= 12px. + // Top element: position fixed, top ≤ 4px, width >= 80%, height >= 12px, + // visibility hidden (so it never appears as a dark bar over page content). expect(layout.top.position).toBe("fixed"); expect(layout.top.top).toBeLessThanOrEqual(4); expect(layout.top.widthPct).toBeGreaterThanOrEqual(80); expect(layout.top.height).toBeGreaterThanOrEqual(12); + expect(layout.top.visibility).toBe("hidden"); - // Bottom element: position fixed, within 3px of viewport bottom, width >= 80%. + // Bottom element: position fixed, within 3px of viewport bottom, width >= 80%, + // visibility hidden. expect(layout.bottom.position).toBe("fixed"); expect(layout.bottom.bottomFromViewport).toBeLessThanOrEqual(3); expect(layout.bottom.widthPct).toBeGreaterThanOrEqual(80); + expect(layout.bottom.visibility).toBe("hidden"); + }); + + test("fixed chrome tint elements are not visible to the user (no extra dark bars)", async ({ + page, + }) => { + await page.goto(site, { waitUntil: "networkidle" }); + // Wait for ChromeTint to set the background colors. + await page.waitForFunction( + () => { + const top = document.querySelector(".chrome-tint--top"); + const bottom = document.querySelector(".chrome-tint--bottom"); + return top && bottom && + top.style.backgroundColor !== "" && + bottom.style.backgroundColor === ""; + }, + { timeout: 2000 }, + ); + // Even though the elements have background colors, they should + // be invisible to the user (visibility: hidden) so they don't + // show as dark bars over the page content. + const visibility = await page.evaluate(() => { + const top = document.querySelector(".chrome-tint--top"); + const bottom = document.querySelector(".chrome-tint--bottom"); + return { + top: getComputedStyle(top).visibility, + bottom: getComputedStyle(bottom).visibility, + }; + }); + expect(visibility.top).toBe("hidden"); + expect(visibility.bottom).toBe("hidden"); }); test("viewport meta includes viewport-fit=cover", async ({ page }) => {