diff --git a/CHANGELOG.md b/CHANGELOG.md index 6382391..66d7860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- On an iPhone, the bar around the Dynamic Island is the theme's background, not the system's own black or white. +- On an iPhone, the top bar's colour runs all the way up behind the status bar and the Dynamic Island, with no band of another colour above it. - A feed's buttons stay on one row on a narrow phone, and the reader's buttons stay together instead of breaking across lines under a long feed name. - A moment's DNS failure no longer marks a feed as failing for a day or calls it gone: iPX tries it again in ten minutes. One failed lookup one morning left 205 podcasts unread, uncategorised and labelled gone until the next day. A name that does not resolve is said to be maybe gone, and only once it has been failing a day, on its Directory page as on its own. diff --git a/tests/ui/app.spec.js b/tests/ui/app.spec.js index 5d404f9..306a8ba 100644 --- a/tests/ui/app.spec.js +++ b/tests/ui/app.spec.js @@ -1667,9 +1667,11 @@ test.describe('on a narrow phone', () => { const tops = await page.locator('#content .acts .btn').evaluateAll(bs => bs.map(b => b.getBoundingClientRect().top)); expect(tops.length).toBe(7); expect(new Set(tops).size).toBe(1); - // iOS paints the status bar round the Dynamic Island in theme-color, its own black or white - // without one (#147). - expect(await page.evaluate(() => document.querySelector('meta[name=theme-color]').content)) - .toBe(await page.evaluate(() => getComputedStyle(document.body).backgroundColor)); + // iOS 26 paints behind the status bar and the Dynamic Island in the colour of a sticky bar at + // the top edge, older ones in theme-color: both the top bar's, which is the page's (#147). + const bar = await page.locator('#topbar').evaluate(b => [getComputedStyle(b).position, b.getBoundingClientRect().top, getComputedStyle(b).backgroundColor]); + expect(bar.slice(0, 2)).toEqual(['sticky', 0]); + expect(bar[2]).toBe(await page.evaluate(() => getComputedStyle(document.body).backgroundColor)); + expect(await page.evaluate(() => document.querySelector('meta[name=theme-color]').content)).toBe(bar[2]); }); }); diff --git a/web/app.css b/web/app.css index 65b25fb..bf3ccf6 100644 --- a/web/app.css +++ b/web/app.css @@ -458,6 +458,10 @@ /* A rule that sets display beats the UA's [hidden], and several below do. */ [hidden]{display:none!important} html,body{height:100%} +/* What iOS paints behind the status bar where nothing sticky is at the top edge, as in the + reader on a phone, which brings its own. With no colour of its own, the root went black or + white there instead (#147). */ +html{background-color:var(--bg)} body{ margin:0;background:var(--bg);color:var(--fg); font:14.5px/1.55 Inter,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; @@ -522,6 +526,10 @@ a{color:var(--accent)} padding-top:calc(7px + var(--safe-t));padding-bottom:7px; padding-left:calc(12px + var(--safe-l));padding-right:calc(12px + var(--safe-r)); background:var(--panel);border-bottom:1px solid var(--line); + /* Nothing scrolls under it: it is sticky only because iOS 26 paints the strip behind the status + bar and the Dynamic Island in the colour of a sticky or fixed bar at the top edge, and ignores + theme-color, so the bar now runs all the way up behind the island (#147). */ + position:sticky;top:0; } #topbar .grow{flex:1} #topbar #epSearch{width:260px;flex:none} @@ -1265,6 +1273,8 @@ body:has(#player.on) #status{padding-bottom:3px} :root[data-theme="classic"] #topbar, :root[data-theme="classic"] #status, :root[data-theme="classic"] #player{background:linear-gradient(#ececec,#c9c9c9);border-color:#8e8e8e;color:#1a1a1a} +/* The colour iOS takes for above the bar, where it reads no gradient: the top of the metal. */ +:root[data-theme="classic"] #topbar{background-color:#ececec} :root[data-theme="classic"] #status{text-align:center} :root[data-theme="classic"] #sidebar{background:#e7ebf1;border-right-color:#a3a3a3} :root[data-theme="classic"] #files{background:#f4f4f4} diff --git a/web/src/theme.ts b/web/src/theme.ts index 0e488b0..0d8d979 100644 --- a/web/src/theme.ts +++ b/web/src/theme.ts @@ -38,9 +38,10 @@ function setTheme(name = theme.name, mode = theme.mode, save = false){ : theme.mode === 'auto' ? (systemDark && !systemDark.matches ? 'light' : 'dark') : theme.mode; // The tab's icon in the same variant as the logo on the page. const fav = $('#favicon'); if(fav) fav.href = fav.dataset[root.dataset.mode]; - // The bar iOS draws round the Dynamic Island takes this colour, as Android's toolbar does. - // Without it the bar was the system's own black or white, a band across the top of the page. - const tc = $('meta[name=theme-color]'); if(tc) tc.content = getComputedStyle(document.body).backgroundColor; + // The top bar's colour for an iPhone before iOS 26 to paint behind the status bar, as Android's + // toolbar does; the page's where the bar is see-through (Glass). iOS 26 reads the bar itself. + const tc = $('meta[name=theme-color]'), bar = $('#topbar'); + if(tc){ const c = bar ? getComputedStyle(bar).backgroundColor : ''; tc.content = /^rgb\(/.test(c) ? c : getComputedStyle(root).backgroundColor; } const sel = $('#stheme'); if(sel) sel.value = theme.name; const ms = $('#smode'); if(ms) ms.value = theme.mode; const mf = $('#smodefield'); if(mf) mf.hidden = !both;