feat(seasonal): redesign all 11 seasonal themes as modular per-theme overlays
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>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { keyframes } from '@vanilla-extract/css';
|
||||
|
||||
/**
|
||||
* Heart rise — a soft heart drifts gently upward while bobbing sideways and
|
||||
* breathing in scale, like a balloon caught in a warm draft. GPU-only: animates
|
||||
* transform + opacity exclusively. The tall translateY lets one keyframe set
|
||||
* serve every heart; per-heart duration/delay/scale supply the variety.
|
||||
*/
|
||||
export const animHeartRise = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 8vh, 0) scale(0.7) rotate(-6deg)', opacity: '0' },
|
||||
'10%': { opacity: '1' },
|
||||
'50%': { transform: 'translate3d(18px, -46vh, 0) scale(1) rotate(5deg)' },
|
||||
'88%': { opacity: '0.85' },
|
||||
'100%': { transform: 'translate3d(-12px, -108vh, 0) scale(1.12) rotate(-4deg)', opacity: '0' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Heart bob — a small lateral sway applied to each heart's wrapper so the rise
|
||||
* reads as a wandering draft, decoupled from the vertical travel so the two
|
||||
* combine into an organic path. Transform only.
|
||||
*/
|
||||
export const animHeartBob = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0)' },
|
||||
'50%': { transform: 'translate3d(16px, 0, 0)' },
|
||||
'100%': { transform: 'translate3d(0, 0, 0)' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Petal tumble — a rose petal falls while swaying horizontally and tumbling on
|
||||
* its own axis, the way a real petal flutters. Opacity + transform only.
|
||||
*/
|
||||
export const animPetalTumble = keyframes({
|
||||
'0%': { transform: 'translate3d(0, -8vh, 0) rotate(0deg)', opacity: '0' },
|
||||
'8%': { opacity: '0.9' },
|
||||
'30%': { transform: 'translate3d(30px, 28vh, 0) rotate(120deg)' },
|
||||
'60%': { transform: 'translate3d(-26px, 62vh, 0) rotate(250deg)' },
|
||||
'92%': { opacity: '0.7' },
|
||||
'100%': { transform: 'translate3d(14px, 112vh, 0) rotate(380deg)', opacity: '0' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Bokeh breathe — dreamy blush orbs softly pulse in scale and brightness, like
|
||||
* soft-focus lights drifting in and out of focus. Opacity + transform only.
|
||||
*/
|
||||
export const animBokehBreathe = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0) scale(0.9)', opacity: '0.45' },
|
||||
'50%': { transform: 'translate3d(0, -10px, 0) scale(1.12)', opacity: '0.9' },
|
||||
'100%': { transform: 'translate3d(0, 0, 0) scale(0.9)', opacity: '0.45' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Blush pulse — a barely-there breathing of the warm vignette so the static
|
||||
* tint feels alive and tender without distracting motion. Opacity only.
|
||||
*/
|
||||
export const animBlushPulse = keyframes({
|
||||
'0%': { opacity: '0.82' },
|
||||
'50%': { opacity: '1' },
|
||||
'100%': { opacity: '0.82' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Sparkle glint — a faint highlight winks on and off with a gentle scale, a
|
||||
* romantic twinkle that never strobes. Transform + opacity only.
|
||||
*/
|
||||
export const animSparkle = keyframes({
|
||||
'0%': { transform: 'scale(0.4) rotate(0deg)', opacity: '0' },
|
||||
'15%': { transform: 'scale(1) rotate(45deg)', opacity: '0.9' },
|
||||
'35%': { transform: 'scale(0.55) rotate(90deg)', opacity: '0' },
|
||||
'100%': { transform: 'scale(0.4) rotate(90deg)', opacity: '0' },
|
||||
});
|
||||
Reference in New Issue
Block a user