import { keyframes, style } from '@vanilla-extract/css'; import { color, toRem } from 'folds'; const spin = keyframes({ from: { transform: 'rotate(0deg)' }, to: { transform: 'rotate(360deg)' }, }); const wobble = keyframes({ '0%': { transform: 'translateX(0) rotateZ(0deg)', }, '20%': { transform: `translateX(-${toRem(4)}) rotateZ(-4deg)`, }, '40%': { transform: `translateX(${toRem(4)}) rotateZ(4deg)`, }, '60%': { transform: `translateX(-${toRem(3)}) rotateZ(-3deg)`, }, '80%': { transform: `translateX(${toRem(3)}) rotateZ(3deg)`, }, '100%': { transform: 'translateX(0) rotateZ(0deg)', }, }); const glowPulse = keyframes({ '0%': { boxShadow: `0 0 0 ${toRem(0)} ${color.Success.ContainerActive}`, }, '100%': { boxShadow: `0 0 0 ${toRem(8)} ${color.Success.ContainerActive}`, }, }); export const WobbleAnimation = style({ animation: `${wobble} 2000ms ease-in-out`, animationIterationCount: 'infinite', }); export const GlowAnimation = style({ animation: `${glowPulse} 2000ms ease-out`, animationIterationCount: 'infinite', }); export const CallAvatarAnimation = style({ animation: `${wobble} 2000ms ease-in-out, ${glowPulse} 2000ms ease-out`, animationIterationCount: 'infinite', }); export const SendingSpinClass = style({ display: 'inline-block', animation: `${spin} 900ms linear infinite`, transformOrigin: 'center', }); const msgAppearKeyframe = keyframes({ from: { opacity: 0.4, transform: 'scale(0.97)' }, to: { opacity: 1, transform: 'scale(1)' }, }); export const MsgAppearClass = style({ '@media': { '(prefers-reduced-motion: no-preference)': { animation: `${msgAppearKeyframe} 150ms ease-out both`, }, }, }); // ─── Animated chat background keyframes ─────────────────────────────────────── /** * Digital Rain — vertical stripe scroll with a phosphor-glow flicker layered on top. * Two stripe layers at different column widths and speeds create parallax depth. */ export const animRainKeyframe = keyframes({ from: { backgroundPosition: '0 0, 0 0' }, to: { backgroundPosition: '0 200px, 0 100px' }, }); /** Phosphor flicker brightness pulse layered over the rain scroll. */ export const animRainGlowKeyframe = keyframes({ '0%': { filter: 'brightness(0.85)' }, '30%': { filter: 'brightness(1.25)' }, '60%': { filter: 'brightness(0.9)' }, '80%': { filter: 'brightness(1.1)' }, '100%': { filter: 'brightness(0.85)' }, }); /** * Star Drift — three dot layers, each moving by exactly one tile width/height per * cycle so loops are seamless even though each layer drifts at a different speed. * Layer sizes: 130 px, 190 px, 260 px → displacement equals one tile each. */ export const animStarsDriftKeyframe = keyframes({ from: { backgroundPosition: '0 0, 65px 32px, 32px 97px' }, to: { backgroundPosition: '-130px -130px, -125px -158px, -228px -163px' }, }); /** * Grid Pulse — subtle backgroundSize breathe so the grid feels alive. * Paired with animGridBrightnessKeyframe at a different period for organic feel. */ export const animGridPulseKeyframe = keyframes({ '0%': { backgroundSize: '60px 60px, 60px 60px, 12px 12px, 12px 12px' }, '50%': { backgroundSize: '68px 68px, 68px 68px, 13px 13px, 13px 13px' }, '100%': { backgroundSize: '60px 60px, 60px 60px, 12px 12px, 12px 12px' }, }); /** Brightness oscillation layered on top of the grid size pulse. */ export const animGridBrightnessKeyframe = keyframes({ '0%': { filter: 'brightness(0.8)' }, '50%': { filter: 'brightness(1.3)' }, '100%': { filter: 'brightness(0.8)' }, }); /** * Aurora Flow — four gradient layers each travel a distinct path through the * 200%–300% canvas, driven by one multi-stop keyframe so they never sync. * Each background-position value corresponds to one radial-gradient layer. */ export const animAuroraKeyframe = keyframes({ '0%': { backgroundPosition: '0% 0%, 100% 0%, 50% 100%, 0% 50%' }, '20%': { backgroundPosition: '60% 40%, 20% 80%, 80% 20%, 100% 0%' }, '40%': { backgroundPosition: '100% 100%, 0% 100%, 100% 0%, 50% 50%' }, '60%': { backgroundPosition: '40% 80%, 80% 30%, 20% 70%, 0% 100%' }, '80%': { backgroundPosition: '0% 50%, 50% 0%, 0% 50%, 100% 50%' }, '100%': { backgroundPosition: '0% 0%, 100% 0%, 50% 100%, 0% 50%' }, }); /** * Fireflies drift — three dot layers move at slightly different diagonal vectors * (each offset equals exactly one tile so loops are seamless). */ export const animFirefliesKeyframe = keyframes({ from: { backgroundPosition: '0 0, 120px 80px, 60px 140px' }, to: { backgroundPosition: '-200px -200px, -80px -120px, -140px -60px' }, }); /** Brightness surge — gives fireflies a "glow pulse" life cycle. */ export const animFirefliesGlowKeyframe = keyframes({ '0%': { filter: 'brightness(0.4)' }, '50%': { filter: 'brightness(1.8)' }, '100%': { filter: 'brightness(0.4)' }, }); /** Opacity blink — runs at a prime period relative to the glow for organic feel. */ export const animFirefliesBlinkKeyframe = keyframes({ '0%': { opacity: 0.35 }, '50%': { opacity: 1 }, '100%': { opacity: 0.35 }, });