The site stops flashing white on every click
build-and-publish / build (pull_request) Successful in 2m15s

Clicking a nav link or a filter chip made the whole site look like it reloaded,
because it did: body carried `opacity: 0` and a half-second fadeIn, so every
navigation went blank, then slid 10px and faded back.

Those two lines are the SPA's. There they ran once, when React booted, and every
navigation afterwards happened inside a document that had already faded in.
Server-rendered, every link is a new document, so a once-per-session animation
became a once-per-click one — and the thing it was decorating was already fast.

Replaced with the cross-document view transition it was reaching for: the
browser holds the old page up until the new one is ready and cross-fades, so
nothing is ever blank. Where it isn't supported the navigation is simply
immediate, which is the right fallback and still better than a blank page.
Reduced motion keeps the navigation and drops the cross-fade; the print rule no
longer has to undo an opacity that isn't set any more.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
2026-07-26 22:07:52 -05:00
co-authored by Claude Opus 5
parent b344d45978
commit 5aa2e4589e
+24 -17
View File
@@ -1,6 +1,6 @@
/*
* Element defaults: the page background, the body type, the fade-in, and the two small resets the site
* needs. Anything with a class name belongs in components.css instead.
* Element defaults: the page background, the body type, how one page gives way to the next, and the two
* small resets the site needs. Anything with a class name belongs in components.css instead.
*/
html {
@@ -14,23 +14,32 @@ body {
color: var(--color-bakery-900);
font-family: var(--font-sans);
overflow-x: hidden;
opacity: 0;
animation: fadeIn 0.5s ease-in forwards;
}
/*
* BETWEEN PAGES.
*
* The body used to open at `opacity: 0` and fade in over half a second on every page. That belonged to
* the SPA, where it ran ONCE, when React booted — the rest of the site's navigation happened inside a
* document that had already faded in. Server-rendered, every nav link and every filter chip is a new
* document, so the same two lines fired on every click: the page went blank, then slid 10px and faded
* back. A navigation that was already fast read as the whole site reloading.
*
* A cross-document view transition is what that fade was reaching for. The browser holds the old page
* up until the new one is ready and cross-fades between the two, so nothing is ever blank — and where
* it isn't supported you get a plain, immediate page load, which is the correct fallback and is still
* better than what this replaced.
*/
@view-transition {
navigation: auto;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
body { animation: none; opacity: 1; }
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
/* Cut the cross-fade, keep the navigation. */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
}
@@ -109,8 +118,6 @@ select.field {
body {
background: #fff;
color: #000;
opacity: 1;
animation: none;
}
/* Sage-on-white blocks become plain text: the words matter, the ink does not. */