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>
88 lines
3.5 KiB
TypeScript
88 lines
3.5 KiB
TypeScript
import { keyframes } from '@vanilla-extract/css';
|
|
|
|
/**
|
|
* New Year overlay keyframes — a midnight celebration. Every animation touches
|
|
* ONLY `transform` and `opacity` so the compositor runs them on the GPU with no
|
|
* layout/paint. keyframes() returns the generated animation-name string, which
|
|
* is applied inline by the component. Heavy/static structure (gradients, SVG
|
|
* data-URIs, geometry) lives in the component; this module is motion only.
|
|
*/
|
|
|
|
/**
|
|
* Firework burst — a thin spark ring expands from a pinpoint, brightens, then
|
|
* fades as it grows. Scale + opacity only; the ring is a radial-gradient border
|
|
* supplied inline. Long pauses between bursts come from a low keyframe-duty:
|
|
* the ring spends most of the cycle collapsed and invisible.
|
|
*/
|
|
export const animBurst = keyframes({
|
|
'0%': { transform: 'scale(0.05)', opacity: '0' },
|
|
'4%': { transform: 'scale(0.12)', opacity: '0.95' },
|
|
'22%': { transform: 'scale(1)', opacity: '0.55' },
|
|
'34%': { transform: 'scale(1.25)', opacity: '0' },
|
|
'100%': { transform: 'scale(1.25)', opacity: '0' },
|
|
});
|
|
|
|
/**
|
|
* Burst core flash — the bright pinpoint at a firework's origin pops just before
|
|
* the ring blooms, then quickly dims. Pairs with animBurst on the same cadence.
|
|
*/
|
|
export const animCoreFlash = keyframes({
|
|
'0%': { transform: 'scale(0.2)', opacity: '0' },
|
|
'3%': { transform: 'scale(1)', opacity: '1' },
|
|
'14%': { transform: 'scale(0.6)', opacity: '0' },
|
|
'100%': { transform: 'scale(0.6)', opacity: '0' },
|
|
});
|
|
|
|
/**
|
|
* Champagne shimmer sweep — a wide soft gold band glides diagonally across the
|
|
* scene and breathes in brightness. translateX + opacity (never
|
|
* background-position) keep it on the compositor.
|
|
*/
|
|
export const animShimmer = keyframes({
|
|
'0%': { transform: 'translate3d(-120%, 0, 0) skewX(-12deg)', opacity: '0' },
|
|
'12%': { opacity: '0.7' },
|
|
'50%': { opacity: '0.5' },
|
|
'88%': { opacity: '0.6' },
|
|
'100%': { transform: 'translate3d(120%, 0, 0) skewX(-12deg)', opacity: '0' },
|
|
});
|
|
|
|
/**
|
|
* Confetti fall — a small sliver tumbles the full height while spinning on two
|
|
* axes, fading in at the top and out at the bottom. A tall translateY lets one
|
|
* keyframe set serve every sliver; per-piece duration/delay/scale add variety.
|
|
*/
|
|
export const animConfettiFall = keyframes({
|
|
'0%': { transform: 'translate3d(0, -10vh, 0) rotateZ(0deg) rotateX(0deg)', opacity: '0' },
|
|
'8%': { opacity: '0.9' },
|
|
'50%': { transform: 'translate3d(2.2vw, 52vh, 0) rotateZ(220deg) rotateX(180deg)' },
|
|
'92%': { opacity: '0.85' },
|
|
'100%': {
|
|
transform: 'translate3d(-1.8vw, 114vh, 0) rotateZ(440deg) rotateX(360deg)',
|
|
opacity: '0',
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Lateral confetti sway on the wrapper, decoupled from the fall so the two
|
|
* combine into an organic drifting path rather than a straight drop.
|
|
*/
|
|
export const animConfettiSway = keyframes({
|
|
'0%': { transform: 'translate3d(0, 0, 0)' },
|
|
'50%': { transform: 'translate3d(2.4vw, 0, 0)' },
|
|
'100%': { transform: 'translate3d(0, 0, 0)' },
|
|
});
|
|
|
|
/** Star twinkle — a sparkle pulses in brightness and size, like a glint. */
|
|
export const animTwinkle = keyframes({
|
|
'0%': { transform: 'scale(0.5) rotate(0deg)', opacity: '0.2' },
|
|
'50%': { transform: 'scale(1) rotate(45deg)', opacity: '0.95' },
|
|
'100%': { transform: 'scale(0.5) rotate(0deg)', opacity: '0.2' },
|
|
});
|
|
|
|
/** Barely-there breathing of the midnight tint so the static base feels alive. */
|
|
export const animSkyPulse = keyframes({
|
|
'0%': { opacity: '0.82' },
|
|
'50%': { opacity: '1' },
|
|
'100%': { opacity: '0.82' },
|
|
});
|