2026-06-04 20:30:04 -04:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< title > OSINT//DASHBOARD — Global Situational Awareness Terminal< / title >
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
< link href = "https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Rajdhani:wght@400;500;600;700&family=Share+Tech+Mono&display=swap" rel = "stylesheet" >
2026-08-24 17:35:44 -04:00
< link rel = "stylesheet" href = "/static/vendor/leaflet/leaflet.css" >
2026-08-27 17:00:09 -04:00
< link rel = "stylesheet" href = "/static/vendor/leaflet/MarkerCluster.css" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/MarkerCluster.Default.css" >
2026-06-04 20:30:04 -04:00
< style >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════════════════════════════════════════════════════
OSINT//DASHBOARD — "SECTION 9" GHOST-SHELL HUD THEME
Cyberpunk military-intel terminal. Near-black deep blue, cyan
primary, magenta secondary, chamfered panels, scanline chrome,
mono data readouts.
═══════════════════════════════════════════════════════════════ */
:root {
color-scheme: dark;
--bg-0: #030609; /* page floor */
--bg-1: #060b14; /* chrome */
--bg-2: #0a1322; /* panels */
--bg-3: #0e1a2e; /* raised */
--line: #14324a; /* borders */
--line-hi: #1e4a6e; /* hover borders */
--cyan: #35e0ff; /* primary accent */
--cyan-dim: #0e4a63;
--cyan-glow: rgba(53, 224, 255, 0.35);
--magenta: #ff2e97; /* secondary accent */
--amber: #ffb454;
--green: #53f0a5;
--red: #ff5d5d;
--text: #c8e6f2;
--muted: #5f7d95;
--topbar-h: 46px;
--dock-h: 62px;
}
2026-06-04 20:30:04 -04:00
* { margin: 0; padding: 0; box-sizing: border-box; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
html, body { height: 100%; }
body {
font-family: 'Rajdhani', 'Segoe UI', system-ui, sans-serif;
background: var(--bg-0); color: var(--text);
overflow: hidden;
font-size: 15px; letter-spacing: 0.01em;
}
::selection { background: var(--cyan-dim); color: #eafcff; }
:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
a { color: var(--cyan); }
.mono, code, kbd { font-family: 'Share Tech Mono', ui-monospace, SFMono-Regular, Menlo, monospace; }
.btn {
background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
border: 1px solid var(--line); color: var(--text);
border-radius: 4px; padding: 0.35rem 0.9rem; cursor: pointer;
font-family: 'Rajdhani', sans-serif; font-weight: 600;
font-size: 0.8rem; letter-spacing: 0.06em; text-transform: uppercase;
transition: border-color .15s, box-shadow .15s, color .15s;
}
.btn:hover { border-color: var(--cyan); color: var(--cyan); box-shadow: 0 0 10px var(--cyan-glow); }
.btn.active { background: var(--cyan); border-color: var(--cyan); color: #02131c; box-shadow: 0 0 14px var(--cyan-glow); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
2026-08-24 17:35:44 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Chrome texture: scanlines for HUD surfaces ─────────────── */
.chrome {
background-image: repeating-linear-gradient(
0deg, rgba(53,224,255,0.028) 0 1px, transparent 1px 3px);
}
/* ═══════════════ TOP BAR ═══════════════ */
.topbar {
height: var(--topbar-h);
display: flex; align-items: center; justify-content: space-between;
gap: 1rem; padding: 0 1.1rem;
background: linear-gradient(180deg, #081120, var(--bg-1));
border-bottom: 1px solid var(--line);
position: relative; z-index: 900;
box-shadow: 0 0 24px rgba(53,224,255,0.08);
}
.topbar::after {
content: ''; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
background: linear-gradient(90deg, transparent, var(--cyan) 18%, transparent 60%);
opacity: 0.5;
}
.brand { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.brand-mark {
font-family: 'Orbitron', sans-serif; color: var(--cyan);
font-size: 1.05rem; text-shadow: 0 0 12px var(--cyan-glow);
border: 1px solid var(--line-hi); padding: 0.1rem 0.34rem; border-radius: 3px;
background: rgba(53,224,255,0.06);
}
.brand-name {
font-family: 'Orbitron', sans-serif; font-weight: 700; font-size: 0.95rem;
color: #eafcff; letter-spacing: 0.14em; white-space: nowrap;
}
.brand-name .accent { color: var(--cyan); text-shadow: 0 0 10px var(--cyan-glow); }
.brand-sub {
font-family: 'Share Tech Mono', monospace; font-size: 0.58rem;
color: var(--muted); letter-spacing: 0.22em; text-transform: uppercase;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 900px) { .brand-sub { display: none; } }
.topbar-right { display: flex; align-items: center; gap: 1rem; }
.sys-status {
display: flex; align-items: center; gap: 0.5rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.72rem;
color: var(--muted); white-space: nowrap;
}
.sys-status .sep { opacity: 0.35; }
.dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.dot.ok { background: var(--green); box-shadow: 0 0 8px var(--green); animation: pulse 2.4s infinite; }
.dot.bad { background: var(--red); box-shadow: 0 0 8px var(--red); }
.dot.dim { background: var(--muted); box-shadow: none; animation: none; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
.clock { color: var(--cyan); }
@media (max-width: 720px) { .sys-status .hide-sm { display: none; } }
/* ── Dropdown nav ── */
.nav { position: relative; }
.nav-btn {
display: flex; align-items: center; gap: 0.5rem;
background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
border: 1px solid var(--line-hi); color: var(--text);
font-family: 'Orbitron', sans-serif; font-size: 0.72rem; font-weight: 700;
letter-spacing: 0.16em; padding: 0.42rem 0.85rem; border-radius: 4px;
cursor: pointer; transition: border-color .15s, box-shadow .15s;
}
.nav-btn:hover, .nav-btn[aria-expanded="true"] {
border-color: var(--cyan); box-shadow: 0 0 12px var(--cyan-glow);
}
.nav-caret { color: var(--cyan); transition: transform .18s; }
.nav-btn[aria-expanded="true"] .nav-caret { transform: rotate(180deg); }
.nav-menu {
position: absolute; right: 0; top: calc(100% + 6px);
min-width: 230px; background: var(--bg-2);
border: 1px solid var(--line-hi); border-radius: 6px;
box-shadow: 0 8px 32px rgba(0,0,0,0.6), 0 0 18px rgba(53,224,255,0.12);
padding: 0.4rem; display: none; z-index: 950;
clip-path: polygon(0 8px, 8px 0, calc(100% - 8px) 0, 100% 8px, 100% calc(100% - 8px), calc(100% - 8px) 100%, 8px 100%, 0 calc(100% - 8px));
}
.nav-menu.open { display: block; animation: menuIn .14s ease-out; }
@keyframes menuIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.nav-item {
display: flex; align-items: center; gap: 0.6rem; width: 100%;
background: none; border: none; color: var(--text); text-align: left;
font-family: 'Rajdhani', sans-serif; font-weight: 600; font-size: 0.88rem;
letter-spacing: 0.09em; text-transform: uppercase;
padding: 0.55rem 0.7rem; border-radius: 4px; cursor: pointer;
border-left: 2px solid transparent;
}
.nav-item:hover, .nav-item:focus-visible { background: rgba(53,224,255,0.08); border-left-color: var(--cyan); color: var(--cyan); }
.nav-item .ico { color: var(--cyan); font-size: 0.8rem; width: 1.1em; text-align: center; }
.nav-item.primary .ico { color: var(--magenta); }
.nav-sep { height: 1px; background: var(--line); margin: 0.35rem 0.3rem; }
/* ═══════════════ STAGE / VIEWS ═══════════════ */
.stage {
height: calc(100vh - var(--topbar-h) - var(--dock-h));
position: relative;
}
.stage.scroll { overflow-y: auto; }
.view { display: none; }
.view.active { display: block; }
/* ── MAP VIEW (landing) ── */
#view-map { height: 100%; }
.map-toolbar {
position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
z-index: 500; display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
padding: 0.35rem 0.5rem; border-radius: 6px;
background: rgba(6,11,20,0.82); backdrop-filter: blur(6px);
border: 1px solid var(--line-hi);
box-shadow: 0 0 16px rgba(53,224,255,0.10);
clip-path: polygon(0 6px, 6px 0, calc(100% - 6px) 0, 100% 6px, 100% calc(100% - 6px), calc(100% - 6px) 100%, 6px 100%, 0 calc(100% - 6px));
}
2026-08-24 17:35:44 -04:00
.map-toolbar select, .map-toolbar input[type="date"], .map-toolbar input[type="datetime-local"] {
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
background: var(--bg-1); color: var(--text); border: 1px solid var(--line);
border-radius: 4px; padding: 0.3rem 0.45rem; font-size: 0.76rem;
font-family: 'Share Tech Mono', monospace; color-scheme: dark;
2026-08-24 17:35:44 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.map-toolbar select:focus, .map-toolbar input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.map-toolbar .btn { padding: 0.3rem 0.6rem; font-size: 0.72rem; }
.mt-label { font-size: 0.6rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted); margin-right: 0.15rem; }
.map-stage { position: relative; height: 100%; }
#map { width: 100%; height: 100%; z-index: 0; background: var(--bg-0); }
.map-stage::after { /* CRT vignette over the satellite globe */
content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 400;
background: radial-gradient(120% 100% at 50% 45%, transparent 55%, rgba(3,6,9,0.55) 100%);
}
.leaflet-container { background: var(--bg-0); font-family: inherit; }
.leaflet-control-zoom a {
background: var(--bg-2) !important; color: var(--text) !important;
border-color: var(--line) !important;
}
.leaflet-control-zoom a:hover { background: var(--bg-3) !important; color: var(--cyan) !important; }
.leaflet-control-attribution {
background: rgba(3,6,9,0.78) !important; color: #8fa8bd !important;
font-family: 'Share Tech Mono', monospace !important; font-size: 10px !important;
}
.leaflet-control-attribution a { color: var(--cyan) !important; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
background: var(--bg-2) !important; color: var(--text) !important;
border: 1px solid var(--line-hi); box-shadow: 0 0 20px rgba(53,224,255,0.15) !important;
border-radius: 6px;
}
.leaflet-popup-content-wrapper::before {
content: ''; position: absolute; inset: 0; pointer-events: none;
background-image: repeating-linear-gradient(0deg, rgba(53,224,255,0.03) 0 1px, transparent 1px 3px);
}
.leaflet-popup-content-wrapper a { color: var(--cyan); }
2026-08-24 17:35:44 -04:00
.leaflet-popup-content { font-size: 0.82rem; }
.leaflet-popup-close-button { color: var(--muted) !important; }
.leaflet-overlay-pane svg { filter: drop-shadow(0 0 3px rgba(0,0,0,0.6)); }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.leaflet-div-icon { background: transparent; border: none; }
2026-08-27 20:50:35 -04:00
/* ── Heading-aware live-feed glyph markers (aircraft/trains/vessels) ── */
.hdg-marker {
width: 26px; height: 26px;
display: flex; align-items: center; justify-content: center;
}
.hdg-glyph {
display: flex; align-items: center; justify-content: center;
width: 22px; height: 22px;
will-change: transform;
filter: drop-shadow(0 0 4px currentColor) drop-shadow(0 1px 2px rgba(0,0,0,0.8));
}
.hdg-glyph svg { width: 100%; height: 100%; display: block; }
2026-08-27 17:33:12 -04:00
/* ── Camera marker clusters (neon green, matches the dots) ──── */
.marker-cluster-small, .marker-cluster-medium, .marker-cluster-large {
background-color: rgba(74, 222, 128, 0.22);
}
.marker-cluster-small div, .marker-cluster-medium div, .marker-cluster-large div {
background-color: rgba(16, 185, 129, 0.92);
color: #04150c;
font-weight: 700;
box-shadow: 0 0 14px rgba(74, 222, 128, 0.55), 0 0 2px rgba(3, 6, 9, 0.9);
}
.marker-cluster-small div { font-size: 10px; }
.marker-cluster-medium div { font-size: 11px; }
.marker-cluster-large div { font-size: 12px; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Map hint + HUD chips ── */
.map-hint {
position: absolute; left: 12px; bottom: 12px; z-index: 500;
font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; color: var(--muted);
background: rgba(6,11,20,0.72); border: 1px solid var(--line);
border-radius: 4px; padding: 0.3rem 0.6rem; max-width: 60%;
text-shadow: 0 0 6px rgba(0,0,0,0.8);
}
.hud-chips {
position: absolute; right: 12px; bottom: 12px; z-index: 500;
display: flex; flex-direction: column; gap: 0.35rem; align-items: flex-end;
}
.hud-chip {
display: flex; align-items: center; gap: 0.45rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted);
background: rgba(6,11,20,0.78); border: 1px solid var(--line);
border-radius: 4px; padding: 0.26rem 0.55rem; letter-spacing: 0.06em;
backdrop-filter: blur(4px);
}
.hud-chip b { color: var(--text); font-weight: 400; }
.hud-chip .c { width: 6px; height: 6px; border-radius: 50%; }
.hud-chip .c.fires { background: #fb923c; box-shadow: 0 0 6px #fb923c; }
.hud-chip .c.cams { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
.hud-chip .c.blips { background: var(--magenta); box-shadow: 0 0 6px var(--magenta); }
.hud-chip.off { opacity: 0.4; }
2026-08-28 09:33:19 -04:00
#dvr-bar {
position: absolute; left: 50%; bottom: 12px; transform: translateX(-50%);
z-index: 520; display: flex; align-items: center; gap: 0.55rem;
background: rgba(6,11,20,0.86); border: 1px solid var(--line-hi);
border-radius: 4px; padding: 0.28rem 0.7rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted);
min-width: 280px; max-width: 52%;
}
#dvr-bar input[type=range] { flex: 1; accent-color: var(--cyan); }
#dvr-bar.live #dvr-live { color: var(--green); text-shadow: 0 0 8px rgba(83,240,165,0.5); }
#dvr-toast {
position: absolute; top: 12px; right: 12px; z-index: 700;
max-width: 280px; pointer-events: none;
}
#dvr-toast .gf-alert {
background: rgba(255,46,151,0.16); border: 1px solid var(--magenta);
color: var(--text); font-family: 'Share Tech Mono', monospace;
font-size: 0.68rem; padding: 0.4rem 0.6rem; margin-bottom: 0.35rem;
border-radius: 4px;
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Layer control panel (dark HUD) ── */
2026-08-24 18:02:56 -04:00
#layer-panel {
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
position: absolute; top: 58px; left: 12px; z-index: 600;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
width: 268px; max-height: calc(100% - 74px); overflow: hidden;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
background: rgba(6,11,20,0.86); backdrop-filter: blur(8px);
border: 1px solid var(--line-hi); border-radius: 6px;
box-shadow: 0 0 22px rgba(53,224,255,0.14), 0 0 2px rgba(53,224,255,0.4);
2026-08-24 18:02:56 -04:00
color: var(--text); font-size: 0.78rem; display: flex; flex-direction: column;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
clip-path: polygon(0 8px, 8px 0, calc(100% - 8px) 0, 100% 8px, 100% calc(100% - 8px), calc(100% - 8px) 100%, 8px 100%, 0 calc(100% - 8px));
2026-08-24 18:02:56 -04:00
}
.lp-head {
display: flex; justify-content: space-between; align-items: center;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
padding: 0.55rem 0.75rem; border-bottom: 1px solid var(--line);
font-family: 'Orbitron', sans-serif; font-size: 0.68rem; font-weight: 700;
letter-spacing: 0.14em; text-transform: uppercase; color: var(--cyan);
text-shadow: 0 0 10px rgba(53,224,255,0.55); cursor: pointer; user-select: none;
background: linear-gradient(180deg, rgba(53,224,255,0.07), transparent);
2026-08-24 18:02:56 -04:00
}
.lp-head .lp-caret { color: var(--muted); font-size: 0.8rem; transition: transform .18s; }
#layer-panel.collapsed .lp-caret { transform: rotate(-90deg); }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-body { padding: 0.55rem 0.75rem 0.65rem; display: flex; flex-direction: column; gap: 0.5rem; overflow-y: auto; }
2026-08-24 18:02:56 -04:00
#layer-panel.collapsed .lp-body { display: none; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-layer { display: flex; flex-direction: column; gap: 0.28rem; padding-bottom: 0.45rem; border-bottom: 1px dashed var(--line); }
2026-08-24 18:02:56 -04:00
.lp-layer:last-child { border-bottom: none; padding-bottom: 0; }
.lp-row { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-name { display: flex; align-items: center; gap: 0.42rem; font-weight: 600; cursor: pointer; text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.68rem; }
.lp-name input[type="checkbox"] { accent-color: var(--cyan); cursor: pointer; }
.lp-name .soon { font-size: 0.55rem; color: var(--amber); border: 1px solid rgba(255,180,84,0.4); border-radius: 3px; padding: 0 0.3rem; letter-spacing: 0.1em; }
2026-08-24 18:02:56 -04:00
.lp-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-dot.base { background: #35e0ff; box-shadow: 0 0 7px #35e0ff; }
2026-08-24 18:02:56 -04:00
.lp-dot.fires { background: #fb923c; box-shadow: 0 0 7px #fb923c; }
.lp-dot.cams { background: #4ade80; box-shadow: 0 0 7px #4ade80; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-dot.blips { background: var(--magenta); box-shadow: 0 0 7px var(--magenta); }
.lp-dot.weather { background: var(--amber); box-shadow: 0 0 7px var(--amber); }
.lp-dot.flights { background: #7dd3fc; box-shadow: 0 0 7px #7dd3fc; }
.lp-dot.vessels { background: #2dd4bf; box-shadow: 0 0 7px #2dd4bf; }
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
.lp-dot.radar { background: #38bdf8; box-shadow: 0 0 7px #38bdf8; }
.lp-dot.thermal { background: #f97316; box-shadow: 0 0 7px #f97316; }
.lp-dot.wfigs { background: #ef4444; box-shadow: 0 0 7px #ef4444; }
.lp-dot.trains { background: #c084fc; box-shadow: 0 0 7px #c084fc; }
.lp-dot.storms { background: #f472b6; box-shadow: 0 0 7px #f472b6; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-count { font-family: 'Share Tech Mono', monospace; font-size: 0.7rem; color: var(--cyan); }
2026-08-24 18:02:56 -04:00
.lp-sub { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-opacity { display: flex; align-items: center; gap: 0.4rem; font-size: 0.62rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.lp-opacity input[type="range"] { flex: 1; accent-color: var(--cyan); height: 4px; cursor: pointer; }
.lp-opacity b { color: var(--text); font-family: 'Share Tech Mono', monospace; font-weight: 400; min-width: 2.4em; text-align: right; }
.lp-select { background: var(--bg-1); color: var(--text); border: 1px solid var(--line); border-radius: 4px; padding: 0.22rem 0.35rem; font-size: 0.68rem; font-family: 'Share Tech Mono', monospace; color-scheme: dark; }
.lp-select:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.lp-legend { border-top: 1px solid var(--line); padding-top: 0.5rem; display: flex; flex-direction: column; gap: 0.35rem; }
.lp-legend h4 { font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); margin: 0; font-family: 'Rajdhani', sans-serif; font-weight: 600; }
.lp-ramp { height: 8px; border-radius: 4px; background: linear-gradient(90deg, #2563eb, #38bdf8, #f59e0b, #ef4444, #fde047); box-shadow: 0 0 8px rgba(251,146,60,0.35) inset; }
.lp-ramp-scale { display: flex; justify-content: space-between; font-size: 0.58rem; color: var(--muted); }
.lp-heat-legend { display: flex; align-items: center; justify-content: space-between; font-size: 0.64rem; color: var(--muted); gap: 0.5rem; }
2026-08-24 18:02:56 -04:00
.lp-heat-legend .sw { width: 10px; height: 10px; border-radius: 3px; display: inline-block; margin-right: 0.3rem; box-shadow: 0 0 6px currentColor; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.lp-future { opacity: 0.5; }
.lp-future .lp-name { cursor: not-allowed; }
.lp-error { color: var(--red); font-size: 0.66rem; }
.lp-note { font-size: 0.6rem; color: var(--muted); opacity: 0.85; line-height: 1.35; }
/* ── Camera / blip popup thumbnails ── */
2026-08-24 18:02:56 -04:00
.cam-pop { min-width: 210px; max-width: 260px; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.cam-pop .thumb { width: 100%; height: 160px; object-fit: cover; border-radius: 4px; border: 1px solid var(--line); margin: 0.3rem 0; box-shadow: 0 0 10px rgba(53,224,255,0.2); background: var(--bg-0); }
2026-08-27 15:54:54 -04:00
.cam-pop video.thumb, .cam-pop iframe.thumb { height: 180px; background: #000; }
2026-08-24 18:02:56 -04:00
.cam-pop .thumb.placeholder { display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 0.7rem; height: 80px; }
.cam-pop table { width: 100%; font-size: 0.72rem; border-collapse: collapse; }
.cam-pop td { padding: 0.12rem 0.2rem; vertical-align: top; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.cam-pop td.k { color: var(--muted); text-transform: uppercase; font-size: 0.6rem; letter-spacing: 0.05em; white-space: nowrap; width: 34%; font-family: 'Share Tech Mono', monospace; }
.cam-pop a { color: var(--cyan); word-break: break-all; }
.blip-pop { min-width: 200px; max-width: 260px; }
.blip-pop .blip-src { font-family: 'Share Tech Mono', monospace; font-size: 0.62rem; color: var(--cyan); text-transform: uppercase; letter-spacing: 0.08em; }
.blip-pop .blip-time { font-size: 0.7rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; margin: 0.2rem 0 0.3rem; }
2026-08-24 18:01:19 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Sub-views (News / Events / Alerts / ... ) ── */
.subview {
padding: 1.2rem clamp(1rem, 3vw, 2rem) 2.2rem;
max-width: 1280px; margin: 0 auto;
}
.subview > h2 {
font-family: 'Orbitron', sans-serif; font-size: 0.95rem; font-weight: 700;
letter-spacing: 0.16em; text-transform: uppercase; color: var(--text);
display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.35rem;
}
.subview > h2 .tick { color: var(--cyan); text-shadow: 0 0 10px var(--cyan-glow); }
.subview > .sub { font-size: 0.85rem; color: var(--muted); margin-bottom: 1.1rem; }
.panel {
background: linear-gradient(180deg, rgba(14,26,46,0.65), rgba(10,19,34,0.8));
border: 1px solid var(--line); border-radius: 6px; padding: 1rem 1.1rem;
box-shadow: 0 0 18px rgba(53,224,255,0.05);
}
.panel h3 {
font-size: 0.68rem; font-family: 'Orbitron', sans-serif; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.16em; color: var(--muted);
margin-bottom: 0.6rem;
}
table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
th, td { text-align: left; padding: 0.5rem 0.65rem; border-bottom: 1px solid var(--line); }
th { color: var(--muted); font-weight: 600; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.1em; font-family: 'Rajdhani', sans-serif; }
tr:hover td { background: rgba(53,224,255,0.04); }
.badge { display: inline-block; padding: 0.12rem 0.5rem; border-radius: 3px; font-size: 0.7rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; }
.badge-positive { background: rgba(83,240,165,0.12); color: var(--green); border: 1px solid rgba(83,240,165,0.35); }
.badge-negative { background: rgba(255,93,93,0.12); color: var(--red); border: 1px solid rgba(255,93,93,0.35); }
.badge-neutral { background: rgba(95,125,149,0.15); color: var(--muted); border: 1px solid var(--line); }
.badge-high, .badge-critical { background: rgba(255,93,93,0.15); color: var(--red); border: 1px solid rgba(255,93,93,0.4); }
.badge-medium { background: rgba(255,180,84,0.12); color: var(--amber); border: 1px solid rgba(255,180,84,0.35); }
.badge-low { background: rgba(83,240,165,0.1); color: var(--green); border: 1px solid rgba(83,240,165,0.3); }
/* ── Stats strip (Events view) ── */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.8rem; margin-bottom: 1.1rem; }
.stat-card { position: relative; overflow: hidden; }
.stat-card .value { font-family: 'Orbitron', sans-serif; font-size: 1.5rem; font-weight: 700; color: var(--text); letter-spacing: 0.04em; }
.stat-card .value.cyan { color: var(--cyan); text-shadow: 0 0 14px var(--cyan-glow); }
.stat-card .value.red { color: var(--red); text-shadow: 0 0 14px rgba(255,93,93,0.4); }
.stat-card::after {
content: ''; position: absolute; top: 0; right: 0; width: 22px; height: 22px;
border-top: 1px solid var(--cyan); border-right: 1px solid var(--cyan); opacity: 0.5;
}
.sentiment-bar { display: flex; height: 18px; border-radius: 3px; overflow: hidden; margin-top: 0.5rem; border: 1px solid var(--line); }
.sentiment-bar div { transition: width 0.4s; }
.search-box { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.search-box input {
flex: 1; background: var(--bg-1); border: 1px solid var(--line);
border-radius: 4px; padding: 0.55rem 0.9rem; color: var(--text);
font-size: 0.88rem; font-family: 'Rajdhani', sans-serif;
}
.search-box input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 10px var(--cyan-glow); }
/* ── News view ── */
2026-08-24 18:01:19 -04:00
.news-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; margin-bottom: 0.9rem; flex-wrap: wrap; }
.news-refresh { display: flex; align-items: center; gap: 0.6rem; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.news-updated { font-size: 0.7rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; }
2026-08-24 18:01:19 -04:00
.news-summary {
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
position: relative; background: linear-gradient(160deg, rgba(53,224,255,0.10), rgba(10,19,34,0.7) 45%);
border: 1px solid rgba(53,224,255,0.35); border-radius: 8px; padding: 1rem 1.25rem 1.1rem;
box-shadow: 0 0 26px rgba(53,224,255,0.12), inset 0 0 32px rgba(53,224,255,0.04);
2026-08-24 18:01:19 -04:00
margin-bottom: 1.2rem; overflow: hidden;
}
.news-summary::after {
content: ''; position: absolute; inset: 0; pointer-events: none;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
background: radial-gradient(140px 70px at 85% -10%, rgba(53,224,255,0.18), transparent 70%);
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.ns-kicker { font-family: 'Orbitron', sans-serif; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; color: var(--cyan); text-shadow: 0 0 12px rgba(53,224,255,0.6); display: flex; align-items: center; gap: 0.5rem; }
.ns-badge { font-size: 0.58rem; font-weight: 700; background: rgba(53,224,255,0.14); border: 1px solid rgba(53,224,255,0.4); border-radius: 3px; padding: 0.1rem 0.45rem; color: var(--cyan); letter-spacing: 0.1em; font-family: 'Share Tech Mono', monospace; }
.ns-time { font-size: 0.68rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; margin: 0.35rem 0 0.6rem; }
.ns-body { font-size: 0.9rem; line-height: 1.62; color: var(--text); white-space: pre-wrap; position: relative; z-index: 1; }
.ns-body h1, .ns-body h2, .ns-body h3, .ns-body h4 { font-size: 0.98rem; margin: 0.5rem 0 0.25rem; color: var(--cyan); font-family: 'Rajdhani', sans-serif; }
2026-08-24 18:01:19 -04:00
.ns-body ul, .ns-body ol { margin: 0.35rem 0 0.35rem 1.2rem; }
.ns-body li { margin-bottom: 0.2rem; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.ns-body code { background: rgba(3,6,9,0.6); border: 1px solid var(--line); border-radius: 3px; padding: 0.05rem 0.3rem; font-size: 0.85em; font-family: 'Share Tech Mono', monospace; }
2026-08-24 18:01:19 -04:00
.ns-empty { color: var(--muted); font-size: 0.85rem; font-style: italic; }
.ns-loading { color: var(--muted); font-size: 0.85rem; }
.ns-error { color: var(--red); font-size: 0.85rem; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.news-list-head { display: flex; justify-content: space-between; align-items: center; font-family: 'Orbitron', sans-serif; font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin-bottom: 0.4rem; }
.news-count { font-size: 0.66rem; color: var(--cyan); font-family: 'Share Tech Mono', monospace; }
.news-list { max-height: 52vh; min-height: 120px; overflow-y: auto; border: 1px solid var(--line); border-radius: 6px; background: rgba(6,11,20,0.4); }
2026-08-24 18:01:19 -04:00
.news-list::-webkit-scrollbar { width: 8px; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.news-list::-webkit-scrollbar-track { background: rgba(14,26,46,0.4); border-radius: 8px; }
.news-list::-webkit-scrollbar-thumb { background: rgba(53,224,255,0.3); border-radius: 8px; }
.news-list::-webkit-scrollbar-thumb:hover { background: rgba(53,224,255,0.5); }
.news-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.55rem 0.9rem; border-bottom: 1px solid rgba(20,50,74,0.5); text-decoration: none; color: var(--text); transition: background .15s, box-shadow .15s; }
2026-08-24 18:01:19 -04:00
.news-item:last-child { border-bottom: none; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
.news-item:hover { background: rgba(53,224,255,0.06); box-shadow: inset 2px 0 0 var(--cyan); }
.news-dom { font-size: 0.62rem; font-family: 'Share Tech Mono', monospace; color: var(--cyan); background: rgba(53,224,255,0.08); border: 1px solid rgba(53,224,255,0.25); border-radius: 3px; padding: 0.1rem 0.45rem; white-space: nowrap; flex-shrink: 0; max-width: 30%; overflow: hidden; text-overflow: ellipsis; }
.news-title { flex: 1; font-size: 0.86rem; line-height: 1.35; color: var(--text); word-break: break-word; }
.news-item:hover .news-title { color: var(--cyan); }
.news-time { font-size: 0.68rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; white-space: nowrap; flex-shrink: 0; }
.news-geo { color: var(--green); font-size: 0.8rem; flex-shrink: 0; text-shadow: 0 0 8px rgba(83,240,165,0.6); }
2026-08-24 18:01:19 -04:00
.news-loading, .news-error { padding: 1rem 0.9rem; color: var(--muted); font-size: 0.85rem; }
.news-error { color: var(--red); }
.news-empty { padding: 1rem 0.9rem; color: var(--muted); font-size: 0.85rem; font-style: italic; }
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Key cards (API Keys view) ── */
.key-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 1rem; }
.key-card { background: linear-gradient(180deg, rgba(14,26,46,0.65), rgba(10,19,34,0.8)); border: 1px solid var(--line); border-radius: 6px; padding: 1rem 1.1rem; box-shadow: 0 0 16px rgba(53,224,255,0.05); transition: box-shadow .2s, border-color .2s; }
.key-card:hover { box-shadow: 0 0 22px rgba(53,224,255,0.12); }
.key-card.set { border-color: rgba(83,240,165,0.45); box-shadow: 0 0 16px rgba(83,240,165,0.12); }
.key-head { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; margin-bottom: 0.35rem; }
.key-name { font-family: 'Share Tech Mono', monospace; font-size: 0.86rem; font-weight: 600; color: var(--cyan); word-break: break-all; }
.key-status { font-size: 0.68rem; font-family: 'Share Tech Mono', monospace; padding: 0.18rem 0.55rem; border-radius: 3px; white-space: nowrap; }
.key-status.set { background: rgba(83,240,165,0.1); color: var(--green); box-shadow: 0 0 10px rgba(83,240,165,0.3); }
.key-status.missing { background: rgba(255,93,93,0.1); color: var(--red); box-shadow: 0 0 10px rgba(255,93,93,0.2); }
.key-desc { font-size: 0.8rem; color: var(--muted); margin-bottom: 0.7rem; }
.key-hint { font-size: 0.7rem; color: var(--muted); opacity: 0.8; margin-top: 0.15rem; font-family: 'Share Tech Mono', monospace; }
.key-form { display: flex; gap: 0.5rem; }
.key-form input { flex: 1; background: var(--bg-0); border: 1px solid var(--line); border-radius: 4px; padding: 0.5rem 0.6rem; color: var(--text); font-size: 0.82rem; font-family: 'Share Tech Mono', monospace; }
.key-form input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.key-form .btn { white-space: nowrap; }
.key-form .btn-danger { border-color: rgba(255,93,93,0.5); color: var(--red); }
.key-form .btn-danger:hover { background: rgba(255,93,93,0.1); }
.key-msg { margin-top: 0.5rem; font-size: 0.78rem; min-height: 1rem; }
.key-msg.ok { color: var(--green); }
.key-msg.err { color: var(--red); }
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Settings view ── */
.set-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); gap: 1rem; align-items: start; }
.set-row { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.9rem; }
.set-row label { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); font-weight: 600; }
.set-row textarea, .set-row input[type="text"], .set-row select {
background: var(--bg-0); color: var(--text); border: 1px solid var(--line);
border-radius: 4px; padding: 0.5rem 0.6rem; font-size: 0.85rem;
font-family: 'Share Tech Mono', monospace; color-scheme: dark;
}
.set-row textarea:focus, .set-row input:focus, .set-row select:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.set-note { font-size: 0.72rem; color: var(--muted); line-height: 1.5; }
.set-note code { background: rgba(3,6,9,0.7); border: 1px solid var(--line); border-radius: 3px; padding: 0.05rem 0.35rem; font-size: 0.82em; }
.set-actions { display: flex; gap: 0.6rem; margin-top: 0.4rem; }
.set-saved { color: var(--green); font-size: 0.75rem; font-family: 'Share Tech Mono', monospace; min-height: 1.1rem; }
.sys-row { display: flex; justify-content: space-between; padding: 0.32rem 0; border-bottom: 1px dashed var(--line); font-size: 0.8rem; }
.sys-row:last-child { border-bottom: none; }
.sys-row .k { color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.68rem; }
.sys-row .v { font-family: 'Share Tech Mono', monospace; }
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ BOTTOM DOCK: tickers ═══════════════ */
.dock {
position: fixed; left: 0; right: 0; bottom: 0; z-index: 900;
height: var(--dock-h); display: flex; flex-direction: column;
border-top: 1px solid var(--line);
background: linear-gradient(180deg, var(--bg-1), #050a13);
box-shadow: 0 -6px 30px rgba(0,0,0,0.5);
}
.ticker { display: flex; align-items: stretch; height: 50%; min-height: 0; }
.ticker + .ticker { border-top: 1px solid rgba(20,50,74,0.6); }
.tick-label {
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
min-width: 74px; padding: 0 0.7rem; flex-shrink: 0;
font-family: 'Orbitron', sans-serif; font-size: 0.62rem; font-weight: 700;
letter-spacing: 0.2em; color: #02131c; background: var(--cyan);
text-shadow: none; position: relative;
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 100%, 0 100%);
}
.ticker.news .tick-label { background: var(--magenta); color: #1c0311; }
.tick-label .st { animation: pulse 1.6s infinite; }
.tick-wrap { flex: 1; overflow: hidden; position: relative; display: flex; align-items: center;
mask-image: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
-webkit-mask-image: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
}
.tick-track { display: flex; width: max-content; animation: marquee 60s linear infinite; }
.ticker.news .tick-track { animation-duration: 90s; }
.tick-wrap:hover .tick-track, .tick-wrap:focus-within .tick-track { animation-play-state: paused; }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.tick-group { display: flex; align-items: center; }
.tick-item {
display: inline-flex; align-items: center; gap: 0.5rem; white-space: nowrap;
font-size: 0.82rem; padding: 0 1rem; color: var(--text); text-decoration: none;
font-weight: 500;
}
.tick-item .sym { font-family: 'Share Tech Mono', monospace; color: var(--cyan); font-size: 0.74rem; }
.tick-item .price { font-family: 'Share Tech Mono', monospace; color: var(--text); }
.tick-item .chg.up { color: var(--green); }
.tick-item .chg.down { color: var(--red); }
.tick-item .chg.flat { color: var(--muted); }
.tick-item .dom { font-family: 'Share Tech Mono', monospace; font-size: 0.64rem; color: var(--cyan); background: rgba(53,224,255,0.08); border: 1px solid rgba(53,224,255,0.22); border-radius: 3px; padding: 0.05rem 0.4rem; }
.tick-item .tt { font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted); }
.tick-item .sep { color: var(--line-hi); }
.tick-item.brief {
cursor: pointer; background: linear-gradient(90deg, rgba(255,46,151,0.12), transparent 70%);
border-left: 2px solid var(--magenta); font-weight: 600;
}
.tick-item.brief .b-tag { font-family: 'Share Tech Mono', monospace; font-size: 0.6rem; color: #1c0311; background: var(--magenta); border-radius: 2px; padding: 0.08rem 0.4rem; letter-spacing: 0.1em; }
.tick-item.brief:hover { color: var(--magenta); }
.tick-item.standby { color: var(--muted); opacity: 0.75; }
.tick-item.standby .price { color: var(--muted); }
.tick-sep { color: var(--line-hi); }
.tick-tag {
display: flex; align-items: center; flex-shrink: 0; padding: 0 0.8rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.62rem;
letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
border-left: 1px solid var(--line);
}
.tick-tag.live { color: var(--green); text-shadow: 0 0 8px rgba(83,240,165,0.5); }
.tick-tag.standby { color: var(--amber); text-shadow: 0 0 8px rgba(255,180,84,0.4); animation: pulse 2.2s infinite; }
.tick-tag.err { color: var(--red); }
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Boot overlay ── */
.boot {
position: fixed; inset: 0; z-index: 9999; background: var(--bg-0);
display: flex; align-items: center; justify-content: center;
transition: opacity .5s ease; pointer-events: auto; cursor: pointer;
}
.boot.gone { opacity: 0; pointer-events: none; }
.boot-inner { text-align: center; }
.boot-mark { font-family: 'Orbitron', sans-serif; font-size: 2.2rem; color: var(--cyan); text-shadow: 0 0 24px var(--cyan-glow); margin-bottom: 0.6rem; }
.boot-title { font-family: 'Orbitron', sans-serif; font-size: 1.3rem; font-weight: 900; letter-spacing: 0.3em; color: #eafcff; }
.boot-title span { color: var(--cyan); }
.boot-sub { font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted); letter-spacing: 0.3em; margin-top: 0.5rem; }
.boot-bar { width: 220px; height: 3px; margin: 1.1rem auto 0; background: var(--bg-2); border-radius: 2px; overflow: hidden; }
.boot-bar i { display: block; height: 100%; width: 40%; background: linear-gradient(90deg, transparent, var(--cyan), transparent); animation: bootSweep 0.9s linear infinite; }
@keyframes bootSweep { from { transform: translateX(-100%); } to { transform: translateX(350%); } }
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
@media (max-width: 820px) {
:root { --topbar-h: 42px; --dock-h: 56px; }
.tick-label { min-width: 56px; padding: 0 0.5rem; }
.tick-tag { display: none; }
.hud-chips { display: none; }
.map-hint { max-width: 80%; }
}
@media (prefers-reduced-motion: reduce) {
.tick-track { animation: none; }
.dot, .tick-label .st, .tick-tag.standby { animation: none; }
.nav-menu.open { animation: none; }
.boot-bar i { animation: none; }
}
< / style >
< / head >
< body >
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- Boot splash (one - time, click to skip) -->
< div id = "boot" class = "boot" role = "presentation" aria-hidden = "true" >
< div class = "boot-inner" >
< div class = "boot-mark" > ◢◤< / div >
< div class = "boot-title" > OSINT< span > //< / span > DASHBOARD< / div >
< div class = "boot-sub" > SECURE TERMINAL INITIALIZING< / div >
< div class = "boot-bar" > < i > < / i > < / div >
2026-06-04 20:30:04 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / div >
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- ═════════════ TOP BAR ═════════════ -->
< header class = "topbar chrome" >
< div class = "brand" >
< span class = "brand-mark" > ◢◤< / span >
< div >
< div class = "brand-name" > OSINT< span class = "accent" > //< / span > DASHBOARD< / div >
< div class = "brand-sub" > GLOBAL SITUATIONAL AWARENESS TERMINAL< / div >
< / div >
< / div >
< div class = "topbar-right" >
< div class = "sys-status" >
< span id = "health-dot" class = "dot dim" > < / span >
< span id = "health-text" > BOOT…< / span >
< span class = "sep" > |< / span >
< span class = "clock" > < span id = "utc-clock" > --:--:--< / span > UTC< / span >
< span class = "sep hide-sm" > |< / span >
< span class = "hide-sm" id = "last-update" > —< / span >
< / div >
< div class = "nav" >
< button id = "nav-btn" class = "nav-btn" aria-haspopup = "true" aria-expanded = "false" >
MENU < span class = "nav-caret" > ▾< / span >
< / button >
< div id = "nav-menu" class = "nav-menu" role = "menu" aria-label = "Application navigation" >
< button class = "nav-item primary" role = "menuitem" data-view = "map" > < span class = "ico" > ◈< / span > Live Map< / button >
< button class = "nav-item primary" role = "menuitem" data-view = "news" > < span class = "ico" > ◉< / span > News Brief< / button >
< div class = "nav-sep" role = "separator" > < / div >
< button class = "nav-item" role = "menuitem" data-view = "events" > < span class = "ico" > ≣< / span > Events< / button >
< button class = "nav-item" role = "menuitem" data-view = "alerts" > < span class = "ico" > ⚠< / span > Alerts< / button >
< button class = "nav-item" role = "menuitem" data-view = "entities" > < span class = "ico" > ◎< / span > Entities< / button >
< button class = "nav-item" role = "menuitem" data-view = "ingest" > < span class = "ico" > ⇪< / span > Ingest< / button >
< button class = "nav-item" role = "menuitem" data-view = "keys" > < span class = "ico" > ⚿< / span > API Keys< / button >
< div class = "nav-sep" role = "separator" > < / div >
< button class = "nav-item" role = "menuitem" data-view = "settings" > < span class = "ico" > ⚙< / span > Settings< / button >
2026-06-04 20:30:04 -04:00
< / div >
< / div >
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / header >
2026-06-04 20:30:04 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- ═════════════ STAGE ═════════════ -->
< main class = "stage" id = "stage" >
Add NASA FIRMS active-fire ingest + /api/fires; API keys management page
Coherent merge of two coordinated features on the shared working tree:
FIRMS fire heatmap (backend, t_6e404c14):
- app/fire_sources.py: fetch FIRMS VIIRS area CSV (free MAP_KEY) -> NATS events.fire
- fires hypertable (TimescaleDB, 1-day chunks) with natural-key PK
(latitude, longitude, acq_time, satellite); idempotent ON CONFLICT DO NOTHING
- alembic/versions/002_fires.py; GET /api/fires?bbox=&since= (JSON only)
- POST /api/ingest/fires; ~15 min poll loop (FIRMS_INTERVAL=900) in ingester
- env-driven config (FIRMS_MAP_KEY/DATASET/BBOX/INTERVAL); docs/firms.md covers
the zero-cost GIBS VIIRS_SNPP_Thermal_Anomalies_375m_All tile alternative
- 18 tests (parser, mapping, idempotency, API contract) verified vs real
TimescaleDB+PostGIS (localhost/osint-dashboard-pg image)
API keys page (frontend, t_4433cff2):
- app/keystore.py: api_keys table (self-creating), FIRMS/GEMINI/TELEGRAM
registry with format validation, ****last4 masking, get_api_key()
- GET/POST/DELETE /api/keys (never returns full values); Keys tab in index.html
DB_NULL_POOL env switch in app/database.py enables a NullPool for tests /
short-lived processes that open a fresh event loop per unit.
2026-08-24 15:37:42 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- ───────────── VIEW: LIVE MAP (landing) ───────────── -->
< section class = "view active" id = "view-map" aria-label = "Live map" >
2026-08-24 17:35:44 -04:00
< div class = "map-toolbar" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< span class = "mt-label" > Layer< / span >
< select id = "map-layer" onchange = "mapLayerChanged()" aria-label = "Satellite basemap layer" > < / select >
< span class = "mt-label" id = "map-time-label" style = "display:none" > UTC< / span >
< input type = "date" id = "map-date" style = "display:none" onchange = "mapDateChanged()" aria-label = "Imagery date" >
2026-08-24 17:35:44 -04:00
< button class = "btn" id = "map-latest-btn" onclick = "mapGoLatest()" style = "display:none" > Latest< / button >
< button class = "btn" id = "map-7d-btn" onclick = "mapGoDays(7)" style = "display:none" > -7d< / button >
< button class = "btn" id = "map-30d-btn" onclick = "mapGoDays(30)" style = "display:none" > -30d< / button >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< button class = "btn" id = "map-reset" onclick = "mapResetView()" > Reset< / button >
2026-08-24 17:35:44 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "map-stage" id = "map-wrap" >
2026-08-24 18:02:56 -04:00
< div id = "map" > < / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- Layer control panel -->
2026-08-24 18:02:56 -04:00
< div id = "layer-panel" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "lp-head" onclick = "toggleLayerPanel()" role = "button" tabindex = "0" aria-expanded = "true"
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleLayerPanel();}">
◈ Layers < span class = "lp-caret" > ▼< / span >
< / div >
2026-08-24 18:02:56 -04:00
< div class = "lp-body" >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-base-on" checked onchange = "toggleBase()" > < span class = "lp-dot base" > < / span > Basemap< / label >
< span class = "lp-count" id = "lp-base-count" > GIBS< / span >
< / div >
< div class = "lp-opacity" > Opacity < input type = "range" id = "lp-base-opacity" min = "5" max = "100" value = "100" oninput = "setBaseOpacity(this.value)" > < b id = "lp-base-val" > 100%< / b > < / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-fires-on" checked onchange = "toggleFires()" > < span class = "lp-dot fires" > < / span > FIRMS Fires< / label >
< span class = "lp-count" id = "lp-fires-count" > 0< / span >
< / div >
< div class = "lp-opacity" > Opacity < input type = "range" id = "lp-fires-opacity" min = "5" max = "100" value = "65" oninput = "setFiresOpacity(this.value)" > < b id = "lp-fires-val" > 65%< / b > < / div >
< div class = "lp-sub" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< label style = "display:flex;align-items:center;gap:0.35rem;font-size:0.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;" > Color
2026-08-24 18:02:56 -04:00
< select class = "lp-select" id = "lp-fires-color" onchange = "setFiresColor()" >
< option value = "brightness" > Brightness< / option >
< option value = "confidence" > Confidence< / option >
< / select >
< / label >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< label style = "display:flex;align-items:center;gap:0.35rem;font-size:0.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;" > Since
2026-08-24 18:02:56 -04:00
< select class = "lp-select" id = "lp-fires-since" onchange = "setFiresSince()" >
< option value = "" > All time< / option >
< option value = "6h" > Last 6h< / option >
< option value = "24h" selected > Last 24h< / option >
< option value = "48h" > Last 48h< / option >
< option value = "7d" > Last 7d< / option >
< option value = "30d" > Last 30d< / option >
< / select >
< / label >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-cams-on" checked onchange = "toggleCams()" > < span class = "lp-dot cams" > < / span > Open Cameras< / label >
< span class = "lp-count" id = "lp-cams-count" > 0< / span >
< / div >
< div class = "lp-opacity" > Opacity < input type = "range" id = "lp-cams-opacity" min = "5" max = "100" value = "100" oninput = "setCamsOpacity(this.value)" > < b id = "lp-cams-val" > 100%< / b > < / div >
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-blips-on" checked onchange = "toggleBlips()" > < span class = "lp-dot blips" > < / span > Event Blips< / label >
< span class = "lp-count" id = "lp-blips-count" > 0< / span >
< / div >
< div class = "lp-sub" >
< label style = "display:flex;align-items:center;gap:0.35rem;font-size:0.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;" > Since
< select class = "lp-select" id = "lp-blips-since" onchange = "setBlipsSince()" >
< option value = "6h" > Last 6h< / option >
< option value = "24h" selected > Last 24h< / option >
< option value = "48h" > Last 48h< / option >
< option value = "7d" > Last 7d< / option >
< option value = "" > All time< / option >
< / select >
< / label >
< / div >
< div class = "lp-note" > Geolocated events from the ingest pipeline. Color = source type.< / div >
< / div >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-radar-on" checked onchange = "toggleRadar()" > < span class = "lp-dot radar" > < / span > Radar< / label >
< span class = "lp-count" id = "lp-radar-count" > IEM< / span >
< / div >
< div class = "lp-opacity" > Opacity < input type = "range" id = "lp-radar-opacity" min = "5" max = "100" value = "70" oninput = "setRadarOpacity(this.value)" > < b id = "lp-radar-val" > 70%< / b > < / div >
< div class = "lp-note" > IEM NEXRAD over CONUS, RainViewer elsewhere. Tiles load in the browser.< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-alerts-on" checked onchange = "toggleWxAlerts()" > < span class = "lp-dot weather" > < / span > NWS / SBW Alerts< / label >
< span class = "lp-count" id = "lp-alerts-count" > 0< / span >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-thermal-on" onchange = "toggleThermal()" > < span class = "lp-dot thermal" > < / span > GIBS Thermal< / label >
< span class = "lp-count" id = "lp-thermal-count" > VIIRS< / span >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-perim-on" checked onchange = "togglePerimeters()" > < span class = "lp-dot wfigs" > < / span > WFIGS Perimeters< / label >
< span class = "lp-count" id = "lp-perim-count" > 0< / span >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-incidents-on" onchange = "toggleIncidents()" > < span class = "lp-dot wfigs" > < / span > WFIGS Incidents< / label >
< span class = "lp-count" id = "lp-incidents-count" > 0< / span >
< / div >
< / div >
< div class = "lp-layer" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "lp-row" >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< label class = "lp-name" > < input type = "checkbox" id = "lp-ac-on" checked onchange = "toggleAircraft()" > < span class = "lp-dot flights" > < / span > Aircraft< / label >
< span class = "lp-count" id = "lp-ac-count" > 0< / span >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / div >
< / div >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< div class = "lp-layer" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "lp-row" >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< label class = "lp-name" > < input type = "checkbox" id = "lp-trains-on" checked onchange = "toggleTrains()" > < span class = "lp-dot trains" > < / span > Trains< / label >
< span class = "lp-count" id = "lp-trains-count" > 0< / span >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / div >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< div class = "lp-note" > Amtraker · ODC-By< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / div >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< div class = "lp-layer" >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "lp-row" >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< label class = "lp-name" > < input type = "checkbox" id = "lp-vessels-on" onchange = "toggleVessels()" > < span class = "lp-dot vessels" > < / span > Vessels · AIS< / label >
< span class = "lp-count" id = "lp-vessels-count" > 0< / span >
< / div >
< div class = "lp-note" > Needs AISSTREAM_API_KEY in Keys.< / div >
< / div >
2026-08-28 09:33:19 -04:00
< div class = "lp-layer" >
< div class = "lp-row" >
< button class = "btn" id = "gf-draw" type = "button" onclick = "toggleGeofenceDraw()" > Draw geofence< / button >
< / div >
< div class = "lp-note" > No Leaflet.Draw — click vertices, double-click to close. Saved to POST /api/geofences.< / div >
< / div >
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
< div class = "lp-layer" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-storms-on" checked onchange = "toggleStorms()" > < span class = "lp-dot storms" > < / span > NHC Storms< / label >
< span class = "lp-count" id = "lp-storms-count" > 0< / span >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / div >
< / div >
2026-08-24 18:02:56 -04:00
< div class = "lp-legend" >
< h4 > Fire heat intensity< / h4 >
< div class = "lp-ramp" > < / div >
< div class = "lp-ramp-scale" > < span > cool / low< / span > < span > hot / high< / span > < / div >
< div class = "lp-heat-legend" id = "lp-heat-legend" > < / div >
< / div >
< / div >
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "map-hint" id = "map-hint" > Initializing…< / div >
< div class = "hud-chips" id = "hud-chips" >
< div class = "hud-chip" id = "hud-fires" > < span class = "c fires" > < / span > FIRMS < b id = "hud-fires-n" > —< / b > < / div >
< div class = "hud-chip" id = "hud-cams" > < span class = "c cams" > < / span > CAMS < b id = "hud-cams-n" > —< / b > < / div >
< div class = "hud-chip" id = "hud-blips" > < span class = "c blips" > < / span > BLIPS < b id = "hud-blips-n" > —< / b > < / div >
< / div >
2026-08-28 09:33:19 -04:00
< div id = "dvr-bar" class = "live" >
< button class = "btn" id = "dvr-live" type = "button" onclick = "dvrGoLive()" > LIVE< / button >
< input type = "range" id = "dvr-slider" min = "0" max = "360" value = "360" oninput = "dvrScrub(this.value)" aria-label = "DVR time" >
< span id = "dvr-label" > live< / span >
< / div >
< div id = "dvr-toast" > < / div >
2026-08-24 18:02:56 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / section >
<!-- ───────────── VIEW: NEWS BRIEF ───────────── -->
< section class = "view" id = "view-news" aria-label = "News brief" >
< div class = "subview" >
< h2 > < span class = "tick" > ◉< / span > Global News Brief< / h2 >
< p class = "sub" > Scraped on the 15-minute cycle · latest LLM executive summary pinned above< / p >
< div class = "news-head" >
< div class = "news-refresh" >
< span class = "news-updated" id = "news-updated" > —< / span >
< button class = "btn" id = "news-refresh-btn" onclick = "loadNews(true)" > Refresh< / button >
< / div >
< / div >
< div class = "news-summary" id = "news-summary-wrap" >
< div class = "ns-kicker" > Executive Summary < span class = "ns-badge" id = "ns-badge" > WAITING< / span > < / div >
< div class = "ns-time" id = "ns-time" > —< / div >
< div class = "ns-body" id = "ns-body" >
< div class = "ns-empty" > No executive summary yet — the Gemini summarizer is idle until < code > GEMINI_API_KEY< / code > is set on the Pi.< / div >
< / div >
< / div >
< div class = "news-list-head" >
< span > Recent Headlines< / span >
< span class = "news-count" id = "news-count" > 0 articles< / span >
< / div >
< div class = "news-list" id = "news-list" >
< div class = "news-loading" > Loading feed…< / div >
< / div >
< / div >
< / section >
<!-- ───────────── VIEW: EVENTS ───────────── -->
< section class = "view" id = "view-events" aria-label = "Events" >
< div class = "subview" >
< h2 > < span class = "tick" > ≣< / span > Event Stream< / h2 >
< p class = "sub" > Ingested intel events across all sources< / p >
< div class = "stat-grid" id = "summary-cards" >
< div class = "panel stat-card" > < h3 > Total Events< / h3 > < div class = "value" id = "total-events" > -< / div > < / div >
< div class = "panel stat-card" > < h3 > Events (24h)< / h3 > < div class = "value cyan" id = "events-24h" > -< / div > < div class = "set-note" style = "margin-top:0.2rem" > last 24 hours< / div > < / div >
< div class = "panel stat-card" > < h3 > Active Sources< / h3 > < div class = "value" id = "active-sources" > -< / div > < / div >
< div class = "panel stat-card" > < h3 > Open Alerts< / h3 > < div class = "value red" id = "open-alerts" > -< / div > < / div >
< div class = "panel stat-card" > < h3 > Tracked Entities< / h3 > < div class = "value" id = "tracked-entities" > -< / div > < / div >
< div class = "panel stat-card" >
< h3 > Sentiment (24h)< / h3 >
< div class = "sentiment-bar" >
< div id = "sent-pos" style = "background:var(--green)" > < / div >
< div id = "sent-neu" style = "background:var(--muted)" > < / div >
< div id = "sent-neg" style = "background:var(--red)" > < / div >
< / div >
< div class = "set-note" style = "margin-top:0.35rem" id = "sent-detail" > < / div >
< / div >
< / div >
2026-08-24 17:35:44 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "panel" style = "margin-bottom:1.1rem" >
< h3 > Search Events< / h3 >
< div class = "search-box" >
< input type = "text" id = "search-q" placeholder = "Search events by keyword…" onkeydown = "if(event.key==='Enter')searchEvents()" / >
< button class = "btn" onclick = "searchEvents()" > Search< / button >
< / div >
2026-08-24 18:01:19 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "panel" id = "search-results" style = "display:none;margin-bottom:1.1rem" >
< h3 > Search Results (< span id = "search-total" > 0< / span > )< / h3 >
< table >
< thead > < tr > < th > Time< / th > < th > Source< / th > < th > Title< / th > < th > Sentiment< / th > < th > Location< / th > < / tr > < / thead >
< tbody id = "search-body" > < / tbody >
< / table >
< / div >
< div class = "panel" >
< h3 > Recent Events< / h3 >
< table >
< thead > < tr > < th > Time< / th > < th > Source< / th > < th > Title< / th > < th > Sentiment< / th > < th > Location< / th > < / tr > < / thead >
< tbody id = "events-body" > < / tbody >
< / table >
2026-08-24 18:01:19 -04:00
< / div >
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / section >
2026-08-24 18:01:19 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- ───────────── VIEW: ALERTS ───────────── -->
< section class = "view" id = "view-alerts" aria-label = "Alerts" >
< div class = "subview" >
< h2 > < span class = "tick" > ⚠< / span > Open Alerts< / h2 >
< p class = "sub" > Rule-triggered alerts from entity mentions, sentiment shifts, geo proximity & thresholds< / p >
< div class = "panel" >
< table >
< thead > < tr > < th > Time< / th > < th > Severity< / th > < th > Type< / th > < th > Title< / th > < / tr > < / thead >
< tbody id = "alerts-body" > < / tbody >
< / table >
2026-08-24 18:01:19 -04:00
< / div >
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / section >
2026-08-24 18:01:19 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- ───────────── VIEW: ENTITIES ───────────── -->
< section class = "view" id = "view-entities" aria-label = "Entities" >
< div class = "subview" >
< h2 > < span class = "tick" > ◎< / span > Tracked Entities< / h2 >
< p class = "sub" > People, organizations, locations, topics & assets of interest< / p >
< div class = "panel" >
< table >
< thead > < tr > < th > Name< / th > < th > Type< / th > < th > Events< / th > < th > Last Seen< / th > < / tr > < / thead >
< tbody id = "entities-body" > < / tbody >
< / table >
< / div >
2026-08-24 18:01:19 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / section >
<!-- ───────────── VIEW: INGEST ───────────── -->
< section class = "view" id = "view-ingest" aria-label = "Ingest" >
< div class = "subview" >
< h2 > < span class = "tick" > ⇪< / span > Data Ingestion< / h2 >
< p class = "sub" > Manually trigger feed pulls & NATS processing< / p >
< div class = "stat-grid" >
< div class = "panel" >
< h3 > RSS Feed< / h3 >
< div style = "margin-top:0.5rem;display:flex;gap:0.5rem" >
< input type = "text" id = "rss-url" placeholder = "https://example.com/feed" style = "flex:1;background:var(--bg-0);border:1px solid var(--line);border-radius:4px;padding:0.45rem;color:var(--text);font-size:0.85rem" >
< button class = "btn" onclick = "ingestRSS()" > Fetch< / button >
< / div >
< / div >
< div class = "panel" >
< h3 > GDELT Articles< / h3 >
< p class = "set-note" style = "margin:0.5rem 0" > Global news monitoring< / p >
< button class = "btn" onclick = "ingestGDELT()" > Fetch Latest< / button >
< / div >
< div class = "panel" >
< h3 > Earthquakes (USGS)< / h3 >
< p class = "set-note" style = "margin:0.5rem 0" > Last hour of seismic data< / p >
< button class = "btn" onclick = "ingestEarthquakes()" > Fetch Latest< / button >
< / div >
< div class = "panel" >
< h3 > NATS Consumer< / h3 >
< p class = "set-note" style = "margin:0.5rem 0" > Process pending messages< / p >
< button class = "btn" onclick = "processNATS()" > Process< / button >
< / div >
< / div >
< div class = "set-note" id = "ingest-result" style = "margin-top:0.6rem" > < / div >
2026-08-24 18:01:19 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< / section >
2026-08-24 18:01:19 -04:00
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
<!-- ───────────── VIEW: API KEYS ───────────── -->
< section class = "view" id = "view-keys" aria-label = "API keys" >
< div class = "subview" >
< h2 > < span class = "tick" > ⚿< / span > API Keys< / h2 >
2026-08-27 22:55:51 -04:00
< p class = "sub" > Keys used by ingest services (FIRMS, Nous Portal, Telegram, AIS, …). Stored in Postgres, never shown again — only status + last 4 chars.< / p >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "key-grid" id = "keys-grid" > < / div >
< / div >
< / section >
<!-- ───────────── VIEW: SETTINGS ───────────── -->
< section class = "view" id = "view-settings" aria-label = "Settings" >
< div class = "subview" >
< h2 > < span class = "tick" > ⚙< / span > Settings< / h2 >
< p class = "sub" > Terminal preferences — stored locally in this browser< / p >
< div class = "set-grid" >
< div class = "panel" >
< h3 > Market Data Stream< / h3 >
< p class = "set-note" style = "margin-bottom:0.9rem" > Space reserved for the stock / futures ticker. The backend feed is not wired yet — when < code > GET /api/market< / code > returns < code > {"symbols": [{"symbol","price","change","change_pct","updated_at"}]}< / code > , the bottom ticker activates automatically.< / p >
< div class = "set-row" >
< label for = "set-symbols" > Tracked symbols (comma separated)< / label >
< textarea id = "set-symbols" rows = "3" spellcheck = "false" > ^GSPC, ^IXIC, ^DJI, ES=F, NQ=F, YM=F, RTY=F, GC=F, CL=F, BTC-USD, ETH-USD, EURUSD=X< / textarea >
< / div >
< div class = "set-actions" >
< button class = "btn" onclick = "saveMarketSettings()" > Save symbols< / button >
< button class = "btn" onclick = "resetMarketSettings()" > Reset< / button >
< / div >
< div class = "set-saved" id = "mkt-saved" > < / div >
< / div >
< div class = "panel" >
< h3 > Map Defaults< / h3 >
< p class = "set-note" style = "margin-bottom:0.9rem" > Applied to the live map on load.< / p >
< div class = "set-row" >
< label for = "set-fires-since" > Fires · since< / label >
< select id = "set-fires-since" >
< option value = "" > All time< / option >
< option value = "6h" > Last 6h< / option >
< option value = "24h" > Last 24h< / option >
< option value = "48h" > Last 48h< / option >
< option value = "7d" > Last 7d< / option >
< option value = "30d" > Last 30d< / option >
< / select >
< / div >
< div class = "set-row" >
< label for = "set-blips-since" > Event blips · since< / label >
< select id = "set-blips-since" >
< option value = "6h" > Last 6h< / option >
< option value = "24h" > Last 24h< / option >
< option value = "48h" > Last 48h< / option >
< option value = "7d" > Last 7d< / option >
< option value = "" > All time< / option >
< / select >
< / div >
< div class = "set-row" >
< label > Layers on by default< / label >
< div style = "display:flex;gap:1.2rem;font-size:0.82rem" >
< label style = "display:flex;align-items:center;gap:0.4rem" > < input type = "checkbox" id = "set-cams-on" checked style = "accent-color:var(--cyan)" > Cameras< / label >
< label style = "display:flex;align-items:center;gap:0.4rem" > < input type = "checkbox" id = "set-blips-on" checked style = "accent-color:var(--cyan)" > Blips< / label >
< label style = "display:flex;align-items:center;gap:0.4rem" > < input type = "checkbox" id = "set-fires-on" checked style = "accent-color:var(--cyan)" > Fires< / label >
< / div >
< / div >
< div class = "set-actions" >
< button class = "btn" onclick = "saveMapSettings()" > Save defaults< / button >
< button class = "btn" onclick = "resetMapSettings()" > Reset< / button >
< / div >
< div class = "set-saved" id = "map-saved" > < / div >
< / div >
< div class = "panel" >
< h3 > System< / h3 >
< div id = "sys-info" >
< div class = "sys-row" > < span class = "k" > API status< / span > < span class = "v" id = "sys-health" > —< / span > < / div >
< div class = "sys-row" > < span class = "k" > Total events< / span > < span class = "v" id = "sys-events" > —< / span > < / div >
< div class = "sys-row" > < span class = "k" > Active sources< / span > < span class = "v" id = "sys-sources" > —< / span > < / div >
< div class = "sys-row" > < span class = "k" > Open alerts< / span > < span class = "v" id = "sys-alerts" > —< / span > < / div >
< div class = "sys-row" > < span class = "k" > Tracked entities< / span > < span class = "v" id = "sys-entities" > —< / span > < / div >
< div class = "sys-row" > < span class = "k" > News cycle< / span > < span class = "v" > 15 min< / span > < / div >
< div class = "sys-row" > < span class = "k" > Market feed< / span > < span class = "v" id = "sys-market" > STANDBY< / span > < / div >
< / div >
< div class = "set-actions" >
< button class = "btn" onclick = "loadSummary();checkHealth();renderSysInfo()" > Refresh< / button >
< / div >
< / div >
< / div >
< / div >
< / section >
< / main >
<!-- ═════════════ BOTTOM DOCK: TICKERS ═════════════ -->
< footer class = "dock chrome" >
< div class = "ticker market" aria-label = "Market data ticker" >
< div class = "tick-label" > < span class = "st" > ●< / span > MKT< / div >
< div class = "tick-wrap" >
< div class = "tick-track" id = "mkt-track" > < / div >
< / div >
< div class = "tick-tag standby" id = "mkt-tag" > STANDBY< / div >
2026-06-04 20:30:04 -04:00
< / div >
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
< div class = "ticker news" aria-label = "Breaking news ticker" >
< div class = "tick-label" > < span class = "st" > ●< / span > NEWS< / div >
< div class = "tick-wrap" >
< div class = "tick-track" id = "nt-track" > < / div >
< / div >
< div class = "tick-tag" id = "nt-tag" > 15-MIN CYCLE< / div >
< / div >
< / footer >
2026-06-04 20:30:04 -04:00
2026-08-24 17:35:44 -04:00
< script src = "/static/vendor/leaflet/leaflet.js" > < / script >
2026-08-27 17:00:09 -04:00
< script src = "/static/vendor/leaflet/leaflet.markercluster.js" > < / script >
2026-08-24 18:02:56 -04:00
< script src = "/static/vendor/leaflet/leaflet.heat.js" > < / script >
2026-06-04 20:30:04 -04:00
< script >
const API = '';
2026-08-27 21:12:58 -04:00
function loadHls() {
if (window.Hls) return Promise.resolve();
if (window._hlsLoading) return window._hlsLoading;
window._hlsLoading = new Promise((res, rej) => {
const s = document.createElement('script');
s.src = '/static/vendor/hls/hls.min.js';
s.onload = res;
s.onerror = rej;
document.head.appendChild(s);
});
return window._hlsLoading;
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ BOOT SPLASH ═══════════════ */
(function () {
const boot = document.getElementById('boot');
const skip = () => boot.classList.add('gone');
const seen = sessionStorage.getItem('osint.booted');
if (seen || window.matchMedia('(prefers-reduced-motion: reduce)').matches) { skip(); }
else {
boot.addEventListener('click', skip);
setTimeout(skip, 1100);
sessionStorage.setItem('osint.booted', '1');
}
})();
/* ═══════════════ TOP BAR: clock + health ═══════════════ */
function tickClock() {
const el = document.getElementById('utc-clock');
if (el) el.textContent = new Date().toISOString().slice(11, 19);
}
tickClock();
setInterval(tickClock, 1000);
async function checkHealth() {
const dot = document.getElementById('health-dot');
const txt = document.getElementById('health-text');
try {
const r = await fetch(`${API}/api/health`);
const d = await r.json();
if (r.ok) {
dot.className = 'dot ok';
txt.textContent = 'SYSTEMS OK';
} else {
dot.className = 'dot bad';
txt.textContent = 'DEGRADED';
}
document.getElementById('last-update').textContent = new Date().toLocaleTimeString();
} catch (e) {
dot.className = 'dot bad';
txt.textContent = 'UNREACHABLE';
document.getElementById('last-update').textContent = '—';
}
}
/* ═══════════════ NAV (dropdown) ═══════════════ */
const VIEWS = ['map', 'news', 'events', 'alerts', 'entities', 'ingest', 'keys', 'settings'];
function toggleNav(force) {
const btn = document.getElementById('nav-btn');
const menu = document.getElementById('nav-menu');
const open = force !== undefined ? force : !menu.classList.contains('open');
menu.classList.toggle('open', open);
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
if (open) menu.querySelector('.nav-item').focus();
}
function closeNav() { toggleNav(false); }
function initNav() {
const btn = document.getElementById('nav-btn');
const menu = document.getElementById('nav-menu');
btn.addEventListener('click', (e) => { e.stopPropagation(); toggleNav(); });
document.addEventListener('click', (e) => {
// Only close when the menu is open AND the click landed outside both
// the button and the menu. (A stray synthetic/double click on < body >
// must not instantly undo the open.)
if (!menu.classList.contains('open')) return;
if (!menu.contains(e.target) & & !btn.contains(e.target)) closeNav();
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') closeNav();
if (e.key === 'ArrowDown' & & menu.classList.contains('open')) {
e.preventDefault();
const items = [...menu.querySelectorAll('.nav-item')];
const idx = items.indexOf(document.activeElement);
items[(idx + 1) % items.length].focus();
}
if (e.key === 'ArrowUp' & & menu.classList.contains('open')) {
e.preventDefault();
const items = [...menu.querySelectorAll('.nav-item')];
const idx = items.indexOf(document.activeElement);
items[(idx - 1 + items.length) % items.length].focus();
}
});
menu.querySelectorAll('.nav-item').forEach(it => {
it.addEventListener('click', () => { showView(it.dataset.view); closeNav(); });
});
}
function showView(name) {
if (!VIEWS.includes(name)) name = 'map';
VIEWS.forEach(v => {
document.getElementById('view-' + v).classList.toggle('active', v === name);
});
document.getElementById('stage').classList.toggle('scroll', name !== 'map');
if (name === 'alerts') loadAlerts();
if (name === 'entities') loadEntities();
if (name === 'keys') loadKeys();
if (name === 'map') {
initMap();
// Leaflet was display:none — re-measure, then refresh live overlays.
if (map) {
setTimeout(() => { map.invalidateSize(); }, 60);
if (firesOn) loadFires();
if (camsOn) loadCams();
if (blipsOn) loadBlips();
}
}
if (name === 'news') loadNews(false);
2026-08-27 21:12:58 -04:00
if (name === 'events') { loadSummary(); loadEvents(); }
if (name === 'settings') { loadSummary(); renderSysInfo(); }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
window.scrollTo(0, 0);
}
/* ═══════════════ MARKET TICKER (space reserved — backend stream later) ═══
Contract for the future backend feed:
GET /api/market → {"symbols": [
{"symbol": "^GSPC", "price": 6123.4, "change": 18.2, "change_pct": 0.30, "updated_at": "2026-08-27T16:00:00Z"}
]}
Until the endpoint answers 200, the ticker renders the configured symbols
in STANDBY (dimmed "--" quotes). The strip auto-promotes to LIVE the
moment the backend stream appears. */
const MARKET_POLL_MS = 15000;
let marketState = 'standby'; // standby | live | err
let marketSymbols = [];
const DEFAULT_SYMBOLS = ['^GSPC', '^IXIC', '^DJI', 'ES=F', 'NQ=F', 'YM=F', 'RTY=F', 'GC=F', 'CL=F', 'BTC-USD', 'ETH-USD', 'EURUSD=X'];
function loadMarketSymbols() {
try {
const raw = localStorage.getItem('osint.marketSymbols');
marketSymbols = raw ? raw.split(',').map(s => s.trim()).filter(Boolean) : DEFAULT_SYMBOLS.slice();
} catch (e) { marketSymbols = DEFAULT_SYMBOLS.slice(); }
}
function marketItem(sym, price, chg, pct, standby) {
const cls = standby ? 'standby' : '';
const pr = price != null ? price.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '--.--';
if (standby) return `< span class = "tick-item standby" > < span class = "sym" > ${esc(sym)}< / span > < span class = "price" > ${pr}< / span > < span class = "sep" > ▸< / span > < / span > `;
const dir = chg > 0.0001 ? 'up' : chg < -0.0001 ? ' down ' : ' flat ' ;
const sign = chg > 0 ? '+' : '';
return `< span class = "tick-item" > < span class = "sym" > ${esc(sym)}< / span > < span class = "price" > ${pr}< / span > ` +
`< span class = "chg ${dir}" > ${sign}${chg.toFixed(2)} (${sign}${pct.toFixed(2)}%)< / span > < span class = "sep" > ▸< / span > < / span > `;
}
function buildMarketTrack(standby) {
const track = document.getElementById('mkt-track');
const group = marketSymbols.length
? marketSymbols.map(s => marketItem(s, null, 0, 0, true)).join('')
: DEFAULT_SYMBOLS.map(s => marketItem(s, null, 0, 0, true)).join('');
// Twin groups for a seamless loop.
track.innerHTML = `< span class = "tick-group" > ${group}< / span > < span class = "tick-group" > ${group}< / span > `;
}
function setMarketTag(state) {
const tag = document.getElementById('mkt-tag');
const sys = document.getElementById('sys-market');
tag.className = 'tick-tag ' + state;
if (state === 'live') { tag.textContent = 'LIVE'; if (sys) sys.textContent = 'LIVE'; }
else if (state === 'err') { tag.textContent = 'FEED ERR'; if (sys) sys.textContent = 'ERR'; }
else { tag.textContent = 'STANDBY'; if (sys) sys.textContent = 'STANDBY'; }
}
async function probeMarket() {
try {
const r = await fetch(`${API}/api/market`, { cache: 'no-store' });
if (!r.ok) throw new Error('http ' + r.status);
const d = await r.json();
const syms = Array.isArray(d & & d.symbols) ? d.symbols : [];
if (!syms.length) throw new Error('empty feed');
marketState = 'live';
setMarketTag('live');
const track = document.getElementById('mkt-track');
const group = syms.map(s => marketItem(
s.symbol, s.price, s.change, s.change_pct, false
)).join('');
track.innerHTML = `< span class = "tick-group" > ${group}< / span > < span class = "tick-group" > ${group}< / span > `;
} catch (e) {
if (marketState === 'live') {
marketState = 'err';
setMarketTag('err');
} else {
marketState = 'standby';
setMarketTag('standby');
buildMarketTrack(true);
}
}
}
function initMarketTicker() {
loadMarketSymbols();
buildMarketTrack(true);
setMarketTag('standby');
probeMarket();
setInterval(probeMarket, MARKET_POLL_MS);
}
/* ═══════════════ NEWS FEED + TICKER ═══════════════ */
let newsInterval = null;
const NEWS_REFRESH_MS = 900000; // 15-min cycle
let newsTickerBuilt = false;
function newsEsc(s) {
return String(s == null ? '' : s).replace(/[& < >"']/g,
c => ({'&':'& ','< ':'< ','>':'> ','"':'" ',"'":'' '}[c]));
}
function newsTimeAgo(ts) {
if (!ts) return '—';
const diff = Date.now() - new Date(ts).getTime();
if (diff < 60000 ) return ` $ { Math . floor ( diff / 1000 ) } s ago ` ;
if (diff < 3600000 ) return ` $ { Math . floor ( diff / 60000 ) } m ago ` ;
if (diff < 86400000 ) return ` $ { Math . floor ( diff / 3600000 ) } h ago ` ;
return `${Math.floor(diff/86400000)}d ago`;
}
function newsSimpleMD(text) {
// Lightweight markdown→HTML for summary bodies (bold, headers, lists, paragraphs)
if (!text) return '';
let h = newsEsc(String(text));
h = h.replace(/`([^`]+)`/g, '< code > $1< / code > ');
h = h.replace(/\*\*(.+?)\*\*/g, '< strong > $1< / strong > ');
h = h.replace(/^### (.+)$/gm, '< h4 > $1< / h4 > ');
h = h.replace(/^## (.+)$/gm, '< h3 > $1< / h3 > ');
h = h.replace(/^# (.+)$/gm, '< h2 > $1< / h2 > ');
h = h.replace(/^[\*\-]\s(.+)$/gm, '< li > $1< / li > ');
h = h.replace(/(< li > .*< \/li>\n?)+/g, '< ul > $&</ul>');
h = h.replace(/\n{2,}/g, '< / p > < p > ');
h = h.replace(/\n/g, '< br > ');
return '< p > '+h+'< / p > ';
}
async function loadNews(force) {
const upd = document.getElementById('news-updated');
if (upd) upd.textContent = 'updating…';
let articles = [];
let summaries = [];
try {
const [rArticles, rSumm] = await Promise.all([
fetch(`${API}/api/news?limit=100`),
fetch(`${API}/api/news/summaries?limit=1`)
]);
if (rArticles.ok) articles = await rArticles.json();
if (rSumm.ok) summaries = await rSumm.json();
} catch (e) {
const list = document.getElementById('news-list');
const body = document.getElementById('ns-body');
if (list) list.innerHTML = '< div class = "news-error" > Failed to load news feed: ' + newsEsc(e.message || e) + '< / div > ';
if (body) body.innerHTML = '< div class = "ns-error" > Failed to load summary: ' + newsEsc(e.message || e) + '< / div > ';
if (upd) upd.textContent = 'error';
console.error('News load failed', e);
return;
}
renderNewsSummary(summaries);
renderNewsList(articles);
renderNewsTicker(articles, summaries);
if (upd) upd.textContent = 'updated ' + new Date().toLocaleTimeString();
if (!newsInterval) {
newsInterval = setInterval(() => loadNews(false), NEWS_REFRESH_MS);
}
}
function renderNewsSummary(summaries) {
const bodyEl = document.getElementById('ns-body');
const timeEl = document.getElementById('ns-time');
const badgeEl = document.getElementById('ns-badge');
if (!bodyEl) return;
if (!summaries || !summaries.length) {
bodyEl.innerHTML = '< div class = "ns-empty" > No executive summary yet — the Gemini summarizer is idle until < code > GEMINI_API_KEY< / code > is set on the Pi.< / div > ';
timeEl.textContent = '—';
badgeEl.textContent = 'WAITING';
return;
}
const s = summaries[0];
badgeEl.textContent = 'LATEST';
timeEl.textContent = s.batch_timestamp ? 'Batch: ' + new Date(s.batch_timestamp).toLocaleString() : '—';
bodyEl.innerHTML = newsSimpleMD(s.summary_text || '');
}
function renderNewsList(articles) {
const listEl = document.getElementById('news-list');
if (!listEl) return;
document.getElementById('news-count').textContent = articles.length + ' articles';
if (!articles || !articles.length) {
listEl.innerHTML = '< div class = "news-empty" > No articles scraped yet — the scraper runs hourly at :00. Check news-scraper logs.< / div > ';
return;
}
listEl.innerHTML = articles.map(a => {
const geoTag = a.lat != null & & a.lon != null ? '< span class = "news-geo" title = "geotagged" > 📍< / span > ' : '';
return '< a class = "news-item" href = "' + newsEsc(a.url || '#') + '" target = "_blank" rel = "noopener" > ' +
'< span class = "news-dom" > ' + newsEsc(a.domain || '?') + '< / span > ' +
'< span class = "news-title" > ' + newsEsc((a.title||'').trim()) + '< / span > ' +
geoTag +
'< span class = "news-time" > ' + newsTimeAgo(a.timestamp) + '< / span > ' +
'< / a > ';
}).join('');
}
function renderNewsTicker(articles, summaries) {
const track = document.getElementById('nt-track');
if (!track) return;
let items = '';
// LLM executive summary flash first (click → News view)
const summ = summaries & & summaries.length ? summaries[0] : null;
if (summ & & summ.summary_text) {
const brief = summ.summary_text.replace(/\s+/g, ' ').trim();
items += `< button class = "tick-item brief" onclick = "showView('news')" title = "Open full brief" > ` +
`< span class = "b-tag" > LLM BRIEF< / span > < span > ${newsEsc(brief.slice(0, 200))}${brief.length > 200 ? '…' : ''}< / span > ` +
`< span class = "tt" > ${newsTimeAgo(summ.batch_timestamp)}< / span > < span class = "sep" > ▸< / span > < / button > `;
}
// Top headlines
(articles || []).slice(0, 30).forEach(a => {
items += `< a class = "tick-item" href = "${newsEsc(a.url || '#')}" target = "_blank" rel = "noopener" > ` +
`< span class = "dom" > ${newsEsc(a.domain || '?')}< / span > ` +
`< span > ${newsEsc((a.title || '').trim())}< / span > ` +
`< span class = "tt" > ${newsTimeAgo(a.timestamp)}< / span > < span class = "sep" > ▸< / span > < / a > `;
});
if (!items) {
items = `< span class = "tick-item standby" > No headlines yet — scraper idle< / span > `;
}
track.innerHTML = `< span class = "tick-group" > ${items}< / span > < span class = "tick-group" > ${items}< / span > `;
newsTickerBuilt = true;
}
/* ═══════════════ SUMMARY / EVENTS / ALERTS / ENTITIES / SEARCH ═══════════ */
let lastSummary = null; // last /api/analytics/summary payload for the Settings System panel
2026-06-04 20:30:04 -04:00
async function loadSummary() {
try {
const r = await fetch(`${API}/api/analytics/summary`);
const d = await r.json();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
lastSummary = d;
2026-06-04 20:30:04 -04:00
document.getElementById('total-events').textContent = d.total_events;
document.getElementById('events-24h').textContent = d.events_last_24h;
document.getElementById('active-sources').textContent = d.active_sources;
document.getElementById('open-alerts').textContent = d.open_alerts;
document.getElementById('tracked-entities').textContent = d.tracked_entities;
const s = d.sentiment;
const total = s.positive_count + s.neutral_count + s.negative_count || 1;
document.getElementById('sent-pos').style.width = ((s.positive_count/total)*100)+'%';
document.getElementById('sent-neu').style.width = ((s.neutral_count/total)*100)+'%';
document.getElementById('sent-neg').style.width = ((s.negative_count/total)*100)+'%';
document.getElementById('sent-detail').textContent = `+${s.positive_count} | ~${s.neutral_count} | -${s.negative_count} (avg: ${s.avg_score.toFixed(3)})`;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
renderSysInfo(d);
2026-06-04 20:30:04 -04:00
} catch(e) { console.error('Summary load failed', e); }
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function renderSysInfo(d) {
// No argument → re-render from the last summary snapshot (a bare refresh
// must not clobber real values with placeholder dashes).
if (!d) d = lastSummary || {};
const set = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
set('sys-events', d.total_events ?? '—');
set('sys-sources', d.active_sources ?? '—');
set('sys-alerts', d.open_alerts ?? '—');
set('sys-entities', d.tracked_entities ?? '—');
set('sys-health', (document.getElementById('health-dot') || {}).className === 'dot ok' ? 'OK' : '—');
}
2026-06-04 20:30:04 -04:00
async function loadEvents() {
try {
const r = await fetch(`${API}/api/events?limit=30`);
const d = await r.json();
const tb = document.getElementById('events-body');
tb.innerHTML = d.map(e => `< tr >
< td > ${new Date(e.ingested_at).toLocaleString()}< / td >
< td > ${e.source_type}< / td >
< td > ${(e.title||'').substring(0,80)}< / td >
< td > ${sentimentBadge(e.sentiment_label)}< / td >
< td > ${e.location_name || '-'}< / td >
< / tr > `).join('');
} catch(e) { console.error('Events load failed', e); }
}
async function loadAlerts() {
try {
const r = await fetch(`${API}/api/alerts?limit=20`);
const d = await r.json();
const tb = document.getElementById('alerts-body');
tb.innerHTML = d.map(a => `< tr >
< td > ${new Date(a.created_at).toLocaleString()}< / td >
< td > < span class = "badge badge-${a.severity}" > ${a.severity}< / span > < / td >
< td > ${a.alert_type}< / td >
< td > ${a.title}< / td >
< / tr > `).join('');
} catch(e) { console.error('Alerts load failed', e); }
}
async function loadEntities() {
try {
const r = await fetch(`${API}/api/entities?limit=20`);
const d = await r.json();
const tb = document.getElementById('entities-body');
tb.innerHTML = d.map(e => `< tr >
< td > ${e.name}< / td >
< td > ${e.entity_type}< / td >
< td > ${e.event_count}< / td >
< td > ${new Date(e.last_seen).toLocaleString()}< / td >
< / tr > `).join('');
} catch(e) { console.error('Entities load failed', e); }
}
async function searchEvents() {
const q = document.getElementById('search-q').value.trim();
if (!q) return;
try {
const r = await fetch(`${API}/api/search`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({q, limit: 50})
});
const d = await r.json();
document.getElementById('search-total').textContent = d.total;
document.getElementById('search-results').style.display = 'block';
const tb = document.getElementById('search-body');
tb.innerHTML = d.events.map(e => `< tr >
< td > ${new Date(e.ingested_at).toLocaleString()}< / td >
< td > ${e.source_type}< / td >
< td > ${(e.title||'').substring(0,80)}< / td >
< td > ${sentimentBadge(e.sentiment_label)}< / td >
< td > ${e.location_name || '-'}< / td >
< / tr > `).join('');
} catch(e) { console.error('Search failed', e); }
}
function sentimentBadge(label) {
if (!label) return '-';
const cls = {positive:'badge-positive',negative:'badge-negative',neutral:'badge-neutral'}[label]||'badge-neutral';
return `< span class = "badge ${cls}" > ${label}< / span > `;
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ API KEYS ═══════════════ */
Add NASA FIRMS active-fire ingest + /api/fires; API keys management page
Coherent merge of two coordinated features on the shared working tree:
FIRMS fire heatmap (backend, t_6e404c14):
- app/fire_sources.py: fetch FIRMS VIIRS area CSV (free MAP_KEY) -> NATS events.fire
- fires hypertable (TimescaleDB, 1-day chunks) with natural-key PK
(latitude, longitude, acq_time, satellite); idempotent ON CONFLICT DO NOTHING
- alembic/versions/002_fires.py; GET /api/fires?bbox=&since= (JSON only)
- POST /api/ingest/fires; ~15 min poll loop (FIRMS_INTERVAL=900) in ingester
- env-driven config (FIRMS_MAP_KEY/DATASET/BBOX/INTERVAL); docs/firms.md covers
the zero-cost GIBS VIIRS_SNPP_Thermal_Anomalies_375m_All tile alternative
- 18 tests (parser, mapping, idempotency, API contract) verified vs real
TimescaleDB+PostGIS (localhost/osint-dashboard-pg image)
API keys page (frontend, t_4433cff2):
- app/keystore.py: api_keys table (self-creating), FIRMS/GEMINI/TELEGRAM
registry with format validation, ****last4 masking, get_api_key()
- GET/POST/DELETE /api/keys (never returns full values); Keys tab in index.html
DB_NULL_POOL env switch in app/database.py enables a NullPool for tests /
short-lived processes that open a fresh event loop per unit.
2026-08-24 15:37:42 -04:00
const KEY_PATTERNS = {
'FIRMS_MAP_KEY': /^[0-9a-fA-F]{32}$/,
2026-08-27 22:55:51 -04:00
'NOUS_API_KEY': /^.{16,}$/,
Add NASA FIRMS active-fire ingest + /api/fires; API keys management page
Coherent merge of two coordinated features on the shared working tree:
FIRMS fire heatmap (backend, t_6e404c14):
- app/fire_sources.py: fetch FIRMS VIIRS area CSV (free MAP_KEY) -> NATS events.fire
- fires hypertable (TimescaleDB, 1-day chunks) with natural-key PK
(latitude, longitude, acq_time, satellite); idempotent ON CONFLICT DO NOTHING
- alembic/versions/002_fires.py; GET /api/fires?bbox=&since= (JSON only)
- POST /api/ingest/fires; ~15 min poll loop (FIRMS_INTERVAL=900) in ingester
- env-driven config (FIRMS_MAP_KEY/DATASET/BBOX/INTERVAL); docs/firms.md covers
the zero-cost GIBS VIIRS_SNPP_Thermal_Anomalies_375m_All tile alternative
- 18 tests (parser, mapping, idempotency, API contract) verified vs real
TimescaleDB+PostGIS (localhost/osint-dashboard-pg image)
API keys page (frontend, t_4433cff2):
- app/keystore.py: api_keys table (self-creating), FIRMS/GEMINI/TELEGRAM
registry with format validation, ****last4 masking, get_api_key()
- GET/POST/DELETE /api/keys (never returns full values); Keys tab in index.html
DB_NULL_POOL env switch in app/database.py enables a NullPool for tests /
short-lived processes that open a fresh event loop per unit.
2026-08-24 15:37:42 -04:00
'TELEGRAM_TOKEN': /^\d{8,10}:[0-9A-Za-z_-]{35}$/,
};
async function loadKeys() {
try {
const r = await fetch(`${API}/api/keys`);
const d = await r.json();
const grid = document.getElementById('keys-grid');
grid.innerHTML = d.map(k => {
const hint = k.validated & & k.example ? `< div class = "key-hint" > expected: ${k.example}< / div > ` : '';
return `< div class = "key-card ${k.set?'set':''}" >
< div class = "key-head" >
< span class = "key-name" > ${k.name}< / span >
< span class = "key-status ${k.set?'set':'missing'}" > ${k.set?'●':'○'} ${k.set ? (k.masked||'set') : 'missing'}< / span >
< / div >
< div class = "key-desc" > ${k.description || 'Custom key (not in registry).'}${hint}< / div >
< div class = "key-form" >
< input type = "password" id = "key-in-${k.name}" placeholder = "Paste new value…" autocomplete = "off" spellcheck = "false" >
< button class = "btn" onclick = "saveKey('${k.name}')" > Save< / button >
${k.set ? `< button class = "btn btn-danger" onclick = "clearKey('${k.name}')" > Clear< / button > ` : ''}
< / div >
< div class = "key-msg" id = "key-msg-${k.name}" > < / div >
< / div > `;
}).join('');
} catch(e) { console.error('Keys load failed', e); }
}
function keyMsg(name, text, isErr) {
const el = document.getElementById('key-msg-'+name);
if (el) { el.textContent = text; el.className = 'key-msg ' + (isErr ? 'err' : 'ok'); }
}
async function saveKey(name) {
const input = document.getElementById('key-in-'+name);
const value = (input.value||'').trim();
if (!value) { keyMsg(name, 'Enter a value to save.', true); return; }
const pat = KEY_PATTERNS[name];
if (pat & & !pat.test(value)) { keyMsg(name, 'Format check failed — see the expected format above.', true); return; }
try {
const r = await fetch(`${API}/api/keys/${encodeURIComponent(name)}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({value})
});
const d = await r.json().catch(() => ({}));
if (!r.ok) { keyMsg(name, d.detail || 'Save failed', true); return; }
input.value = '';
keyMsg(name, `Saved (${d.masked || 'set'}).`, false);
loadKeys();
} catch(e) { keyMsg(name, 'Network error.', true); }
}
async function clearKey(name) {
if (!confirm(`Delete ${name}? This cannot be undone.`)) return;
try {
const r = await fetch(`${API}/api/keys/${encodeURIComponent(name)}`, {method:'DELETE'});
if (!r.ok) { keyMsg(name, 'Clear failed.', true); return; }
keyMsg(name, 'Key removed.', false);
loadKeys();
} catch(e) { keyMsg(name, 'Network error.', true); }
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ INGEST TRIGGERS ═══════════════ */
2026-06-04 20:30:04 -04:00
async function ingestRSS() {
const url = document.getElementById('rss-url').value;
const r = await fetch(`${API}/api/ingest/rss?feed_url=${encodeURIComponent(url)}`, {method:'POST'});
const d = await r.json();
document.getElementById('ingest-result').textContent = `RSS: ${d.items_ingested} items ingested`;
loadSummary(); loadEvents();
}
async function ingestGDELT() {
const r = await fetch(`${API}/api/ingest/gdelt?max_articles=50`, {method:'POST'});
const d = await r.json();
document.getElementById('ingest-result').textContent = `GDELT: ${d.articles_ingested} articles ingested`;
loadSummary(); loadEvents();
}
async function ingestEarthquakes() {
const r = await fetch(`${API}/api/ingest/earthquakes`, {method:'POST'});
const d = await r.json();
document.getElementById('ingest-result').textContent = `USGS: ${d.events_ingested} events ingested`;
loadSummary(); loadEvents();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
if (blipsOn & & map) loadBlips();
2026-06-04 20:30:04 -04:00
}
async function processNATS() {
const r = await fetch(`${API}/api/ingest/process?batch_size=100`, {method:'POST'});
const d = await r.json();
document.getElementById('ingest-result').textContent = `NATS: ${d.processed} messages processed`;
loadSummary(); loadEvents();
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ SETTINGS ═══════════════ */
function saveMarketSettings() {
const raw = document.getElementById('set-symbols').value;
const syms = raw.split(',').map(s => s.trim()).filter(Boolean);
2026-06-04 20:30:04 -04:00
try {
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
localStorage.setItem('osint.marketSymbols', syms.join(','));
marketSymbols = syms;
buildMarketTrack(true);
if (marketState !== 'live') setMarketTag('standby');
document.getElementById('mkt-saved').textContent = 'Symbols saved — ticker updated.';
} catch (e) {
document.getElementById('mkt-saved').textContent = 'Could not save (storage unavailable).';
2026-06-04 20:30:04 -04:00
}
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function resetMarketSettings() {
try { localStorage.removeItem('osint.marketSymbols'); } catch (e) {}
marketSymbols = DEFAULT_SYMBOLS.slice();
document.getElementById('set-symbols').value = DEFAULT_SYMBOLS.join(', ');
buildMarketTrack(true);
document.getElementById('mkt-saved').textContent = 'Defaults restored.';
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function saveMapSettings() {
const prefs = {
firesSince: document.getElementById('set-fires-since').value,
blipsSince: document.getElementById('set-blips-since').value,
camsOn: document.getElementById('set-cams-on').checked,
blipsOn: document.getElementById('set-blips-on').checked,
firesOn: document.getElementById('set-fires-on').checked,
};
try { localStorage.setItem('osint.mapDefaults', JSON.stringify(prefs)); } catch (e) {}
applyMapSettings(prefs, true);
document.getElementById('map-saved').textContent = 'Map defaults saved & applied.';
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function resetMapSettings() {
try { localStorage.removeItem('osint.mapDefaults'); } catch (e) {}
document.getElementById('set-fires-since').value = '24h';
document.getElementById('set-blips-since').value = '24h';
document.getElementById('set-cams-on').checked = true;
document.getElementById('set-blips-on').checked = true;
document.getElementById('set-fires-on').checked = true;
applyMapSettings({firesSince:'24h', blipsSince:'24h', camsOn:true, blipsOn:true, firesOn:true}, true);
document.getElementById('map-saved').textContent = 'Defaults restored.';
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function applyMapSettings(prefs, live) {
const fs = document.getElementById('lp-fires-since');
const bs = document.getElementById('lp-blips-since');
if (fs) fs.value = prefs.firesSince || '24h';
if (bs) bs.value = prefs.blipsSince || '24h';
const cf = document.getElementById('lp-cams-on');
const bf = document.getElementById('lp-blips-on');
const ff = document.getElementById('lp-fires-on');
if (cf) cf.checked = prefs.camsOn !== false;
if (bf) bf.checked = prefs.blipsOn !== false;
if (ff) ff.checked = prefs.firesOn !== false;
if (!live) return;
if (map) {
if (prefs.camsOn === false & & camsGroup) { map.removeLayer(camsGroup); camsGroup = null; }
if (prefs.blipsOn === false & & blipsGroup) { map.removeLayer(blipsGroup); blipsGroup = null; }
if (prefs.firesOn === false & & firesHeat) { map.removeLayer(firesHeat); firesHeat = null; }
if (prefs.firesOn !== false & & firesOn) loadFires();
if (prefs.camsOn !== false & & camsOn) loadCams();
if (prefs.blipsOn !== false & & blipsOn) loadBlips();
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
firesSince = prefs.firesSince || firesSince;
blipsSince = prefs.blipsSince || blipsSince;
camsOn = prefs.camsOn !== false;
blipsOn = prefs.blipsOn !== false;
firesOn = prefs.firesOn !== false;
syncHud();
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function initSettings() {
try {
const raw = localStorage.getItem('osint.marketSymbols');
document.getElementById('set-symbols').value = raw ? raw.split(',').map(s=>s.trim()).filter(Boolean).join(', ') : DEFAULT_SYMBOLS.join(', ');
const prefs = JSON.parse(localStorage.getItem('osint.mapDefaults') || 'null');
if (prefs) {
document.getElementById('set-fires-since').value = prefs.firesSince || '24h';
document.getElementById('set-blips-since').value = prefs.blipsSince || '24h';
document.getElementById('set-cams-on').checked = prefs.camsOn !== false;
document.getElementById('set-blips-on').checked = prefs.blipsOn !== false;
document.getElementById('set-fires-on').checked = prefs.firesOn !== false;
}
} catch (e) {}
2026-08-24 18:01:19 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ MAP (NASA GIBS basemap + FIRMS + cameras + blips) ═══════
The satellite map core is production-proven — preserved verbatim. */
2026-08-24 17:35:44 -04:00
let map = null, mapLayer = null, mapInitStarted = false;
2026-08-24 18:02:56 -04:00
let firesHeat = null, camsGroup = null, firesOn = false, camsOn = false;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
let blipsGroup = null, blipsOn = false, blipsSince = '24h';
2026-08-24 18:02:56 -04:00
let firesOpacity = 0.65, firesColor = 'brightness', firesSince = '24h';
let camsOpacity = 1.0, baseOpacity = 1.0;
2026-08-27 17:33:12 -04:00
let camReq = 0, fireReq = 0, blipReq = 0; // stale-response guards (rapid zoom races)
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
let mapLayers = [];
let mapDomain = null;
let mapLatest = null;
let mapAttributionAdded = null;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
let extraAttribs = new Set();
let radarLayer = null, radarOn = true, radarOpacity = 0.7, radarMeta = null, radarTimer = null;
let thermalLayer = null, thermalOn = false;
let wxAlertsGroup = null, wxAlertsOn = true;
let perimGroup = null, perimOn = true;
let incidentsGroup = null, incidentsOn = false;
let acGroup = null, acOn = true;
let trainsGroup = null, trainsOn = true;
let vesselsGroup = null, vesselsOn = false;
let stormsGroup = null, stormsOn = true;
let overlayReq = {ac:0, trains:0, vessels:0, alerts:0, perim:0, incidents:0, storms:0};
let moveDebounce = null;
2026-08-27 21:23:25 -04:00
let overlayAbort = null;
let lastCell = '';
2026-08-27 21:46:37 -04:00
let lastVesselSubBox = ''; // last viewport box sent to the AIS stream
let vesselRefollowTimer = null; // one follow-up fetch after a retune
2026-08-27 21:23:25 -04:00
function bboxCell() {
if (!map) return '';
return currentBBox().split(',').map(n => Number(n).toFixed(2)).join(',') + '@' + map.getZoom();
}
2026-08-28 09:33:19 -04:00
let liveWs = null;
const FF_ICAO = new Set(['AT802','AT8T','AT8P','C130','C30J','C130J','DC10','MD10','MD87','S64','UH60','S70','CL415','CL215','B350','OV10','C208']);
let firefighterHex = new Set();
let dvrTs = null;
let dvrMinMs = 0, dvrMaxMs = 0;
let gfDrawOn = false, gfVerts = [], gfLayer = null, gfSaved = null;
function liveWsUrl() {
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
return proto + '//' + location.host + '/ws/live';
}
function sendLiveViewport() {
if (!liveWs || liveWs.readyState !== 1 || !map) return;
liveWs.send(JSON.stringify({ type: 'viewport', bbox: currentBBox() }));
}
function upsertLivePoint(group, p, colorFn, feed) {
if (!map || !p || p.id == null || p.lat == null || p.lon == null) return group;
if (!group || !map.hasLayer(group) || !group._osintById) {
return renderPoints(group, [p], colorFn, true, feed);
}
const id = String(p.id);
const col = sanitizeColor(colorFn(p), '#35e0ff');
let m = group._osintById.get(id);
if (m) {
m.setLatLng([p.lat, p.lon]);
if (feed) m.setIcon(feedIcon(feed, col, p.heading));
else if (m.setStyle) m.setStyle({ color: col, fillColor: col });
} else {
m = makePointMarker(p, colorFn, feed, pointCanvas());
if (m) { group.addLayer(m); group._osintById.set(id, m); }
}
return group;
}
function applyLiveMarker(kind, p) {
if (!p) return;
if (kind === 'ais' & & vesselsOn & & map & & map.getZoom() > 3 & & !dvrTs) {
vesselsGroup = upsertLivePoint(vesselsGroup, p, q => {
const sog = Number(q.speed || 0);
return sog > 0.5 ? '#2dd4bf' : '#64748b';
}, 'vessel');
} else if (kind === 'adsb' & & acOn & & map & & map.getZoom() > 3 & & !dvrTs) {
acGroup = upsertLivePoint(acGroup, p, q => acColor(q), 'ac');
} else if (kind === 'geofence_alert') {
showGeofenceToast(p);
} else if (kind === 'fire_aircraft') {
firefighterHex.add(String(p.aircraft_hex || ''));
if (acOn & & p.aircraft_lat != null) {
acGroup = upsertLivePoint(acGroup, {
id: p.aircraft_hex, lat: p.aircraft_lat, lon: p.aircraft_lon,
label: p.label || p.aircraft_hex, extra: { type: p.aircraft_type, firefighter: true },
}, q => acColor(q), 'ac');
}
}
}
function acColor(p) {
const t = String(((p.extra || {}).type || '')).toUpperCase();
if ((p.extra || {}).firefighter || firefighterHex.has(String(p.id)) || FF_ICAO.has(t)) return '#fb923c';
return altColor((p.extra || {}).alt_baro);
}
function connectLiveWs() {
if (liveWs & & (liveWs.readyState === 0 || liveWs.readyState === 1)) return;
try { liveWs = new WebSocket(liveWsUrl()); } catch (e) { return; }
liveWs.onopen = () => sendLiveViewport();
liveWs.onmessage = (ev) => {
try {
const msg = JSON.parse(ev.data);
applyLiveMarker(msg.type, msg.payload);
} catch (e) { /* ignore malformed */ }
};
liveWs.onclose = () => { liveWs = null; setTimeout(connectLiveWs, 4000); };
}
2026-08-27 21:23:25 -04:00
function overlayFetch(url) {
return fetch(url, overlayAbort ? { signal: overlayAbort.signal } : {});
}
function isAbort(e) { return e & & e.name === 'AbortError'; }
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const pointCanvas = () => L.canvas({ padding: 0.5 });
2026-08-24 17:35:44 -04:00
async function initMap() {
if (mapInitStarted) { if (map) setTimeout(() => map.invalidateSize(), 60); return; }
mapInitStarted = true;
const hint = document.getElementById('map-hint');
hint.textContent = 'Loading layer catalog…';
try {
const r = await fetch(`${API}/api/map/layers`);
const d = await r.json();
mapLayers = d.layers || [];
const sel = document.getElementById('map-layer');
sel.innerHTML = mapLayers.map(l =>
`< option value = "${l.id}" > ${l.title}< / option > `).join('');
2026-08-27 21:12:58 -04:00
const preferred =
mapLayers.find(l => l.id === 'BlueMarble_ShadedRelief_Bathymetry')
|| mapLayers.find(l => !l.has_time)
|| mapLayers[0];
2026-08-24 17:35:44 -04:00
if (preferred) sel.value = preferred.id;
map = L.map('map', {
center: [25, 10], zoom: 2,
2026-08-24 18:02:56 -04:00
zoomControl: false,
2026-08-24 17:35:44 -04:00
worldCopyJump: true,
minZoom: 1,
maxZoom: 12,
attributionControl: true,
});
2026-08-24 18:02:56 -04:00
L.control.zoom({ position: 'topright' }).addTo(map);
2026-08-24 17:35:44 -04:00
map.attributionControl.setPrefix('');
2026-08-27 15:54:54 -04:00
let activeHls = null;
map.on('popupopen', (e) => {
if (activeHls) { try { activeHls.destroy(); } catch (_) {} activeHls = null; }
const root = e.popup.getElement();
const v = root & & root.querySelector('video[data-hls]');
if (!v) return;
const fallback = () => {
const img = document.createElement('img');
img.className = 'thumb';
img.src = v.dataset.fallback;
img.alt = 'camera preview';
v.replaceWith(img);
};
2026-08-27 21:12:58 -04:00
const startHls = () => {
if (window.Hls & & Hls.isSupported()) {
activeHls = new Hls({ enableWorker: true, lowLatencyMode: true });
activeHls.loadSource(v.dataset.hls);
activeHls.attachMedia(v);
v.play & & v.play().catch(() => {});
activeHls.on(Hls.Events.ERROR, (_, data) => {
if (data & & data.fatal) fallback();
});
} else if (v.canPlayType & & v.canPlayType('application/vnd.apple.mpegurl')) {
v.src = v.dataset.hls;
v.addEventListener('error', fallback, { once: true });
} else {
fallback();
}
};
if (window.Hls) startHls();
else loadHls().then(startHls).catch(fallback);
2026-08-27 15:54:54 -04:00
});
map.on('popupclose', () => {
if (activeHls) { try { activeHls.destroy(); } catch (_) {} activeHls = null; }
});
2026-08-24 18:02:56 -04:00
updateHeatLegend();
2026-08-27 17:48:39 -04:00
// Reload overlays when the user pans/zooms. A live popup makes the
// rebuild skip (so the popup's autopan doesn't destroy it), but user
// zoom/drag must close the popup FIRST — otherwise zooming out with a
// camera popup open leaves the stale, zoomed-in marker group on the
// map and the cameras "vanish" until a refresh.
//
// NOTE: `map._popup` is NOT a reliable open-check in Leaflet 1.9.4 —
// Map.closePopup() never nulls it, so once any popup has been opened
// it stays truthy forever. Track the real state via popupopen/close.
let camPopupOpen = false;
map.on('popupopen', () => { camPopupOpen = true; });
map.on('popupclose', () => { camPopupOpen = false; });
map.on('zoomstart', () => { if (camPopupOpen) map.closePopup(); });
map.on('dragstart', () => { if (camPopupOpen) map.closePopup(); });
2026-08-24 18:02:56 -04:00
map.on('moveend', () => {
2026-08-27 17:48:39 -04:00
if (camPopupOpen) return; // only the popup's own autopan now
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
if (moveDebounce) clearTimeout(moveDebounce);
moveDebounce = setTimeout(() => {
2026-08-27 21:23:25 -04:00
const cell = bboxCell();
if (cell === lastCell) return;
lastCell = cell;
if (overlayAbort) overlayAbort.abort();
overlayAbort = new AbortController();
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
if (firesOn) loadFires();
if (camsOn) loadCams();
if (blipsOn) loadBlips();
refreshLiveOverlays();
2026-08-28 09:33:19 -04:00
sendLiveViewport();
}, 500);
2026-08-24 18:02:56 -04:00
});
2026-08-27 21:12:58 -04:00
// Static Blue Marble has no time-domain fetch — don't block overlays on GIBS.
mapLayerChanged();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
// Overlays default ON (checkboxes in the layer panel + saved settings)
applyMapSettings(readMapPrefs(), false);
2026-08-24 18:02:56 -04:00
firesOn = document.getElementById('lp-fires-on').checked;
camsOn = document.getElementById('lp-cams-on').checked;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
blipsOn = document.getElementById('lp-blips-on').checked;
firesSince = document.getElementById('lp-fires-since').value;
blipsSince = document.getElementById('lp-blips-since').value;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
radarOn = document.getElementById('lp-radar-on').checked;
wxAlertsOn = document.getElementById('lp-alerts-on').checked;
thermalOn = document.getElementById('lp-thermal-on').checked;
perimOn = document.getElementById('lp-perim-on').checked;
incidentsOn = document.getElementById('lp-incidents-on').checked;
acOn = document.getElementById('lp-ac-on').checked;
trainsOn = document.getElementById('lp-trains-on').checked;
vesselsOn = document.getElementById('lp-vessels-on').checked;
stormsOn = document.getElementById('lp-storms-on').checked;
2026-08-24 18:02:56 -04:00
if (firesOn) loadFires();
if (camsOn) loadCams();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
if (blipsOn) loadBlips();
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
refreshLiveOverlays();
2026-08-28 09:33:19 -04:00
connectLiveWs();
2026-08-24 17:35:44 -04:00
} catch(e) {
hint.textContent = `Failed to load map layers: ${e.message || e}`;
console.error('Map init failed', e);
}
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
function readMapPrefs() {
try {
return JSON.parse(localStorage.getItem('osint.mapDefaults') || 'null') || {};
} catch (e) { return {}; }
}
function syncHud() {
const set = (id, n, on) => {
const chip = document.getElementById('hud-' + id);
const num = document.getElementById('hud-' + id + '-n');
if (num) num.textContent = n == null ? '—' : Number(n).toLocaleString();
if (chip) chip.classList.toggle('off', on === false);
};
set('fires', firesOn ? (hudFiresCount) : '—', firesOn);
set('cams', camsOn ? (hudCamsCount) : '—', camsOn);
set('blips', blipsOn ? (hudBlipsCount) : '—', blipsOn);
}
let hudFiresCount = null, hudCamsCount = null, hudBlipsCount = null;
2026-08-24 17:35:44 -04:00
function setMapAttribution(text) {
if (!map) return;
if (mapAttributionAdded) map.attributionControl.removeAttribution(mapAttributionAdded);
mapAttributionAdded = text;
if (text) map.attributionControl.addAttribution(text);
}
function tileUrlFor(layerMeta, time) {
// RESTful WMTS template: .../best/{layer}/default[/{time}]/{tms}/{z}/{y}/{x}.{fmt}
const base = `https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/${layerMeta.id}/default`;
const timePart = layerMeta.has_time ? `/${time || 'default'}` : '';
return `${base}${timePart}/${layerMeta.tms}/{z}/{y}/{x}.${layerMeta.format}`;
}
async function mapLayerChanged() {
const hint = document.getElementById('map-hint');
const sel = document.getElementById('map-layer');
const meta = mapLayers.find(l => l.id === sel.value);
if (!meta) return;
const hasTime = !!meta.has_time;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
document.getElementById('map-time-label').style.display = hasTime ? '' : 'none';
document.getElementById('map-date').style.display = hasTime ? '' : 'none';
2026-08-24 17:35:44 -04:00
document.getElementById('map-latest-btn').style.display = hasTime ? '' : 'none';
document.getElementById('map-7d-btn').style.display = hasTime ? '' : 'none';
document.getElementById('map-30d-btn').style.display = hasTime ? '' : 'none';
let time = null;
if (hasTime) {
hint.textContent = 'Loading available dates…';
const input = document.getElementById('map-date');
try {
const r = await fetch(`${API}/api/map/times?layer=${encodeURIComponent(meta.id)}`);
mapDomain = await r.json();
mapLatest = mapDomain.latest;
if (mapDomain.min & & mapDomain.max) {
input.min = mapDomain.min;
input.max = mapDomain.max;
}
input.value = mapLatest || mapDomain.max || '';
time = input.value;
} catch(e) {
mapDomain = null;
const today = new Date().toISOString().slice(0,10);
input.value = today;
time = today;
}
} else {
mapDomain = null;
mapLatest = null;
time = null;
}
const url = tileUrlFor(meta, time);
const attribution = `NASA GIBS / EOSDIS · < a href = "https://earthdata.nasa.gov/gibs" target = "_blank" rel = "noopener" > ${meta.id}< / a > `;
if (mapLayer) {
mapLayer.setUrl(url);
mapLayer.options.maxNativeZoom = meta.max_zoom;
} else {
mapLayer = L.tileLayer(url, {
maxZoom: 12,
maxNativeZoom: meta.max_zoom,
attribution: '',
}).addTo(map);
}
setMapAttribution(attribution);
hint.textContent = hasTime
? `${meta.subtitle || meta.title} · tile date ${time || 'default'} UTC`
: `${meta.subtitle || meta.title} · static basemap`;
}
function mapDateChanged() {
const meta = mapLayers.find(l => l.id === document.getElementById('map-layer').value);
if (!meta || !meta.has_time) return;
const time = document.getElementById('map-date').value;
if (!time) return;
mapLayer.setUrl(tileUrlFor(meta, time));
document.getElementById('map-hint').textContent =
`${meta.subtitle || meta.title} · tile date ${time} UTC`;
if (mapDomain & & mapDomain.ranges & & mapDomain.ranges.length) {
const inRange = mapDomain.ranges.some(r => time >= r.start & & time < = r.end);
if (!inRange) {
document.getElementById('map-hint').textContent += ' ⚠ outside published windows (GIBS serves nearest-time)';
}
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
if (thermalOn) loadThermal();
2026-08-24 17:35:44 -04:00
}
function mapGoLatest() { document.getElementById('map-date').value = mapLatest || ''; mapDateChanged(); }
function mapGoDays(n) {
const base = mapLatest || document.getElementById('map-date').value;
if (!base) return;
const dt = new Date(base + 'T00:00:00Z');
dt.setUTCDate(dt.getUTCDate() - n);
const iso = dt.toISOString().slice(0,10);
document.getElementById('map-date').value = iso;
mapDateChanged();
}
function mapResetView() { if (map) map.setView([25, 10], 2); }
function currentBBox() {
const b = map.getBounds();
2026-08-27 15:59:22 -04:00
let west = b.getWest();
let east = b.getEast();
2026-08-27 16:03:50 -04:00
let south = Math.max(-90, Math.min(90, b.getSouth()));
let north = Math.max(-90, Math.min(90, b.getNorth()));
2026-08-27 15:59:22 -04:00
const lonSpan = east - west;
2026-08-27 16:03:50 -04:00
// World view, or a wrap that spans ~the whole globe.
2026-08-27 15:59:22 -04:00
if (map.getZoom() < = 3 || lonSpan >= 300) {
return '-180.0000,-85.0000,180.0000,85.0000';
}
2026-08-27 16:03:50 -04:00
// Normalize each edge into [-180, 180] WITHOUT clamping both to 180
// (that collapsed CA-on-a-wrapped-copy to an empty sliver).
const norm = (x) => ((x + 180) % 360 + 360) % 360 - 180;
west = norm(west);
east = norm(east);
2026-08-27 15:59:22 -04:00
if (west >= east) {
2026-08-27 16:03:50 -04:00
// Crosses the dateline: keep the latitude strip, don't drop the state.
return `-180.0000,${south.toFixed(4)},180.0000,${north.toFixed(4)}`;
2026-08-27 15:59:22 -04:00
}
2026-08-24 18:02:56 -04:00
return `${west.toFixed(4)},${south.toFixed(4)},${east.toFixed(4)},${north.toFixed(4)}`;
2026-08-24 17:35:44 -04:00
}
2026-08-27 21:46:37 -04:00
function quantizeBBox(bbox, decimals = 1) {
// Round outward (west/south floor, east/north ceil) so the box always
// covers the viewport; a coarse box keeps AIS stream retunes sparse.
const [w, s, e, n] = bbox.split(',').map(Number);
const p = Math.pow(10, decimals);
const wq = Math.floor(w * p) / p;
const sq = Math.floor(s * p) / p;
const eq = Math.ceil(e * p) / p;
const nq = Math.ceil(n * p) / p;
return [wq, sq, eq, nq].map(v => v.toFixed(decimals)).join(',');
}
2026-08-24 18:02:56 -04:00
function toggleLayerPanel() {
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
const panel = document.getElementById('layer-panel');
panel.classList.toggle('collapsed');
panel.querySelector('.lp-head').setAttribute('aria-expanded', !panel.classList.contains('collapsed'));
2026-08-24 17:35:44 -04:00
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Basemap visibility / opacity ── */
2026-08-24 18:02:56 -04:00
function toggleBase() {
const on = document.getElementById('lp-base-on').checked;
if (mapLayer) mapLayer.setOpacity(on ? baseOpacity : 0);
}
function setBaseOpacity(v) {
baseOpacity = v / 100;
document.getElementById('lp-base-val').textContent = `${Math.round(v)}%`;
if (mapLayer & & document.getElementById('lp-base-on').checked) mapLayer.setOpacity(baseOpacity);
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── FIRMS fire heatmap ── */
2026-08-24 18:02:56 -04:00
function firesIntensity(f) {
2026-08-27 21:19:42 -04:00
const confidence = f.confidence || f.c;
const brightness = f.brightness != null ? f.brightness : f.i;
2026-08-24 18:02:56 -04:00
if (firesColor === 'confidence') {
2026-08-27 21:19:42 -04:00
return confidence === 'h' ? 1.0 : confidence === 'l' ? 0.35 : 0.6;
2026-08-24 18:02:56 -04:00
}
2026-08-27 21:19:42 -04:00
const b = brightness || 300;
2026-08-24 18:02:56 -04:00
return Math.max(0.05, Math.min(1, (b - 290) / 110));
}
function firesGradient() {
return firesColor === 'confidence'
? { 0.2: '#3b82f6', 0.5: '#fbbf24', 0.75: '#fb923c', 1.0: '#ef4444' }
: { 0.1: '#2563eb', 0.4: '#38bdf8', 0.65: '#f59e0b', 0.85: '#ef4444', 1.0: '#fde047' };
}
function updateHeatLegend() {
const el = document.getElementById('lp-heat-legend');
if (firesColor === 'confidence') {
el.innerHTML = '< span > < span class = "sw" style = "background:#fbbf24" > l< / span > < span class = "sw" style = "background:#fb923c" > n< / span > < span class = "sw" style = "background:#ef4444" > h< / span > < / span > < span > VIIRS confidence< / span > ';
} else {
el.innerHTML = '< span > ~290K< / span > < span > ~400K< / span > ';
}
}
function sinceToISO(sel) {
if (!sel) return '';
const m = sel.match(/^(\d+)([hd])$/);
if (!m) return '';
const n = parseInt(m[1], 10) * (m[2] === 'h' ? 3600 : 86400) * 1000;
return new Date(Date.now() - n).toISOString();
}
async function toggleFires() {
firesOn = document.getElementById('lp-fires-on').checked;
if (firesOn) await loadFires();
else if (firesHeat) { map.removeLayer(firesHeat); firesHeat = null; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
hudFiresCount = null;
syncHud();
2026-08-24 18:02:56 -04:00
}
function setFiresOpacity(v) {
firesOpacity = v / 100;
document.getElementById('lp-fires-val').textContent = `${Math.round(v)}%`;
if (firesHeat & & firesHeat._canvas) firesHeat._canvas.style.opacity = firesOpacity;
}
function setFiresColor() {
firesColor = document.getElementById('lp-fires-color').value;
updateHeatLegend();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
if (firesOn) loadFires();
2026-08-24 18:02:56 -04:00
}
function setFiresSince() {
firesSince = document.getElementById('lp-fires-since').value;
if (firesOn) loadFires();
}
2026-08-24 17:35:44 -04:00
async function loadFires() {
if (!map) return;
2026-08-27 17:00:09 -04:00
const req = ++fireReq;
2026-08-24 17:35:44 -04:00
try {
2026-08-27 21:19:42 -04:00
let url = `${API}/api/fires?bbox=${currentBBox()}&limit=2000&format=heat`;
2026-08-24 18:02:56 -04:00
const since = sinceToISO(firesSince);
if (since) url += `&since=${encodeURIComponent(since)}`;
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(url);
2026-08-24 17:35:44 -04:00
const fires = await r.json();
2026-08-27 17:00:09 -04:00
if (req !== fireReq) return; // superseded by a newer pan/zoom
2026-08-24 18:02:56 -04:00
if (firesHeat) map.removeLayer(firesHeat);
2026-08-27 21:19:42 -04:00
const pts = fires.map(f => [f.lat ?? f.latitude, f.lon ?? f.longitude, firesIntensity(f)]);
2026-08-24 18:02:56 -04:00
firesHeat = L.heatLayer(pts, {
radius: 22, blur: 20, maxZoom: 9, max: 1.0, minOpacity: 0.2,
gradient: firesGradient(),
}).addTo(map);
firesHeat._canvas.style.opacity = firesOpacity;
document.getElementById('lp-fires-count').textContent = fires.length.toLocaleString();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
hudFiresCount = fires.length;
syncHud();
2026-08-24 17:35:44 -04:00
document.getElementById('map-hint').textContent =
2026-08-24 18:02:56 -04:00
`${fires.length.toLocaleString()} fire hotspots in view` +
(since ? ` · since ${since.slice(0,16).replace('T',' ')}Z` : '');
2026-08-24 17:35:44 -04:00
} catch(e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
2026-08-24 17:35:44 -04:00
document.getElementById('map-hint').textContent = `Fires load failed: ${e.message || e}`;
console.error('Fires load failed', e);
}
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── Open IP cameras (thumbnail popups) ── */
2026-08-24 18:02:56 -04:00
async function toggleCams() {
camsOn = document.getElementById('lp-cams-on').checked;
2026-08-24 17:35:44 -04:00
if (camsOn) await loadCams();
else if (camsGroup) { map.removeLayer(camsGroup); camsGroup = null; }
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
hudCamsCount = null;
syncHud();
2026-08-24 18:02:56 -04:00
}
function setCamsOpacity(v) {
camsOpacity = v / 100;
document.getElementById('lp-cams-val').textContent = `${Math.round(v)}%`;
if (camsGroup) camsGroup.eachLayer(l => l.setOpacity & & l.setOpacity(camsOpacity));
}
function esc(s) {
return String(s == null ? '' : s).replace(/[& < >"']/g,
c => ({'&':'& ','< ':'< ','>':'> ','"':'" ',"'":'' '}[c]));
}
2026-08-24 23:36:34 -04:00
function camSourceLink(c) {
const url = c.source_url || '';
if (!url) return '';
// Never emit an rtsp:// href — the OS opens VLC (or another player).
if (url.toLowerCase().startsWith('rtsp://')) {
const host = url.replace(/^rtsp:\/\//i, '').replace(/\/.*$/, '');
return `< span style = "color:var(--muted)" > RTSP ${esc(host)}:554< / span > `;
}
return `< a href = "${esc(url)}" target = "_blank" rel = "noopener" > source page ↗< / a > `;
}
2026-08-27 15:54:54 -04:00
function youtubeId(url) {
const m = String(url || '').match(/(?:youtu\.be\/|v=)([A-Za-z0-9_-]{6,})/);
return m ? m[1] : '';
}
function camKind(c) {
const u = String(c.snapshot_url || c.source_url || '').toLowerCase();
if (c.device_type === 'youtube' || u.includes('youtube.com') || u.includes('youtu.be')) return 'youtube';
if (c.device_type === 'hls' || u.includes('.m3u8')) return 'hls';
if (c.device_type === 'rtsp' || u.startsWith('rtsp://')) return 'rtsp';
return 'http';
}
2026-08-24 18:02:56 -04:00
function camThumb(c) {
2026-08-24 23:36:34 -04:00
if (!c.id) return '< div class = "thumb placeholder" > no preview available< / div > ';
2026-08-27 15:54:54 -04:00
const kind = camKind(c);
if (kind === 'youtube') {
const vid = youtubeId(c.snapshot_url || c.source_url);
if (!vid) return '< div class = "thumb placeholder" > youtube id missing< / div > ';
return `< iframe class = "thumb" src = "https://www.youtube-nocookie.com/embed/${esc(vid)}?autoplay=1&mute=1" allow = "autoplay; encrypted-media; picture-in-picture" allowfullscreen loading = "lazy" > < / iframe > `;
}
if (kind === 'hls') {
return `< video class = "thumb" data-hls = "/api/cameras/${esc(c.id)}/hls.m3u8" data-fallback = "/api/cameras/${esc(c.id)}/stream" muted autoplay playsinline controls > < / video > `;
}
const first = kind === 'rtsp'
2026-08-24 23:36:34 -04:00
? `/api/cameras/${esc(c.id)}/snapshot`
: `/api/cameras/${esc(c.id)}/stream`;
2026-08-24 21:38:46 -04:00
const onerr = (
"if (!this.dataset.f) { this.dataset.f='1'; this.src='/api/cameras/" + esc(c.id) + "/snapshot'; } " +
"else { this.classList.add('placeholder'); this.onerror=null; this.alt='preview unavailable'; this.removeAttribute('src'); }"
);
2026-08-24 23:36:34 -04:00
return `< img class = "thumb" src = "${first}" alt = "camera preview" loading = "lazy" onerror = "${onerr}" > `;
2026-08-24 17:35:44 -04:00
}
async function loadCams() {
if (!map) return;
2026-08-27 21:12:58 -04:00
if (tooZoomedOut()) {
camsGroup = dropLayer(camsGroup);
markZoom('lp-cams-count');
hudCamsCount = null;
syncHud();
return;
}
2026-08-27 17:00:09 -04:00
const req = ++camReq;
2026-08-24 17:35:44 -04:00
try {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/cameras?bbox=${currentBBox()}&limit=2000`);
2026-08-24 17:35:44 -04:00
const cams = await r.json();
2026-08-27 17:00:09 -04:00
if (req !== camReq) return; // superseded by a newer pan/zoom
2026-08-24 17:35:44 -04:00
if (camsGroup) map.removeLayer(camsGroup);
2026-08-27 17:00:09 -04:00
// Clustered markers: camera coverage stays visible as numbered
// clusters at every zoom instead of vanishing into a sparse/empty
// viewport when zoomed in (and a 5000-dot soup when zoomed out).
camsGroup = L.markerClusterGroup({
maxClusterRadius: 50,
showCoverageOnHover: false,
spiderfyOnMaxZoom: true,
chunkedLoading: true,
chunkInterval: 100,
chunkDelay: 10,
});
cams.forEach(c => {
// Never render a phantom marker for a camera with no coords.
if (c.lat == null || c.lon == null) return;
2026-08-24 17:35:44 -04:00
const icon = L.divIcon({
className: '',
2026-08-24 18:02:56 -04:00
html: '< span style = "display:inline-block;width:10px;height:10px;border-radius:50%;background:#4ade80;box-shadow:0 0 8px #4ade80,0 0 2px #0f172a;border:1px solid #0f172a;" > < / span > ',
iconSize: [12, 12], iconAnchor: [6, 6],
2026-08-24 17:35:44 -04:00
});
2026-08-27 17:00:09 -04:00
camsGroup.addLayer(L.marker([c.lat, c.lon], { icon })
2026-08-27 21:23:25 -04:00
.bindPopup(() => `< div class = "cam-pop" > ` +
2026-08-24 18:02:56 -04:00
`< b > ${esc(c.location_name || 'Open camera')}< / b > ` +
`${c.id ? camThumb(c) : '< div class = "thumb placeholder" > no snapshot< / div > '}` +
`< table > ` +
`< tr > < td class = "k" > Vendor< / td > < td > ${esc(c.vendor || '?')} ${esc(c.device_type || '')}< / td > < / tr > ` +
`< tr > < td class = "k" > Source< / td > < td > ${esc(c.discovery_source || '?')}< / td > < / tr > ` +
`< tr > < td class = "k" > First seen< / td > < td > ${esc((c.first_seen||'').slice(0,16).replace('T',' '))}< / td > < / tr > ` +
`< tr > < td class = "k" > Last seen< / td > < td > ${esc((c.last_seen||'').slice(0,16).replace('T',' '))}< / td > < / tr > ` +
`< tr > < td class = "k" > Coords< / td > < td > ${(c.lat!=null& & c.lon!=null) ? c.lat.toFixed(3)+', '+c.lon.toFixed(3) : 'unknown'}< / td > < / tr > ` +
`< / table > ` +
2026-08-24 23:36:34 -04:00
camSourceLink(c) +
2026-08-27 17:00:09 -04:00
`< / div > `));
});
2026-08-24 17:35:44 -04:00
camsGroup.addTo(map);
2026-08-24 18:02:56 -04:00
camsGroup.eachLayer(l => l.setOpacity & & l.setOpacity(camsOpacity));
document.getElementById('lp-cams-count').textContent = cams.length.toLocaleString();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
hudCamsCount = cams.length;
syncHud();
2026-08-24 17:35:44 -04:00
document.getElementById('map-hint').textContent =
2026-08-24 18:02:56 -04:00
`${cams.length.toLocaleString()} open cameras in view`;
2026-08-24 17:35:44 -04:00
} catch(e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
2026-08-24 17:35:44 -04:00
document.getElementById('map-hint').textContent = `Cameras load failed: ${e.message || e}`;
console.error('Cameras load failed', e);
}
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ── EVENT BLIPS (geolocated ingested events) ── */
const BLIP_COLORS = {
earthquake: '#ffb454',
fire: '#fb923c',
weather: '#35e0ff',
disaster: '#ff5d5d',
'gdel-t2': '#c084fc',
rss: '#ff2e97',
social: '#f0abfc',
satellite: '#a3e635',
};
function blipColor(src) { return BLIP_COLORS[src] || '#8b9db5'; }
async function toggleBlips() {
blipsOn = document.getElementById('lp-blips-on').checked;
if (blipsOn) await loadBlips();
else if (blipsGroup) { map.removeLayer(blipsGroup); blipsGroup = null; }
hudBlipsCount = null;
syncHud();
}
function setBlipsSince() {
blipsSince = document.getElementById('lp-blips-since').value;
if (blipsOn) loadBlips();
}
async function loadBlips() {
if (!map) return;
2026-08-27 17:33:12 -04:00
const req = ++blipReq;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
try {
let url = `${API}/api/events?bbox=${currentBBox()}&has_coords=true&limit=500`;
const since = sinceToISO(blipsSince);
if (since) url += `&since=${encodeURIComponent(since)}`;
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(url);
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
const evs = (await r.json()).filter(ev => ev.source_type !== 'camera');
2026-08-27 17:33:12 -04:00
if (req !== blipReq) return; // superseded by a newer pan/zoom
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
if (blipsGroup) map.removeLayer(blipsGroup);
blipsGroup = L.layerGroup(evs.map(ev => {
const col = blipColor(ev.source_type);
const icon = L.divIcon({
className: '',
html: `< span style = "display:inline-block;width:9px;height:9px;border-radius:50%;background:${col};box-shadow:0 0 8px ${col},0 0 2px #0f172a;border:1px solid #030609;" > < / span > `,
iconSize: [11, 11], iconAnchor: [5, 5],
});
return L.marker([ev.location_lat, ev.location_lon], { icon })
.bindPopup(`< div class = "blip-pop" > ` +
`< div class = "blip-src" > ◈ ${esc(ev.source_type)}< / div > ` +
`< b > ${esc(ev.title || 'Untitled event')}< / b > ` +
`< div class = "blip-time" > ${newsTimeAgo(ev.ingested_at)} · ${esc(ev.location_name || 'no location label')}< / div > ` +
(ev.url ? `< a href = "${esc(ev.url)}" target = "_blank" rel = "noopener" > source ↗< / a > ` : '') +
`< / div > `);
}));
blipsGroup.addTo(map);
document.getElementById('lp-blips-count').textContent = evs.length.toLocaleString();
hudBlipsCount = evs.length;
syncHud();
if (document.getElementById('map-hint').textContent.includes('fire hotspots')) {
// keep the fires line dominant; append blips quietly
} else {
document.getElementById('map-hint').textContent =
`${evs.length.toLocaleString()} event blips in view`;
}
} catch(e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
document.getElementById('map-hint').textContent = `Blips load failed: ${e.message || e}`;
console.error('Blips load failed', e);
}
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
/* ── Live overlays (radar / alerts / WFIGS / aircraft / trains / AIS / NHC) ── */
function refreshLiveOverlays() {
if (!map) return;
if (radarOn) loadRadar();
if (thermalOn) loadThermal();
if (wxAlertsOn) loadWxAlerts();
if (perimOn) loadPerimeters();
if (incidentsOn) loadIncidents();
if (acOn) loadAircraft();
if (trainsOn) loadTrains();
if (vesselsOn) loadVessels();
if (stormsOn) loadStorms();
2026-08-28 09:33:19 -04:00
loadGeofences();
loadFireAircraftHits();
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
}
function addExtraAttrib(html) {
if (!map || !html || extraAttribs.has(html)) return;
extraAttribs.add(html);
map.attributionControl.addAttribution(html);
}
function dropLayer(ref) {
if (ref & & map & & map.hasLayer(ref)) map.removeLayer(ref);
return null;
}
2026-08-27 21:12:58 -04:00
/* Polygon/camera layers hitch at world scale. Same idea as aircraft's zoom< =3
skip: do not fetch or render these until the user is zoomed in. */
const HEAVY_MIN_ZOOM = 4;
function tooZoomedOut() { return !map || map.getZoom() < = HEAVY_MIN_ZOOM; }
function markZoom(id) {
const el = document.getElementById(id);
if (el) el.textContent = 'zoom';
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
function intersectsConus() {
if (!map) return false;
const b = map.getBounds();
const west = Math.max(b.getWest(), -125);
const east = Math.min(b.getEast(), -66);
const south = Math.max(b.getSouth(), 24);
const north = Math.min(b.getNorth(), 50);
return west < east & & south < north & & map . getZoom ( ) > = 4;
}
function severityColor(sev) {
const s = String(sev || '').toLowerCase();
if (s === 'extreme' || s === 'warning') return '#ff5d5d';
if (s === 'severe') return '#fb923c';
if (s === 'moderate') return '#ffb454';
if (s === 'minor') return '#53f0a5';
return '#35e0ff';
}
function altColor(alt) {
const a = Number(alt) || 0;
if (a >= 35000) return '#e0f2fe';
if (a >= 20000) return '#7dd3fc';
if (a >= 10000) return '#38bdf8';
if (a >= 1000) return '#fbbf24';
return '#fb923c';
}
function pointPopup(p) {
const extra = p.extra || {};
const rows = Object.keys(extra).filter(k => k !== 'stations' & & extra[k] != null & & extra[k] !== '')
.slice(0, 8)
.map(k => `< tr > < td class = "k" > ${esc(k)}< / td > < td > ${esc(extra[k])}< / td > < / tr > `).join('');
return `< div class = "cam-pop" > < b > ${esc(p.label || p.id)}< / b > < table > ${rows}< / table > < / div > `;
}
2026-08-27 20:50:35 -04:00
/* ── Heading-aware live-feed glyph markers ───────────────────────────────
Each feed gets a distinct SVG silhouette (pointing north = up). The glyph
is wrapped in a .hdg-glyph span whose CSS transform rotates it to the
object's heading. Icons are cached per (feed, color, heading) so thousands
of markers reuse a handful of L.divIcon instances instead of one per marker.
Rotation lives on the inner glyph span only, never the marker container,
so clustering + iconAnchor stay intact. */
const FEED_GLYPHS = {
ac: '< svg viewBox = "0 0 24 24" fill = "currentColor" aria-hidden = "true" > < path d = "M12 2L13.5 7L20 8.5V10.5L13.5 11L14.5 15L17 16.5V18L12 16.8L7 18V16.5L9.5 15L10.5 11L4 10.5V8.5L10.5 7L12 2Z" / > < / svg > ',
train: '< svg viewBox = "0 0 24 24" fill = "currentColor" aria-hidden = "true" > < path d = "M4.5 3.5h15a1.5 1.5 0 0 1 1.5 1.5v10.5h-18V5a1.5 1.5 0 0 1 1.5-1.5ZM3 15.5h18l-1.5 2.5h-15ZM6.5 6.5h3.5v3.5H6.5ZM13 6.5h3.5v3.5H13ZM7 20.5h2v1H7ZM15 20.5h2v1h-2Z" / > < / svg > ',
vessel: '< svg viewBox = "0 0 24 24" fill = "currentColor" aria-hidden = "true" > < path d = "M12 2 18 13H6Z" / > < path d = "M12 4v9" / > < path d = "M3.5 13h17l-2.5 5h-12Z" / > < / svg > ',
};
const feedIconCache = new Map();
function sanitizeColor(c, fallback) {
const s = String(c || '').trim();
// Hex colors, rgb()/rgba() triplets, or plain alphabetic CSS keywords.
// Anything with quotes/angle brackets/url()/semicolons is rejected so a
// malicious API value can never break out of the inline style attribute.
if (/^#[0-9a-fA-F]{3,8}$/.test(s)) return s;
if (/^[a-z]{3,20}$/.test(s)) return s;
if (/^rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*(,\s*[\d.]+\s*)?\)$/.test(s)) return s;
return fallback;
}
function feedIcon(feed, color, heading) {
// Normalize heading to [0,360) integer so the cache stays bounded.
// Missing/empty/NaN heading -> -1 sentinel -> glyph rendered upright.
const hnum = Number(heading);
const h = (heading === null || heading === '' || heading === undefined || !Number.isFinite(hnum))
? -1
: (Math.round(hnum % 360) + 360) % 360;
const key = `${feed}|${color}|${h}`;
let ic = feedIconCache.get(key);
if (!ic) {
const rot = h >= 0 ? `transform:rotate(${h}deg);` : '';
ic = L.divIcon({
className: '',
html: `< span class = "hdg-marker" > < span class = "hdg-glyph hdg-${feed}" style = "color:${color};${rot}" > ${FEED_GLYPHS[feed]}< / span > < / span > `,
iconSize: [26, 26], iconAnchor: [13, 13],
});
feedIconCache.set(key, ic);
}
return ic;
}
2026-08-27 21:23:25 -04:00
function makePointMarker(p, colorFn, feed, renderer) {
if (p.lat == null || p.lon == null) return null;
const col = sanitizeColor(colorFn(p), '#35e0ff');
if (feed) {
const heading = Number(p.heading);
const icon = feedIcon(feed, col, Number.isNaN(heading) ? null : heading);
return L.marker([p.lat, p.lon], { icon }).bindPopup(() => pointPopup(p));
}
return L.circleMarker([p.lat, p.lon], {
radius: 5, color: col, fillColor: col, fillOpacity: 0.9, weight: 1,
renderer,
}).bindPopup(() => pointPopup(p));
}
2026-08-27 20:50:35 -04:00
function renderPoints(existing, points, colorFn, cluster, feed) {
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const zoom = map.getZoom();
const useCluster = cluster & & (zoom < 7 | | points . length > 200);
2026-08-27 21:23:25 -04:00
const canReuse = existing & & map.hasLayer(existing)
& & !!existing._osintCluster === !!useCluster
& & existing._osintById;
if (!canReuse) {
if (existing) map.removeLayer(existing);
const group = useCluster
? L.markerClusterGroup({ maxClusterRadius: 48, showCoverageOnHover: false, spiderfyOnMaxZoom: true, chunkedLoading: true })
: L.layerGroup();
group._osintCluster = !!useCluster;
group._osintById = new Map();
const renderer = pointCanvas();
points.forEach(p => {
const m = makePointMarker(p, colorFn, feed, renderer);
if (!m) return;
group.addLayer(m);
if (p.id != null) group._osintById.set(String(p.id), m);
});
group.addTo(map);
return group;
}
const group = existing;
const byId = group._osintById;
const next = new Set();
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const renderer = pointCanvas();
points.forEach(p => {
2026-08-27 21:23:25 -04:00
if (p.lat == null || p.lon == null || p.id == null) return;
const id = String(p.id);
next.add(id);
2026-08-27 20:50:35 -04:00
const col = sanitizeColor(colorFn(p), '#35e0ff');
2026-08-27 21:23:25 -04:00
const m = byId.get(id);
if (m) {
m.setLatLng([p.lat, p.lon]);
if (feed) m.setIcon(feedIcon(feed, col, p.heading));
else if (m.setStyle) m.setStyle({ color: col, fillColor: col });
2026-08-27 20:50:35 -04:00
} else {
2026-08-27 21:23:25 -04:00
const nm = makePointMarker(p, colorFn, feed, renderer);
if (!nm) return;
group.addLayer(nm);
byId.set(id, nm);
}
});
byId.forEach((m, id) => {
if (!next.has(id)) {
group.removeLayer(m);
byId.delete(id);
2026-08-27 20:50:35 -04:00
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
});
return group;
}
async function toggleRadar() {
radarOn = document.getElementById('lp-radar-on').checked;
if (radarOn) await loadRadar();
else { radarLayer = dropLayer(radarLayer); if (radarTimer) { clearInterval(radarTimer); radarTimer = null; } }
}
function setRadarOpacity(v) {
radarOpacity = v / 100;
document.getElementById('lp-radar-val').textContent = `${Math.round(v)}%`;
if (radarLayer) radarLayer.setOpacity(radarOpacity);
}
async function loadRadar() {
if (!map) return;
try {
if (!radarMeta) {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/map/radar`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
radarMeta = await r.json();
addExtraAttrib('< a href = "https://www.rainviewer.com/api.html" > Weather data by RainViewer< / a > ');
addExtraAttrib('Iowa Environmental Mesonet');
if (!radarTimer) radarTimer = setInterval(() => { radarMeta = null; if (radarOn) loadRadar(); }, 5 * 60 * 1000);
}
const useIem = intersectsConus();
const url = useIem
? (radarMeta.iemTileUrl || 'https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q/{z}/{x}/{y}.png')
: (radarMeta.tileUrl);
if (!url) return;
document.getElementById('lp-radar-count').textContent = useIem ? 'IEM' : 'RV';
if (radarLayer) {
radarLayer.setUrl(url);
radarLayer.setOpacity(radarOpacity);
} else {
radarLayer = L.tileLayer(url, { opacity: radarOpacity, maxZoom: 12, maxNativeZoom: useIem ? 18 : 7, attribution: '' }).addTo(map);
}
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Radar load failed', e);
document.getElementById('lp-radar-count').textContent = 'err';
}
}
async function toggleThermal() {
thermalOn = document.getElementById('lp-thermal-on').checked;
if (thermalOn) loadThermal();
else thermalLayer = dropLayer(thermalLayer);
}
function loadThermal() {
if (!map) return;
const time = (document.getElementById('map-date') & & document.getElementById('map-date').value)
|| new Date().toISOString().slice(0, 10);
const url = `https://gibs.earthdata.nasa.gov/wmts/epsg3857/best/VIIRS_SNPP_Thermal_Anomalies_375m_All/default/${time}/GoogleMapsCompatible_Level9/{z}/{y}/{x}.png`;
addExtraAttrib('NASA GIBS thermal anomalies');
if (thermalLayer) thermalLayer.setUrl(url);
else thermalLayer = L.tileLayer(url, { opacity: 0.85, maxNativeZoom: 9, maxZoom: 12, attribution: '' }).addTo(map);
}
async function toggleWxAlerts() {
wxAlertsOn = document.getElementById('lp-alerts-on').checked;
if (wxAlertsOn) await loadWxAlerts();
else wxAlertsGroup = dropLayer(wxAlertsGroup);
}
async function loadWxAlerts() {
if (!map) return;
2026-08-27 21:12:58 -04:00
if (tooZoomedOut()) {
wxAlertsGroup = dropLayer(wxAlertsGroup);
markZoom('lp-alerts-count');
return;
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const req = ++overlayReq.alerts;
try {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/weather-alerts?bbox=${currentBBox()}`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const fc = await r.json();
if (req !== overlayReq.alerts) return;
wxAlertsGroup = dropLayer(wxAlertsGroup);
const feats = fc.features || [];
wxAlertsGroup = L.geoJSON(fc, {
2026-08-27 21:23:25 -04:00
renderer: L.canvas({ padding: 0.5 }),
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
style: (f) => ({
color: severityColor((f.properties || {}).severity),
weight: 2, fillOpacity: 0.18,
}),
onEachFeature: (f, layer) => {
const p = f.properties || {};
layer.bindPopup(`< div class = "blip-pop" > < div class = "blip-src" > ${esc(p.source || 'alert')}< / div > < b > ${esc(p.event || p.headline || 'Alert')}< / b > < div class = "blip-time" > ${esc(p.severity || '')} · ${esc(p.areaDesc || p.wfo || '')}< / div > < / div > `);
},
}).addTo(map);
document.getElementById('lp-alerts-count').textContent = feats.length.toLocaleString();
addExtraAttrib('NWS / IEM storm-based warnings');
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Alerts load failed', e);
document.getElementById('lp-alerts-count').textContent = 'err';
}
}
async function togglePerimeters() {
perimOn = document.getElementById('lp-perim-on').checked;
if (perimOn) await loadPerimeters();
else perimGroup = dropLayer(perimGroup);
}
async function loadPerimeters() {
if (!map) return;
2026-08-27 21:12:58 -04:00
if (tooZoomedOut()) {
perimGroup = dropLayer(perimGroup);
markZoom('lp-perim-count');
return;
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const req = ++overlayReq.perim;
try {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/fire-perimeters?bbox=${currentBBox()}`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const fc = await r.json();
if (req !== overlayReq.perim) return;
perimGroup = dropLayer(perimGroup);
const feats = fc.features || [];
perimGroup = L.geoJSON(fc, {
2026-08-27 21:23:25 -04:00
renderer: L.canvas({ padding: 0.5 }),
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
style: (f) => {
const acres = Number((f.properties || {}).poly_GISAcres || (f.properties || {}).attr_IncidentSize || 0);
return { color: acres > 10000 ? '#ef4444' : '#fb923c', weight: 2, fillOpacity: 0.25, fillColor: '#fb923c' };
},
onEachFeature: (f, layer) => {
const p = f.properties || {};
const name = p.poly_IncidentName || p.IncidentName || 'Fire';
const acres = p.poly_GISAcres || p.attr_IncidentSize || '?';
layer.bindPopup(`< div class = "blip-pop" > < b > ${esc(name)}< / b > < div class = "blip-time" > ${esc(acres)} acres · ${esc(p.attr_PercentContained || '')}% contained< / div > < / div > `);
},
}).addTo(map);
document.getElementById('lp-perim-count').textContent = feats.length.toLocaleString();
addExtraAttrib('NIFC WFIGS');
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Perimeters load failed', e);
document.getElementById('lp-perim-count').textContent = 'err';
}
}
async function toggleIncidents() {
incidentsOn = document.getElementById('lp-incidents-on').checked;
if (incidentsOn) await loadIncidents();
else incidentsGroup = dropLayer(incidentsGroup);
}
async function loadIncidents() {
if (!map) return;
2026-08-27 21:12:58 -04:00
if (tooZoomedOut()) {
incidentsGroup = dropLayer(incidentsGroup);
markZoom('lp-incidents-count');
return;
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const req = ++overlayReq.incidents;
try {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/fire-incidents?bbox=${currentBBox()}`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const pts = await r.json();
if (req !== overlayReq.incidents) return;
incidentsGroup = renderPoints(incidentsGroup, pts, () => '#ef4444', false);
document.getElementById('lp-incidents-count').textContent = pts.length.toLocaleString();
addExtraAttrib('NIFC WFIGS');
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Incidents load failed', e);
document.getElementById('lp-incidents-count').textContent = 'err';
}
}
async function toggleAircraft() {
acOn = document.getElementById('lp-ac-on').checked;
if (acOn) await loadAircraft();
else acGroup = dropLayer(acGroup);
}
async function loadAircraft() {
if (!map) return;
if (map.getZoom() < = 3) {
document.getElementById('lp-ac-count').textContent = 'zoom';
return;
}
const req = ++overlayReq.ac;
try {
2026-08-28 09:33:19 -04:00
const r = await overlayFetch(`${API}/api/aircraft?bbox=${currentBBox()}${dvrQs()}`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const pts = await r.json();
if (req !== overlayReq.ac) return;
2026-08-28 09:33:19 -04:00
acGroup = renderPoints(acGroup, Array.isArray(pts) ? pts : [], p => acColor(p), true, 'ac');
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
document.getElementById('lp-ac-count').textContent = (pts.length || 0).toLocaleString();
addExtraAttrib('< a href = "https://www.adsb.lol/docs/open-data/api" > ADSB.lol< / a > ODbL');
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Aircraft load failed', e);
document.getElementById('lp-ac-count').textContent = 'err';
}
}
async function toggleTrains() {
trainsOn = document.getElementById('lp-trains-on').checked;
if (trainsOn) await loadTrains();
else trainsGroup = dropLayer(trainsGroup);
}
async function loadTrains() {
if (!map) return;
const req = ++overlayReq.trains;
try {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/trains?bbox=${currentBBox()}`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const pts = await r.json();
if (req !== overlayReq.trains) return;
2026-08-27 20:50:35 -04:00
trainsGroup = renderPoints(trainsGroup, Array.isArray(pts) ? pts : [], p => (p.extra || {}).iconColor || '#c084fc', false, 'train');
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
document.getElementById('lp-trains-count').textContent = (pts.length || 0).toLocaleString();
addExtraAttrib('< a href = "https://amtraker.com/about" > Amtraker< / a > ');
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Trains load failed', e);
document.getElementById('lp-trains-count').textContent = 'err';
}
}
async function toggleVessels() {
vesselsOn = document.getElementById('lp-vessels-on').checked;
2026-08-27 21:46:37 -04:00
if (vesselsOn) {
lastVesselSubBox = '';
await loadVessels();
} else {
vesselsGroup = dropLayer(vesselsGroup);
clearTimeout(vesselRefollowTimer);
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
}
async function loadVessels() {
if (!map) return;
if (map.getZoom() < = 3) {
document.getElementById('lp-vessels-count').textContent = 'zoom';
return;
}
const req = ++overlayReq.vessels;
2026-08-27 21:46:37 -04:00
const bb = currentBBox();
// Retune the server-side AIS stream to this viewport. Skip whole-world
// boxes (zoom out / dateline wrap) so the free tier is not flooded; the
// worker coalesces retunes and caps them at 1/s upstream.
const [bw, bs, be, bn] = bb.split(',').map(Number);
const isWorld = (be - bw) >= 300 || bb.startsWith('-180.0000,-85.0000,180.0000,85.0000');
const subKey = isWorld ? '' : quantizeBBox(bb);
let retuned = false;
if (subKey & & subKey !== lastVesselSubBox) {
lastVesselSubBox = subKey;
retuned = true;
fetch(`${API}/api/vessels/subscribe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bbox: subKey })
}).catch(() => {});
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
try {
2026-08-28 09:33:19 -04:00
const r = await overlayFetch(`${API}/api/vessels?bbox=${bb}${dvrQs()}`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const pts = await r.json();
if (req !== overlayReq.vessels) return;
vesselsGroup = renderPoints(vesselsGroup, Array.isArray(pts) ? pts : [], p => {
const sog = Number((p.speed) || 0);
return sog > 0.5 ? '#2dd4bf' : '#64748b';
2026-08-27 20:50:35 -04:00
}, true, 'vessel');
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
document.getElementById('lp-vessels-count').textContent = (pts.length || 0).toLocaleString();
addExtraAttrib('AISStream');
2026-08-27 21:46:37 -04:00
// First positions for a new zone arrive ~1-3s after the retune.
if (retuned) {
clearTimeout(vesselRefollowTimer);
vesselRefollowTimer = setTimeout(() => { if (vesselsOn) loadVessels(); }, 2200);
}
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Vessels load failed', e);
document.getElementById('lp-vessels-count').textContent = 'err';
}
}
async function toggleStorms() {
stormsOn = document.getElementById('lp-storms-on').checked;
if (stormsOn) await loadStorms();
else stormsGroup = dropLayer(stormsGroup);
}
async function loadStorms() {
if (!map) return;
const req = ++overlayReq.storms;
try {
2026-08-27 21:23:25 -04:00
const r = await overlayFetch(`${API}/api/storms`);
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
const pts = await r.json();
if (req !== overlayReq.storms) return;
stormsGroup = renderPoints(stormsGroup, Array.isArray(pts) ? pts : [], () => '#f472b6', false);
document.getElementById('lp-storms-count').textContent = (pts.length || 0).toLocaleString();
addExtraAttrib('NHC');
} catch (e) {
2026-08-27 21:23:25 -04:00
if (isAbort(e)) return;
feat: toggleable live map feeds (ADS-B, trains, AIS, radar, WFIGS, NWS)
Wire the free data streams from docs/free-data-streams.md into the
dashboard as layer-panel toggles. Third-party APIs are proxied/cached
in FastAPI; raster tiles (IEM, RainViewer, GIBS) stay in the browser.
- Aircraft via ADSB.lol viewport poll (bbox required, radius ≤ 150 nm)
- Amtraker trains, NHC storms, WFIGS incidents/perimeters
- NWS + IEM SBW as /api/weather-alerts (does not collide with /api/alerts)
- AISStream worker is server-side only and idles without AISSTREAM_API_KEY
- Caltrans CWWP2 D1–D12 camera parser; FIRMS dual-write NOAA-20/21
2026-08-27 19:08:30 -04:00
console.error('Storms load failed', e);
document.getElementById('lp-storms-count').textContent = 'err';
}
}
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
/* ═══════════════ INITIAL LOAD ═══════════════ */
initNav();
initSettings();
initMarketTicker();
2026-08-27 21:12:58 -04:00
checkHealth();
frontend: Ghost-in-the-Shell overhaul — map-first landing, HUD chrome, event blips, tickers
- Map is now the landing view: full-viewport NASA GIBS globe, existing
fires/cameras/HLS map machinery preserved verbatim
- New 'Event Blips' layer: geolocated ingest events, color-coded by source,
bbox/since/has_coords filters added to GET /api/events
- Weather / Flights(ADS-B) / Vessels(AIS) layer slots reserved (feed pending)
- Market ticker strip with configurable symbols — auto-promotes to LIVE when
GET /api/market returns {symbols:[...]} (contract documented in Settings)
- Breaking-news ticker: LLM exec-summary flash + headline marquee, 15-min cycle
- Dropdown nav (Map/News/Events/Alerts/Entities/Ingest/API Keys/Settings),
Settings view (localStorage: symbols, map layer defaults), System panel
- Section-9 theme: near-black navy, cyan/magenta accents, Orbitron/Rajdhani/
Share Tech Mono, chamfered HUD panels, scanlines, boot splash, UTC clock
- Fix: events.camera enum value missing from models.py/schemas.py caused 500s
on every events query once camera events flowed in (migration 004 added it
to the DB enum only)
2026-08-27 17:22:14 -04:00
initMap();
2026-08-27 21:36:54 -04:00
// Ticker lives on the map HUD — load after overlays start so it does not
// contend with the first bbox burst. Payload is slim (no article bodies).
setTimeout(() => loadNews(true), 400);
2026-08-27 21:12:58 -04:00
setInterval(checkHealth, 30000);
2026-08-28 09:33:19 -04:00
/* Phase 2: DVR slider + geofence draw (no Leaflet.Draw) + fire/aircraft hits */
function dvrQs() { return dvrTs ? `& timestamp=${encodeURIComponent(dvrTs)}` : ''; }
function dvrGoLive() {
dvrTs = null;
const bar = document.getElementById('dvr-bar');
if (bar) bar.classList.add('live');
const sl = document.getElementById('dvr-slider');
if (sl) sl.value = sl.max;
const lab = document.getElementById('dvr-label');
if (lab) lab.textContent = 'live';
if (acOn) loadAircraft();
if (vesselsOn) loadVessels();
}
function dvrScrub(v) {
if (!dvrMaxMs) { dvrGoLive(); return; }
const ms = dvrMinMs + (Number(v) / 360) * (dvrMaxMs - dvrMinMs);
dvrTs = new Date(ms).toISOString();
const bar = document.getElementById('dvr-bar');
if (bar) bar.classList.toggle('live', Number(v) >= 359);
if (Number(v) >= 359) { dvrGoLive(); return; }
const lab = document.getElementById('dvr-label');
if (lab) lab.textContent = dvrTs.slice(11, 16) + 'Z';
if (acOn) loadAircraft();
if (vesselsOn) loadVessels();
}
async function initDvrRange() {
try {
const r = await fetch(`${API}/api/tracks/range`);
const b = await r.json();
dvrMinMs = Date.parse(b.min);
dvrMaxMs = Date.parse(b.max);
if (!dvrMinMs || !dvrMaxMs) {
dvrMaxMs = Date.now();
dvrMinMs = dvrMaxMs - 6 * 3600 * 1000;
}
} catch (e) {
dvrMaxMs = Date.now();
dvrMinMs = dvrMaxMs - 6 * 3600 * 1000;
}
}
function showGeofenceToast(p) {
const box = document.getElementById('dvr-toast');
if (!box) return;
const el = document.createElement('div');
el.className = 'gf-alert';
el.textContent = `GEOFENCE ${p.geofence_name || ''} · ${p.source_kind} ${p.entity_id || ''}`;
box.prepend(el);
setTimeout(() => el.remove(), 8000);
}
function toggleGeofenceDraw() {
gfDrawOn = !gfDrawOn;
gfVerts = [];
if (gfLayer & & map) { map.removeLayer(gfLayer); gfLayer = null; }
const btn = document.getElementById('gf-draw');
if (btn) btn.textContent = gfDrawOn ? 'Click map… (dbl-click close)' : 'Draw geofence';
if (!map) return;
if (gfDrawOn) {
map.getContainer().style.cursor = 'crosshair';
map.on('click', onGfClick);
map.on('dblclick', onGfClose);
} else {
map.getContainer().style.cursor = '';
map.off('click', onGfClick);
map.off('dblclick', onGfClose);
}
}
function onGfClick(e) {
if (!gfDrawOn) return;
L.DomEvent.stop(e);
gfVerts.push([e.latlng.lng, e.latlng.lat]);
if (gfLayer & & map) map.removeLayer(gfLayer);
if (gfVerts.length >= 2) {
gfLayer = L.polyline(gfVerts.map(v => [v[1], v[0]]), { color: '#ff2e97', weight: 2 }).addTo(map);
}
}
async function onGfClose(e) {
if (!gfDrawOn) return;
L.DomEvent.stop(e);
if (gfVerts.length < 3 ) return ;
const ring = gfVerts.concat([gfVerts[0]]);
const geojson = { type: 'Polygon', coordinates: [ring] };
try {
await fetch(`${API}/api/geofences`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Fence ' + new Date().toISOString().slice(11, 19), geojson }),
});
} catch (err) { console.error('geofence save failed', err); }
toggleGeofenceDraw();
loadGeofences();
}
async function loadGeofences() {
if (!map) return;
try {
const r = await overlayFetch(`${API}/api/geofences`);
const rows = await r.json();
if (gfSaved & & map.hasLayer(gfSaved)) map.removeLayer(gfSaved);
const feats = (Array.isArray(rows) ? rows : []).map(f => ({
type: 'Feature', properties: { name: f.name, id: f.id }, geometry: f.geojson,
}));
gfSaved = L.geoJSON({ type: 'FeatureCollection', features: feats }, {
style: { color: '#ff2e97', weight: 2, fillOpacity: 0.08 },
}).addTo(map);
} catch (e) { if (!isAbort(e)) console.error('geofences load failed', e); }
}
async function loadFireAircraftHits() {
try {
const r = await fetch(`${API}/api/fire-aircraft`);
const rows = await r.json();
(Array.isArray(rows) ? rows : []).forEach(h => {
if (h.aircraft_hex) firefighterHex.add(String(h.aircraft_hex));
});
} catch (e) { /* empty without table */ }
}
initDvrRange();
2026-06-04 20:30:04 -04:00
< / script >
< / body >
< / html >