From 9d72c88a2858a10607a96bb2667175a7d5ba5aef Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 19 May 2026 23:08:49 +0100 Subject: [PATCH] fix: sweep final hardcoded cyan from mode JS files and CSS - proximity-radar.js: fix missed dot stroke in new-device creation path - gps.js: GPS constellation color via object getter; globe atmosphere reads CSS var - websdr.js: globe atmosphere, map markers, popup buttons, point label use CSS var - subghz.js: canvas strokeStyle reads --accent-cyan - sstv.js: ISS track polyline reads --accent-cyan - app.js: info message border-left uses var(--accent-cyan) - subghz.css, gps.css: replace all #00d4ff with var(--accent-cyan) Co-Authored-By: Claude Sonnet 4.6 --- static/css/modes/gps.css | 2 +- static/css/modes/subghz.css | 82 ++-- static/js/components/proximity-radar.js | 2 +- static/js/core/app.js | 2 +- static/js/modes/gps.js | 482 ++++++++++++------------ static/js/modes/sstv.js | 4 +- static/js/modes/subghz.js | 4 +- static/js/modes/websdr.js | 17 +- 8 files changed, 298 insertions(+), 297 deletions(-) diff --git a/static/css/modes/gps.css b/static/css/modes/gps.css index cd7449f..0a434e7 100644 --- a/static/css/modes/gps.css +++ b/static/css/modes/gps.css @@ -364,7 +364,7 @@ } /* Constellation colors */ -.gps-const-gps { background-color: #00d4ff; } +.gps-const-gps { background-color: var(--accent-cyan); } .gps-const-glonass { background-color: #00ff88; } .gps-const-galileo { background-color: #ff8800; } .gps-const-beidou { background-color: #ff4466; } diff --git a/static/css/modes/subghz.css b/static/css/modes/subghz.css index bcb2847..b2ee27d 100644 --- a/static/css/modes/subghz.css +++ b/static/css/modes/subghz.css @@ -93,9 +93,9 @@ } .subghz-preset-btn:hover { - background: var(--accent-cyan, #00d4ff); + background: var(--accent-cyan, var(--accent-cyan)); color: var(--text-inverse); - border-color: var(--accent-cyan, #00d4ff); + border-color: var(--accent-cyan, var(--accent-cyan)); } /* Tab navigation for RX / Decode / Sweep */ @@ -126,8 +126,8 @@ } .subghz-tab.active { - color: var(--accent-cyan, #00d4ff); - border-bottom-color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); + border-bottom-color: var(--accent-cyan, var(--accent-cyan)); } .subghz-tab-content { @@ -225,7 +225,7 @@ } .subghz-status-dot.decode { - background: #00d4ff; + background: var(--accent-cyan); animation: subghz-pulse 0.8s ease-in-out infinite; } @@ -250,7 +250,7 @@ } .subghz-status-timer { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); } /* Control buttons */ @@ -296,13 +296,13 @@ .subghz-btn:hover { background: var(--bg-tertiary, #1a1f2e); - border-color: var(--accent-cyan, #00d4ff); + border-color: var(--accent-cyan, var(--accent-cyan)); } .subghz-btn.active { background: rgba(0, 212, 255, 0.1); - border-color: var(--accent-cyan, #00d4ff); - color: var(--accent-cyan, #00d4ff); + border-color: var(--accent-cyan, var(--accent-cyan)); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-btn.start { @@ -456,7 +456,7 @@ .subghz-capture-tag.auto { border-color: rgba(0, 212, 255, 0.55); - color: #00d4ff; + color: var(--accent-cyan); background: rgba(0, 212, 255, 0.12); } @@ -473,7 +473,7 @@ } .subghz-capture-freq { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); font-weight: 600; } @@ -526,8 +526,8 @@ } .subghz-capture-actions button.trim-btn:hover { - color: #00d4ff; - border-color: #00d4ff; + color: var(--accent-cyan); + border-color: var(--accent-cyan); } .subghz-capture-actions button.delete-btn:hover { @@ -537,7 +537,7 @@ .subghz-capture-actions button.select-btn { border-color: rgba(0, 212, 255, 0.5); - color: #00d4ff; + color: var(--accent-cyan); } .subghz-capture-actions button.select-btn.selected { @@ -622,7 +622,7 @@ } .subghz-decode-model { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); font-weight: 600; } @@ -693,7 +693,7 @@ } .subghz-tx-modal .tx-freq { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); font-weight: 600; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; } @@ -754,7 +754,7 @@ margin-top: 8px !important; margin-bottom: 0 !important; font-size: 11px !important; - color: var(--accent-cyan, #00d4ff) !important; + color: var(--accent-cyan, var(--accent-cyan)) !important; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; } @@ -807,7 +807,7 @@ margin: 0 0 8px 0; font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 10px; - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-tx-burst-marker { @@ -864,7 +864,7 @@ border: 1px solid rgba(0, 212, 255, 0.5); border-radius: 3px; background: transparent; - color: #00d4ff; + color: var(--accent-cyan); font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 10px; cursor: pointer; @@ -902,7 +902,7 @@ .subghz-tx-trim-btn { background: rgba(0, 212, 255, 0.14); - color: #00d4ff; + color: var(--accent-cyan); border-color: rgba(0, 212, 255, 0.55) !important; } @@ -952,7 +952,7 @@ } .subghz-sweep-tooltip .tip-freq { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-sweep-tooltip .tip-power { @@ -1044,8 +1044,8 @@ .subghz-action-btn.decode:hover { background: rgba(0, 212, 255, 0.12); - border-color: var(--accent-cyan, #00d4ff); - color: var(--accent-cyan, #00d4ff); + border-color: var(--accent-cyan, var(--accent-cyan)); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-action-btn.capture:hover { @@ -1092,7 +1092,7 @@ } .subghz-peak-item .peak-freq { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-peak-item .peak-power { @@ -1148,7 +1148,7 @@ } .subghz-strip-dot.rx { background: var(--neon-green); } -.subghz-strip-dot.decode { background: #00d4ff; } +.subghz-strip-dot.decode { background: var(--accent-cyan); } .subghz-strip-dot.tx { background: #ff4444; } .subghz-strip-dot.sweep { background: var(--neon-orange); } @@ -1169,7 +1169,7 @@ color: var(--text-primary, #e0e0e0); } -.subghz-strip-value.accent-cyan { color: var(--accent-cyan, #00d4ff); } +.subghz-strip-value.accent-cyan { color: var(--accent-cyan, var(--accent-cyan)); } .subghz-strip-value.accent-green { color: var(--neon-green); } .subghz-strip-value.accent-orange { color: var(--neon-orange); } @@ -1181,7 +1181,7 @@ } .subghz-strip-timer { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); font-weight: 600; min-width: 40px; } @@ -1274,7 +1274,7 @@ } .subghz-phase-step.active { - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); text-shadow: 0 0 6px rgba(0, 212, 255, 0.3); } @@ -1329,12 +1329,12 @@ .subghz-burst-indicator.recent { border-color: rgba(0, 212, 255, 0.45); - color: #00d4ff; + color: var(--accent-cyan); background: rgba(0, 212, 255, 0.1); } .subghz-burst-indicator.recent .subghz-burst-dot { - background: #00d4ff; + background: var(--accent-cyan); } .subghz-console-toggle { @@ -1382,7 +1382,7 @@ } .subghz-log-msg { color: var(--text-secondary, #999); } -.subghz-log-msg.info { color: var(--accent-cyan, #00d4ff); } +.subghz-log-msg.info { color: var(--accent-cyan, var(--accent-cyan)); } .subghz-log-msg.success { color: var(--neon-green); } .subghz-log-msg.warn { color: var(--neon-orange); } .subghz-log-msg.error { color: var(--accent-red, #ff4444); } @@ -1405,7 +1405,7 @@ font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 20px; font-weight: 700; - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); letter-spacing: 1px; } @@ -1446,8 +1446,8 @@ } .subghz-hub-card--cyan { border-color: rgba(0, 212, 255, 0.2); } -.subghz-hub-card--cyan:hover { border-color: var(--accent-cyan, #00d4ff); background: rgba(0, 212, 255, 0.05); } -.subghz-hub-card--cyan .subghz-hub-icon { color: var(--accent-cyan, #00d4ff); } +.subghz-hub-card--cyan:hover { border-color: var(--accent-cyan, var(--accent-cyan)); background: rgba(0, 212, 255, 0.05); } +.subghz-hub-card--cyan .subghz-hub-icon { color: var(--accent-cyan, var(--accent-cyan)); } .subghz-hub-card--green { border-color: rgba(0, 255, 136, 0.2); } .subghz-hub-card--green:hover { border-color: var(--neon-green); background: rgba(0, 255, 136, 0.05); } @@ -1528,7 +1528,7 @@ .subghz-saved-selection-count { font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 10px; - color: var(--accent-cyan, #00d4ff); + color: var(--accent-cyan, var(--accent-cyan)); margin-right: 4px; } @@ -1545,8 +1545,8 @@ } .subghz-op-back-btn:hover { - border-color: var(--accent-cyan, #00d4ff); - color: var(--accent-cyan, #00d4ff); + border-color: var(--accent-cyan, var(--accent-cyan)); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-op-panel-title { @@ -1667,7 +1667,7 @@ color: var(--text-primary, #e0e0e0); } -.subghz-rx-info-value.accent-cyan { color: var(--accent-cyan, #00d4ff); } +.subghz-rx-info-value.accent-cyan { color: var(--accent-cyan, var(--accent-cyan)); } .subghz-rx-level-wrapper { display: flex; @@ -1735,7 +1735,7 @@ } .subghz-rx-burst-pill.recent { - color: #00d4ff; + color: var(--accent-cyan); border-color: rgba(0, 212, 255, 0.65); background: rgba(0, 212, 255, 0.12); } @@ -1861,8 +1861,8 @@ } .subghz-wf-pause-btn:hover { - border-color: var(--accent-cyan, #00d4ff); - color: var(--accent-cyan, #00d4ff); + border-color: var(--accent-cyan, var(--accent-cyan)); + color: var(--accent-cyan, var(--accent-cyan)); } .subghz-wf-pause-btn.paused { diff --git a/static/js/components/proximity-radar.js b/static/js/components/proximity-radar.js index 8df1d7d..c450579 100644 --- a/static/js/components/proximity-radar.js +++ b/static/js/components/proximity-radar.js @@ -308,7 +308,7 @@ const ProximityRadar = (function() { dot.setAttribute('r', dotSize); dot.setAttribute('fill', color); dot.setAttribute('fill-opacity', isSelected ? 1 : 0.4 + confidence * 0.5); - dot.setAttribute('stroke', isSelected ? '#00d4ff' : color); + dot.setAttribute('stroke', isSelected ? _accent() : color); dot.setAttribute('stroke-width', isSelected ? 2 : 1); innerG.appendChild(dot); diff --git a/static/js/core/app.js b/static/js/core/app.js index ffc3c04..e7b25ca 100644 --- a/static/js/core/app.js +++ b/static/js/core/app.js @@ -374,7 +374,7 @@ function showInfo(text) { const infoEl = document.createElement('div'); infoEl.className = 'info-msg'; - infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid #00d4ff; font-family: "Roboto Condensed", "Arial Narrow", sans-serif; font-size: 11px; color: #888; word-break: break-all;'; + infoEl.style.cssText = 'padding: 12px 15px; margin-bottom: 8px; background: #0a0a0a; border: 1px solid #1a1a1a; border-left: 2px solid var(--accent-cyan); font-family: "Roboto Condensed", "Arial Narrow", sans-serif; font-size: 11px; color: #888; word-break: break-all;'; infoEl.textContent = text; output.insertBefore(infoEl, output.firstChild); } diff --git a/static/js/modes/gps.js b/static/js/modes/gps.js index f7f7d6e..7d7f603 100644 --- a/static/js/modes/gps.js +++ b/static/js/modes/gps.js @@ -1,9 +1,9 @@ -/** - * GPS Mode - * Live GPS data display with satellite sky view, signal strength bars, - * position/velocity/DOP readout. Connects to gpsd via backend SSE stream. - */ - +/** + * GPS Mode + * Live GPS data display with satellite sky view, signal strength bars, + * position/velocity/DOP readout. Connects to gpsd via backend SSE stream. + */ + const GPS = (function() { let connected = false; let lastPosition = null; @@ -17,10 +17,10 @@ const GPS = (function() { // Constellation color map const CONST_COLORS = { - 'GPS': '#00d4ff', + get GPS() { return getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff'; }, 'GLONASS': '#00ff88', - 'Galileo': '#ff8800', - 'BeiDou': '#ff4466', + 'Galileo': '#ff8800', + 'BeiDou': '#ff4466', 'SBAS': '#ffdd00', 'QZSS': '#cc66ff', }; @@ -266,7 +266,7 @@ const GPS = (function() { .backgroundColor('rgba(0,0,0,0)') .globeImageUrl(GPS_GLOBE_TEXTURE_URL) .showAtmosphere(true) - .atmosphereColor('#3bb9ff') + .atmosphereColor(getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#3bb9ff') .atmosphereAltitude(0.17) .pointRadius('radius') .pointAltitude('altitude') @@ -462,16 +462,16 @@ const GPS = (function() { fetch('/gps/auto-connect', { method: 'POST' }) .then(r => r.json()) .then(data => { - if (data.status === 'connected') { - connected = true; - updateConnectionUI(true, data.has_fix); - if (data.position) { - lastPosition = data.position; - updatePositionUI(data.position); - } - if (data.sky) { - lastSky = data.sky; - updateSkyUI(data.sky); + if (data.status === 'connected') { + connected = true; + updateConnectionUI(true, data.has_fix); + if (data.position) { + lastPosition = data.position; + updatePositionUI(data.position); + } + if (data.sky) { + lastSky = data.sky; + updateSkyUI(data.sky); } subscribeToStream(); startSkyPolling(); @@ -484,14 +484,14 @@ const GPS = (function() { } else { connected = false; updateConnectionUI(false, false, 'error', data.message || 'gpsd not available'); - } - }) - .catch(() => { - connected = false; - updateConnectionUI(false, false, 'error', 'Connection failed — is the server running?'); - }); - } - + } + }) + .catch(() => { + connected = false; + updateConnectionUI(false, false, 'error', 'Connection failed — is the server running?'); + }); + } + function disconnect() { unsubscribeFromStream(); stopSkyPolling(); @@ -501,10 +501,10 @@ const GPS = (function() { connected = false; updateConnectionUI(false); }); - } - - function onGpsStreamData(data) { - if (!connected) return; + } + + function onGpsStreamData(data) { + if (!connected) return; if (data.type === 'position') { lastPosition = data; updatePositionUI(data); @@ -517,7 +517,7 @@ const GPS = (function() { updateSkyUI(data); } } - + function startSkyPolling() { stopSkyPolling(); // Poll satellite data every 5 seconds as a reliable fallback @@ -526,22 +526,22 @@ const GPS = (function() { skyPollTimer = setInterval(pollSatellites, 5000); } - function stopSkyPolling() { - if (skyPollTimer) { - clearInterval(skyPollTimer); - skyPollTimer = null; - } - } - + function stopSkyPolling() { + if (skyPollTimer) { + clearInterval(skyPollTimer); + skyPollTimer = null; + } + } + function pollSatellites() { if (!connected) return; fetch('/gps/satellites') .then(r => r.json()) .then(data => { - if (data.status === 'ok' && data.sky) { - lastSky = data.sky; - updateSkyUI(data.sky); - } + if (data.status === 'ok' && data.sky) { + lastSky = data.sky; + updateSkyUI(data.sky); + } }) .catch(() => {}); } @@ -589,141 +589,141 @@ const GPS = (function() { }) .catch(() => {}); } - - function subscribeToStream() { - // Subscribe to the global GPS stream instead of opening a separate SSE connection - if (typeof addGpsStreamSubscriber === 'function') { - addGpsStreamSubscriber(onGpsStreamData); - } - } - - function unsubscribeFromStream() { - if (typeof removeGpsStreamSubscriber === 'function') { - removeGpsStreamSubscriber(onGpsStreamData); - } - } - - // ======================== - // UI Updates - // ======================== - - function updateConnectionUI(isConnected, hasFix, state, message) { - const dot = document.getElementById('gpsStatusDot'); - const text = document.getElementById('gpsStatusText'); - const connectBtn = document.getElementById('gpsConnectBtn'); - const disconnectBtn = document.getElementById('gpsDisconnectBtn'); - const devicePath = document.getElementById('gpsDevicePath'); - - if (dot) { - dot.className = 'gps-status-dot'; - if (state === 'connecting') dot.classList.add('waiting'); - else if (state === 'error') dot.classList.add('error'); - else if (isConnected && hasFix) dot.classList.add('connected'); - else if (isConnected) dot.classList.add('waiting'); - } - if (text) { - if (state === 'connecting') text.textContent = 'Connecting...'; - else if (state === 'error') text.textContent = message || 'Connection failed'; - else if (isConnected && hasFix) text.textContent = 'Connected (Fix)'; - else if (isConnected) text.textContent = 'Connected (No Fix)'; - else text.textContent = 'Disconnected'; - } - if (connectBtn) { - connectBtn.style.display = isConnected ? 'none' : ''; - connectBtn.disabled = state === 'connecting'; - } - if (disconnectBtn) disconnectBtn.style.display = isConnected ? '' : 'none'; - if (devicePath) devicePath.textContent = isConnected ? 'gpsd://localhost:2947' : ''; - } - - function updatePositionUI(pos) { - // Sidebar fields - setText('gpsLat', pos.latitude != null ? pos.latitude.toFixed(6) + '\u00b0' : '---'); - setText('gpsLon', pos.longitude != null ? pos.longitude.toFixed(6) + '\u00b0' : '---'); - setText('gpsAlt', pos.altitude != null ? pos.altitude.toFixed(1) + ' m' : '---'); - setText('gpsSpeed', pos.speed != null ? (pos.speed * 3.6).toFixed(1) + ' km/h' : '---'); - setText('gpsHeading', pos.heading != null ? pos.heading.toFixed(1) + '\u00b0' : '---'); - setText('gpsClimb', pos.climb != null ? pos.climb.toFixed(2) + ' m/s' : '---'); - - // Fix type - const fixEl = document.getElementById('gpsFixType'); - if (fixEl) { - const fq = pos.fix_quality; - if (fq === 3) fixEl.innerHTML = '3D FIX'; - else if (fq === 2) fixEl.innerHTML = '2D FIX'; - else fixEl.innerHTML = 'NO FIX'; - } - - // Error estimates - const eph = (pos.epx != null && pos.epy != null) ? Math.sqrt(pos.epx * pos.epx + pos.epy * pos.epy) : null; - setText('gpsEph', eph != null ? eph.toFixed(1) + ' m' : '---'); - setText('gpsEpv', pos.epv != null ? pos.epv.toFixed(1) + ' m' : '---'); - setText('gpsEps', pos.eps != null ? pos.eps.toFixed(2) + ' m/s' : '---'); - - // GPS time - if (pos.timestamp) { - const t = new Date(pos.timestamp); - setText('gpsTime', t.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC')); - } - - // Visuals: position panel - setText('gpsVisPosLat', pos.latitude != null ? pos.latitude.toFixed(6) + '\u00b0' : '---'); - setText('gpsVisPosLon', pos.longitude != null ? pos.longitude.toFixed(6) + '\u00b0' : '---'); - setText('gpsVisPosAlt', pos.altitude != null ? pos.altitude.toFixed(1) + ' m' : '---'); - setText('gpsVisPosSpeed', pos.speed != null ? (pos.speed * 3.6).toFixed(1) + ' km/h' : '---'); - setText('gpsVisPosHeading', pos.heading != null ? pos.heading.toFixed(1) + '\u00b0' : '---'); - setText('gpsVisPosClimb', pos.climb != null ? pos.climb.toFixed(2) + ' m/s' : '---'); - - // Visuals: fix badge - const visFixEl = document.getElementById('gpsVisFixBadge'); - if (visFixEl) { - const fq = pos.fix_quality; - if (fq === 3) { visFixEl.textContent = '3D FIX'; visFixEl.className = 'gps-fix-badge fix-3d'; } - else if (fq === 2) { visFixEl.textContent = '2D FIX'; visFixEl.className = 'gps-fix-badge fix-2d'; } - else { visFixEl.textContent = 'NO FIX'; visFixEl.className = 'gps-fix-badge no-fix'; } - } - - // Visuals: GPS time - if (pos.timestamp) { - const t = new Date(pos.timestamp); - setText('gpsVisTime', t.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC')); - } - } - - function updateSkyUI(sky) { - // Sidebar sat counts - setText('gpsSatUsed', sky.usat != null ? sky.usat : '-'); - setText('gpsSatTotal', sky.nsat != null ? sky.nsat : '-'); - - // DOP values - setDop('gpsHdop', sky.hdop); - setDop('gpsVdop', sky.vdop); - setDop('gpsPdop', sky.pdop); - setDop('gpsTdop', sky.tdop); - setDop('gpsGdop', sky.gdop); - - // Visuals - drawSkyView(sky.satellites || []); - drawSignalBars(sky.satellites || []); - } - - function setDop(id, val) { - const el = document.getElementById(id); - if (!el) return; - if (val == null) { el.textContent = '---'; el.className = 'gps-info-value gps-mono'; return; } - el.textContent = val.toFixed(1); - let cls = 'gps-info-value gps-mono '; - if (val <= 2) cls += 'gps-dop-good'; - else if (val <= 5) cls += 'gps-dop-moderate'; - else cls += 'gps-dop-poor'; - el.className = cls; - } - - function setText(id, val) { - const el = document.getElementById(id); - if (el) el.textContent = val; - } - + + function subscribeToStream() { + // Subscribe to the global GPS stream instead of opening a separate SSE connection + if (typeof addGpsStreamSubscriber === 'function') { + addGpsStreamSubscriber(onGpsStreamData); + } + } + + function unsubscribeFromStream() { + if (typeof removeGpsStreamSubscriber === 'function') { + removeGpsStreamSubscriber(onGpsStreamData); + } + } + + // ======================== + // UI Updates + // ======================== + + function updateConnectionUI(isConnected, hasFix, state, message) { + const dot = document.getElementById('gpsStatusDot'); + const text = document.getElementById('gpsStatusText'); + const connectBtn = document.getElementById('gpsConnectBtn'); + const disconnectBtn = document.getElementById('gpsDisconnectBtn'); + const devicePath = document.getElementById('gpsDevicePath'); + + if (dot) { + dot.className = 'gps-status-dot'; + if (state === 'connecting') dot.classList.add('waiting'); + else if (state === 'error') dot.classList.add('error'); + else if (isConnected && hasFix) dot.classList.add('connected'); + else if (isConnected) dot.classList.add('waiting'); + } + if (text) { + if (state === 'connecting') text.textContent = 'Connecting...'; + else if (state === 'error') text.textContent = message || 'Connection failed'; + else if (isConnected && hasFix) text.textContent = 'Connected (Fix)'; + else if (isConnected) text.textContent = 'Connected (No Fix)'; + else text.textContent = 'Disconnected'; + } + if (connectBtn) { + connectBtn.style.display = isConnected ? 'none' : ''; + connectBtn.disabled = state === 'connecting'; + } + if (disconnectBtn) disconnectBtn.style.display = isConnected ? '' : 'none'; + if (devicePath) devicePath.textContent = isConnected ? 'gpsd://localhost:2947' : ''; + } + + function updatePositionUI(pos) { + // Sidebar fields + setText('gpsLat', pos.latitude != null ? pos.latitude.toFixed(6) + '\u00b0' : '---'); + setText('gpsLon', pos.longitude != null ? pos.longitude.toFixed(6) + '\u00b0' : '---'); + setText('gpsAlt', pos.altitude != null ? pos.altitude.toFixed(1) + ' m' : '---'); + setText('gpsSpeed', pos.speed != null ? (pos.speed * 3.6).toFixed(1) + ' km/h' : '---'); + setText('gpsHeading', pos.heading != null ? pos.heading.toFixed(1) + '\u00b0' : '---'); + setText('gpsClimb', pos.climb != null ? pos.climb.toFixed(2) + ' m/s' : '---'); + + // Fix type + const fixEl = document.getElementById('gpsFixType'); + if (fixEl) { + const fq = pos.fix_quality; + if (fq === 3) fixEl.innerHTML = '3D FIX'; + else if (fq === 2) fixEl.innerHTML = '2D FIX'; + else fixEl.innerHTML = 'NO FIX'; + } + + // Error estimates + const eph = (pos.epx != null && pos.epy != null) ? Math.sqrt(pos.epx * pos.epx + pos.epy * pos.epy) : null; + setText('gpsEph', eph != null ? eph.toFixed(1) + ' m' : '---'); + setText('gpsEpv', pos.epv != null ? pos.epv.toFixed(1) + ' m' : '---'); + setText('gpsEps', pos.eps != null ? pos.eps.toFixed(2) + ' m/s' : '---'); + + // GPS time + if (pos.timestamp) { + const t = new Date(pos.timestamp); + setText('gpsTime', t.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC')); + } + + // Visuals: position panel + setText('gpsVisPosLat', pos.latitude != null ? pos.latitude.toFixed(6) + '\u00b0' : '---'); + setText('gpsVisPosLon', pos.longitude != null ? pos.longitude.toFixed(6) + '\u00b0' : '---'); + setText('gpsVisPosAlt', pos.altitude != null ? pos.altitude.toFixed(1) + ' m' : '---'); + setText('gpsVisPosSpeed', pos.speed != null ? (pos.speed * 3.6).toFixed(1) + ' km/h' : '---'); + setText('gpsVisPosHeading', pos.heading != null ? pos.heading.toFixed(1) + '\u00b0' : '---'); + setText('gpsVisPosClimb', pos.climb != null ? pos.climb.toFixed(2) + ' m/s' : '---'); + + // Visuals: fix badge + const visFixEl = document.getElementById('gpsVisFixBadge'); + if (visFixEl) { + const fq = pos.fix_quality; + if (fq === 3) { visFixEl.textContent = '3D FIX'; visFixEl.className = 'gps-fix-badge fix-3d'; } + else if (fq === 2) { visFixEl.textContent = '2D FIX'; visFixEl.className = 'gps-fix-badge fix-2d'; } + else { visFixEl.textContent = 'NO FIX'; visFixEl.className = 'gps-fix-badge no-fix'; } + } + + // Visuals: GPS time + if (pos.timestamp) { + const t = new Date(pos.timestamp); + setText('gpsVisTime', t.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC')); + } + } + + function updateSkyUI(sky) { + // Sidebar sat counts + setText('gpsSatUsed', sky.usat != null ? sky.usat : '-'); + setText('gpsSatTotal', sky.nsat != null ? sky.nsat : '-'); + + // DOP values + setDop('gpsHdop', sky.hdop); + setDop('gpsVdop', sky.vdop); + setDop('gpsPdop', sky.pdop); + setDop('gpsTdop', sky.tdop); + setDop('gpsGdop', sky.gdop); + + // Visuals + drawSkyView(sky.satellites || []); + drawSignalBars(sky.satellites || []); + } + + function setDop(id, val) { + const el = document.getElementById(id); + if (!el) return; + if (val == null) { el.textContent = '---'; el.className = 'gps-info-value gps-mono'; return; } + el.textContent = val.toFixed(1); + let cls = 'gps-info-value gps-mono '; + if (val <= 2) cls += 'gps-dop-good'; + else if (val <= 5) cls += 'gps-dop-moderate'; + else cls += 'gps-dop-poor'; + el.className = cls; + } + + function setText(id, val) { + const el = document.getElementById(id); + if (el) el.textContent = val; + } + // ======================== // Sky View Globe (WebGL with 2D fallback) // ======================== @@ -1478,60 +1478,60 @@ const GPS = (function() { (num & 255) / 255, ]; } - - // ======================== - // Signal Strength Bars - // ======================== - - function drawSignalBars(satellites) { - const container = document.getElementById('gpsSignalBars'); - if (!container) return; - - container.innerHTML = ''; - - if (satellites.length === 0) return; - - // Sort: used first, then by PRN - const sorted = [...satellites].sort((a, b) => { - if (a.used !== b.used) return a.used ? -1 : 1; - return a.prn - b.prn; - }); - - const maxSnr = 50; // dB-Hz typical max for display - - sorted.forEach(sat => { - const snr = sat.snr || 0; - const heightPct = Math.min(snr / maxSnr * 100, 100); - const color = CONST_COLORS[sat.constellation] || CONST_COLORS['GPS']; - const constClass = 'gps-const-' + (sat.constellation || 'GPS').toLowerCase(); - - const wrap = document.createElement('div'); - wrap.className = 'gps-signal-bar-wrap'; - - const snrLabel = document.createElement('span'); - snrLabel.className = 'gps-signal-snr'; - snrLabel.textContent = snr > 0 ? Math.round(snr) : ''; - - const bar = document.createElement('div'); - bar.className = 'gps-signal-bar ' + constClass + (sat.used ? '' : ' unused'); - bar.style.height = Math.max(heightPct, 2) + '%'; - bar.title = `PRN ${sat.prn} (${sat.constellation}) - ${Math.round(snr)} dB-Hz${sat.used ? ' [USED]' : ''}`; - - const prn = document.createElement('span'); - prn.className = 'gps-signal-prn'; - prn.textContent = sat.prn; - - wrap.appendChild(snrLabel); - wrap.appendChild(bar); - wrap.appendChild(prn); - container.appendChild(wrap); - }); - } - - // ======================== - // Cleanup - // ======================== - + + // ======================== + // Signal Strength Bars + // ======================== + + function drawSignalBars(satellites) { + const container = document.getElementById('gpsSignalBars'); + if (!container) return; + + container.innerHTML = ''; + + if (satellites.length === 0) return; + + // Sort: used first, then by PRN + const sorted = [...satellites].sort((a, b) => { + if (a.used !== b.used) return a.used ? -1 : 1; + return a.prn - b.prn; + }); + + const maxSnr = 50; // dB-Hz typical max for display + + sorted.forEach(sat => { + const snr = sat.snr || 0; + const heightPct = Math.min(snr / maxSnr * 100, 100); + const color = CONST_COLORS[sat.constellation] || CONST_COLORS['GPS']; + const constClass = 'gps-const-' + (sat.constellation || 'GPS').toLowerCase(); + + const wrap = document.createElement('div'); + wrap.className = 'gps-signal-bar-wrap'; + + const snrLabel = document.createElement('span'); + snrLabel.className = 'gps-signal-snr'; + snrLabel.textContent = snr > 0 ? Math.round(snr) : ''; + + const bar = document.createElement('div'); + bar.className = 'gps-signal-bar ' + constClass + (sat.used ? '' : ' unused'); + bar.style.height = Math.max(heightPct, 2) + '%'; + bar.title = `PRN ${sat.prn} (${sat.constellation}) - ${Math.round(snr)} dB-Hz${sat.used ? ' [USED]' : ''}`; + + const prn = document.createElement('span'); + prn.className = 'gps-signal-prn'; + prn.textContent = sat.prn; + + wrap.appendChild(snrLabel); + wrap.appendChild(bar); + wrap.appendChild(prn); + container.appendChild(wrap); + }); + } + + // ======================== + // Cleanup + // ======================== + function destroy() { unsubscribeFromStream(); stopSkyPolling(); @@ -1548,11 +1548,11 @@ const GPS = (function() { skyRendererInitPromise = null; setSkyCanvasFallbackMode(false); } - - return { - init: init, - connect: connect, - disconnect: disconnect, - destroy: destroy, - }; -})(); + + return { + init: init, + connect: connect, + disconnect: disconnect, + destroy: destroy, + }; +})(); diff --git a/static/js/modes/sstv.js b/static/js/modes/sstv.js index c70e76e..1183754 100644 --- a/static/js/modes/sstv.js +++ b/static/js/modes/sstv.js @@ -254,14 +254,14 @@ const SSTV = (function() { // Past track (dimmer, solid) issTrackPast = L.polyline([], { - color: '#00d4ff', + color: getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff', weight: 1.5, opacity: 0.3, }).addTo(issMap); // Future track (brighter, dashed) issTrackLine = L.polyline([], { - color: '#00d4ff', + color: getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff', weight: 2, opacity: 0.7, dashArray: '6, 4' diff --git a/static/js/modes/subghz.js b/static/js/modes/subghz.js index 4be68c2..d4fadac 100644 --- a/static/js/modes/subghz.js +++ b/static/js/modes/subghz.js @@ -499,7 +499,7 @@ const SubGhz = (function() { // Auto-scale low-amplitude noise/signal so activity is visible. const gain = peak > 0 ? Math.min(12, 0.92 / peak) : 1; - ctx.strokeStyle = '#00d4ff'; + ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff'; ctx.lineWidth = 1.5; ctx.beginPath(); const n = data.length; @@ -1779,7 +1779,7 @@ const SubGhz = (function() { // Spectrum line ctx.beginPath(); - ctx.strokeStyle = '#00d4ff'; + ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff'; ctx.lineWidth = 1.5; for (let i = 0; i < sweepData.length; i++) { diff --git a/static/js/modes/websdr.js b/static/js/modes/websdr.js index 2fb58f2..57eed30 100644 --- a/static/js/modes/websdr.js +++ b/static/js/modes/websdr.js @@ -133,8 +133,8 @@ function plotReceiversOnMap(receivers) { const marker = L.circleMarker([rx.lat, rx.lon], { radius: 6, - fillColor: rx.available ? '#00d4ff' : '#666', - color: rx.available ? '#00d4ff' : '#666', + fillColor: rx.available ? (getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff') : '#666', + color: rx.available ? (getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff') : '#666', weight: 1, opacity: 0.8, fillOpacity: 0.6, @@ -146,7 +146,7 @@ function plotReceiversOnMap(receivers) { ${rx.location ? `${escapeHtmlWebsdr(rx.location)}
` : ''} Antenna: ${escapeHtmlWebsdr(rx.antenna || 'Unknown')}
Users: ${rx.users}/${rx.users_max}
- + `); @@ -263,11 +263,12 @@ function initWebsdrGlobe(mapEl) { mapEl.style.background = 'radial-gradient(circle at 30% 20%, rgba(14, 42, 68, 0.9), rgba(4, 9, 16, 0.95) 58%, rgba(2, 4, 9, 0.98) 100%)'; mapEl.style.cursor = 'grab'; + const _wsdrAccent = getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#3bb9ff'; websdrGlobe = window.Globe()(mapEl) .backgroundColor('rgba(0,0,0,0)') .globeImageUrl(WEBSDR_GLOBE_TEXTURE_URL) .showAtmosphere(true) - .atmosphereColor('#3bb9ff') + .atmosphereColor(_wsdrAccent) .atmosphereAltitude(0.17) .pointRadius('radius') .pointAltitude('altitude') @@ -396,7 +397,7 @@ function plotReceiversOnGlobe(receivers) { receiverIndex: idx, radius: selected ? 0.52 : 0.38, altitude: selected ? 0.1 : 0.04, - color: selected ? '#00ff88' : (rx.available ? '#00d4ff' : '#5f6976'), + color: selected ? '#00ff88' : (rx.available ? (getComputedStyle(document.documentElement).getPropertyValue('--accent-cyan').trim() || '#00d4ff') : '#5f6976'), label: buildWebsdrPointLabel(rx, idx), }); }); @@ -513,7 +514,7 @@ function showWebsdrGlobePopup(point, event) { ${rx.location ? `
${escapeHtmlWebsdr(rx.location)}
` : ''}
Antenna: ${escapeHtmlWebsdr(rx.antenna || 'Unknown')}
Users: ${rx.users}/${rx.users_max}
- + `; websdrGlobePopup.style.display = 'block'; @@ -551,8 +552,8 @@ function buildWebsdrPointLabel(rx, idx) { const location = rx.location ? escapeHtmlWebsdr(rx.location) : 'Unknown location'; const antenna = escapeHtmlWebsdr(rx.antenna || 'Unknown antenna'); return ` -
-
${escapeHtmlWebsdr(rx.name)}
+
+
${escapeHtmlWebsdr(rx.name)}
${location}
${antenna} · ${rx.users}/${rx.users_max}
Receiver #${idx + 1}