feat: ship waterfall receiver overhaul and platform mode updates

This commit is contained in:
Smittix
2026-02-22 23:22:37 +00:00
parent 5d4b61b4c3
commit 5f480caa3f
41 changed files with 7635 additions and 3516 deletions

View File

@@ -1,211 +0,0 @@
<!-- ANALYTICS MODE -->
<div id="analyticsMode" class="mode-content">
{# Analytics Dashboard Sidebar Panel #}
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Summary</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="analytics-grid" id="analyticsSummaryCards">
<div class="analytics-card" data-mode="adsb">
<div class="card-count" id="analyticsCountAdsb">0</div>
<div class="card-label">Aircraft</div>
<div class="card-sparkline" id="analyticsSparkAdsb"></div>
</div>
<div class="analytics-card" data-mode="ais">
<div class="card-count" id="analyticsCountAis">0</div>
<div class="card-label">Vessels</div>
<div class="card-sparkline" id="analyticsSparkAis"></div>
</div>
<div class="analytics-card" data-mode="wifi">
<div class="card-count" id="analyticsCountWifi">0</div>
<div class="card-label">WiFi</div>
<div class="card-sparkline" id="analyticsSparkWifi"></div>
</div>
<div class="analytics-card" data-mode="bluetooth">
<div class="card-count" id="analyticsCountBt">0</div>
<div class="card-label">Bluetooth</div>
<div class="card-sparkline" id="analyticsSparkBt"></div>
</div>
<div class="analytics-card" data-mode="dsc">
<div class="card-count" id="analyticsCountDsc">0</div>
<div class="card-label">DSC</div>
<div class="card-sparkline" id="analyticsSparkDsc"></div>
</div>
<div class="analytics-card" data-mode="acars">
<div class="card-count" id="analyticsCountAcars">0</div>
<div class="card-label">ACARS</div>
<div class="card-sparkline" id="analyticsSparkAcars"></div>
</div>
<div class="analytics-card" data-mode="vdl2">
<div class="card-count" id="analyticsCountVdl2">0</div>
<div class="card-label">VDL2</div>
<div class="card-sparkline" id="analyticsSparkVdl2"></div>
</div>
<div class="analytics-card" data-mode="aprs">
<div class="card-count" id="analyticsCountAprs">0</div>
<div class="card-label">APRS</div>
<div class="card-sparkline" id="analyticsSparkAprs"></div>
</div>
<div class="analytics-card" data-mode="meshtastic">
<div class="card-count" id="analyticsCountMesh">0</div>
<div class="card-label">Mesh</div>
<div class="card-sparkline" id="analyticsSparkMesh"></div>
</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Operational Insights</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="analytics-insight-grid" id="analyticsInsights">
<div class="analytics-empty">Insights loading...</div>
</div>
<div class="analytics-top-changes">
<div class="analytics-section-header">Top Changes</div>
<div id="analyticsTopChanges">
<div class="analytics-empty">No change signals yet</div>
</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Mode Health</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="analytics-health" id="analyticsHealth"></div>
</div>
</div>
<div class="section" id="analyticsSquawkSection" style="display:none;">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Emergency Squawks</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="squawk-emergency" id="analyticsSquawkPanel">
<div class="squawk-title">Active Emergency Codes</div>
<div id="analyticsSquawkList"></div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Temporal Patterns</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div id="analyticsPatternList">
<div class="analytics-empty">No recurring patterns detected</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Recent Alerts</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="analytics-alert-feed" id="analyticsAlertFeed">
<div class="analytics-empty">No recent alerts</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Correlations</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div id="analyticsCorrelations">
<div class="analytics-empty">No correlations detected</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Geofences</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div id="analyticsGeofenceList"></div>
<button class="btn btn-sm" onclick="Analytics.addGeofence()" style="margin-top:8px; font-size:10px; padding:4px 10px; background:var(--accent-cyan); color:#fff; border:none; border-radius:4px; cursor:pointer;">
+ Add Zone
</button>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Target View</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="analytics-target-toolbar">
<input id="analyticsTargetQuery" type="text" placeholder="Search callsign, ICAO, MMSI, MAC, SSID, node..." onkeydown="if(event.key==='Enter'){Analytics.searchTarget();}">
<button onclick="Analytics.searchTarget()">Search</button>
</div>
<div id="analyticsTargetSummary" class="analytics-target-summary">Search to correlate entities across modes</div>
<div id="analyticsTargetResults">
<div class="analytics-empty">No target selected</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Session Replay</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="analytics-replay-toolbar">
<select id="analyticsReplaySelect"></select>
<button onclick="Analytics.loadReplay()">Load</button>
<button onclick="Analytics.playReplay()">Play</button>
<button onclick="Analytics.pauseReplay()">Pause</button>
<button onclick="Analytics.stepReplay()">Step</button>
</div>
<div id="analyticsReplayMeta" class="analytics-target-summary">No replay loaded</div>
<div id="analyticsReplayTimeline">
<div class="analytics-empty">Select a recording to replay key events</div>
</div>
</div>
</div>
<div class="section">
<h3 class="section-header collapsible" onclick="toggleSection(this)">
<span>Export Data</span>
<span class="collapse-icon">&#9660;</span>
</h3>
<div class="section-content">
<div class="export-controls">
<select id="exportMode">
<option value="adsb">ADS-B</option>
<option value="ais">AIS</option>
<option value="wifi">WiFi</option>
<option value="bluetooth">Bluetooth</option>
<option value="dsc">DSC</option>
</select>
<select id="exportFormat">
<option value="json">JSON</option>
<option value="csv">CSV</option>
</select>
<button onclick="Analytics.exportData()">Export</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,115 @@
<!-- FINGERPRINT MODE -->
<div id="fingerprintMode" class="mode-content">
<!-- Intro -->
<div class="section">
<div style="font-size:11px; color:var(--text-dim); line-height:1.6;">
RF Fingerprinting captures the baseline radio environment at a location.
Record a baseline when the environment is "clean", then compare later to
detect new transmitters, surveillance devices, or signal anomalies.
</div>
</div>
<!-- Workflow tab selector -->
<div class="section">
<h3>Workflow</h3>
<div style="display:flex; gap:4px;">
<button class="fp-tab-btn active" id="fpTabRecord" onclick="Fingerprint.showTab('record')">
1 — Record
</button>
<button class="fp-tab-btn" id="fpTabCompare" onclick="Fingerprint.showTab('compare')">
2 — Compare
</button>
</div>
<div id="fpTabHint" style="margin-top:8px; font-size:11px; color:var(--text-dim); line-height:1.5;">
Record a <strong style="color:var(--text-secondary);">baseline</strong> in a known-clean RF environment, then use <strong style="color:var(--text-secondary);">Compare</strong> later to detect new or anomalous signals.
</div>
</div>
<!-- Record tab -->
<div id="fpRecordPanel">
<div class="section">
<h3>Step 1 — Select Device</h3>
<div class="form-group">
<label>SDR Device</label>
<select id="fpDevice">
<option value="">Loading…</option>
</select>
</div>
</div>
<div class="section">
<h3>Step 2 — Scanner Status</h3>
<div style="display:flex; align-items:center; gap:8px; padding:6px 0;">
<span id="fpScannerDot" style="width:8px; height:8px; border-radius:50%; background:rgba(255,255,255,0.2); flex-shrink:0;"></span>
<span id="fpScannerStatusText" style="font-size:11px; color:var(--text-secondary); flex:1;">Checking…</span>
</div>
<div style="display:flex; gap:6px;">
<button class="run-btn" id="fpScannerStartBtn" onclick="Fingerprint.startScanner()" style="flex:1;">Start Scanner</button>
<button class="stop-btn" id="fpScannerStopBtn" onclick="Fingerprint.stopScanner()" style="flex:1; display:none;">Stop Scanner</button>
</div>
</div>
<div class="section">
<h3>Step 3 — Record Baseline</h3>
<div class="form-group">
<label>Session Name</label>
<input type="text" id="fpSessionName" placeholder="e.g. Office — Mon morning">
</div>
<div class="form-group">
<label>Location <span style="color:var(--text-dim); font-weight:normal;">(optional)</span></label>
<input type="text" id="fpSessionLocation" placeholder="e.g. 3rd floor, room 301">
</div>
<div style="display:flex; align-items:center; gap:10px; margin:6px 0;">
<span style="font-size:10px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em;">Observations</span>
<span id="fpObsCount" style="font-size:14px; font-family:var(--font-mono); color:var(--accent-cyan, #4aa3ff);">0</span>
</div>
<div id="fpRecordStatus" style="font-size:11px; color:var(--text-dim); margin-bottom:6px; min-height:14px;"></div>
<button class="run-btn" id="fpStartBtn" onclick="Fingerprint.startRecording()">Start Recording</button>
<button class="stop-btn" id="fpStopBtn" style="display:none;" onclick="Fingerprint.stopRecording()">Stop &amp; Save</button>
</div>
</div>
<!-- Compare tab -->
<div id="fpComparePanel" style="display:none;">
<div class="section">
<h3>How It Works</h3>
<div style="font-size:11px; color:var(--text-dim); line-height:1.6;">
<div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:6px;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">1.</span>
<span>Ensure the scanner is running (switch to Record tab to start it).</span>
</div>
<div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:6px;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">2.</span>
<span>Select a previously recorded baseline below.</span>
</div>
<div style="display:flex; gap:8px; align-items:flex-start; margin-bottom:6px;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">3.</span>
<span>Click <strong style="color:var(--text-secondary);">Compare Now</strong> — a 3-second live scan is collected.</span>
</div>
<div style="display:flex; gap:8px; align-items:flex-start;">
<span style="color:var(--accent-cyan); font-weight:700; flex-shrink:0;">4.</span>
<span>Anomalies are scored by z-score. <span style="color:#ef4444;">Red = strong deviation</span>, <span style="color:#a855f7;">purple = new signal</span>.</span>
</div>
</div>
</div>
<div class="section">
<h3>Baseline</h3>
<div class="form-group">
<label>Session</label>
<select id="fpBaselineSelect">
<option value="">No baselines saved yet</option>
</select>
</div>
<div id="fpCompareStatus" style="font-size:11px; color:var(--text-dim); margin-bottom:6px; min-height:14px;"></div>
<button class="run-btn" onclick="Fingerprint.compareNow()">Compare Now</button>
</div>
<div class="section" id="fpAnomalyList" style="display:none;">
<h3>Anomalies</h3>
<div id="fpAnomalyItems"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,126 @@
<!-- RF HEATMAP MODE -->
<div id="rfheatmapMode" class="mode-content">
<!-- What is this? -->
<div class="section">
<h3>RF Heatmap</h3>
<div style="background:rgba(74,163,255,0.07); border:1px solid rgba(74,163,255,0.2); border-radius:6px; padding:10px; font-size:11px; color:var(--text-secondary); line-height:1.6;">
Walk around with INTERCEPT running. Your GPS position and the current signal strength are saved as a point on the map every few metres. The result is a <strong style="color:var(--accent-cyan);">coverage heatmap</strong> — bright areas have strong signal, dark areas are weak or absent.
</div>
</div>
<!-- Step 1 — Signal source -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">1</span>What to Map</h3>
<div class="form-group">
<label>Signal Source</label>
<select id="rfhmSource" onchange="RFHeatmap.setSource(this.value); RFHeatmap.onSourceChange()">
<option value="wifi">WiFi — RSSI of nearby networks</option>
<option value="bluetooth">Bluetooth — RSSI of nearby devices</option>
<option value="scanner">SDR Scanner — broadband RF power</option>
</select>
</div>
<!-- SDR device picker — only shown for Scanner source -->
<div id="rfhmDeviceGroup" style="display:none;">
<div class="form-group">
<label>SDR Device</label>
<select id="rfhmDevice">
<option value="">Loading…</option>
</select>
</div>
</div>
<div id="rfhmSourceHint" style="font-size:11px; color:var(--text-dim); margin-top:4px; line-height:1.5;">
Walk near WiFi access points — their signal strength at each location is recorded.
</div>
<!-- Source running status + inline start/stop -->
<div id="rfhmSourceStatusRow" style="margin-top:10px; padding:8px 10px; background:rgba(0,0,0,0.3); border-radius:6px;">
<div style="display:flex; align-items:center; gap:7px; margin-bottom:6px;">
<span id="rfhmSourceDot" style="width:7px; height:7px; border-radius:50%; background:rgba(255,255,255,0.2); flex-shrink:0;"></span>
<span id="rfhmSourceStatusText" style="font-size:11px; color:var(--text-dim);">Checking…</span>
</div>
<button id="rfhmSourceStartBtn" class="run-btn" style="padding:6px; font-size:11px;" onclick="RFHeatmap.startSource()">Start Scanner</button>
<button id="rfhmSourceStopBtn" class="stop-btn" style="display:none; padding:6px; font-size:11px;" onclick="RFHeatmap.stopSource()">Stop Scanner</button>
</div>
</div>
<!-- Step 2 — Location -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">2</span>Your Location</h3>
<div style="display:flex; justify-content:space-between; align-items:center; padding:6px 0; border-bottom:1px solid rgba(255,255,255,0.06);">
<span style="font-size:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em;">GPS</span>
<span id="rfhmGpsPill" style="font-family:var(--font-mono); font-size:11px; color:var(--text-dim);">No Fix</span>
</div>
<div style="margin-top:8px;">
<div style="font-size:10px; color:var(--text-muted); margin-bottom:6px; line-height:1.5;">
No GPS? Enter a fixed location to map signals from a stationary point.
</div>
<div style="display:flex; gap:6px;">
<div class="form-group" style="flex:1; margin-bottom:0;">
<label>Latitude</label>
<input type="number" id="rfhmManualLat" step="0.0001" placeholder="37.7749" oninput="RFHeatmap.setManualCoords()">
</div>
<div class="form-group" style="flex:1; margin-bottom:0;">
<label>Longitude</label>
<input type="number" id="rfhmManualLon" step="0.0001" placeholder="-122.4194" oninput="RFHeatmap.setManualCoords()">
</div>
</div>
<button class="preset-btn" onclick="RFHeatmap.useObserverLocation()" style="font-size:10px; margin-top:5px;">
Use Saved Observer Location
</button>
</div>
</div>
<!-- Step 3 — Verify live signal -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">3</span>Live Signal</h3>
<div style="background:rgba(0,0,0,0.3); border-radius:6px; padding:10px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<span style="font-size:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em;">Current</span>
<span id="rfhmLiveSignal" style="font-family:var(--font-mono); font-size:16px; color:var(--text-dim);">— dBm</span>
</div>
<!-- Signal strength bar -->
<div style="height:4px; background:rgba(255,255,255,0.08); border-radius:2px; overflow:hidden;">
<div id="rfhmSignalBar" style="height:100%; width:0%; background:var(--accent-cyan); border-radius:2px; transition:width 0.3s ease;"></div>
</div>
<div id="rfhmSignalStatus" style="font-size:10px; color:var(--text-dim); margin-top:5px;">Waiting for signal data…</div>
</div>
</div>
<!-- Step 4 — Record -->
<div class="section">
<h3><span style="color:var(--accent-cyan); margin-right:6px;">4</span>Record</h3>
<div class="form-group">
<label>Sample Every</label>
<div style="display:flex; align-items:center; gap:8px;">
<input type="range" id="rfhmMinDist" min="1" max="50" value="5" step="1" style="flex:1;"
oninput="document.getElementById('rfhmMinDistVal').textContent=this.value+'m'; RFHeatmap.setMinDist(parseInt(this.value))">
<span id="rfhmMinDistVal" style="font-family:var(--font-mono); font-size:11px; color:var(--accent-cyan); min-width:28px; text-align:right;">5m</span>
</div>
<div style="font-size:10px; color:var(--text-dim); margin-top:3px;">A new point is added after you move this distance.</div>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; padding:6px 0; margin-bottom:4px; border-top:1px solid rgba(255,255,255,0.06);">
<span style="font-size:10px; color:var(--text-muted); text-transform:uppercase; letter-spacing:.05em;">Points Captured</span>
<span id="rfhmPointCount" style="font-family:var(--font-mono); font-size:14px; color:var(--accent-cyan);">0</span>
</div>
<button class="run-btn" id="rfhmRecordBtn" onclick="RFHeatmap.startRecording()">Start Recording</button>
<button class="stop-btn" id="rfhmStopBtn" style="display:none;" onclick="RFHeatmap.stopRecording()">Stop Recording</button>
</div>
<!-- Map actions -->
<div class="section">
<h3>Map</h3>
<div style="display:flex; gap:6px;">
<button class="preset-btn" style="flex:1;" onclick="RFHeatmap.clearPoints()">Clear</button>
<button class="preset-btn" style="flex:1;" onclick="RFHeatmap.exportGeoJSON()">Export GeoJSON</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,142 @@
<!-- WATERFALL MODE -->
<div id="waterfallMode" class="mode-content">
<div class="section">
<h3>Spectrum Waterfall</h3>
<div style="font-size:11px; color:var(--text-secondary); line-height:1.45;">
Click spectrum or waterfall to tune. Scroll to step-tune. Ctrl/Cmd + scroll to zoom span.
</div>
</div>
<div class="section">
<h3>Device</h3>
<div class="form-group">
<label>SDR Device</label>
<select id="wfDevice" onchange="Waterfall && Waterfall.onDeviceChange && Waterfall.onDeviceChange()">
<option value="">Loading devices...</option>
</select>
</div>
<div id="wfDeviceInfo" style="display:none; background:rgba(0,0,0,0.32); border:1px solid rgba(74,163,255,0.22); border-radius:6px; padding:8px; margin-top:6px; font-size:11px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<span style="color:var(--text-muted); text-transform:uppercase; font-size:10px; letter-spacing:.05em;">Type</span>
<span id="wfDeviceType" style="color:var(--accent-cyan); font-family:var(--font-mono);">--</span>
</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:4px;">
<span style="color:var(--text-muted); text-transform:uppercase; font-size:10px; letter-spacing:.05em;">Range</span>
<span id="wfDeviceRange" style="color:var(--text-secondary); font-family:var(--font-mono);">--</span>
</div>
<div style="display:flex; justify-content:space-between; align-items:center;">
<span style="color:var(--text-muted); text-transform:uppercase; font-size:10px; letter-spacing:.05em;">Capture SR</span>
<span id="wfDeviceBw" style="color:var(--text-secondary); font-family:var(--font-mono);">--</span>
</div>
</div>
</div>
<div class="section">
<h3>Tuning</h3>
<div class="form-group">
<label>Center Frequency (MHz)</label>
<input type="number" id="wfCenterFreq" value="100.0000" step="0.001" min="0.001" max="6000">
</div>
<div class="form-group">
<label>Span (MHz)</label>
<input type="number" id="wfSpanMhz" value="2.4" step="0.1" min="0.05" max="30">
</div>
<div class="button-group" style="display:grid; grid-template-columns:1fr 1fr; gap:6px;">
<button class="preset-btn" onclick="Waterfall.applyPreset('fm')">FM Broadcast</button>
<button class="preset-btn" onclick="Waterfall.applyPreset('air')">Airband</button>
<button class="preset-btn" onclick="Waterfall.applyPreset('marine')">Marine</button>
<button class="preset-btn" onclick="Waterfall.applyPreset('ham2m')">2m Ham</button>
</div>
</div>
<div class="section">
<h3>Capture</h3>
<div class="form-group">
<label>Gain <span style="color:var(--text-dim); font-weight:normal;">(dB or AUTO)</span></label>
<input type="text" id="wfGain" value="AUTO" placeholder="AUTO or numeric">
</div>
<div class="form-group">
<label>FFT Size</label>
<select id="wfFftSize">
<option value="256">256</option>
<option value="512">512</option>
<option value="1024" selected>1024</option>
<option value="2048">2048</option>
<option value="4096">4096</option>
</select>
</div>
<div class="form-group">
<label>Frame Rate</label>
<select id="wfFps">
<option value="10">10 fps</option>
<option value="20" selected>20 fps</option>
<option value="30">30 fps</option>
<option value="40">40 fps</option>
</select>
</div>
<div class="form-group">
<label>FFT Averaging</label>
<select id="wfAvgCount">
<option value="1">1 (none)</option>
<option value="2">2</option>
<option value="4" selected>4</option>
<option value="8">8</option>
<option value="16">16</option>
</select>
</div>
<div class="form-group">
<label>PPM Correction</label>
<input type="number" id="wfPpm" value="0" step="1" min="-200" max="200" placeholder="0">
</div>
<div class="checkbox-group" style="margin-top:8px;">
<label>
<input type="checkbox" id="wfBiasT">
Bias-T (antenna power)
</label>
</div>
</div>
<div class="section">
<h3>Display</h3>
<div class="form-group">
<label>Color Palette</label>
<select id="wfPalette" onchange="Waterfall.setPalette(this.value)">
<option value="turbo" selected>Turbo</option>
<option value="plasma">Plasma</option>
<option value="inferno">Inferno</option>
<option value="viridis">Viridis</option>
</select>
</div>
<div class="form-group">
<label>Noise Floor (dB)</label>
<input type="number" id="wfDbMin" value="-100" step="5" disabled>
</div>
<div class="form-group">
<label>Ceiling (dB)</label>
<input type="number" id="wfDbMax" value="-20" step="5" disabled>
</div>
<div class="checkbox-group" style="margin-top:8px;">
<label>
<input type="checkbox" id="wfPeakHold" onchange="Waterfall.togglePeakHold(this.checked)">
Peak Hold
</label>
<label>
<input type="checkbox" id="wfBandAnnotations" checked onchange="Waterfall.toggleAnnotations(this.checked)">
Band Labels
</label>
<label>
<input type="checkbox" id="wfAutoRange" checked onchange="Waterfall.toggleAutoRange(this.checked)">
Auto Range
</label>
</div>
</div>
<div class="section">
<button class="run-btn" id="wfStartBtn" onclick="Waterfall.start()">Start Waterfall</button>
<button class="stop-btn" id="wfStopBtn" style="display:none;" onclick="Waterfall.stop()">Stop Waterfall</button>
<div id="wfStatus" style="margin-top:8px; font-size:11px; color:var(--text-dim);"></div>
<div style="margin-top:6px; font-size:10px; color:var(--text-muted);">
Tune with click. Use Monitor in the top strip for audio listen.
</div>
</div>
</div>