mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
fix: Resolve light/dark theme issues across dashboards and settings
- Add missing setThemePreference() and setAnimationsEnabled() functions
to settings-manager.js; sync theme/animations dropdowns in _updateUI
- Fix base.html toggleTheme() saving to wrong localStorage key ('theme'
instead of 'intercept-theme'), causing theme not to persist in ADS-B
and AIS dashboards; also sync button icon and persist to server
- Add [data-theme="light"] CSS variable overrides to adsb_dashboard.css
and ais_dashboard.css so the dashboards respond to light theme
- Fix GPS sky view canvas (gps.js) to read grid/label colours from CSS
variables instead of hardcoded dark hex values; add MutationObserver
to redraw immediately on theme change
- Fix satellite_dashboard.html polar plot functions to read background,
accent and text colours from CSS variables
Closes #139
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,27 @@
|
||||
--radar-bg: #101823;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-dark: #f4f7fb;
|
||||
--bg-panel: #e9eef5;
|
||||
--bg-card: #ffffff;
|
||||
--border-color: #d1d9e6;
|
||||
--border-glow: #1f5fa8;
|
||||
--text-primary: #122034;
|
||||
--text-secondary: #3a4a5f;
|
||||
--text-dim: #6b7c93;
|
||||
--accent-green: #1f8a57;
|
||||
--accent-cyan: #1f5fa8;
|
||||
--accent-orange: #b5863a;
|
||||
--accent-red: #c74444;
|
||||
--accent-yellow: #b5863a;
|
||||
--accent-amber: #b5863a;
|
||||
--grid-line: rgba(31, 95, 168, 0.12);
|
||||
--noise-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='40'%20height='40'%20viewBox='0%200%2040%2040'%3E%3Cg%20fill='%23000000'%20fill-opacity='0.05'%3E%3Ccircle%20cx='3'%20cy='5'%20r='1'/%3E%3Ccircle%20cx='11'%20cy='9'%20r='1'/%3E%3Ccircle%20cx='18'%20cy='3'%20r='1'/%3E%3Ccircle%20cx='26'%20cy='12'%20r='1'/%3E%3Ccircle%20cx='34'%20cy='6'%20r='1'/%3E%3Ccircle%20cx='7'%20cy='19'%20r='1'/%3E%3Ccircle%20cx='15'%20cy='24'%20r='1'/%3E%3Ccircle%20cx='28'%20cy='22'%20r='1'/%3E%3Ccircle%20cx='36'%20cy='18'%20r='1'/%3E%3Ccircle%20cx='5'%20cy='33'%20r='1'/%3E%3Ccircle%20cx='19'%20cy='32'%20r='1'/%3E%3Ccircle%20cx='31'%20cy='34'%20r='1'/%3E%3C/g%3E%3C/svg%3E");
|
||||
--radar-cyan: #1f5fa8;
|
||||
--radar-bg: #e9eef5;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-dark);
|
||||
|
||||
@@ -30,6 +30,27 @@
|
||||
--radar-bg: #101823;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg-dark: #f4f7fb;
|
||||
--bg-panel: #e9eef5;
|
||||
--bg-card: #ffffff;
|
||||
--border-color: #d1d9e6;
|
||||
--border-glow: #1f5fa8;
|
||||
--text-primary: #122034;
|
||||
--text-secondary: #3a4a5f;
|
||||
--text-dim: #6b7c93;
|
||||
--accent-green: #1f8a57;
|
||||
--accent-cyan: #1f5fa8;
|
||||
--accent-orange: #b5863a;
|
||||
--accent-red: #c74444;
|
||||
--accent-yellow: #b5863a;
|
||||
--accent-amber: #b5863a;
|
||||
--grid-line: rgba(31, 95, 168, 0.12);
|
||||
--noise-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='40'%20height='40'%20viewBox='0%200%2040%2040'%3E%3Cg%20fill='%23000000'%20fill-opacity='0.05'%3E%3Ccircle%20cx='3'%20cy='5'%20r='1'/%3E%3Ccircle%20cx='11'%20cy='9'%20r='1'/%3E%3Ccircle%20cx='18'%20cy='3'%20r='1'/%3E%3Ccircle%20cx='26'%20cy='12'%20r='1'/%3E%3Ccircle%20cx='34'%20cy='6'%20r='1'/%3E%3Ccircle%20cx='7'%20cy='19'%20r='1'/%3E%3Ccircle%20cx='15'%20cy='24'%20r='1'/%3E%3Ccircle%20cx='28'%20cy='22'%20r='1'/%3E%3Ccircle%20cx='36'%20cy='18'%20r='1'/%3E%3Ccircle%20cx='5'%20cy='33'%20r='1'/%3E%3Ccircle%20cx='19'%20cy='32'%20r='1'/%3E%3Ccircle%20cx='31'%20cy='34'%20r='1'/%3E%3C/g%3E%3C/svg%3E");
|
||||
--radar-cyan: #1f5fa8;
|
||||
--radar-bg: #e9eef5;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-dark);
|
||||
|
||||
@@ -323,6 +323,18 @@ const Settings = {
|
||||
if (customRow) {
|
||||
customRow.style.display = this.get('offline.tile_provider') === 'custom' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Theme select
|
||||
const themeSelect = document.getElementById('themeSelect');
|
||||
if (themeSelect) {
|
||||
themeSelect.value = localStorage.getItem('intercept-theme') || 'dark';
|
||||
}
|
||||
|
||||
// Animations toggle
|
||||
const animationsEnabled = document.getElementById('animationsEnabled');
|
||||
if (animationsEnabled) {
|
||||
animationsEnabled.checked = localStorage.getItem('intercept-animations') !== 'off';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -983,3 +995,34 @@ function toggleApiKeyVisibility() {
|
||||
if (!input) return;
|
||||
input.type = input.type === 'password' ? 'text' : 'password';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set theme preference from the Display settings tab
|
||||
*/
|
||||
function setThemePreference(value) {
|
||||
document.documentElement.setAttribute('data-theme', value);
|
||||
localStorage.setItem('intercept-theme', value);
|
||||
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) {
|
||||
btn.textContent = value === 'light' ? '🌙' : '☀️';
|
||||
}
|
||||
|
||||
fetch('/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme: value })
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set animations preference from the Display settings tab
|
||||
*/
|
||||
function setAnimationsEnabled(enabled) {
|
||||
if (enabled) {
|
||||
document.documentElement.removeAttribute('data-animations');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-animations', 'off');
|
||||
}
|
||||
localStorage.setItem('intercept-animations', enabled ? 'on' : 'off');
|
||||
}
|
||||
|
||||
@@ -23,6 +23,16 @@ const GPS = (function() {
|
||||
function init() {
|
||||
drawEmptySkyView();
|
||||
connect();
|
||||
|
||||
// Redraw sky view when theme changes
|
||||
const observer = new MutationObserver(() => {
|
||||
if (lastSky) {
|
||||
drawSkyView(lastSky.satellites || []);
|
||||
} else {
|
||||
drawEmptySkyView();
|
||||
}
|
||||
});
|
||||
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
}
|
||||
|
||||
function connect() {
|
||||
@@ -316,13 +326,18 @@ const GPS = (function() {
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const bgColor = cs.getPropertyValue('--bg-card').trim() || '#0d1117';
|
||||
const gridColor = cs.getPropertyValue('--border-color').trim() || '#2a3040';
|
||||
const dimColor = cs.getPropertyValue('--text-dim').trim() || '#555';
|
||||
const secondaryColor = cs.getPropertyValue('--text-secondary').trim() || '#888';
|
||||
|
||||
// Background
|
||||
const bgStyle = getComputedStyle(document.documentElement).getPropertyValue('--bg-card').trim();
|
||||
ctx.fillStyle = bgStyle || '#0d1117';
|
||||
ctx.fillStyle = bgColor;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// Elevation rings (0, 30, 60, 90)
|
||||
ctx.strokeStyle = '#2a3040';
|
||||
ctx.strokeStyle = gridColor;
|
||||
ctx.lineWidth = 0.5;
|
||||
[90, 60, 30].forEach(el => {
|
||||
const gr = r * (1 - el / 90);
|
||||
@@ -330,7 +345,7 @@ const GPS = (function() {
|
||||
ctx.arc(cx, cy, gr, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
// Label
|
||||
ctx.fillStyle = '#555';
|
||||
ctx.fillStyle = dimColor;
|
||||
ctx.font = '9px Roboto Condensed, monospace';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'middle';
|
||||
@@ -338,14 +353,14 @@ const GPS = (function() {
|
||||
});
|
||||
|
||||
// Horizon circle
|
||||
ctx.strokeStyle = '#3a4050';
|
||||
ctx.strokeStyle = gridColor;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
// Cardinal directions
|
||||
ctx.fillStyle = '#888';
|
||||
ctx.fillStyle = secondaryColor;
|
||||
ctx.font = 'bold 11px Roboto Condensed, monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
@@ -355,7 +370,7 @@ const GPS = (function() {
|
||||
ctx.fillText('W', cx - r - 12, cy);
|
||||
|
||||
// Crosshairs
|
||||
ctx.strokeStyle = '#2a3040';
|
||||
ctx.strokeStyle = gridColor;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(cx, cy - r);
|
||||
@@ -365,7 +380,7 @@ const GPS = (function() {
|
||||
ctx.stroke();
|
||||
|
||||
// Zenith dot
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.fillStyle = dimColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
@@ -133,7 +133,17 @@
|
||||
const currentTheme = html.getAttribute('data-theme') || 'dark';
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
html.setAttribute('data-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
localStorage.setItem('intercept-theme', newTheme);
|
||||
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (btn) btn.textContent = newTheme === 'light' ? '🌙' : '☀️';
|
||||
|
||||
// Persist to server
|
||||
fetch('/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ theme: newTheme })
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
// Apply saved theme
|
||||
|
||||
@@ -609,11 +609,17 @@
|
||||
const cy = canvas.height / 2;
|
||||
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
||||
|
||||
ctx.fillStyle = '#0a0a0f';
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const bgColor = cs.getPropertyValue('--bg-card').trim() || '#0a0a0f';
|
||||
const accentCyan = cs.getPropertyValue('--accent-cyan').trim() || '#00d4ff';
|
||||
const accentRed = cs.getPropertyValue('--accent-red').trim() || '#ff4444';
|
||||
const textDim = cs.getPropertyValue('--text-dim').trim() || 'rgba(0,212,255,0.4)';
|
||||
|
||||
ctx.fillStyle = bgColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// Elevation rings
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.15)';
|
||||
ctx.strokeStyle = accentCyan + '26'; // ~15% opacity
|
||||
ctx.lineWidth = 1;
|
||||
for (let el = 30; el <= 90; el += 30) {
|
||||
const r = radius * (1 - el / 90);
|
||||
@@ -621,20 +627,20 @@
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = 'rgba(0, 212, 255, 0.4)';
|
||||
ctx.fillStyle = textDim;
|
||||
ctx.font = '10px Roboto Condensed';
|
||||
ctx.fillText(el + '°', cx + 5, cy - r + 12);
|
||||
}
|
||||
|
||||
// Horizon
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.3)';
|
||||
ctx.strokeStyle = accentCyan + '4D'; // ~30% opacity
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
// Cardinal lines
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.1)';
|
||||
ctx.strokeStyle = accentCyan + '1A'; // ~10% opacity
|
||||
ctx.lineWidth = 1;
|
||||
for (let az = 0; az < 360; az += 45) {
|
||||
const angle = (az - 90) * Math.PI / 180;
|
||||
@@ -647,10 +653,10 @@
|
||||
// Cardinal labels
|
||||
ctx.font = 'bold 14px Orbitron';
|
||||
const labels = [
|
||||
{ text: 'N', az: 0, color: '#ff4444' },
|
||||
{ text: 'E', az: 90, color: '#00d4ff' },
|
||||
{ text: 'S', az: 180, color: '#00d4ff' },
|
||||
{ text: 'W', az: 270, color: '#00d4ff' }
|
||||
{ text: 'N', az: 0, color: accentRed },
|
||||
{ text: 'E', az: 90, color: accentCyan },
|
||||
{ text: 'S', az: 180, color: accentCyan },
|
||||
{ text: 'W', az: 270, color: accentCyan }
|
||||
];
|
||||
labels.forEach(l => {
|
||||
const angle = (l.az - 90) * Math.PI / 180;
|
||||
@@ -978,10 +984,18 @@
|
||||
const cy = canvas.height / 2;
|
||||
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
||||
|
||||
ctx.fillStyle = '#0a0a0f';
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const bgColor = cs.getPropertyValue('--bg-card').trim() || '#0a0a0f';
|
||||
const accentCyan = cs.getPropertyValue('--accent-cyan').trim() || '#00d4ff';
|
||||
const accentRed = cs.getPropertyValue('--accent-red').trim() || '#ff4444';
|
||||
const accentGreen = cs.getPropertyValue('--accent-green').trim() || '#00ff88';
|
||||
const textPrimary = cs.getPropertyValue('--text-primary').trim() || '#fff';
|
||||
const textDim = cs.getPropertyValue('--text-dim').trim() || 'rgba(0,212,255,0.4)';
|
||||
|
||||
ctx.fillStyle = bgColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.15)';
|
||||
ctx.strokeStyle = accentCyan + '26';
|
||||
ctx.lineWidth = 1;
|
||||
for (let elRing = 30; elRing <= 90; elRing += 30) {
|
||||
const r = radius * (1 - elRing / 90);
|
||||
@@ -989,18 +1003,18 @@
|
||||
ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = 'rgba(0, 212, 255, 0.4)';
|
||||
ctx.fillStyle = textDim;
|
||||
ctx.font = '10px Roboto Condensed';
|
||||
ctx.fillText(elRing + '°', cx + 5, cy - r + 12);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.3)';
|
||||
ctx.strokeStyle = accentCyan + '4D';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.strokeStyle = 'rgba(0, 212, 255, 0.1)';
|
||||
ctx.strokeStyle = accentCyan + '1A';
|
||||
ctx.lineWidth = 1;
|
||||
for (let azLine = 0; azLine < 360; azLine += 45) {
|
||||
const angle = (azLine - 90) * Math.PI / 180;
|
||||
@@ -1012,10 +1026,10 @@
|
||||
|
||||
ctx.font = 'bold 14px Orbitron';
|
||||
const labels = [
|
||||
{ text: 'N', az: 0, color: '#ff4444' },
|
||||
{ text: 'E', az: 90, color: '#00d4ff' },
|
||||
{ text: 'S', az: 180, color: '#00d4ff' },
|
||||
{ text: 'W', az: 270, color: '#00d4ff' }
|
||||
{ text: 'N', az: 0, color: accentRed },
|
||||
{ text: 'E', az: 90, color: accentCyan },
|
||||
{ text: 'S', az: 180, color: accentCyan },
|
||||
{ text: 'W', az: 270, color: accentCyan }
|
||||
];
|
||||
labels.forEach(l => {
|
||||
const angle = (l.az - 90) * Math.PI / 180;
|
||||
@@ -1048,21 +1062,21 @@
|
||||
ctx.arc(x, y, 10, 0, Math.PI * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.strokeStyle = textPrimary;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = 'bold 11px Orbitron';
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillStyle = textPrimary;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(satellites[selectedSatellite]?.name || 'SAT', x, y - 20);
|
||||
|
||||
ctx.font = '10px Roboto Condensed';
|
||||
ctx.fillStyle = el > 0 ? '#00ff88' : '#ff4444';
|
||||
ctx.fillStyle = el > 0 ? accentGreen : accentRed;
|
||||
ctx.fillText(el.toFixed(1) + '°', x, y + 25);
|
||||
} else {
|
||||
ctx.font = '12px Rajdhani';
|
||||
ctx.fillStyle = '#ff4444';
|
||||
ctx.fillStyle = accentRed;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText('BELOW HORIZON', cx, cy + radius + 35);
|
||||
}
|
||||
@@ -1076,6 +1090,11 @@
|
||||
const cy = canvas.height / 2;
|
||||
const radius = Math.max(10, Math.min(cx, cy) - 40);
|
||||
|
||||
const cs = getComputedStyle(document.documentElement);
|
||||
const accentRed = cs.getPropertyValue('--accent-red').trim() || '#ff4444';
|
||||
const accentGreen = cs.getPropertyValue('--accent-green').trim() || '#00ff88';
|
||||
const textPrimary = cs.getPropertyValue('--text-primary').trim() || '#fff';
|
||||
|
||||
if (el > -5) {
|
||||
const posEl = Math.max(0, el);
|
||||
const r = radius * (1 - posEl / 90);
|
||||
@@ -1097,17 +1116,17 @@
|
||||
ctx.arc(x, y, 10, 0, Math.PI * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.strokeStyle = textPrimary;
|
||||
ctx.lineWidth = 3;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = 'bold 11px Orbitron';
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fillStyle = textPrimary;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(satellites[selectedSatellite]?.name || 'SAT', x, y - 20);
|
||||
|
||||
ctx.font = '10px Roboto Condensed';
|
||||
ctx.fillStyle = el > 0 ? '#00ff88' : '#ff4444';
|
||||
ctx.fillStyle = el > 0 ? accentGreen : accentRed;
|
||||
ctx.fillText(el.toFixed(1) + '°', x, y + 25);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user