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>
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import { keyframes } from '@vanilla-extract/css';
|
|
|
|
/**
|
|
* Snowfall — a flake drifts downward while swaying horizontally and slowly
|
|
* rotating. GPU-only: animates transform + opacity exclusively. The vertical
|
|
* travel uses a tall translateY so a single keyframe set serves all flakes;
|
|
* per-flake duration/delay/scale create the parallax variety.
|
|
*/
|
|
export const animSnowFall = keyframes({
|
|
'0%': { transform: 'translate3d(0, -8vh, 0) rotate(0deg)', opacity: '0' },
|
|
'8%': { opacity: '1' },
|
|
'50%': { transform: 'translate3d(14px, 50vh, 0) rotate(180deg)' },
|
|
'92%': { opacity: '0.85' },
|
|
'100%': { transform: 'translate3d(-10px, 112vh, 0) rotate(360deg)', opacity: '0' },
|
|
});
|
|
|
|
/**
|
|
* Gentle lateral sway applied to a flake's wrapper so the drift reads as wind,
|
|
* decoupled from the fall so the two combine into an organic path.
|
|
*/
|
|
export const animSnowSway = keyframes({
|
|
'0%': { transform: 'translate3d(0, 0, 0)' },
|
|
'50%': { transform: 'translate3d(18px, 0, 0)' },
|
|
'100%': { transform: 'translate3d(0, 0, 0)' },
|
|
});
|
|
|
|
/**
|
|
* String-light breathing — bokeh orbs softly pulse in brightness and scale,
|
|
* like incandescent bulbs warming and cooling. Opacity + transform only.
|
|
*/
|
|
export const animBulbBreathe = keyframes({
|
|
'0%': { transform: 'scale(0.92)', opacity: '0.55' },
|
|
'50%': { transform: 'scale(1.08)', opacity: '0.95' },
|
|
'100%': { transform: 'scale(0.92)', opacity: '0.55' },
|
|
});
|
|
|
|
/**
|
|
* Aurora shimmer — a wide soft band high in the scene slowly slides and
|
|
* breathes. Uses translateX + opacity (never background-position) so it stays
|
|
* on the compositor.
|
|
*/
|
|
export const animAurora = keyframes({
|
|
'0%': { transform: 'translate3d(-6%, 0, 0) scaleY(1)', opacity: '0.5' },
|
|
'50%': { transform: 'translate3d(6%, 0, 0) scaleY(1.08)', opacity: '0.8' },
|
|
'100%': { transform: 'translate3d(-6%, 0, 0) scaleY(1)', opacity: '0.5' },
|
|
});
|
|
|
|
/**
|
|
* Vignette frost — a barely-there breathing of the cold frame so the static
|
|
* tint feels alive without distracting motion.
|
|
*/
|
|
export const animFrostPulse = keyframes({
|
|
'0%': { opacity: '0.85' },
|
|
'50%': { opacity: '1' },
|
|
'100%': { opacity: '0.85' },
|
|
});
|