import { keyframes } from '@vanilla-extract/css'; /** * Arcade overlay keyframes — retro synthwave CRT. * * Every animation touches ONLY `transform` and `opacity` so the compositor can * run them on the GPU without triggering layout or paint. keyframes() returns * the generated animation-name string, which is applied inline in Arcade.tsx. * * Motion philosophy: a neon perspective grid scrolls toward the viewer, a soft * CRT scanline field breathes, the whole screen glows and flickers ever so * faintly, sparse pixel sparkles drift up, and an "INSERT COIN" blip pulses. * The grid scroll is done with a translateY on a tiled, perspective-projected * plane — never background-position — so it rides the compositor. */ /** * The neon grid plane is laid out twice its visible height and tiled with the * horizontal rule lines. Translating it up by exactly one tile makes the lines * appear to flow continuously toward the viewer (the horizon). Because the * plane sits under a `perspective` transform, the lines also accelerate as they * approach, giving a true receding-grid illusion. Pure transform. */ export const animGridScroll = keyframes({ '0%': { transform: 'translateZ(0) translateY(0)' }, '100%': { transform: 'translateZ(0) translateY(50%)' }, }); /** * Slow vertical drift of the fine scanline field — a couple of pixels so the * raster looks like it's gently rolling, the way a real CRT does. Transform * only; the line texture itself never moves on the GPU's paint layer. */ export const animScanRoll = keyframes({ '0%': { transform: 'translate3d(0, 0, 0)' }, '100%': { transform: 'translate3d(0, 4px, 0)' }, }); /** * The overall CRT screen-glow breathes: a barely-there opacity swell that keeps * the static neon tint feeling alive and powered-on. Opacity only. */ export const animScreenGlow = keyframes({ '0%': { opacity: '0.72' }, '50%': { opacity: '1' }, '100%': { opacity: '0.72' }, }); /** * A faint, irregular CRT brightness flicker laid over the glow — the classic * unstable-tube shimmer. Kept extremely shallow so it never distracts or harms * readability. Opacity only. */ export const animCrtFlicker = keyframes({ '0%': { opacity: '0.94' }, '12%': { opacity: '1' }, '20%': { opacity: '0.9' }, '34%': { opacity: '0.98' }, '52%': { opacity: '0.92' }, '70%': { opacity: '1' }, '83%': { opacity: '0.95' }, '100%': { opacity: '0.94' }, }); /** * Chromatic-aberration twin: the magenta/cyan fringe layers nudge a sub-pixel * apart and back so the edges shimmer with RGB split, like a misconverged tube. * transform + opacity only. */ export const animChromaShift = keyframes({ '0%': { transform: 'translate3d(0, 0, 0)', opacity: '0.5' }, '50%': { transform: 'translate3d(1.5px, 0, 0)', opacity: '0.8' }, '100%': { transform: 'translate3d(0, 0, 0)', opacity: '0.5' }, }); /** * Pixel sparkle drift: a tiny neon speck rises and twinkles like a coin-burst * particle floating up off the grid. transform + opacity, single tall path. */ export const animSparkleDrift = keyframes({ '0%': { transform: 'translate3d(0, 0, 0) scale(0.6)', opacity: '0' }, '12%': { opacity: '1' }, '50%': { transform: 'translate3d(8px, -42vh, 0) scale(1)', opacity: '0.85' }, '78%': { transform: 'translate3d(-6px, -70vh, 0) scale(0.8)', opacity: '0.5' }, '92%': { opacity: '0.18' }, '100%': { transform: 'translate3d(6px, -92vh, 0) scale(0.55)', opacity: '0' }, }); /** * Independent pixel twinkle layered on the drift so specks blink on/off like a * low-res sprite. Stepped opacity for a crisp 8-bit feel. */ export const animSparkleTwinkle = keyframes({ '0%, 44%': { opacity: '1' }, '50%, 94%': { opacity: '0.35' }, '100%': { opacity: '1' }, }); /** * "INSERT COIN" blink: the classic attract-mode pulse. Stepped so it reads as a * hard retro blink rather than a soft fade, but with a brief bright swell. * Opacity + a hair of scale for a CRT bloom feel. */ export const animCoinBlink = keyframes({ '0%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' }, '6%': { opacity: '1', transform: 'translateX(-50%) scale(1.015)' }, '12%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' }, '49%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' }, '50%': { opacity: '0', transform: 'translateX(-50%) scale(1)' }, '100%': { opacity: '0', transform: 'translateX(-50%) scale(1)' }, }); /** * Score-blip pulse for the corner HUD glyph: a quick pop then settle, like a * counter ticking up. transform + opacity. */ export const animScoreBlip = keyframes({ '0%': { opacity: '0.4', transform: 'scale(1)' }, '50%': { opacity: '0.85', transform: 'scale(1.12)' }, '100%': { opacity: '0.4', transform: 'scale(1)' }, });