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,71 @@
|
||||
import { keyframes } from '@vanilla-extract/css';
|
||||
|
||||
/**
|
||||
* Doodle float-up — a hand-drawn glyph drifts gently upward while bobbing
|
||||
* side to side and lazily rotating, like a thought balloon escaping the page.
|
||||
* GPU-only: transform + opacity exclusively. A tall translateY lets one set of
|
||||
* keyframes serve every doodle; per-element duration/delay/scale add variety.
|
||||
*/
|
||||
export const animDoodleFloat = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 8vh, 0) rotate(-8deg) scale(0.85)', opacity: '0' },
|
||||
'10%': { opacity: '1' },
|
||||
'35%': { transform: 'translate3d(16px, -28vh, 0) rotate(6deg) scale(1)' },
|
||||
'65%': { transform: 'translate3d(-14px, -64vh, 0) rotate(-5deg) scale(1.04)' },
|
||||
'90%': { opacity: '0.8' },
|
||||
'100%': { transform: 'translate3d(10px, -112vh, 0) rotate(7deg) scale(1.1)', opacity: '0' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Confetti tumble — a small chip falls while flipping. Reuses a single tall
|
||||
* translateY; the flip (rotate + scaleX) sells the paper tumble cheaply.
|
||||
*/
|
||||
export const animConfettiTumble = keyframes({
|
||||
'0%': { transform: 'translate3d(0, -8vh, 0) rotate(0deg) scaleX(1)', opacity: '0' },
|
||||
'8%': { opacity: '1' },
|
||||
'50%': { transform: 'translate3d(18px, 50vh, 0) rotate(220deg) scaleX(-1)' },
|
||||
'92%': { opacity: '0.9' },
|
||||
'100%': { transform: 'translate3d(-12px, 112vh, 0) rotate(440deg) scaleX(1)', opacity: '0' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Playful wobble — an almost-imperceptible skew/rotate of a faux tint layer so
|
||||
* the whole scene feels gently "tickled". Tiny amplitude keeps it from being
|
||||
* disorienting. Transform only, stays on the compositor.
|
||||
*/
|
||||
export const animWobble = keyframes({
|
||||
'0%': { transform: 'rotate(-0.5deg) skewX(-0.4deg) scale(1.01)' },
|
||||
'50%': { transform: 'rotate(0.5deg) skewX(0.4deg) scale(1.01)' },
|
||||
'100%': { transform: 'rotate(-0.5deg) skewX(-0.4deg) scale(1.01)' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Pastel aurora drift — a soft rainbow wash high in the scene slides and
|
||||
* breathes. translateX + opacity (never background-position) to stay on GPU.
|
||||
*/
|
||||
export const animRainbowDrift = keyframes({
|
||||
'0%': { transform: 'translate3d(-5%, 0, 0) scaleY(1)', opacity: '0.55' },
|
||||
'50%': { transform: 'translate3d(5%, 0, 0) scaleY(1.06)', opacity: '0.8' },
|
||||
'100%': { transform: 'translate3d(-5%, 0, 0) scaleY(1)', opacity: '0.55' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Googly-eye look-around — the pupil layer nudges around its socket, giving
|
||||
* each eye a cheeky wandering gaze. Small translate only.
|
||||
*/
|
||||
export const animGoogly = keyframes({
|
||||
'0%': { transform: 'translate3d(1.5px, 1px, 0)' },
|
||||
'20%': { transform: 'translate3d(-1.5px, 1.5px, 0)' },
|
||||
'45%': { transform: 'translate3d(1px, -1.5px, 0)' },
|
||||
'70%': { transform: 'translate3d(-1px, -0.5px, 0)' },
|
||||
'100%': { transform: 'translate3d(1.5px, 1px, 0)' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Sly wink/sparkle — a four-point glint that twinkles open and shut, scaling
|
||||
* and fading like a sly little wink. Transform + opacity only.
|
||||
*/
|
||||
export const animSparkle = keyframes({
|
||||
'0%, 100%': { transform: 'scale(0.2) rotate(0deg)', opacity: '0' },
|
||||
'40%': { transform: 'scale(1) rotate(35deg)', opacity: '0.9' },
|
||||
'60%': { transform: 'scale(0.95) rotate(45deg)', opacity: '0.7' },
|
||||
});
|
||||
Reference in New Issue
Block a user