From 7f9d6f8bf05f3346cba295760657b62dfebc982a Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Fri, 3 Jul 2026 05:07:30 +0700 Subject: [PATCH] ui: crossfade page changes with cross-document view transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same-origin page loads now crossfade via the CSS-only @view-transition opt-in (Chrome 126+ / Safari 18.2+; other browsers keep the instant swap). The navbar and sidebar carry their own view-transition-name so the app shell stays put — only the content and the active sidebar item crossfade, at --timing-fast (120ms). Gated on prefers-reduced-motion: no-preference. --- assets/style/_base.scss | 18 ++++++++++++++++++ assets/style/_layout.scss | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/assets/style/_base.scss b/assets/style/_base.scss index 5957692..cde1c25 100644 --- a/assets/style/_base.scss +++ b/assets/style/_base.scss @@ -48,3 +48,21 @@ body { border-radius: var(--radius-sm); } .skip-link:focus { left: 0.5rem; } + +/* Page-change animation (cross-document view transitions): same-origin + * page loads crossfade instead of flashing (Chrome 126+ / Safari 18.2+; + * other browsers keep the instant swap). Navbar + sidebar carry their own + * snapshots (_layout.scss) so the app shell stays put. group(*) included: + * its geometry animation would otherwise run at the UA default 250ms and + * hold the transition open after the fades end. */ +@media (prefers-reduced-motion: no-preference) { + @view-transition { + navigation: auto; + } + + ::view-transition-group(*), + ::view-transition-old(*), + ::view-transition-new(*) { + animation-duration: var(--timing-fast); + } +} diff --git a/assets/style/_layout.scss b/assets/style/_layout.scss index bb708df..1fb795e 100644 --- a/assets/style/_layout.scss +++ b/assets/style/_layout.scss @@ -15,6 +15,10 @@ backdrop-filter: saturate(140%) blur(12px); border-bottom: 1px solid var(--color-ink-line); color: var(--color-content-inv); + /* Own view-transition snapshot: the app shell stays put while the page + * crossfades (_base.scss). The backdrop blur isn't part of the element's + * own paint, so it drops out for the 120ms transition — imperceptible. */ + view-transition-name: navbar; } .navbar-brand { @@ -143,6 +147,9 @@ html.dark .navbar-theme-toggle .icon-moon { display: inline-block; } padding: 1.75rem 0.75rem 3rem 1.25rem; border-right: 1px solid var(--color-line); background-color: var(--color-base); + /* Own view-transition snapshot: stays put during page changes; only the + * active item crossfades. */ + view-transition-name: sidebar; } .sidebar-mobile-nav { display: none;