[feat](trx-frontend-http): display RDS PS name overlay on overview strip

When a WFM/SDR spectrum stream carries RDS data, show the Program
Service name (8-char station name) centered on the visible waveform
area in DSEG14 monospace — the same font as the frequency display.

- Add #rds-ps-overlay div inside .overview-strip (pointer-events: none,
  z-index: 2, absolutely positioned at the center of the visible canvas)
- updateRdsPsOverlay(rds) shows/hides and updates text on every spectrum
  frame; trims trailing spaces common in RDS PS strings
- Overlay cleared on spectrum disconnect / null frame
- text-shadow uses CSS color-mix against --bg for legibility across all
  style/theme combinations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-28 02:04:51 +01:00
parent ff885dd5fd
commit f65135cb5e
3 changed files with 32 additions and 0 deletions
@@ -330,6 +330,7 @@ const overviewPeakHoldEl = document.getElementById("overview-peak-hold");
const themeToggleBtn = document.getElementById("theme-toggle"); const themeToggleBtn = document.getElementById("theme-toggle");
const headerRigSwitchSelect = document.getElementById("header-rig-switch-select"); const headerRigSwitchSelect = document.getElementById("header-rig-switch-select");
const headerStylePickSelect = document.getElementById("header-style-pick-select"); const headerStylePickSelect = document.getElementById("header-style-pick-select");
const rdsPsOverlay = document.getElementById("rds-ps-overlay");
let overviewPeakHoldMs = Number(loadSetting("overviewPeakHoldMs", 2000)); let overviewPeakHoldMs = Number(loadSetting("overviewPeakHoldMs", 2000));
let overviewDrawPending = false; let overviewDrawPending = false;
@@ -2793,6 +2794,7 @@ function startSpectrumStreaming() {
overviewWaterfallRows = []; overviewWaterfallRows = [];
scheduleOverviewDraw(); scheduleOverviewDraw();
clearSpectrumCanvas(); clearSpectrumCanvas();
updateRdsPsOverlay(null);
return; return;
} }
try { try {
@@ -2800,6 +2802,7 @@ function startSpectrumStreaming() {
pushOverviewWaterfallFrame(lastSpectrumData); pushOverviewWaterfallFrame(lastSpectrumData);
refreshCenterFreqDisplay(); refreshCenterFreqDisplay();
scheduleSpectrumDraw(); scheduleSpectrumDraw();
updateRdsPsOverlay(lastSpectrumData.rds);
} catch (_) {} } catch (_) {}
}; };
spectrumSource.onerror = () => { spectrumSource.onerror = () => {
@@ -2824,6 +2827,7 @@ function stopSpectrumStreaming() {
lastSpectrumData = null; lastSpectrumData = null;
overviewWaterfallRows = []; overviewWaterfallRows = [];
scheduleOverviewDraw(); scheduleOverviewDraw();
updateRdsPsOverlay(null);
clearSpectrumCanvas(); clearSpectrumCanvas();
} }
@@ -2835,6 +2839,17 @@ function clearSpectrumCanvas() {
ctx.fillRect(0, 0, spectrumCanvas.width, spectrumCanvas.height); ctx.fillRect(0, 0, spectrumCanvas.width, spectrumCanvas.height);
} }
function updateRdsPsOverlay(rds) {
if (!rdsPsOverlay) return;
const ps = rds?.program_service?.trim();
if (ps) {
rdsPsOverlay.textContent = ps;
rdsPsOverlay.style.display = "";
} else {
rdsPsOverlay.style.display = "none";
}
}
function scheduleSpectrumDraw() { function scheduleSpectrumDraw() {
if (spectrumDrawPending) return; if (spectrumDrawPending) return;
spectrumDrawPending = true; spectrumDrawPending = true;
@@ -48,6 +48,7 @@
</div> </div>
<div class="overview-strip"> <div class="overview-strip">
<canvas id="overview-canvas" aria-hidden="true"></canvas> <canvas id="overview-canvas" aria-hidden="true"></canvas>
<div id="rds-ps-overlay" aria-live="polite" aria-label="RDS station name"></div>
</div> </div>
<!-- Auth gate (hidden by default, shown if auth is required) --> <!-- Auth gate (hidden by default, shown if auth is required) -->
<div id="auth-gate" style="display:none; max-width: 30rem; margin: 0 auto 0.9rem; padding: 1.25rem 0 1.5rem; text-align: center;"> <div id="auth-gate" style="display:none; max-width: 30rem; margin: 0 auto 0.9rem; padding: 1.25rem 0 1.5rem; text-align: center;">
@@ -411,6 +411,22 @@ small { color: var(--text-muted); }
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
#rds-ps-overlay {
display: none;
position: absolute;
top: calc(var(--header-waterfall-overlap) + clamp(4.2rem, 11vh, 6.25rem) / 2);
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
pointer-events: none;
font-family: 'DSEG14 Classic', monospace;
font-size: 2rem;
letter-spacing: 0.05em;
color: var(--text-heading);
opacity: 0.88;
text-shadow: 0 1px 8px color-mix(in srgb, var(--bg) 60%, transparent);
white-space: nowrap;
}
.overview-toolbar { .overview-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;