Files
trx-rs/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html
T
Claude 2150f61828 [feat](trx-frontend-http): add Statistics panel, move summaries from Map tab
Extract the three summary sections (longest decode paths, strongest/weakest
signals) from the Map tab into a new dedicated Statistics tab. Add new
analytics: decode counters, unique stations/grids, decode rate, decode-by-type
breakdown, band activity, per-receiver comparison, and DX distance histogram.
The Statistics panel has its own receiver and history filters independent of
the map view.

https://claude.ai/code/session_01R9T4Byg7uw6qpkTsyVJd9k
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-30 07:31:14 +02:00

1514 lines
90 KiB
HTML

<!DOCTYPE html>
<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="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="preconnect" href="https://unpkg.com" crossorigin />
<link rel="preload" as="style" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" /></noscript>
<link rel="stylesheet" href="/style.css" />
<link rel="preload" as="style" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /></noscript>
</head>
<body>
<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">
<button class="tab active" data-tab="main">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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"/></svg>
<span class="tab-label">Main</span>
</button>
<button class="tab" data-tab="bookmarks">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 2h8v12l-4-2.5L4 14V2z"/></svg>
<span class="tab-label">Bookmarks</span>
</button>
<button class="tab" data-tab="digital-modes">
<svg class="tab-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><rect x="1" y="11" width="2.5" height="4" rx="0.5"/><rect x="4.75" y="8" width="2.5" height="7" rx="0.5"/><rect x="8.5" y="5" width="2.5" height="10" rx="0.5"/><rect x="12.25" y="2" width="2.5" height="13" rx="0.5"/></svg>
<span class="tab-label">Digital modes</span>
</button>
<button class="tab" data-tab="map">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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"/></svg>
<span class="tab-label">Map</span>
</button>
<button class="tab" data-tab="statistics">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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"/></svg>
<span class="tab-label">Statistics</span>
</button>
<button class="tab" data-tab="settings">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><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"/></svg>
<span class="tab-label">Settings</span>
</button>
<button class="tab" data-tab="about">
<svg class="tab-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><circle cx="8" cy="8" r="6"/><path d="M8 7v5"/><circle cx="8" cy="5" r="0.5" fill="currentColor" stroke="none"/></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>
<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="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" style="display:none; max-width: 30rem; margin: 0 auto 0.9rem; padding: 1.25rem 0 1.5rem; text-align: center;">
<div style="margin-bottom: 1.5rem;">
<div style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;">Access Required</div>
<div style="color: var(--text-muted);">Enter passphrase to continue</div>
</div>
<form id="auth-form" style="margin: 1rem 0 0.35rem;">
<input type="password" id="auth-passphrase" placeholder="Passphrase" autocomplete="off" style="width: 100%; padding: 0.65rem 0.75rem; margin-bottom: 1rem; border: 1px solid var(--border-light); border-radius: 0.45rem; background: var(--input-bg); color: var(--text); font-size: 1rem; box-sizing: border-box;" />
<button type="submit" style="width: 100%; padding: 0.65rem 0.75rem; background: var(--accent-green); color: #fff; border: none; border-radius: 0.45rem; font-weight: 700; cursor: pointer; font-size: 1rem; box-sizing: border-box;">Login</button>
</form>
<button id="auth-guest-btn" type="button" style="width: 100%; padding: 0.65rem 0.75rem; background: var(--btn-bg); color: var(--text); border: 1px solid var(--border-light); border-radius: 0.45rem; font-weight: 600; cursor: pointer; font-size: 1rem; box-sizing: border-box; margin-top: 0; display: none;">Continue as Guest</button>
<div id="auth-error" style="color: #ff6b6b; font-size: 0.9rem; margin-top: 1rem; display: none;"></div>
<div id="auth-role" style="margin-top: 1rem; color: var(--text-muted); font-size: 0.85rem; 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" 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:#9aa4b5;"></div>
</div>
<div id="content" style="display:none;">
<div class="signal-visual-block">
<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-bandplan-strip" aria-label="Band plan allocations"></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"></canvas>
<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" 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">Bandwidth <input type="number" id="spectrum-bw-input" value="" step="0.1" min="0.1" /> kHz</label>
<button id="spectrum-bw-set-btn" type="button">Set</button>
<button id="spectrum-bw-auto-btn" type="button">Auto BW</button>
<button id="spectrum-bw-sweet-btn" type="button">Sweet-spot</button>
</div>
<div id="spectrum-level-row">
<label class="overview-control" id="spectrum-peak-hold-label">Peak Hold
<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">Floor <input type="number" id="spectrum-floor-input" value="-115" step="5" /> dB</label>
<label id="spectrum-range-label">Range <input type="number" id="spectrum-range-input" value="90" step="10" min="10" /> dB</label>
<button id="spectrum-auto-btn" type="button">Auto</button>
<label id="spectrum-gamma-label">Contrast <input type="range" id="spectrum-gamma-input" min="0.2" max="3.0" step="0.1" value="1.0" /><span id="spectrum-gamma-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="--" />
<div class="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="--" />
<div class="label"><span>Center Frequency</span></div>
</div>
<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="full-row controls-tray-shell">
<div class="controls-tray-scroll">
<div class="controls-tray">
<div class="controls-row full-row">
<div class="controls-col label-below-col">
<div class="label"><span>Mode</span></div>
<div class="inline">
<select class="status-input" id="mode"></select>
</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-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 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">Toggle PTT</button>
<button id="power-btn" type="button">Toggle Power</button>
<button id="lock-btn" type="button">Lock</button>
</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 / Scheduler</span></div>
<div class="channel-scheduler-controls">
<div class="vchan-picker" id="vchan-picker"></div>
<div class="scheduler-control-row" style="display:none">
<div class="scheduler-release-wrap">
<button id="scheduler-release-btn" type="button">Release to Scheduler</button>
<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>
<div id="scheduler-release-status" class="scheduler-release-status">Scheduler is controlling the rig.</div>
<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>
</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>
<div class="full-row label-below-row" id="audio-row">
<div class="label"><span>Audio</span></div>
<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>
<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>
<label class="vol-label" id="sdr-squelch-wrap" style="display:none;">SQL<input type="range" id="sdr-squelch" min="0" max="100" value="0" class="vol-slider" /><small class="vol-pct" id="sdr-squelch-pct">Open</small><button id="sdr-squelch-auto" type="button" class="sql-auto-btn" title="Set squelch to current noise level">Auto</button></label>
<div id="audio-level">
<div id="audio-level-fill"></div>
</div>
<small id="audio-status" style="min-width: 60px;">Off</small>
</div>
</div>
</div>
</div>
</div>
</div>
</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">
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-aprs" value="aprs" /> APRS</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ais" value="ais" /> AIS</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft8" value="ft8" /> FT8</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft4" value="ft4" /> FT4</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-ft2" value="ft2" /> FT2</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-wspr" value="wspr" /> WSPR</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-hf-aprs" value="hf-aprs" /> HF APRS</label>
<label class="bm-decoder-check"><input type="checkbox" id="bm-dec-lrpt" value="lrpt" /> Meteor LRPT</label>
</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">
<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">
<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>
</div>
<div id="subtab-overview" class="sub-tab-panel">
<div class="plugin-item">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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>
<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="ais-summary">
<div class="ais-summary-card">
<span class="ais-summary-label">Channels</span>
<span id="ais-channel-summary" class="ais-summary-value">A 161.975 MHz · B 162.025 MHz</span>
</div>
<div class="ais-summary-card">
<span class="ais-summary-label">Tracked</span>
<span id="ais-vessel-count" class="ais-summary-value">0 vessels</span>
</div>
<div class="ais-summary-card">
<span class="ais-summary-label">Latest</span>
<span id="ais-latest-seen" class="ais-summary-value">No traffic yet</span>
</div>
</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-summary">
<div class="aprs-summary-card">
<span class="aprs-summary-label">Frames</span>
<span id="aprs-total-count" class="aprs-summary-value">0 total</span>
</div>
<div class="aprs-summary-card">
<span class="aprs-summary-label">Visible</span>
<span id="aprs-visible-count" class="aprs-summary-value">0 shown</span>
</div>
<div class="aprs-summary-card">
<span class="aprs-summary-label">Latest</span>
<span id="aprs-latest-seen" class="aprs-summary-value">No packets yet</span>
</div>
</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>
</div>
<div class="aprs-filter-row">
<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>
</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-summary">
<div class="aprs-summary-card">
<span class="aprs-summary-label">Frames</span>
<span id="hf-aprs-total-count" class="aprs-summary-value">0 total</span>
</div>
<div class="aprs-summary-card">
<span class="aprs-summary-label">Visible</span>
<span id="hf-aprs-visible-count" class="aprs-summary-value">0 shown</span>
</div>
<div class="aprs-summary-card">
<span class="aprs-summary-label">Latest</span>
<span id="hf-aprs-latest-seen" class="aprs-summary-value">No packets yet</span>
</div>
</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>
</div>
<div class="aprs-filter-row">
<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>
</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>
<button id="sat-view-predictions" class="sat-view-btn" type="button">Predictions</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>
<!-- Predictions view -->
<div id="sat-predictions-view" style="display:none;">
<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>
</div>
<div id="tab-map" class="tab-panel" style="display:none;">
<div id="map-stage">
<div class="map-overlay-panel">
<div class="map-locator-filter-group">
<span class="map-locator-filter-label">Filter by</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">Search</span>
<input type="text" id="map-search-filter" class="map-search-input" placeholder="Callsign, MMSI, locator, message..." />
</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">TRX Paths On</button>
<button type="button" id="map-contact-paths-toggle" class="map-locator-phase-btn">Contact Paths On</button>
<span class="map-locator-empty">TRX paths on popup, directed decode paths when target locator is known</span>
</div>
</div>
</div>
<div class="map-corner-controls">
<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 id="map-band-legend" class="map-band-legend" aria-label="Band color legend"></div>
<div id="aprs-map"></div>
</div>
</div>
<div id="tab-statistics" class="tab-panel" style="display:none;">
<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>
</div>
<div id="tab-settings" class="tab-panel" style="display:none;">
<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" 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>
<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">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>
</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">
<summary>Entry details</summary>
<table class="sch-ts-table">
<thead>
<tr><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></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>
</select>
</label>
<label class="bm-label">Satellite name
<input type="text" id="scheduler-sat-name" class="status-input" placeholder="e.g. NOAA 19" 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" style="display:none;"></div>
<!-- Now Playing status card (moved to top) -->
<div class="now-playing-card">
<div id="background-decode-status-card" class="sch-status-card">No background decode bookmarks configured.</div>
</div>
<div class="sch-section">
<div class="sch-section-title">Configuration</div>
<div class="sch-row">
<label class="sch-label bgd-toggle-wrap">Background decode
<span class="bgd-toggle-row">
<input type="checkbox" id="background-decode-enabled" />
<span>Enable hidden background decoder channels</span>
</span>
</label>
</div>
<div class="sch-row" style="flex-direction:column;gap:0.5rem;">
<label class="sch-label" style="min-width:100%;">Bookmarks
<input type="text" id="bgd-bookmark-filter" class="bgd-checklist-filter" placeholder="Filter bookmarks..." />
</label>
<div id="bgd-bookmark-checklist" class="bgd-checklist"></div>
</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;">
<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>
<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>
</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>
</div>
<div class="footer">
<div 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> · <span class="gh-link-wrap"><a class="gh-link" href="https://github.com/sgrams/trx-rs" target="_blank" rel="noopener" aria-label="Open trx-rs on GitHub"><svg class="gh-link-icon" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0.2a8 8 0 0 0-2.53 15.59c0.4 0.07 0.55-0.17 0.55-0.39l-0.01-1.37c-2.23 0.49-2.7-0.95-2.7-0.95-0.36-0.91-0.89-1.15-0.89-1.15-0.73-0.49 0.06-0.48 0.06-0.48 0.8 0.06 1.22 0.82 1.22 0.82 0.72 1.22 1.88 0.87 2.34 0.67 0.07-0.51 0.28-0.86 0.5-1.06-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.58 0.81-2.14-0.08-0.2-0.35-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82a7.56 7.56 0 0 1 4.01 0c1.53-1.03 2.2-0.82 2.2-0.82 0.43 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.81 1.27 0.81 2.14 0 3.07-1.87 3.75-3.66 3.95 0.29 0.25 0.54 0.73 0.54 1.48l-0.01 2.2c0 0.22 0.14 0.47 0.55 0.39A8 8 0 0 0 8 0.2Z"></path></svg><span>trx-rs on GitHub</span></a></span><span id="copyright-year"></span>
</div>
<div class="hint" id="power-hint">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="decode-history-overlay is-hidden" aria-live="polite" aria-atomic="true">
<div class="decode-history-overlay-card">
<div id="decode-history-overlay-title" class="decode-history-overlay-title">Loading decode history…</div>
<div id="decode-history-overlay-sub" class="decode-history-overlay-sub">Preparing recent decodes for the UI</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/opus-decoder@0.7.11/dist/opus-decoder.min.js" charset="UTF-8"></script>
<script src="/webgl-renderer.js"></script>
<script src="/app.js"></script>
<script src="/ais.js"></script>
<script src="/vdes.js"></script>
<script src="/aprs.js"></script>
<script src="/hf-aprs.js"></script>
<script src="/ft8.js"></script>
<script src="/ft4.js"></script>
<script src="/ft2.js"></script>
<script src="/wspr.js"></script>
<script src="/cw.js"></script>
<script src="/sat.js"></script>
<script src="/bookmarks.js"></script>
<script src="/scheduler.js"></script>
<script src="/sat-scheduler.js"></script>
<script src="/background-decode.js"></script>
<script src="/vchan.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="/leaflet-ais-tracksymbol.js"></script>
</body>
</html>