diff --git a/src/pages/index.astro b/src/pages/index.astro
index cf778ba..b61d472 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -9,11 +9,15 @@ const canonical = new URL('/', Astro.site);
+
-
+
{title}
@@ -173,14 +177,42 @@ const canonical = new URL('/', Astro.site);
/* Deliberately not inset: 0. A fixed box pinned to the bottom stops at the small
viewport, but iOS Safari keeps rendering the page behind its translucent
toolbar — so the strip between svh and lvh fell back to flat paper and read as
- a grey bar under the chrome, several shades off the ambient-lit page above it.
- Sized to the large viewport, the field paints through the toolbar instead. */
+ a grey bar under the chrome. Sized to the large viewport, the field paints
+ through the toolbar instead. */
height: 100%;
height: 100lvh;
overflow: hidden;
pointer-events: none;
}
+ /* The grey bar, properly.
+
+ iOS reserves a strip above and below the layout viewport for its own chrome, and
+ fills it from the document canvas — which takes its paint from the root element
+ and nowhere else. Measured on iOS 27: screen.height 874, 100lvh 754, both safe-area
+ insets 0. Those 120px are not addressable by the page. No height, no viewport unit
+ and no viewport-fit=cover reaches them; earlier attempts only moved the seam.
+
+ So the canvas is the single point, and there is exactly one thing to get right:
+ the canvas colour and the page's own edge colour have to be equal. The canvas can
+ only be flat --paper, and the field is a moving light whose edge tone drifts, so
+ matching by picking a constant is a value that is wrong again as soon as the bands
+ move. Instead the field is brought to zero at the bottom of the viewport — then
+ page edge, canvas and theme-color are all --paper by construction, whatever the
+ animation is doing, and the seam cannot come back.
+
+ Costs nothing to look at: this falls inside the strip Safari's toolbar already
+ covers. Static, so the layer still composites without repainting per frame. */
+ .ambient::after {
+ content: '';
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ height: 5rem;
+ background: linear-gradient(to top, var(--paper), rgb(10 16 12 / 0%));
+ }
+
/* Oversized so the bands can travel a long way without an edge ever entering the
frame — what should be visible is the light moving, never the thing carrying it. */
.ambient-haze,
@@ -241,6 +273,7 @@ const canonical = new URL('/', Astro.site);
/* One inset for everything. It is deliberately generous: at mid widths a tighter
margin puts the copy and the window edge in each other's way. */
--edge: clamp(1.35rem, 4.8vw, 5rem);
+ --shell-y: clamp(1.25rem, 2.6vh, 2.5rem);
position: relative;
display: grid;
@@ -248,7 +281,7 @@ const canonical = new URL('/', Astro.site);
width: min(100%, 1600px);
min-height: 100svh;
margin: 0 auto;
- padding: clamp(1.25rem, 2.6vh, 2.5rem) var(--edge);
+ padding: var(--shell-y) var(--edge);
}
.masthead,