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%; } } }