bed49ce13b
Move template cloning into navigateToTab() so deferred <template> content is materialized before any tab-specific initialization runs. Previously the document-level template cloner fired after navigateToTab due to event propagation order, causing initAprsMap() and scheduleStatsRender() to target elements that did not yet exist. Also defer statistics control wiring until the first render so event listeners are attached after the template is cloned. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
1662 lines
98 KiB
HTML
1662 lines
98 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="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-signal" viewBox="0 0 16 16" fill="currentColor"><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"/></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-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">
|
|
<button class="tab active" data-tab="main">
|
|
<svg class="tab-icon" aria-hidden="true"><use href="#icon-home"/></svg>
|
|
<span class="tab-label">Main</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="digital-modes">
|
|
<svg class="tab-icon" aria-hidden="true"><use href="#icon-signal"/></svg>
|
|
<span class="tab-label">Digital modes</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="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="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" 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:#9aa4b5;"></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"></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" tabindex="0" role="img" aria-label="Waterfall display" 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">‹</button>
|
|
<button id="spectrum-center-right-btn" class="spectrum-edge-shift spectrum-edge-shift-right" type="button" aria-label="Shift spectrum center right">›</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 · Ctrl+Scroll to tune · Drag to pan · Drag BW edges to resize · +/- zoom · Arrows pan · 0 reset</div>
|
|
<div id="spectrum-hint-touch" class="spectrum-hint-touch">Pinch to zoom · Drag to pan · 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">−</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" 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">
|
|
<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>
|
|
<button class="sub-tab" data-subtab="wefax">WEFAX</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="wefax">
|
|
<strong>WEFAX Decoder</strong>
|
|
<div style="color:var(--text-muted); font-size:0.85rem; margin-top:0.2rem;">
|
|
Weather Facsimile — HF/satellite image reception (60/90/120/240 LPM)
|
|
</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> — Low Rate Picture Transmission from Meteor-M N2-3/N2-4 (137 MHz QPSK at 72 kbps).
|
|
Multi-channel CCSDS-framed imagery (APIDs 64–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…</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> — 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="map-loading-msg">Loading map…</div>
|
|
<template id="tmpl-map">
|
|
<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>
|
|
</template>
|
|
</div>
|
|
<div id="tab-statistics" class="tab-panel" style="display:none;">
|
|
<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="🔍 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;">
|
|
<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>
|
|
<!-- 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 class="bgd-select-actions">
|
|
<button type="button" id="bgd-select-all-btn" class="bgd-select-btn" aria-label="Select all bookmarks">Select All</button>
|
|
<button type="button" id="bgd-deselect-all-btn" class="bgd-select-btn" aria-label="Deselect all bookmarks">Deselect All</button>
|
|
</div>
|
|
<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>
|
|
<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="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" 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="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 defer src="https://cdn.jsdelivr.net/npm/opus-decoder@0.7.11/dist/opus-decoder.min.js" charset="UTF-8"></script>
|
|
<script defer src="/webgl-renderer.js"></script>
|
|
<script defer src="/app.js"></script>
|
|
<script>
|
|
// Lazy plugin loader: loads plugin scripts when their tab/feature is first activated
|
|
(function() {
|
|
var pluginScripts = {
|
|
'digital-modes': ['/ft8.js', '/ft4.js', '/ft2.js', '/wspr.js', '/cw.js', '/background-decode.js', '/sat.js', '/wefax.js'],
|
|
'map-data': ['/map-core.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js'],
|
|
'map': ['/map-core.js', '/leaflet-ais-tracksymbol.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js', '/sat.js', '/sat-scheduler.js'],
|
|
'statistics': ['/map-core.js'],
|
|
'bookmarks': ['/bookmarks.js'],
|
|
'recorder': [],
|
|
'settings': ['/vchan.js', '/scheduler.js']
|
|
};
|
|
var loaded = new Set();
|
|
function loadPlugins(tab) {
|
|
var scripts = pluginScripts[tab];
|
|
if (!scripts) return;
|
|
scripts.forEach(function(src) {
|
|
if (loaded.has(src)) return;
|
|
loaded.add(src);
|
|
var s = document.createElement('script');
|
|
s.src = src;
|
|
s.defer = true;
|
|
document.body.appendChild(s);
|
|
});
|
|
}
|
|
// Load core plugins immediately (needed on main tab).
|
|
// 'map-data' loads map-core.js + data handlers (ais/aprs/vdes/hf-aprs) eagerly
|
|
// so decode history replay can populate map data structures before the map
|
|
// tab is opened. The 'loaded' Set prevents double-loading when the map tab
|
|
// is later activated.
|
|
['digital-modes', 'map-data', 'bookmarks', 'settings'].forEach(loadPlugins);
|
|
// Load others on tab switch
|
|
document.addEventListener('click', function(e) {
|
|
var tab = e.target.closest('[data-tab]');
|
|
if (tab) loadPlugins(tab.dataset.tab);
|
|
});
|
|
window.loadPluginsForTab = loadPlugins;
|
|
})();
|
|
</script>
|
|
<!-- Template cloning is handled by navigateToTab() in app.js -->
|
|
<script defer src="/vendor/leaflet.js"></script>
|
|
</body>
|
|
</html>
|