feat: seasonal theme overlays + improved animated chat backgrounds
Adds 11 CSS-only seasonal overlays (Halloween, Christmas, New Year, Autumn, April Fool's, Lunar New Year, Valentine's Day, St. Patrick's Day, Earth Day, Deep Space, Retro Arcade) with date-based auto-detection and a manual override dropdown in Settings → Appearance → Seasonal Theme. All themes respect prefers-reduced-motion. SeasonalEffect mounts at z-index 9997 in App.tsx. Also rewrites all 5 animated chat background keyframes for smoother, more organic motion: Digital Rain gains a phosphor glow flicker; Star Drift now loops each layer by exactly its own tile size (no more seam); Grid Pulse adds an independent brightness oscillation at a prime period; Aurora Flow drives all four gradient layers through distinct paths; Fireflies adds glow-pulse and opacity-blink animations at prime periods for unsynchronised bioluminescence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,38 +70,86 @@ export const MsgAppearClass = style({
|
||||
},
|
||||
});
|
||||
|
||||
// Animated chat background keyframes
|
||||
// ─── Animated chat background keyframes ───────────────────────────────────────
|
||||
|
||||
// Animated chat background keyframes
|
||||
|
||||
/** Matrix rain — two stripe layers scroll at different speeds for parallax depth */
|
||||
/**
|
||||
* 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' },
|
||||
});
|
||||
|
||||
/** Drifting stars — three layers drift diagonally */
|
||||
export const animStarsDriftKeyframe = keyframes({
|
||||
from: { backgroundPosition: '0 0, 65px 32px, 32px 97px' },
|
||||
to: { backgroundPosition: '130px 130px, 195px 162px, 162px 227px' },
|
||||
/** 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)' },
|
||||
});
|
||||
|
||||
/** Grid pulse — expands/contracts backgroundSize slightly */
|
||||
/**
|
||||
* 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: '66px 66px, 66px 66px, 13px 13px, 13px 13px' },
|
||||
'50%': { backgroundSize: '68px 68px, 68px 68px, 13px 13px, 13px 13px' },
|
||||
'100%': { backgroundSize: '60px 60px, 60px 60px, 12px 12px, 12px 12px' },
|
||||
});
|
||||
|
||||
/** Aurora — sweeps backgroundPosition in large cycle */
|
||||
export const animAuroraKeyframe = keyframes({
|
||||
'0%': { backgroundPosition: '0% 0%' },
|
||||
'50%': { backgroundPosition: '-50% -25%' },
|
||||
'100%': { backgroundPosition: '0% 0%' },
|
||||
/** 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)' },
|
||||
});
|
||||
|
||||
/** Fireflies — three layers of glowing dots drift diagonally */
|
||||
/**
|
||||
* 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 150px, 320px 230px, 260px 290px' },
|
||||
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 },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user