Phone layout: opaque status bar and one safe-area rule for every edge

Each fix to the home-screen app's layout on iOS broke something else, and they
all traced back to black-translucent: it runs the page under the clock, so any
top-anchored control without its own inset sat behind it (the play view's back
button), and since iOS 26 it sizes the app one status bar short with a strip at
the bottom that CSS cannot paint (WebKit 301108). The status bar is opaque now
("black"), the page starts below it, and lib/fullHeight.ts - the JS workaround
for the short viewport - is gone with the CSS that depended on it.

viewport-fit=cover stays, so everything against a screen edge now adds that
edge's inset from shared --safe-* variables: the corner back button, the
remote-assign button, the assign toast, the tool buttons and the tab rail did
not before. The rules are written down under "Layout on phones" in the README.

On a phone held sideways the mini player and the tabs share one row instead of
taking half the screen.

Home-screen installs read the status-bar tag once: remove and re-add the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 10:46:06 +02:00
parent 34f2f016f9
commit 4428357e13
16 changed files with 115 additions and 92 deletions

View File

@@ -159,6 +159,37 @@ Nunito is self-hosted in `public/fonts/` (one variable file per subset, ~74 kB)
than pulled from Google. The mouse may have no internet, and a cached shell rendering in
a fallback face looks broken.
## Layout on phones
Phones (`usePhoneLayout`: narrow, or short landscape on touch) get the touch layout in
`components/mobile/`; tablets and the kiosk keep the keyboard one. The home-screen app
on iOS has caught every shortcut taken here, so these are the rules - follow them rather
than patching one element at a time:
- **The status bar is opaque** (`apple-mobile-web-app-status-bar-style: black`). The page
starts below it, so nothing can end up under the clock. `black-translucent` looks
nicer, but it ran the page behind the status bar, and since iOS 26 it also sizes the
app one status bar short, leaving a strip at the bottom that CSS cannot paint
([WebKit 301108](https://bugs.webkit.org/show_bug.cgi?id=301108)). The workarounds for
that (`100lvh`, measuring `screen` in JS) each broke something else. Installed apps
read this tag once: after changing it, remove the app and add it again.
- **`viewport-fit=cover` stays**, so the sea fills the rounded corners and the home
indicator area, which means:
- **Anything placed against a screen edge adds that edge's inset**: `--safe-top`,
`--safe-right`, `--safe-bottom` and `--safe-left` in `app.css`, always the variables
and never a bare `env()`. That covers `top`/`bottom`/`left`/`right` offsets of
positioned elements, and the padding of bars and scroll containers that reach the
edge. Scrolling content clears what floats over it (`--m-dock-clearance`). The top
inset is 0 with the opaque status bar, but stays in the formulas for Safari tabs and
landscape.
- **The stage is pinned, not sized**: `position: fixed; inset: 0` on phones, with the sea
colour on `html`/`body` so any gap would show sea, not white. No viewport-height unit
is trusted in the home-screen app.
- **The keyboard is measured, not assumed**: iOS slides it over the page without resizing
anything, so the dock is raised by `visualViewport` (`useKeyboardInset`) and drops its
home-indicator padding while the keyboard is up
([safe-area-inset-bottom does not update for the keyboard](https://webventures.rejh.nl/blog/2025/safe-area-inset-bottom-does-not-update/)).
## Not implemented
The mockup's second page, "Mein Zimmer" (Hue lights, a roller shutter and scenes), is