From 42cde48a38b7f536f686825255f19790b7e12bae Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Fri, 29 May 2026 01:01:55 +0200 Subject: [PATCH 01/13] 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 02/13] 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 03/13] 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 04/13] 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 05/13] 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 06/13] 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 07/13] 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 08/13] 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(); }); From 8c60d2f98ca50eb0c1974eb84d7b24e480bbabf1 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Thu, 16 Jul 2026 22:14:57 +0200 Subject: [PATCH 09/13] fix: escape externally-sourced data before DOM insertion Alerts, Live and History tabs rendered alert values, API data (callsign/route) and imported backup data via innerHTML without escaping, allowing HTML/script injection via a crafted backup file or malicious ADS-B field. Replaced with textContent/createElement throughout. Also fixed the inline note editor, which reinserted user-typed text via innerHTML (self-XSS), and replaced the fragile "is this note empty" text-comparison with a data-note attribute. --- popup/alerts.js | 64 +++++++++++++++++++++++++++++++++++---------- popup/history.js | 66 +++++++++++++++++++++++++++++++++++------------ popup/live.js | 67 ++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 153 insertions(+), 44 deletions(-) diff --git a/popup/alerts.js b/popup/alerts.js index a478543..6a882de 100644 --- a/popup/alerts.js +++ b/popup/alerts.js @@ -62,6 +62,19 @@ const placeholders = { // ─── EVENTS ──────────────────────────────────────────────────────────────── +function setNoteDisplay(el, noteText) { + el.innerHTML = ''; + el.dataset.note = noteText || ''; + if (noteText) { + el.textContent = noteText; + } else { + const emptySpan = document.createElement('span'); + emptySpan.className = 'alert-note-empty'; + emptySpan.textContent = '+ add note'; + el.appendChild(emptySpan); + } +} + function setupAlertsEvents() { document.getElementById('alertType').addEventListener('change', (e) => { @@ -130,15 +143,40 @@ async function renderAlerts(alerts) { for (const alert of alerts) { const item = document.createElement('div'); item.className = 'alert-item'; - item.innerHTML = ` - -
-
${alert.type === 'dbflag' ? (flagLabels[alert.value] || alert.value) : alert.value}
-
${typeLabels[alert.type] || alert.type}
-
${alert.note || '+ add note'}
-
- - `; + + const toggleBtn = document.createElement('button'); + toggleBtn.className = `alert-toggle ${alert.active ? 'on' : ''}`; + toggleBtn.dataset.id = alert.id; + + const info = document.createElement('div'); + info.className = 'alert-info'; + + const valueDiv = document.createElement('div'); + valueDiv.className = 'alert-value'; + valueDiv.textContent = alert.type === 'dbflag' ? (flagLabels[alert.value] || alert.value) : alert.value; + + const typeDiv = document.createElement('div'); + typeDiv.className = 'alert-type-label'; + typeDiv.textContent = typeLabels[alert.type] || alert.type; + + const noteDiv = document.createElement('div'); + noteDiv.className = 'alert-note'; + noteDiv.dataset.id = alert.id; + noteDiv.title = 'Click to edit note'; + setNoteDisplay(noteDiv, alert.note); + + info.appendChild(valueDiv); + info.appendChild(typeDiv); + info.appendChild(noteDiv); + + const removeBtn = document.createElement('button'); + removeBtn.className = 'btn-remove'; + removeBtn.dataset.id = alert.id; + removeBtn.textContent = 'X'; + + item.appendChild(toggleBtn); + item.appendChild(info); + item.appendChild(removeBtn); list.appendChild(item); } @@ -149,7 +187,7 @@ async function renderAlerts(alerts) { if (el.querySelector('input')) return; const id = el.dataset.id; - const currentNote = el.textContent.trim() === '+ add note' ? '' : el.textContent.trim(); + const currentNote = el.dataset.note || ''; const input = document.createElement('input'); input.type = 'text'; @@ -170,15 +208,13 @@ async function renderAlerts(alerts) { alert.note = newNote; await chrome.storage.local.set({ alerts }); } - el.innerHTML = newNote - ? newNote - : '+ add note'; + setNoteDisplay(el, newNote); } input.addEventListener('blur', saveNote); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { input.blur(); } - if (e.key === 'Escape') { el.innerHTML = currentNote || '+ add note'; } + if (e.key === 'Escape') { setNoteDisplay(el, currentNote); } }); }); }); diff --git a/popup/history.js b/popup/history.js index 516e1aa..48352b6 100644 --- a/popup/history.js +++ b/popup/history.js @@ -1,4 +1,4 @@ -// history.js v1.2.0 — injecteert History tab HTML en beheert notificatiegeschiedenis +// history.js v1.2.1 — injecteert History tab HTML en beheert notificatiegeschiedenis // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -201,14 +201,31 @@ async function renderCaughtList() { const label = caughtAircraftLabels[hex] || hex; const row = document.createElement('div'); row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid #1a2040;gap:8px'; - row.innerHTML = ` -
-
${label}
- ${label !== hex ? `
${hex}
` : ''} -
- - `; - row.querySelector('button').addEventListener('click', async () => { + + const infoDiv = document.createElement('div'); + infoDiv.style.minWidth = '0'; + + const labelDiv = document.createElement('div'); + labelDiv.style.cssText = "font-family:'Space Mono',monospace;font-size:11px;color:#c8d4f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"; + labelDiv.textContent = label; + infoDiv.appendChild(labelDiv); + + if (label !== hex) { + const hexDiv = document.createElement('div'); + hexDiv.style.cssText = "font-family:'Space Mono',monospace;font-size:9px;color:#3a4560"; + hexDiv.textContent = hex; + infoDiv.appendChild(hexDiv); + } + + const releaseBtn = document.createElement('button'); + releaseBtn.style.cssText = 'flex-shrink:0;background:none;border:1px solid #2a3060;color:#4b5680;font-size:10px;padding:2px 8px;border-radius:5px;cursor:pointer'; + releaseBtn.dataset.hex = hex; + releaseBtn.textContent = '↩️ Release'; + + row.appendChild(infoDiv); + row.appendChild(releaseBtn); + + releaseBtn.addEventListener('click', async () => { const { caughtAircraft: current = [], caughtAircraftLabels: labels = {} } = await chrome.storage.local.get(['caughtAircraft', 'caughtAircraftLabels']); delete labels[hex]; @@ -240,18 +257,33 @@ async function loadHistory() { const time = new Date(entry.ts); const timeStr = time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const dateStr = time.toLocaleDateString([], { day: '2-digit', month: 'short' }); - item.innerHTML = ` -
- ${entry.callsign} - ${dateStr} ${timeStr} -
-
${entry.detail}
- `; + + const topRow = document.createElement('div'); + topRow.className = 'history-item-top'; + + const callsignSpan = document.createElement('span'); + callsignSpan.className = 'history-callsign'; + callsignSpan.textContent = entry.callsign; + + const timeSpan = document.createElement('span'); + timeSpan.className = 'history-time'; + timeSpan.textContent = `${dateStr} ${timeStr}`; + + topRow.appendChild(callsignSpan); + topRow.appendChild(timeSpan); + + const detailDiv = document.createElement('div'); + detailDiv.className = 'history-detail'; + detailDiv.textContent = entry.detail; + + item.appendChild(topRow); + item.appendChild(detailDiv); + if (entry.hex) { item.style.cursor = 'pointer'; item.title = 'Open on map'; item.addEventListener('click', () => { - chrome.tabs.create({ url: `https://globe.airplanes.live/?icao=${entry.hex}` }); + chrome.tabs.create({ url: `https://globe.airplanes.live/?icao=${encodeURIComponent(entry.hex)}` }); }); } list.appendChild(item); diff --git a/popup/live.js b/popup/live.js index f1d391a..d70516e 100644 --- a/popup/live.js +++ b/popup/live.js @@ -1,4 +1,4 @@ -// live.js v1.3.0 — injecteert Live tab HTML en beheert vliegtuigenlijst, filters, detail dropdown +// live.js v1.3.1 — injecteert Live tab HTML en beheert vliegtuigenlijst, filters, detail dropdown // ─── HTML INJECTIE ────────────────────────────────────────────────────────── @@ -384,17 +384,58 @@ async function renderAircraftList() { const item = document.createElement('div'); item.className = `ac-item${match ? ' match' : ''}${isOpen ? ' open' : ''}`; - item.innerHTML = ` -
-
${flight}${match ? 'MATCH' : ''}${caught ? 'CAUGHT' : ''}
-
${route}
- ${dist ? `
${dist}
` : ''} -
-
-
${altitude}
- ${isOpen ? '▲' : '▼'} -
- `; + + // ── Linker kolom: flight, route, distance ── + const leftDiv = document.createElement('div'); + leftDiv.style.minWidth = '0'; + + const flightDiv = document.createElement('div'); + flightDiv.className = 'ac-flight'; + flightDiv.appendChild(document.createTextNode(flight)); + if (match) { + const matchBadge = document.createElement('span'); + matchBadge.className = 'match-badge'; + matchBadge.textContent = 'MATCH'; + flightDiv.appendChild(matchBadge); + } + if (caught) { + const caughtBadge = document.createElement('span'); + caughtBadge.className = 'caught-badge'; + caughtBadge.textContent = 'CAUGHT'; + flightDiv.appendChild(caughtBadge); + } + + const detailDiv = document.createElement('div'); + detailDiv.className = 'ac-detail'; + detailDiv.textContent = route; + + leftDiv.appendChild(flightDiv); + leftDiv.appendChild(detailDiv); + + if (dist) { + const distDiv = document.createElement('div'); + distDiv.className = 'ac-distance'; + distDiv.textContent = dist; + leftDiv.appendChild(distDiv); + } + + // ── Rechter kolom: altitude, chevron ── + const rightDiv = document.createElement('div'); + rightDiv.style.cssText = 'display:flex;align-items:center;gap:8px'; + + const altDiv = document.createElement('div'); + altDiv.className = 'ac-altitude'; + altDiv.textContent = altitude; + + const chevronSpan = document.createElement('span'); + chevronSpan.className = 'ac-chevron'; + chevronSpan.textContent = isOpen ? '▲' : '▼'; + + rightDiv.appendChild(altDiv); + rightDiv.appendChild(chevronSpan); + + item.appendChild(leftDiv); + item.appendChild(rightDiv); const dropdown = document.createElement('div'); dropdown.className = `ac-dropdown${isOpen ? ' open' : ''}`; @@ -488,7 +529,7 @@ async function buildDropdownContent(dropdown, ac, units) { mapBtn.textContent = '🗺️ Open on map'; mapBtn.addEventListener('click', (e) => { e.stopPropagation(); - chrome.tabs.create({ url: `https://globe.airplanes.live/?icao=${ac.hex}` }); + chrome.tabs.create({ url: `https://globe.airplanes.live/?icao=${encodeURIComponent(ac.hex)}` }); }); const catchBtn = document.createElement('button'); From 021ce9ab55de4de6665f4988a7c64bceb13bcf04 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Thu, 16 Jul 2026 22:42:13 +0200 Subject: [PATCH 10/13] chore: remove unneeded web_accessible_resources, encode hex in URLs offscreen.html/offscreen.js were listed as web-accessible to chrome-extension://*/*, but they're only ever loaded internally via chrome.offscreen.createDocument() and never need to be fetched from outside the extension. Removed the entry to reduce exposure. Also wrapped ac.hex / entry.hex in encodeURIComponent() before building globe.airplanes.live URLs, in case of malformed or unusual hex values from the ADS-B feed or notification history. --- background.js | 2 +- manifest.json | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/background.js b/background.js index 5d12427..c40c054 100644 --- a/background.js +++ b/background.js @@ -211,7 +211,7 @@ chrome.notifications.onButtonClicked.addListener(async (notifId, btnIdx) => { if (btnIdx === 0) { const hexParts = notifId.split('_'); const hex = hexParts.length >= 3 ? hexParts[1] : ''; - chrome.tabs.create({ url: `https://globe.airplanes.live${hex ? `/?icao=${hex}` : ''}` }); + chrome.tabs.create({ url: `https://globe.airplanes.live${hex ? `/?icao=${encodeURIComponent(hex)}` : ''}` }); } if (btnIdx === 1) { const parts = notifId.split('_'); diff --git a/manifest.json b/manifest.json index fe30b31..ca049b7 100644 --- a/manifest.json +++ b/manifest.json @@ -29,16 +29,5 @@ "16": "icons/icon16.png", "48": "icons/icon48.png", "128": "icons/icon128.png" - }, - "web_accessible_resources": [ - { - "resources": [ - "offscreen.html", - "offscreen.js" - ], - "matches": [ - "chrome-extension://*/*" - ] - } - ] + } } \ No newline at end of file From eb6e47273c717f08e61107e3d4c2329a55652fa2 Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Thu, 16 Jul 2026 22:58:54 +0200 Subject: [PATCH 11/13] add: added fonts for offline font usage --- fonts/outfit-latin-400-normal.woff2 | Bin 0 -> 14032 bytes fonts/outfit-latin-600-normal.woff2 | Bin 0 -> 14140 bytes fonts/outfit-latin-800-normal.woff2 | Bin 0 -> 14048 bytes fonts/space-mono-latin-400-normal.woff2 | Bin 0 -> 16520 bytes fonts/space-mono-latin-700-normal.woff2 | Bin 0 -> 16724 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 fonts/outfit-latin-400-normal.woff2 create mode 100644 fonts/outfit-latin-600-normal.woff2 create mode 100644 fonts/outfit-latin-800-normal.woff2 create mode 100644 fonts/space-mono-latin-400-normal.woff2 create mode 100644 fonts/space-mono-latin-700-normal.woff2 diff --git a/fonts/outfit-latin-400-normal.woff2 b/fonts/outfit-latin-400-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2c401f59f92b53a2d43643a81a4dcb6a35c6947b GIT binary patch literal 14032 zcmZ9y1CS`&vMt)SZQHhO+jjSA+qP}nwrz8@ZCiiuf9`w#o}W<>g{V2Ba*mNBXGWHr zyciPzAizJyHV=UC?>PYDpKIIyJp1?lzYQzU5qp^pCm0XRfL~cfP=yX41O^cTDj<~S zTopRV9srOHhy*xH2ZR7Lm=8Qu4h9^%Db#@atkqqW)04(4>I*>T6k4 zJC&`O$LOSlXBcab*^~Nd)bG+K9S%OQNMwO{>V5>AT;0G4+{xiyzab~(R6+<&KZ%)b zCDN;5)o8Wrm#W>vC){at4qm|or50pmi=WraHaQf67Fc{aqzTm^5M)?le+d!b>*;&P zcgLTBolMnx3$uU#gG&YLCxg%tY7ROkDq#;*KgY;tc%D$ zm6jX?cr*DhH+%7l2N5PrKfk~57N5ENObh{JcqEw;HK-&A8<|-N*D!uzy<%zE{fn%XH5p{i_ z(~v%!l1B$)`;v>xhC@r)KbJhLK^VigM80l_+;JEkBTsR-5 z{@$WPw49&H#Mg(<=;_|lHEsc>HY6jrP65jr+#|`}yhaJZFqCjNS11i~QHR@T-!%Yz z4b%i{f9L^g7`bw}+%mlW+NhO2t6t=+7CH}iRUd|=;oC2RZ-m&md$_2!sjReoFDIsI z^^}u7_X`ir2NY+#vN%U4WRxTr?m-YfgtC>z-W~hzkH86;BaXo>E?0Q`{HEzQHJMAG zMR>=ilKvGr*H`r$)D)ffLlx9_OsL9Af2O#@P#-2}AczL){nh81+PXXg-Zo6&gor}5 z;k!1sux(PwC_NiAq}}F-X72CHXmQ`w)aZ9!J@(_Ua%pE zFJLHKa4aY&B_bXSvU*l{Ml7F%tXv06nJO@XRWSmL44FK0Ef=G1TJ3}Sd)m^67!nu^ zfX-|LHr+p?+l1X!*_Z@DTp`@7j`4=DzbZI1eIgRAMk7Kh zwbHp`QavB3gi1Z>5`zG?6j9mm<`Iv{0Ad1NKQ5Y~J&CgCTu|6gT!59lp&l}i8O`AK zjjJ9S8#8{bW_K)U%kRdz!Y`%G#Bb6?rrvfk`vi>n7JX#luSkB;0o570Vzb@xy)PCS zlou6J_okrdBFu80nzctx(>7`YoG6ZS?|m(mIj`RAjrRq6(db*T&5j82u=^zw0EO-3VSdbK3xk__IV49@R8-Ze*z zCkipvp?z3v!ou&wdfvUXw{5+f%X4?w_PG{1_x8d|Thj|$bERpE$*hhOtg5P#-scoW z#X1p`op-OtaVi;+X5e~wD8t)Yc%Jua-JG>Nul*G2X-@E!74ngnvv5GYnwoQIX4wEs zsLR6SSYZ>TrhV(um}f^`)#`)h%voVdoTzyscpZslyOvj5Xjjy~FEKMl%@B%3V>F0z z)MTu97euM?q2)+Qx;k~@w!8=({J<9C9zVQrsyHL1FHH!&nyTX7o)m^zl zVC$7Rx?o^ZXWsA}mx)5H8U~0b>@3@#$6kf0i6xHej$Tg8aXBMCL;^7ayG^Hvof#cz zgJ4Q{d+ztqOS5a+BMX*%{K9(F&{2;O+EoF}GmY)IzPKQcOk)hU2{Wyl6TB8@(lv14 zXxPrC5hp&HYH2GDDv>u)`V}kdPxNaw=V9A!SNwCE)oXN}Z5I^qWf&nMAQy*v8`X39 z=R!}dr}=j!Z?4`jU`inV11H|2vM&|LXq%^SYD(?`TOhp^f2_~&z?>Po++JwyOZV&` z-C%7R%iDe}u$nB15^C?yf{j<=f?oD_nrekAl-Ifl1l+R0pu-&_1%-VDXH)5+td#s* zC_O(&5Q3Fqp~Z0cKA9|{+p*QV^|D1GrWr%JZ7TuV7D!iq(1HuQC)<$jDz@{u=&ZsxMD*`SSQg)lk10i_H;cz4~(Ne}SG0K^lJGusL;Cb2x9_Ble z1sA^Aw2y_t3_P-p#eAT)et%Hp;i6eC`q}3ZM*BI3fi&vZ^Sx z_3_UK?1NY1`EC7~tiHUb_x8;Man`_FGT{9iabSx~u6%DZA-O1bVZhfS!l zALHHo=lk=5vMlh_)^^3Pu+OH?p-|e2Y?{$>Nck&{vZeVBl4HNqGylJKFMiw^-tvkfX zHPFZ*%Qd~lX!OzqJ?cj52`NI6Nm!Q|OZjbdV#Eq(Tncs(| z2Pa2o2Qr3C0^7s4UU_-+=H4*AkPuKl-s^XXN=0L$Jjm^af`P(8g!36VfG8TQP4@Z^e0TI+q7?|03^429HS5*mmZpBPf3z^q5=~mu>^#r*PdFL7|(qqSNz_;?)&Y zl++Yg#+~iD#+N{}q_nwKjxV2=#`^Ep<|a7fItUIUZ%4_b^dbRa0*yEXm4s)Q0lPSS zQ~fC>A~Vr0LoGLAT|Z7HW5%FgKWj1on4zh$xxvlhk{}O=?C2!`X&_L*#0f}CiWe|w z;Pf#A1#rzd0+J?LCW`XQi>nJatze#6#*5_>8gs~|_snLpJ)mt9q!$-gFlvsbDVPZfBXGr}YXJLDCA0pmCK|)@JN<~eEdC=Ry z&I1V*FmM8rlM}^^>p6G|8s8@=Jy1|ml2uh$qhA5^WYQ5D(1M1kw;G;s^^+`pcN0Ol zp|c++L`kmGySxUB4_jl)D5C$fxZpJOl%>NPOWb9*-MP>|uXK^RV!8pkE3QNO?3Vo1 zAM}|d1-r_%ICYAE6!kM3Ujxw!!pi1wi^kgwH>I%R55CQHzYl(O0H-u?)00;6+xVbv z7|bzuOfHK=S5C~|E{ zCj>tK4IqqY{xQu2H3b!ARk@W3S6c29EX-VOOBLeo`VUn&I2cgK0OGj9ISDVNWD%nV zPA?M}$b(nyPy9HQxOMD)JF*;^dZM_XqNJuME9>%7IawQ#;bwoKe1*APDSqHtWvu|c z)GiFnEv)Z$V*8A1@j2;{%ggD`1?Hm+$hm)jfT2Bzl9Ciw%)r523L;{B(*ID2V}?qO z8DfEJ;mEQeC-(dt8-tGxsDUB%!X6A37hZKUCDUelP=3e&CKRm_>4d602u&)>wmeqkMNb8>Q)N;Z#|VZbZpNH z2P4ejO?_g|q3$$Jwtf2JIf)TJ6-r8uc}JSuOWV%wxw!KF+2Q%Oc9qDiZqKwq5B2Ug84(bp`oeq>E4l* zp3d6@1Zie6XpSKeu)_cX1x&1fl+^#gsui60f8{7+mNrVGxlDDjwF8-^CN{uu8=ip* z^YnM(Mj*@M>#!&o5m6$Uu=JOV@D5x%RjfJbsHD`XGgo8eFL@2=*|pc@2@JCWlLkcO zK8Pf&3uMy7=!!dtVj?Ol?JDe2Bkr|Hm*e4mVdQRR#KiwkJZbb$`2zKU0)=+bLPZ!9 zS(#2a2*m-37zAPd6`cYjNlHr=u&AN7XdJ^Y`PWr6xG@K;7_p)S3l^NR{dSyxa3Lz` zPfbi5JU%))LNJkyo_;TXcUtW!(`2tFY}mm2mBb%4hz9K(B{56YF#LB_KKS6Lr%*>F zM)I{2c5(kVv-B~;6)xH>$?cgq|A*l%D>7AOf@D7WPeUvwVguFF8Ey6iipN$>N(dl= zP`K3Bq0w2_Xk*uuK&PyZetPULTU=ym-DOE|(1MKF1Qlj17#PsapS)%o8XN1J938!L zMIPIOdV{>9Z~qqFEK-!v018mY^F%~MbkZVx*El_hlJOGxd@k?F_tJ4cGuHyx9%q9x zh_&UBAW>&96E3pi&bm#RtGy+(t18jnOFHC9=n5F5sI2~>skP^nm>6LMQIw?rQB(4@ zRpC1#Z{d4l0t#ooYT82TO>ScsEE9;({~dxLoF1T}q^78gec?THSARTMQ83fl-HMeY1PLz4sN+XFGx%_)3P>1P0PBd z#&z?uJZZM`tTOKhjF0mTZ!s>m)^Fcy?R|j;eZAE$yk*IqF1Ko^68Y`sazf$8KOi_d za1si3=j>gc#s)`c)%^`bWSFGbJ&#;cwZD#s9RH3@W-IN8tch6b&Rk0`r|BR z&WgYLUOz^ZZHvAz16(^91b_hG4nQK2B)~ot3?h&q9A>Mznnt?;`d`hK_@7Ww7YiWZ zd`J0DhNeg`=F(=SGGjE~8I(rvA=Er;@LXDutSgTDXRA@4jT%~&uARK)!Q=Qi9;fp< zQ3zXz%2Vk-F=9O5rM=p${FrDFJ7C+^KnAb;e`y8?6D=G+gVO(>K%QXz32Hl;G|Jyu zv19m)(<^sd6gzhfzUH@4y|n8x?V_lp2y9_-zOzp+$=>ade0BVOZOr|lW8CoU&2|}-Sx+ZbeX7$+ z#+htku#v_l5e5J;^1)&5TAUe$AZMg3<+FO<2#nU$fQ7QrQCc#e zm7Dpn4!QLaw?KQ(5!d>yMDLw*ZcACB2V7He*?0SwxS9g@*UnP>@UzSLQA^LS3hw6U z<<_bVi$H+^Q#n9nju{yy+61+?LnM?tcGEu#6bqCwr4xim2H1$L@C$@)#?mamQsXa0 z!x*U90DC8&cRCXDWn3>}Mj!Ro;7izu$IAk`6@K^$(BD*P0S;W|2g1x(w?npQ;=Trav{sA6;Z&n z6$=J2F#UyQVS>#e#X$J*PZ9>PFiHbyAcV-HEd>I>%8rxE8^!ea7Y8EiE_@V0xNpnFa7UAOeqq zQnMsh&O`+wMgW9~CK@h(6AlZPE36mVFUw5Ld1h{igb7kiJ2I1yXgMV8zB~wWZ_TD_ z$+Z=$JS`5cC~hFQA6gizwG1LbO6e{HWWoBMKJZ(326zEHVgFzLRh=~w>w}enmHidH zMi=0nyd23-(&3(^?(J8HKZhT7FS>vrFu-W%%s}DLXk$cjZNtAaJB?E=;Jo((wCvgT zi-7dDPQfFDTi5eN(rvtULT4Bn93a6W!zjrrHVDf)?G9xnhlVt~_`P2_4(pn?IIl$j zf`OPJ5lFOR)g+WkC6Z|s3Jpjlld9#aWqLuDa_7!kdH7c^VW(+(qGm$uF5sG-rJgHW*%aZ^}v*}=&$S+ua+FWDUKGYRbSdZTxT zmD3;rqZuaM5imkBXmmowB4qbS!ISaS(yiW{JLAelN-0*#C>fKQ0n%LAP6MK+gcNaH zfL{Q^>I-Q6AOHN^$_~2)ZrPJa_dpg-kW=PcU^gx+(XuG1d)3c|(bw3ePOyxRize^mc{~M^1hE_x!(>_txD|1Yw#W^D4GZVQf^xF=G4?ZUM$Of&*4*Ae1 zlruj(bXHeK2hBdp7z60>yDA+XCG~zcguHr>=u>XsnfD5OWi^;`I0RPP#3xuXdvFLp ztnM4SKlZA#4VK|o-%ApRw$-{$(6O>$HkV$r(-7h>qOf$=pMT7KJibFmIS?>d#bn(n zD(G^E4DT2-+F;hGiqT5Zq7jc`DKX1sl|1wfBa7=MBlErv@@C4a*=(-0TLx{hutV!$dP( z+LI$B;yU-hU9YA=(wYiN!O>SorjBP|*89&1ze)>J=gDq0%1uXn=(e*HM(2GzapCaU zoFs8alN$qG$0mikriCli&6)ijp_0b~{>@|S%x9p3C7ZC9!_~30x9(#?=51Da z2P4ZZL&4|}66E2I!Fk$`oJ%16eOfI=v{Vc?ucH8`&L?R6`SLyZwgK&C5TN?<)7#et z!3BG?uTy=*(~B2gtKU#BsUhbqk5OQssUBj)-^@2hBUlxA4Q zGfCd99na#r*?406CD!Q*WUIa6TM1lawOj?i+Jjb7;*9#QWEEcmJb43PS%ClNVbt*Vozg6p@*TB2LSy6&5bda zW&naPl9)mw&vXE;4801-9;tib4MuYY^dKOQh$(xw$xTY#HH;>S>_t9v;9&AS%OO@# zq%;A{m?+O|^V^_2HpVA^t4o~qT?hyp2Qr4U94XJhCt;Zj-?gCU;ct-khW@ZLgFIqp zx+HY8@$HZ^1T{7Y>>a+9FbV8+m{%#NY`|r(1e;%$$eL+{Hogh(6RS}qX0tRX$YFip zNLBU8leGYKG0f|?e*77=q_mzo5M(8djw72Y5{{UD`~k2L@Oqc_zk^op@YQP^V9u~g z1_>XP6x-XDW^s>nP4wv7`YhHnur$XF_AqwHq7k$}`Ol@(M(?Fx?Et$OdEo|QrvSxL zV_KsTMVn1VDs*yUQ_9eh=iv=8`91g(xc%FVzNy6sC%PBJsrB7|DOP*cheD)%cjK&n zeL*n32vSH-tt{cXwxuWq7?mr$?@|CH3Cg=gfv5OLM61QigodL(ArUurk8ww8>^Q^4 zUu&WtEVt8CT<;ObR!K7sg(cNicftfr#dh8Cd@u2|s3q9t1pnRu_Y)#l8?q(6oDv1g z+%0A+p9AbF>FZ%2S~5|8jFT$bPo-L1QS<_Fuk^>uUU#g|D#3bvv6StfJobl11OnTj z1LE-*=uucUPI;VElp`P%6D4Ym5`NOK?F8WIPZ$Ic#XH)|%y| zuEKd8ng{?(kv-}Z&>c|^TP!XXX0=wJ*gI_HXL5LxN>YlZfQJub`jJmna6WGJsYd~!oBsRh*30ny{foQ5wkyUb9A(zo8&1&|Nm08G))9uj z66pS|9Cd=OOiBJjT9Ml4*2=VXcECYbaq{U=zxb4yW@H=LHoMk6!TYCc!OUyuhvk(< zw>~`)t&H7gimj1uQHpC`2OLRnQVoq}h}hgXrP9Zo#EQN`>XHQQxRnEKbU8cu_{s@| zRYgJ7rAt+nN@JNL9Bo_jEf{|WZa($N#5;OeK5i_0X)Jsl`AfM0_eBbG11*Li>G%Q8 z>q%rb4}LH;W+J8wH+`@fwQ zqO-WP3pkeaEwp#cA@yX>-MSc59ciM7Z8}uQCr)j^ksWZJ=1ww$i>6etMbPC5@7FPq zA74MRph4!j$6yt|dIgPN$!{~$oIwsMIL3_Z{lZMq^>}KNGioCnQ#IL29+sP&?|985 z-mX-lks2?iBoD%lOP15b>UV6(){(B{S5`YtTzX7PGa)yzqKSA3SA-Z*l10=kkI~0< zyaowAZb3vuu=e(538uM8@uqR7sx66jV1KXjW6P#D<2c~FPoY1>cx!?pmb!{NN>x-v zT;%G!&5A&^WvSLs;rqJ0;-2y-a0{%niU*XWmWuh0_YV&``!FolK3HpmOj^FM7RD-G zAl`BADEHM3R6y2lz+RL_`;l}DE38vkO;*sVG_cnFrgw|{Jjha=!V_Ml=>U>MilVnC8??t$oiku!A&v`#L?RA)uAbfPNsX!T}uyJ_--< zk%GK(wKIEVpNI>CWzCC6Bs5^AM+*u*3LoWEM-;NIz#l5BD(xZS9`WJgtc%kk-ldLx zL+LVSH81Hq@xyo)88OT)Sg@l|=AC?T<#D21mu3Lx%Go$nz2GHobe;SO$MR-@H=-i1 z1pW6he^x3t$G19Ft6o-9SHJEnkVW8oDz1TV7E}Hyu6Iqo^DF$GR}>AMN-!JAV6&m^g5%*xGp#2@KBh;k6K*h<8WNLOiVVfLX{cN|_7BShCB3l~c8+7GZn5dq7BRj`r4hR`yRZ91xJrJ%G{%jp(O zMw?-;J1-Z0E+6tRksaHmbZ`prVdC>C>byzI9D^AcjJ<`?%3Mu%K-af84k5dQo!+^C znJ-T+?~vwdf26B(R5p<`d#H>FR-`sflooxS9r5 zCe0d1NYod*zt88_zLvelssA(@;5Ys`q8+D2b`6T%%5Ptzdi}BxHz+lkG-zWz=e@4) zfzm!&xGmBNB3T5(PF&PZl(Pn*uWl~*tRrx}%zTi7lLq`K6YF2Xs$z&(X;5%g|(@zf7|&}1{@twIUE4E_S1k+a^gmT z`h&h~wVynir84=WTNS@;tx}kbuc>A55lfq#;NNG^W~P{J>swtDBpls6wT{ulvjC84 zM=vvT&9;Ce03;7)ME|*lrY6+(kz5}3R83R;yfp<10@J;vYQF}0(?xI5lNOYQcT<_o zylMYJ(fA0KyEiF(ThP^Y(C)WpkxSaiII7RGF$W)IO} zGi=T`V#M3F)tvNc*e1S1F@IngDr}e|%Iom)Bn_`g4NXOT^2sx^@=)q=Db5ZS6_xJs zt00OGe`Q=MZ@c^Ld+&3o)E()V#W=I2gIOg3TiS-G9F1g|N7wrr-k-PPH7dwJpJDBn9V=m}oIALbjdDI2ca z7iP^3A+tLRasa8}cnE-vIcjMy%CZTq5^GbrAR?RzfdgqP7V)XxBlQ%m^H0+}Q-hhR z9nIyT6J1RxXDE;tQZl%o40%X|U(K44uzHAMn>s{^FVHf_?od z)Ct5(PLASsvHVtv3cg}F42<`FM9kCPLIDh}`9c$-(8V*om@p_{wLe)8d;zAIT-C|y zxm$^~Ovw*o(7l-xotJ|HgR?#-jy#3~zmk1#k0j5+)n3o3relLA_l1jrv(!OF`C1>C z!?}~^QKSwCAG*Wzd)BwR_@FD$>=oA3{`p zBNJ_MyM9H4D%9nJP|Qvhkcq(|W9mBQC}#moW+ z+eJ6%MvO+{97Q4sr;AMjsm4Ty|BiR>} z(uDY$EKxu3OI3%aE!C?AX3Xs|!x*lw2Dwk+_8_t^V4T+POUw7MsgJm@XMK%tjm8V# zb0|}Tu&E_ch2eezxrLwr0esboW`rfn##sg$5$q=f(Sd%%IJVQlHFKu|;H-Cx{q9G? z<)Exd7GlV{g4Mq^7-K>J(S4}alJo{MX49*x%u&d9e}gw*>i_m;@>R^Ak9fLG z#HN0mr3(w{guHNjposN(@OS7T3tgRHIH(+-pUIf0J?z&mqsC;ng?wlEB zn}q?r0lDwTO9Y==mS#x{GC&NW=n9_g{8;JIWJ@_;eMaj1YQ2`0vaWbW1b6y#arc_n zeTN(m_PAL46}1c}G1Wxf5yYVTZYfn-#T<2xQFiXqB$2Oq&)U9G6|p<#A?cudHl54X zFE%|lgle>NbA7oRvph5ke$)NwtF^4r(4l`~ff(0<(r=!m&3-Pdvx`*22HDI(ODSxB(Ix|mhX zWTqiCTtdWCPI2amkJmzRST}VfFQ$q%ibGnT1{R)QXVdUVH=I0Skmi=xIeT9oypZ~B zJ?sCgms{9!MQ2P#7bVA%r-ka10Ws#|x}gjflF}*yaW&%0WR+BzLy(fZE2*BlMUQst zB|(+5a|-|Xv(-O#RdM~;bTK{>D_gTud*4-nf3qfMGCB#*Pju>r`+f|^k>NezrH3}7r?INhn zGn$t=Z1FUE*Kuy?Jb_j?2fkYC=_(S zycO(xN)yQM?Vi_XEk((QH4n>l09OZ@n_p3@KRN_o9jANmad^eO6u*x`%2d2|6UWtu zG}hR2?D85Ji$BgwA+#SU&5W)GO-oi0#ZKjOxG}a6Z|o$5VF!g`!g_BnKxmv zmJl^+!gxV6PNRZDhfbAU%bDD&51cI@4bZr`VLX3CK?vIkgUi7e$6xeZmV>oAvPVja zL=_ot@NNR5nLSq$hFH;EA%!9p%}3Dlxg5@Ejm0*BIPbu4u~w(Ho3YnOF9eaP-Hc8d z;4XAH2V!CXTIeTom$LyU4GS4RV;wKyNDy~JQnj_n(N>UEYi|THtaC&`Cc3gEdQIFeielmzt@)BB zsmGEl4^2IW==&JLjG0V~E%mt^e0w<^c6yyFPTJo*-n$`&fH;}s4+l4W_?kB_VD?m9 zRBru?YDQe-b`_5o&}yM)U0p_TEvqP_3QC**9QJcuvQd~#@-dMAErP0Ut!;{DwnU%w5x){D+`#dCdySaW-`(#dyu$}k95#wO zjr!aao?;xatrDv{(9YMNZF9l+Rap&M?(WIeFuU>An}{zGPDr@Dgc*%qs~yaUN>isx zG@8tHhVHMl!(NQ+#Svi;t-*UmJ0WtvuNa% zM~4(WPw-Q1dY(s+XC9rLpd<3op(Xud!TB6mr^<$obq@l2Q2&efDX28tKJuK~{oMO~ zyr>X+@KrP{-4pLouu=VN%2Y&NptDyR^7032ko^lNkliqI?6=WSx{BmK7DvBVD_@!0 zg785&(wA~4Fjeel}`}#@NJ^tt_{<( zK?JL=uM4TBf;0}Zoazm6@OsyKn)3d(X*3@i%LXkYu%YQ8L2DKvEqaLccPT@8Q>fx$ z$&tf~ek-I$Z=LX$7bj~gb2W8Yz@FS2k7dvy;<5IrW7bE1Rxg6C7nM`J4YVS=%Zq2WXnxN1bW3jCY|u^BhGQn8+KgU6!-4v{u6tbjCv6Juvi%z2zs$pRRN=Cza~qtT1j%jma=k_3 z0%+TTs8g`>#K~i&Pc+Zf1L)l~p*_#)j{-iSwH^}yV>#GgY%w+u+3b-HFmM9GLPRSK z-mzeUn`3;8m%;4LW3;pQ-cico|C-k4SVGr9n5D`DNCF>X_B(f)c51@{uJhPjUPB4H zTh$XWrdz{2Ve_}o+9`~}wiiqa;QQ~;fTa}h3LMK+L>%`9zp}V%UyD}zxn&qeqq;-D zAI`l&|9n{Z0vG-MJ}pr_Qk^&%8@xVyK?@vXZ~~O{In!oe`c^FYV@`d-r~fFV1f1eu-@?fUx$Y2icY%s%eC4995Dp^y*@aft%08XW<>?y z^sn)8(bhEC%%5Fm)mfFr&qe&cpZrLvG9lyn-YFP6Q}MtgFtL0hz%~K#_?22Hf#ryn z(RT)myqLJ=LtXHp6HOr-gUv%KvNll_X;r~R^8J!$ybuQ<{}7XQw+V5YndJF)Y1A0z ztpgh8)v}k7T;*@5)zg)$G@9SV#_@q6@Ft%Yu+{uwz@HeUhne=6keU1$f4UMl+vp*7 zEdg{axM~|Ja`i8E$g_i0G?^cAu|sh^=XU*?;DTvCHS3aD_-TS5;INC8_rKY7T&|OL z>nstO8q$W+vvrm>PfE09s!Uv0tGLsz_pA-A@*XRcggG#ParcL{7=Rx2|I=)4U2dJm zJ*9oX2DMI;>dB~50x-8HCc@ru2#pIKZ5&W=dp#r=0BJ06_jAxJKbj70l$q~yl94f{ zRd1Lk7mBl-mVa6aSz45nY9+|9;!DQ3xYOJ&zg0APh2iJdc~Q60H3rCw!~=L+V1s;+ z72k`!W%Z+bhz&~CElGAnU93!^J?KH?u}*`-^EeyBmZ48CL2YgU$lta~o&pdY@re%b zu>kD4v>tfW5~^O6KDz}(3}=z|ZhhF^FUdJzmk!{zSG^~)%k4jaa5p4rqgd`hFP$Zm z(s@Aw3b!|V^)4gA@*)GnZ9o>$r_DUULKC>C9BHPs!8~oSWK4#r zTa?S6qff7|ySHjlj#%TVxL?>w99h(1j3J9=97%EU35%4RoTWXIBUxaitTL`jE?K~( ztBCssBCmyvH}w}V64g?HmP#nW*R|#AO1S53oF$v!&| zSSyCig`@+7Ud{bprM+AzI5%9jb7iuV$&Z>`(gf1B@Mp6Pd7)85I@#olN9+AEgz6lI z=>Te($4!jM(qffs2q%`2fA9GH6Y$BNouQp}j_@7M`cR9mASWdLeG+6g+*B;#`*SKoLP=IiEL?R-X<#}HsVz`P(%_f^`s z?V@`+0THye9ZSU|!~!C$X7m1b#r7}xhhLXtt;t!WcEdRIi#fY~7Co zO#->(60NdQ%ejbN0fx$I^ovPPLn(fJ3v{gzPeGbyS4W?&GpzzGX(_2Tmb&*o(JJWl zyee6unw#!rzPQ|8txWVa<8(Cc#+gi`xSntV(U)w0=bnvhjM%w1;3VyWA z!J^L>b!Xl_1bgsP5*c+Ql8@Ujfw8^bc-GyH^XN-HNBcW1ZhO*db&GbYWa|M+5#CTj i^GN2Eiw0-FKnt`bA@6luX5k?dn_FV&!!+Ok0RIoAQ-EMNC zi~xWD{~8-N0K$LHfqwx2SU3Os+JE~0Z^H@#!d@1{36=!Y=TlM^P^JP1fkA|T3J5p8 z6OmW|3(KB2#1qM71k00<(He-J{2zn@3bc}-!U4tH-Okk+!wrIzl(aTX5VW%H@|k^Z~& zH#bgY{IJDdpPq7BiB|K`7&l$XNuNXB3EZDP`W&)NBYu<02PpATRaVJFF7c+xCGqMF z3|JpQ0)Ym_1PB>G=t`TyprdymGpqpMhp+`Xj5Orb!c}f$u{=>pI7$Yr+p$sm>vyj- z@22b5Y$CbCgHL}#C`6XVyzwUx~=U&9~Lc>b1TYwTPz zUl@2Syz<0P@xOmwC1AENRqy?+FFM3c{m~z~K83rRox+*WeVI)q z&iSa;rJl;VAGG?Eg7FAiv7b8%|S=F?pRPRobQqVy5n|IeQMp?*@#^VzwTIS0GQuj-Qy6Yh?W>4sD^;)?416aE(Bm;k5Ui; z+|YOIz&==5IG#yOTojHHlkiV~e7!z}G6)JO7ULE0NJJ14OmJc`zlCEy_q%)pQn@sX zu|Ys}sPQC`cC{R8i=|kjM^G?Ay1%(*i-jAH7f2}vDS_+O&?#6qQ!FeSARg4bmzVo% zwy$ASfo>}>|6^GIJ3_8JeqE0n9|S#27tmEmOe}mu08CRDslE7!ND(PHS%1jpCD*e_ zSUzeOyv^2LVRsz@7J@W8iFs%cbNvw+@e7e}_3(ZKT-0EiI`;*o1jHOIPMVt%d*&)@ z%O9B(p9gK@BSrSB5rNpSff)!NwvnMN1d2GV;0Nk#2wJp&Dkg?BDsWo6H94kf@uFx} zu#{E(rd0uTHb%wPB38B9)k;mpr^+@{Wi6k=flO7UszUA)8ZAsIoytU%It&p?ZPL2k z@V|mcC_mIjyT=-M#aKv8@5N2&>ZMJ5pN(93#zw0qB4{SEp6o7WgBFrH-rsIY8c<2- zQqL2SM1}T1VJOz}8PU=t`*CWy?Pn?Ew;czGrsTK{@SB}7^KaNe1_&`W)%7V4>?Bct zWr%;nY~10>sJ~Wizk|DsUB>U(hPvZEPtv5yfV%0^6P!SX=%+v1_3G8(7z~HQX$yAL z!&+H8+A;q1*10W@neu(Y8h`wy?Jl0^raUlcO|s#F>m4&l`|H12+=|9xNNzDr6f7~+ ze;WLtP^$s4m=~QVkI3W)r=QzIq)+}08Sgc}*kF-;EVYu~3@0rhC5jiMGG5>8qc zj#uYJU6(0CwmV_&vSfhcsRT|{T0q?nR#d(fBEjmpPG?q=$Y;%P)K$KMyv?FE#-II^ zZ)54x1xM4o;fXJMPL)%YLG&&5frSdI1$Fo7VB>SUcBWk%%BV8-vOLDz0R`wwwrc!J zfnEh>w&%P`p>{P*(KE4>?UA%M*Oa>SX#@}CSUumYF-@oDLtKnCu6?+o#=rNC7x_da zkUxou6@h2~DsyO9cQRh=H05PsgYyP)#4OLh4^fB&XhD|xlPlvPsWL8=fkh@1wZ0?H zFqr->QmOQ3QX$4Ld!abZ&SWxnA8)PDXeifaR|>EK4DIEVHKU!?HK*YNMO9NbV?qF4L^zJW1hHwm2?X(4qlRO=mvN z#vj1KyA-z}@dT_w=alSe&$H%j662gmXJ0JrZ;8#KkA@`{g(7on!6I2f z*F&Hm(4TQErLU(`j}>mCTzM=Nmebtnoh&1#SDHJte)*QC%pMLVE+&p9E7OT*y(RFn z=cLcGT;``emJVOXnd8EVT8Xz}heW0a^TG7L^b70>q*|e)B1JPoXZ@J_uf^XB(IGzp z*&d?1G<}(sbJE5#bnpx8j^WVLPGcvO*$(mi<;Z18!BnD>C`BaTqEqrjq8pi?RR%2m z2tuTFP}!cB%ttfvwk+NDE$Sky1gYyJshf1E8~*l00nf%%rprwg^pjzhnsCEueiyYu<)cmH zs^IJ|#e>92G^(WdSTSTwPtjCYH}tL4KFx9S1LynCG13hXSq~6u@1B~USRl3$p%57+ z`lN1>IcVIZ3+Twwrrbr4ngIg{ajJbjl4I`fE zi_WeUl?rNCHO=(X6D-FwI%DmotuNu}&VJw$UgO=WNtS@J;kEH{w4E(SnH87PG6AEt zEs&6k^D=Q8iG|72vV^AK%KGRR@)hI-%=MjNq_gRk;b-?|PB31b_6O&Cv{^J#(^r_r zVS3xiulo?hQLs3{$W$Hf9U?R}MP-=h7Z;f59gjS&c2~SJ(Uh6liHn8J&w>9veh4@# z9!My^fjtOR%)kMpu;Ns)pt89$SklO;J@PReQ{jGUqU5p>ux29@J$G-bUY%dtclE3q z-glb0P7@*;!WeX9L$XTGX{AZb|IT7dw^<4cYpZL^@5lc6@%71JqS^SNv~jC!faMGrH*jbx9sJz|`I44L(57l8 ztwu(Ty29vcqKqJ*#Xa&O!(lL+Su*^rM0AhVpoj$RFJ#P-nGXY zw{n7e+jaKVQww~IvlNM8&PtA}RDQZA1?O-y%6Qwz?rGNwb`xTChWToS{bj;op!(gK zWijWSFU3cAPCTzZ|089g`pYy+s+83~ER+Q4Xdz>!@BU?x!~^bkWC!GdpbvopCKfCm z_BbE--{5XSWz4?7bdYF7R2$B8Rw=Mx{TI~5VZPh-4_%}n$^)gu@n{l-Q@&6_y^ z20QdVO|}VbPuzNT>UTdkL8p4HMFHLr^dL|{#Pa4aaB>8Rnyd2pi~mjdR`8%}UsGao z=oLoAicZa#+5Y>Gv+aZWx3#mO0HFV86a`OF4HYftf6bHJ=W>|SB0NY!ZO5sMV=X)S zkf&VhJYw{A+xlD&Sz{ipE(=%Z@zktu-qSc8>9knE|B8z_LLvVjn1n>dQp6uLJg}z4 zY0GNdRmQZ9u~j97SpS9XMa&X8-NwBccQw#Cf-qI8cv|KD+DSK19n5W>NpcLVSjB>h z+kC2t?vedjpb--6)90AN%zTybf{Iq0CHp8NH)J3|IOL8dIY z>G(uUFc|43lfG@4K7PozF`Q!fifPDR)?Sg;``1-_XV*h(_AOn`4V)tYgmmo+CdMv1 z&!tYyE+ZkI4OCsbV>D+(p5G0Si-R#4*(f!}c*y@|v?Zn*Hx)By(1f8=yOq3x0Z1q< zHT2lSZ`z(++*x6(Lz%3xo&~E0>Egu%e0S?ZQ<2>c-L0)!J8lu)_aW9k~y?i0@@7gojBI zUiWC9BySVhusdivh+u($U;+mB!{d!mu!4q=!;2F&GJY3)diyJ=008`{lr@Bf$Dbm^ zGcr>6<0ZVzB8)m&Rd>DEGC#4+KA7s@wgB0`*=mtmn#-2)V4IzvVWPL)@jTUDCtxF| zG+ekbuc}E&NJy9g|35BDno{ zAW%z$cv?@lRnPXnPtgEN{*RQto9(8(7YA-)nh`?WHvGp(!!bPp0GOt)NhBV>i9d3jWs{^#-tKZu zE!mQ5hI%2@QBt68qcZmYdPz)hY4)Ods(h-kigL-cmKmH~^xGttTSJHx&d)Q@H$)l* zM$8l6a<)L06EM^N)A3oRZu$c&J!D8iBC-s$m^RJhmHvyMz#o2~|0Uc=A(BN*=`^fi zpFh!gRw~I~kS=N+=xR&mTR@*SKqOGGxWLF)j}ZF@LOy@>ui!>V{3tR}$AlrE8go1F~tfCg>Y$GO}CCx!fMZyl*)6^Z21y9Xwqydz>)`qpfvP zd^#lp(Ek*6Z_-l+ipau$TRm}!XwZME6pn4+b{A6%jx>p)D3yv@lnYpBo2$$x@6USO z**W^qp5%Ayr`+$$iJuo!B|TFXRVz0kZwKY8xpQd-bR8|2m7 z@+4iu^)ziF4-%(j|J2<^?v_?s9$|Z%Ah;4M%&fp8hJh3k8XFLyf&+nEAf}y#1%w(5 zCOXzXoY6O!qKO&So+43ctMKjKZ>;xi)h?QnGt<`okj5dJpOG{LBQj&$<;%b`o<6KeKxeYG`#j)$%nme-hK0Cp94B_uixMKfKsur*G%^<+{ zmXi#@6|x#){@+y(m~da#G-LqhWf0i(07_|aMcu#9{|qq`up!I@b=vij1b_z|azM<} z4z>cjnvR$Y`{ML~I57J`WD~>Wr0Ra|3^S6>`#@xheebq;wUNZ_cJ+Gsj}>+h(MfQe z4TxZ*0YpS7F$qQ_0(63*35Ajw@dw_RA51!E?dgwXw#CBXz@spP8TIZWWAP0c@%=pQ zMKQVNr9L}8-vEg)M!gNg03?3xd~C{MD!K3pFp9;7mR~f)&oYF50ct_66T-nTAAyl_ zCyjteGDQKSI#5e_Bo#bX`IV1Z{aw(6!dF}&B$T@_`FBkvDEUTBqx-Yo?=Rte?=)PW z9B)N^4^k?g{-%Bds~yMi2>Dt_QMHD&5jWP&W`2Wo;F@M8 zx|)v)`6Nx14Jl<}g?4l>DWW(ElC)yNbFmE-i${lLfF{2{-|rj^sUDe&D+rxyq}F(= zvw|WOq}%)?`mL%y9{`W|{x5!m6zvE;C-+Y!ZGYHWBHbtKu{RjsjY(okhp&qLTMMwt^L@k43w7dT zG#*d5>eX7JQmJ4T&+FSh;;x<77sYY_8Tj$R-=p*Uf;|$cAM^Du_5n2a@ZW&cPYg7Z zk_!r1WZXCw)AXEQE)r6{nXGDHSe7@(HNqR4(=#@9sdL-94X|2b@orrQ7Mu>I^H@?y zgq=iIe5`@#qhhgW@rC=2U%3}dr2yhGYNC`c)Iu>Y7gJUUWUlI>z$aD2G#7dW zt}aHY11$>kSFNR3Ca+%y@c`pA%C5`xSNF5$wKvV70J&fy-6`h5s@!#!8^y6z5Ez79 z8@Dq^E|CAZBJoAYZcA{ZvmnQeKnCscX&_)s3RRVO{F&e0^XF~LMJIMC!EJzw+7$|P z65}ObdYO~>Qb2*l6JGToi(4^kCId1M!WjO19Zd4BaaLOR!yvC?i}AGetlX)-{TN}u z`JRYQgBXLDaMd!qMdb=n)-Dp7HyTiI)S&egEhTEgePNYWX-)0J^|XCaIhL*kG3rd4 zS)Wl39lQFBq6A#(YI8cKqA(oR_ytaMcZzFUwj1)jPCZ4TSpcDxV9mfEwPfpE?^ zDlvz3CEIO{l1lguBU;KK*^*S0q;wd(B>G3$c`;%xg zjO{D3GAEx>Ge}*+w#67}<7Zud^r+|6>X_FuZ>K7zv$B|wnDpz^)OUO5bA_0cf9 zMPV8WQ%$!7$8hvLxKmR6|i5eR4yQ|`sn zkJ2Vw>fP~B5Lecg?Z=u){|-4ItGq;ItXy<%FD$WIAAZt`lPxlT+Zkbsm8cnv6o>OD zo;lXpJoL?a@OBK3wz|+qFySW~>!{#%ejrOaQ~N?=?Zm~`G1MT&KgOn7nHvsADX=R~wLscbmX9sG=rD zvaDCcXlmwJnHt7}Y@vYb$h~ouQ6d~%M8yciuKMRu zF|Hldzq%3@#avMsCBZ3ofjRb1ydQAXBSs^*v6bPmi%ugBv9^O+gy}3I)GJ)Kc2^;> zvynW1YYQYifMi?>n|U6#?DoX(EAA@|f>_7=zMrUH^mDB_8`-E}jnZzPhqP?p@?~CC zR(9>b+E0H`>hjeo)a=K#*^t7IMj@`uWoa_e#o>Y{r8QS4kc$G1Y+^u(ig|VR6EeoUdmK zVWNh9xGBqgTd|Vwu7lYn$SYx5k%)eADuRbP9@2z%Ct1Hx-5>kiXz98N`p9#Iz<<*d zm6YXO+{9N>vhhIMqU`_tL?7>z#XKLcA8#~Y^rbCx?ZFh0r6Md3Wfz!)EU=eBKW{m3 zP{qAMl*Jj;)Iy6m^tbBOZil5ExQ0P`tg>XViN**Z%FVhOS=Ea0u@^loV=4$V(4g2X zoPs^ook-O*_?>RIx~#pU2Gc^XyZdvC{I494R8hOg%_Go-T zd0O2fHIKdl?%+lm24lT|!704!W_X0h=#>7|ISb8UDVr_w9Mt%2 z-nYqCT*19tdclgnoRYPJU$_n{-F$#${@K;Q)3^`p zF|h>|CVBrO8*xJgz&eEzZbIe|>@?*Ak~cWi83vdb^4EPQUT#jnp@;b7g#d1t ztx6PRZ&~!X2&WC{uKz%>++#z)RTceSxX+4#N^8KkvhYm%v3U`_BB@W9s-(nq^KUva z!!M4@SxTYOhQa(;PYvc2pcQOvw>)i|nJf}94Muxl9N1?25r|$?YYJWi&hHN2)zw*! zw#9SedRl<7;~tB-AJ)4QQ68Kq&}o?_cB5w>MY)-%Z(j9Snk-U*g-FQSx-~)Kx5M^7 zqbu8TRk5q*PPsfo-W4IMZdhPMxN6<2_W>QvxU%c${+=p1^dR6u=?UiysmVZTfKS6u z|Abk#MZ}w=$j=cRea$P9@ax{xd|!-FU4khnCxFTAh^N{-854)L!LRu;`y9&)OBEW6 z+QM#0)ey*1g-tKBHuV(XhR}w+g#7&j&@WY4#%jMQ-)Y%%ujhrDdT`>kD!t1aGY^xa z4kZU-Y<`S&>kE~ueWsCQzusEo(?JP8$)oblS?7!2lnKvVuHcuP8?)Im!O30WQPTKW z&y<4HT*v_Yi2XT|)sqsU=JiNl=5v?FY;p|?( zchU7V^1bZ$&9E{WBc%FSCc3h!Ok8wCWo0>_{L97Q-*oHL9U=m%d*-dFEud>4FvQj_ zy%u}*-pNerHbEa7o69u`=p9g0mI1>`!|wgpumtV42bITpoRukEmEL)i3laqiE!*&7 zcj6@n`^l9cLB!#<`mS4DU9x{I&Hn%_#kyE)Dk?e>I5PCdFhkUym`mu;>M$AbG6ee2 zy!hu-hKJuE3^@JZgl)NS>oR>ebvCuhZvbJ<;oi(>dL~sL`pr~KXlx_FgBXF_8ZL_- z_Xmz1g`L1!|Il=o7~gzz#ISK)dbPG+z$hgA4#hMb8n#@KYm;ZCI6h^0qPjStgIJ$o zaF_h)oy3PVt9VXrh1#vX%S1_1YObRk$^g$IU7aEU2|Dy?e9)Qg#oU52ofLG7Snu2l zB~mj<;G$09{8bE&^JAnJN~3Lt?5d*j9DPfH_(_!iakF-`meyK$sCqb)%hc`JX|UHG zwX~@0=y^wgRMb0xs-1Wl5RI_9hf%Mkdd)QLP4&}s3P)sWGCQ7`U3%Zl#H&JAT znHHEEm6^fOO$)0DIu&)&SmY}K9=l@Lhg|Nn*3tNOrDcq1O12?Kt?t+h)9C~?Yl&X# z?4{r5;z_KZvA9R?_G#kNhE<~u{$=7C?kNywV&HA8g3iaKvv8M$5Dn%mX=Jm4N~A|Y zl4|paBna0G3i{Iw9gU>}Lfa4WpFd%!S2JyDo5X_;#~e)b9_pe4gYkc-@y@B>BDH8rg6{5Epcz?Ce)4M7rVMn5Tn1f4(Gv~9roHtPX)eAy2E#Lvp-L$8-lQU#481w`r zbglD}7)0STbfxNQbra^SS&W%iEO5gmy_(JtE1TGsfmC-32VMdcb}a<`#56TnI!zPS zaxF4hMbC@|Y1oYM`MK(x(boavqM2JZkBkH05QcO~?}W=1&q(A-#)L44!K&!kdjeN< z6~X2zZofvko6B2KmLg%6p8#+MwhQM#U^=0FswpCVA6LF#Ca3*v7%mzLP}@ zYTSlS;+Ij( z6)Dwv^08;=^pD_ZhM7{l+p_+ob-jD1X+M9L^h)uwtn!s2#`BUPf{P!A2rmNVV_jhb zN2rl_KGrXBa3_Zk9FafAs6jnGsvlJ_s80peo6bA`ojK^TvKv1B3GR{B^(;ZbzZBq} z5*Vws9AiKU^hOt>nmoIIN^X>*nG&_U|eqJVB= zz#$^9UTI60?$obZwT3)I@`Ubd(Zu~g z%eu&uGo^RXy0||>&WN7K3p3_Qr4M5l|6(WCYJ5%L@!2`2r>eG7;2)4gW^sR|9_z9_ zhsLRR+?kBZ1PsALt#x&~!_)eav%}$7Rob5`Yh5`@gWnZvc-}W7SqfO~$KS+-X0d=# zR3?gCeX&8GK5%fR%VaD{d|y4Fs31M?i;*jK_0bYJOSjDK58YS(Ozv&zQmVH?M}|DJ z7nVSVJZjrtx;#Bxmz9;i`wJtIqGvI$Hc3L-_#6#HE^3j&r0qzz;kzgT))e{?nyVIK zDM$cT@G5b#d%f(-EU=O}+TAbmR#IuAn+KxZ z`4ZnPwl4kzEGjk#Wy(v!`wt|V6)lZ<2ClV+aZQlGS-Z4?M~HNdt0X2~#-Gp^J~AhG z;CC8?O=L-kVv3NF2Ufm$NU3(&GJePzT@%V2qwHv&tkK!sldv%H(FVxYFAI~oS6#)tBe~N! zR9Owa+$m^2Q8_5R*|s@R!c$Npg^kulrL{6GCF>6(?*ZjPX+@!%y^hQa0+&}~r1iZhu}<*U1_@uBo><|M5iNHy_zp@G zsehmQF#S{lW7PsU;(+02Z1_$F0b=S|Bbg?bLYlRUu5l?BvrEe;O+Zt=uC4#*spc1# zTvVMV#e-qes-@@Z^6F$SiK^xhBg|EfqpZpa56;mv2Ilgck<4Eha|Xfib`NHE|IR9T zrs}0=R4rWv62?qy7cR)IrBzY)j(G2vdj1wJ{!A-1DquaEvE>s#dPdT2VP!eC_B$To zTJ9_^8}DqMOk5pX7B7EnYnSU1A2>22S?bv0wdhiV-ieyVg-_cwvyN}OHpBM6QkLU4 zAx>n^{pE&!GwBF2xn}K(X;Caun-Rbk?i?bIg{7liZfl5ukjgSo8dI_@a?B_zVB}XR zbHJas!`*72go+by?5m|K{3!lDc)BX;?cm1ig$9wR!_QX#toF!w6ZyQkKt?=;2i1x+ z0dv0&6Wx^X^FJ^f<37}38n)12%qu8hI^rgZh}Vcgy9On898_68A*Tmc%h>*wlWOX0 zZQ?~(O(&A4Z`HA86Tb1#Igx=^$45>7ZoTvga0@2`8_vLEU(+V_{h{fGQ>B9PHgR)d z(d*@kUYgkiwn`kxVVVjuw8ByvgjpwvrzFF%zGw6*BXo zuVy$4X6d9t&3r^ohqNvKl!@qrqaviae^HDlOVZ9ed|{;=Sv%^{^+$xR+qIQM=iU1y zU1MH`k$Jv;6b_b3yM-ev4M#p|8^LI-oI!DIrPLC*=2A{xqgC*P5aTAW*&ERHI}6EM z;elA@ORtsB&AHApK}nk3+oHyK7hU3% zFh8$iIgiki%P-%Y>6O-8Iz602Ff>>27{vVX$M2H{44SQ-BHnqCaW ze-fcJgfrGN68CqMP8Oq-7w_&lob$jl;rWP20_wL5A4cX}jtn<_5)|e^@X!9n*#fch z7x|64^$ZpF5q{{{kJ7xk(kDqHQcB>RqXd^Eg6hW=wqqqH2_O zzxUC+jlOhDtc4y`(Y&ZA9!up+ns~#Eu+!c2w*BpTx~HORl42XzS1xSR$V&<62HW;# z25m@=qtgY=5a+!{VhrDx6Do$zD#a=XX1AjBhX3HHWAmosgyE7X?)052`vjxXK)A(f2t*NWtrw|coN&#eu`MO<@%+HSkW z*3lDotnhc^^?xx1SSFOdyhfIs%n6bKofTT2^6TWZl+GDkL{bQwpovXRn?}g!nbLW$ zs&s!io$f5>+GZiCDlXMDWM-pU!nWp^v}l0}>W}THTFHS})B!hOkzQliaL&mDga!*H z91tu;eglj}h#4xlXMZN@Ix0V^#kCN=lmR87A5gX`)ofRkP{?2p$_vVhrEuNIji*?H zXlHd=o3{@3$kbNJC|O-Q52r#$EJW^AlwYSlitK(|>!{Fw{p;_FX5zbf5&&v^3JUeY zoumz}lO>xI>n*FJi%;)>NKslNCv}Czk{`z+LF(#|BA!o&Z9UwcB2loy_{~W zK9STv`yItdxk=^Hl3cY)L?@lPcE@EX7T#aS5|9sy;u4x68)%(wdL#S7x65zmN#}G)mJ$BFgAKrtQ6XfZ@)`OH0QAQy6~_xLXWZS=RaH$>gmtOPtpVQ%b5_-AmNZB({191d%E5vz*NJ{3T6ptR&#G>HwTicb zdapEZ1h3bi_Ype`PPT~V^xy)NU84jN;jt27{}M(jl+IG*j;>h&D@Khu8NeWQ9&}Wb zv;3(qcF%RgTaT%Co0{|~6dap#{(57Yc=L)`Y{RjW2pVb|WXsTZHS+a! zYJy8%R#TY)IkB3p!;XLsms3zILyC^nGUpW@hd3RJ+ku`!h?(W))hs2?)@V=%*-h3s zCauN`r^<|DE8BtW8tPK@+cLd6f*3N?b+qI&X8y?;I(2y!m(Y=j(p;CQx8_zx^2G+L zM3yll31E7uI?VoP2aL16+C%&D6EFz!3LBRUSQMW4hAEj>A^?i~6Ka-Ri3B19JY-46 zlpksq2L^|bf_GId&YKwh0~muROb(Bf?My4ZM4!Hks6dpYO9s69_frup5&XEvId=~r z{ki>H(CcFj;2s8`{V^(z02rX$kt66YL&P&fLbw0ZpLM${Ft$Zr=nkt?7=J?~-wXdL zFq9*PC{^@aJn+-0TPT3rcY$HI)29+ea=m>NmIO4#`2`c?{Uu{}4=50W)CF^%iXYzV zf>Y92G13`1;W;@;O#nvmW2+jRL@R!M?XD<1m%v}9_kP@gnoy87j>Xqa2V!gNC~PNhJ06fy zADZ=Y1^ck27Y_b5WC%k?3 z4`gvr{k(WHPBGk_gEH(ypwMb)(;YzXqMlm(jQN%y_~TczGBS;1<<;-FdEMjWbf}Hf1596+VCDZr zSBF^+biGM5K(~j&0H6bs8W3BZcgbH>4Sny3* zwEG_M&r;qxq4qMHxQ@zR?Q$njBJPwaa-MOZaNv34>AK>{dY0!2l3 z9~%dL-G=Q6sx%-7gFxIZlT4IG*>HVABh^3}yPX)Qt9VxGgV{CYQidnU8;r0iVMu}@ zJAA^c%LQf0bl4N5xK)xR<2`7u2TpROemEeFkDs=+Ug@Qv=sm-t3Ap~QyXGI@{;)0f zrd_@3&r`5X7JV2%gnRSB^X)C*wcktXfK0n*J?v#8J_I`LyH~O59FN~3qqn1hK5(hp zU`%mruW^RX_;zK2h_17R$BKHW0f$eiA2N`5bi94s#PpCT2kD$DmK6l;mSl~j! z`3{^W_woenVXi2hchE?A+pr1Y#JVX8pWLl5Ki}@+#;TBJ^nGsAKiFj2m}&cP7flj| z4gX#UZQ!(*!UMAqa`3R}qNQE7rZN3KE)w^$zjbKnx=sRji#`QZQywaY;D%l#g79d5 zDw05T8ymb~-Tvs;!yVHfxW(BT-$pG$=qtJ)W=MeUSDvPCrug7Ot!!KoT zf4*N=BKDqiyTqi10RRVpqXUf2fN#UYOeM6-IBN}bw}98r5b9hJpFIX&kVM;~JRbaE zpBX340>!dP{7EFZPy6#QTnO*C+gX7bRVT+*55Vn3btN_`^^ELZ@Kr23o1)~)7c*ha zm!sQ|N1oo7Hy4r2!AwYJ7>Vk}Ik`+K6Ufo?`=w}ftmL^7~H$0guWR`^4f;%Ee}05feGx zRG~WvnQ|NW$9FTI(Dj*58(_4KHfd&I9nQ~>m|tV%{T%4p$D=vma%tMGBP(L2e6D;U zUph`Y30bEb7HI%>puDD^2|JEj4P1li!B@kjP31=!B~&Wq$TjS3VQbTsP6n&oM4nnc z+a@gQ2tmWT&qv*Q6TP9AX6cBcShc$pT`zC50KlY~WD<=FqgZCsM+13Uooun=?GNHX zNwOyBjPVgP>Lx5>V|mnZyFYVv)E9&aLE6nic$>t0kP^~~M!oJAD+PhFj*9V!PWp19 zw$ziFmawzwgK#9(bijn3)k z?4sJzjeqOicR%+DNKr#T9jlkV7wL&%d4myzpg5A{fkV*NtrJW;&Kf@P+!J>Uf;mb# zYD-FM$Vy+TJp=mG@oxAw@o)G%HMRH8RqfWV-d4SaAXO{XbdPd2qgsT!5T7W)OEtcc zfy`E{qYk2gxnEO&OE$ti1Oy}_l(xKS3zWFdp~)wO^*|MPEiE`(A}%F9EmP70&4s1U zU%A9BK$bsu65m8cegrGx&c&2-){xBYV2O^?V2Wq4!5~n=<2<;#PMN@yHh6nkylVzg zbf6l+=LrVOSY=;W#Y2T1jaQ=3Wvw7Vt}N&C*bJ@^Wn*S7?VrHZTFkMAfclcv3TbXL OeG&HNd{LtS0sap##2Lu| literal 0 HcmV?d00001 diff --git a/fonts/outfit-latin-800-normal.woff2 b/fonts/outfit-latin-800-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..fddb805a53d5b6eee663b5a52e06af75b491fab7 GIT binary patch literal 14048 zcmZ8_Q;;aivSr)0ZQH%uwr$(CZQHhO+qSja_MCI?n}|1)Q4yt&m8z9d=_V)21ON!| zkFf0nApAQAX8*JH`|sJm`~O$40zI*p0dRsP!Swl*lm(Qj07768A)o@nr7xMGgPZ{X z*?>rZ!;C=)Ktlk)!vJ8wu|4d;3!Tm+XnQnJ$LnQ%8pMbV?Q=;C#_o+2l2y0N^O$kNKmR!aY@Bj{qp zZZauMPKOc-2q*|2&s6g?AMW@(znL4fs%b8Exz+4?FXt1D1%XsH4fCyF5{ z3T42JvYgmQIsbN&W7;{Znj9~(3QDzgCEhfQ1LUvm+Qk%5sVFWxq&*!7wS7g(J+m5J z5z?~(qTcH>9>XtqTpeZ)rU$ta2NIX=Ak1hi;#u*4&P8j=tOTMvgrrxKt+APr;F zuu5aG*iea%l>g`ye9$VFXRFCIJ3jR6erlXKJi;yrUVY`-gY71Ae2f@7#c0fg^0Lkx zEhGP=x^1N%;U9+PCEj#0ND$E#1=dcqZupGP-mzckR!@y~6EN>35*>Q@}Z_0v=BQ(E+yukd^c& z3BgrCP>6w@sfTWNxR{}?Jl||@*rY{vzBpM7`R(bNF?$JBRj@%B3A8&T{+d3{soj`b;xHa1wS5$c&YQ&NF;rUF6++d1Krqa*|Hb!EQ zj_xOeObWB!2(&6@2AU)A zH60kIGE60eBs>%$P?W6C1XF@p&p~xtgmgoU7795K_i<=8$Du|fml|>9+-!?wTeHQ0vO9|SZy z|D8JMvc)vI`YF+n-)2^(3x|t{Q=1`_zGI0A7qJ5Y>-8TuzU(@`a<-^n=$Ma3)ECOU z#6#)-6Nck~6CZe1SMvskdybybE`R!BW$N9_X=t&@XdT_USelU2re)MHUqlgZ+GB2z zTxWK})<{`aR#FjG4OTlX`p8ld*9@0SZ>k*K^=9sgsm?{`P?8{T5otBG<~azzsa2X~ zoq1uJ4k<^sn3{=D`8!gvR>wp=GIdmC7ow)`!ab@rRRmLVRZS&vAJuATTjLuQ%*rth>6aK2t(l=XLdOlw>Eo5JUycX`?`2e^X5n6KfhwM? zg1jO#Xr-p-k+S^(ct-1ra8`+?oITS)#B-{MDB9BfNLp+nRVd-^&tlDDJ|EuL<*U-O zdN~X~4BwYytHRXyY85WpSCFzri&DI1fCBYqWNUNotw~Y8mbt$O!g5R4=%I}bwRklR zC(nXlvw(WAC`=G%Qqzz#$EHcKFW{+DGKw(~N`9WJf7ni(FAxb+gQ+DTt$xwGq?7&3 zkv$i9>QK4ADX8h;xfDi$WXvpSab)({ac!_=-KUwH*;qx)hDrmTd)YqyhCIq(;6w&e ztvfW}>{5n|m_Dk;&d7h5tUt@r6+F_)^x#GUKzJ2cM78bW9g=Cl=5Jk<_I)Yj=9Lb{ z0bLJ-V&pjqA>pADO&jz`vr&YzG*B^eqUug=*B-otHE`*#OC^u7uggq1i)j%Fg`bZl z-wl0hYjWn+a&CU|w48Y0T7qX#*#KDs?aVc>W6th{*%!%jF`5at(dNh@at1W75&Vd8 z_{u*N{+;4mQ1AMQx_c_tj%K#0@gjQSS_`6mal}<;%!l9nmnNETtfZⅈ@@1Pv%8P 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 literal 0 HcmV?d00001 diff --git a/fonts/space-mono-latin-400-normal.woff2 b/fonts/space-mono-latin-400-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..2752f605c1ece3797d3285191f5ffebe3c2873e1 GIT binary patch literal 16520 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 literal 0 HcmV?d00001 diff --git a/fonts/space-mono-latin-700-normal.woff2 b/fonts/space-mono-latin-700-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..6563bad69c80c737252e98378763276116c17a3a GIT binary patch literal 16724 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 literal 0 HcmV?d00001 From b5478b000b6a978c1f966468a5bb02df7019f1ad Mon Sep 17 00:00:00 2001 From: PearMan95 Date: Fri, 17 Jul 2026 12:17:44 +0200 Subject: [PATCH 12/13] add: include local font definitions for Space Mono and Outfit in popup and uninstall pages --- popup/popup.css | 37 +++++++++++++++++++++++++++++++++++++ popup/popup.html | 2 -- uninstall.html | 37 +++++++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/popup/popup.css b/popup/popup.css index 1cfaee3..ea91cd4 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -1,3 +1,40 @@ +/* ─── LOKALE FONTS ─────────────────────────────────────────────────────────── */ +@font-face { + font-family: 'Space Mono'; + src: url('../fonts/space-mono-latin-400-normal.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'Space Mono'; + src: url('../fonts/space-mono-latin-700-normal.woff2') format('woff2'); + font-weight: 700; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'Outfit'; + src: url('../fonts/outfit-latin-400-normal.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'Outfit'; + src: url('../fonts/outfit-latin-600-normal.woff2') format('woff2'); + font-weight: 600; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: 'Outfit'; + src: url('../fonts/outfit-latin-800-normal.woff2') format('woff2'); + font-weight: 800; + font-style: normal; + font-display: swap; +} + /* ─── CSS VARIABELEN ───────────────────────────────────────────────────────── */ :root { --bg: #08090f; diff --git a/popup/popup.html b/popup/popup.html index 1903aff..bd2e7d8 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -3,8 +3,6 @@ Plane Alert - - diff --git a/uninstall.html b/uninstall.html index 079884e..07a79e8 100644 --- a/uninstall.html +++ b/uninstall.html @@ -4,9 +4,42 @@ Plane Alert — Goodbye! - -