diff --git a/src/pages/index.astro b/src/pages/index.astro
index 54eda96..4f0964f 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -15,10 +15,12 @@ const canonical = new URL('/', Astro.site);
hand those 120px to the page, so no element and no viewport unit reaches them —
see the note on .ambient for what actually resolves it. -->
-
-
+
+
{title}
@@ -175,10 +177,10 @@ const canonical = new URL('/', Astro.site);
right: 0;
left: 0;
/* 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. Sized to the large viewport, the field paints
- through the toolbar instead. */
+ viewport, leaving the strip between svh and lvh unpainted by this layer — which
+ matters because the toolbar retracts and hands those pixels back to the page.
+ Sized to the large viewport, the layer owns them in both states; what it puts
+ there is the ::after note's business. */
height: 100%;
height: 100lvh;
overflow: hidden;
@@ -194,23 +196,55 @@ const canonical = new URL('/', Astro.site);
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.
+ every surface Safari fills around the page, and the page's own colour where it
+ meets them, must be the same paint. Those surfaces can only be flat colours, and
+ the field is a moving light whose edge tone drifts, so matching them by picking a
+ constant is a value that is wrong again as soon as the bands move — and wrong in a
+ different way in each scroll state, which is what made the bar come and go.
- 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. */
+ The field is therefore brought to zero at both edges of the viewport. --paper is
+ then the only colour in play: canvas, theme-color, overscroll, the chrome strips,
+ and the page itself where it touches any of them. There is no second value left
+ for Safari to swap in, so no scroll state, toolbar collapse or fixed-layer
+ reposition has anything to expose. That is the property to preserve — if this ever
+ needs editing, keep the edges at --paper rather than tuning a colour to match.
+
+ The one thing that has to be right is where the bottom reaches --paper, and it is
+ not a number to taste. This layer is fixed and 100lvh tall, but the bottom edge of
+ what the reader can see is only at 100lvh with the toolbar collapsed; expanded, it
+ is at 100svh, ~100px higher, and Safari slides between the two mid-scroll. Ending
+ the fade at the element's own bottom therefore left the visible edge sitting part
+ way up the fade whenever the toolbar was out — lighter than the strip below it, so
+ the bar came back — and clean again once the toolbar went away. A boundary at a
+ coordinate that moves is not a fix, it is the same bug relocated.
+
+ Nor can that band simply be held flat: those pixels are behind the chrome in one
+ state and are ordinary page in the other, so a flat patch there is invisible with
+ the toolbar out and a hard-edged band the moment it retracts. Both failures are
+ the same failure — an edge existing anywhere the viewport boundary can land.
+
+ So there is no edge. The field decays to --paper continuously, and is already
+ exactly --paper by 100svh, above the highest position the visible bottom can take;
+ everything from there to 100lvh is the same paint. Every boundary the viewport
+ could land on is gone, in either state and part way through the transition between
+ them. The decay is spread over ~14rem because the whole excursion is about five
+ steps of RGB — under a step per 40px, below anything the eye reads as an edge, and
+ the grain dithers the remainder. Keep that shape if this is ever edited: reach
+ --paper at or before 100svh, and get there gradually. Top edge likewise, though it
+ is simpler — the layout viewport's top does not move, so it only needs to arrive.
+ 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%));
+ inset: 0;
+ background: linear-gradient(
+ to bottom,
+ var(--paper) 0,
+ rgb(10 16 12 / 0%) 8rem,
+ rgb(10 16 12 / 0%) calc(100svh - 14rem),
+ var(--paper) 100svh,
+ var(--paper) 100%
+ );
}
/* Oversized so the bands can travel a long way without an edge ever entering the
@@ -679,14 +713,28 @@ const canonical = new URL('/', Astro.site);
which lands on load and leaves it plainly visible. */
@supports (animation-timeline: view()) {
@media (max-width: 720px) {
+ /* Where the reveal runs, the footer is transparent until it is scrolled to, so
+ opacity is what hides it and the layout no longer has to. That buys back both
+ halves of the drop the fallback needs: the toolbar term goes, because a footer
+ behind Safari's bar cannot show through it at zero alpha, and the clearance
+ shrinks to the little that keeps the rule from being mid-fade at rest. All of
+ it was scroll the reader had to spend before anything began to happen. */
+ .page-shell {
+ --footer-drop: clamp(4.75rem, 9.3vh, 5.1rem);
+ }
+
.footer {
opacity: 0;
animation: footer-reveal linear both;
animation-timeline: view();
- /* Finishes at 88% rather than 100%: the last stretch of "entry" is the
- element clearing Safari's translucent toolbar, and waiting that long
- means it is still fading while it looks fully on screen. */
- animation-range: entry 12% entry 88%;
+ /* "entry" opens before the footer's edge reaches the fold, not at it: parked,
+ it already measures 9-11% entered across phone sizes. So the start has to be
+ past that or the footer sits permanently part-faded at rest — 20% clears it
+ on every size tested with room to spare, and still begins the reveal within
+ the first few pixels of scroll. Ending at 45% means it reads as arrived at
+ roughly a third of the travel, instead of still fading while it already
+ looks fully on screen. */
+ animation-range: entry 20% entry 45%;
}
}
}