2026-09-25 14:50:35 -04:00
|
|
|
import { keyframes, style } from '@vanilla-extract/css';
|
2026-06-30 19:41:58 -04:00
|
|
|
|
|
|
|
|
/**
|
2026-09-25 14:50:35 -04:00
|
|
|
* 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).
|
2026-06-30 19:41:58 -04:00
|
|
|
*/
|
2026-09-25 14:50:35 -04:00
|
|
|
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' },
|
|
|
|
|
});
|
2026-06-30 19:41:58 -04:00
|
|
|
|
2026-09-25 14:50:35 -04:00
|
|
|
/** The ghost's tail wisps, a gentle wobble. */
|
|
|
|
|
export const animGhostWobble = keyframes({
|
|
|
|
|
'0%': { transform: 'rotate(-4deg)' },
|
|
|
|
|
'50%': { transform: 'rotate(4deg)' },
|
|
|
|
|
'100%': { transform: 'rotate(-4deg)' },
|
2026-06-30 19:41:58 -04:00
|
|
|
});
|