Files
trx-rs/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html
T
sjgandClaude Opus 5 ee185b98bc [feat](trx-logbook): work a contest, and record what came back
Phase 5 of the logbook: the exchange, the entry sponsors take, and the
confirmations an award counts.

Contest fields go on the contact — the contest, the serials both ways as
numbers and as words, and the zones — because an exchange is not always a
number: a zone, a section or a name goes in as written.  The serial sent and
the contest stay between contacts, since they belong to the session and not
to the contact just logged, and the serial counts on by itself rather than
being retyped forty times an hour.

Cabrillo 3.0 is written because ADIF cannot do this job: sponsors take
Cabrillo and reject everything else.  Its shape is not ADIF's either — the
frequency is kilohertz below 30 MHz and a band designator above it, the modes
are CW, PH, FM, RY and DG, and the contacts go oldest first, as a contest log
is read.  The header cannot be derived from a log — how many operators, how
much power, what the score is claimed to be — so it comes from the operator,
with single-op, low power, all bands and mixed behind it.

QSL, LoTW and eQSL states are held as ADIF's single letters, and anything
else is refused rather than written: a log that grew states of its own would
be one no other program could read.  A contact is confirmed when any one of
the three says so — an award wants a card or an electronic match, not one of
each, and counting them separately would tell the operator they were short of
what they have.

The bands report counts contacts, distinct stations and confirmations per
band, ordered by wavelength as a band plan reads.

Closes #54

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SyX26FCpMQxiBoC7r5K1A7
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-08-07 21:28:40 +02:00

1914 lines
112 KiB
HTML

