diff --git a/static/css/adsb_dashboard.css b/static/css/adsb_dashboard.css index cca964b..6cedd7f 100644 --- a/static/css/adsb_dashboard.css +++ b/static/css/adsb_dashboard.css @@ -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); diff --git a/static/css/ais_dashboard.css b/static/css/ais_dashboard.css index b69642b..4232caf 100644 --- a/static/css/ais_dashboard.css +++ b/static/css/ais_dashboard.css @@ -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); diff --git a/static/js/core/settings-manager.js b/static/js/core/settings-manager.js index 1a5e922..6cfe2a3 100644 --- a/static/js/core/settings-manager.js +++ b/static/js/core/settings-manager.js @@ -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'); +} diff --git a/static/js/modes/gps.js b/static/js/modes/gps.js index 906dde4..4c6e9c3 100644 --- a/static/js/modes/gps.js +++ b/static/js/modes/gps.js @@ -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(); diff --git a/templates/layout/base.html b/templates/layout/base.html index 4450f65..0486d85 100644 --- a/templates/layout/base.html +++ b/templates/layout/base.html @@ -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 diff --git a/templates/satellite_dashboard.html b/templates/satellite_dashboard.html index 8b46dcf..71fba97 100644 --- a/templates/satellite_dashboard.html +++ b/templates/satellite_dashboard.html @@ -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); } }