diff --git a/webui/static/api-monitor.js b/webui/static/api-monitor.js
index 1b4215b1..fa653f5a 100644
--- a/webui/static/api-monitor.js
+++ b/webui/static/api-monitor.js
@@ -236,6 +236,7 @@ function _renderEqualizerBars(grid, data) {
+
0
@@ -275,6 +276,26 @@ function _renderEqualizerBars(grid, data) {
const fill = bar.querySelector('.rate-eq-fill');
if (fill) fill.style.height = `${pct * 100}%`;
+ // Peak-hold tick — the VU-meter idiom: a thin marker sticks at the
+ // recent maximum, holds ~1.2s, then falls a few %/update until it
+ // rests on the live fill. A burst stays readable after it's over.
+ const nowTs = performance.now();
+ let peak = prev.peak ?? pct;
+ let peakAt = prev.peakAt ?? nowTs;
+ if (pct >= peak) {
+ peak = pct;
+ peakAt = nowTs;
+ } else if (nowTs - peakAt > 1200) {
+ peak = Math.max(pct, peak - 0.045);
+ }
+ const peakEl = bar.querySelector('.rate-eq-peak');
+ if (peakEl) {
+ peakEl.style.bottom = `${peak * 100}%`;
+ // Only show while meaningfully above the live fill — idle bars
+ // shouldn't carry a stray line.
+ peakEl.classList.toggle('visible', peak > pct + 0.015 && peak > 0.06);
+ }
+
// The reflection puddle (CSS ::after on the track) fades in
// proportional to the real (unclamped) rate so idle services
// don't pollute the row with a floor of glow. Bound to a
@@ -329,7 +350,7 @@ function _renderEqualizerBars(grid, data) {
bar.classList.toggle('active', value > 0 || wStatus === 'running');
bar.classList.toggle('rate-limited', isRateLimited);
- _eqDisplay[svc] = { value, pct };
+ _eqDisplay[svc] = { value, pct, peak, peakAt };
}
}
diff --git a/webui/static/style.css b/webui/static/style.css
index 4c153f12..a67d3318 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -63242,6 +63242,29 @@ body.reduce-effects .dash-card::after {
opacity: 0.85;
}
+/* Peak-hold tick — sticks at the recent maximum and slowly falls, like a
+ * hardware VU meter's peak LED. Position (bottom %) is driven from JS;
+ * only visible while it's meaningfully above the live fill so idle bars
+ * don't carry a stray line. */
+.rate-eq-peak {
+ position: absolute;
+ left: 10%;
+ right: 10%;
+ bottom: 4%;
+ height: 2px;
+ border-radius: 1px;
+ background: color-mix(in srgb, var(--eq-accent) 55%, white 45%);
+ box-shadow: 0 0 6px color-mix(in srgb, var(--eq-accent) 70%, transparent);
+ opacity: 0;
+ transition: bottom 0.9s linear, opacity 0.4s;
+ pointer-events: none;
+ z-index: 2;
+}
+
+.rate-eq-peak.visible {
+ opacity: 0.75;
+}
+
/* Vertical shimmer scan — runs continuously when active */
.rate-eq-shimmer {
position: absolute;