feat(P5-4): animated chat backgrounds + pause toggle

5 new CSS-only animated backgrounds in the chat background picker:
- Digital Rain: two-layer vertical stripe scroll with parallax (wide
  stripes at 8s, narrow at 4s via single keyframe with split positions)
- Star Drift: three-layer radial-gradient star field drifting diagonally
- Grid Pulse: neon grid lines that expand/contract (backgroundSize keyframe)
- Aurora Flow: large radial gradient bands sweeping across 200% canvas
- Fireflies: three layers of glowing dots drifting across the viewport

All use vanilla-extract keyframes (GPU-composited transforms/positions,
no canvas, no JS timers). prefers-reduced-motion is respected in
getChatBg() by stripping the animation property at call time. A "Pause
Background Animations" toggle in Settings → Appearance provides an
in-app override for the same purpose.

BG labels de-duplicated ("Digital Rain", "Star Drift", "Aurora Flow")
to avoid the duplicate "Stars" and "Aurora" entries that had appeared.
LIGHT anim-fireflies background corrected from near-black #0a0a10 to
warm white #fffdf0. Four unused keyframe exports removed from
Animations.css.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 11:46:16 -04:00
parent 8ac63e3771
commit 9447e64e5e
6 changed files with 224 additions and 8 deletions
+6 -2
View File
@@ -31,6 +31,7 @@ export function SidebarNav() {
const [glassmorphismSidebar] = useSetting(settingsAtom, 'glassmorphismSidebar');
const [chatBackground] = useSetting(settingsAtom, 'chatBackground');
const [lotusTerminal] = useSetting(settingsAtom, 'lotusTerminal');
const [pauseAnimations] = useSetting(settingsAtom, 'pauseAnimations');
const theme = useTheme();
const isDark = theme.kind === ThemeKind.Dark;
@@ -44,21 +45,24 @@ export function SidebarNav() {
style.removeProperty('background-color');
style.removeProperty('background-size');
style.removeProperty('background-position');
style.removeProperty('animation');
return;
}
const effectiveBg = lotusTerminal && chatBackground === 'none' ? 'tactical' : chatBackground;
const bgStyle = getChatBg(effectiveBg, isDark);
const bgStyle = getChatBg(effectiveBg, isDark, pauseAnimations);
style.backgroundImage = (bgStyle.backgroundImage as string | undefined) ?? '';
style.backgroundColor = (bgStyle.backgroundColor as string | undefined) ?? '';
style.backgroundSize = (bgStyle.backgroundSize as string | undefined) ?? '';
style.backgroundPosition = (bgStyle.backgroundPosition as string | undefined) ?? '';
style.animation = (bgStyle.animation as string | undefined) ?? '';
return () => {
style.removeProperty('background-image');
style.removeProperty('background-color');
style.removeProperty('background-size');
style.removeProperty('background-position');
style.removeProperty('animation');
};
}, [glassmorphismSidebar, chatBackground, lotusTerminal, isDark]);
}, [glassmorphismSidebar, chatBackground, lotusTerminal, isDark, pauseAnimations]);
return (
<Sidebar className={classNames(glassmorphismSidebar && SidebarGlass)}>