The site stops flashing white on every click

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.
This commit is contained in:
2026-07-26 22:07:52 -05:00
parent 4b40acf6b8
commit 79fa690f7b
+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 * Element defaults: the page background, the body type, how one page gives way to the next, and the two
* needs. Anything with a class name belongs in components.css instead. * small resets the site needs. Anything with a class name belongs in components.css instead.
*/ */
html { html {
@@ -14,23 +14,32 @@ body {
color: var(--color-bakery-900); color: var(--color-bakery-900);
font-family: var(--font-sans); font-family: var(--font-sans);
overflow-x: hidden; 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) { @media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; } html { scroll-behavior: auto; }
body { animation: none; opacity: 1; } /* Cut the cross-fade, keep the navigation. */
} ::view-transition-old(root),
::view-transition-new(root) {
@keyframes fadeIn { animation: none;
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
} }
} }
@@ -109,8 +118,6 @@ select.field {
body { body {
background: #fff; background: #fff;
color: #000; color: #000;
opacity: 1;
animation: none;
} }
/* Sage-on-white blocks become plain text: the words matter, the ink does not. */ /* Sage-on-white blocks become plain text: the words matter, the ink does not. */