Files
trx-rs/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html
T
sjg d8f7ebafa0 [fix](trx-frontend): fix CW picker visibility and widen tone range
Fix CW picker redraw when the decoder sub-tab becomes visible to avoid
white/blank canvas rendering.
Widen CW tone picker/input range to 100-10000 Hz and raise CW/CWR
bandwidth max to 9 kHz.

Co-authored-by: Codex <codex@openai.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
2026-03-04 23:04:56 +01:00

634 lines
36 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="https://cdn.jsdelivr.net/npm/@fontsource/dseg14-classic/400.css" />
<link rel="stylesheet" href="/style.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="/leaflet-ais-tracksymbol.js"></script>
</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"></div>
<div class="subtitle" id="rig-subtitle" style="font-weight: 700;">Rig: --</div>
</div>
</div>
<div class="tab-bar-nav">
<button class="tab active" data-tab="main">Main</button>
<button class="tab" data-tab="bookmarks">Bookmarks</button>
<button class="tab" data-tab="decoders">Decoders</button>
<button class="tab" data-tab="map">Map</button>
<button class="tab" data-tab="about">About</button>
</div>
</div>
<div class="top-bar-actions">
<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>
</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="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>
<div id="spectrum-bookmark-axis"></div>
<div id="spectrum-panel" style="display:none;">
<div class="spectrum-wrap">
<div id="spectrum-bookmark-side-left" class="spectrum-bookmark-side spectrum-bookmark-side-left" aria-hidden="true"></div>
<button id="spectrum-center-left-btn" class="spectrum-edge-shift spectrum-edge-shift-left" type="button" aria-label="Shift spectrum center left">&lsaquo;</button>
<canvas id="spectrum-canvas"></canvas>
<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 id="spectrum-bookmark-side-right" class="spectrum-bookmark-side spectrum-bookmark-side-right" aria-hidden="true"></div>
<div id="spectrum-tooltip"></div>
<div id="spectrum-freq-axis"></div>
</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>
</select>
</label>
<label id="spectrum-floor-label">Floor <input type="number" id="spectrum-floor-input" value="-115" step="5" /> dB</label>
<button id="spectrum-auto-btn" type="button">Auto</button>
</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</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>
<canvas id="signal-overlay-canvas" aria-hidden="true"></canvas>
</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 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>
<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>
<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>
</div>
<div class="label"><span>WFM</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">
<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>
<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-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>
</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="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">Decoders
<div class="bm-decoder-checks">
<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-wspr" value="wspr" /> WSPR</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>Name</th>
<th>Frequency</th>
<th>Mode</th>
<th>BW</th>
<th>Locator</th>
<th>Category</th>
<th>Decoders</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>
</div>
<div id="tab-decoders" 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="cw">CW</button>
<button class="sub-tab" data-subtab="ft8">FT8</button>
<button class="sub-tab" data-subtab="wspr">WSPR</button>
<button class="sub-tab" data-subtab="rds">RDS</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>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>
<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">
<button id="ais-pause-btn" type="button">Pause</button>
<button id="ais-clear-btn" type="button">Clear</button>
<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">
<button id="vdes-pause-btn" type="button">Pause</button>
<button id="vdes-clear-btn" type="button">Clear</button>
<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">
<button id="aprs-pause-btn" type="button">Pause</button>
<button id="aprs-clear-btn" type="button">Clear</button>
<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-ft8" class="sub-tab-panel" style="display:none;">
<div class="ft8-controls">
<button id="ft8-decode-toggle-btn" type="button">Enable FT8</button>
<button id="ft8-pause-btn" type="button">Pause</button>
<button id="ft8-clear-btn" type="button">Clear</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>
</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>
<button id="wspr-pause-btn" type="button">Pause</button>
<button id="wspr-clear-btn" type="button">Clear</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">
<button id="cw-pause-btn" type="button">Pause</button>
<button id="cw-clear-btn" type="button">Clear</button>
<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>
<div id="tab-map" class="tab-panel" style="display:none;">
<div class="map-locator-filters">
<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">Search</span>
<input type="text" id="map-search-filter" class="map-search-input" placeholder="Callsign, MMSI, locator, message..." />
</div>
</div>
<div id="map-stage">
<button type="button" id="map-fullscreen-btn" class="map-fullscreen-btn">Fullscreen</button>
<div id="aprs-map"></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>
<table class="about-table">
<tr><td>Server version</td><td id="about-server-ver">--</td></tr>
<tr><td>Server address</td><td id="about-server-addr">--</td></tr>
<tr><td>Server callsign</td><td id="about-server-call">--</td></tr>
<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>Rig connection</td><td id="about-rig-access">--</td></tr>
<tr><td>Supported modes</td><td id="about-modes">--</td></tr>
<tr><td>VFOs</td><td id="about-vfos">--</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>
<tr><td>PSK Reporter</td><td id="about-pskreporter">--</td></tr>
<tr><td>Client version</td><td>{pkg} v{ver}</td></tr>
<tr><td>Connected clients</td><td id="about-clients">--</td></tr>
</table>
</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>
- <span id="footer-server-build">trx-server v-- --</span>
- <span id="footer-client-build">trx-client v{ver} {client_build_date}</span>
</div>
<div class="hint" id="power-hint">Connecting…</div>
</div>
</div>
<script src="/app.js"></script>
<script src="/ais.js"></script>
<script src="/vdes.js"></script>
<script src="/aprs.js"></script>
<script src="/ft8.js"></script>
<script src="/wspr.js"></script>
<script src="/cw.js"></script>
<script src="/bookmarks.js"></script>
</body>
</html>