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 ? `