// Quick feedback animations (0.15 to 0.25 s), so buying feels immediate. // All of them are plain CSS animations restarted from here; users who prefer // reduced motion get none (see components.css). const FALLBACK_MS = 400; // clean-up if the browser never fires animationend /** Restart a one-shot CSS animation class on an element. */ function play(el, className) { if (!el) return; el.classList.remove(className); void el.offsetWidth; // let the browser notice the class was removed el.classList.add(className); const done = () => el.classList.remove(className); el.addEventListener('animationend', done, { once: true }); setTimeout(done, FALLBACK_MS); } /** A block that was just bought: a quick pop and an accent glow. */ export const pop = (el) => play(el, 'fx-pop'); /** A number that just changed: a quick bump. */ export const bump = (el) => play(el, 'fx-bump'); /** A resource that was just spent: a quick flash. */ export const spend = (el) => play(el, 'fx-spend'); /** * A short "+3" that rises from the middle of `anchor` (or from a point) and fades. * `at` = { x, y } in viewport coordinates, used when given instead of an element. * `tone` ('good' | 'bad') colours it green or red; by default it takes the accent colour. */ export function floater(anchor, text, at, tone) { const rect = at ? { left: at.x, top: at.y, width: 0 } : anchor?.getBoundingClientRect(); if (!rect) return; const el = document.createElement('span'); el.className = tone ? `floater floater--${tone}` : 'floater'; el.textContent = text; el.style.left = `${rect.left + rect.width / 2}px`; el.style.top = `${rect.top}px`; document.body.append(el); el.addEventListener('animationend', () => el.remove(), { once: true }); setTimeout(() => el.remove(), FALLBACK_MS); } /** * Pop a bought block, float a label above it and flash what was spent (the funds * in the top bar unless `spentId` says otherwise, or is null for no flash). */ export function purchased(el, label, spentId = 'v-funds') { pop(el); floater(el, label); if (spentId) spend(document.getElementById(spentId)); return true; }