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,73 @@
|
||||
import { keyframes } from '@vanilla-extract/css';
|
||||
|
||||
/**
|
||||
* Deep Space overlay keyframes. Everything here animates ONLY transform/opacity
|
||||
* so the compositor can run it cheaply. The `keyframes()` helper returns the
|
||||
* generated class name string, which the component splices into inline
|
||||
* `animation` shorthands.
|
||||
*/
|
||||
|
||||
/** Cosmos breathe: the whole nebula backdrop drifts and dims almost imperceptibly. */
|
||||
export const animCosmosDrift = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0) scale(1)', opacity: '0.9' },
|
||||
'50%': { transform: 'translate3d(-1.5%, 1%, 0) scale(1.04)', opacity: '1' },
|
||||
'100%': { transform: 'translate3d(0, 0, 0) scale(1)', opacity: '0.9' },
|
||||
});
|
||||
|
||||
/** Nebula cloud drift: a single blurred cloud floats slowly across its layer. */
|
||||
export const animNebulaA = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0) scale(1)' },
|
||||
'50%': { transform: 'translate3d(4%, -3%, 0) scale(1.08)' },
|
||||
'100%': { transform: 'translate3d(0, 0, 0) scale(1)' },
|
||||
});
|
||||
|
||||
export const animNebulaB = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0) scale(1.05)' },
|
||||
'50%': { transform: 'translate3d(-5%, 2.5%, 0) scale(1)' },
|
||||
'100%': { transform: 'translate3d(0, 0, 0) scale(1.05)' },
|
||||
});
|
||||
|
||||
/** Galaxy spiral: an exceptionally slow rotation of a distant pinwheel. */
|
||||
export const animGalaxySpin = keyframes({
|
||||
'0%': { transform: 'rotate(0deg) scale(1)' },
|
||||
'50%': { transform: 'rotate(180deg) scale(1.03)' },
|
||||
'100%': { transform: 'rotate(360deg) scale(1)' },
|
||||
});
|
||||
|
||||
/** Tiny star twinkle: gentle opacity + micro-scale pulse. */
|
||||
export const animTwinkle = keyframes({
|
||||
'0%': { transform: 'scale(0.85)', opacity: '0.35' },
|
||||
'50%': { transform: 'scale(1)', opacity: '1' },
|
||||
'100%': { transform: 'scale(0.85)', opacity: '0.35' },
|
||||
});
|
||||
|
||||
/** Bright star pulse: a slower, fuller bloom for the few hero stars. */
|
||||
export const animStarPulse = keyframes({
|
||||
'0%': { transform: 'scale(0.8) rotate(0deg)', opacity: '0.55' },
|
||||
'50%': { transform: 'scale(1.15) rotate(45deg)', opacity: '1' },
|
||||
'100%': { transform: 'scale(0.8) rotate(0deg)', opacity: '0.55' },
|
||||
});
|
||||
|
||||
/** Parallax depth: a star layer drifts as if the viewer is gliding through space. */
|
||||
export const animParallaxNear = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0)' },
|
||||
'100%': { transform: 'translate3d(-3%, 1.5%, 0)' },
|
||||
});
|
||||
|
||||
export const animParallaxFar = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0)' },
|
||||
'100%': { transform: 'translate3d(-1.2%, 0.6%, 0)' },
|
||||
});
|
||||
|
||||
/**
|
||||
* Comet streak: a thin meteor crosses the field on a diagonal, fading in then
|
||||
* out. The element is rotated by the component; this only translates along its
|
||||
* own local X axis (its length direction) and fades.
|
||||
*/
|
||||
export const animComet = keyframes({
|
||||
'0%': { transform: 'translate3d(0, 0, 0)', opacity: '0' },
|
||||
'6%': { opacity: '1' },
|
||||
'40%': { opacity: '0.9' },
|
||||
'60%': { transform: 'translate3d(150%, 0, 0)', opacity: '0' },
|
||||
'100%': { transform: 'translate3d(150%, 0, 0)', opacity: '0' },
|
||||
});
|
||||
Reference in New Issue
Block a user