7520796869
Add a style picker dropdown to the tab bar (right of rig picker) with four styles — Original, Nord, Monokai, Contrast — each with full light/dark variants. CSS: define data-style attribute overrides for all CSS custom properties (bg, card-bg, borders, text, accents, jog, audio level, filter, spectrum background) for each of the three new styles × two themes (6 new blocks). JS: introduce CANVAS_PALETTE lookup table covering spectrum/waveform/ waterfall colors for all style×theme combinations. Add currentStyle(), canvasPalette(), setStyle() helpers. Persist selection to localStorage. Replace all isLight ternaries in drawing code with palette lookups: - drawOverviewWaterfall, drawOverviewSignalHistory, waterfallColor signatures changed from isLight flag to pal object - drawSpectrum uses canvasPalette() for grid lines, labels, fill, line - spectrumBgColor() now delegates to canvasPalette().bg Theme toggle also triggers a spectrum redraw so canvas colors update immediately when switching light/dark. Also fix light-theme spectrum rendering broken since canvas drawing used hardcoded dark-only colors (white grid lines invisible on light bg). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
356 lines
19 KiB
HTML
356 lines
19 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" href="/favicon.ico" />
|
|
<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>
|
|
</head>
|
|
<body>
|
|
<div class="card" id="card">
|
|
<div class="tab-bar" style="display:none;" id="tab-bar">
|
|
<div class="tab-bar-nav">
|
|
<button class="tab active" data-tab="main">Main</button>
|
|
<button class="tab" data-tab="plugins">Plugins</button>
|
|
<button class="tab" data-tab="about">About</button>
|
|
</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="nord">Nord</option>
|
|
<option value="monokai">Monokai</option>
|
|
<option value="contrast">Contrast</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>
|
|
<div class="header">
|
|
<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>
|
|
<div class="overview-strip">
|
|
<canvas id="overview-canvas" aria-hidden="true"></canvas>
|
|
</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 id="spectrum-panel" style="display:none;">
|
|
<div class="spectrum-wrap">
|
|
<canvas id="spectrum-canvas"></canvas>
|
|
<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>
|
|
</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="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 · Ctrl+Scroll to tune · Drag to pan · Drag BW edges to resize</div>
|
|
<div id="spectrum-hint-touch" class="spectrum-hint-touch">Pinch to zoom · Drag to pan · Drag BW edges to resize</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 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>
|
|
</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">Deemphasis
|
|
<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">Audio
|
|
<select id="wfm-audio-mode" class="status-input">
|
|
<option value="stereo">Stereo</option>
|
|
<option value="mono">Mono</option>
|
|
</select>
|
|
</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">RX Audio</button>
|
|
<button id="tx-audio-btn" type="button">TX 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-plugins" 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="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="map">Map</button>
|
|
</div>
|
|
<div id="subtab-overview" class="sub-tab-panel">
|
|
<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>
|
|
<div id="subtab-map" class="sub-tab-panel" style="display:none;">
|
|
<div class="map-controls">
|
|
<label><input type="checkbox" id="map-filter-aprs" checked /> APRS</label>
|
|
<label><input type="checkbox" id="map-filter-ft8" checked /> FT8</label>
|
|
<label><input type="checkbox" id="map-filter-wspr" checked /> WSPR</label>
|
|
</div>
|
|
<div id="aprs-map"></div>
|
|
</div>
|
|
<div id="subtab-aprs" class="sub-tab-panel" style="display:none;">
|
|
<div class="aprs-controls">
|
|
<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 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-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-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-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 <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="300" max="1200" value="700" /></label>
|
|
</div>
|
|
<div id="cw-output"></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 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="/aprs.js"></script>
|
|
<script src="/ft8.js"></script>
|
|
<script src="/wspr.js"></script>
|
|
<script src="/cw.js"></script>
|
|
</body>
|
|
</html>
|