From 42cde48a38b7f536f686825255f19790b7e12bae Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Fri, 29 May 2026 01:01:55 +0200 Subject: [PATCH 1/8] feat: add notification statistics (types, airlines, totals) --- background.js | 40 +++++++++++++++- popup/history.js | 119 +++++++++++++++++++++++++++++++++++++++++++++- popup/popup.css | 115 ++++++++++++++++++++++++++++++++++++++++++++ popup/settings.js | 3 +- 4 files changed, 273 insertions(+), 4 deletions(-) diff --git a/background.js b/background.js index bb73f21..5d12427 100644 --- a/background.js +++ b/background.js @@ -1,4 +1,4 @@ -// background.js — runs in the background and polls the API +// background.js v1.1.0 — runs in the background and polls the API // Depends on shared.js (loaded via manifest.json background.scripts) // ─── OFFSCREEN DOCUMENT ──────────────────────────────────────────────────── @@ -72,6 +72,40 @@ chrome.runtime.onMessage.addListener((msg) => { } }); +// ─── STATISTIEKEN BIJHOUDEN ──────────────────────────────────────────────── + +async function recordStats(ac) { + const keys = ['statsTotalCount', 'statsFirstDetection', 'statsTypeCounts', 'statsAirlineCounts']; + const data = await chrome.storage.local.get(keys); + + const totalCount = (data.statsTotalCount || 0) + 1; + const firstDetect = data.statsFirstDetection || new Date().toISOString(); + const typeCounts = data.statsTypeCounts || {}; + const airlineCounts = data.statsAirlineCounts || {}; + + // Type tellen (alleen als bekend) + if (ac.t) { + const t = ac.t.toUpperCase().trim(); + typeCounts[t] = (typeCounts[t] || 0) + 1; + } + + // Airline tellen: eerste 3 letters van vluchtcode, alleen als vlucht minstens 4 tekens heeft + if (ac.flight && ac.flight.trim().length >= 4) { + const prefix = ac.flight.trim().toUpperCase().substring(0, 3); + // Alleen letters (geen cijfers-prefix zoals militairen of privé) + if (/^[A-Z]{3}$/.test(prefix)) { + airlineCounts[prefix] = (airlineCounts[prefix] || 0) + 1; + } + } + + await chrome.storage.local.set({ + statsTotalCount: totalCount, + statsFirstDetection: firstDetect, + statsTypeCounts: typeCounts, + statsAirlineCounts: airlineCounts + }); +} + async function pollAircraft() { const { enabled = true } = await chrome.storage.local.get('enabled'); if (!enabled) return; @@ -106,7 +140,6 @@ async function pollAircraft() { for (const ac of aircraft) { if (ac.hex && caughtAircraft.includes(ac.hex)) continue; - // matchesAlert komt uit shared.js const matchingAlert = config.alerts.find(alert => alert.active && matchesAlert(ac, alert)); if (!matchingAlert) continue; @@ -119,6 +152,9 @@ async function pollAircraft() { inRange[key] = true; await chrome.storage.local.set({ inRange }); + // Statistieken bijhouden voor elke nieuwe match + await recordStats(ac); + const { notificationsEnabled = true, notifShow = {} } = await chrome.storage.local.get(['notificationsEnabled', 'notifShow']); const show = Object.assign({ reg: false, type: true, alt: true, speed: true, route: true, dir: true }, notifShow); diff --git a/popup/history.js b/popup/history.js index d3ae6ef..107c3ab 100644 --- a/popup/history.js +++ b/popup/history.js @@ -1,9 +1,25 @@ -// history.js — injecteert History tab HTML en beheert notificatiegeschiedenis +// history.js v1.1.0 — injecteert History tab HTML en beheert notificatiegeschiedenis // ─── HTML INJECTIE ────────────────────────────────────────────────────────── function initHistoryTab() { document.getElementById('tab-history').innerHTML = ` + + +
+ + +
+ +
@@ -47,6 +63,107 @@ function setupHistoryEvents() { await chrome.storage.local.set({ caughtAircraft: [], caughtAircraftLabels: {} }); renderCaughtList(); }); + + document.getElementById('btnToggleStats').addEventListener('click', () => { + const panel = document.getElementById('statsPanel'); + const chevron = document.getElementById('statsChevron'); + const isOpen = panel.style.display !== 'none'; + panel.style.display = isOpen ? 'none' : 'block'; + chevron.style.transform = isOpen ? '' : 'rotate(180deg)'; + if (!isOpen) renderStats(); + }); + + document.getElementById('btnResetStats').addEventListener('click', async () => { + const btn = document.getElementById('btnResetStats'); + if (btn.dataset.confirm !== '1') { + btn.dataset.confirm = '1'; + btn.textContent = 'Sure? Click again to confirm'; + btn.style.color = '#ef4444'; + btn.style.borderColor = '#ef4444'; + setTimeout(() => { + btn.dataset.confirm = ''; + btn.textContent = 'Reset statistics'; + btn.style.color = ''; + btn.style.borderColor = ''; + }, 2500); + return; + } + await chrome.storage.local.remove([ + 'statsTotalCount', 'statsTypeCounts', 'statsAirlineCounts' + ]); + btn.dataset.confirm = ''; + btn.textContent = 'Reset statistics'; + btn.style.color = ''; + btn.style.borderColor = ''; + renderStats(); + }); +} + +// ─── STATISTIEKEN RENDEREN ───────────────────────────────────────────────── + +async function renderStats() { + const container = document.getElementById('statsPanelInner'); + if (!container) return; + + const { statsTotalCount = 0, statsFirstDetection, statsTypeCounts = {}, statsAirlineCounts = {} } = + await chrome.storage.local.get(['statsTotalCount', 'statsFirstDetection', 'statsTypeCounts', 'statsAirlineCounts']); + + if (statsTotalCount === 0 && !statsFirstDetection) { + container.innerHTML = '
No data yet. Stats are recorded as notifications fire.
'; + return; + } + + function topN(obj, n) { + return Object.entries(obj) + .sort((a, b) => b[1] - a[1]) + .slice(0, n); + } + + const topTypes = topN(statsTypeCounts, 5); + const topAirlines = topN(statsAirlineCounts, 5); + + const firstDate = statsFirstDetection + ? new Date(statsFirstDetection).toLocaleDateString([], { day: '2-digit', month: 'short', year: 'numeric' }) + : '—'; + + function barRow(label, count, max) { + const pct = max > 0 ? Math.round((count / max) * 100) : 0; + return ` +
+ ${label} +
+
+
+ ${count} +
+ `; + } + + const maxType = topTypes[0]?.[1] || 1; + const maxAirline = topAirlines[0]?.[1] || 1; + + container.innerHTML = ` +
+
+
Total notifications
+
${statsTotalCount}
+
+
+
First detection
+
${firstDate}
+
+
+ + ${topTypes.length > 0 ? ` + + ${topTypes.map(([t, c]) => barRow(t, c, maxType)).join('')} + ` : ''} + + ${topAirlines.length > 0 ? ` + + ${topAirlines.map(([a, c]) => barRow(a, c, maxAirline)).join('')} + ` : ''} + `; } async function renderCaughtList() { diff --git a/popup/popup.css b/popup/popup.css index 02f820c..c7500c8 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1210,4 +1210,119 @@ input::placeholder { color: #3a4560; } border-color: #1a2a3a; color: #22c55e; cursor: default; +} + +/* ── Statistics dropdown ── */ +/* Voeg toe aan het einde van popup.css */ + +.stats-dropdown { + border: 1px solid #1a2a4a; + border-radius: 8px; + overflow: hidden; + margin-bottom: 14px; +} + +.stats-toggle-btn { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 9px 12px; + background: #0d1530; + border: none; + cursor: pointer; + color: #c8d4f0; + font-family: 'Space Mono', monospace; + font-size: 11px; +} + +.stats-toggle-btn:hover { background: #101a38; } + +.stats-chevron { + font-size: 10px; + transition: transform 0.2s; + color: #5a6a90; +} + +.stats-panel-inner { + padding: 10px 12px 4px; + background: #080f25; +} + +.stats-meta-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 12px; +} + +.stats-meta-cell { + background: #0f1322; + border: 1px solid #1e2840; + border-radius: 6px; + padding: 8px 10px; +} + +.stats-meta-label { + font-family: 'Space Mono', monospace; + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.8px; + color: #5a6a90; + margin-bottom: 4px; +} + +.stats-meta-value { + font-family: 'Space Mono', monospace; + font-size: 15px; + font-weight: 700; + color: #60a5fa; +} + +.stats-section-label { + font-family: 'Space Mono', monospace; + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.8px; + color: #5a6a90; + margin-bottom: 6px; +} + +.stats-bar-row { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 5px; +} + +.stats-bar-label { + font-family: 'Space Mono', monospace; + font-size: 10px; + color: #c8d4f0; + width: 44px; + flex-shrink: 0; +} + +.stats-bar-track { + flex: 1; + height: 5px; + background: #1a2040; + border-radius: 3px; + overflow: hidden; +} + +.stats-bar-fill { + height: 100%; + background: #2a5aaa; + border-radius: 3px; + transition: width 0.3s ease; +} + +.stats-bar-count { + font-family: 'Space Mono', monospace; + font-size: 10px; + color: #7a8ab0; + width: 24px; + text-align: right; + flex-shrink: 0; } \ No newline at end of file diff --git a/popup/settings.js b/popup/settings.js index 716126a..cefa2fd 100644 --- a/popup/settings.js +++ b/popup/settings.js @@ -517,7 +517,8 @@ async function initSettings() { 'hideGround', 'notificationsEnabled', 'notifShow', 'alertSound', 'alertVolume', 'startupTab', 'lastTab', - 'caughtAircraft', 'caughtAircraftLabels' + 'caughtAircraft', 'caughtAircraftLabels', + 'statsTotalCount', 'statsFirstDetection', 'statsTypeCounts', 'statsAirlineCounts' ]; document.getElementById('btnExportAlerts').addEventListener('click', async () => { From 598023e10c4270c883261eaa41a1a21d8b203d58 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Fri, 29 May 2026 01:05:35 +0200 Subject: [PATCH 2/8] feat: update privacy policy and enhance statistics display in history tab --- PRIVACY.md | 5 +++-- popup/history.js | 6 +++--- popup/popup.css | 37 ++++++++++++++++++++----------------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 0bda008..9c2f45a 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ # Privacy Policy -**Last updated: March 18, 2026** +**Last updated: May 29, 2026** ## Overview @@ -15,6 +15,7 @@ Plane Alert collects and stores the following data locally on your device: - **Notification preferences** — your notification and sound settings. - **Caught aircraft** — ICAO hex addresses of aircraft you have marked as caught. - **Notification history** — a local log of triggered alerts (callsign, timestamp, flight details). +- **Statistics** — all-time counters derived from triggered notifications: total notification count, date of first detection, aircraft type counts, and airline prefix counts. These are stored locally and can be reset at any time via the History tab. ## How Your Data Is Used @@ -34,7 +35,7 @@ We do not sell, trade, or share any of your data with third parties. ## Data Deletion -You can delete all locally stored data at any time by removing the extension from Chrome, or by using the "Export backup" feature and clearing your settings manually. +You can delete all locally stored data at any time by removing the extension from Chrome, or by using the "Export backup" feature and clearing your settings manually. Statistics can be reset independently via the History tab. ## Changes to This Policy diff --git a/popup/history.js b/popup/history.js index 107c3ab..063e339 100644 --- a/popup/history.js +++ b/popup/history.js @@ -1,4 +1,4 @@ -// history.js v1.1.0 — injecteert History tab HTML en beheert notificatiegeschiedenis +// history.js v1.1.1 — injecteert History tab HTML en beheert notificatiegeschiedenis // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -15,7 +15,7 @@ function initHistoryTab() {
Loading…
- +
@@ -109,7 +109,7 @@ async function renderStats() { await chrome.storage.local.get(['statsTotalCount', 'statsFirstDetection', 'statsTypeCounts', 'statsAirlineCounts']); if (statsTotalCount === 0 && !statsFirstDetection) { - container.innerHTML = '
No data yet. Stats are recorded as notifications fire.
'; + container.innerHTML = '
No data yet. Stats are recorded once notifications start firing.
'; return; } diff --git a/popup/popup.css b/popup/popup.css index c7500c8..b770555 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1212,16 +1212,13 @@ input::placeholder { color: #3a4560; } cursor: default; } -/* ── Statistics dropdown ── */ -/* Voeg toe aan het einde van popup.css */ - .stats-dropdown { border: 1px solid #1a2a4a; border-radius: 8px; overflow: hidden; margin-bottom: 14px; } - + .stats-toggle-btn { width: 100%; display: flex; @@ -1235,34 +1232,34 @@ input::placeholder { color: #3a4560; } font-family: 'Space Mono', monospace; font-size: 11px; } - + .stats-toggle-btn:hover { background: #101a38; } - + .stats-chevron { font-size: 10px; transition: transform 0.2s; color: #5a6a90; } - + .stats-panel-inner { padding: 10px 12px 4px; background: #080f25; } - + .stats-meta-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; } - + .stats-meta-cell { background: #0f1322; border: 1px solid #1e2840; border-radius: 6px; padding: 8px 10px; } - + .stats-meta-label { font-family: 'Space Mono', monospace; font-size: 9px; @@ -1271,14 +1268,14 @@ input::placeholder { color: #3a4560; } color: #5a6a90; margin-bottom: 4px; } - + .stats-meta-value { font-family: 'Space Mono', monospace; font-size: 15px; font-weight: 700; color: #60a5fa; } - + .stats-section-label { font-family: 'Space Mono', monospace; font-size: 9px; @@ -1287,14 +1284,14 @@ input::placeholder { color: #3a4560; } color: #5a6a90; margin-bottom: 6px; } - + .stats-bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; } - + .stats-bar-label { font-family: 'Space Mono', monospace; font-size: 10px; @@ -1302,7 +1299,7 @@ input::placeholder { color: #3a4560; } width: 44px; flex-shrink: 0; } - + .stats-bar-track { flex: 1; height: 5px; @@ -1310,14 +1307,14 @@ input::placeholder { color: #3a4560; } border-radius: 3px; overflow: hidden; } - + .stats-bar-fill { height: 100%; background: #2a5aaa; border-radius: 3px; transition: width 0.3s ease; } - + .stats-bar-count { font-family: 'Space Mono', monospace; font-size: 10px; @@ -1325,4 +1322,10 @@ input::placeholder { color: #3a4560; } width: 24px; text-align: right; flex-shrink: 0; +} + +/* Reset-knop krijgt zelfde zijpadding als stats-panel-inner */ +#btnResetStats { + margin: 0 12px 10px; + width: calc(100% - 24px); } \ No newline at end of file From d7a60fc1d2ae423db4437f3349f36e9316f4a36a Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Fri, 29 May 2026 10:48:57 +0200 Subject: [PATCH 3/8] refactor(settings): restructure cards, split Sound, improve notif preview --- popup/popup.css | 49 +++++++++++++++++++++++++ popup/popup.html | 2 +- popup/settings.js | 92 +++++++++++++++++++++++++++-------------------- 3 files changed, 103 insertions(+), 40 deletions(-) diff --git a/popup/popup.css b/popup/popup.css index b770555..1b2998e 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1328,4 +1328,53 @@ input::placeholder { color: #3a4560; } #btnResetStats { margin: 0 12px 10px; width: calc(100% - 24px); +} + +/* ── Notificatie OS-preview ── */ +/* Vervangt .notif-builder-preview / .notif-preview-title / .notif-preview-body */ + +.notif-os-preview { + display: flex; + align-items: flex-start; + gap: 10px; + background: #1c2033; + border: 1px solid #2a3a5a; + border-radius: 10px; + padding: 10px 12px; + margin-bottom: 12px; + box-shadow: 0 2px 8px rgba(0,0,0,0.4); +} + +.notif-os-icon { + width: 32px; + height: 32px; + border-radius: 6px; + flex-shrink: 0; + margin-top: 1px; +} + +.notif-os-body { + flex: 1; + min-width: 0; +} + +.notif-os-preview .notif-preview-title { + font-family: 'Outfit', sans-serif; + font-size: 12px; + font-weight: 700; + color: #e8e4f0; + margin-bottom: 3px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.notif-os-preview .notif-preview-body { + font-family: 'Space Mono', monospace; + font-size: 10px; + color: #8b9cc8; + line-height: 1.5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } \ No newline at end of file diff --git a/popup/popup.html b/popup/popup.html index d087db3..52b499e 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -84,7 +84,7 @@
RadiusChoose 25, 50 or 100 km (or nm in imperial mode), or set a custom value with the slider
Hide ground trafficGlobal setting — suppresses both notifications and Live tab entries for aircraft on the ground
-
UnitsSwitch between metric (km · m · km/h) and imperial (nm · ft · kts). Affects the Live tab, detail panel, and notifications.
+
UnitsSwitch between metric (km · m · km/h) and imperial (nm · ft · kts). Affects the Live tab, detail dropdown, and notifications.
NotificationsToggle desktop notifications on or off. Polling continues either way.
SoundChoose an alert sound and volume. A preview plays when you select a sound.
ContentChoose which details appear in each notification: aircraft type, altitude, speed, route and/or direction
diff --git a/popup/settings.js b/popup/settings.js index cefa2fd..4b98a04 100644 --- a/popup/settings.js +++ b/popup/settings.js @@ -1,4 +1,4 @@ -// settings.js — injecteert Settings tab HTML en beheert alle instellingen +// settings.js v1.1.0 — injecteert Settings tab HTML en beheert alle instellingen // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -34,6 +34,21 @@ function initSettingsTab() {
+ +
+ + +
+
- -
- - -
-
- +
`; From ee0a9e3ef4f9d6a04e322ccdc595d39f2077c482 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Fri, 29 May 2026 11:23:03 +0200 Subject: [PATCH 4/8] Implement code changes to enhance functionality and improve performance --- popup/popup.css | 1240 ++++++++++++++++++++--------------------------- 1 file changed, 535 insertions(+), 705 deletions(-) diff --git a/popup/popup.css b/popup/popup.css index 1b2998e..9e8e453 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1,10 +1,49 @@ +/* ─── CSS VARIABELEN ─────────────────────────────────────────────────────── */ + +:root { + /* Achtergronden */ + --bg: #08090f; + --bg-surface: #0f1322; + --bg-deep: #080a12; + --bg-card: #0d1020; + --bg-input: #080a12; + + /* Borders */ + --border: #1e2840; + --border-hover: #2a3860; + --border-blue: #2a4a8a; + + /* Tekst */ + --text: #e8e4f0; + --text-muted: #8b9cc8; + --text-dim: #7a8ab0; + --text-faint: #5a6a90; + --text-ghost: #4b5680; + + /* Accenten */ + --blue: #60a5fa; + --blue-bg: #1a3a6a; + --blue-bg-dark: #1a2a4a; + --blue-dim: #2a3a6a; + --green: #22c55e; + --green-bg: #14532d; + --green-bright: #4ade80; + --red: #ef4444; + + /* Fonts */ + --font-sans: 'Outfit', sans-serif; + --font-mono: 'Space Mono', monospace; +} + +/* ─── RESET ──────────────────────────────────────────────────────────────── */ + * { box-sizing: border-box; margin: 0; padding: 0; } body { width: 380px; - background: #08090f; - color: #e8e4f0; - font-family: 'Outfit', sans-serif; + background: var(--bg); + color: var(--text); + font-family: var(--font-sans); font-size: 13px; display: flex; flex-direction: column; @@ -12,11 +51,12 @@ body { overflow: hidden; } -/* ── Header ── */ +/* ─── HEADER ─────────────────────────────────────────────────────────────── */ + .header { background: linear-gradient(135deg, #0f1729 0%, #0a1020 100%); padding: 16px 18px; - border-bottom: 1px solid #1a2040; + border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; @@ -25,16 +65,16 @@ body { .header-icon { font-size: 22px; } .header-title { - font-family: 'Outfit', sans-serif; font-weight: 800; font-size: 17px; color: #fff; letter-spacing: -0.4px; } -.header-title span { color: #60a5fa; } +.header-title span { color: var(--blue); } + +/* ─── MASTER TOGGLE ──────────────────────────────────────────────────────── */ -/* ── Master toggle ── */ .master-toggle { margin-left: auto; display: flex; @@ -43,13 +83,13 @@ body { } .master-toggle-label { - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 10px; - color: #4b5680; + color: var(--text-ghost); transition: color 0.2s; } -.master-toggle-label.on { color: #22c55e; } +.master-toggle-label.on { color: var(--green); } .toggle-pill { width: 40px; @@ -81,41 +121,49 @@ body { } .toggle-pill.on::after { - background: #22c55e; + background: var(--green); left: 21px; box-shadow: 0 0 6px #22c55e88; } -/* ── Status dot ── */ +/* ─── STATUS DOT ─────────────────────────────────────────────────────────── */ + .status-dot { display: flex; align-items: center; gap: 6px; font-size: 10px; - color: #4b5680; - font-family: 'Space Mono', monospace; + color: var(--text-ghost); + font-family: var(--font-mono); } .dot { - width: 7px; height: 7px; + width: 7px; + height: 7px; border-radius: 50%; background: #1e3a1e; } -.dot.active { background: #22c55e; box-shadow: 0 0 6px #22c55e88; animation: pulse 2s infinite; } + +.dot.active { + background: var(--green); + box-shadow: 0 0 6px #22c55e88; + animation: pulse 2s infinite; +} @keyframes pulse { 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + 50% { opacity: 0.5; } } -/* ── Help button ── */ +/* ─── HELP BUTTON ────────────────────────────────────────────────────────── */ + .btn-help { width: 22px; height: 22px; border-radius: 50%; - background: #1a2a4a; - border: 1px solid #2a3a6a; - color: #60a5fa; + background: var(--blue-bg-dark); + border: 1px solid var(--blue-dim); + color: var(--blue); font-size: 12px; font-weight: 700; display: flex; @@ -125,9 +173,11 @@ body { flex-shrink: 0; transition: background 0.15s; } + .btn-help:hover { background: #1e3255; } -/* ── Help overlay ── */ +/* ─── HELP OVERLAY ───────────────────────────────────────────────────────── */ + .help-overlay { display: none; position: fixed; @@ -138,11 +188,12 @@ body { justify-content: center; padding: 16px; } + .help-overlay.visible { display: flex; } .help-box { - background: #0d1020; - border: 1px solid #1e2840; + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 12px; width: 348px; max-height: calc(100% - 32px); @@ -156,8 +207,7 @@ body { justify-content: space-between; align-items: center; padding: 12px 16px; - border-bottom: 1px solid #1e2840; - font-family: 'Outfit', sans-serif; + border-bottom: 1px solid var(--border); font-weight: 700; font-size: 14px; color: #fff; @@ -167,12 +217,13 @@ body { .help-close { background: none; border: none; - color: #4b5680; + color: var(--text-ghost); font-size: 20px; cursor: pointer; line-height: 1; padding: 0 2px; } + .help-close:hover { color: #fff; } .help-content { @@ -188,13 +239,13 @@ body { font-weight: 700; text-transform: uppercase; letter-spacing: 1px; - color: #60a5fa; + color: var(--blue); margin-bottom: 8px; } .help-section p { font-size: 11px; - color: #8b9cc8; + color: var(--text-muted); line-height: 1.6; } @@ -214,30 +265,33 @@ body { line-height: 1.5; } -.help-row.faq { grid-template-columns: 1fr; gap: 2px; } -.help-row.faq span:first-child, .help-q { color: #c0cce8; font-weight: 600; } +.help-row span:last-child { color: #6b7ca8; } + +.help-row.faq { grid-template-columns: 1fr; gap: 2px; } +.help-row.faq span:first-child, +.help-q { color: #c0cce8; font-weight: 600; } .help-key { - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 10px; - color: #e8e4f0; + color: var(--text); padding-top: 1px; } -.help-row span:last-child { color: #6b7ca8; } .help-row code { background: #1a2040; padding: 1px 4px; border-radius: 3px; - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 10px; color: #7dd3fc; } -/* ── Tabs ── */ +/* ─── TABS ───────────────────────────────────────────────────────────────── */ + .tabs { display: flex; - border-bottom: 1px solid #1a2040; + border-bottom: 1px solid var(--border); background: #0a0c14; } @@ -247,16 +301,17 @@ body { text-align: center; font-size: 12px; font-weight: 600; - color: #4b5680; + color: var(--text-ghost); cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.15s; } -.tab:hover { color: #8b9cc8; } -.tab.active { color: #60a5fa; border-bottom-color: #60a5fa; } +.tab:hover { color: var(--text-muted); } +.tab.active { color: var(--blue); border-bottom-color: var(--blue); } + +/* ─── PANELS ─────────────────────────────────────────────────────────────── */ -/* ── Panels wrapper ── */ .panels-wrapper { position: relative; flex: 1; min-height: 0; overflow-y: auto; } .disabled-overlay { @@ -271,31 +326,21 @@ body { gap: 10px; } -.disabled-overlay.visible { display: flex; } - -.disabled-overlay-icon { - font-size: 32px; - opacity: 0.5; -} - -.disabled-overlay-text { - font-size: 13px; - font-weight: 600; - color: #8b9cc8; - text-align: center; - line-height: 1.7; -} +.disabled-overlay.visible { display: flex; } +.disabled-overlay-icon { font-size: 32px; opacity: 0.5; } +.disabled-overlay-text { font-size: 13px; font-weight: 600; color: var(--text-muted); text-align: center; line-height: 1.7; } -.panel { display: none; padding: 16px 18px; } +.panel { display: none; padding: 16px 18px; } .panel.active { display: block; } -/* ── Shared components ── */ +/* ─── GEDEELDE COMPONENTEN ───────────────────────────────────────────────── */ + .section-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; - color: #7a8ab0; + color: var(--text-dim); margin-bottom: 10px; } @@ -303,19 +348,20 @@ button { cursor: pointer; border: none; border-radius: 8px; - font-family: 'Outfit', sans-serif; + font-family: var(--font-sans); font-weight: 600; transition: all 0.15s; } .btn-add { width: 100%; - background: #1a3a6a; - color: #60a5fa; + background: var(--blue-bg); + color: var(--blue); padding: 9px; font-size: 13px; - border: 1px solid #2a4a8a; + border: 1px solid var(--border-blue); } + .btn-add:hover { background: #1e4278; } .btn-group { @@ -328,19 +374,18 @@ button { flex: 1; padding: 7px 4px; font-size: 12px; - font-family: 'Outfit', sans-serif; font-weight: 600; - background: #0f1322; - border: 1px solid #1e2840; - color: #7a8ab0; /* was #4b5680 */ + background: var(--bg-surface); + border: 1px solid var(--border); + color: var(--text-dim); border-radius: 7px; cursor: pointer; transition: all 0.15s; text-align: center; } -.btn-option:hover { color: #8b9cc8; border-color: #2a3860; } -.btn-option.active { background: #1a3a6a; border-color: #2a4a8a; color: #60a5fa; } +.btn-option:hover { color: var(--text-muted); border-color: var(--border-hover); } +.btn-option.active { background: var(--blue-bg); border-color: var(--border-blue); color: var(--blue); } .custom-slider-row { display: none; @@ -359,25 +404,27 @@ button { input[type="range"] { width: 100%; - accent-color: #60a5fa; + accent-color: var(--blue); cursor: pointer; } -select, input[type="text"] { - background: #080a12; - border: 1px solid #1e2840; +select, +input[type="text"] { + background: var(--bg-input); + border: 1px solid var(--border); border-radius: 6px; - color: #e8e4f0; - font-family: 'Outfit', sans-serif; + color: var(--text); + font-family: var(--font-sans); font-size: 12px; padding: 7px 10px; outline: none; transition: border-color 0.15s; } -select:focus, input[type="text"]:focus { border-color: #60a5fa; } -select { flex: 0 0 130px; cursor: pointer; } -input[type="text"] { flex: 1; } +select:focus, +input[type="text"]:focus { border-color: var(--blue); } +select { flex: 0 0 130px; cursor: pointer; } +input[type="text"]{ flex: 1; } input::placeholder { color: #3a4560; } .error-message { @@ -392,16 +439,17 @@ input::placeholder { color: #3a4560; } .empty-state { text-align: center; - color: #5a6a90; /* was #2a3060 */ + color: var(--text-faint); padding: 20px; font-size: 12px; line-height: 1.7; } -/* ── Alert items ── */ +/* ─── ALERTS TAB ─────────────────────────────────────────────────────────── */ + .alert-form { - background: #0f1322; - border: 1px solid #1e2840; + background: var(--bg-surface); + border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-bottom: 12px; @@ -419,17 +467,18 @@ input::placeholder { color: #3a4560; } display: flex; align-items: center; gap: 10px; - background: #0f1322; - border: 1px solid #1e2840; + background: var(--bg-surface); + border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; transition: border-color 0.15s; } -.alert-item:hover { border-color: #2a3860; } +.alert-item:hover { border-color: var(--border-hover); } .alert-toggle { - width: 32px; height: 17px; + width: 32px; + height: 17px; background: #1a2040; border-radius: 20px; cursor: pointer; @@ -442,28 +491,30 @@ input::placeholder { color: #3a4560; } .alert-toggle::after { content: ''; position: absolute; - width: 11px; height: 11px; - background: #4b5680; + width: 11px; + height: 11px; + background: var(--text-ghost); border-radius: 50%; - top: 3px; left: 3px; + top: 3px; + left: 3px; transition: all 0.2s; } -.alert-toggle.on { background: #1a3a2a; } -.alert-toggle.on::after { background: #22c55e; left: 18px; } +.alert-toggle.on { background: #1a3a2a; } +.alert-toggle.on::after { background: var(--green); left: 18px; } .alert-info { flex: 1; min-width: 0; } .alert-value { font-weight: 600; font-size: 13px; - color: #e8e4f0; - font-family: 'Space Mono', monospace; + color: var(--text); + font-family: var(--font-mono); } .alert-type-label { font-size: 10px; - color: #5a6a90; /* was #3a4560 */ + color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.5px; } @@ -475,110 +526,94 @@ input::placeholder { color: #3a4560; } padding: 2px 6px; flex-shrink: 0; } -.btn-remove:hover { color: #ef4444; } -/* ── Live tab ── */ -.live-stats { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; - margin-bottom: 14px; -} +.btn-remove:hover { color: var(--red); } -.stat-card { - background: #0f1322; - border: 1px solid #1e2840; - border-radius: 8px; - padding: 10px 12px; -} +/* ─── ALERT NOTE ─────────────────────────────────────────────────────────── */ -.stat-card .label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: #5a6a90; /* was #3a4560 */ - margin-bottom: 4px; -} +.alert-note-input { width: 100%; margin-bottom: 8px; } -.stat-card .value { - font-family: 'Space Mono', monospace; - font-size: 18px; - font-weight: 700; - color: #60a5fa; +.alert-note { + font-size: 10px; + color: var(--text-dim); + margin-top: 3px; + cursor: pointer; + min-height: 14px; + transition: color 0.15s; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.stat-card .value.green { color: #22c55e; } +.alert-note:hover { color: var(--text-muted); } +.alert-note-empty { color: var(--text-ghost); font-style: italic; } +.alert-note:hover .alert-note-empty { color: var(--text-dim); } + +.alert-note-field { + width: 100%; + font-size: 10px; + padding: 2px 5px; + background: var(--bg-input); + border: 1px solid var(--blue); + border-radius: 4px; + color: var(--text); + font-family: var(--font-sans); + outline: none; +} -.ac-list { display: flex; flex-direction: column; gap: 5px; } +/* ─── LIVE TAB ───────────────────────────────────────────────────────────── */ -.ac-item { +.refresh-row { display: flex; - justify-content: space-between; align-items: center; - background: #0f1322; - border: 1px solid #1e2840; - border-radius: 6px; - padding: 7px 10px; - cursor: pointer; - transition: border-color 0.15s; + gap: 8px; + margin-bottom: 10px; } -.ac-item:hover { border-color: #60a5fa; } -.ac-item.match { border-color: #22c55e44; background: #0f1f12; } - -.ac-flight { - font-family: 'Space Mono', monospace; +.refresh-btn { + flex: 1; + background: var(--bg-surface); + border: 1px solid var(--border); + color: var(--text-dim); + padding: 8px; font-size: 12px; - color: #e8e4f0; - font-weight: 700; } -.ac-detail { +.refresh-btn:hover { color: var(--blue); border-color: var(--border-hover); } + +.auto-refresh-countdown { + font-family: var(--font-mono); font-size: 10px; - color: #7a8ab0; /* was #4b5680 */ - margin-top: 2px; + color: var(--text-faint); + white-space: nowrap; + min-width: 44px; + text-align: right; } -.ac-altitude { - font-family: 'Space Mono', monospace; - font-size: 11px; - color: #7a8ab0; /* was #4b5680 */ - text-align: right; +.live-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 14px; } -.match-badge { - font-size: 9px; - background: #14532d; - color: #4ade80; - padding: 2px 6px; - border-radius: 10px; - margin-left: 6px; +.stat-card { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 8px; + padding: 10px 12px; } -.caught-badge { +.stat-card .label { font-size: 9px; - background: #0a2a1a; - color: #4ade80; - padding: 2px 6px; - border-radius: 10px; - margin-left: 6px; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-faint); + margin-bottom: 4px; } -.detail-catch-btn { - width: 100%; - background: #1a1a2a; - color: #6b7ca8; - border: 1px solid #2a2a4a; - padding: 8px; - font-size: 12px; - font-family: 'Outfit', sans-serif; - font-weight: 600; - border-radius: 7px; - margin-top: 6px; - cursor: pointer; - transition: all 0.15s; -} -.detail-catch-btn:hover { color: #4ade80; border-color: #22c55e44; background: #0f1f12; } +.stat-card .value { font-family: var(--font-mono); font-size: 18px; font-weight: 700; color: var(--blue); } +.stat-card .value.green { color: var(--green); } .live-controls { margin-bottom: 10px; @@ -593,121 +628,102 @@ input::placeholder { color: #3a4560; } align-items: center; } -.control-row select { - flex: 0 0 auto; - font-size: 11px; - padding: 5px 8px; -} +.control-row select { flex: 0 0 auto; font-size: 11px; padding: 5px 8px; } -.filter-toggles { - display: flex; - gap: 5px; - flex-wrap: wrap; -} +.filter-toggles { display: flex; gap: 5px; flex-wrap: wrap; } .filter-btn { font-size: 10px; - font-family: 'Outfit', sans-serif; font-weight: 600; padding: 5px 9px; - background: #0f1322; - border: 1px solid #1e2840; - color: #7a8ab0; /* was #4b5680 */ + background: var(--bg-surface); + border: 1px solid var(--border); + color: var(--text-dim); border-radius: 6px; cursor: pointer; transition: all 0.15s; white-space: nowrap; } -.filter-btn:hover { color: #8b9cc8; border-color: #2a3860; } -.filter-btn.active { background: #1a3a6a; border-color: #2a4a8a; color: #60a5fa; } - -.alt-label { - font-size: 10px; - color: #5a6a90; /* was #3a4560 */ - white-space: nowrap; -} +.filter-btn:hover { color: var(--text-muted); border-color: var(--border-hover); } +.filter-btn.active { background: var(--blue-bg); border-color: var(--border-blue); color: var(--blue); } .live-search { flex: 1; font-size: 11px; - font-family: 'Outfit', sans-serif; padding: 5px 8px; - background: #0f1322; - border: 1px solid #1e2840; + background: var(--bg-surface); + border: 1px solid var(--border); border-radius: 6px; - color: #e8e4f0; + color: var(--text); outline: none; transition: border-color 0.15s; } .live-search::placeholder { color: #3a4560; } -.live-search:focus { border-color: #2a4a8a; } +.live-search:focus { border-color: var(--border-blue); } + +.alt-label { font-size: 10px; color: var(--text-faint); white-space: nowrap; } .alt-value { - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 10px; - color: #60a5fa; + color: var(--blue); white-space: nowrap; min-width: 42px; text-align: right; } -.ac-distance { - font-family: 'Space Mono', monospace; - font-size: 10px; - color: #5a6a90; /* was #3a4560 */ - margin-top: 2px; -} +/* ─── AIRCRAFT LIJST ─────────────────────────────────────────────────────── */ -.refresh-btn { - flex: 1; - background: #0f1322; - border: 1px solid #1e2840; - color: #7a8ab0; - padding: 8px; - font-size: 12px; -} -.refresh-btn:hover { color: #60a5fa; border-color: #2a3860; } +.ac-list { display: flex; flex-direction: column; gap: 5px; } +.ac-wrapper { display: flex; flex-direction: column; } -.refresh-row { +.ac-item { display: flex; + justify-content: space-between; align-items: center; - gap: 8px; - margin-bottom: 10px; -} - -.auto-refresh-countdown { - font-family: 'Space Mono', monospace; - font-size: 10px; - color: #5a6a90; - white-space: nowrap; - min-width: 44px; - text-align: right; + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 6px; + padding: 7px 10px; + cursor: pointer; + transition: border-color 0.15s; } -/* ── Aircraft wrapper & dropdown ── */ -.ac-wrapper { - display: flex; - flex-direction: column; -} +.ac-item:hover { border-color: var(--blue); } +.ac-item.match { border-color: #22c55e44; background: #0f1f12; } .ac-item.open { - border-color: #2a4a8a; + border-color: var(--border-blue); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -.ac-chevron { - font-size: 8px; - color: #5a6a90; /* was #3a4560 */ - flex-shrink: 0; +.ac-flight { font-family: var(--font-mono); font-size: 12px; color: var(--text); font-weight: 700; } +.ac-detail { font-size: 10px; color: var(--text-dim); margin-top: 2px; } +.ac-altitude { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); text-align: right; } +.ac-distance { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); margin-top: 2px; } +.ac-chevron { font-size: 8px; color: var(--text-faint); flex-shrink: 0; } + +.match-badge, +.caught-badge { + font-size: 9px; + color: var(--green-bright); + padding: 2px 6px; + border-radius: 10px; + margin-left: 6px; } +.match-badge { background: var(--green-bg); } +.caught-badge { background: #0a2a1a; } + +/* ─── DETAIL DROPDOWN ────────────────────────────────────────────────────── */ + .ac-dropdown { display: none; background: #0d1120; - border: 1px solid #2a4a8a; + border: 1px solid var(--border-blue); border-top: none; border-bottom-left-radius: 6px; border-bottom-right-radius: 6px; @@ -730,8 +746,8 @@ input::placeholder { color: #3a4560; } } .detail-cell { - background: #080a12; - border: 1px solid #1e2840; + background: var(--bg-deep); + border: 1px solid var(--border); border-radius: 6px; padding: 7px 10px; } @@ -740,45 +756,116 @@ input::placeholder { color: #3a4560; } font-size: 9px; text-transform: uppercase; letter-spacing: 0.8px; - color: #5a6a90; /* was #3a4560 */ + color: var(--text-faint); margin-bottom: 3px; } -.detail-cell .val { - font-family: 'Space Mono', monospace; - font-size: 12px; - color: #e8e4f0; -} +.detail-cell .val { font-family: var(--font-mono); font-size: 12px; color: var(--text); } .detail-map-btn { width: 100%; - background: #1a3a6a; - color: #60a5fa; - border: 1px solid #2a4a8a; + background: var(--blue-bg); + color: var(--blue); + border: 1px solid var(--border-blue); padding: 8px; font-size: 12px; - font-family: 'Outfit', sans-serif; font-weight: 600; border-radius: 7px; } + .detail-map-btn:hover { background: #1e4278; } -/* ── History tab ── */ -.history-list { - display: flex; - flex-direction: column; - gap: 6px; +.detail-catch-btn { + width: 100%; + background: #1a1a2a; + color: #6b7ca8; + border: 1px solid #2a2a4a; + padding: 8px; + font-size: 12px; + font-weight: 600; + border-radius: 7px; + margin-top: 6px; + cursor: pointer; + transition: all 0.15s; +} + +.detail-catch-btn:hover { color: var(--green-bright); border-color: #22c55e44; background: #0f1f12; } + +/* ─── DETAIL DROPDOWN: ADD AS ALERT ─────────────────────────────────────── */ + +.detail-add-alert { + margin-bottom: 6px; + padding: 8px 10px; + background: var(--bg-deep); + border: 1px solid var(--border); + border-radius: 7px; +} + +.detail-add-alert-label { + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-faint); + margin-bottom: 7px; +} + +.detail-add-alert-note { + width: 100%; + font-size: 11px; + padding: 5px 8px; + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--text); + outline: none; + margin-bottom: 7px; + transition: border-color 0.15s; +} + +.detail-add-alert-note::placeholder { color: #3a4560; } +.detail-add-alert-note:focus { border-color: var(--border-blue); } + +.detail-add-alert-btns { display: flex; gap: 5px; } + +.detail-add-alert-btn { + flex: 1; + padding: 6px 4px; + font-size: 11px; + font-weight: 600; + background: var(--blue-bg-dark); + border: 1px solid var(--blue-dim); + color: var(--blue); + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.detail-add-alert-btn:hover:not(:disabled) { background: #1e3255; } +.detail-add-alert-btn:disabled { + background: #0f1a2a; + border-color: #1a2a3a; + color: var(--green); + cursor: default; } +/* ─── HISTORY TAB ────────────────────────────────────────────────────────── */ + +.history-list { display: flex; flex-direction: column; gap: 6px; } + .history-item { - background: #0f1322; - border: 1px solid #1e2840; + background: var(--bg-surface); + border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; transition: border-color 0.15s; } -.history-item[title]:hover { border-color: #2a3860; } +.history-item[title]:hover { border-color: var(--border-hover); } .history-item-top { display: flex; @@ -787,107 +874,108 @@ input::placeholder { color: #3a4560; } margin-bottom: 3px; } -.history-callsign { - font-family: 'Space Mono', monospace; - font-size: 12px; - font-weight: 700; - color: #e8e4f0; -} - -.history-time { - font-family: 'Space Mono', monospace; - font-size: 10px; - color: #5a6a90; /* was #3a4560 */ -} - -.history-detail { - font-size: 11px; - color: #7a8ab0; /* was #4b5680 */ -} +.history-callsign { font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--text); } +.history-time { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); } +.history-detail { font-size: 11px; color: var(--text-dim); } .btn-clear-history { font-size: 11px; - font-family: 'Outfit', sans-serif; font-weight: 600; background: none; border: 1px solid #2a3060; - color: #7a8ab0; /* was #4b5680 */ + color: var(--text-dim); padding: 4px 10px; border-radius: 6px; cursor: pointer; } -.btn-clear-history:hover { color: #ef4444; border-color: #ef4444; } -/* ── Notification builder ── */ -.notif-builder { - background: #0f1322; - border: 1px solid #1e2840; - border-radius: 10px; - padding: 12px; - margin-bottom: 12px; -} +.btn-clear-history:hover { color: var(--red); border-color: var(--red); } + +/* ─── STATISTIEKEN ───────────────────────────────────────────────────────── */ -.notif-builder-preview { - background: #080a12; - border: 1px solid #1e2840; +.stats-dropdown { + border: 1px solid #1a2a4a; border-radius: 8px; - padding: 10px 12px; - margin-bottom: 10px; + overflow: hidden; + margin-bottom: 14px; } -.notif-preview-title { - font-size: 12px; - font-weight: 700; - color: #e8e4f0; - margin-bottom: 3px; +.stats-toggle-btn { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 9px 12px; + background: #0d1530; + border: none; + cursor: pointer; + color: #c8d4f0; + font-family: var(--font-mono); + font-size: 11px; } -.notif-preview-body { - font-size: 11px; - color: #7a8ab0; /* was #4b5680 */ - font-family: 'Space Mono', monospace; - line-height: 1.5; +.stats-toggle-btn:hover { background: #101a38; } + +.stats-chevron { + font-size: 10px; + transition: transform 0.2s; + color: var(--text-faint); } -.notif-toggle-list { - display: flex; - flex-direction: column; - gap: 7px; +.stats-panel-inner { padding: 10px 12px 4px; background: #080f25; } + +.stats-meta-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 12px; } -.notif-toggle-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 2px; +.stats-meta-cell { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 10px; } -.notif-toggle-label { - font-size: 12px; - color: #8b9cc8; +.stats-meta-label { + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-faint); + margin-bottom: 4px; } -.notif-toggle-row .alert-toggle { flex-shrink: 0; } +.stats-meta-value { font-family: var(--font-mono); font-size: 15px; font-weight: 700; color: var(--blue); } + +.stats-section-label { + font-family: var(--font-mono); + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-faint); + margin-bottom: 6px; +} + +.stats-bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; } +.stats-bar-label { font-family: var(--font-mono); font-size: 10px; color: #c8d4f0; width: 44px; flex-shrink: 0; } +.stats-bar-track { flex: 1; height: 5px; background: #1a2040; border-radius: 3px; overflow: hidden; } +.stats-bar-fill { height: 100%; background: #2a5aaa; border-radius: 3px; transition: width 0.3s ease; } +.stats-bar-count { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); width: 24px; text-align: right; flex-shrink: 0; } + +#btnResetStats { margin: 0 12px 10px; width: calc(100% - 24px); } + +/* ─── SETTINGS TAB ───────────────────────────────────────────────────────── */ -/* ── Settings kaarten ── */ .settings-card { - background: #0d1020; - border: 1px solid #1e2840; + background: var(--bg-card); + border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px; } -.settings-card-title { - font-family: 'Outfit', sans-serif; - font-size: 11px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - color: #7a8ab0; - margin-bottom: 12px; -} - .settings-card-toggle { width: 100%; display: flex; @@ -897,35 +985,25 @@ input::placeholder { color: #3a4560; } border: none; cursor: pointer; padding: 0; - font-family: 'Outfit', sans-serif; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; - color: #7a8ab0; + color: var(--text-dim); } -.settings-card-toggle:hover { - color: #a0b0d0; -} +.settings-card-toggle:hover { color: #a0b0d0; } -.settings-chevron { - font-size: 9px; - transition: transform 0.2s; - color: #5a6a90; /* was #4b5680 */ -} - -.settings-card-body { - margin-top: 12px; -} +.settings-chevron { font-size: 9px; transition: transform 0.2s; color: var(--text-faint); } +.settings-card-body { margin-top: 12px; } .settings-sublabel { - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; - color: #7a8ab0; + color: var(--text-dim); margin-bottom: 7px; margin-top: 2px; } @@ -937,31 +1015,11 @@ input::placeholder { color: #3a4560; } gap: 10px; } -.settings-toggle-info { - display: flex; - flex-direction: column; - gap: 2px; -} - -.settings-toggle-label { - font-family: 'Outfit', sans-serif; - font-size: 13px; - color: #c8d4f0; - font-weight: 500; -} - -.settings-toggle-sub { - font-family: 'Space Mono', monospace; - font-size: 9px; - color: #7a8ab0; /* was #4b5680 */ -} +.settings-toggle-info { display: flex; flex-direction: column; gap: 2px; } +.settings-toggle-label { font-size: 13px; color: #c8d4f0; font-weight: 500; } +.settings-toggle-sub { font-family: var(--font-mono); font-size: 9px; color: var(--text-dim); } -.radius-value { - font-family: 'Space Mono', monospace; - font-size: 13px; - color: #60a5fa; - font-weight: 700; -} +.radius-value { font-family: var(--font-mono); font-size: 13px; color: var(--blue); font-weight: 700; } .location-row { display: flex; @@ -972,108 +1030,113 @@ input::placeholder { color: #3a4560; } .coord-display { flex: 1; - background: #0f1322; - border: 1px solid #1e2840; + background: var(--bg-surface); + border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 11px; - color: #60a5fa; + color: var(--blue); } -.coord-display.empty { color: #5a6a90; /* was #3a4560 */ } +.coord-display.empty { color: var(--text-faint); } .btn-location { - background: #1a2a4a; - color: #60a5fa; + background: var(--blue-bg-dark); + color: var(--blue); padding: 8px 12px; font-size: 12px; white-space: nowrap; } + .btn-location:hover { background: #1e3255; } -/* ── Saved toast ── */ -.saved-toast { - position: fixed; - bottom: 12px; - left: 50%; - transform: translateX(-50%) translateY(8px); - background: #14532d; - border: 1px solid #22c55e44; - color: #4ade80; - font-size: 11px; - font-family: 'Space Mono', monospace; - padding: 6px 14px; - border-radius: 20px; - opacity: 0; - transition: opacity 0.2s, transform 0.2s; - pointer-events: none; - z-index: 200; - white-space: nowrap; -} -.saved-toast.visible { - opacity: 1; - transform: translateX(-50%) translateY(0); +/* ─── NOTIFICATIONS BUILDER ──────────────────────────────────────────────── */ + +.notif-builder { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px; + margin-bottom: 12px; } -/* ── Attribution footer ── */ -.attribution-footer { - border-top: 1px solid #1a2040; - padding: 7px 18px; - background: #08090f; +.notif-os-preview { display: flex; - align-items: center; - justify-content: space-between; - flex-shrink: 0; + align-items: flex-start; + gap: 10px; + background: #1c2033; + border: 1px solid #2a3a5a; + border-radius: 10px; + padding: 10px 12px; + margin-bottom: 12px; + box-shadow: 0 2px 8px rgba(0,0,0,0.4); } -.attribution-logo { - height: 20px; - opacity: 1; - transition: opacity 0.2s; - display: block; +.notif-os-icon { + width: 32px; + height: 32px; + border-radius: 6px; + flex-shrink: 0; + margin-top: 1px; } -.attribution-brand:hover .attribution-logo { - opacity: 0.75; -} +.notif-os-body { flex: 1; min-width: 0; } -.attribution-coverage { - font-family: 'Outfit', sans-serif; - font-size: 11px; - font-weight: 600; - color: #60a5fa; - text-decoration: none; - opacity: 0.75; - transition: opacity 0.2s; +.notif-os-preview .notif-preview-title { + font-size: 12px; + font-weight: 700; + color: var(--text); + margin-bottom: 3px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.attribution-coverage:hover { - opacity: 1; - text-decoration: underline; +.notif-os-preview .notif-preview-body { + font-family: var(--font-mono); + font-size: 10px; + color: var(--text-muted); + line-height: 1.5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -/* ── Donate footer ── */ -.donate-footer { - border-top: 1px solid #1a2040; - padding: 10px 18px; - background: #08090f; +.notif-toggle-list { display: flex; flex-direction: column; gap: 7px; } + +.notif-toggle-row { display: flex; - justify-content: center; + align-items: center; + justify-content: space-between; + padding: 0 2px; +} + +.notif-toggle-label { font-size: 12px; color: var(--text-muted); } +.notif-toggle-row .alert-toggle { flex-shrink: 0; } + +/* ─── FOOTERS ────────────────────────────────────────────────────────────── */ + +.donate-footer, +.attribution-footer { + border-top: 1px solid var(--border); + background: var(--bg); flex-shrink: 0; } +.donate-footer { padding: 10px 18px; display: flex; justify-content: center; } +.attribution-footer { padding: 7px 18px; display: flex; align-items: center; justify-content: space-between; } + .kofi-btn { display: inline-flex; align-items: center; gap: 8px; - background: #0f1322; - border: 1px solid #2a3a6a; - color: #8b9cc8; + background: var(--bg-surface); + border: 1px solid var(--blue-dim); + color: var(--text-muted); text-decoration: none; padding: 8px 18px; border-radius: 20px; - font-family: 'Outfit', sans-serif; font-size: 12px; font-weight: 600; transition: all 0.2s; @@ -1081,300 +1144,67 @@ input::placeholder { color: #3a4560; } justify-content: center; } -.kofi-btn:hover { - background: #1a2a50; - border-color: #60a5fa; - color: #e8e4f0; -} +.kofi-btn:hover { background: #1a2a50; border-color: var(--blue); color: var(--text); } .kofi-icon { font-size: 14px; } - .kofi-text { flex: 1; text-align: center; } .kofi-badge { - font-family: 'Space Mono', monospace; + font-family: var(--font-mono); font-size: 9px; font-weight: 700; background: #1e3a6a; - color: #60a5fa; + color: var(--blue); padding: 2px 7px; border-radius: 10px; letter-spacing: 0.5px; text-transform: uppercase; } - -/* ── Alert note ── */ -.alert-note-input { - width: 100%; - margin-bottom: 8px; -} - -.alert-note { - font-size: 10px; - color: #7a8ab0; /* was #4b5680 */ - margin-top: 3px; - cursor: pointer; - min-height: 14px; - transition: color 0.15s; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.alert-note:hover { color: #8b9cc8; } - -.alert-note-empty { - color: #4b5680; /* was #2a3060 */ - font-style: italic; -} - -.alert-note:hover .alert-note-empty { color: #7a8ab0; /* was #4b5680 */ } - -.alert-note-field { - width: 100%; - font-size: 10px; - padding: 2px 5px; - background: #080a12; - border: 1px solid #60a5fa; - border-radius: 4px; - color: #e8e4f0; - font-family: 'Outfit', sans-serif; - outline: none; -} - -/* ── Scrollbar ── */ -::-webkit-scrollbar { width: 4px; } -::-webkit-scrollbar-track { background: #0a0c14; } -::-webkit-scrollbar-thumb { background: #1e2840; border-radius: 2px; } - -/* ── Detail dropdown: add as alert ── */ -.detail-add-alert { - margin-bottom: 6px; - padding: 8px 10px; - background: #080a12; - border: 1px solid #1e2840; - border-radius: 7px; -} - -.detail-add-alert-label { - font-family: 'Space Mono', monospace; - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: #5a6a90; - margin-bottom: 7px; -} - -.detail-add-alert-note { - width: 100%; - font-size: 11px; - font-family: 'Outfit', sans-serif; - padding: 5px 8px; - background: #0f1322; - border: 1px solid #1e2840; - border-radius: 6px; - color: #e8e4f0; - outline: none; - margin-bottom: 7px; - transition: border-color 0.15s; +.attribution-logo { + height: 20px; + display: block; + transition: opacity 0.2s; } -.detail-add-alert-note::placeholder { color: #3a4560; } -.detail-add-alert-note:focus { border-color: #2a4a8a; } -.detail-add-alert-btns { - display: flex; - gap: 5px; -} +.attribution-brand:hover .attribution-logo { opacity: 0.75; } -.detail-add-alert-btn { - flex: 1; - padding: 6px 4px; +.attribution-coverage { font-size: 11px; - font-family: 'Outfit', sans-serif; font-weight: 600; - background: #1a2a4a; - border: 1px solid #2a3a6a; - color: #60a5fa; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.detail-add-alert-btn:hover:not(:disabled) { background: #1e3255; } -.detail-add-alert-btn:disabled { - background: #0f1a2a; - border-color: #1a2a3a; - color: #22c55e; - cursor: default; -} - -.stats-dropdown { - border: 1px solid #1a2a4a; - border-radius: 8px; - overflow: hidden; - margin-bottom: 14px; -} - -.stats-toggle-btn { - width: 100%; - display: flex; - justify-content: space-between; - align-items: center; - padding: 9px 12px; - background: #0d1530; - border: none; - cursor: pointer; - color: #c8d4f0; - font-family: 'Space Mono', monospace; - font-size: 11px; -} - -.stats-toggle-btn:hover { background: #101a38; } - -.stats-chevron { - font-size: 10px; - transition: transform 0.2s; - color: #5a6a90; -} - -.stats-panel-inner { - padding: 10px 12px 4px; - background: #080f25; -} - -.stats-meta-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; - margin-bottom: 12px; -} - -.stats-meta-cell { - background: #0f1322; - border: 1px solid #1e2840; - border-radius: 6px; - padding: 8px 10px; -} - -.stats-meta-label { - font-family: 'Space Mono', monospace; - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: #5a6a90; - margin-bottom: 4px; -} - -.stats-meta-value { - font-family: 'Space Mono', monospace; - font-size: 15px; - font-weight: 700; - color: #60a5fa; -} - -.stats-section-label { - font-family: 'Space Mono', monospace; - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: #5a6a90; - margin-bottom: 6px; -} - -.stats-bar-row { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 5px; -} - -.stats-bar-label { - font-family: 'Space Mono', monospace; - font-size: 10px; - color: #c8d4f0; - width: 44px; - flex-shrink: 0; -} - -.stats-bar-track { - flex: 1; - height: 5px; - background: #1a2040; - border-radius: 3px; - overflow: hidden; -} - -.stats-bar-fill { - height: 100%; - background: #2a5aaa; - border-radius: 3px; - transition: width 0.3s ease; -} - -.stats-bar-count { - font-family: 'Space Mono', monospace; - font-size: 10px; - color: #7a8ab0; - width: 24px; - text-align: right; - flex-shrink: 0; -} - -/* Reset-knop krijgt zelfde zijpadding als stats-panel-inner */ -#btnResetStats { - margin: 0 12px 10px; - width: calc(100% - 24px); + color: var(--blue); + text-decoration: none; + opacity: 0.75; + transition: opacity 0.2s; } -/* ── Notificatie OS-preview ── */ -/* Vervangt .notif-builder-preview / .notif-preview-title / .notif-preview-body */ +.attribution-coverage:hover { opacity: 1; text-decoration: underline; } -.notif-os-preview { - display: flex; - align-items: flex-start; - gap: 10px; - background: #1c2033; - border: 1px solid #2a3a5a; - border-radius: 10px; - padding: 10px 12px; - margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0,0,0,0.4); -} +/* ─── SAVED TOAST ────────────────────────────────────────────────────────── */ -.notif-os-icon { - width: 32px; - height: 32px; - border-radius: 6px; - flex-shrink: 0; - margin-top: 1px; +.saved-toast { + position: fixed; + bottom: 12px; + left: 50%; + transform: translateX(-50%) translateY(8px); + background: var(--green-bg); + border: 1px solid #22c55e44; + color: var(--green-bright); + font-size: 11px; + font-family: var(--font-mono); + padding: 6px 14px; + border-radius: 20px; + opacity: 0; + transition: opacity 0.2s, transform 0.2s; + pointer-events: none; + z-index: 200; + white-space: nowrap; } -.notif-os-body { - flex: 1; - min-width: 0; -} +.saved-toast.visible { opacity: 1; transform: translateX(-50%) translateY(0); } -.notif-os-preview .notif-preview-title { - font-family: 'Outfit', sans-serif; - font-size: 12px; - font-weight: 700; - color: #e8e4f0; - margin-bottom: 3px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +/* ─── SCROLLBAR ──────────────────────────────────────────────────────────── */ -.notif-os-preview .notif-preview-body { - font-family: 'Space Mono', monospace; - font-size: 10px; - color: #8b9cc8; - line-height: 1.5; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} \ No newline at end of file +::-webkit-scrollbar { width: 4px; } +::-webkit-scrollbar-track { background: #0a0c14; } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } \ No newline at end of file From c08dba714df7f4ed7af4462754657affd1a84dc3 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Tue, 2 Jun 2026 23:48:58 +0200 Subject: [PATCH 5/8] refactor(css): consolidate variables, remove dead rules, --- popup/popup.css | 761 +++++++++++++++--------------------------------- 1 file changed, 228 insertions(+), 533 deletions(-) diff --git a/popup/popup.css b/popup/popup.css index 9e8e453..1cfaee3 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1,49 +1,28 @@ -/* ─── CSS VARIABELEN ─────────────────────────────────────────────────────── */ - +/* ─── CSS VARIABELEN ───────────────────────────────────────────────────────── */ :root { - /* Achtergronden */ - --bg: #08090f; - --bg-surface: #0f1322; - --bg-deep: #080a12; - --bg-card: #0d1020; - --bg-input: #080a12; - - /* Borders */ - --border: #1e2840; - --border-hover: #2a3860; - --border-blue: #2a4a8a; - - /* Tekst */ - --text: #e8e4f0; - --text-muted: #8b9cc8; - --text-dim: #7a8ab0; - --text-faint: #5a6a90; - --text-ghost: #4b5680; - - /* Accenten */ - --blue: #60a5fa; - --blue-bg: #1a3a6a; - --blue-bg-dark: #1a2a4a; - --blue-dim: #2a3a6a; - --green: #22c55e; - --green-bg: #14532d; - --green-bright: #4ade80; - --red: #ef4444; - - /* Fonts */ - --font-sans: 'Outfit', sans-serif; - --font-mono: 'Space Mono', monospace; -} - -/* ─── RESET ──────────────────────────────────────────────────────────────── */ - -* { box-sizing: border-box; margin: 0; padding: 0; } + --bg: #08090f; + --surface: #0f1322; + --surface-alt: #0d1020; + --surface-dark:#080a12; + --border: #1e2840; + --border-blue: #2a4a8a; + --blue: #60a5fa; + --blue-dim: #1a3a6a; + --green: #22c55e; + --text: #e8e4f0; + --subtext: #8b9cc8; + --muted: #7a8ab0; + --muted-dark: #5a6a90; +} + +/* ─── RESET ────────────────────────────────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { width: 380px; background: var(--bg); color: var(--text); - font-family: var(--font-sans); + font-family: 'Outfit', sans-serif; font-size: 13px; display: flex; flex-direction: column; @@ -51,8 +30,7 @@ body { overflow: hidden; } -/* ─── HEADER ─────────────────────────────────────────────────────────────── */ - +/* ─── HEADER ───────────────────────────────────────────────────────────────── */ .header { background: linear-gradient(135deg, #0f1729 0%, #0a1020 100%); padding: 16px 18px; @@ -73,8 +51,7 @@ body { .header-title span { color: var(--blue); } -/* ─── MASTER TOGGLE ──────────────────────────────────────────────────────── */ - +/* ─── MASTER TOGGLE ────────────────────────────────────────────────────────── */ .master-toggle { margin-left: auto; display: flex; @@ -83,12 +60,11 @@ body { } .master-toggle-label { - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 10px; - color: var(--text-ghost); + color: #4b5680; transition: color 0.2s; } - .master-toggle-label.on { color: var(--green); } .toggle-pill { @@ -102,7 +78,6 @@ body { transition: background 0.25s, border-color 0.25s; flex-shrink: 0; } - .toggle-pill::after { content: ''; position: absolute; @@ -114,55 +89,34 @@ body { left: 3px; transition: all 0.25s; } +.toggle-pill.on { background: #14532d; border-color: #22c55e44; } +.toggle-pill.on::after { background: var(--green); left: 21px; box-shadow: 0 0 6px #22c55e88; } -.toggle-pill.on { - background: #14532d; - border-color: #22c55e44; -} - -.toggle-pill.on::after { - background: var(--green); - left: 21px; - box-shadow: 0 0 6px #22c55e88; -} - -/* ─── STATUS DOT ─────────────────────────────────────────────────────────── */ - +/* ─── STATUS DOT ───────────────────────────────────────────────────────────── */ .status-dot { display: flex; align-items: center; gap: 6px; font-size: 10px; - color: var(--text-ghost); - font-family: var(--font-mono); + color: #4b5680; + font-family: 'Space Mono', monospace; } -.dot { - width: 7px; - height: 7px; - border-radius: 50%; - background: #1e3a1e; -} - -.dot.active { - background: var(--green); - box-shadow: 0 0 6px #22c55e88; - animation: pulse 2s infinite; -} +.dot { width: 7px; height: 7px; border-radius: 50%; background: #1e3a1e; } +.dot.active { background: var(--green); box-shadow: 0 0 6px #22c55e88; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } -/* ─── HELP BUTTON ────────────────────────────────────────────────────────── */ - +/* ─── HELP BUTTON ──────────────────────────────────────────────────────────── */ .btn-help { width: 22px; height: 22px; border-radius: 50%; - background: var(--blue-bg-dark); - border: 1px solid var(--blue-dim); + background: #1a2a4a; + border: 1px solid #2a3a6a; color: var(--blue); font-size: 12px; font-weight: 700; @@ -173,11 +127,9 @@ body { flex-shrink: 0; transition: background 0.15s; } - .btn-help:hover { background: #1e3255; } -/* ─── HELP OVERLAY ───────────────────────────────────────────────────────── */ - +/* ─── HELP OVERLAY ─────────────────────────────────────────────────────────── */ .help-overlay { display: none; position: fixed; @@ -188,11 +140,10 @@ body { justify-content: center; padding: 16px; } - .help-overlay.visible { display: flex; } .help-box { - background: var(--bg-card); + background: var(--surface-alt); border: 1px solid var(--border); border-radius: 12px; width: 348px; @@ -217,13 +168,12 @@ body { .help-close { background: none; border: none; - color: var(--text-ghost); + color: #4b5680; font-size: 20px; cursor: pointer; line-height: 1; padding: 0 2px; } - .help-close:hover { color: #fff; } .help-content { @@ -243,19 +193,10 @@ body { margin-bottom: 8px; } -.help-section p { - font-size: 11px; - color: var(--text-muted); - line-height: 1.6; -} - +.help-section p { font-size: 11px; color: var(--subtext); line-height: 1.6; } .help-section p strong { color: #c0cce8; } -.help-table { - display: flex; - flex-direction: column; - gap: 5px; -} +.help-table { display: flex; flex-direction: column; gap: 5px; } .help-row { display: grid; @@ -264,31 +205,26 @@ body { font-size: 11px; line-height: 1.5; } - -.help-row span:last-child { color: #6b7ca8; } - -.help-row.faq { grid-template-columns: 1fr; gap: 2px; } +.help-row.faq { grid-template-columns: 1fr; gap: 2px; } .help-row.faq span:first-child, -.help-q { color: #c0cce8; font-weight: 600; } - +.help-q { color: #c0cce8; font-weight: 600; } .help-key { - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 10px; color: var(--text); padding-top: 1px; } - +.help-row span:last-child { color: #6b7ca8; } .help-row code { background: #1a2040; padding: 1px 4px; border-radius: 3px; - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 10px; color: #7dd3fc; } -/* ─── TABS ───────────────────────────────────────────────────────────────── */ - +/* ─── TABS ─────────────────────────────────────────────────────────────────── */ .tabs { display: flex; border-bottom: 1px solid var(--border); @@ -301,17 +237,15 @@ body { text-align: center; font-size: 12px; font-weight: 600; - color: var(--text-ghost); + color: #4b5680; cursor: pointer; border-bottom: 2px solid transparent; transition: all 0.15s; } - -.tab:hover { color: var(--text-muted); } +.tab:hover { color: var(--subtext); } .tab.active { color: var(--blue); border-bottom-color: var(--blue); } -/* ─── PANELS ─────────────────────────────────────────────────────────────── */ - +/* ─── PANELS ───────────────────────────────────────────────────────────────── */ .panels-wrapper { position: relative; flex: 1; min-height: 0; overflow-y: auto; } .disabled-overlay { @@ -325,22 +259,20 @@ body { flex-direction: column; gap: 10px; } - -.disabled-overlay.visible { display: flex; } -.disabled-overlay-icon { font-size: 32px; opacity: 0.5; } -.disabled-overlay-text { font-size: 13px; font-weight: 600; color: var(--text-muted); text-align: center; line-height: 1.7; } +.disabled-overlay.visible { display: flex; } +.disabled-overlay-icon { font-size: 32px; opacity: 0.5; } +.disabled-overlay-text { font-size: 13px; font-weight: 600; color: var(--subtext); text-align: center; line-height: 1.7; } .panel { display: none; padding: 16px 18px; } .panel.active { display: block; } -/* ─── GEDEELDE COMPONENTEN ───────────────────────────────────────────────── */ - +/* ─── SHARED COMPONENTS ────────────────────────────────────────────────────── */ .section-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; - color: var(--text-dim); + color: var(--muted); margin-bottom: 10px; } @@ -348,83 +280,60 @@ button { cursor: pointer; border: none; border-radius: 8px; - font-family: var(--font-sans); + font-family: 'Outfit', sans-serif; font-weight: 600; transition: all 0.15s; } .btn-add { width: 100%; - background: var(--blue-bg); + background: var(--blue-dim); color: var(--blue); padding: 9px; font-size: 13px; border: 1px solid var(--border-blue); } - .btn-add:hover { background: #1e4278; } -.btn-group { - display: flex; - gap: 6px; - margin-bottom: 6px; -} +.btn-group { display: flex; gap: 6px; margin-bottom: 6px; } .btn-option { flex: 1; padding: 7px 4px; font-size: 12px; font-weight: 600; - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); - color: var(--text-dim); + color: var(--muted); border-radius: 7px; cursor: pointer; transition: all 0.15s; text-align: center; } +.btn-option:hover { color: var(--subtext); border-color: #2a3860; } +.btn-option.active { background: var(--blue-dim); border-color: var(--border-blue); color: var(--blue); } -.btn-option:hover { color: var(--text-muted); border-color: var(--border-hover); } -.btn-option.active { background: var(--blue-bg); border-color: var(--border-blue); color: var(--blue); } - -.custom-slider-row { - display: none; - flex-direction: column; - gap: 4px; - margin-bottom: 4px; -} - +.custom-slider-row { display: none; flex-direction: column; gap: 4px; margin-bottom: 4px; } .custom-slider-row.visible { display: flex; } -.slider-header { - display: flex; - justify-content: space-between; - align-items: center; -} +.slider-header { display: flex; justify-content: space-between; align-items: center; } -input[type="range"] { - width: 100%; - accent-color: var(--blue); - cursor: pointer; -} +input[type="range"] { width: 100%; accent-color: var(--blue); cursor: pointer; } -select, -input[type="text"] { - background: var(--bg-input); +select, input[type="text"] { + background: var(--surface-dark); border: 1px solid var(--border); border-radius: 6px; color: var(--text); - font-family: var(--font-sans); + font-family: 'Outfit', sans-serif; font-size: 12px; padding: 7px 10px; outline: none; transition: border-color 0.15s; } - -select:focus, -input[type="text"]:focus { border-color: var(--blue); } -select { flex: 0 0 130px; cursor: pointer; } -input[type="text"]{ flex: 1; } +select:focus, input[type="text"]:focus { border-color: var(--blue); } +select { flex: 0 0 130px; cursor: pointer; } +input[type="text"] { flex: 1; } input::placeholder { color: #3a4560; } .error-message { @@ -437,44 +346,31 @@ input::placeholder { color: #3a4560; } line-height: 1.6; } -.empty-state { - text-align: center; - color: var(--text-faint); - padding: 20px; - font-size: 12px; - line-height: 1.7; -} - -/* ─── ALERTS TAB ─────────────────────────────────────────────────────────── */ +.empty-state { text-align: center; color: var(--muted-dark); padding: 20px; font-size: 12px; line-height: 1.7; } +/* ─── ALERTS TAB ───────────────────────────────────────────────────────────── */ .alert-form { - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px; margin-bottom: 12px; } -.form-row { - display: flex; - gap: 8px; - margin-bottom: 8px; -} - +.form-row { display: flex; gap: 8px; margin-bottom: 8px; } .alert-list { display: flex; flex-direction: column; gap: 6px; } .alert-item { display: flex; align-items: center; gap: 10px; - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; transition: border-color 0.15s; } - -.alert-item:hover { border-color: var(--border-hover); } +.alert-item:hover { border-color: #2a3860; } .alert-toggle { width: 32px; @@ -487,19 +383,17 @@ input::placeholder { color: #3a4560; } border: none; transition: background 0.2s; } - .alert-toggle::after { content: ''; position: absolute; width: 11px; height: 11px; - background: var(--text-ghost); + background: #4b5680; border-radius: 50%; top: 3px; left: 3px; transition: all 0.2s; } - .alert-toggle.on { background: #1a3a2a; } .alert-toggle.on::after { background: var(--green); left: 18px; } @@ -509,33 +403,25 @@ input::placeholder { color: #3a4560; } font-weight: 600; font-size: 13px; color: var(--text); - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; } .alert-type-label { font-size: 10px; - color: var(--text-faint); + color: var(--muted-dark); text-transform: uppercase; letter-spacing: 0.5px; } -.btn-remove { - background: none; - color: #2a3060; - font-size: 16px; - padding: 2px 6px; - flex-shrink: 0; -} - -.btn-remove:hover { color: var(--red); } - -/* ─── ALERT NOTE ─────────────────────────────────────────────────────────── */ +.btn-remove { background: none; color: #2a3060; font-size: 16px; padding: 2px 6px; flex-shrink: 0; } +.btn-remove:hover { color: #ef4444; } +/* ── Alert note ── */ .alert-note-input { width: 100%; margin-bottom: 8px; } .alert-note { font-size: 10px; - color: var(--text-dim); + color: var(--muted); margin-top: 3px; cursor: pointer; min-height: 14px; @@ -544,90 +430,73 @@ input::placeholder { color: #3a4560; } overflow: hidden; text-overflow: ellipsis; } +.alert-note:hover { color: var(--subtext); } -.alert-note:hover { color: var(--text-muted); } -.alert-note-empty { color: var(--text-ghost); font-style: italic; } -.alert-note:hover .alert-note-empty { color: var(--text-dim); } +.alert-note-empty { color: #4b5680; font-style: italic; } +.alert-note:hover .alert-note-empty { color: var(--muted); } .alert-note-field { width: 100%; font-size: 10px; padding: 2px 5px; - background: var(--bg-input); + background: var(--surface-dark); border: 1px solid var(--blue); border-radius: 4px; color: var(--text); - font-family: var(--font-sans); + font-family: 'Outfit', sans-serif; outline: none; } -/* ─── LIVE TAB ───────────────────────────────────────────────────────────── */ - -.refresh-row { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 10px; -} - -.refresh-btn { - flex: 1; - background: var(--bg-surface); - border: 1px solid var(--border); - color: var(--text-dim); - padding: 8px; - font-size: 12px; -} - -.refresh-btn:hover { color: var(--blue); border-color: var(--border-hover); } - -.auto-refresh-countdown { - font-family: var(--font-mono); - font-size: 10px; - color: var(--text-faint); - white-space: nowrap; - min-width: 44px; - text-align: right; -} - -.live-stats { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; - margin-bottom: 14px; -} +/* ─── LIVE TAB ─────────────────────────────────────────────────────────────── */ +.live-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 14px; } .stat-card { - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; } - -.stat-card .label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: var(--text-faint); - margin-bottom: 4px; -} - -.stat-card .value { font-family: var(--font-mono); font-size: 18px; font-weight: 700; color: var(--blue); } +.stat-card .label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--muted-dark); margin-bottom: 4px; } +.stat-card .value { font-family: 'Space Mono', monospace; font-size: 18px; font-weight: 700; color: var(--blue); } .stat-card .value.green { color: var(--green); } -.live-controls { - margin-bottom: 10px; - display: flex; - flex-direction: column; - gap: 6px; -} +.ac-list { display: flex; flex-direction: column; gap: 5px; } -.control-row { +.ac-item { display: flex; - gap: 6px; + justify-content: space-between; align-items: center; + background: var(--surface); + border: 1px solid var(--border); + border-radius: 6px; + padding: 7px 10px; + cursor: pointer; + transition: border-color 0.15s; } +.ac-item:hover { border-color: var(--blue); } +.ac-item.match { border-color: #22c55e44; background: #0f1f12; } +.ac-item.open { border-color: var(--border-blue); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } + +.ac-flight { font-family: 'Space Mono', monospace; font-size: 12px; color: var(--text); font-weight: 700; } +.ac-detail { font-size: 10px; color: var(--muted); margin-top: 2px; } +.ac-altitude { font-family: 'Space Mono', monospace; font-size: 11px; color: var(--muted); text-align: right; } +.ac-distance { font-family: 'Space Mono', monospace; font-size: 10px; color: var(--muted-dark); margin-top: 2px; } +.ac-chevron { font-size: 8px; color: var(--muted-dark); flex-shrink: 0; } +.match-badge, .caught-badge { + font-size: 9px; + color: #4ade80; + padding: 2px 6px; + border-radius: 10px; + margin-left: 6px; +} +.match-badge { background: #14532d; } +.caught-badge { background: #0a2a1a; } + +/* ── Live controls ── */ +.live-controls { margin-bottom: 10px; display: flex; flex-direction: column; gap: 6px; } + +.control-row { display: flex; gap: 6px; align-items: center; } .control-row select { flex: 0 0 auto; font-size: 11px; padding: 5px 8px; } .filter-toggles { display: flex; gap: 5px; flex-wrap: wrap; } @@ -636,37 +505,35 @@ input::placeholder { color: #3a4560; } font-size: 10px; font-weight: 600; padding: 5px 9px; - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); - color: var(--text-dim); + color: var(--muted); border-radius: 6px; cursor: pointer; transition: all 0.15s; white-space: nowrap; } +.filter-btn:hover { color: var(--subtext); border-color: #2a3860; } +.filter-btn.active { background: var(--blue-dim); border-color: var(--border-blue); color: var(--blue); } -.filter-btn:hover { color: var(--text-muted); border-color: var(--border-hover); } -.filter-btn.active { background: var(--blue-bg); border-color: var(--border-blue); color: var(--blue); } +.alt-label { font-size: 10px; color: var(--muted-dark); white-space: nowrap; } .live-search { flex: 1; font-size: 11px; padding: 5px 8px; - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 6px; color: var(--text); outline: none; transition: border-color 0.15s; } - .live-search::placeholder { color: #3a4560; } .live-search:focus { border-color: var(--border-blue); } -.alt-label { font-size: 10px; color: var(--text-faint); white-space: nowrap; } - .alt-value { - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 10px; color: var(--blue); white-space: nowrap; @@ -674,51 +541,29 @@ input::placeholder { color: #3a4560; } text-align: right; } -/* ─── AIRCRAFT LIJST ─────────────────────────────────────────────────────── */ - -.ac-list { display: flex; flex-direction: column; gap: 5px; } -.ac-wrapper { display: flex; flex-direction: column; } - -.ac-item { - display: flex; - justify-content: space-between; - align-items: center; - background: var(--bg-surface); +.refresh-btn { + flex: 1; + background: var(--surface); border: 1px solid var(--border); - border-radius: 6px; - padding: 7px 10px; - cursor: pointer; - transition: border-color 0.15s; -} - -.ac-item:hover { border-color: var(--blue); } -.ac-item.match { border-color: #22c55e44; background: #0f1f12; } - -.ac-item.open { - border-color: var(--border-blue); - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; + color: var(--muted); + padding: 8px; + font-size: 12px; } +.refresh-btn:hover { color: var(--blue); border-color: #2a3860; } -.ac-flight { font-family: var(--font-mono); font-size: 12px; color: var(--text); font-weight: 700; } -.ac-detail { font-size: 10px; color: var(--text-dim); margin-top: 2px; } -.ac-altitude { font-family: var(--font-mono); font-size: 11px; color: var(--text-dim); text-align: right; } -.ac-distance { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); margin-top: 2px; } -.ac-chevron { font-size: 8px; color: var(--text-faint); flex-shrink: 0; } +.refresh-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; } -.match-badge, -.caught-badge { - font-size: 9px; - color: var(--green-bright); - padding: 2px 6px; - border-radius: 10px; - margin-left: 6px; +.auto-refresh-countdown { + font-family: 'Space Mono', monospace; + font-size: 10px; + color: var(--muted-dark); + white-space: nowrap; + min-width: 44px; + text-align: right; } -.match-badge { background: var(--green-bg); } -.caught-badge { background: #0a2a1a; } - -/* ─── DETAIL DROPDOWN ────────────────────────────────────────────────────── */ +/* ── Aircraft dropdown ── */ +.ac-wrapper { display: flex; flex-direction: column; } .ac-dropdown { display: none; @@ -730,7 +575,6 @@ input::placeholder { color: #3a4560; } padding: 10px; animation: slideIn 0.15s ease; } - .ac-dropdown.open { display: block; } @keyframes slideIn { @@ -738,33 +582,20 @@ input::placeholder { color: #3a4560; } to { opacity: 1; transform: translateY(0); } } -.detail-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; - margin-bottom: 10px; -} +.detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-bottom: 10px; } .detail-cell { - background: var(--bg-deep); + background: var(--surface-dark); border: 1px solid var(--border); border-radius: 6px; padding: 7px 10px; } - -.detail-cell .label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: var(--text-faint); - margin-bottom: 3px; -} - -.detail-cell .val { font-family: var(--font-mono); font-size: 12px; color: var(--text); } +.detail-cell .label { font-size: 9px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--muted-dark); margin-bottom: 3px; } +.detail-cell .val { font-family: 'Space Mono', monospace; font-size: 12px; color: var(--text); } .detail-map-btn { width: 100%; - background: var(--blue-bg); + background: var(--blue-dim); color: var(--blue); border: 1px solid var(--border-blue); padding: 8px; @@ -772,7 +603,6 @@ input::placeholder { color: #3a4560; } font-weight: 600; border-radius: 7px; } - .detail-map-btn:hover { background: #1e4278; } .detail-catch-btn { @@ -788,117 +618,51 @@ input::placeholder { color: #3a4560; } cursor: pointer; transition: all 0.15s; } +.detail-catch-btn:hover { color: #4ade80; border-color: #22c55e44; background: #0f1f12; } -.detail-catch-btn:hover { color: var(--green-bright); border-color: #22c55e44; background: #0f1f12; } - -/* ─── DETAIL DROPDOWN: ADD AS ALERT ─────────────────────────────────────── */ - -.detail-add-alert { - margin-bottom: 6px; - padding: 8px 10px; - background: var(--bg-deep); - border: 1px solid var(--border); - border-radius: 7px; -} - -.detail-add-alert-label { - font-family: var(--font-mono); - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: var(--text-faint); - margin-bottom: 7px; -} - -.detail-add-alert-note { - width: 100%; - font-size: 11px; - padding: 5px 8px; - background: var(--bg-surface); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--text); - outline: none; - margin-bottom: 7px; - transition: border-color 0.15s; -} - -.detail-add-alert-note::placeholder { color: #3a4560; } -.detail-add-alert-note:focus { border-color: var(--border-blue); } - -.detail-add-alert-btns { display: flex; gap: 5px; } - -.detail-add-alert-btn { - flex: 1; - padding: 6px 4px; - font-size: 11px; - font-weight: 600; - background: var(--blue-bg-dark); - border: 1px solid var(--blue-dim); - color: var(--blue); - border-radius: 6px; +.detail-bell-btn { + background: none; + border: none; + font-size: 14px; cursor: pointer; - transition: all 0.15s; - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.detail-add-alert-btn:hover:not(:disabled) { background: #1e3255; } -.detail-add-alert-btn:disabled { - background: #0f1a2a; - border-color: #1a2a3a; - color: var(--green); - cursor: default; + padding: 0 2px; + line-height: 1; + transition: opacity 0.15s; + flex-shrink: 0; } +.detail-bell-btn:hover { opacity: 1 !important; } -/* ─── HISTORY TAB ────────────────────────────────────────────────────────── */ - +/* ─── HISTORY TAB ──────────────────────────────────────────────────────────── */ .history-list { display: flex; flex-direction: column; gap: 6px; } .history-item { - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; transition: border-color 0.15s; } +.history-item[title]:hover { border-color: #2a3860; } -.history-item[title]:hover { border-color: var(--border-hover); } - -.history-item-top { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 3px; -} - -.history-callsign { font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--text); } -.history-time { font-family: var(--font-mono); font-size: 10px; color: var(--text-faint); } -.history-detail { font-size: 11px; color: var(--text-dim); } +.history-item-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 3px; } +.history-callsign { font-family: 'Space Mono', monospace; font-size: 12px; font-weight: 700; color: var(--text); } +.history-time { font-family: 'Space Mono', monospace; font-size: 10px; color: var(--muted-dark); } +.history-detail { font-size: 11px; color: var(--muted); } .btn-clear-history { font-size: 11px; font-weight: 600; background: none; border: 1px solid #2a3060; - color: var(--text-dim); + color: var(--muted); padding: 4px 10px; border-radius: 6px; cursor: pointer; } +.btn-clear-history:hover { color: #ef4444; border-color: #ef4444; } -.btn-clear-history:hover { color: var(--red); border-color: var(--red); } - -/* ─── STATISTIEKEN ───────────────────────────────────────────────────────── */ - -.stats-dropdown { - border: 1px solid #1a2a4a; - border-radius: 8px; - overflow: hidden; - margin-bottom: 14px; -} +/* ─── STATISTICS ───────────────────────────────────────────────────────────── */ +.stats-dropdown { border: 1px solid #1a2a4a; border-radius: 8px; overflow: hidden; margin-bottom: 14px; } .stats-toggle-btn { width: 100%; @@ -910,66 +674,39 @@ input::placeholder { color: #3a4560; } border: none; cursor: pointer; color: #c8d4f0; - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 11px; } - .stats-toggle-btn:hover { background: #101a38; } -.stats-chevron { - font-size: 10px; - transition: transform 0.2s; - color: var(--text-faint); -} +.stats-chevron { font-size: 10px; transition: transform 0.2s; color: var(--muted-dark); } .stats-panel-inner { padding: 10px 12px 4px; background: #080f25; } -.stats-meta-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; - margin-bottom: 12px; -} +.stats-meta-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 12px; } .stats-meta-cell { - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; } +.stats-meta-label { font-family: 'Space Mono', monospace; font-size: 9px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--muted-dark); margin-bottom: 4px; } +.stats-meta-value { font-family: 'Space Mono', monospace; font-size: 15px; font-weight: 700; color: var(--blue); } -.stats-meta-label { - font-family: var(--font-mono); - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: var(--text-faint); - margin-bottom: 4px; -} - -.stats-meta-value { font-family: var(--font-mono); font-size: 15px; font-weight: 700; color: var(--blue); } - -.stats-section-label { - font-family: var(--font-mono); - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.8px; - color: var(--text-faint); - margin-bottom: 6px; -} +.stats-section-label { font-family: 'Space Mono', monospace; font-size: 9px; text-transform: uppercase; letter-spacing: 0.8px; color: var(--muted-dark); margin-bottom: 6px; } .stats-bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; } -.stats-bar-label { font-family: var(--font-mono); font-size: 10px; color: #c8d4f0; width: 44px; flex-shrink: 0; } +.stats-bar-label { font-family: 'Space Mono', monospace; font-size: 10px; color: #c8d4f0; width: 44px; flex-shrink: 0; } .stats-bar-track { flex: 1; height: 5px; background: #1a2040; border-radius: 3px; overflow: hidden; } .stats-bar-fill { height: 100%; background: #2a5aaa; border-radius: 3px; transition: width 0.3s ease; } -.stats-bar-count { font-family: var(--font-mono); font-size: 10px; color: var(--text-dim); width: 24px; text-align: right; flex-shrink: 0; } +.stats-bar-count { font-family: 'Space Mono', monospace; font-size: 10px; color: var(--muted); width: 24px; text-align: right; flex-shrink: 0; } #btnResetStats { margin: 0 12px 10px; width: calc(100% - 24px); } -/* ─── SETTINGS TAB ───────────────────────────────────────────────────────── */ - +/* ─── SETTINGS TAB ─────────────────────────────────────────────────────────── */ .settings-card { - background: var(--bg-card); + background: var(--surface-alt); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; @@ -989,72 +726,52 @@ input::placeholder { color: #3a4560; } font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--text-dim); + color: var(--muted); } - .settings-card-toggle:hover { color: #a0b0d0; } -.settings-chevron { font-size: 9px; transition: transform 0.2s; color: var(--text-faint); } +.settings-chevron { font-size: 9px; transition: transform 0.2s; color: var(--muted-dark); } + .settings-card-body { margin-top: 12px; } .settings-sublabel { - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; - color: var(--text-dim); + color: var(--muted); margin-bottom: 7px; margin-top: 2px; } -.settings-toggle-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} - -.settings-toggle-info { display: flex; flex-direction: column; gap: 2px; } +.settings-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; } +.settings-toggle-info { display: flex; flex-direction: column; gap: 2px; } .settings-toggle-label { font-size: 13px; color: #c8d4f0; font-weight: 500; } -.settings-toggle-sub { font-family: var(--font-mono); font-size: 9px; color: var(--text-dim); } +.settings-toggle-sub { font-family: 'Space Mono', monospace; font-size: 9px; color: var(--muted); } -.radius-value { font-family: var(--font-mono); font-size: 13px; color: var(--blue); font-weight: 700; } +.radius-value { font-family: 'Space Mono', monospace; font-size: 13px; color: var(--blue); font-weight: 700; } -.location-row { - display: flex; - gap: 8px; - align-items: center; - margin-bottom: 14px; -} +.location-row { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; } .coord-display { flex: 1; - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 8px 12px; - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 11px; color: var(--blue); } +.coord-display.empty { color: var(--muted-dark); } -.coord-display.empty { color: var(--text-faint); } - -.btn-location { - background: var(--blue-bg-dark); - color: var(--blue); - padding: 8px 12px; - font-size: 12px; - white-space: nowrap; -} - +.btn-location { background: #1a2a4a; color: var(--blue); padding: 8px 12px; font-size: 12px; white-space: nowrap; } .btn-location:hover { background: #1e3255; } -/* ─── NOTIFICATIONS BUILDER ──────────────────────────────────────────────── */ - +/* ─── NOTIFICATIONS ────────────────────────────────────────────────────────── */ .notif-builder { - background: var(--bg-surface); + background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px; @@ -1072,15 +789,7 @@ input::placeholder { color: #3a4560; } margin-bottom: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); } - -.notif-os-icon { - width: 32px; - height: 32px; - border-radius: 6px; - flex-shrink: 0; - margin-top: 1px; -} - +.notif-os-icon { width: 32px; height: 32px; border-radius: 6px; flex-shrink: 0; margin-top: 1px; } .notif-os-body { flex: 1; min-width: 0; } .notif-os-preview .notif-preview-title { @@ -1092,11 +801,10 @@ input::placeholder { color: #3a4560; } overflow: hidden; text-overflow: ellipsis; } - .notif-os-preview .notif-preview-body { - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 10px; - color: var(--text-muted); + color: var(--subtext); line-height: 1.5; white-space: nowrap; overflow: hidden; @@ -1104,36 +812,27 @@ input::placeholder { color: #3a4560; } } .notif-toggle-list { display: flex; flex-direction: column; gap: 7px; } - -.notif-toggle-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 2px; -} - -.notif-toggle-label { font-size: 12px; color: var(--text-muted); } +.notif-toggle-row { display: flex; align-items: center; justify-content: space-between; padding: 0 2px; } +.notif-toggle-label { font-size: 12px; color: var(--subtext); } .notif-toggle-row .alert-toggle { flex-shrink: 0; } -/* ─── FOOTERS ────────────────────────────────────────────────────────────── */ - -.donate-footer, -.attribution-footer { +/* ─── FOOTERS ──────────────────────────────────────────────────────────────── */ +.donate-footer { border-top: 1px solid var(--border); + padding: 10px 18px; background: var(--bg); + display: flex; + justify-content: center; flex-shrink: 0; } -.donate-footer { padding: 10px 18px; display: flex; justify-content: center; } -.attribution-footer { padding: 7px 18px; display: flex; align-items: center; justify-content: space-between; } - .kofi-btn { display: inline-flex; align-items: center; gap: 8px; - background: var(--bg-surface); - border: 1px solid var(--blue-dim); - color: var(--text-muted); + background: var(--surface); + border: 1px solid #2a3a6a; + color: var(--subtext); text-decoration: none; padding: 8px 18px; border-radius: 20px; @@ -1143,14 +842,11 @@ input::placeholder { color: #3a4560; } width: 100%; justify-content: center; } - .kofi-btn:hover { background: #1a2a50; border-color: var(--blue); color: var(--text); } - .kofi-icon { font-size: 14px; } .kofi-text { flex: 1; text-align: center; } - .kofi-badge { - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; font-size: 9px; font-weight: 700; background: #1e3a6a; @@ -1161,14 +857,17 @@ input::placeholder { color: #3a4560; } text-transform: uppercase; } -.attribution-logo { - height: 20px; - display: block; - transition: opacity 0.2s; +.attribution-footer { + border-top: 1px solid var(--border); + padding: 7px 18px; + background: var(--bg); + display: flex; + align-items: center; + justify-content: space-between; + flex-shrink: 0; } - +.attribution-logo { height: 20px; opacity: 1; transition: opacity 0.2s; display: block; } .attribution-brand:hover .attribution-logo { opacity: 0.75; } - .attribution-coverage { font-size: 11px; font-weight: 600; @@ -1177,21 +876,19 @@ input::placeholder { color: #3a4560; } opacity: 0.75; transition: opacity 0.2s; } - .attribution-coverage:hover { opacity: 1; text-decoration: underline; } -/* ─── SAVED TOAST ────────────────────────────────────────────────────────── */ - +/* ─── TOAST ────────────────────────────────────────────────────────────────── */ .saved-toast { position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%) translateY(8px); - background: var(--green-bg); + background: #14532d; border: 1px solid #22c55e44; - color: var(--green-bright); + color: #4ade80; font-size: 11px; - font-family: var(--font-mono); + font-family: 'Space Mono', monospace; padding: 6px 14px; border-radius: 20px; opacity: 0; @@ -1200,11 +897,9 @@ input::placeholder { color: #3a4560; } z-index: 200; white-space: nowrap; } - .saved-toast.visible { opacity: 1; transform: translateX(-50%) translateY(0); } -/* ─── SCROLLBAR ──────────────────────────────────────────────────────────── */ - +/* ─── SCROLLBAR ────────────────────────────────────────────────────────────── */ ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-track { background: #0a0c14; } ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } \ No newline at end of file From b10ccd87dfc0a7f15b1de83953c756119e2dd16d Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Thu, 4 Jun 2026 00:46:48 +0200 Subject: [PATCH 6/8] docs: update README to v1.5.0, clarify notifHistory in PRIVACY, add bell button help, fix live tab empty state --- PRIVACY.md | 12 +++++++++--- README.md | 22 ++++++++++++++++++---- popup/live.js | 15 +++++---------- popup/popup.html | 6 ++++-- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 9c2f45a..087dfc2 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ # Privacy Policy -**Last updated: May 29, 2026** +**Last updated: June 4, 2026** ## Overview @@ -14,7 +14,7 @@ Plane Alert collects and stores the following data locally on your device: - **Alert settings** — the alerts you configure (e.g. registrations, flight numbers, aircraft types). - **Notification preferences** — your notification and sound settings. - **Caught aircraft** — ICAO hex addresses of aircraft you have marked as caught. -- **Notification history** — a local log of triggered alerts (callsign, timestamp, flight details). +- **Notification history** — a local log of triggered alerts (callsign, timestamp, flight details). This log is stored only on your current device and is intentionally excluded from backup exports, as it reflects activity specific to your device and is not meaningful to restore. - **Statistics** — all-time counters derived from triggered notifications: total notification count, date of first detection, aircraft type counts, and airline prefix counts. These are stored locally and can be reset at any time via the History tab. ## How Your Data Is Used @@ -25,6 +25,12 @@ All data is used solely to provide the core functionality of the extension: poll All data is stored **locally on your device**, so nothing is shared with the developer. With one exception: your approximate location and radius are sent to the airplanes.live API with each poll to retrieve nearby aircraft. +## Backup & Export + +The backup export (Settings → Backup) includes: alerts, location, radius, units, notification preferences, sound settings, startup tab, caught aircraft, and statistics. + +**Notification history is excluded from backups by design.** It is an ephemeral device-local log and carries no value when restored to a different device or after a reinstall. + ## Third-Party Services Plane Alert uses the [airplanes.live](https://airplanes.live) API to retrieve real-time aircraft data. Your approximate location (latitude, longitude, radius) is sent to this API with each poll. Please refer to the [airplanes.live](https://airplanes.live) website for their own privacy practices. @@ -35,7 +41,7 @@ We do not sell, trade, or share any of your data with third parties. ## Data Deletion -You can delete all locally stored data at any time by removing the extension from Chrome, or by using the "Export backup" feature and clearing your settings manually. Statistics can be reset independently via the History tab. +You can delete all locally stored data at any time by removing the extension from Chrome, or by using the "Export backup" feature and clearing your settings manually. Notification history and statistics can be cleared independently via the History tab. ## Changes to This Policy diff --git a/README.md b/README.md index f95b183..4b3bf1b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Chrome extension that notifies you when a tracked aircraft enters your radius — powered by the [airplanes.live](https://airplanes.live) API. -![Version](https://img.shields.io/badge/version-1.4.0-blue) +![Version](https://img.shields.io/badge/version-1.5.0-blue) ![Manifest](https://img.shields.io/badge/manifest-v3-green) ![License](https://img.shields.io/badge/license-GPL%20v3-lightgrey) @@ -15,7 +15,7 @@ A Chrome extension that notifies you when a tracked aircraft enters your radius - **Catch aircraft** — mark an aircraft as caught directly from the notification or the Live tab detail panel. Caught aircraft won't trigger future notifications. Manage your caught list in the History tab. - **Alert sound** — choose from Ping, Radar, Alert or Chime with adjustable volume (soft, medium, loud) - **Live tab** — see all aircraft in your radius in real time, with sorting and filtering options -- **Detail dropdown** — click any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading +- **Detail dropdown** — click any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading. Use the 🔔/🔕 bell buttons to toggle alerts for that registration or type directly from the dropdown. - **Notification history** — a log of every triggered alert with callsign, time and flight details - **Settings** — organised into collapsible cards: Location, Filters, Units, Notifications, Startup tab, and Backup & Test - **Startup tab** — choose which tab opens on launch, or always return to the last tab you were on @@ -60,18 +60,32 @@ Plane Alert polls the airplanes.live API in the background every minute. When an --- +## Data & privacy + +All data is stored locally on your device. The only external call is to the airplanes.live API, which receives your approximate location and radius with each poll. + +**What is included in a backup export:** alerts, location, radius, units, notification preferences, sound settings, startup tab, caught aircraft, and statistics. + +**What is intentionally excluded from backups:** notification history (`notifHistory`). This log is ephemeral by design — it reflects what happened on your device and is not meaningful to restore on another machine or after a reinstall. + +See [PRIVACY.md](PRIVACY.md) for the full privacy policy. + +--- + ## File structure ``` plane-alert/ ├── manifest.json ├── background.js — background service worker, polling & notifications +├── shared.js — shared match logic (used by background & popup) ├── offscreen.html — offscreen document for audio playback ├── offscreen.js — Web Audio API sound engine ├── icons/ │ ├── icon16.png │ ├── icon48.png -│ └── icon128.png +│ ├── icon128.png +│ └── airplanes-live-logo.png └── popup/ ├── popup.html — extension UI skeleton ├── popup.css — all styles @@ -104,4 +118,4 @@ This project is licensed under the **GNU General Public License v3.0**. You are free to view, modify and redistribute the source code, but any derivative work must also be open source under the same license. -**Publishing this extension or any derivative to a browser extension store (Chrome, Firefox, Edge, etc.) requires explicit written permission from the author.** See the [LICENSE](LICENSE) file for full details. +**Publishing this extension or any derivative to a browser extension store (Chrome, Firefox, Edge, etc.) requires explicit written permission from the author.** See the [LICENSE](LICENSE) file for full details. \ No newline at end of file diff --git a/popup/live.js b/popup/live.js index abe68af..23b8fec 100644 --- a/popup/live.js +++ b/popup/live.js @@ -1,4 +1,4 @@ -// live.js v1.1.0 — injecteert Live tab HTML en beheert vliegtuigenlijst, filters, detail dropdown +// live.js v1.2.0 — injecteert Live tab HTML en beheert vliegtuigenlijst, filters, detail dropdown // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -41,9 +41,7 @@ function initLiveTab() { 0 m
-
-
Press refresh to load aircraft.
-
+
`; setupLiveEvents(); } @@ -233,8 +231,6 @@ function setupLiveEvents() { // ─── BELL BUTTON HELPER ──────────────────────────────────────────────────── -// Maakt een bell-knop aan voor een alert-type/waarde combinatie. -// Grijs + doorgestreept = geen alert actief. Groen = alert bestaat. Klik togglet. async function createBellBtn(type, value) { if (!value) return null; @@ -279,7 +275,6 @@ async function createBellBtn(type, value) { await chrome.storage.local.set({ alerts }); await syncState(); - // Alerts tab direct bijwerken if (typeof renderAlerts === 'function') { renderAlerts(alerts); } @@ -356,7 +351,9 @@ async function renderAircraftList() { matchEl.textContent = aircraft.filter(isMatch).length; if (aircraft.length === 0) { - list.innerHTML = '
No aircraft match the current filters.
'; + list.innerHTML = lastAcData.length === 0 + ? '
Press refresh to load aircraft.
' + : '
No aircraft match the current filters.
'; currentDetailHex = null; return; } @@ -435,7 +432,6 @@ async function renderAircraftList() { // ─── DROPDOWN CONTENT ────────────────────────────────────────────────────── async function buildDropdownContent(dropdown, ac, units, caught) { - // Cellen met optionele bell-knop (bell: null = geen knop) const cellDefs = [ { label: 'Registration', val: ac.r || '—', bell: { type: 'registration', value: ac.r } }, { label: 'Type', val: ac.t || '—', bell: { type: 'type', value: ac.t } }, @@ -466,7 +462,6 @@ async function buildDropdownContent(dropdown, ac, units, caught) { valEl.textContent = def.val; valRow.appendChild(valEl); - // Voeg bell-knop toe als het veld een waarde heeft if (def.bell && def.bell.value) { const bellBtn = await createBellBtn(def.bell.type, def.bell.value); if (bellBtn) valRow.appendChild(bellBtn); diff --git a/popup/popup.html b/popup/popup.html index 52b499e..1903aff 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -69,7 +69,9 @@
Matching onlyHide aircraft that don't match any active alert
Airborne onlyTemporarily hides ground traffic in the Live tab only. Does not affect notifications.
Min. altitudeOnly show aircraft above a set altitude
-
Detail dropdownClick any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading. Use 🎯 Catch to mark it as caught — it won't trigger notifications again. Click again to collapse.
+
Detail dropdownClick any aircraft to expand its details inline: registration, type, altitude, speed, route, squawk and heading. Click again to collapse.
+
🔔 / 🔕 buttonsEach detail dropdown shows a bell button next to the registration and aircraft type. 🔔 means an alert is active for that value — click to remove it. 🔕 means no alert exists — click to add one instantly without going to the Alerts tab.
+
🎯 CatchMark an aircraft as caught from the detail dropdown. It won't trigger notifications again until you release it.
@@ -89,7 +91,7 @@
SoundChoose an alert sound and volume. A preview plays when you select a sound.
ContentChoose which details appear in each notification: aircraft type, altitude, speed, route and/or direction
Startup tabChoose which tab opens when you launch the extension. Set it to a fixed tab or to Last used to always return to where you left off.
-
BackupExport all settings (alerts, location, radius, notification preferences) to a JSON file. Import restores everything at once. Old alerts-only backups are still supported.
+
BackupExport all settings (alerts, location, radius, notification preferences) to a JSON file. Import restores everything at once. Old alerts-only backups are still supported. Note: notification history is not included in exports.
From 489c6f0a82216ff40fe33d0194d3d9828bc02357 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Thu, 4 Jun 2026 00:56:06 +0200 Subject: [PATCH 7/8] feat: add toast on alert add, move showSaved to popup.js, add release-all confirmation --- popup/alerts.js | 5 +++-- popup/history.js | 22 +++++++++++++++++++++- popup/popup.js | 14 +++++++++++++- popup/settings.js | 16 ++-------------- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/popup/alerts.js b/popup/alerts.js index 40413d0..a478543 100644 --- a/popup/alerts.js +++ b/popup/alerts.js @@ -1,4 +1,4 @@ -// alerts.js — injecteert Alerts tab HTML en beheert alert logica +// alerts.js v1.1.0 — injecteert Alerts tab HTML en beheert alert logica // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -108,6 +108,7 @@ function setupAlertsEvents() { document.getElementById('alertValue').value = ''; document.getElementById('alertNote').value = ''; renderAlerts(alerts); + showSaved('Alert added'); }); } @@ -145,7 +146,7 @@ async function renderAlerts(alerts) { list.querySelectorAll('.alert-note').forEach(el => { el.addEventListener('click', (e) => { e.stopPropagation(); - if (el.querySelector('input')) return; // al in edit mode + if (el.querySelector('input')) return; const id = el.dataset.id; const currentNote = el.textContent.trim() === '+ add note' ? '' : el.textContent.trim(); diff --git a/popup/history.js b/popup/history.js index 063e339..516e1aa 100644 --- a/popup/history.js +++ b/popup/history.js @@ -1,4 +1,4 @@ -// history.js v1.1.1 — injecteert History tab HTML en beheert notificatiegeschiedenis +// history.js v1.2.0 — injecteert History tab HTML en beheert notificatiegeschiedenis // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -60,6 +60,26 @@ function setupHistoryEvents() { }); document.getElementById('btnClearCaught').addEventListener('click', async () => { + const btn = document.getElementById('btnClearCaught'); + if (btn.dataset.confirm !== '1') { + btn.dataset.confirm = '1'; + btn.textContent = 'Sure? Click again to confirm'; + btn.style.color = '#ef4444'; + btn.style.borderColor = '#ef4444'; + setTimeout(() => { + if (btn.dataset.confirm === '1') { + btn.dataset.confirm = ''; + btn.textContent = 'Release all'; + btn.style.color = ''; + btn.style.borderColor = ''; + } + }, 2500); + return; + } + btn.dataset.confirm = ''; + btn.textContent = 'Release all'; + btn.style.color = ''; + btn.style.borderColor = ''; await chrome.storage.local.set({ caughtAircraft: [], caughtAircraftLabels: {} }); renderCaughtList(); }); diff --git a/popup/popup.js b/popup/popup.js index 86ea3bf..2a96783 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -1,4 +1,16 @@ -// popup.js — init, tab switching, help overlay, master toggle, status +// popup.js v1.1.0 — init, tab switching, help overlay, master toggle, status + +// ─── TOAST (gedeeld door alle popup-scripts) ─────────────────────────────── + +let toastTimer = null; +function showSaved(label = 'Saved') { + const toast = document.getElementById('savedToast'); + if (!toast) return; + toast.textContent = `✓ ${label}`; + toast.classList.add('visible'); + clearTimeout(toastTimer); + toastTimer = setTimeout(() => toast.classList.remove('visible'), 1800); +} // ─── TAB SWITCHING ───────────────────────────────────────────────────────── diff --git a/popup/settings.js b/popup/settings.js index 4b98a04..6e8f5d8 100644 --- a/popup/settings.js +++ b/popup/settings.js @@ -1,4 +1,5 @@ -// settings.js v1.1.0 — injecteert Settings tab HTML en beheert alle instellingen +// settings.js v1.2.0 — injecteert Settings tab HTML en beheert alle instellingen +// showSaved() is beschikbaar via popup.js // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -182,17 +183,6 @@ function initSettingsTab() { setupSettingsEvents(); } -// ─── SAVED TOAST ─────────────────────────────────────────────────────────── - -let toastTimer = null; -function showSaved(label = 'Saved') { - const toast = document.getElementById('savedToast'); - toast.textContent = `✓ ${label}`; - toast.classList.add('visible'); - clearTimeout(toastTimer); - toastTimer = setTimeout(() => toast.classList.remove('visible'), 1800); -} - // ─── LOCATIE ─────────────────────────────────────────────────────────────── function updateCoordDisplay(lat, lon) { @@ -341,7 +331,6 @@ function setupSettingsEvents() { // ─── INSTELLINGEN HERLADEN (na backup import) ────────────────────────────── async function loadSettings() { - // Onthoud welke kaarten open waren voor we de HTML herinjekten const openCards = []; document.querySelectorAll('.settings-card-body').forEach(body => { if (body.style.display !== 'none') openCards.push(body.id); @@ -355,7 +344,6 @@ async function loadSettings() { initRadiusButtons(radius, units); await initSettings(); - // Herstel open kaarten openCards.forEach(id => { const body = document.getElementById(id); if (body) { From aa14645d3efeb6873c18edd43012a3a8d9eb16da Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Thu, 4 Jun 2026 10:49:43 +0200 Subject: [PATCH 8/8] perf: merge storage reads in buildDropdownContent, debounce storage.onChanged renders --- popup/live.js | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/popup/live.js b/popup/live.js index 23b8fec..f1d391a 100644 --- a/popup/live.js +++ b/popup/live.js @@ -1,4 +1,4 @@ -// live.js v1.2.0 — injecteert Live tab HTML en beheert vliegtuigenlijst, filters, detail dropdown +// live.js v1.3.0 — injecteert Live tab HTML en beheert vliegtuigenlijst, filters, detail dropdown // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -61,6 +61,7 @@ let searchQuery = ''; let liveSettingsCache = null; let autoRefreshTimer = null; let autoRefreshCountdown = null; +let renderDebounceTimer = null; // ─── AUTO-REFRESH ────────────────────────────────────────────────────────── @@ -212,6 +213,9 @@ function setupLiveEvents() { renderAircraftList(); }); + // ── Gedebouncede storage listener ────────────────────────────────────── + // Voorkomt dat snelle opeenvolgende changes (bijv. unit-wissel) meerdere + // renders triggeren. Cache wordt direct geïnvalideerd; render wacht 50ms. chrome.storage.onChanged.addListener((changes, area) => { if (area !== 'local') return; if (!changes.units && !changes.hideGround && !changes.alerts && !changes.caughtAircraft) return; @@ -225,7 +229,9 @@ function setupLiveEvents() { } liveSettingsCache = null; - renderAircraftList(); + + clearTimeout(renderDebounceTimer); + renderDebounceTimer = setTimeout(() => renderAircraftList(), 50); }); } @@ -394,7 +400,7 @@ async function renderAircraftList() { dropdown.className = `ac-dropdown${isOpen ? ' open' : ''}`; if (isOpen) { - await buildDropdownContent(dropdown, ac, units, caught); + await buildDropdownContent(dropdown, ac, units); } item.addEventListener('click', async () => { @@ -418,7 +424,7 @@ async function renderAircraftList() { item.classList.add('open'); item.querySelector('.ac-chevron').textContent = '▲'; dropdown.classList.add('open'); - await buildDropdownContent(dropdown, ac, units, caught); + await buildDropdownContent(dropdown, ac, units); wrapper.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }); @@ -431,7 +437,12 @@ async function renderAircraftList() { // ─── DROPDOWN CONTENT ────────────────────────────────────────────────────── -async function buildDropdownContent(dropdown, ac, units, caught) { +// Storage reads samengevoegd tot één call bovenaan de functie. +// De catch-knop hergebruikt dezelfde data en doet geen losse get meer. +async function buildDropdownContent(dropdown, ac, units) { + const { caughtAircraft: caughtList = [], caughtAircraftLabels: labels = {} } = + await chrome.storage.local.get(['caughtAircraft', 'caughtAircraftLabels']); + const cellDefs = [ { label: 'Registration', val: ac.r || '—', bell: { type: 'registration', value: ac.r } }, { label: 'Type', val: ac.t || '—', bell: { type: 'type', value: ac.t } }, @@ -483,27 +494,23 @@ async function buildDropdownContent(dropdown, ac, units, caught) { const catchBtn = document.createElement('button'); catchBtn.className = 'detail-catch-btn'; - async function updateCatchBtn() { - const { caughtAircraft: current = [] } = await chrome.storage.local.get('caughtAircraft'); - const isCaughtNow = current.includes(ac.hex); - catchBtn.textContent = isCaughtNow ? '↩️ Release this aircraft' : '🎯 Catch this aircraft'; - } - - await updateCatchBtn(); + // Gebruik de al opgehaalde caughtList -- geen extra storage read nodig + const isCaughtNow = caughtList.includes(ac.hex); + catchBtn.textContent = isCaughtNow ? '↩️ Release this aircraft' : '🎯 Catch this aircraft'; catchBtn.addEventListener('click', async (e) => { e.stopPropagation(); - const { caughtAircraft: current = [], caughtAircraftLabels: labels = {} } = + const { caughtAircraft: current = [], caughtAircraftLabels: currentLabels = {} } = await chrome.storage.local.get(['caughtAircraft', 'caughtAircraftLabels']); const idx = current.indexOf(ac.hex); if (idx === -1) { current.push(ac.hex); - labels[ac.hex] = buildCaughtLabel(ac); + currentLabels[ac.hex] = buildCaughtLabel(ac); } else { current.splice(idx, 1); - delete labels[ac.hex]; + delete currentLabels[ac.hex]; } - await chrome.storage.local.set({ caughtAircraft: current, caughtAircraftLabels: labels }); + await chrome.storage.local.set({ caughtAircraft: current, caughtAircraftLabels: currentLabels }); currentDetailHex = null; renderAircraftList(); });