<!DOCTYPE html>
<!--
SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
SPDX-License-Identifier: GPL-2.0-or-later
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>trx-rs v{ver}</title>
<link rel="icon" type="image/png" sizes="any" href="/favicon.ico?v=5" />
<link rel="shortcut icon" href="/favicon.ico?v=5" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon.png?v=5" />
<link rel="stylesheet" href="/style.css" />
<link rel="stylesheet" href="/themes.css" media="print" onload="this.media='all'" />
<noscript><link rel="stylesheet" href="/themes.css" /></noscript>
<link rel="stylesheet" href="/vendor/leaflet.css" media="print" onload="this.media='all'" />
<noscript><link rel="stylesheet" href="/vendor/leaflet.css" /></noscript>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-home" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2.8 7 8 2.9 13.2 7"/><path d="M4.3 5.9V13h7.4V5.9"/><path d="M6.8 13V9.3h2.4V13"/></symbol>
<symbol id="icon-bookmark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2h8v12l-4-2.5L4 14V2z"/></symbol>
<symbol id="icon-logbook" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 2h8a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z"/><path d="M5.5 2v12"/><path d="M7.5 5.5h3M7.5 8h3M7.5 10.5h2"/></symbol>
<symbol id="icon-digital" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M1 11.5h2.6V4.5h3.2v7h3.2v-7h3.2v7H15"/></symbol>
<symbol id="icon-map" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2a4 4 0 0 1 4 4c0 3-4 8-4 8S4 9 4 6a4 4 0 0 1 4-4z"/><circle cx="8" cy="6" r="1.2" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon-satellite" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 13.2 5.6 9.6"/><path d="M4.2 8.2a3.4 3.4 0 0 1 4.8 4.8z"/><path d="M9.2 6.6a3.2 3.2 0 0 1 2.6 2.6"/><path d="M9.8 3.6a6.2 6.2 0 0 1 5.2 5.2"/></symbol>
<symbol id="icon-stats" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 14h12"/><rect x="3" y="8" width="2" height="6" rx="0.4" fill="currentColor" stroke="none" opacity="0.6"/><rect x="7" y="5" width="2" height="9" rx="0.4" fill="currentColor" stroke="none" opacity="0.75"/><rect x="11" y="2" width="2" height="12" rx="0.4" fill="currentColor" stroke="none" opacity="0.9"/></symbol>
<symbol id="icon-record" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="6"/><circle cx="8" cy="8" r="2.5" fill="currentColor" stroke="none"/></symbol>
<symbol id="icon-settings" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M9.8 3.1a2.6 2.6 0 0 0-2.2 3.9L3.4 11.2a1.2 1.2 0 1 0 1.7 1.7l4.2-4.2a2.6 2.6 0 0 0 3.9-2.2l-1.8.6-1.2-1.2z"/><path d="M10.2 5.8 12 4"/></symbol>
<symbol id="icon-about" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="6"/><path d="M8 7v5"/><circle cx="8" cy="5" r="0.5" fill="currentColor" stroke="none"/></symbol>
</svg>
<div class="card" id="card">
<div class="tab-bar" style="display:none;" id="tab-bar">
<div class="tab-bar-left">
<div class="header-main">
<div class="header-left">
<img id="logo" class="header-logo" src="/logo.png?v=1" alt="trx logo" onerror="this.style.display='none'" />
</div>
<div class="header-text">
<div class="title"><span id="rig-title">trx-rs</span></div>
<div class="subtitle" id="server-subtitle">trx-client v{ver}</div>
<div class="subtitle" id="rig-subtitle" style="font-weight: 700;">Rig: --</div>
<div class="subtitle" id="location-subtitle" style="display:none;"></div>
</div>
</div>
<div class="tab-bar-nav" aria-label="Primary navigation">
<button class="tab active" data-tab="main">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-home"/></svg>
<span class="tab-label">Radio</span>
</button>
<button class="tab" data-tab="bookmarks">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-bookmark"/></svg>
<span class="tab-label">Bookmarks</span>
</button>
<button class="tab" data-tab="logbook">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-logbook"/></svg>
<span class="tab-label">Logbook</span>
</button>
<button class="tab" data-tab="digital-modes" aria-label="Digital modes">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-digital"/></svg>
<span class="tab-label">Digital modes</span>
<!-- A fifth of a phone screen does not hold "Digital modes"; the
button keeps the full name for assistive tech. -->
<span class="tab-label-short" aria-hidden="true">Digital</span>
</button>
<button class="tab" data-tab="map">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-map"/></svg>
<span class="tab-label">Map</span>
</button>
<button class="tab" data-tab="satellites">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-satellite"/></svg>
<span class="tab-label">Satellites</span>
</button>
<button class="tab" data-tab="statistics">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-stats"/></svg>
<span class="tab-label">Statistics</span>
</button>
<button class="tab" data-tab="recorder">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-record"/></svg>
<span class="tab-label">Recorder</span>
</button>
<button class="tab" data-tab="settings">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-settings"/></svg>
<span class="tab-label">Settings</span>
</button>
<button class="tab" data-tab="about">
<svg class="tab-icon" aria-hidden="true"><use href="#icon-about"/></svg>
<span class="tab-label">About</span>
</button>
</div>
</div>
<div class="top-bar-actions">
<button id="header-audio-toggle" class="header-bar-btn header-audio-btn" aria-label="Toggle audio playback" title="Toggle audio playback">
<svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M5 3v10l8-5z"/></svg>
</button>
<button id="header-rec-btn" class="header-bar-btn header-rec-btn" type="button" aria-label="Toggle recording" title="Toggle recording">REC</button>
<div class="header-rig-switch">
<select id="header-rig-switch-select" aria-label="Select active rig"></select>
</div>
<div class="header-style-pick">
<select id="header-style-pick-select" aria-label="Select UI style">
<option value="original">Original</option>
<option value="arctic">Arctic</option>
<option value="lime">Lime</option>
<option value="contrast">Contrast</option>
<option value="neon-disco">Neon Disco</option>
<option value="golden-rain">Donald</option>
<option value="amber">Amber</option>
<option value="fire">Fire</option>
<option value="phosphor">Phosphor</option>
</select>
</div>
<button id="header-share-btn" class="header-bar-btn header-share-btn" type="button" aria-label="Copy a link to this frequency" title="Copy a link to this frequency">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M6.5 9.5a2.6 2.6 0 0 0 3.9.3l2.2-2.2a2.6 2.6 0 0 0-3.7-3.7l-1 1"/><path d="M9.5 6.5a2.6 2.6 0 0 0-3.9-.3L3.4 8.4a2.6 2.6 0 0 0 3.7 3.7l1-1"/></svg>
</button>
<button id="theme-toggle" class="header-bar-btn" type="button" aria-label="Toggle dark or light theme">Light</button>
<button id="header-auth-btn" class="header-bar-btn" type="button" style="display:none;" aria-label="Login or Logout">Login</button>
</div>
</div>
<!-- Auth gate (hidden by default, shown if auth is required) -->
<div id="auth-gate" class="auth-gate" style="display:none;">
<div class="auth-gate-head">
<div class="auth-gate-title">Access Required</div>
<div class="auth-gate-sub">Enter passphrase to continue</div>
</div>
<form id="auth-form" class="auth-form">
<input type="password" id="auth-passphrase" class="auth-input" placeholder="Passphrase" autocomplete="off" />
<button type="submit" class="auth-submit">Login</button>
</form>
<button id="auth-guest-btn" type="button" class="auth-guest" style="display: none;">Continue as Guest</button>
<div id="auth-error" class="auth-error" style="display: none;"></div>
<div id="auth-role" class="auth-role" style="display: none;"></div>
</div>
<div id="tab-main" class="tab-panel">
<div id="server-lost-banner" aria-live="assertive"><span class="banner-dot"></span>trx-server connection lost — waiting for reconnect</div>
<div id="loading" role="status" aria-live="polite" style="text-align:center; padding:2rem 0;">
<div id="loading-title" style="margin-bottom:0.4rem; font-size:1.1rem; font-weight:600;">Initializing (rig)…</div>
<div id="loading-sub" style="color:var(--text-muted);"></div>
</div>
<div id="content" style="display:none;">
<div class="signal-visual-block">
<div id="spectrum-bandplan-strip" aria-label="Band plan allocations"></div>
<div class="overview-strip">
<canvas id="overview-canvas" aria-hidden="true"></canvas>
<div id="rds-ps-overlay" aria-live="polite" aria-label="RDS station name"></div>
<div id="ais-bar-overlay" aria-live="polite" aria-label="Recent AIS messages"></div>
<div id="vdes-bar-overlay" aria-live="polite" aria-label="Recent VDES bursts"></div>
<div id="ft8-bar-overlay" aria-live="polite" aria-label="Recent FT8 decodes"></div>
<div id="aprs-bar-overlay" aria-live="polite" aria-label="Recent APRS frames"></div>
<div id="hf-aprs-bar-overlay" aria-live="polite" aria-label="Recent HF APRS frames"></div>
<div id="cw-bar-overlay" aria-live="polite" aria-label="Recent CW decodes"></div>
</div>
<div id="spectrum-panel" style="display:none;">
<div class="spectrum-wrap">
<div id="spectrum-bookmark-axis"></div>
<div id="spectrum-bookmark-side-left" class="spectrum-bookmark-side spectrum-bookmark-side-left" aria-hidden="true"></div>
<canvas id="spectrum-canvas" tabindex="0" role="img" aria-label="Spectrum display" aria-describedby="spectrum-text-summary"></canvas>
<p id="spectrum-text-summary" class="visually-hidden">Spectrum data is waiting for the receiver.</p>
<div id="spectrum-squelch-line" class="spectrum-squelch-line" style="display:none;" data-state="closed">
<span class="spectrum-squelch-grip" id="spectrum-squelch-grip" role="slider" tabindex="0"
aria-label="Squelch threshold" aria-valuemin="-120" aria-valuemax="-30" aria-valuenow="-95">SQL <span id="spectrum-squelch-label">-95</span> dB</span>
</div>
<div id="spectrum-zoom-indicator" aria-hidden="true"></div>
<div id="spectrum-minimap" aria-hidden="true"><div class="minimap-view"></div></div>
<div id="spectrum-db-axis" aria-hidden="true"></div>
<div id="spectrum-bookmark-side-right" class="spectrum-bookmark-side spectrum-bookmark-side-right" aria-hidden="true"></div>
<div id="spectrum-tooltip"></div>
<canvas id="spectrum-waterfall-canvas" tabindex="0" role="img" aria-label="Waterfall display" aria-describedby="spectrum-text-summary" style="display:none;"></canvas>
<div id="spectrum-freq-axis">
<button id="spectrum-center-left-btn" class="spectrum-edge-shift spectrum-edge-shift-left" type="button" aria-label="Shift spectrum center left">&lsaquo;</button>
<button id="spectrum-center-right-btn" class="spectrum-edge-shift spectrum-edge-shift-right" type="button" aria-label="Shift spectrum center right">&rsaquo;</button>
</div>
</div>
<div id="spectrum-size-grip" title="Drag to resize spectrum height" aria-label="Resize spectrum height"></div>
<div id="spectrum-controls">
<div id="spectrum-bw-row">
<label id="spectrum-bw-label" class="spectrum-field"><span class="spectrum-field-name">Bandwidth</span><input type="number" id="spectrum-bw-input" value="" step="0.1" min="0.1" /><span class="spectrum-field-unit">kHz</span></label>
<button id="spectrum-bw-set-btn" class="spectrum-btn" type="button">Set</button>
<button id="spectrum-bw-auto-btn" class="spectrum-btn" type="button">Auto BW</button>
<button id="spectrum-bw-sweet-btn" class="spectrum-btn" type="button">Sweet-spot</button>
</div>
<div class="spectrum-controls-spacer" aria-hidden="true"></div>
<div id="spectrum-level-row">
<label class="overview-control spectrum-field" id="spectrum-peak-hold-label"><span class="spectrum-field-name">Peak Hold</span>
<select id="overview-peak-hold" class="status-input">
<option value="0">Off</option>
<option value="500">0.5 s</option>
<option value="1000">1 s</option>
<option value="2000" selected>2 s</option>
<option value="5000">5 s</option>
<option value="10000">10 s</option>
<option value="15000">15 s</option>
<option value="30000">30 s</option>
<option value="60000">60 s</option>
</select>
</label>
<label id="spectrum-floor-label" class="spectrum-field"><span class="spectrum-field-name">Floor</span><input type="number" id="spectrum-floor-input" value="-115" step="5" /><span class="spectrum-field-unit">dB</span></label>
<label id="spectrum-range-label" class="spectrum-field"><span class="spectrum-field-name">Range</span><input type="number" id="spectrum-range-input" value="90" step="10" min="10" /><span class="spectrum-field-unit">dB</span></label>
<button id="spectrum-auto-btn" class="spectrum-btn" type="button">Auto</button>
<label id="spectrum-gamma-label" class="spectrum-field"><span class="spectrum-field-name">Contrast</span><input type="range" id="spectrum-gamma-input" min="0.2" max="3.0" step="0.1" value="1.0" /><span id="spectrum-gamma-value" class="spectrum-field-value">1.0</span></label>
</div>
</div>
<div id="spectrum-hint" class="spectrum-hint-mouse">Scroll to zoom &middot; Ctrl+Scroll to tune &middot; Drag to pan &middot; Drag BW edges to resize &middot; +/- zoom &middot; Arrows pan &middot; 0 reset</div>
<div id="spectrum-hint-touch" class="spectrum-hint-touch">Pinch to zoom &middot; Drag to pan &middot; Drag BW edges to resize</div>
</div>
<div id="signal-split-control" title="Set waterfall and waveform split" aria-label="Set waterfall and waveform split">
<input id="signal-split-slider" type="range" min="20" max="80" step="1" value="50" aria-label="Set waterfall and waveform split">
<span id="signal-split-value" aria-live="polite">50/50</span>
</div>
<canvas id="signal-overlay-canvas" aria-hidden="true"></canvas>
<div id="fast-freq-marker" aria-hidden="true"></div>
<div id="fast-bw-left" aria-hidden="true"></div>
<div id="fast-bw-right" aria-hidden="true"></div>
</div>
<div class="status">
<div class="full-row freq-row">
<div class="inline freq-inline">
<div class="freq-field wavelength-col">
<div class="wavelength-display" id="wavelength" title="Wavelength">--</div>
<div class="label"><span>Wavelength</span></div>
</div>
<div class="freq-field sig-strength-col">
<div class="sig-strength-display" id="sig-strength" title="Click to change unit">--</div>
<div class="label"><span>Signal strength</span></div>
</div>
<div class="freq-field frequency-col">
<input class="status-input" id="freq" type="text" value="--" aria-describedby="freq-label" aria-label="Tuned frequency" autocomplete="off" autocorrect="off" spellcheck="false" />
<div class="label" id="freq-label"><span>Frequency</span></div>
</div>
<div class="freq-field frequency-col center-frequency-col" id="center-freq-field" style="display:none;">
<input class="status-input" id="center-freq" type="text" value="--" aria-describedby="center-freq-label" aria-label="SDR center frequency" autocomplete="off" autocorrect="off" spellcheck="false" />
<div class="label" id="center-freq-label"><span>Center Frequency</span></div>
</div>
</div>
</div>
<div class="full-row controls-tray-shell">
<div class="controls-tray-scroll">
<div class="controls-tray">
<div class="controls-row full-row">
<div class="controls-col controls-col-mode label-below-col">
<div class="label"><span>Mode</span></div>
<div class="inline">
<!-- The select stays as the mode's value: a dozen call sites and
several plugins read #mode.value. It is hidden from sight and
from assistive tech; the buttons beside it are the control. -->
<select class="visually-hidden" id="mode" tabindex="-1" aria-hidden="true"></select>
<div id="mode-picker" class="mode-picker" role="group" aria-label="Demodulation mode"></div>
</div>
</div>
<div class="controls-col controls-col-center">
<div class="jog-container">
<button id="jog-down" type="button" class="jog-btn">&minus;</button>
<div class="jog-wheel" id="jog-wheel">
<div class="jog-indicator" id="jog-indicator"></div>
</div>
<button id="jog-up" type="button" class="jog-btn">+</button>
</div>
</div>
<div class="controls-col controls-col-step">
<div class="inline step-controls-inline">
<div class="freq-field unit-col">
<div class="jog-step" id="jog-step">
<button type="button" data-step="1000000">MHz</button>
<button type="button" data-step="1000" class="active">kHz</button>
<button type="button" data-step="1">Hz</button>
</div>
<div class="label"><span>Unit</span></div>
</div>
<div class="freq-field mult-col">
<div class="jog-mult" id="jog-mult">
<button type="button" data-mult="1" class="active" aria-label="Use full tune step">1x</button>
<button type="button" data-mult="10" aria-label="Use one tenth tune step">0.1x</button>
</div>
<div class="label"><span>Step Scale</span></div>
</div>
</div>
</div>
<div class="controls-col controls-col-power label-below-col" id="tx-power-col">
<div class="label"><span>Transmit / Power</span></div>
<div class="btn-grid">
<button id="ptt-btn" type="button" aria-pressed="false">Start TX</button>
<button id="power-btn" type="button" aria-pressed="false">Power On</button>
<button id="lock-btn" type="button" aria-pressed="false">Lock Tuning</button>
</div>
</div>
</div>
<div class="controls-row controls-row-mode full-row" id="mode-controls-row" style="display:none">
<div class="controls-col controls-col-wfm label-below-col" id="wfm-controls-col" style="display:none;">
<div class="inline wfm-controls-inline">
<label class="wfm-control">
<span class="wfm-control-label">Deemp</span>
<select id="wfm-deemphasis" class="status-input">
<option value="50">50 uS</option>
<option value="75">75 uS</option>
</select>
</label>
<label class="wfm-control">
<span class="wfm-control-label">Audio</span>
<select id="wfm-audio-mode" class="status-input">
<option value="stereo">Stereo</option>
<option value="mono">Mono</option>
</select>
</label>
<label class="wfm-control">
<span class="wfm-control-label">Denoise Level</span>
<select id="wfm-denoise" class="status-input">
<option value="off">Off</option>
<option value="auto">Auto</option>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</label>
<label class="wfm-control wfm-st-flag-wrap" aria-label="Stereo pilot status">
<span class="wfm-control-label">Pilot</span>
<span id="wfm-st-flag" class="wfm-st-flag wfm-st-flag-mono">MO</span>
</label>
<label class="wfm-control wfm-intf-bar-wrap" aria-label="Co-Channel Interference">
<span class="wfm-control-label">CCI</span>
<span class="wfm-intf-bar"><span id="wfm-cci-fill" class="wfm-intf-fill"></span><span id="wfm-cci-val" class="wfm-intf-val">0</span></span>
</label>
<label class="wfm-control wfm-intf-bar-wrap" aria-label="Adjacent Channel Interference">
<span class="wfm-control-label">ACI</span>
<span class="wfm-intf-bar"><span id="wfm-aci-fill" class="wfm-intf-fill"></span><span id="wfm-aci-val" class="wfm-intf-val">0</span></span>
</label>
</div>
<div class="label"><span>WFM</span></div>
</div>
<div class="controls-col controls-col-sam label-below-col" id="sam-controls-col" style="display:none;">
<div class="inline sam-controls-inline">
<label class="wfm-control">
<span class="wfm-control-label">Stereo Width</span>
<input type="range" id="sam-stereo-width" min="0" max="100" value="100" class="status-input" />
</label>
<label class="wfm-control">
<span class="wfm-control-label">Carrier Sync</span>
<select id="sam-carrier-sync" class="status-input">
<option value="on">On</option>
<option value="off">Off</option>
</select>
</label>
</div>
<div class="label"><span>SAM</span></div>
</div>
</div>
<div class="full-row label-below-row" id="vfo-row">
<div class="label"><span>VFO</span></div>
<div class="vfo-picker" id="vfo-picker"></div>
</div>
<div class="full-row label-below-row" id="sdr-settings-row" style="display:none;">
<div class="label"><span>SDR settings</span></div>
<div class="inline" style="gap: 0.6rem; flex-wrap: wrap; align-items: flex-end;">
<label class="wfm-control" id="sdr-agc-wrap">
<span class="wfm-control-label">Hardware AGC</span>
<div style="height:2.1rem; display:flex; align-items:center;">
<input type="checkbox" id="sdr-agc-enabled">
</div>
</label>
<div class="wfm-gain-group" id="sdr-gain-controls">
<label class="wfm-control">
<span class="wfm-control-label">RF Gain</span>
<input id="sdr-gain-db" class="status-input" type="number" min="0" max="60" step="1" inputmode="decimal">
</label>
<button id="sdr-gain-set" type="button" class="wfm-inline-btn">Set</button>
</div>
<div class="wfm-gain-group" id="sdr-lna-gain-controls">
<label class="wfm-control">
<span class="wfm-control-label">LNA Gain</span>
<input id="sdr-lna-gain-db" class="status-input" type="number" min="0" max="60" step="1" inputmode="decimal">
</label>
<button id="sdr-lna-gain-set" type="button" class="wfm-inline-btn">Set</button>
</div>
<label class="wfm-control" id="sdr-nb-wrap" style="display:none;">
<span class="wfm-control-label">Noise Blanker</span>
<div style="height:2.1rem; display:flex; align-items:center;">
<input type="checkbox" id="sdr-nb-enabled">
</div>
</label>
<div class="wfm-gain-group" id="sdr-nb-threshold-controls" style="display:none;">
<label class="wfm-control">
<span class="wfm-control-label">NB Threshold</span>
<input id="sdr-nb-threshold" class="status-input" type="number" min="1" max="100" step="1" inputmode="decimal">
</label>
<button id="sdr-nb-threshold-set" type="button" class="wfm-inline-btn">Set</button>
</div>
</div>
</div>
<div class="full-row label-below-row" id="vchan-row">
<div class="label"><span>Channels</span></div>
<div class="channel-scheduler-controls">
<div class="vchan-picker" id="vchan-picker"></div>
</div>
</div>
<div class="full-row label-below-row">
<div class="label"><span>Signal</span></div>
<div class="signal" style="gap: 1rem;">
<div class="signal-bar"><div class="signal-bar-fill" id="signal-bar"></div></div>
<div class="signal-value" id="signal-value">--</div>
</div>
<div class="signal-measure">
<button id="sig-measure-btn" type="button">Measure</button>
<button id="sig-clear-btn" type="button">Clear</button>
<span id="sig-result"></span>
</div>
</div>
<div class="full-row label-below-row" id="tx-meters" style="display:none;">
<div class="label"><span>TX Meters</span></div>
<div class="meter" style="gap: 1rem; margin-bottom: 0.4rem;">
<div class="meter-bar"><div class="meter-fill" id="pwr-bar"></div></div>
<div class="meter-value" id="pwr-value">PWR --</div>
</div>
<div class="meter" style="gap: 1rem;">
<div class="meter-bar"><div class="meter-fill" id="swr-bar"></div></div>
<div class="meter-value" id="swr-value">SWR --</div>
</div>
</div>
<div id="tx-limit-row" style="display:none;">
<div class="label"><span>TX Limit — units depend on rig (percentage/watts)</span></div>
<div class="inline">
<input class="status-input" id="tx-limit" type="number" min="0" max="255" step="1" value="" placeholder="--" />
<button id="tx-limit-btn" type="button">Set</button>
</div>
</div>
<details id="audio-controls" class="advanced-radio-controls audio-controls-section">
<summary>Audio controls</summary>
<div class="advanced-radio-body">
<div class="full-row" id="audio-row">
<div class="inline" style="gap: 0.6rem; flex-wrap: wrap; align-items: center;">
<button id="rx-audio-btn" type="button">Play Audio</button>
<button id="tx-audio-btn" type="button">Transmit Audio</button>
<span class="audio-group audio-volume-group">
<span class="audio-group-label">Volume</span>
<label class="vol-label">RX<input type="range" id="rx-vol" min="0" max="100" value="80" class="vol-slider" /><small class="vol-pct" id="rx-vol-pct">80%</small></label>
<label class="vol-label">TX<input type="range" id="tx-vol" min="0" max="100" value="80" class="vol-slider" /><small class="vol-pct" id="tx-vol-pct">80%</small></label>
</span>
<span class="sql-control" id="sdr-squelch-wrap" style="display:none;">
<button id="sdr-squelch-toggle" type="button" class="sql-toggle" aria-pressed="false" title="Turn the squelch on or off"><span class="sql-state" id="sdr-squelch-state" data-state="off" aria-hidden="true"></span>SQL</button>
<label class="sql-db"><input type="number" id="sdr-squelch-db" min="-120" max="-30" step="1" value="-95" inputmode="numeric" aria-label="Squelch threshold in dB" /><span class="sql-db-unit">dB</span></label>
<button id="sdr-squelch-auto" type="button" class="sql-auto-btn" title="Set the threshold just above the noise floor">Auto</button>
</span>
<span class="audio-group audio-level-group">
<span class="audio-group-label">Level</span>
<div id="audio-level">
<div id="audio-level-fill"></div>
</div>
<small id="audio-status">Off</small>
</span>
</div>
</div>
</div>
</details>
<details id="scheduler-controls" class="advanced-radio-controls scheduler-controls-section">
<summary>Scheduler controls</summary>
<div class="advanced-radio-body">
<div class="scheduler-control-row" style="display:none">
<div class="scheduler-release-wrap">
<div class="scheduler-action-row">
<div class="scheduler-step-controls">
<button id="scheduler-prev-btn" type="button">Previous Entry</button>
<button id="scheduler-next-btn" type="button">Next Entry</button>
</div>
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
<div id="scheduler-cycle-status" class="interleave-ring-wrap" style="display:none;">
<svg class="interleave-ring" viewBox="0 0 36 36">
<circle class="interleave-ring-bg" cx="18" cy="18" r="15.915" />
<circle class="interleave-ring-fill" id="interleave-ring-fill" cx="18" cy="18" r="15.915"
stroke-dasharray="100" stroke-dashoffset="100" />
</svg>
<div class="interleave-ring-text">
<div class="interleave-ring-label" id="interleave-active-name">--</div>
<div class="interleave-ring-sub" id="interleave-countdown">--</div>
</div>
</div>
</div>
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
</div>
</div>
</div>
</details>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="tab-logbook" class="tab-panel" style="display:none;">
<div id="logbook-panel" class="log-panel">
<!-- Station: who the contacts are logged under, and from where. Shown
once rather than typed into every entry. -->
<div class="log-station">
<div class="log-station-identity">
<span class="log-station-call" id="log-station-callsign">--</span>
<label class="log-station-operator">
<span>Operator</span>
<input type="text" id="log-operator" class="status-input" autocomplete="off"
spellcheck="false" aria-label="Operator callsign" />
</label>
</div>
<div class="log-station-rig">
<span id="log-station-rig-name">--</span>
<span id="log-station-grid" class="log-station-grid"></span>
<span id="log-clock" class="log-clock" aria-live="polite"></span>
</div>
</div>
<!-- Entry: opens pre-filled with the six fields the radio and the clock
can answer for. Everything else is the operator's. -->
<form id="log-entry-form" class="log-entry" autocomplete="off">
<div class="log-entry-grid">
<label class="log-field log-field-call">
<span>Callsign</span>
<input type="text" id="log-call" class="status-input" required
spellcheck="false" aria-describedby="log-worked-before" />
</label>
<label class="log-field">
<span>Frequency</span>
<input type="text" id="log-freq" class="status-input" inputmode="decimal" />
</label>
<label class="log-field log-field-narrow">
<span>Mode</span>
<input type="text" id="log-mode" class="status-input" spellcheck="false" />
</label>
<label class="log-field log-field-narrow">
<span>RST sent</span>
<input type="text" id="log-rst-sent" class="status-input" spellcheck="false" />
</label>
<label class="log-field log-field-narrow">
<span>RST rcvd</span>
<input type="text" id="log-rst-rcvd" class="status-input" spellcheck="false" />
</label>
<label class="log-field log-field-narrow">
<span>Locator</span>
<input type="text" id="log-grid" class="status-input" spellcheck="false" />
</label>
<label class="log-field">
<span>Name</span>
<input type="text" id="log-name" class="status-input" />
</label>
<label class="log-field">
<span>Comment</span>
<input type="text" id="log-comment" class="status-input" />
</label>
</div>
<details class="log-contest" id="log-contest-block">
<summary>Contest exchange</summary>
<div class="log-entry-grid">
<label class="log-field">
<span>Contest</span>
<input type="text" id="log-contest-id" class="status-input" spellcheck="false"
placeholder="CQ-WW-SSB" />
</label>
<label class="log-field log-field-narrow">
<span>Serial sent</span>
<input type="text" id="log-stx" class="status-input" inputmode="numeric" />
</label>
<label class="log-field log-field-narrow">
<span>Received</span>
<input type="text" id="log-srx" class="status-input" />
</label>
</div>
</details>
<div class="log-entry-actions">
<span id="log-worked-before" class="log-worked" aria-live="polite"></span>
<span class="log-entry-buttons">
<button type="button" id="log-clear-btn" class="log-secondary-btn">Clear</button>
<button type="submit" id="log-save-btn" class="log-primary-btn">Log contact</button>
</span>
</div>
</form>
<!-- The log itself. -->
<div class="log-list-toolbar">
<input type="text" id="log-filter-call" class="status-input" placeholder="Callsign&hellip;"
aria-label="Filter by callsign" />
<select id="log-filter-band" class="status-input" aria-label="Filter by band">
<option value="">All bands</option>
</select>
<select id="log-filter-mode" class="status-input" aria-label="Filter by mode">
<option value="">All modes</option>
</select>
<span class="log-toolbar-spacer"></span>
<a id="log-export-btn" class="log-secondary-btn" href="/api/logbook/export.adi"
download>Export ADIF</a>
<button type="button" id="log-import-btn" class="log-secondary-btn">Import ADIF</button>
<input type="file" id="log-import-file" accept=".adi,.adif,text/plain" hidden />
</div>
<div class="log-table-wrap">
<table class="log-table">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Time</th>
<th scope="col">Callsign</th>
<th scope="col">Band</th>
<th scope="col">Mode</th>
<th scope="col">Sent</th>
<th scope="col">Rcvd</th>
<th scope="col">Locator</th>
<th scope="col">Rig</th>
<th scope="col">QSL</th>
<th scope="col"><span class="visually-hidden">Actions</span></th>
</tr>
</thead>
<tbody id="log-rows"></tbody>
</table>
</div>
<div id="log-summary" class="log-summary" aria-live="polite"></div>
<details class="log-report" id="log-statistics-block">
<summary>Bands worked</summary>
<div class="log-table-wrap">
<table class="log-table">
<thead>
<tr>
<th scope="col">Band</th>
<th scope="col">Contacts</th>
<th scope="col">Stations</th>
<th scope="col">Confirmed</th>
</tr>
</thead>
<tbody id="log-statistics-rows"></tbody>
</table>
</div>
</details>
<details class="log-report" id="log-contest-export-block">
<summary>Contest entry (Cabrillo)</summary>
<p class="log-report-note">
Contest logs are submitted in Cabrillo and rejected in anything else.
Only the contacts of the contest named here go into the entry.
</p>
<div class="log-entry-grid">
<label class="log-field">
<span>Contest</span>
<input type="text" id="log-cbr-contest" class="status-input" spellcheck="false"
placeholder="CQ-WW-SSB" />
</label>
<label class="log-field">
<span>Callsign</span>
<input type="text" id="log-cbr-callsign" class="status-input" spellcheck="false" />
</label>
<label class="log-field">
<span>Operators</span>
<select id="log-cbr-operator" class="status-input">
<option value="SINGLE-OP">Single operator</option>
<option value="MULTI-OP">Multi operator</option>
<option value="CHECKLOG">Checklog</option>
</select>
</label>
<label class="log-field">
<span>Power</span>
<select id="log-cbr-power" class="status-input">
<option value="LOW">Low</option>
<option value="HIGH">High</option>
<option value="QRP">QRP</option>
</select>
</label>
<label class="log-field log-field-narrow">
<span>Claimed score</span>
<input type="text" id="log-cbr-score" class="status-input" inputmode="numeric" />
</label>
</div>
<div class="log-entry-actions">
<span class="log-entry-buttons">
<a id="log-cbr-export" class="log-secondary-btn" href="/api/logbook/export.cbr"
download>Export Cabrillo</a>
</span>
</div>
</details>
</div>
</div>
<div id="tab-bookmarks" class="tab-panel" style="display:none;">
<div class="bm-toolbar">
<select id="bm-scope-picker" class="status-input" aria-label="Bookmark scope">
<option value="general">General</option>
</select>
<select id="bm-category-filter" class="status-input" aria-label="Filter by category">
<option value="">All categories</option>
</select>
<select id="bm-mode-filter" class="status-input" aria-label="Filter by mode">
<option value="">All modes</option>
</select>
<input type="search" id="bm-text-filter" class="status-input" placeholder="Search bookmarks…" aria-label="Search bookmarks" />
<button id="bm-add-btn" type="button" class="bm-add-btn" style="display:none;">+ Add Bookmark</button>
<button id="bm-select-all-btn" type="button" class="bm-add-btn" style="display:none;">Select All</button>
<button id="bm-del-selected-btn" type="button" class="bm-add-btn bm-del-btn" style="display:none;">Delete Selected (<span id="bm-del-selected-count">0</span>)</button>
<span id="bm-move-selected-wrap" class="bm-move-wrap" style="display:none;">
<select id="bm-move-target" class="status-input" aria-label="Move destination"></select>
<button id="bm-move-selected-btn" type="button" class="bm-add-btn">Move (<span id="bm-move-selected-count">0</span>)</button>
</span>
</div>
<div id="bm-form-wrap" style="display:none;">
<form id="bm-form" class="bm-form">
<div class="bm-form-title" id="bm-form-title">Add Bookmark</div>
<input type="hidden" id="bm-id" />
<div class="bm-form-grid">
<label class="bm-label">Name
<input type="text" id="bm-name" class="status-input" required placeholder="e.g. 40m FT8" />
</label>
<label class="bm-label">Category
<input type="text" id="bm-category-input" class="status-input" placeholder="Uncategorised" />
</label>
<label class="bm-label">Frequency (Hz)
<input type="number" id="bm-freq" class="status-input" required min="0" placeholder="e.g. 7074000" />
</label>
<label class="bm-label">Mode
<input type="text" id="bm-mode" class="status-input" list="bm-mode-list" required placeholder="e.g. DIG" />
<datalist id="bm-mode-list">
<option value="LSB">
<option value="USB">
<option value="AM">
<option value="SAM">
<option value="FM">
<option value="DIG">
<option value="CW">
<option value="WFM">
</datalist>
</label>
<label class="bm-label">Bandwidth (Hz)
<input type="number" id="bm-bw" class="status-input" min="0" placeholder="optional" />
</label>
<label class="bm-label">Locator
<input type="text" id="bm-locator" class="status-input" maxlength="6" placeholder="e.g. JO93" />
</label>
<div class="bm-label">Digital modes
<div class="bm-decoder-checks" id="bm-decoder-checkboxes">
<!-- Populated dynamically from /decoders registry -->
</div>
</div>
<label class="bm-label bm-label-wide">Comment
<input type="text" id="bm-comment" class="status-input" placeholder="optional" />
</label>
</div>
<div class="bm-form-actions">
<div id="bm-form-error" class="form-error" role="alert" aria-live="polite"></div>
<button type="submit" class="bm-save-btn">Save</button>
<button type="button" id="bm-form-cancel">Cancel</button>
</div>
</form>
</div>
<div id="bm-table-wrap">
<table class="bm-table">
<thead>
<tr>
<th class="bm-col-sel"><input type="checkbox" id="bm-select-all" aria-label="Select all bookmarks" /></th>
<th>Name</th>
<th>Frequency</th>
<th>Mode</th>
<th>BW</th>
<th>Locator</th>
<th>Category</th>
<th>Digital modes</th>
<th>Comment</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="bm-tbody"></tbody>
</table>
<div id="bm-empty" class="bm-empty" style="display:none;">No bookmarks yet. Click <strong>+ Add Bookmark</strong> to save a frequency.</div>
<div id="bm-paginator" class="bm-paginator" style="display:none;">
<div id="bm-page-summary" class="bm-page-summary">Showing 0-0 of 0</div>
<div class="bm-page-controls">
<button id="bm-page-prev" type="button">Previous</button>
<span id="bm-page-indicator" class="bm-page-indicator">Page 1 of 1</span>
<button id="bm-page-next" type="button">Next</button>
</div>
</div>
</div>
</div>
<div id="tab-digital-modes" class="tab-panel" style="display:none;">
<div class="sub-tab-bar" aria-label="Decoder views">
<button class="sub-tab active" data-subtab="overview">Overview</button>
<button class="sub-tab" data-subtab="ais">AIS</button>
<button class="sub-tab" data-subtab="vdes">VDES</button>
<button class="sub-tab" data-subtab="aprs">APRS</button>
<button class="sub-tab" data-subtab="hf-aprs">HF APRS</button>
<button class="sub-tab" data-subtab="cw">CW</button>
<button class="sub-tab" data-subtab="ft8">FT8</button>
<button class="sub-tab" data-subtab="ft4">FT4</button>
<button class="sub-tab" data-subtab="ft2">FT2</button>
<button class="sub-tab" data-subtab="wspr">WSPR</button>
<button class="sub-tab" data-subtab="rds">RDS</button>
<button class="sub-tab" data-subtab="sat">SAT</button>
<button class="sub-tab" data-subtab="wefax">WEFAX</button>
<button class="sub-tab" data-subtab="sstv">SSTV</button>
</div>
<div id="subtab-overview" class="sub-tab-panel">
<div class="plugin-item" data-decoder="ais">
<strong>AIS Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes dual-channel AIS traffic from RX audio using 9.6 kbit/s GMSK and HDLC.
</div>
</div>
<div class="plugin-item" data-decoder="vdes">
<strong>VDES Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes single-channel 100 kHz VDES bursts from SDR IQ using the dedicated VDES path.
</div>
</div>
<div class="plugin-item" data-decoder="aprs">
<strong>APRS Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes APRS packets from RX audio using Bell 202 AFSK (1200 baud).
</div>
</div>
<div class="plugin-item" data-decoder="cw">
<strong>CW Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes CW (Morse code) from RX audio.
</div>
</div>
<div class="plugin-item" data-decoder="ft8">
<strong>FT8 Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes FT8 messages from RX audio (DIG/USB only, toggle required).
</div>
</div>
<div class="plugin-item" data-decoder="ft4">
<strong>FT4 Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes FT4 messages from RX audio (DIG/USB only, toggle required). 7.5-second slots.
</div>
</div>
<div class="plugin-item" data-decoder="ft2">
<strong>FT2 Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes FT2 messages from RX audio (DIG/USB only, toggle required). 3.75-second slots.
</div>
</div>
<div class="plugin-item" data-decoder="wspr">
<strong>WSPR Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes WSPR messages from RX audio (DIG/USB only, toggle required).
</div>
</div>
<div class="plugin-item" data-decoder="rds">
<strong>RDS Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes Radio Data System (RDS) metadata from WFM broadcasts (57 kHz subcarrier).
</div>
</div>
<div class="plugin-item" data-decoder="lrpt">
<strong>Weather Satellite Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Decodes Meteor-M LRPT (137 MHz QPSK) weather satellite imagery.
</div>
</div>
<div class="plugin-item" data-decoder="sstv">
<strong>SSTV Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Slow-Scan Television &mdash; pictures in Martin, Scottie, Robot, PD and Wraase modes
</div>
</div>
<div class="plugin-item" data-decoder="wefax">
<strong>WEFAX Decoder</strong>
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
Weather Facsimile &mdash; HF/satellite image reception (60/90/120/240 LPM)
</div>
</div>
</div>
<div id="subtab-sstv" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="sstv-decode-toggle-btn" type="button">Enable SSTV</button>
<small id="sstv-status" style="color:var(--text-muted);">Idle</small>
</div>
<!-- View selector -->
<div class="sat-view-bar">
<button id="sstv-view-live" class="sat-view-btn sat-view-active" type="button">Live</button>
<button id="sstv-view-history" class="sat-view-btn" type="button">History</button>
</div>
<!-- Live view -->
<div id="sstv-live-view">
<div style="margin:0 0 0.5rem;">
<div style="color:var(--text-muted); font-size:0.82rem; line-height:1.5;">
<strong>Slow-Scan Television</strong> &mdash; pictures sent as tones, in Martin,
Scottie, Robot, PD and Wraase modes. Tune a sideband or FM signal and enable the
decoder; the mode is read from the header the sender opens with.
</div>
</div>
<div id="sstv-live-container" style="display:none; margin:0.5rem 0;">
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.3rem;">
<strong>Receiving</strong>
<small id="sstv-live-info" style="color:var(--text-muted);"></small>
</div>
<canvas id="sstv-live-canvas" width="320" height="256"
style="width:100%; max-width:640px; image-rendering:pixelated; background:#111;"></canvas>
</div>
<div id="sstv-live-latest" style="margin-top:0.5rem;"></div>
</div>
<!-- History view -->
<div id="sstv-history-view" style="display:none;">
<div class="sat-history-controls">
<input id="sstv-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. Martin, PD120)" />
<select id="sstv-sort" class="sat-sort-select">
<option value="newest">Newest first</option>
<option value="oldest">Oldest first</option>
</select>
<button id="sstv-clear-btn" type="button" style="font-size:0.8rem;">Clear All</button>
</div>
<div class="sat-history-header">
<span class="sat-col-time">Time</span>
<span class="sat-col-type">Mode</span>
<span class="sat-col-sat">Size</span>
<span class="sat-col-lines">Lines</span>
<span class="sat-col-link">Image</span>
</div>
<div id="sstv-history-list"></div>
<small id="sstv-history-count" style="color:var(--text-muted);font-size:0.75rem;">No pictures yet</small>
</div>
</div>
<div id="subtab-rds" class="sub-tab-panel" style="display:none;">
<div class="rds-grid">
<div class="rds-field"><span class="rds-label">Status</span><span id="rds-status" class="rds-value rds-no-signal">No signal</span></div>
<div class="rds-field"><span class="rds-label">Active mode</span><span id="rds-mode" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">PI</span><span id="rds-pi" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">PS</span><span id="rds-ps" class="rds-value rds-ps">--</span></div>
<div class="rds-field"><span class="rds-label">PTY</span><span id="rds-pty" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">PTY Code</span><span id="rds-pty-name" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">PTYN</span><span id="rds-ptyn" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">TP</span><span id="rds-tp" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">TA</span><span id="rds-ta" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">Audio</span><span id="rds-music" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">Stereo</span><span id="rds-stereo" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">Compressed</span><span id="rds-compressed" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">Artificial Head</span><span id="rds-artificial-head" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">Dynamic PTY</span><span id="rds-dynamic-pty" class="rds-value">--</span></div>
<div class="rds-field"><span class="rds-label">AF</span><span id="rds-af-list" class="rds-value rds-af-list">--</span></div>
<div class="rds-field"><span class="rds-label">RadioText</span><span id="rds-radio-text" class="rds-value rds-text">--</span></div>
</div>
<div class="rds-raw-header">
<div class="rds-raw-label">Raw JSON (last spectrum frame)</div>
<button id="rds-raw-copy-btn" class="header-bar-btn" type="button">Copy</button>
</div>
<pre id="rds-raw" class="rds-raw">--</pre>
</div>
<div id="subtab-ais" class="sub-tab-panel" style="display:none;">
<div class="aprs-controls">
<input id="ais-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. MMSI, vessel, A)" />
<small id="ais-status" style="color:var(--text-muted);">Waiting for server decode</small>
</div>
<div class="aprs-filter-row">
<span class="aprs-counts">
<span id="ais-vessel-count" class="aprs-counts-value">0 vessels</span>
<span id="ais-latest-seen" class="aprs-counts-value">No traffic yet</span>
<span id="ais-channel-summary" class="aprs-counts-value">A 161.975 MHz · B 162.025 MHz</span>
</span>
</div>
<div id="ais-messages"></div>
</div>
<div id="subtab-vdes" class="sub-tab-panel" style="display:none;">
<div class="aprs-controls">
<input id="vdes-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. frame, RMS, payload)" />
<small id="vdes-status" style="color:var(--text-muted);">Waiting for server decode</small>
</div>
<div class="ais-summary">
<div class="ais-summary-card">
<span class="ais-summary-label">Channel</span>
<span id="vdes-channel-summary" class="ais-summary-value">100 kHz centered on tuned frequency</span>
</div>
<div class="ais-summary-card">
<span class="ais-summary-label">Frames</span>
<span id="vdes-frame-count" class="ais-summary-value">0 bursts</span>
</div>
<div class="ais-summary-card">
<span class="ais-summary-label">Latest</span>
<span id="vdes-latest-seen" class="ais-summary-value">No traffic yet</span>
</div>
</div>
<div id="vdes-messages"></div>
</div>
<div id="subtab-aprs" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls aprs-controls">
<input id="aprs-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. SP2, beacon)" />
<small id="aprs-status" style="color:var(--text-muted);">Waiting for server decode</small>
</div>
<div class="aprs-filter-row">
<button id="aprs-type-all" class="aprs-chip active" type="button">All</button>
<button id="aprs-type-position" class="aprs-chip" type="button">Pos</button>
<button id="aprs-type-message" class="aprs-chip" type="button">Msg</button>
<button id="aprs-type-weather" class="aprs-chip" type="button">Wx</button>
<button id="aprs-type-telemetry" class="aprs-chip" type="button">Tlm</button>
<button id="aprs-type-other" class="aprs-chip" type="button">Other</button>
<span class="aprs-filter-sep" aria-hidden="true"></span>
<button id="aprs-only-pos-btn" class="aprs-chip" type="button">Pos Only</button>
<button id="aprs-hide-crc-btn" class="aprs-chip" type="button">No CRC</button>
<button id="aprs-collapse-dup-btn" class="aprs-chip" type="button">Dupes</button>
<span class="aprs-counts">
<span id="aprs-visible-count" class="aprs-counts-value">0 shown</span>
<span id="aprs-total-count" class="aprs-counts-value">0 total</span>
<span id="aprs-latest-seen" class="aprs-counts-value">No packets yet</span>
</span>
</div>
<div id="aprs-packets"></div>
</div>
<div id="subtab-hf-aprs" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls aprs-controls">
<button id="hf-aprs-decode-toggle-btn" type="button">Enable HF APRS</button>
<input id="hf-aprs-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. SP2, beacon)" />
<small id="hf-aprs-status" style="color:var(--text-muted);">Waiting for server decode</small>
</div>
<div class="aprs-filter-row">
<button id="hf-aprs-type-all" class="aprs-chip active" type="button">All</button>
<button id="hf-aprs-type-position" class="aprs-chip" type="button">Pos</button>
<button id="hf-aprs-type-message" class="aprs-chip" type="button">Msg</button>
<button id="hf-aprs-type-weather" class="aprs-chip" type="button">Wx</button>
<button id="hf-aprs-type-telemetry" class="aprs-chip" type="button">Tlm</button>
<button id="hf-aprs-type-other" class="aprs-chip" type="button">Other</button>
<span class="aprs-filter-sep" aria-hidden="true"></span>
<button id="hf-aprs-only-pos-btn" class="aprs-chip" type="button">Pos Only</button>
<button id="hf-aprs-hide-crc-btn" class="aprs-chip" type="button">No CRC</button>
<button id="hf-aprs-collapse-dup-btn" class="aprs-chip" type="button">Dupes</button>
<span class="aprs-counts">
<span id="hf-aprs-visible-count" class="aprs-counts-value">0 shown</span>
<span id="hf-aprs-total-count" class="aprs-counts-value">0 total</span>
<span id="hf-aprs-latest-seen" class="aprs-counts-value">No packets yet</span>
</span>
</div>
<div id="hf-aprs-packets"></div>
</div>
<div id="subtab-ft8" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="ft8-decode-toggle-btn" type="button">Enable FT8</button>
<input id="ft8-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
<small id="ft8-status" style="color:var(--text-muted);">Waiting for server decode</small>
<small id="ft8-period" style="color:var(--text-muted);">Next slot --s</small>
</div>
<div class="ft8-header">
<span class="ft8-time">Time</span>
<span class="ft8-snr">SNR</span>
<span class="ft8-dt">DT</span>
<span class="ft8-freq">RF</span>
<span class="ft8-msg">Message</span>
</div>
<div id="ft8-messages"></div>
<small style="color:var(--text-muted);font-size:0.75rem;">Showing up to 200 most recent decodes. Full history is available on the <strong>Map</strong> tab.</small>
</div>
<div id="subtab-ft4" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="ft4-decode-toggle-btn" type="button">Enable FT4</button>
<input id="ft4-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
<small id="ft4-status" style="color:var(--text-muted);">Waiting for server decode</small>
<small id="ft4-period" style="color:var(--text-muted);">Next slot --s</small>
</div>
<div class="ft8-header">
<span class="ft8-time">Time</span>
<span class="ft8-snr">SNR</span>
<span class="ft8-dt">DT</span>
<span class="ft8-freq">RF</span>
<span class="ft8-msg">Message</span>
</div>
<div id="ft4-messages"></div>
<small style="color:var(--text-muted);font-size:0.75rem;">Showing up to 200 most recent decodes. Full history is available on the <strong>Map</strong> tab.</small>
</div>
<div id="subtab-ft2" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="ft2-decode-toggle-btn" type="button">Enable FT2</button>
<input id="ft2-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. CQ, DL4)" />
<small id="ft2-status" style="color:var(--text-muted);">Waiting for server decode</small>
<small id="ft2-period" style="color:var(--text-muted);">Next slot --s</small>
</div>
<div class="ft8-header">
<span class="ft8-time">Time</span>
<span class="ft8-snr">SNR</span>
<span class="ft8-dt">DT</span>
<span class="ft8-freq">RF</span>
<span class="ft8-msg">Message</span>
</div>
<div id="ft2-messages"></div>
<small style="color:var(--text-muted);font-size:0.75rem;">Showing up to 200 most recent decodes. Full history is available on the <strong>Map</strong> tab.</small>
</div>
<div id="subtab-wspr" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="wspr-decode-toggle-btn" type="button">Enable WSPR</button>
<input id="wspr-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. K1ABC, FN31)" />
<small id="wspr-status" style="color:var(--text-muted);">Waiting for server decode</small>
<small id="wspr-period" style="color:var(--text-muted);">Next slot --:--</small>
</div>
<div class="ft8-header">
<span class="ft8-time">Time</span>
<span class="ft8-snr">SNR</span>
<span class="ft8-dt">DT</span>
<span class="ft8-freq">RF</span>
<span class="ft8-msg">Message</span>
</div>
<div id="wspr-messages"></div>
</div>
<div id="subtab-cw" class="sub-tab-panel" style="display:none;">
<div class="cw-controls">
<small id="cw-status" style="color:var(--text-muted);">Waiting for server decode</small>
<div id="cw-signal-indicator" class="cw-signal-off"></div>
</div>
<div class="cw-config">
<label class="cw-auto-label">Auto WPM + Tone <input type="checkbox" id="cw-auto" checked /></label>
<label>WPM <input type="number" id="cw-wpm" min="5" max="40" value="15" /></label>
<label>Tone (Hz) <input type="number" id="cw-tone" min="100" max="10000" value="700" /></label>
</div>
<div class="cw-tone-picker">
<div class="cw-tone-picker-head">
<span>CW Audio Tone Picker</span>
<small id="cw-tone-range">--</small>
</div>
<canvas id="cw-tone-waterfall" width="320" height="56" aria-label="CW tone selector"></canvas>
</div>
<div id="cw-output"></div>
</div>
<div id="subtab-sat" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="lrpt-decode-toggle-btn" type="button">Enable Meteor LRPT</button>
<small id="sat-status" style="color:var(--text-muted);">Waiting for satellite pass</small>
</div>
<!-- View selector -->
<div class="sat-view-bar">
<button id="sat-view-live" class="sat-view-btn sat-view-active" type="button">Live</button>
<button id="sat-view-history" class="sat-view-btn" type="button">History</button>
</div>
<!-- Live view -->
<div id="sat-live-view">
<div class="sat-live-grid">
<div class="sat-live-card">
<span class="sat-live-label">Meteor LRPT</span>
<span id="sat-lrpt-state" class="sat-live-value sat-state-idle">Idle</span>
</div>
</div>
<div style="margin:0.5rem 0;">
<div style="color:var(--text-muted); font-size:0.82rem; line-height:1.5;">
<strong>Meteor-M LRPT</strong> &mdash; Low Rate Picture Transmission from Meteor-M N2-3/N2-4 (137 MHz QPSK at 72 kbps).
Multi-channel CCSDS-framed imagery (APIDs 64&ndash;69) with RGB composite output.
</div>
</div>
<div id="sat-live-latest" style="margin-top:0.5rem;"></div>
</div>
<!-- History view -->
<div id="sat-history-view" style="display:none;">
<div class="sat-history-controls">
<input id="sat-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. Meteor, LRPT)" />
<select id="sat-sort" class="sat-sort-select">
<option value="newest">Newest first</option>
<option value="oldest">Oldest first</option>
</select>
<select id="sat-type-filter" class="sat-sort-select">
<option value="all">All types</option>
<option value="lrpt">Meteor LRPT only</option>
</select>
</div>
<div class="sat-history-header">
<span class="sat-col-time">Time</span>
<span class="sat-col-type">Type</span>
<span class="sat-col-sat">Satellite</span>
<span class="sat-col-ch">Channels</span>
<span class="sat-col-lines">Lines</span>
<span class="sat-col-link">Image</span>
</div>
<div id="sat-history-list"></div>
<small id="sat-history-count" style="color:var(--text-muted);font-size:0.75rem;">No images yet</small>
</div>
</div>
<div id="subtab-wefax" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="wefax-decode-toggle-btn" type="button">Enable WEFAX</button>
<small id="wefax-status" style="color:var(--text-muted);">Idle</small>
</div>
<!-- View selector -->
<div class="sat-view-bar">
<button id="wefax-view-live" class="sat-view-btn sat-view-active" type="button">Live</button>
<button id="wefax-view-history" class="sat-view-btn" type="button">History</button>
</div>
<!-- Live view -->
<div id="wefax-live-view">
<div style="margin:0 0 0.5rem;">
<div style="color:var(--text-muted); font-size:0.82rem; line-height:1.5;">
<strong>Weather Facsimile</strong> &mdash; HF/satellite image reception (60/90/120/240 LPM).
Tune to a WEFAX station in USB mode and enable the decoder to begin receiving.
</div>
</div>
<div id="wefax-live-container" style="display:none; margin:0.5rem 0;">
<div style="display:flex; align-items:center; gap:0.5rem; margin-bottom:0.3rem;">
<strong>Receiving</strong>
<small id="wefax-live-info" style="color:var(--text-muted);"></small>
</div>
<canvas id="wefax-live-canvas" width="1809" height="800"
style="width:100%; image-rendering:pixelated; background:#000;"></canvas>
</div>
<div id="wefax-live-latest" style="margin-top:0.5rem;"></div>
</div>
<!-- History view -->
<div id="wefax-history-view" style="display:none;">
<div class="sat-history-controls">
<input id="wefax-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. IOC, LPM)" />
<select id="wefax-sort" class="sat-sort-select">
<option value="newest">Newest first</option>
<option value="oldest">Oldest first</option>
</select>
<button id="wefax-clear-btn" type="button" style="font-size:0.8rem;">Clear All</button>
</div>
<div class="sat-history-header">
<span class="sat-col-time">Time</span>
<span class="sat-col-type">IOC</span>
<span class="sat-col-sat">LPM</span>
<span class="sat-col-lines">Lines</span>
<span class="sat-col-link">Image</span>
</div>
<div id="wefax-history-list"></div>
<small id="wefax-history-count" style="color:var(--text-muted);font-size:0.75rem;">No images yet</small>
</div>
</div>
</div>
<div id="tab-map" class="tab-panel" data-tab="map" style="display:none;">
<div id="map-loading" class="decode-history-overlay content-overlay" aria-live="polite" aria-atomic="true">
<div class="decode-history-overlay-card">
<div class="decode-history-overlay-title">Loading map&hellip;</div>
<div class="decode-history-overlay-sub">Waiting for map engine to initialise</div>
</div>
</div>
<template id="tmpl-map">
<div id="map-stage">
<div class="map-overlay-panel">
<div class="map-overlay-filters">
<div class="map-locator-filter-group">
<span class="map-locator-filter-label">Filter</span>
<div id="map-locator-phase" class="map-locator-phase-row"></div>
</div>
<div class="map-locator-filter-group">
<span class="map-locator-filter-label" id="map-locator-choice-label">Show</span>
<div id="map-locator-choice-filter" class="map-locator-chip-row"></div>
</div>
<div class="map-locator-filter-group">
<span class="map-locator-filter-label">Rig</span>
<select id="map-rig-filter" class="map-history-select" aria-label="Filter by rig">
<option value="">All</option>
</select>
</div>
<div class="map-locator-filter-group">
<span class="map-locator-filter-label">History</span>
<select id="map-history-limit" class="map-history-select" aria-label="Map history limit">
<option value="15">15 min</option>
<option value="30">30 min</option>
<option value="60">1 hr</option>
<option value="180">3 hrs</option>
<option value="360">6 hrs</option>
<option value="720">12 hrs</option>
<option value="1440">24 hrs</option>
</select>
</div>
<div class="map-locator-filter-group">
<span class="map-locator-filter-label">Paths</span>
<div class="map-locator-phase-row">
<button type="button" id="map-p2p-paths-toggle" class="map-locator-phase-btn" aria-pressed="true" title="TRX paths are drawn from a station popup">TRX</button>
<button type="button" id="map-contact-paths-toggle" class="map-locator-phase-btn" aria-pressed="true" title="Directed decode paths are drawn when the target locator is known">Contact</button>
<span class="map-paths-hint">TRX paths on popup, directed decode paths when target locator is known</span>
</div>
</div>
<!-- Last, so the search field takes whatever the fixed-width
groups leave on the bar's final row rather than a sliver. -->
<div class="map-locator-filter-group map-filter-grow">
<span class="map-locator-filter-label">Search</span>
<input type="text" id="map-search-filter" class="map-search-input" placeholder="Callsign, MMSI, locator, message..." />
</div>
</div>
<div class="map-overlay-actions">
<button type="button" id="map-fullscreen-btn" class="map-fullscreen-btn">Fullscreen</button>
<button type="button" id="map-overlay-toggle-btn" class="map-overlay-toggle-btn">Hide Filters</button>
</div>
</div>
<div id="map-band-legend" class="map-band-legend" aria-label="Band color legend"></div>
<div id="aprs-map"></div>
</div>
</template>
</div>
<div id="tab-satellites" class="tab-panel" style="display:none;">
<h2 class="section-heading">Satellites</h2>
<div class="ft8-controls">
<input id="sat-pred-filter" class="ft8-filter" type="text" placeholder="Filter (e.g. ISS, NOAA, Meteor)" />
<select id="sat-pred-category" class="sat-sort-select">
<option value="all">All</option>
<option value="weather">Weather</option>
<option value="amateur">Ham Radio</option>
<option value="other">Other</option>
</select>
<select id="sat-pred-min-el" class="sat-sort-select">
<option value="0">All passes</option>
<option value="10">Min 10°</option>
<option value="20">Min 20°</option>
<option value="45">Min 45°</option>
</select>
</div>
<!-- Current passes -->
<div id="sat-pred-current-section">
<div class="sat-pred-section-title">Currently receivable</div>
<div class="sat-pred-header sat-pred-header-current">
<span class="sat-pred-col-sat">Satellite</span>
<span class="sat-pred-col-el">Max El</span>
<span class="sat-pred-col-time">AOS Start</span>
<span class="sat-pred-col-time">AOS End</span>
<span class="sat-pred-col-countdown">Time left</span>
<span class="sat-pred-col-dir">Direction</span>
</div>
<div id="sat-pred-current-list"></div>
</div>
<!-- Upcoming passes -->
<div id="sat-pred-upcoming-section">
<div class="sat-pred-section-title">Upcoming passes</div>
<div class="sat-pred-header">
<span class="sat-pred-col-time">AOS (UTC)</span>
<span class="sat-pred-col-sat">Satellite</span>
<span class="sat-pred-col-el">Max El</span>
<span class="sat-pred-col-dur">Duration</span>
<span class="sat-pred-col-dir">Direction</span>
</div>
<div id="sat-pred-list"></div>
</div>
<small id="sat-pred-status" style="color:var(--text-muted);font-size:0.75rem;">Loading predictions&hellip;</small>
</div>
<div id="tab-statistics" class="tab-panel" style="display:none;">
<h2 class="section-heading">Statistics</h2>
<template id="tmpl-statistics">
<div class="stats-controls">
<div class="stats-control-group">
<label class="stats-control-label" for="stats-rig-filter">Receiver</label>
<select id="stats-rig-filter" class="stats-select">
<option value="">All receivers</option>
</select>
</div>
<div class="stats-control-group">
<label class="stats-control-label" for="stats-history-limit">History</label>
<select id="stats-history-limit" class="stats-select">
<option value="15">15 min</option>
<option value="30">30 min</option>
<option value="60">1 hr</option>
<option value="180">3 hrs</option>
<option value="360">6 hrs</option>
<option value="720">12 hrs</option>
<option value="1440" selected>24 hrs</option>
</select>
</div>
</div>
<div class="stats-counters" id="stats-counters">
<div class="stats-counter-card">
<div class="stats-counter-value" id="stats-total-decodes">0</div>
<div class="stats-counter-label">Total decodes</div>
</div>
<div class="stats-counter-card">
<div class="stats-counter-value" id="stats-unique-stations">0</div>
<div class="stats-counter-label">Unique stations</div>
</div>
<div class="stats-counter-card">
<div class="stats-counter-value" id="stats-unique-grids">0</div>
<div class="stats-counter-label">Unique grids</div>
</div>
<div class="stats-counter-card">
<div class="stats-counter-value" id="stats-decode-rate">0</div>
<div class="stats-counter-label">Decodes / min</div>
</div>
</div>
<section class="stats-section" aria-labelledby="stats-decode-type-title">
<div class="stats-section-head">
<div id="stats-decode-type-title" class="stats-section-title">Decodes by type</div>
<div class="stats-section-subtitle">Breakdown of decoded signals by decoder type</div>
</div>
<div id="stats-decode-type-bars" class="stats-bar-chart"></div>
</section>
<section class="stats-section" aria-labelledby="stats-band-activity-title">
<div class="stats-section-head">
<div id="stats-band-activity-title" class="stats-section-title">Band activity</div>
<div class="stats-section-subtitle">Decode volume per amateur band</div>
</div>
<div id="stats-band-activity-bars" class="stats-bar-chart"></div>
</section>
<section class="stats-section" aria-labelledby="stats-rig-compare-title" id="stats-rig-compare-section" style="display:none;">
<div class="stats-section-head">
<div id="stats-rig-compare-title" class="stats-section-title">Per-receiver comparison</div>
<div class="stats-section-subtitle">Decode counts across connected receivers</div>
</div>
<div id="stats-rig-compare-bars" class="stats-bar-chart"></div>
</section>
<section class="stats-section" aria-labelledby="stats-dx-histogram-title">
<div class="stats-section-head">
<div id="stats-dx-histogram-title" class="stats-section-title">DX distance distribution</div>
<div class="stats-section-subtitle">Contact path distance histogram</div>
</div>
<div id="stats-dx-histogram-bars" class="stats-bar-chart"></div>
</section>
<section class="map-qso-summary" aria-labelledby="map-qso-summary-title">
<div class="map-qso-summary-head">
<div>
<div id="map-qso-summary-title" class="map-qso-summary-title">Longest decode paths</div>
<div class="map-qso-summary-subtitle">Top 5 directed decode paths by distance</div>
</div>
</div>
<div id="map-qso-summary-list" class="map-qso-summary-list"></div>
</section>
<section class="map-qso-summary" aria-labelledby="map-signal-summary-title">
<div class="map-qso-summary-head">
<div>
<div id="map-signal-summary-title" class="map-qso-summary-title">Strongest decoded signal</div>
<div class="map-qso-summary-subtitle">Top 5 strongest signals by SNR</div>
</div>
</div>
<div id="map-signal-summary-list" class="map-qso-summary-list"></div>
</section>
<section class="map-qso-summary" aria-labelledby="map-weak-signal-summary-title">
<div class="map-qso-summary-head">
<div>
<div id="map-weak-signal-summary-title" class="map-qso-summary-title">Weakest decoded signal</div>
<div class="map-qso-summary-subtitle">Top 5 weakest signals by SNR</div>
</div>
</div>
<div id="map-weak-signal-summary-list" class="map-qso-summary-list"></div>
</section>
</template>
</div>
<div id="tab-recorder" class="tab-panel" style="display:none;">
<h2 class="section-heading">Recorder</h2>
<div class="recorder-controls-bar">
<button id="recorder-start-btn" class="recorder-action-btn" type="button">Start recording</button>
<button id="recorder-stop-btn" class="recorder-action-btn recorder-stop" type="button" disabled>Stop recording</button>
<span id="recorder-status-indicator" class="recorder-status-indicator"></span>
</div>
<section class="recorder-section">
<h3 class="recorder-section-heading">Active recordings</h3>
<div id="recorder-active-list" class="recorder-list">
<p class="recorder-empty">No active recordings.</p>
</div>
</section>
<section class="recorder-section">
<h3 class="recorder-section-heading">Recorded files</h3>
<div class="recorder-filter-bar">
<input id="recorder-filter" type="search" class="status-input recorder-filter-input" placeholder="&#x1F50D; Filter recordings..." title="Filter recordings by filename — supports partial match (e.g. rig name, frequency, mode, date)" />
<select id="recorder-sort" class="status-input recorder-sort-select">
<option value="name-desc">Newest first</option>
<option value="name-asc">Oldest first</option>
<option value="size-desc">Largest first</option>
<option value="size-asc">Smallest first</option>
</select>
</div>
<div id="recorder-files-list" class="recorder-list">
<p class="recorder-empty">No recorded files.</p>
</div>
<div class="recorder-page-bar">
<div id="rec-page-summary" class="bm-page-summary">Showing 0-0 of 0</div>
<div class="bm-page-controls">
<button id="rec-page-prev" type="button" disabled>Previous</button>
<span id="rec-page-indicator" class="bm-page-indicator">Page 1 of 1</span>
<button id="rec-page-next" type="button" disabled>Next</button>
</div>
</div>
</section>
</div>
<div id="tab-settings" class="tab-panel" style="display:none;">
<h2 class="section-heading">Settings</h2>
<div class="sub-tab-bar">
<button class="sub-tab active" data-subtab="settings-scheduler">Scheduler</button>
<button class="sub-tab" data-subtab="settings-background-decode">Background Decode</button>
<button class="sub-tab" data-subtab="settings-bandplan">Bandplan</button>
<button class="sub-tab" data-subtab="settings-history">History</button>
</div>
<div id="subtab-settings-scheduler" class="sub-tab-panel">
<div id="scheduler-panel" class="sch-panel">
<div class="sch-toast" id="scheduler-toast" role="alert" aria-live="polite" style="display:none;"></div>
<!-- Now Playing status card (moved to top) -->
<div class="now-playing-card">
<div id="scheduler-status-card" class="sch-status-card">No activity yet.</div>
</div>
<details class="sch-activity-log-details" id="scheduler-activity-log-wrap" style="display:none;">
<summary>Activity Log</summary>
<div id="scheduler-activity-log" class="sch-activity-log"></div>
</details>
<div class="sch-row">
<label class="sch-label">Mode
<select id="scheduler-mode-select" class="status-input" aria-label="Scheduler mode">
<option value="disabled">Disabled</option>
<option value="grayline">Grayline</option>
<option value="time_span">Time Span</option>
</select>
</label>
</div>
<!-- Grayline section -->
<div id="scheduler-grayline-section" class="sch-section" style="display:none;">
<div class="sch-section-title">Grayline Settings</div>
<div class="sch-row">
<label class="sch-label">Latitude (°)
<input type="number" id="scheduler-gl-lat" class="status-input" step="0.001" placeholder="e.g. 54.352" />
</label>
<label class="sch-label">Longitude (°)
<input type="number" id="scheduler-gl-lon" class="status-input" step="0.001" placeholder="e.g. 18.646" />
</label>
<label class="sch-label">Grid square
<input type="text" id="scheduler-gl-grid" class="status-input" placeholder="e.g. JO94" maxlength="8" style="width:7rem;" />
</label>
<label class="sch-label">Transition window (min)
<input type="number" id="scheduler-gl-window" class="status-input" min="5" max="120" value="20" />
</label>
</div>
<div class="sch-row">
<label class="sch-label">Dawn bookmark
<select id="scheduler-gl-dawn" class="status-input" aria-label="Dawn bookmark"></select>
</label>
<label class="sch-label">Day bookmark
<select id="scheduler-gl-day" class="status-input" aria-label="Day bookmark"></select>
</label>
<label class="sch-label">Dusk bookmark
<select id="scheduler-gl-dusk" class="status-input" aria-label="Dusk bookmark"></select>
</label>
<label class="sch-label">Night bookmark
<select id="scheduler-gl-night" class="status-input" aria-label="Night bookmark"></select>
</label>
</div>
</div>
<!-- Time Span section -->
<div id="scheduler-timespan-section" class="sch-section" style="display:none;">
<div class="sch-section-title">Time Span Entries (UTC)</div>
<div class="sch-row" style="margin-bottom:0.75rem;">
<label class="sch-label">Interleave time (min)
<input type="number" id="scheduler-ts-interleave" class="status-input" min="1" max="60" placeholder="off" style="width:7rem;" />
</label>
<small style="color:var(--text-muted);align-self:flex-end;padding-bottom:0.35rem;">When multiple entries overlap, spend this many minutes at each before cycling. Leave blank to disable.</small>
</div>
<div id="scheduler-ts-timeline" class="sch-timeline-wrap"></div>
<button id="scheduler-ts-add-btn" class="sch-write" type="button" style="margin-bottom:0.75rem;">+ Add Entry</button>
<div id="sch-entry-form-wrap" style="display:none;">
<form id="sch-entry-form" class="bm-form">
<div class="bm-form-title" id="sch-entry-form-title">Add Entry</div>
<div class="bm-form-grid">
<label class="bm-label">Start (UTC)
<input type="time" id="scheduler-ts-start" class="status-input" title="Set both to 00:00 for all-day" />
</label>
<label class="bm-label">End (UTC)
<input type="time" id="scheduler-ts-end" class="status-input" title="Set both to 00:00 for all-day" />
</label>
<label class="bm-label" id="scheduler-ts-center-hz-wrap" title="SDR only — sets center frequency before tuning">Center freq (Hz, SDR)
<input type="number" id="scheduler-ts-center-hz" class="status-input" min="0" placeholder="optional" />
</label>
<label class="bm-label bm-label-wide">Primary bookmark
<select id="scheduler-ts-bookmark" class="status-input" aria-label="Entry bookmark"></select>
</label>
<label class="bm-label">Extra channels (virtual)
<div id="scheduler-ts-extra-bm-list" class="sch-extra-bm-list"></div>
<div style="display:flex;gap:0.4rem;margin-top:0.3rem;">
<select id="scheduler-ts-extra-bm-pick" class="status-input" aria-label="Extra bookmark"></select>
<button id="scheduler-ts-extra-bm-add" type="button" class="sch-write" style="padding:0 0.7rem;">+</button>
</div>
</label>
<label class="bm-label">Label (optional)
<input type="text" id="scheduler-ts-label" class="status-input" placeholder="e.g. 40m FT8" />
</label>
<label class="bm-label">Interleave (min, optional)
<input type="number" id="scheduler-ts-entry-interleave" class="status-input" min="1" max="60" placeholder="default" />
</label>
<label class="bm-label" style="flex-direction:row;align-items:center;gap:0.5rem;">
<input type="checkbox" id="scheduler-ts-entry-record" />
Record audio
</label>
</div>
<div class="bm-form-actions">
<button type="submit" class="bm-save-btn">Save</button>
<button type="button" id="sch-entry-form-cancel">Cancel</button>
</div>
</form>
</div>
<details class="sch-ts-details" open>
<summary>Entry details</summary>
<table class="sch-ts-table">
<thead>
<tr><th class="sch-drag-th"></th><th>Start</th><th>End</th><th>Center freq</th><th>Primary bookmark</th><th>Extra channels</th><th>Label</th><th>Interleave (min)</th><th>REC</th><th></th></tr>
</thead>
<tbody id="scheduler-ts-tbody"></tbody>
</table>
</details>
</div>
<!-- Satellite Overlay section -->
<div id="scheduler-sat-section" class="sch-section">
<div class="sch-section-title">Satellite Pass Scheduling</div>
<div class="sch-row" style="margin-bottom:0.5rem;">
<label class="sch-label" style="min-width:auto;">
<span class="sch-sat-toggle-row">
<input type="checkbox" id="scheduler-sat-enabled" />
<span>Enable satellite pass scheduler (satellite passes take priority over the regular scheduler)</span>
</span>
</label>
</div>
<div id="scheduler-sat-body" style="display:none;">
<div class="sch-row" style="margin-bottom:0.75rem;">
<label class="sch-label">Pre-tune (seconds before AOS)
<input type="number" id="scheduler-sat-pretune" class="status-input" min="0" max="300" value="60" style="width:7rem;" />
</label>
<small style="color:var(--text-muted);align-self:flex-end;padding-bottom:0.35rem;">Tune to the satellite bookmark this many seconds before acquisition. Gives decoders time to lock.</small>
</div>
<button id="scheduler-sat-add-btn" class="sch-write" type="button" style="margin-bottom:0.75rem;">+ Add Satellite</button>
<table class="sch-ts-table">
<thead>
<tr><th>Satellite</th><th>NORAD ID</th><th>Bookmark</th><th>Min elev.</th><th>Priority</th><th></th></tr>
</thead>
<tbody id="scheduler-sat-tbody"></tbody>
</table>
<div id="scheduler-sat-pass-status" class="sch-sat-pass-status" style="margin-top:0.5rem;"></div>
</div>
</div>
<!-- Satellite entry form modal -->
<div id="sch-sat-form-wrap" style="display:none;">
<form id="sch-sat-form" class="bm-form">
<div class="bm-form-title" id="sch-sat-form-title">Add Satellite</div>
<div class="bm-form-grid">
<label class="bm-label">Satellite preset
<select id="scheduler-sat-preset" class="status-input" aria-label="Satellite preset">
<option value="">— custom —</option>
<option value="METEOR-M2 3|57166">Meteor-M2 3 (137.900 MHz LRPT)</option>
<option value="METEOR-M2-4|59051">Meteor-M2-4 (137.900 MHz LRPT)</option>
<option value="ISS|25544">ISS (145.825 MHz APRS)</option>
<option value="SO-50|27607">SO-50 (436.795 MHz FM)</option>
</select>
</label>
<label class="bm-label">Satellite name
<input type="text" id="scheduler-sat-name" class="status-input" placeholder="e.g. ISS" required />
</label>
<label class="bm-label">NORAD catalog number
<input type="number" id="scheduler-sat-norad" class="status-input" min="1" placeholder="e.g. 33591" required />
</label>
<label class="bm-label bm-label-wide">Bookmark (sets freq, mode, decoders)
<select id="scheduler-sat-bookmark" class="status-input" aria-label="Satellite bookmark"></select>
</label>
<label class="bm-label">Min elevation (°)
<input type="number" id="scheduler-sat-min-el" class="status-input" min="0" max="90" step="1" value="5" />
</label>
<label class="bm-label">Priority (lower = higher)
<input type="number" id="scheduler-sat-priority" class="status-input" min="0" value="0" />
</label>
<label class="bm-label" title="SDR only — sets center frequency before tuning">Center freq (Hz, SDR)
<input type="number" id="scheduler-sat-center-hz" class="status-input" min="0" placeholder="optional" />
</label>
</div>
<div class="bm-form-actions">
<button type="submit" class="bm-save-btn">Save</button>
<button type="button" id="sch-sat-form-cancel">Cancel</button>
</div>
</form>
</div>
<!-- Actions -->
<div class="sch-actions">
<button id="scheduler-save-btn" class="sch-write sch-save-btn" type="button" style="display:none;">Save</button>
<button id="scheduler-reset-btn" class="sch-write sch-reset-btn" type="button" style="display:none;">Reset to Disabled</button>
</div>
</div>
</div>
<div id="subtab-settings-background-decode" class="sub-tab-panel" style="display:none;">
<div id="background-decode-panel" class="sch-panel">
<div class="sch-toast" id="background-decode-toast" role="alert" aria-live="polite" style="display:none;"></div>
<div class="sch-section">
<div class="sch-section-title">Background Decode</div>
<p class="bgd-intro">
Decodes saved bookmarks on hidden channels while you work another band.
Needs an SDR rig, and a bookmark is only decoded while it falls inside
the span the rig is tuned across.
</p>
<div class="bgd-controls">
<label class="bgd-enable">
<input type="checkbox" id="background-decode-enabled" />
<span>Enabled</span>
</label>
<span id="bgd-span-summary" class="bgd-span" aria-live="polite"></span>
</div>
<div class="bgd-list-toolbar">
<input type="text" id="bgd-bookmark-filter" class="bgd-checklist-filter"
placeholder="Filter bookmarks&hellip;" aria-label="Filter bookmarks" />
<div class="bgd-select-actions">
<button type="button" id="bgd-select-all-btn" class="bgd-select-btn" aria-label="Select every bookmark">All</button>
<button type="button" id="bgd-deselect-all-btn" class="bgd-select-btn" aria-label="Select no bookmarks">None</button>
</div>
</div>
<div id="bgd-bookmark-checklist" class="bgd-checklist" role="group"
aria-label="Bookmarks decoded in the background"></div>
<div id="bgd-selection-summary" class="bgd-summary" aria-live="polite"></div>
</div>
<div class="sch-actions">
<button id="background-decode-save-btn" class="sch-write sch-save-btn" type="button" style="display:none;">Save</button>
<button id="background-decode-reset-btn" class="sch-write sch-reset-btn" type="button" style="display:none;">Reset</button>
</div>
</div>
</div>
<div id="subtab-settings-bandplan" class="sub-tab-panel" style="display:none;">
<div class="sch-panel">
<div class="sch-section">
<div class="sch-section-title">Bandplan Display</div>
<div class="sch-row">
<label class="sch-label">Region
<select id="bandplan-region-select" class="status-input" aria-label="Bandplan region">
<option value="off">Off</option>
<option value="iaru_r1">IARU Region 1 (Europe, Africa, Middle East, N. Asia)</option>
<option value="iaru_r2">IARU Region 2 (Americas)</option>
<option value="iaru_r3">IARU Region 3 (S/E Asia, Pacific)</option>
</select>
</label>
</div>
<div class="sch-row">
<label class="sch-label">Show Labels
<input type="checkbox" id="bandplan-labels-check" checked aria-label="Show segment labels on bandplan strip" />
</label>
</div>
<div class="sch-row">
<small style="color:var(--text-muted);">The bandplan strip is shown above the spectrum plot when a region is selected and the visible frequency range overlaps a band allocation.</small>
</div>
<div class="sch-section" id="bandplan-legend" style="margin-top:0.5rem;">
<div class="sch-section-title">Legend</div>
<div class="bandplan-legend-grid">
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="CW"></span> CW</span>
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="Phone"></span> Phone / SSB</span>
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="Narrow"></span> Narrow / Digital</span>
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="FM"></span> FM</span>
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="All"></span> All Modes</span>
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="Beacon"></span> Beacon</span>
<span class="bandplan-legend-item"><span class="bandplan-legend-swatch" data-mode="Satellite"></span> Satellite</span>
</div>
</div>
</div>
</div>
</div>
<div id="subtab-settings-history" class="sub-tab-panel" style="display:none;">
<div class="sch-panel">
<div class="sch-section">
<div class="sch-section-title">Full Decode History</div>
<div class="sch-row">
<small style="color:var(--text-muted);">These buttons clear the server-side decode history and reset the corresponding decoder state.</small>
</div>
<div class="sch-row" style="flex-wrap:wrap;">
<button id="settings-clear-ais-history" class="sch-write sch-reset-btn" type="button">Clear full AIS history</button>
<button id="settings-clear-vdes-history" class="sch-write sch-reset-btn" type="button">Clear full VDES history</button>
<button id="settings-clear-aprs-history" class="sch-write sch-reset-btn" type="button">Clear full APRS history</button>
<button id="settings-clear-hf-aprs-history" class="sch-write sch-reset-btn" type="button">Clear full HF APRS history</button>
<button id="settings-clear-cw-history" class="sch-write sch-reset-btn" type="button">Clear full CW history</button>
<button id="settings-clear-ft8-history" class="sch-write sch-reset-btn" type="button">Clear full FT8 history</button>
<button id="settings-clear-ft4-history" class="sch-write sch-reset-btn" type="button">Clear full FT4 history</button>
<button id="settings-clear-ft2-history" class="sch-write sch-reset-btn" type="button">Clear full FT2 history</button>
<button id="settings-clear-wspr-history" class="sch-write sch-reset-btn" type="button">Clear full WSPR history</button>
<button id="settings-clear-sat-history" class="sch-write sch-reset-btn" type="button">Clear full Sat history</button>
</div>
</div>
</div>
</div>
</div>
<div id="tab-about" class="tab-panel" style="display:none;">
<h2 class="section-heading">About</h2>
<div id="auth-badge" style="display:none; margin-bottom: 1rem; padding: 0.5rem; background: var(--bg-secondary); border-radius: 0.25rem; color: var(--text-muted); font-size: 0.85rem;">Authenticated as: <strong id="auth-role-badge">--</strong></div>
<template id="tmpl-about">
<div class="sub-tab-bar">
<button class="sub-tab active" data-subtab="about-server">Server</button>
<button class="sub-tab" data-subtab="about-client">Client</button>
</div>
<div id="subtab-about-server" class="sub-tab-panel">
<div class="about-grid">
<!-- Server -->
<div class="about-card">
<div class="about-card-title">
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="12" height="5" rx="1"/><rect x="2" y="9" width="12" height="5" rx="1"/><circle cx="5" cy="4.5" r="0.7" fill="currentColor" stroke="none"/><circle cx="5" cy="11.5" r="0.7" fill="currentColor" stroke="none"/></svg>
Server
</div>
<table class="about-table">
<tr><td>Version</td><td id="about-server-ver">--</td></tr>
<tr><td>Build date</td><td id="about-server-build-date">--</td></tr>
<tr><td>Address</td><td id="about-server-addr">--</td></tr>
<tr><td>Callsign</td><td id="about-server-call">--</td></tr>
<tr><td>Location</td><td id="about-server-location">--</td></tr>
<tr><td>Uptime</td><td id="about-uptime">--</td></tr>
</table>
</div>
<!-- Radio -->
<div class="about-card">
<div class="about-card-title">
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 5h12v8H2z"/><path d="M4 5V3l8 -1v3"/><circle cx="11" cy="9" r="2"/><path d="M4 8h4"/><path d="M4 10h3"/></svg>
Radio
</div>
<table class="about-table">
<tr><td>Rig</td><td id="about-rig-info">--</td></tr>
<tr><td>Active rig</td><td id="about-active-rig">--</td></tr>
<tr><td>Available rigs</td><td id="about-rig-list">--</td></tr>
<tr><td>Connection</td><td id="about-rig-access">--</td></tr>
<tr><td>Modes</td><td id="about-modes">--</td></tr>
<tr><td>VFOs</td><td id="about-vfos">--</td></tr>
</table>
</div>
<!-- Audio -->
<div class="about-card">
<div class="about-card-title">
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5v6"/><path d="M6 3v10"/><path d="M9 6v4"/><path d="M12 4v8"/></svg>
Audio
</div>
<table class="about-table">
<tr><td>Codec</td><td id="about-audio-codec">--</td></tr>
<tr><td>Sample rate</td><td id="about-audio-samplerate">--</td></tr>
<tr><td>Channels</td><td id="about-audio-channels">--</td></tr>
<tr><td>Bitrate</td><td id="about-audio-bitrate">--</td></tr>
<tr><td>Frame duration</td><td id="about-audio-frame">--</td></tr>
<tr><td>RX status</td><td id="about-audio-rx">Off</td></tr>
<tr><td>Active streams</td><td id="about-audio-streams">--</td></tr>
</table>
</div>
<!-- Decoders -->
<div class="about-card">
<div class="about-card-title">
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12 L5 4 L8 10 L10 6 L14 12"/></svg>
Decoders
</div>
<table class="about-table">
<tr><td>FT8</td><td id="about-dec-ft8" class="about-status-off">Off</td></tr>
<tr><td>FT4</td><td id="about-dec-ft4" class="about-status-off">Off</td></tr>
<tr><td>FT2</td><td id="about-dec-ft2" class="about-status-off">Off</td></tr>
<tr><td>WSPR</td><td id="about-dec-wspr" class="about-status-off">Off</td></tr>
<tr><td>CW</td><td id="about-dec-cw" class="about-status-off">Off</td></tr>
<tr><td>APRS</td><td id="about-dec-aprs" class="about-status-off">Off</td></tr>
<tr><td>Meteor LRPT</td><td id="about-dec-lrpt" class="about-status-off">Off</td></tr>
<tr id="about-dec-wefax"><td>WEFAX</td><td>Weather Facsimile decoder</td></tr>
</table>
</div>
<!-- Integrations -->
<div class="about-card">
<div class="about-card-title">
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="8" r="2"/><path d="M8 2v2"/><path d="M8 12v2"/><path d="M2 8h2"/><path d="M12 8h2"/><path d="M3.8 3.8l1.4 1.4"/><path d="M10.8 10.8l1.4 1.4"/><path d="M3.8 12.2l1.4-1.4"/><path d="M10.8 5.2l1.4-1.4"/></svg>
Integrations
</div>
<table class="about-table">
<tr><td>PSK Reporter</td><td id="about-pskreporter">--</td></tr>
<tr><td>APRS-IS</td><td id="about-aprs-is">--</td></tr>
<tr><td>Rigctl endpoint</td><td id="about-rigctl-endpoint">--</td></tr>
<tr><td>Rigctl clients</td><td id="about-rigctl-clients">--</td></tr>
</table>
</div>
</div>
</div>
<div id="subtab-about-client" class="sub-tab-panel" style="display:none;">
<div class="about-grid">
<!-- Client -->
<div class="about-card">
<div class="about-card-title">
<svg class="about-card-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="5" r="3"/><path d="M2 14c0-3 2.7-5 6-5s6 2 6 5"/></svg>
Client
</div>
<table class="about-table">
<tr><td>Version</td><td>{pkg} v{ver}</td></tr>
<tr><td>Build date</td><td>{client_build_date}</td></tr>
<tr><td>HTTP clients</td><td id="about-clients">--</td></tr>
</table>
</div>
</div>
</div>
</template>
</div>
<div class="footer">
<div class="footer-meta">
<span class="copyright">
Built by <a href="https://www.qrzcq.com/call/SP2SJG" target="_blank" rel="noopener">SP2SJG</a> from <a href="https://haxx.space" target="_blank" rel="noopener">haxx.space</a> · &copy; <span id="copyright-year"></span>
</span>
<span class="gh-link-wrap"><a class="gh-link" href="https://git.haxx.space/sjg/trx-rs" target="_blank" rel="noopener" aria-label="Open trx-rs source repository"><svg class="gh-link-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg><span>trx-rs source</span></a></span>
</div>
<div class="hint" id="power-hint" data-state="busy" aria-live="polite">Connecting…</div>
</div>
<div id="conn-lost-overlay" class="decode-history-overlay content-overlay is-hidden" aria-live="assertive" aria-atomic="true">
<div class="decode-history-overlay-card">
<div id="conn-lost-overlay-title" class="decode-history-overlay-title">Connection lost</div>
<div id="conn-lost-overlay-sub" class="decode-history-overlay-sub">Retrying…</div>
</div>
</div>
</div>
<div id="shortcut-overlay" class="shortcut-overlay is-hidden" aria-live="polite" aria-atomic="true">
<div class="shortcut-overlay-card">
<div class="shortcut-overlay-title">Keyboard Shortcuts</div>
<table class="shortcut-table">
<tbody>
<tr><td class="shortcut-key"><kbd>F</kbd></td><td>Pick frequency (focus input)</td></tr>
<tr><td class="shortcut-key"><kbd>R</kbd></td><td>Round to nearest step</td></tr>
<tr><td class="shortcut-key"><kbd>B</kbd></td><td>Jump to previous freq/bw/mode</td></tr>
<tr><td class="shortcut-key"><kbd>[</kbd> / <kbd>]</kbd></td><td>Narrow / widen bandwidth (±10 kHz)</td></tr>
<tr><td class="shortcut-key"><kbd></kbd> / <kbd></kbd></td><td>Tune by current step</td></tr>
<tr><td class="shortcut-key"><kbd></kbd> / <kbd></kbd></td><td>Shift center frequency</td></tr>
<tr><td class="shortcut-key"><kbd>M</kbd></td><td>Open mode picker</td></tr>
<tr><td class="shortcut-key"><kbd>Z</kbd></td><td>Toggle mono / stereo (WFM)</td></tr>
<tr><td class="shortcut-key"><kbd>N</kbd></td><td>Toggle noise blanker</td></tr>
<tr><td class="shortcut-key"><kbd>Q</kbd></td><td>Toggle squelch (off / auto)</td></tr>
<tr><td class="shortcut-key"><kbd>S</kbd></td><td>Spectrum screenshot</td></tr>
<tr><td class="shortcut-key"><kbd>+</kbd> / <kbd>-</kbd></td><td>Zoom spectrum in / out</td></tr>
<tr><td class="shortcut-key"><kbd>0</kbd></td><td>Reset spectrum zoom</td></tr>
<tr><td class="shortcut-key"><kbd>F1</kbd></td><td>Toggle this help</td></tr>
<tr><td class="shortcut-key"><kbd>Esc</kbd></td><td>Close overlay / exit fullscreen</td></tr>
</tbody>
</table>
<div class="shortcut-overlay-hint">Press <kbd>F1</kbd> or <kbd>Esc</kbd> to close</div>
</div>
</div>
<div id="decode-history-overlay" class="history-progress is-hidden" role="status" aria-live="polite" aria-atomic="true">
<div class="history-progress-text">
<span id="decode-history-overlay-title" class="history-progress-title">Loading decode history…</span>
<span id="decode-history-overlay-sub" class="history-progress-sub">Preparing recent decodes for the UI</span>
</div>
<span class="history-progress-track"><span id="decode-history-progress-bar" class="history-progress-bar"></span></span>
</div>
<script defer src="/vendor/opus-decoder-0.7.11.min.js" charset="UTF-8"></script>
<script defer src="/vendor/leaflet.js"></script>
<script type="module" src="/app.js"></script>
<!-- Template cloning is handled by the typed application bundle. -->
</body>
</html>