CI / Build & Quality Checks (pull_request) Successful in 1m47s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 7s
CI / Playwright smoke (e2e) (pull_request) Successful in 13m39s
Jared felt the first redesign was too sparse for a full theme: most of the time only two webs and a spider were on screen. - Will-o'-the-wisps: six soft green/orange glows wandering and pulsing, always present while animating (not in the still scene, where static glows would sit on text). - A witch on a broom crosses just below the header about once a minute (redrawn so she reads as a seated figure, not a blob). - Bats every 20 s (was 30), plus a faster swarm of eight every 60 s. - The big spider now climbs up its thread and drops back with a bounce. - A second, small spider on the left web; a tiny jack-o'-lantern with a flickering candle hangs by the right web (desktop). Rendered and inspected at the witch/swarm moments in light, dark and phone, plus reduced; still 60 fps on vs off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
147 lines
5.3 KiB
TypeScript
147 lines
5.3 KiB
TypeScript
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' },
|
|
});
|