Files
cinny/src/app/components/seasonal/themes/NewYear.css.ts
T
jared 26f998d243
CI / Build & Quality Checks (push) Successful in 11m7s
CI / Trigger Desktop Build (push) Successful in 12s
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>
2026-06-30 19:41:58 -04:00

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' },
});