From c3e1fbfff547d92a65cd149f2ca8ad876d87b626 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 19 Jul 2026 00:00:07 -0400 Subject: [PATCH] fix(a11y): honor prefers-reduced-motion for avatar decorations (P2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avatar decorations are animated APNGs and were the only motion feature not gated on prefers-reduced-motion (chat backgrounds / seasonal overlays all suppress motion under it). Since there's no static-frame asset to freeze to, render just the avatar (no decoration overlay) when the user prefers reduced motion — the only motion-respecting option. Users without the preference are unaffected; live OS-toggle is reactive via useReducedMotion. Also relieves the mobile perf drain of dozens of live APNGs in scrolling lists. Reviewed: correct a11y behavior, hooks-safe, no layout dependency on the overlay. Co-Authored-By: Claude Opus 4.8 --- .../components/avatar-decoration/AvatarDecoration.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/components/avatar-decoration/AvatarDecoration.tsx b/src/app/components/avatar-decoration/AvatarDecoration.tsx index 26666f610..8f44a7a70 100644 --- a/src/app/components/avatar-decoration/AvatarDecoration.tsx +++ b/src/app/components/avatar-decoration/AvatarDecoration.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { useAvatarDecoration } from '../../hooks/useAvatarDecoration'; +import { useReducedMotion } from '../../hooks/useReducedMotion'; import { decorationUrl } from '../../features/lotus/avatarDecorations'; const DEFAULT_INSET = 8; @@ -16,8 +17,14 @@ export function AvatarDecoration({ inset = DEFAULT_INSET, }: AvatarDecorationProps) { const slug = useAvatarDecoration(userId); + const reducedMotion = useReducedMotion(); - if (!slug) { + // Decorations are animated APNGs with no static asset to freeze to, so honor + // prefers-reduced-motion by not rendering the animation at all (consistent + // with the rest of the theming stack — chat backgrounds / seasonal overlays — + // which all suppress motion under this preference; also avoids dozens of live + // APNGs animating in scrolling mobile lists). + if (!slug || reducedMotion) { return <>{children}; }