26f998d243
Split the 808-line SeasonalEffect monolith into one self-contained module per theme under seasonal/themes/ (<Theme>.tsx + <Theme>.css.ts), and gave every theme a premium, research-backed redesign (one Opus agent per theme against a shared brief). SeasonalEffect now just imports the 11 overlays and dispatches; the orphaned shared Seasonal.css.ts is removed (each theme owns its keyframes). Each overlay: layered oklch palettes, GPU-only animation (transform/opacity), `contain: layout paint style` to kill repaint flicker, ≤~40-element perf budget, particles seeded once via useMemo (no per-frame state), a gorgeous STATIC prefers-reduced-motion form (the settings preview thumbnail), WCAG-AA-preserving low opacities, and no new deps / no external assets (inline SVG data-URIs, Tauri/CSP-safe). Themes: Halloween, Christmas, New Year, Autumn, April Fools, Lunar New Year, Valentines, St. Patrick's, Earth Day, Deep Space, Arcade. Gates: tsc clean, ESLint clean, Prettier clean, build OK, 551 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
import { keyframes } from '@vanilla-extract/css';
|
|
|
|
/**
|
|
* Clover tumble — a shamrock silhouette drifts down while tumbling on two axes.
|
|
* GPU-only: a single tall translateY plus rotate; per-clover duration/delay and
|
|
* a decoupled sway (below) create organic, non-repeating paths. The horizontal
|
|
* offsets stay small so clovers fall roughly in their column.
|
|
*/
|
|
export const animCloverTumble = keyframes({
|
|
'0%': { transform: 'translate3d(0, -10vh, 0) rotate(0deg)', opacity: '0' },
|
|
'8%': { opacity: '1' },
|
|
'50%': { transform: 'translate3d(12px, 50vh, 0) rotate(220deg)' },
|
|
'92%': { opacity: '0.8' },
|
|
'100%': { transform: 'translate3d(-8px, 114vh, 0) rotate(420deg)', opacity: '0' },
|
|
});
|
|
|
|
/**
|
|
* Lateral sway applied to a clover's wrapper so the descent reads as a leaf
|
|
* caught by a breeze, decoupled from the fall for an organic combined path.
|
|
*/
|
|
export const animCloverSway = keyframes({
|
|
'0%': { transform: 'translate3d(0, 0, 0)' },
|
|
'50%': { transform: 'translate3d(20px, 0, 0)' },
|
|
'100%': { transform: 'translate3d(0, 0, 0)' },
|
|
});
|
|
|
|
/**
|
|
* Verdant ambiance breathe — the emerald wash and vignette gently swell so the
|
|
* static tint feels alive without distracting motion. Opacity only.
|
|
*/
|
|
export const animVerdantBreathe = keyframes({
|
|
'0%': { opacity: '0.8' },
|
|
'50%': { opacity: '1' },
|
|
'100%': { opacity: '0.8' },
|
|
});
|
|
|
|
/**
|
|
* Rainbow shimmer — the soft arc in the corner slowly slides and breathes.
|
|
* Uses translate + scale + opacity (never background-position) so it stays on
|
|
* the compositor.
|
|
*/
|
|
export const animRainbowShimmer = keyframes({
|
|
'0%': { transform: 'translate3d(-3%, 1%, 0) scale(1)', opacity: '0.45' },
|
|
'50%': { transform: 'translate3d(3%, -1%, 0) scale(1.04)', opacity: '0.7' },
|
|
'100%': { transform: 'translate3d(-3%, 1%, 0) scale(1)', opacity: '0.45' },
|
|
});
|
|
|
|
/**
|
|
* Gold coin glint — a metallic disc tilts and brightens as a struck-light
|
|
* flicker, then settles. Transform + opacity only so it composites cheaply.
|
|
*/
|
|
export const animCoinGlint = keyframes({
|
|
'0%': { transform: 'scale(0.9) rotate(-8deg)', opacity: '0.35' },
|
|
'20%': { transform: 'scale(1.06) rotate(0deg)', opacity: '0.9' },
|
|
'45%': { transform: 'scale(0.94) rotate(6deg)', opacity: '0.5' },
|
|
'100%': { transform: 'scale(0.9) rotate(-8deg)', opacity: '0.35' },
|
|
});
|
|
|
|
/**
|
|
* Sparkle mote twinkle — a tiny golden point pulses in scale and brightness
|
|
* like a struck spark of luck. Opacity + transform only.
|
|
*/
|
|
export const animMoteTwinkle = keyframes({
|
|
'0%': { transform: 'scale(0.5)', opacity: '0.1' },
|
|
'50%': { transform: 'scale(1.25)', opacity: '0.95' },
|
|
'100%': { transform: 'scale(0.5)', opacity: '0.1' },
|
|
});
|