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 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-05-19 23:08:49 +01:00
parent e1922d7a30
commit 9d72c88a28
8 changed files with 298 additions and 297 deletions
+1 -1
View File
@@ -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; }
+41 -41
View File
@@ -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 {
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
}
+241 -241
View File
@@ -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 = '<span class="gps-fix-badge fix-3d">3D FIX</span>';
else if (fq === 2) fixEl.innerHTML = '<span class="gps-fix-badge fix-2d">2D FIX</span>';
else fixEl.innerHTML = '<span class="gps-fix-badge no-fix">NO FIX</span>';
}
// 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 = '<span class="gps-fix-badge fix-3d">3D FIX</span>';
else if (fq === 2) fixEl.innerHTML = '<span class="gps-fix-badge fix-2d">2D FIX</span>';
else fixEl.innerHTML = '<span class="gps-fix-badge no-fix">NO FIX</span>';
}
// 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,
};
})();
+2 -2
View File
@@ -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'
+2 -2
View File
@@ -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++) {
+9 -8
View File
@@ -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 ? `<span style="color: #aaa;">${escapeHtmlWebsdr(rx.location)}</span><br>` : ''}
<span style="color: #888;">Antenna: ${escapeHtmlWebsdr(rx.antenna || 'Unknown')}</span><br>
<span style="color: #888;">Users: ${rx.users}/${rx.users_max}</span><br>
<button onclick="selectReceiver(${idx})" style="margin-top: 6px; padding: 4px 12px; background: #00d4ff; color: #000; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;">Listen</button>
<button onclick="selectReceiver(${idx})" style="margin-top: 6px; padding: 4px 12px; background: var(--accent-cyan); color: #000; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;">Listen</button>
</div>
`);
@@ -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 ? `<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 3px;">${escapeHtmlWebsdr(rx.location)}</div>` : ''}
<div style="font-size: 10px; color: var(--text-muted); margin-bottom: 2px;">Antenna: ${escapeHtmlWebsdr(rx.antenna || 'Unknown')}</div>
<div style="font-size: 10px; color: var(--text-muted); margin-bottom: 10px;">Users: ${rx.users}/${rx.users_max}</div>
<button type="button" data-websdr-listen style="width: 100%; padding: 5px 10px; background: #00d4ff; color: #041018; border: none; border-radius: 4px; cursor: pointer; font-weight: 700;">Listen</button>
<button type="button" data-websdr-listen style="width: 100%; padding: 5px 10px; background: var(--accent-cyan); color: #041018; border: none; border-radius: 4px; cursor: pointer; font-weight: 700;">Listen</button>
`;
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 `
<div style="padding: 4px 6px; font-size: 11px; background: rgba(4, 12, 19, 0.9); border: 1px solid rgba(0,212,255,0.28); border-radius: 4px;">
<div style="color: #00d4ff; font-weight: 600;">${escapeHtmlWebsdr(rx.name)}</div>
<div style="padding: 4px 6px; font-size: 11px; background: rgba(4, 12, 19, 0.9); border: 1px solid var(--border-glow); border-radius: 4px;">
<div style="color: var(--accent-cyan); font-weight: 600;">${escapeHtmlWebsdr(rx.name)}</div>
<div style="color: #a5b1c3;">${location}</div>
<div style="color: #8f9fb3;">${antenna} · ${rx.users}/${rx.users_max}</div>
<div style="color: #7a899b; margin-top: 2px;">Receiver #${idx + 1}</div>