From 41ccb3af55d189b5aedd4ee497e49ceda4f1fc8e Mon Sep 17 00:00:00 2001
From: Fuwn <contact@fuwn.net>
Date: Tue, 19 May 2026 02:48:04 +0000
Subject: feat(a11y): rename Disable Page Transitions to Reduce Motion

The setting previously only gated the fly slide in Root.svelte.
Renaming the label to "Reduce motion" sets the expectation that it
acts as a force-on for the browser's prefers-reduced-motion: reduce
across the board, so the setting's scope is broadened to match.

Storage key (displayDisableAnimations) stays the same so existing
user preferences carry over. Only the i18n label and behavior scope
change.

Adds a reducesMotion() helper that combines the media query with
the setting. View Transitions, Lenis init, and the details JS
animation now all bypass via this helper. A reactive $: toggles
html.reduce-motion on the root element based on the setting; the
normalise.css bandaid that already collapsed animations under
prefers-reduced-motion is duplicated under that class so every CSS
animation and transition (Svelte fly, dropdown panels, hamburger
morph, header scroll-hide, the global :where transitions) snaps to
0.01ms when the setting is on.
---
 src/routes/+layout.svelte | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

(limited to 'src/routes/+layout.svelte')

diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index a61d0a29..9ad33ca9 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -91,10 +91,19 @@ $: way = data.url.includes("/user")
 
 $: if ($navigating) isMenuOpen = false;
 
+const reducesMotion = () =>
+	window.matchMedia("(prefers-reduced-motion: reduce)").matches ||
+	get(settings).displayDisableAnimations;
+
+$: if (browser)
+	document.documentElement.classList.toggle(
+		"reduce-motion",
+		$settings.displayDisableAnimations,
+	);
+
 onNavigate((navigation) => {
 	if (!("startViewTransition" in document)) return;
-	if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
-	if (get(settings).displayDisableAnimations) return;
+	if (reducesMotion()) return;
 
 	const fromPath = navigation.from?.url.pathname ?? "/";
 	const toPath = navigation.to?.url.pathname ?? "/";
@@ -138,7 +147,7 @@ const animateDetails = (e: MouseEvent) => {
 	if (!summary) return;
 	const details = summary.parentElement as HTMLDetailsElement | null;
 	if (!details || details.tagName !== "DETAILS") return;
-	if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
+	if (reducesMotion()) return;
 
 	e.preventDefault();
 	detailsAnimations.get(details)?.cancel();
@@ -182,7 +191,7 @@ const animateDetails = (e: MouseEvent) => {
 
 onMount(async () => {
 	if (browser) {
-		if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
+		if (!reducesMotion()) {
 			lenis = new Lenis({ autoRaf: true });
 
 			lenisStore.set(lenis);
-- 
cgit v1.2.3

