import { keyframes, style } from '@vanilla-extract/css'; /** * Halloween overlay styles. Motion is transform/opacity only. * * [#239] Colours come from two custom properties set on the overlay root so * the bats/spider stay dark silhouettes on light themes and turn to a pale * dusk-violet on dark themes (a black bat on a black theme is invisible). */ export const halloweenRoot = style({ position: 'absolute', inset: 0, vars: { '--hw-ink': 'oklch(0.24 0.04 300)', '--hw-web': 'oklch(0.42 0.02 290 / 0.42)', '--hw-eye': 'oklch(0.72 0.18 55)', }, selectors: { 'body.dark-theme &, body.butter-theme &': { vars: { '--hw-ink': 'oklch(0.72 0.06 300)', '--hw-web': 'oklch(0.85 0.02 290 / 0.36)', '--hw-eye': 'oklch(0.8 0.17 60)', }, }, }, }); /** A bat group crosses the top of the screen in ~9 s, then a long calm. */ export const animBatFlight = keyframes({ '0%': { transform: 'translate3d(-10vw, 0, 0)', opacity: '0' }, '1%': { opacity: '1' }, '29%': { opacity: '1' }, '30%': { transform: 'translate3d(110vw, -6vh, 0)', opacity: '0' }, '100%': { transform: 'translate3d(110vw, -6vh, 0)', opacity: '0' }, }); /** Up-and-down bobbing on each bat, so the group doesn't move as one block. */ export const animBatBob = keyframes({ '0%': { transform: 'translate3d(0, 0, 0)' }, '50%': { transform: 'translate3d(0, 14px, 0)' }, '100%': { transform: 'translate3d(0, 0, 0)' }, }); /** Wing beat: the silhouette squashes vertically around the body. */ export const animBatFlap = keyframes({ '0%': { transform: 'scaleY(1)' }, '50%': { transform: 'scaleY(0.62)' }, '100%': { transform: 'scaleY(1)' }, }); /** The spider bobs on its thread (thread and spider move together). */ export const animSpiderBob = keyframes({ '0%': { transform: 'translate3d(0, 0, 0)' }, '50%': { transform: 'translate3d(0, 18px, 0)' }, '100%': { transform: 'translate3d(0, 0, 0)' }, }); /** A slow sway of the spider itself on the end of the thread. */ export const animSpiderSway = keyframes({ '0%': { transform: 'rotate(-6deg)' }, '50%': { transform: 'rotate(6deg)' }, '100%': { transform: 'rotate(-6deg)' }, }); /** * A small ghost rises from the lower part of the screen, drifting sideways, * and fades out — ~8 s in a 50 s cycle. */ export const animGhost = keyframes({ '0%': { transform: 'translate3d(0, 0, 0) scale(0.9)', opacity: '0' }, '3%': { opacity: '0.8' }, '8%': { transform: 'translate3d(6vw, -20vh, 0) scale(1)' }, '13%': { opacity: '0.75' }, '16%': { transform: 'translate3d(-2vw, -42vh, 0) scale(1.05)', opacity: '0' }, '100%': { transform: 'translate3d(-2vw, -42vh, 0) scale(1.05)', opacity: '0' }, }); /** The ghost's tail wisps, a gentle wobble. */ export const animGhostWobble = keyframes({ '0%': { transform: 'rotate(-4deg)' }, '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' }, });