diff --git a/web/src/App.tsx b/web/src/App.tsx index 77a0aaa..c80ab0f 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -446,6 +446,7 @@ export function App() { return (
diff --git a/web/src/styles/mobile.css b/web/src/styles/mobile.css index 04a39de..6a85767 100644 --- a/web/src/styles/mobile.css +++ b/web/src/styles/mobile.css @@ -13,6 +13,22 @@ --m-dock-clearance: calc(96px + env(safe-area-inset-bottom)); } +/* iOS can leave the layout viewport shorter than the screen - after the keyboard has + been up, `100dvh` in a home-screen app comes back one status bar short - and the + page behind the stage showed as a white strip along the bottom. Pinning the stage to + the viewport's edges does not depend on that height at all, and the sea colour + underneath covers whatever might still peek through. */ +html, +body { + background: var(--stage-deep, #001b21); +} + +.stage[data-phone] { + position: fixed; + inset: 0; + height: auto; +} + .m-glass { background: var(--m-glass-fill); backdrop-filter: blur(14px) saturate(1.7); @@ -39,7 +55,8 @@ overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; - padding: env(safe-area-inset-top) 0 calc(var(--m-dock-clearance) + 84px); + /* No top inset here: the sticky bar reaches up under the status bar itself. */ + padding: 0 0 calc(var(--m-dock-clearance) + 84px); animation: viewEnter 180ms ease-out; } @@ -49,7 +66,7 @@ display: flex; align-items: center; gap: 10px; - padding: 14px var(--m-gutter) 4px; + padding: calc(14px + env(safe-area-inset-top)) var(--m-gutter) 4px; } .m-title-mascot { @@ -82,21 +99,45 @@ color: oklch(80% 0.16 30); } -/* The bar that stays put while the page scrolls under it. Fades from the top of the - sea into transparency rather than ending in a hard edge. */ +/* The bar that stays put while the page scrolls under it. Sticks to the very top of + the screen, under the status bar, so content never shows behind the clock. + + Frosted rather than painted: the background behind it is the ambience canvas, + which takes its colour from whatever is playing, so any fixed fill was the wrong + colour as soon as an album with different art started. A blur with a neutral + darkening suits every one of them, and the mask fades it out instead of ending in + a hard edge. */ .m-sticky { position: sticky; top: 0; z-index: 2; - padding: 10px var(--m-gutter) 14px; - background: linear-gradient(180deg, #2f6f7aee 0%, #2f6f7a99 65%, #2f6f7a00 100%); + padding: calc(10px + env(safe-area-inset-top)) var(--m-gutter) 14px; +} + +/* Under the title row, overlap the row by the status bar's height: the bar keeps its + place in the layout but already covers the status bar once it sticks. */ +.m-title-row + .m-sticky { + margin-top: calc(-1 * env(safe-area-inset-top)); +} + +.m-sticky::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + background: oklch(0% 0 0 / 0.16); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent); + mask-image: linear-gradient(180deg, #000 70%, transparent); + pointer-events: none; } .m-navbar { display: flex; align-items: center; gap: 10px; - padding-top: 12px; + padding-top: calc(12px + env(safe-area-inset-top)); } .m-navbar-title {