From 8baaa0d760975f8522ac0d06495000e1a10e0de6 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Tue, 26 May 2026 20:42:34 +0300 Subject: [PATCH] harden(motion): global prefers-reduced-motion defence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit: only 3 selectors respected prefers-reduced-motion (.hover- border-gradient, .bento-card, .admin-orb). Codebase has ~358 animation declarations and ~970 transition declarations — without a global rule the rest ignored the OS-level preference. Add a universal rule that collapses every animation and transition to 0.01ms when the user has requested reduced motion. State-conveying motion (toast slide-in, modal fade, accordion expand) still completes to its final frame — only the journey is skipped. Pure decorative motion (background gradients, etc.) effectively disappears. The 3 specific rules above stay as documentation of intent — this catch-all subsumes them via !important. Affects every animation in the app without per-call-site touchups. WCAG 2.3.3. Note: dangerouslySetInnerHTML across all 17 hit sites was already sanitized via DOMPurify (getSvgHtml uses SVG profile, renderMarkdown uses strict ALLOWED_TAGS, linkifyText / sanitizeHtml / formatContent all wire through DOMPurify). The earlier audit P2 flag was a false positive — no source change needed for that item. --- src/styles/globals.css | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/styles/globals.css b/src/styles/globals.css index d233437..d18749b 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -51,6 +51,24 @@ } } +/* Global reduced-motion defence — collapses every animation and transition + to ~0ms so users with vestibular disorders, motion sensitivity, or just + the OS-level preference set get instant state changes. State-conveying + motion still arrives at its final frame; only the journey is skipped. + Specific rules above (animation-play-state: paused, animation: none) + stay as documentation of intent — this rule subsumes them via !important. + WCAG 2.3.3 (Animation from Interactions). */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + @keyframes border-rotate { to { --border-angle: 360deg;