Web frontend: phone header glass and full-height stage

On an iPhone the sticky group switch sat on a fixed teal fade, which was the
wrong colour as soon as the ambience took on another album's tint (brown for
Conni). It is now a neutral frosted blur that fades out, so it suits whatever
is playing, and it reaches up under the status bar so scrolled content never
shows behind the clock.

After the search keyboard had been up, 100dvh in the home-screen app came back
a status bar short and the white page showed as a strip along the bottom. On
phones the stage is pinned to the viewport edges instead, with the sea colour
behind it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 22:45:52 +02:00
parent 6981890be0
commit 21d65cad9e
2 changed files with 49 additions and 7 deletions

View File

@@ -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 {