diff --git a/webui/static/api-monitor.js b/webui/static/api-monitor.js index 1433bb93..3f605003 100644 --- a/webui/static/api-monitor.js +++ b/webui/static/api-monitor.js @@ -383,6 +383,14 @@ function _renderEqualizerBars(grid, data) { } bar.classList.toggle('cooldown', cooling); + // Call embers: tiny accent sparks rise off the fill tip, spawned per + // socket update in proportion to REAL traffic — motion strictly means + // API calls are happening right now. Suppressed during cooldown and + // under reduced-effects. + if (!window._reduceEffectsActive && !cooling && realPct > 0.03) { + _spawnEmbers(bar, pct, realPct > 0.6 ? 3 : realPct > 0.25 ? 2 : 1); + } + // Daily-budget ring (Spotify is the only service with a real daily // cap): a conic rim inside the avatar disc fills as the budget is // spent — green → amber → red, purple once the worker has bridged @@ -409,6 +417,23 @@ function _renderEqualizerBars(grid, data) { } } +// Spawn ember particles at a bar's fill tip. Self-removing DOM sparks with +// a per-bar live cap so a busy hour can't accumulate nodes. +function _spawnEmbers(bar, pct, count) { + const track = bar.querySelector('.rate-eq-track'); + if (!track || track.querySelectorAll('.rate-eq-ember').length > 6) return; + for (let i = 0; i < count; i++) { + const e = document.createElement('span'); + e.className = 'rate-eq-ember'; + e.style.left = `${20 + Math.random() * 60}%`; + e.style.bottom = `${Math.min(96, pct * 100)}%`; + e.style.setProperty('--ember-drift', `${(Math.random() - 0.5) * 14}px`); + e.style.animationDuration = `${1.1 + Math.random() * 0.7}s`; + e.addEventListener('animationend', () => e.remove()); + track.appendChild(e); + } +} + // Animate a single integer counter from `from` to `to` with an // easeOutCubic curve. Used by the equalizer bars so the live count // digit-rolls instead of snapping when sockets push a new value. diff --git a/webui/static/style.css b/webui/static/style.css index a112b21f..8b44a29d 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -63340,6 +63340,26 @@ body.reduce-effects .dash-card::after { text-shadow: 0 0 8px rgba(239, 68, 68, 0.8); } +/* Call embers — tiny accent sparks rising off the fill tip, spawned per + * socket update in proportion to real traffic. Motion == actual API calls. */ +.rate-eq-ember { + position: absolute; + width: 3px; + height: 3px; + border-radius: 50%; + background: color-mix(in srgb, var(--eq-accent) 60%, white 40%); + box-shadow: 0 0 6px var(--eq-accent); + pointer-events: none; + animation: rate-eq-ember-rise 1.4s ease-out forwards; + z-index: 1; +} + +@keyframes rate-eq-ember-rise { + 0% { opacity: 0; transform: translate(0, 0) scale(1); } + 15% { opacity: 0.9; } + 100% { opacity: 0; transform: translate(var(--ember-drift, 0px), -26px) scale(0.4); } +} + /* Recovery flash — the moment a ban expires the track blinks back alive */ .rate-eq.recovered .rate-eq-track { animation: rate-eq-recovered 1.2s ease-out;