Rate monitor: rate-limit bans drain down as a visible countdown

A banned service used to just tint red. The payload carries the seconds
remaining (rl_remaining), so the bar now locks into a cooldown state: the live
VU dims, a red column drains away as the ban ticks down (largest remaining
seen is latched as the denominator — only remaining is sent), and an m:ss
timer counts to recovery. The moment the ban expires the track flashes green
('recovered') and the VU takes the stage back. 'Back in 4:00', not
'something's red'.
This commit is contained in:
BoulderBadgeDad 2026-06-06 15:42:05 -07:00
parent 12ad373a83
commit 09af31154e
2 changed files with 105 additions and 1 deletions

View file

@ -237,6 +237,10 @@ function _renderEqualizerBars(grid, data) {
<div class="rate-eq-tip"></div>
</div>
<div class="rate-eq-peak"></div>
<div class="rate-eq-cooldown">
<div class="rate-eq-cooldown-fill"></div>
<div class="rate-eq-cooldown-time"></div>
</div>
<div class="rate-eq-value">0</div>
</div>
<div class="rate-eq-meta">
@ -350,7 +354,36 @@ function _renderEqualizerBars(grid, data) {
bar.classList.toggle('active', value > 0 || wStatus === 'running');
bar.classList.toggle('rate-limited', isRateLimited);
_eqDisplay[svc] = { value, pct, peak, peakAt };
// Cooldown drain — a ban isn't just "red", it's a COUNTDOWN. The
// payload only carries seconds remaining, so latch the largest value
// seen this ban as the denominator; the red column then drains away
// as the ban ticks down and the timer shows m:ss until recovery.
const rlRemaining = isRateLimited ? Math.max(0, Math.round(d.rl_remaining || 0)) : 0;
let rlTotal = prev.rlTotal || 0;
const cooling = rlRemaining > 0;
if (cooling) {
rlTotal = Math.max(rlTotal, rlRemaining);
const cdFill = bar.querySelector('.rate-eq-cooldown-fill');
if (cdFill) cdFill.style.height = `${(rlRemaining / rlTotal) * 100}%`;
const cdTime = bar.querySelector('.rate-eq-cooldown-time');
if (cdTime) {
const m = Math.floor(rlRemaining / 60);
const s = String(rlRemaining % 60).padStart(2, '0');
cdTime.textContent = `${m}:${s}`;
}
} else {
rlTotal = 0;
// Recovery moment: the ban just ended — flash the bar back alive.
if (prev.cooling) {
bar.classList.remove('recovered');
void bar.offsetWidth; // restart the animation
bar.classList.add('recovered');
window.setTimeout(() => bar.classList.remove('recovered'), 1300);
}
}
bar.classList.toggle('cooldown', cooling);
_eqDisplay[svc] = { value, pct, peak, peakAt, rlTotal, cooling };
}
}

View file

@ -63265,6 +63265,77 @@ body.reduce-effects .dash-card::after {
opacity: 0.75;
}
/* Cooldown drain while a service is rate-limit banned, the track shows a
* red column that DRAINS as the ban ticks down plus an m:ss countdown.
* "Back in 4:00", not just "something's red". JS drives the fill height
* (remaining/total) and timer text; .cooldown gates visibility. */
.rate-eq-cooldown {
position: absolute;
inset: 0;
opacity: 0;
pointer-events: none;
transition: opacity 0.4s;
z-index: 3;
}
.rate-eq.cooldown .rate-eq-cooldown {
opacity: 1;
}
.rate-eq.cooldown .rate-eq-fill,
.rate-eq.cooldown .rate-eq-peak {
opacity: 0.18; /* the live VU yields the stage to the countdown */
}
.rate-eq-cooldown-fill {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 100%;
background: linear-gradient(180deg,
rgba(239, 68, 68, 0.55) 0%,
rgba(239, 68, 68, 0.28) 60%,
rgba(239, 68, 68, 0.12) 100%);
border-radius: 0 0 11px 11px;
box-shadow: inset 0 1px 0 rgba(255, 120, 120, 0.5);
transition: height 0.9s linear;
}
.rate-eq-cooldown-time {
position: absolute;
left: 0;
right: 0;
top: 50%;
transform: translateY(-50%);
text-align: center;
font-size: 11px;
font-weight: 700;
font-variant-numeric: tabular-nums;
color: rgba(255, 200, 200, 0.95);
text-shadow: 0 0 8px rgba(239, 68, 68, 0.8);
}
/* 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;
}
@keyframes rate-eq-recovered {
0% {
border-color: rgba(74, 222, 128, 0.9);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.04),
0 0 26px rgba(74, 222, 128, 0.55);
}
100% {
border-color: rgba(255, 255, 255, 0.06);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.04),
inset 0 -10px 24px rgba(0, 0, 0, 0.35);
}
}
/* Vertical shimmer scan — runs continuously when active */
.rate-eq-shimmer {
position: absolute;