Phone layout: make the whole document reach the bottom in the home-screen app

Sizing the stage from the screen height was not enough on iOS 26: with the layout
viewport a status bar short, WebKit clips at the short viewport and draws nothing
below the document, so the dock still floated over a dark strip. In standalone mode
`<html>` now takes the larger of `100vh` and the screen height too, and the stage
follows it.

Coming back from the background, or a relaunch restoring the page, is when iOS is most
likely to hand back a short viewport and it does not always fire `resize`, so the
height is also re-applied on `pageshow` and `visibilitychange`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:29:50 +02:00
parent 34656a8517
commit f67f84ded3
2 changed files with 19 additions and 7 deletions

View File

@@ -34,4 +34,8 @@ export function installFullHeight(): void {
apply(); apply();
window.addEventListener("resize", apply); window.addEventListener("resize", apply);
window.addEventListener("orientationchange", apply); window.addEventListener("orientationchange", apply);
// Coming back from the background (or a relaunch restoring the page) is when iOS is
// most likely to hand back a short viewport, and it does not always fire `resize`.
window.addEventListener("pageshow", apply);
document.addEventListener("visibilitychange", apply);
} }

View File

@@ -13,17 +13,25 @@
--m-dock-clearance: calc(96px + env(safe-area-inset-bottom)); --m-dock-clearance: calc(96px + env(safe-area-inset-bottom));
} }
/* iOS can leave the layout viewport shorter than the screen in a home-screen app - /* iOS 26 can leave the layout viewport of a home-screen app one status bar short -
`100dvh`, and even `position: fixed; inset: 0`, come back one status bar short - so `100dvh`, `innerHeight` and `position: fixed; inset: 0` all come back short, and
the dock floated that far above the bottom with an empty strip below it. There the WebKit draws nothing below the document - so the dock floated that far above the
stage takes its height from the screen instead (`--full-height` and bottom over a dark strip. Sizing the stage alone was not enough: it got clipped at
`data-standalone`, set by lib/fullHeight.ts only in standalone mode); everywhere else it pins to the viewport. the short viewport. The document itself has to reach the bottom of the screen, so in
The sea colour underneath covers whatever might still peek through. */ standalone mode (`data-standalone` and `--full-height`, set by lib/fullHeight.ts)
`<html>` takes the larger of `100vh` and the screen's height, and the stage follows.
Everywhere else the stage just pins to the viewport. The sea colour underneath
covers whatever might still peek through. */
html, html,
body { body {
background: var(--stage-deep, #001b21); background: var(--stage-deep, #001b21);
} }
:root[data-standalone] {
height: max(100vh, var(--full-height, 0px));
overflow: hidden;
}
.stage[data-phone] { .stage[data-phone] {
position: fixed; position: fixed;
inset: 0; inset: 0;
@@ -32,7 +40,7 @@ body {
:root[data-standalone] .stage[data-phone] { :root[data-standalone] .stage[data-phone] {
bottom: auto; bottom: auto;
height: var(--full-height); height: max(100vh, var(--full-height, 0px));
} }
.m-glass { .m-glass {