harden(motion): global prefers-reduced-motion defence

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.
This commit is contained in:
c0mrade
2026-05-26 20:42:34 +03:00
parent 80898ee2fe
commit 8baaa0d760

View File

@@ -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;