diff --git a/src/app/components/seasonal/themes/Halloween.css.ts b/src/app/components/seasonal/themes/Halloween.css.ts index 70ce577ce..110e80265 100644 --- a/src/app/components/seasonal/themes/Halloween.css.ts +++ b/src/app/components/seasonal/themes/Halloween.css.ts @@ -82,3 +82,65 @@ export const animGhostWobble = keyframes({ '50%': { transform: 'rotate(4deg)' }, '100%': { transform: 'rotate(-4deg)' }, }); + +/** [#239] Will-o'-the-wisp: a slow wandering loop (paired with a pulse). */ +export const animWispWander = keyframes({ + '0%': { transform: 'translate3d(0, 0, 0)' }, + '25%': { transform: 'translate3d(40px, -30px, 0)' }, + '50%': { transform: 'translate3d(-10px, -60px, 0)' }, + '75%': { transform: 'translate3d(-45px, -20px, 0)' }, + '100%': { transform: 'translate3d(0, 0, 0)' }, +}); + +/** Soft breathing glow of a wisp. */ +export const animWispPulse = keyframes({ + '0%': { opacity: '0.35', transform: 'scale(0.85)' }, + '50%': { opacity: '0.9', transform: 'scale(1.1)' }, + '100%': { opacity: '0.35', transform: 'scale(0.85)' }, +}); + +/** A witch on a broom crosses high up, right to left, in ~11 s of 60 s. */ +export const animWitch = keyframes({ + '0%': { transform: 'translate3d(0, 0, 0) rotate(-4deg)', opacity: '0' }, + '1%': { opacity: '1' }, + '9%': { transform: 'translate3d(-60vw, -3vh, 0) rotate(3deg)' }, + '17.5%': { opacity: '1' }, + '18%': { transform: 'translate3d(-118vw, 2vh, 0) rotate(-3deg)', opacity: '0' }, + '100%': { transform: 'translate3d(-118vw, 2vh, 0) rotate(-3deg)', opacity: '0' }, +}); + +/** + * The big spider: bobs for most of the cycle, then climbs up its thread, + * pauses, and drops back with a little bounce (24 s cycle). + */ +export const animSpiderClimb = keyframes({ + '0%': { transform: 'translate3d(0, 0, 0)' }, + '20%': { transform: 'translate3d(0, 14px, 0)' }, + '40%': { transform: 'translate3d(0, 0, 0)' }, + '55%': { transform: 'translate3d(0, -22vh, 0)' }, + '68%': { transform: 'translate3d(0, -22vh, 0)' }, + '71%': { transform: 'translate3d(0, 24px, 0)' }, + '74%': { transform: 'translate3d(0, -8px, 0)' }, + '77%': { transform: 'translate3d(0, 6px, 0)' }, + '80%': { transform: 'translate3d(0, 0, 0)' }, + '100%': { transform: 'translate3d(0, 0, 0)' }, +}); + +/** Jack-o'-lantern candle flicker (opacity of the glow behind the face). */ +export const animCandle = keyframes({ + '0%': { opacity: '0.75' }, + '20%': { opacity: '1' }, + '35%': { opacity: '0.6' }, + '55%': { opacity: '0.95' }, + '70%': { opacity: '0.7' }, + '100%': { opacity: '0.75' }, +}); + +/** The swarm crosses faster than the regular bats: ~7 s of a 60 s cycle. */ +export const animSwarmFlight = keyframes({ + '0%': { transform: 'translate3d(-10vw, 0, 0)', opacity: '0' }, + '0.5%': { opacity: '1' }, + '11.5%': { opacity: '1' }, + '12%': { transform: 'translate3d(110vw, -8vh, 0)', opacity: '0' }, + '100%': { transform: 'translate3d(110vw, -8vh, 0)', opacity: '0' }, +}); diff --git a/src/app/components/seasonal/themes/Halloween.tsx b/src/app/components/seasonal/themes/Halloween.tsx index dc3298e81..3b29db4b6 100644 --- a/src/app/components/seasonal/themes/Halloween.tsx +++ b/src/app/components/seasonal/themes/Halloween.tsx @@ -4,10 +4,16 @@ import { animBatBob, animBatFlap, animBatFlight, + animCandle, animGhost, animGhostWobble, animSpiderBob, + animSpiderClimb, animSpiderSway, + animSwarmFlight, + animWispPulse, + animWispWander, + animWitch, halloweenRoot, } from './Halloween.css'; @@ -134,30 +140,34 @@ function Spider({ size }: { size: number }) { ); } -/** Spider hanging from the top edge; thread and body bob together. */ +/** Spider hanging from the top edge; thread and body move together. */ function HangingSpider({ left, length, animate, + size = 30, + climb = false, }: { left: string; length: string; animate: boolean; + size?: number; + climb?: boolean; }) { + let motion: string | undefined; + if (animate) { + motion = climb + ? `${animSpiderClimb} 24s ease-in-out 6s infinite` + : `${animSpiderBob} 7s ease-in-out infinite`; + } return (