@ -3,10 +3,10 @@
< head >
< head >
< meta charset = "UTF-8" >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, viewport-fit=cover" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, viewport-fit=cover" >
< title > OSINT//DASHBOARD — Global Situational Awareness Terminal < / title >
< title > OSINT//< / title >
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link rel = "preconnect" href = "https://fonts.gstatic.com" crossorigin >
< 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" >
< link href = "https://fonts.googleapis.com/css2?family= IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600 &display=swap" rel = "stylesheet" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/leaflet.css" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/leaflet.css" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/MarkerCluster.css" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/MarkerCluster.css" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/MarkerCluster.Default.css" >
< link rel = "stylesheet" href = "/static/vendor/leaflet/MarkerCluster.Default.css" >
@ -35,12 +35,12 @@
--text: #c8e6f2;
--text: #c8e6f2;
--muted: #5f7d95;
--muted: #5f7d95;
--topbar-h: 46px;
--topbar-h: 46px;
--dock-h: 62px;
--dock-h: 32px; /* one NEWS ticker; MKT hidden until live */
}
}
* { margin: 0; padding: 0; box-sizing: border-box; }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
html, body { height: 100%; }
body {
body {
font-family: 'Rajdhani ', 'Segoe UI', system-ui, sans-serif;
font-family: 'IBM Plex Sans ', 'Segoe UI', system-ui, sans-serif;
background: var(--bg-0); color: var(--text);
background: var(--bg-0); color: var(--text);
overflow: hidden;
overflow: hidden;
font-size: 15px; letter-spacing: 0.01em;
font-size: 15px; letter-spacing: 0.01em;
@ -48,12 +48,12 @@
::selection { background: var(--cyan-dim); color: #eafcff; }
::selection { background: var(--cyan-dim); color: #eafcff; }
:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
a { color: var(--cyan); }
a { color: var(--cyan); }
.mono, code, kbd { font-family: 'Share Tech Mono', ui-monospace, SFMono-Regular, Menlo, monospace; }
.mono, code, kbd { font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; }
.btn {
.btn {
background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
border: 1px solid var(--line); color: var(--text);
border: 1px solid var(--line); color: var(--text);
border-radius: 4px; padding: 0.35rem 0.9rem; cursor: pointer;
border-radius: 4px; padding: 0.35rem 0.9rem; cursor: pointer;
font-family: 'Rajdhani ', sans-serif; font-weight: 600;
font-family: 'IBM Plex Sans ', sans-serif; font-weight: 600;
font-size: 0.8rem; letter-spacing: 0.06em; text-transform: uppercase;
font-size: 0.8rem; letter-spacing: 0.06em; text-transform: uppercase;
transition: border-color .15s, box-shadow .15s, color .15s;
transition: border-color .15s, box-shadow .15s, color .15s;
}
}
@ -84,18 +84,18 @@
}
}
.brand { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.brand { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.brand-mark {
.brand-mark {
font-family: 'Orbitron ', sans-serif; color: var(--cyan);
font-family: 'IBM Plex Sans ', sans-serif; color: var(--cyan);
font-size: 1.05rem; text-shadow: 0 0 12px var(--cyan-glow);
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;
border: 1px solid var(--line-hi); padding: 0.1rem 0.34rem; border-radius: 3px;
background: rgba(53,224,255,0.06);
background: rgba(53,224,255,0.06);
}
}
.brand-name {
.brand-name {
font-family: 'Orbitron ', sans-serif; font-weight: 700; font-size: 0.95rem;
font-family: 'IBM Plex Sans ', sans-serif; font-weight: 700; font-size: 0.95rem;
color: #eafcff; letter-spacing: 0.14em; white-space: nowrap;
color: #eafcff; letter-spacing: 0.14em; white-space: nowrap;
}
}
.brand-name .accent { color: var(--cyan); text-shadow: 0 0 10px var(--cyan-glow); }
.brand-name .accent { color: var(--cyan); text-shadow: 0 0 10px var(--cyan-glow); }
.brand-sub {
.brand-sub {
font-family: 'Share Tech Mono', monospace; font-size: 0.58rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.58rem;
color: var(--muted); letter-spacing: 0.22em; text-transform: uppercase;
color: var(--muted); letter-spacing: 0.22em; text-transform: uppercase;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
}
@ -103,7 +103,7 @@
.topbar-right { display: flex; align-items: center; gap: 1rem; }
.topbar-right { display: flex; align-items: center; gap: 1rem; }
.sys-status {
.sys-status {
display: flex; align-items: center; gap: 0.5rem;
display: flex; align-items: center; gap: 0.5rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.72rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem;
color: var(--muted); white-space: nowrap;
color: var(--muted); white-space: nowrap;
}
}
.sys-status .sep { opacity: 0.35; }
.sys-status .sep { opacity: 0.35; }
@ -112,6 +112,10 @@
.dot.bad { background: var(--red); box-shadow: 0 0 8px var(--red); }
.dot.bad { background: var(--red); box-shadow: 0 0 8px var(--red); }
.dot.dim { background: var(--muted); box-shadow: none; animation: none; }
.dot.dim { background: var(--muted); box-shadow: none; animation: none; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
.clock, .lp-count, .hud-chip b, #utc-clock, #dvr-label {
font-variant-numeric: tabular-nums;
font-family: 'IBM Plex Mono', ui-monospace, monospace;
}
.clock { color: var(--cyan); }
.clock { color: var(--cyan); }
@media (max-width: 720px) { .sys-status .hide-sm { display: none; } }
@media (max-width: 720px) { .sys-status .hide-sm { display: none; } }
@ -121,7 +125,7 @@
display: flex; align-items: center; gap: 0.5rem;
display: flex; align-items: center; gap: 0.5rem;
background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
background: linear-gradient(180deg, var(--bg-3), var(--bg-2));
border: 1px solid var(--line-hi); color: var(--text);
border: 1px solid var(--line-hi); color: var(--text);
font-family: 'Orbitron ', sans-serif; font-size: 0.72rem; font-weight: 700;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.72rem; font-weight: 700;
letter-spacing: 0.16em; padding: 0.42rem 0.85rem; border-radius: 4px;
letter-spacing: 0.16em; padding: 0.42rem 0.85rem; border-radius: 4px;
cursor: pointer; transition: border-color .15s, box-shadow .15s;
cursor: pointer; transition: border-color .15s, box-shadow .15s;
}
}
@ -143,7 +147,7 @@
.nav-item {
.nav-item {
display: flex; align-items: center; gap: 0.6rem; width: 100%;
display: flex; align-items: center; gap: 0.6rem; width: 100%;
background: none; border: none; color: var(--text); text-align: left;
background: none; border: none; color: var(--text); text-align: left;
font-family: 'Rajdhani ', sans-serif; font-weight: 600; font-size: 0.88rem;
font-family: 'IBM Plex Sans ', sans-serif; font-weight: 600; font-size: 0.88rem;
letter-spacing: 0.09em; text-transform: uppercase;
letter-spacing: 0.09em; text-transform: uppercase;
padding: 0.55rem 0.7rem; border-radius: 4px; cursor: pointer;
padding: 0.55rem 0.7rem; border-radius: 4px; cursor: pointer;
border-left: 2px solid transparent;
border-left: 2px solid transparent;
@ -177,17 +181,12 @@
max-width: calc(100% - 300px); /* keep off LAYERS (left) and zoom (right) */
max-width: calc(100% - 300px); /* keep off LAYERS (left) and zoom (right) */
justify-content: center;
justify-content: center;
}
}
.chokepoint-btns { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem; }
.chokepoint-btns { display: none; }
.chokepoint-btns .btn { padding: 0.22rem 0.42rem; font-size: 0.66rem; letter-spacing: 0.04em; }
#chokepoint-select { display: inline-block; min-width: 8rem; }
.chokepoint-btns .btn.is-on {
border-color: var(--cyan); color: var(--cyan);
box-shadow: 0 0 8px var(--cyan-glow); background: rgba(53,224,255,0.10);
}
#chokepoint-select { display: none; }
.map-toolbar select, .map-toolbar input[type="date"], .map-toolbar input[type="datetime-local"] {
.map-toolbar select, .map-toolbar input[type="date"], .map-toolbar input[type="datetime-local"] {
background: var(--bg-1); color: var(--text); border: 1px solid var(--line);
background: var(--bg-1); color: var(--text); border: 1px solid var(--line);
border-radius: 4px; padding: 0.3rem 0.45rem; font-size: 0.76rem;
border-radius: 4px; padding: 0.3rem 0.45rem; font-size: 0.76rem;
font-family: 'Share Tech Mono', monospace; color-scheme: dark;
font-family: 'IBM Plex Mono', monospace; color-scheme: dark;
}
}
.map-toolbar select:focus, .map-toolbar input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.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; }
.map-toolbar .btn { padding: 0.3rem 0.6rem; font-size: 0.72rem; }
@ -206,7 +205,7 @@
.leaflet-control-zoom a:hover { background: var(--bg-3) !important; color: var(--cyan) !important; }
.leaflet-control-zoom a:hover { background: var(--bg-3) !important; color: var(--cyan) !important; }
.leaflet-control-attribution {
.leaflet-control-attribution {
background: rgba(3,6,9,0.78) !important; color: #8fa8bd !important;
background: rgba(3,6,9,0.78) !important; color: #8fa8bd !important;
font-family: 'Share Tech Mono', monospace !important; font-size: 10px !important;
font-family: 'IBM Plex Mono', monospace !important; font-size: 10px !important;
}
}
.leaflet-control-attribution a { color: var(--cyan) !important; }
.leaflet-control-attribution a { color: var(--cyan) !important; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
@ -264,7 +263,7 @@
/* ── Map hint + HUD chips ── */
/* ── Map hint + HUD chips ── */
.map-hint {
.map-hint {
position: absolute; left: 12px; bottom: 12px; z-index: 500;
position: absolute; left: 12px; bottom: 12px; z-index: 500;
font-family: 'Share Tech Mono', monospace; font-size: 0.68rem; color: var(--muted);
font-family: 'IBM Plex Mono', monospace; font-size: 0.68rem; color: var(--muted);
background: rgba(6,11,20,0.72); border: 1px solid var(--line);
background: rgba(6,11,20,0.72); border: 1px solid var(--line);
border-radius: 4px; padding: 0.3rem 0.6rem; max-width: 60%;
border-radius: 4px; padding: 0.3rem 0.6rem; max-width: 60%;
text-shadow: 0 0 6px rgba(0,0,0,0.8);
text-shadow: 0 0 6px rgba(0,0,0,0.8);
@ -275,7 +274,7 @@
}
}
.hud-chip {
.hud-chip {
display: flex; align-items: center; gap: 0.45rem;
display: flex; align-items: center; gap: 0.45rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted);
font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--muted);
background: rgba(6,11,20,0.78); border: 1px solid var(--line);
background: rgba(6,11,20,0.78); border: 1px solid var(--line);
border-radius: 4px; padding: 0.26rem 0.55rem; letter-spacing: 0.06em;
border-radius: 4px; padding: 0.26rem 0.55rem; letter-spacing: 0.06em;
backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
@ -292,7 +291,7 @@
z-index: 520; display: flex; align-items: center; gap: 0.55rem;
z-index: 520; display: flex; align-items: center; gap: 0.55rem;
background: rgba(6,11,20,0.86); border: 1px solid var(--line-hi);
background: rgba(6,11,20,0.86); border: 1px solid var(--line-hi);
border-radius: 4px; padding: 0.28rem 0.7rem;
border-radius: 4px; padding: 0.28rem 0.7rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted);
font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--muted);
min-width: 280px; max-width: 52%;
min-width: 280px; max-width: 52%;
}
}
#dvr-bar input[type=range] { flex: 1; accent-color: var(--cyan); }
#dvr-bar input[type=range] { flex: 1; accent-color: var(--cyan); }
@ -303,7 +302,7 @@
}
}
#dvr-toast .gf-alert {
#dvr-toast .gf-alert {
background: rgba(255,46,151,0.16); border: 1px solid var(--magenta);
background: rgba(255,46,151,0.16); border: 1px solid var(--magenta);
color: var(--text); font-family: 'Share Tech Mono', monospace;
color: var(--text); font-family: 'IBM Plex Mono', monospace;
font-size: 0.68rem; padding: 0.4rem 0.6rem; margin-bottom: 0.35rem;
font-size: 0.68rem; padding: 0.4rem 0.6rem; margin-bottom: 0.35rem;
border-radius: 4px;
border-radius: 4px;
}
}
@ -311,17 +310,19 @@
/* ── Layer control panel (dark HUD) ── */
/* ── Layer control panel (dark HUD) ── */
#layer-panel {
#layer-panel {
position: absolute; top: 58px; left: 12px; z-index: 600;
position: absolute; top: 58px; left: 12px; z-index: 600;
width: 268px; max-height: calc(100% - 74px); overflow: hidden;
width: min(320px, calc(100% - 24px)); max-width: 320px;
max-height: calc(100% - 74px); overflow: hidden;
background: rgba(6,11,20,0.86); backdrop-filter: blur(8px);
background: rgba(6,11,20,0.86); backdrop-filter: blur(8px);
border: 1px solid var(--line-hi); border-radius: 6px;
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);
box-shadow: 0 0 22px rgba(53,224,255,0.14), 0 0 2px rgba(53,224,255,0.4);
color: var(--text); font-size: 0.78rem; display: flex; flex-direction: column;
color: var(--text); font-size: 0.78rem; display: flex; flex-direction: column;
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));
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));
}
}
#layer-panel.collapsed { width: auto; max-width: 320px; }
.lp-head {
.lp-head {
display: flex; justify-content: space-between; align-items: center;
display: flex; justify-content: space-between; align-items: center;
padding: 0.55rem 0.75rem; border-bottom: 1px solid var(--line);
padding: 0.55rem 0.75rem; border-bottom: 1px solid var(--line);
font-family: 'Orbitron ', sans-serif; font-size: 0.68rem; font-weight: 700;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.68rem; font-weight: 700;
letter-spacing: 0.14em; text-transform: uppercase; color: var(--cyan);
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;
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);
background: linear-gradient(180deg, rgba(53,224,255,0.07), transparent);
@ -341,7 +342,7 @@
.lp-dot.fires { background: #fb923c; box-shadow: 0 0 7px #fb923c; }
.lp-dot.fires { background: #fb923c; box-shadow: 0 0 7px #fb923c; }
.lp-dot.cams { background: #4ade80; box-shadow: 0 0 7px #4ade80; }
.lp-dot.cams { background: #4ade80; box-shadow: 0 0 7px #4ade80; }
.lp-dot.blips { background: var(--magenta); box-shadow: 0 0 7px var(--magenta); }
.lp-dot.blips { background: var(--magenta); box-shadow: 0 0 7px var(--magenta); }
.lp-dot.news { background: var(--magenta); box-shadow: 0 0 7px var(--magenta); border-radius: 1px; transform: rotate(45deg ); }
.lp-dot.news { background: var(--cyan); box-shadow: 0 0 7px var(--cyan ); }
.lp-dot.weather { background: var(--amber); box-shadow: 0 0 7px var(--amber); }
.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.flights { background: #7dd3fc; box-shadow: 0 0 7px #7dd3fc; }
.lp-dot.mil { background: #f472b6; box-shadow: 0 0 7px #f472b6; }
.lp-dot.mil { background: #f472b6; box-shadow: 0 0 7px #f472b6; }
@ -357,15 +358,16 @@
box-shadow: 0 0 7px #64748b;
box-shadow: 0 0 7px #64748b;
}
}
.lp-dot.conflicts { background: #ff2a6d; box-shadow: 0 0 7px #ff2a6d; }
.lp-dot.conflicts { background: #ff2a6d; box-shadow: 0 0 7px #ff2a6d; }
.lp-count { font-family: 'Share Tech Mono', monospace; font-size: 0.7rem; color: var(--cyan); }
.lp-count { font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem; color: var(--cyan); }
.lp-sub { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.lp-sub { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.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 { display: none; align-items: center; gap: 0.4rem; font-size: 0.62rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.lp-layer:has(.lp-name input[type="checkbox"]:checked) .lp-opacity { display: flex; }
.lp-opacity input[type="range"] { flex: 1; accent-color: var(--cyan); height: 4px; cursor: pointer; }
.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-opacity b { color: var(--text); font-family: 'IBM Plex 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 { 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: 'IBM Plex Mono', monospace; color-scheme: dark; }
.lp-select:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.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 { 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-legend h4 { font-size: 0.58rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); margin: 0; font-family: 'IBM Plex Sans ', 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 { 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-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; }
.lp-heat-legend { display: flex; align-items: center; justify-content: space-between; font-size: 0.64rem; color: var(--muted); gap: 0.5rem; }
@ -381,7 +383,7 @@
.gf-item .gf-name:hover { color: var(--cyan); }
.gf-item .gf-name:hover { color: var(--cyan); }
.gf-item .gf-actions { display: flex; align-items: center; gap: 0.28rem; flex: 0 0 auto; }
.gf-item .gf-actions { display: flex; align-items: center; gap: 0.28rem; flex: 0 0 auto; }
.gf-item input.gf-active { accent-color: var(--cyan); cursor: pointer; width: 14px; height: 14px; }
.gf-item input.gf-active { accent-color: var(--cyan); cursor: pointer; width: 14px; height: 14px; }
.gf-del, .gf-go, .gf-ren { background: transparent; border: 1px solid var(--magenta); color: var(--magenta); font-family: 'Share Tech Mono', monospace; font-size: 0.58rem; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.12rem 0.35rem; border-radius: 3px; cursor: pointer; }
.gf-del, .gf-go, .gf-ren { background: transparent; border: 1px solid var(--magenta); color: var(--magenta); font-family: 'IBM Plex Mono', monospace; font-size: 0.58rem; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.12rem 0.35rem; border-radius: 3px; cursor: pointer; }
.gf-go, .gf-ren { border-color: var(--cyan); color: var(--cyan); }
.gf-go, .gf-ren { border-color: var(--cyan); color: var(--cyan); }
.gf-del:hover { background: rgba(255,46,151,0.16); }
.gf-del:hover { background: rgba(255,46,151,0.16); }
.gf-go:hover, .gf-ren:hover { background: rgba(53,224,255,0.14); }
.gf-go:hover, .gf-ren:hover { background: rgba(53,224,255,0.14); }
@ -398,7 +400,7 @@
.gf-hit {
.gf-hit {
cursor: pointer; font-size: 0.58rem; line-height: 1.35; color: var(--text);
cursor: pointer; font-size: 0.58rem; line-height: 1.35; color: var(--text);
padding: 0.22rem 0.28rem; border: 1px solid var(--line); border-radius: 3px;
padding: 0.22rem 0.28rem; border: 1px solid var(--line); border-radius: 3px;
font-family: 'Share Tech Mono', monospace;
font-family: 'IBM Plex Mono', monospace;
}
}
.gf-hit:hover, .gf-hit:focus { border-color: var(--cyan); outline: none; background: rgba(53,224,255,0.08); }
.gf-hit:hover, .gf-hit:focus { border-color: var(--cyan); outline: none; background: rgba(53,224,255,0.08); }
.gf-hit .gf-hit-meta { color: var(--muted); }
.gf-hit .gf-hit-meta { color: var(--muted); }
@ -419,7 +421,7 @@
}
}
.cheat-sheet[hidden] { display: none !important; }
.cheat-sheet[hidden] { display: none !important; }
.cheat-sheet h3 {
.cheat-sheet h3 {
font-family: 'Orbitron ', sans-serif; font-size: 0.72rem; letter-spacing: 0.16em;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.72rem; letter-spacing: 0.16em;
color: var(--cyan); margin: 0 0 0.65rem; text-transform: uppercase;
color: var(--cyan); margin: 0 0 0.65rem; text-transform: uppercase;
}
}
.cheat-sheet dl { display: grid; grid-template-columns: auto 1fr; gap: 0.35rem 0.85rem; margin: 0; }
.cheat-sheet dl { display: grid; grid-template-columns: auto 1fr; gap: 0.35rem 0.85rem; margin: 0; }
@ -445,26 +447,26 @@
.pd-head {
.pd-head {
display: flex; justify-content: space-between; align-items: center;
display: flex; justify-content: space-between; align-items: center;
padding: 0.55rem 0.95rem 0.55rem 0.85rem; border-bottom: 1px solid var(--line);
padding: 0.55rem 0.95rem 0.55rem 0.85rem; border-bottom: 1px solid var(--line);
font-family: 'Orbitron ', sans-serif; font-size: 0.68rem; font-weight: 700;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.68rem; font-weight: 700;
letter-spacing: 0.14em; text-transform: uppercase; color: var(--cyan);
letter-spacing: 0.14em; text-transform: uppercase; color: var(--cyan);
text-shadow: 0 0 10px rgba(53,224,255,0.55);
text-shadow: 0 0 10px rgba(53,224,255,0.55);
background: linear-gradient(180deg, rgba(53,224,255,0.07), transparent);
background: linear-gradient(180deg, rgba(53,224,255,0.07), transparent);
}
}
.pd-close {
.pd-close {
background: transparent; border: 1px solid var(--line); color: var(--muted);
background: transparent; border: 1px solid var(--line); color: var(--muted);
font-family: 'Share Tech Mono', monospace; font-size: 0.78rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.78rem;
width: 28px; height: 28px; border-radius: 4px; cursor: pointer; line-height: 1;
width: 28px; height: 28px; border-radius: 4px; cursor: pointer; line-height: 1;
flex-shrink: 0; margin-right: 2px;
flex-shrink: 0; margin-right: 2px;
}
}
.pd-close:hover { border-color: var(--cyan); color: var(--cyan); }
.pd-close:hover { border-color: var(--cyan); color: var(--cyan); }
.pd-body { padding: 0.55rem 0.75rem 0.7rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.55rem; min-height: 0; flex: 1; }
.pd-body { padding: 0.55rem 0.75rem 0.7rem; overflow-y: auto; display: flex; flex-direction: column; gap: 0.55rem; min-height: 0; flex: 1; }
.pd-status { font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--muted); }
.pd-status { font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--muted); }
.pd-status.err { color: var(--red); }
.pd-status.err { color: var(--red); }
.pd-coords { font-family: 'Share Tech Mono', monospace; font-size: 0.66rem; color: var(--cyan); letter-spacing: 0.04em; }
.pd-coords { font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--cyan); letter-spacing: 0.04em; }
.pd-name { font-family: 'Rajdhani ', sans-serif; font-weight: 700; font-size: 0.92rem; color: var(--text); line-height: 1.25; }
.pd-name { font-family: 'IBM Plex Sans ', sans-serif; font-weight: 700; font-size: 0.92rem; color: var(--text); line-height: 1.25; }
.pd-addr { font-size: 0.72rem; color: var(--muted); line-height: 1.4; }
.pd-addr { font-size: 0.72rem; color: var(--muted); line-height: 1.4; }
.pd-sec {
.pd-sec {
font-family: 'Orbitron ', sans-serif; font-size: 0.58rem; letter-spacing: 0.12em;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.58rem; letter-spacing: 0.12em;
text-transform: uppercase; color: var(--muted); margin-top: 0.15rem;
text-transform: uppercase; color: var(--muted); margin-top: 0.15rem;
}
}
.pd-list { display: flex; flex-direction: column; gap: 0.18rem; }
.pd-list { display: flex; flex-direction: column; gap: 0.18rem; }
@ -472,11 +474,11 @@
display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem;
display: flex; justify-content: space-between; align-items: baseline; gap: 0.5rem;
background: transparent; border: 0; border-left: 2px solid var(--line);
background: transparent; border: 0; border-left: 2px solid var(--line);
color: var(--text); text-align: left; cursor: pointer; padding: 0.22rem 0.35rem;
color: var(--text); text-align: left; cursor: pointer; padding: 0.22rem 0.35rem;
font-family: 'Rajdhani ', sans-serif; font-size: 0.78rem; font-weight: 600;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.78rem; font-weight: 600;
}
}
.pd-item:hover, .pd-item:focus-visible { border-left-color: var(--cyan); color: var(--cyan); background: rgba(53,224,255,0.06); }
.pd-item:hover, .pd-item:focus-visible { border-left-color: var(--cyan); color: var(--cyan); background: rgba(53,224,255,0.06); }
.pd-item .k { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.58rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; flex-shrink: 0; }
.pd-item .k { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.58rem; color: var(--muted); font-family: 'IBM Plex Mono', monospace; flex-shrink: 0; }
.pd-item .d { font-family: 'Share Tech Mono', monospace; font-size: 0.62rem; color: var(--cyan); white-space: nowrap; }
.pd-item .d { font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem; color: var(--cyan); white-space: nowrap; }
.pd-empty { font-size: 0.72rem; color: var(--muted); font-style: italic; }
.pd-empty { font-size: 0.72rem; color: var(--muted); font-style: italic; }
.pd-note { font-size: 0.6rem; color: var(--muted); opacity: 0.85; line-height: 1.35; }
.pd-note { font-size: 0.6rem; color: var(--muted); opacity: 0.85; line-height: 1.35; }
@ -487,7 +489,7 @@
.cam-pop .thumb.placeholder { display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: 0.7rem; height: 80px; }
.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 table { width: 100%; font-size: 0.72rem; border-collapse: collapse; }
.cam-pop td { padding: 0.12rem 0.2rem; vertical-align: top; }
.cam-pop td { padding: 0.12rem 0.2rem; vertical-align: top; }
.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 td.k { color: var(--muted); text-transform: uppercase; font-size: 0.6rem; letter-spacing: 0.05em; white-space: nowrap; width: 34%; font-family: 'IBM Plex Mono', monospace; }
.cam-pop a { color: var(--cyan); word-break: break-all; }
.cam-pop a { color: var(--cyan); word-break: break-all; }
.cam-pop .ps-photo { margin-top: 0.45rem; }
.cam-pop .ps-photo { margin-top: 0.45rem; }
.cam-pop .ps-photo a.ps-link { display: block; }
.cam-pop .ps-photo a.ps-link { display: block; }
@ -500,7 +502,7 @@
.cam-pop .ps-credit a { color: var(--cyan); }
.cam-pop .ps-credit a { color: var(--cyan); }
.cam-pop .ps-loading { font-size: 0.68rem; color: var(--muted); margin-top: 0.35rem; }
.cam-pop .ps-loading { font-size: 0.68rem; color: var(--muted); margin-top: 0.35rem; }
.role-badge {
.role-badge {
display: inline-block; font-family: 'Share Tech Mono', monospace;
display: inline-block; font-family: 'IBM Plex Mono', monospace;
font-size: 0.58rem; font-weight: 700; letter-spacing: 0.12em;
font-size: 0.58rem; font-weight: 700; letter-spacing: 0.12em;
padding: 0.08rem 0.4rem; border-radius: 3px; margin-left: 0.4rem;
padding: 0.08rem 0.4rem; border-radius: 3px; margin-left: 0.4rem;
vertical-align: middle;
vertical-align: middle;
@ -514,8 +516,8 @@
animation: emerg-pulse 1.4s ease-in-out infinite;
animation: emerg-pulse 1.4s ease-in-out infinite;
}
}
.blip-pop { min-width: 200px; max-width: 260px; }
.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-src { font-family: 'IBM Plex 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; }
.blip-pop .blip-time { font-size: 0.7rem; color: var(--muted); font-family: 'IBM Plex Mono', monospace; margin: 0.2rem 0 0.3rem; }
.blip-pop .blip-desc { font-size: 0.72rem; color: var(--text); line-height: 1.35; margin-top: 0.15rem; }
.blip-pop .blip-desc { font-size: 0.72rem; color: var(--text); line-height: 1.35; margin-top: 0.15rem; }
/* ── Sub-views (News / Events / Alerts / ... ) ── */
/* ── Sub-views (News / Events / Alerts / ... ) ── */
@ -524,7 +526,7 @@
max-width: 1280px; margin: 0 auto;
max-width: 1280px; margin: 0 auto;
}
}
.subview > h2 {
.subview > h2 {
font-family: 'Orbitron ', sans-serif; font-size: 0.95rem; font-weight: 700;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.95rem; font-weight: 700;
letter-spacing: 0.16em; text-transform: uppercase; color: var(--text);
letter-spacing: 0.16em; text-transform: uppercase; color: var(--text);
display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.35rem;
display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.35rem;
}
}
@ -536,13 +538,13 @@
box-shadow: 0 0 18px rgba(53,224,255,0.05);
box-shadow: 0 0 18px rgba(53,224,255,0.05);
}
}
.panel h3 {
.panel h3 {
font-size: 0.68rem; font-family: 'Orbitron ', sans-serif; font-weight: 700;
font-size: 0.68rem; font-family: 'IBM Plex Sans ', sans-serif; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.16em; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.16em; color: var(--muted);
margin-bottom: 0.6rem;
margin-bottom: 0.6rem;
}
}
table { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
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, 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; }
th { color: var(--muted); font-weight: 600; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.1em; font-family: 'IBM Plex Sans ', sans-serif; }
tr:hover td { background: rgba(53,224,255,0.04); }
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 { 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-positive { background: rgba(83,240,165,0.12); color: var(--green); border: 1px solid rgba(83,240,165,0.35); }
@ -555,7 +557,7 @@
/* ── Stats strip (Events view) ── */
/* ── 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-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 { 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 { font-family: 'IBM Plex Sans ', 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.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 .value.red { color: var(--red); text-shadow: 0 0 14px rgba(255,93,93,0.4); }
.stat-card::after {
.stat-card::after {
@ -568,14 +570,14 @@
.search-box input {
.search-box input {
flex: 1; background: var(--bg-1); border: 1px solid var(--line);
flex: 1; background: var(--bg-1); border: 1px solid var(--line);
border-radius: 4px; padding: 0.55rem 0.9rem; color: var(--text);
border-radius: 4px; padding: 0.55rem 0.9rem; color: var(--text);
font-size: 0.88rem; font-family: 'Rajdhani ', sans-serif;
font-size: 0.88rem; font-family: 'IBM Plex Sans ', sans-serif;
}
}
.search-box input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 10px var(--cyan-glow); }
.search-box input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 10px var(--cyan-glow); }
/* ── News view ── */
/* ── News view ── */
.news-head { display: flex; justify-content: space-between; align-items: flex-end; gap: 1rem; margin-bottom: 0.9rem; flex-wrap: wrap; }
.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; }
.news-refresh { display: flex; align-items: center; gap: 0.6rem; }
.news-updated { font-size: 0.7rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; }
.news-updated { font-size: 0.7rem; color: var(--muted); font-family: 'IBM Plex Mono', monospace; }
.news-summary {
.news-summary {
position: relative; background: linear-gradient(160deg, rgba(53,224,255,0.10), rgba(10,19,34,0.7) 45%);
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;
border: 1px solid rgba(53,224,255,0.35); border-radius: 8px; padding: 1rem 1.25rem 1.1rem;
@ -586,19 +588,19 @@
content: ''; position: absolute; inset: 0; pointer-events: none;
content: ''; position: absolute; inset: 0; pointer-events: none;
background: radial-gradient(140px 70px at 85% -10%, rgba(53,224,255,0.18), transparent 70%);
background: radial-gradient(140px 70px at 85% -10%, rgba(53,224,255,0.18), transparent 70%);
}
}
.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-kicker { font-family: 'IBM Plex Sans ', 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-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: 'IBM Plex Mono', monospace; }
.ns-time { font-size: 0.68rem; color: var(--muted); font-family: 'Share Tech Mono', monospace; margin: 0.35rem 0 0.6rem; }
.ns-time { font-size: 0.68rem; color: var(--muted); font-family: 'IBM Plex 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 { 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; }
.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: 'IBM Plex Sans ', sans-serif; }
.ns-body ul, .ns-body ol { margin: 0.35rem 0 0.35rem 1.2rem; }
.ns-body ul, .ns-body ol { margin: 0.35rem 0 0.35rem 1.2rem; }
.ns-body li { margin-bottom: 0.2rem; }
.ns-body li { margin-bottom: 0.2rem; }
.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; }
.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: 'IBM Plex Mono', monospace; }
.ns-empty { color: var(--muted); font-size: 0.85rem; font-style: italic; }
.ns-empty { color: var(--muted); font-size: 0.85rem; font-style: italic; }
.ns-loading { color: var(--muted); font-size: 0.85rem; }
.ns-loading { color: var(--muted); font-size: 0.85rem; }
.ns-error { color: var(--red); font-size: 0.85rem; }
.ns-error { color: var(--red); font-size: 0.85rem; }
.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-list-head { display: flex; justify-content: space-between; align-items: center; font-family: 'IBM Plex Sans ', 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-count { font-size: 0.66rem; color: var(--cyan); font-family: 'IBM Plex 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); }
.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); }
.news-list::-webkit-scrollbar { width: 8px; }
.news-list::-webkit-scrollbar { width: 8px; }
.news-list::-webkit-scrollbar-track { background: rgba(14,26,46,0.4); border-radius: 8px; }
.news-list::-webkit-scrollbar-track { background: rgba(14,26,46,0.4); border-radius: 8px; }
@ -607,10 +609,10 @@
.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; }
.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; }
.news-item:last-child { border-bottom: none; }
.news-item:last-child { border-bottom: none; }
.news-item:hover { background: rgba(53,224,255,0.06); box-shadow: inset 2px 0 0 var(--cyan); }
.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-dom { font-size: 0.62rem; font-family: 'IBM Plex 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-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-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-time { font-size: 0.68rem; color: var(--muted); font-family: 'IBM Plex 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); }
.news-geo { color: var(--green); font-size: 0.8rem; flex-shrink: 0; text-shadow: 0 0 8px rgba(83,240,165,0.6); }
.news-loading, .news-error { padding: 1rem 0.9rem; color: var(--muted); font-size: 0.85rem; }
.news-loading, .news-error { padding: 1rem 0.9rem; color: var(--muted); font-size: 0.85rem; }
.news-error { color: var(--red); }
.news-error { color: var(--red); }
@ -622,14 +624,14 @@
.key-card:hover { box-shadow: 0 0 22px rgba(53,224,255,0.12); }
.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-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-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-name { font-family: 'IBM Plex 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 { font-size: 0.68rem; font-family: 'IBM Plex 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.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-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; overflow-wrap: anywhere; }
.key-desc { font-size: 0.8rem; color: var(--muted); margin-bottom: 0.7rem; overflow-wrap: anywhere; }
.key-hint { font-size: 0.7rem; color: var(--muted); opacity: 0.8; margin-top: 0.15rem; font-family: 'Share Tech Mono', monospace; }
.key-hint { font-size: 0.7rem; color: var(--muted); opacity: 0.8; margin-top: 0.15rem; font-family: 'IBM Plex Mono', monospace; }
.key-form { display: flex; gap: 0.5rem; }
.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 { 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: 'IBM Plex Mono', monospace; }
.key-form input:focus { outline: none; border-color: var(--cyan); box-shadow: 0 0 8px var(--cyan-glow); }
.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 { white-space: nowrap; }
.key-form .btn-danger { border-color: rgba(255,93,93,0.5); color: var(--red); }
.key-form .btn-danger { border-color: rgba(255,93,93,0.5); color: var(--red); }
@ -645,17 +647,17 @@
.set-row textarea, .set-row input[type="text"], .set-row select {
.set-row textarea, .set-row input[type="text"], .set-row select {
background: var(--bg-0); color: var(--text); border: 1px solid var(--line);
background: var(--bg-0); color: var(--text); border: 1px solid var(--line);
border-radius: 4px; padding: 0.5rem 0.6rem; font-size: 0.85rem;
border-radius: 4px; padding: 0.5rem 0.6rem; font-size: 0.85rem;
font-family: 'Share Tech Mono', monospace; color-scheme: dark;
font-family: 'IBM Plex 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-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 { 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-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-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; }
.set-saved { color: var(--green); font-size: 0.75rem; font-family: 'IBM Plex 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 { 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:last-child { border-bottom: none; }
.sys-row .k { color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.68rem; }
.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; }
.sys-row .v { font-family: 'IBM Plex Mono', monospace; }
/* ═══════════════ BOTTOM DOCK: tickers ═══════════════ */
/* ═══════════════ BOTTOM DOCK: tickers ═══════════════ */
.dock {
.dock {
@ -672,12 +674,15 @@
.tick-label {
.tick-label {
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
min-width: 74px; padding: 0 0.7rem; flex-shrink: 0;
min-width: 74px; padding: 0 0.7rem; flex-shrink: 0;
font-family: 'Orbitron ', sans-serif; font-size: 0.62rem; font-weight: 700;
font-family: 'IBM Plex Sans ', sans-serif; font-size: 0.62rem; font-weight: 700;
letter-spacing: 0.2em; color: #02131c; background: var(--cyan);
letter-spacing: 0.2em; color: #02131c; background: var(--cyan);
text-shadow: none; position: relative;
text-shadow: none; position: relative;
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 100%, 0 100%);
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 100%, 0 100%);
}
}
.ticker.news .tick-label { background: var(--magenta); color: #1c0311; }
.ticker.news .tick-label { background: var(--cyan); color: #02131c; }
.ticker.market[hidden] { display: none !important; }
.dock.news-only { height: 32px; }
.dock.news-only .ticker { height: 100%; }
.tick-label .st { animation: pulse 1.6s infinite; }
.tick-label .st { animation: pulse 1.6s infinite; }
.tick-wrap { flex: 1; overflow: hidden; position: relative; display: flex; align-items: center;
.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%);
mask-image: linear-gradient(90deg, transparent 0, #000 26px, #000 calc(100% - 26px), transparent 100%);
@ -693,20 +698,20 @@
font-size: 0.82rem; padding: 0 1rem; color: var(--text); text-decoration: none;
font-size: 0.82rem; padding: 0 1rem; color: var(--text); text-decoration: none;
font-weight: 500;
font-weight: 500;
}
}
.tick-item .sym { font-family: 'Share Tech Mono', monospace; color: var(--cyan); font-size: 0.74rem; }
.tick-item .sym { font-family: 'IBM Plex Mono', monospace; color: var(--cyan); font-size: 0.74rem; }
.tick-item .price { font-family: 'Share Tech Mono', monospace; color: var(--text); }
.tick-item .price { font-family: 'IBM Plex Mono', monospace; color: var(--text); }
.tick-item .chg.up { color: var(--green); }
.tick-item .chg.up { color: var(--green); }
.tick-item .chg.down { color: var(--red); }
.tick-item .chg.down { color: var(--red); }
.tick-item .chg.flat { color: var(--muted); }
.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 .dom { font-family: 'IBM Plex 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 .tt { font-family: 'IBM Plex Mono', monospace; font-size: 0.66rem; color: var(--muted); }
.tick-item .sep { color: var(--line-hi); }
.tick-item .sep { color: var(--line-hi); }
button.tick-item { cursor: pointer; background: transparent; border: 0; font: inherit; color: inherit; }
button.tick-item { cursor: pointer; background: transparent; border: 0; font: inherit; color: inherit; }
.tick-item.brief {
.tick-item.brief {
cursor: pointer; background: linear-gradient(90deg, rgba(255,46,151,0.12), transparent 70%);
cursor: pointer; background: linear-gradient(90deg, rgba(255,46,151,0.12), transparent 70%);
border-left: 2px solid var(--magenta); font-weight: 600;
border-left: 2px solid var(--magenta); font-weight: 600;
}
}
.tick-item .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 .b-tag { font-family: 'IBM Plex 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 .b-tag.critical { background: var(--red); }
.tick-item .b-tag.critical { background: var(--red); }
.tick-item .b-tag.high { background: var(--amber); }
.tick-item .b-tag.high { background: var(--amber); }
.tick-item .b-tag.medium { background: var(--cyan); color: #041018; }
.tick-item .b-tag.medium { background: var(--cyan); color: #041018; }
@ -717,7 +722,7 @@
.tick-sep { color: var(--line-hi); }
.tick-sep { color: var(--line-hi); }
.tick-tag {
.tick-tag {
display: flex; align-items: center; flex-shrink: 0; padding: 0 0.8rem;
display: flex; align-items: center; flex-shrink: 0; padding: 0 0.8rem;
font-family: 'Share Tech Mono', monospace; font-size: 0.62rem;
font-family: 'IBM Plex Mono', monospace; font-size: 0.62rem;
letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
border-left: 1px solid var(--line);
border-left: 1px solid var(--line);
}
}
@ -733,16 +738,16 @@
}
}
.boot.gone { opacity: 0; pointer-events: none; }
.boot.gone { opacity: 0; pointer-events: none; }
.boot-inner { text-align: center; }
.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-mark { font-family: 'IBM Plex Sans ', 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 { font-family: 'IBM Plex Sans ', sans-serif; font-size: 1.3rem; font-weight: 900; letter-spacing: 0.3em; color: #eafcff; }
.boot-title span { color: var(--cyan); }
.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-sub { font-family: 'IBM Plex 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 { 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; }
.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%); } }
@keyframes bootSweep { from { transform: translateX(-100%); } to { transform: translateX(350%); } }
@media (max-width: 820px) {
@media (max-width: 820px) {
:root { --topbar-h: 42px; --dock-h: 56 px; }
:root { --topbar-h: 42px; --dock-h: 32 px; }
.tick-label { min-width: 56px; padding: 0 0.5rem; }
.tick-label { min-width: 56px; padding: 0 0.5rem; }
.tick-tag { display: none; }
.tick-tag { display: none; }
.hud-chips { display: none; }
.hud-chips { display: none; }
@ -856,8 +861,7 @@
< div class = "brand" >
< div class = "brand" >
< span class = "brand-mark" > ◢◤< / span >
< span class = "brand-mark" > ◢◤< / span >
< div >
< div >
< div class = "brand-name" > OSINT< span class = "accent" > //< / span > DASHBOARD< / div >
< div class = "brand-name" > OSINT< span class = "accent" > //< / span > < / div >
< div class = "brand-sub" > GLOBAL SITUATIONAL AWARENESS TERMINAL< / div >
< / div >
< / div >
< / div >
< / div >
< div class = "topbar-right" >
< div class = "topbar-right" >
@ -906,7 +910,7 @@
< button class = "btn" id = "map-30d-btn" onclick = "mapGoDays(30)" style = "display:none" > -30d< / button >
< button class = "btn" id = "map-30d-btn" onclick = "mapGoDays(30)" style = "display:none" > -30d< / button >
< button class = "btn" id = "map-reset" onclick = "mapResetView()" > Reset< / button >
< button class = "btn" id = "map-reset" onclick = "mapResetView()" > Reset< / button >
< span class = "mt-label" id = "chokepoint-label" > Strait< / span >
< span class = "mt-label" id = "chokepoint-label" > Strait< / span >
< div class = "chokepoint-btns" id = "chokepoint-btns" role= "group" aria-label = "Chokepoint presets "> < / div >
< div class = "chokepoint-btns" id = "chokepoint-btns" hidden role = "group" aria-hidden = "true "> < / div >
< select id = "chokepoint-select" aria-label = "Chokepoint preset" onchange = "applyChokepoint(this.value)" >
< select id = "chokepoint-select" aria-label = "Chokepoint preset" onchange = "applyChokepoint(this.value)" >
< option value = "" > Strait…< / option >
< option value = "" > Strait…< / option >
< / select >
< / select >
@ -915,8 +919,8 @@
< div id = "map" > < / div >
< div id = "map" > < / div >
<!-- Layer control panel -->
<!-- Layer control panel -->
< div id = "layer-panel" >
< div id = "layer-panel" class = "collapsed" >
< div class = "lp-head" onclick = "toggleLayerPanel()" role = "button" tabindex = "0" aria-expanded = " tru e"
< div class = "lp-head" onclick = "toggleLayerPanel()" role = "button" tabindex = "0" aria-expanded = " fals e"
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleLayerPanel();}">
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleLayerPanel();}">
◈ Layers < span class = "lp-caret" > ▼< / span >
◈ Layers < span class = "lp-caret" > ▼< / span >
< / div >
< / div >
@ -928,12 +932,20 @@
< / div >
< / 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 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 >
< div class = "lp-layer" >
< div class = "lp-row gf-draw-row" >
< button class = "btn" id = "gf-draw" type = "button" onclick = "toggleGeofenceDraw()" title = "Tap vertices, then Finish" > Draw geofence< / button >
< button class = "btn" id = "gf-finish" type = "button" hidden onclick = "onGfClose(event)" > Finish< / button >
< button class = "btn" id = "gf-cancel" type = "button" hidden onclick = "cancelGeofenceDraw()" > Cancel< / button >
< / div >
< div id = "gf-list" class = "gf-list" > < / div >
< div id = "gf-inbox" class = "gf-inbox" aria-label = "Geofence alerts" > < / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-terminator-on" onchange = "toggleTerminator()" > < span class = "lp-dot night" > < / span > Day/Night< / label >
< label class = "lp-name" title = "Sun terminator. No API." > < input type = "checkbox" id = "lp-terminator-on" onchange = "toggleTerminator()" > < span class = "lp-dot night" > < / span > Day/Night< / label >
< span class = "lp-count" id = "lp-terminator-count" > off< / span >
< span class = "lp-count" id = "lp-terminator-count" > off< / span >
< / div >
< / div >
< div class = "lp-note" > Sun terminator overlay. Default off. No API.< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
@ -962,14 +974,14 @@
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< 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 >
< label class = "lp-name" > < input type = "checkbox" id = "lp-cams-on" onchange= "toggleCams()" > < span class = "lp-dot cams" > < / span > Open Cameras< / label >
< span class = "lp-count" id = "lp-cams-count" > 0< / span >
< span class = "lp-count" id = "lp-cams-count" > 0< / span >
< / div >
< / 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 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 >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< 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 >
< label class = "lp-name" title = "Geolocated ingest events" > < input type = "checkbox" id = "lp-blips-on" onchange = "toggleBlips()" > < span class = "lp-dot blips" > < / span > Event Blips< / label >
< span class = "lp-count" id = "lp-blips-count" > 0< / span >
< span class = "lp-count" id = "lp-blips-count" > 0< / span >
< / div >
< / div >
< div class = "lp-sub" >
< div class = "lp-sub" >
@ -983,27 +995,24 @@
< / select >
< / select >
< / label >
< / label >
< / div >
< / div >
< div class = "lp-note" > Geolocated events from the ingest pipeline. Color = source type.< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-news-on" checked onchange = "toggleNewsPins()" > < span class = "lp-dot news" > < / span > Critical News< / label >
< label class = "lp-name" title = "LLM-estimated locations from the 15-min brief" > < input type = "checkbox" id = "lp-news-on" onchange = "toggleNewsPins()" > < span class = "lp-dot news" > < / span > Critical News< / label >
< span class = "lp-count" id = "lp-news-count" > 0< / span >
< span class = "lp-count" id = "lp-news-count" > 0< / span >
< / div >
< / div >
< div class = "lp-note" > LLM-estimated locations from the 15-min brief. Pins only for critical/high.< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< 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 >
< label class = "lp-name" title = "IEM NEXRAD over CONUS, RainViewer elsewhere" > < input type = "checkbox" id = "lp-radar-on" onchange = "toggleRadar()" > < span class = "lp-dot radar" > < / span > Radar< / label >
< span class = "lp-count" id = "lp-radar-count" > IEM< / span >
< span class = "lp-count" id = "lp-radar-count" > IEM< / span >
< / div >
< / 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-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 >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< 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 >
< label class = "lp-name" > < input type = "checkbox" id = "lp-alerts-on" onchange= "toggleWxAlerts()" > < span class = "lp-dot weather" > < / span > NWS / SBW Alerts< / label >
< span class = "lp-count" id = "lp-alerts-count" > 0< / span >
< span class = "lp-count" id = "lp-alerts-count" > 0< / span >
< / div >
< / div >
< / div >
< / div >
@ -1015,15 +1024,14 @@
< / div >
< / div >
< div class = "lp-layer" id = "sentinel-1-sar" >
< div class = "lp-layer" id = "sentinel-1-sar" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-sentinel-on" onchange = "toggleSentinel1()" > < span class = "lp-dot sar" > < / span > Sentinel-1 SAR (Cloud-Penetrating)< / label >
< label class = "lp-name" title = "C-band SAR, last 7 days, viewport" > < input type = "checkbox" id = "lp-sentinel-on" onchange = "toggleSentinel1()" > < span class = "lp-dot sar" > < / span > Sentinel-1 SAR (Cloud-Penetrating)< / label >
< span class = "lp-count" id = "lp-sentinel-count" > 7d< / span >
< span class = "lp-count" id = "lp-sentinel-count" > 7d< / span >
< / div >
< / div >
< div class = "lp-opacity" > Opacity < input type = "range" id = "lp-sentinel-opacity" min = "5" max = "100" value = "80" oninput = "setSentinelOpacity(this.value)" > < b id = "lp-sentinel-val" > 80%< / b > < / div >
< div class = "lp-opacity" > Opacity < input type = "range" id = "lp-sentinel-opacity" min = "5" max = "100" value = "80" oninput = "setSentinelOpacity(this.value)" > < b id = "lp-sentinel-val" > 80%< / b > < / div >
< div class = "lp-note" > Cloud-penetrating C-band. Last 7 days, viewport bbox. Tiles from same-origin TiTiler — not fetched on load.< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< 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 >
< label class = "lp-name" > < input type = "checkbox" id = "lp-perim-on" onchange= "togglePerimeters()" > < span class = "lp-dot wfigs" > < / span > WFIGS Perimeters< / label >
< span class = "lp-count" id = "lp-perim-count" > 0< / span >
< span class = "lp-count" id = "lp-perim-count" > 0< / span >
< / div >
< / div >
< / div >
< / div >
@ -1035,51 +1043,37 @@
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-ac-on" checked onchange = "toggleAircraft()" > < span class = "lp-dot flights" > < / span > Aircraft< / label >
< label class = "lp-name" title = "MIL / firefighting / emergency squawk styling" > < input type = "checkbox" id = "lp-ac-on" onchange = "toggleAircraft()" > < span class = "lp-dot flights" > < / span > Aircraft< / label >
< span class = "lp-count" id = "lp-ac-count" > 0< / span >
< span class = "lp-count" id = "lp-ac-count" > 0< / span >
< / div >
< / div >
< div class = "lp-row" id = "lp-ac-mil-row" hidden >
< div class = "lp-row" id = "lp-ac-mil-row" hidden >
< label class = "lp-name" > < input type = "checkbox" id = "lp-ac-mil-on" onchange = "toggleAircraftMil()" > < span class = "lp-dot mil" > < / span > MIL< / label >
< label class = "lp-name" > < input type = "checkbox" id = "lp-ac-mil-on" onchange = "toggleAircraftMil()" > < span class = "lp-dot mil" > < / span > MIL< / label >
< span class = "lp-count" id = "lp-ac-mil-count" > < / span >
< span class = "lp-count" id = "lp-ac-mil-count" > < / span >
< / div >
< / div >
< div class = "lp-note" > Magenta = military · orange = firefighting · red = emergency (7700/7600/7500) · else altitude. Click a plane for type / squawk / operator.< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-trains-on" checked onchange = "toggleTrains()" > < span class = "lp-dot trains" > < / span > Trains< / label >
< label class = "lp-name" title = "Amtraker · ODC-By" > < input type = "checkbox" id = "lp-trains-on" onchange = "toggleTrains()" > < span class = "lp-dot trains" > < / span > Trains< / label >
< span class = "lp-count" id = "lp-trains-count" > 0< / span >
< span class = "lp-count" id = "lp-trains-count" > 0< / span >
< / div >
< / div >
< div class = "lp-note" > Amtraker · ODC-By< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-vessels-on" onchange = "toggleVessels()" > < span class = "lp-dot vessels" > < / span > Vessels · AIS< / label >
< label class = "lp-name" title = "AISStream US live · VesselAPI Hormuz" > < 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 >
< span class = "lp-count" id = "lp-vessels-count" > 0< / span >
< / div >
< / div >
< div class = "lp-note" > Two feeds: AISStream (US live) and VesselAPI (Hormuz, 5× /day). Paste both keys on the Keys page. Magenta = military · gold = law/gov · teal = underway. Click for type / dest / IMO.< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-row gf-draw-row" >
< button class = "btn" id = "gf-draw" type = "button" onclick = "toggleGeofenceDraw()" > Draw geofence< / button >
< button class = "btn" id = "gf-finish" type = "button" hidden onclick = "onGfClose(event)" > Finish< / button >
< button class = "btn" id = "gf-cancel" type = "button" hidden onclick = "cancelGeofenceDraw()" > Cancel< / button >
< / div >
< div id = "gf-list" class = "gf-list" > < / div >
< div id = "gf-inbox" class = "gf-inbox" aria-label = "Geofence alerts" > < / div >
< div class = "lp-note" > Tap vertices, then Finish (or double-click). Inbox keeps hits after the toast fades.< / div >
< / div >
< / div >
< div class = "lp-layer" >
< div class = "lp-layer" >
< div class = "lp-row" >
< 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 >
< label class = "lp-name" > < input type = "checkbox" id = "lp-storms-on" onchange = "toggleStorms()" > < span class = "lp-dot storms" > < / span > NHC Storms< / label >
< span class = "lp-count" id = "lp-storms-count" > 0< / span >
< span class = "lp-count" id = "lp-storms-count" > 0< / span >
< / div >
< / div >
< / div >
< / div >
< div class = "lp-layer" id = "conflicts-layer" >
< div class = "lp-layer" id = "conflicts-layer" >
< div class = "lp-row" >
< div class = "lp-row" >
< label class = "lp-name" > < input type = "checkbox" id = "lp-conflicts-on" onchange = "toggleConflicts()" > < span class = "lp-dot conflicts" > < / span > Conflicts< / label >
< label class = "lp-name" title = "Curated theatres · news counts" > < input type = "checkbox" id = "lp-conflicts-on" onchange = "toggleConflicts()" > < span class = "lp-dot conflicts" > < / span > Conflicts< / label >
< span class = "lp-count" id = "lp-conflicts-count" > 0< / span >
< span class = "lp-count" id = "lp-conflicts-count" > 0< / span >
< / div >
< / div >
< div class = "lp-note" > Curated theatres · color by severity · news counts, not LiveUAMap.< / div >
< / div >
< / div >
< div class = "lp-legend" >
< div class = "lp-legend" >
@ -1121,7 +1115,7 @@
< dt > < kbd > R< / kbd > < / dt > < dd > Reset map view< / dd >
< dt > < kbd > R< / kbd > < / dt > < dd > Reset map view< / dd >
< dt > < kbd > Esc< / kbd > < / dt > < dd > Close panels< / dd >
< dt > < kbd > Esc< / kbd > < / dt > < dd > Close panels< / dd >
< / dl >
< / dl >
< p class = " lp -note" style = "margin-top:0.7rem" > Camera / HLS thumbs at zoom 12+. F and S are left unbound.< / p >
< p class = " cheat -note" style = "margin-top:0.7rem" > Camera / HLS thumbs at zoom 12+. F and S are left unbound.< / p >
< / div >
< / div >
< div id = "dvr-bar" class = "live" >
< div id = "dvr-bar" class = "live" >
< button class = "btn" id = "dvr-live" type = "button" onclick = "dvrGoLive()" > LIVE< / button >
< button class = "btn" id = "dvr-live" type = "button" onclick = "dvrGoLive()" > LIVE< / button >
@ -1355,8 +1349,8 @@
< div class = "set-row" >
< div class = "set-row" >
< label > Layers on by default< / label >
< label > Layers on by default< / label >
< div style = "display:flex;gap:1.2rem;font-size:0.82rem" >
< 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-cams-on" 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-blips-on" 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 >
< 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 >
< / div >
@ -1387,8 +1381,8 @@
< / main >
< / main >
<!-- ═════════════ BOTTOM DOCK: TICKERS ═════════════ -->
<!-- ═════════════ BOTTOM DOCK: TICKERS ═════════════ -->
< footer class = "dock chrome ">
< footer class = "dock chrome news-only ">
< div class = "ticker market" aria-label = "Market data ticker" >
< div class = "ticker market" hidden aria-hidden = "true" aria-label = "Market data ticker" >
< div class = "tick-label" > < span class = "st" > ●< / span > MKT< / div >
< div class = "tick-label" > < span class = "st" > ●< / span > MKT< / div >
< div class = "tick-wrap" >
< div class = "tick-wrap" >
< div class = "tick-track" id = "mkt-track" > < / div >
< div class = "tick-track" id = "mkt-track" > < / div >
@ -1483,10 +1477,20 @@ const STATS_COUNT_IDS = {
alerts: 'lp-alerts-count',
alerts: 'lp-alerts-count',
};
};
const COUNT_HUD = {
'lp-fires-count': 'hud-fires-n',
'lp-cams-count': 'hud-cams-n',
'lp-blips-count': 'hud-blips-n',
'lp-news-count': 'hud-news-n',
};
function setLayerCount(id, value) {
function setLayerCount(id, value) {
if (statsRailActive & & Object.values(STATS_COUNT_IDS).indexOf(id) !== -1) return;
const el = document.getElementById(id);
const el = document.getElementById(id);
if (el) el.textContent = value;
if (el) el.textContent = value;
const hid = COUNT_HUD[id];
if (hid) {
const h = document.getElementById(hid);
if (h) h.textContent = value;
}
}
}
async function pollLayerStats() {
async function pollLayerStats() {
@ -1498,11 +1502,19 @@ async function pollLayerStats() {
const s = await r.json();
const s = await r.json();
if (!s || typeof s !== 'object') return;
if (!s || typeof s !== 'object') return;
statsRailActive = true;
statsRailActive = true;
const skipIfOn = {
fires: firesOn,
cameras: camsOn,
aircraft: acOn,
vessels: vesselsOn,
trains: trainsOn,
alerts: wxAlertsOn,
};
Object.keys(STATS_COUNT_IDS).forEach((k) => {
Object.keys(STATS_COUNT_IDS).forEach((k) => {
if (skipIfOn[k]) return;
const n = s[k];
const n = s[k];
if (typeof n !== 'number' || !isFinite(n)) return;
if (typeof n !== 'number' || !isFinite(n)) return;
const el = document.getElementById(STATS_COUNT_IDS[k]);
setLayerCount(STATS_COUNT_IDS[k], n.toLocaleString());
if (el) el.textContent = n.toLocaleString();
});
});
} catch (e) {
} catch (e) {
// Never block the map on stats failure — keep last viewport lengths.
// Never block the map on stats failure — keep last viewport lengths.
@ -1758,6 +1770,11 @@ function initMarketTicker() {
buildMarketTrack(true);
buildMarketTrack(true);
setMarketTag('standby');
setMarketTag('standby');
// GET /api/market is not wired — do not 404-poll every 15s.
// GET /api/market is not wired — do not 404-poll every 15s.
const el = document.querySelector('.ticker.market');
if (el) { el.hidden = true; el.setAttribute('aria-hidden', 'true'); }
const dock = document.querySelector('.dock');
if (dock) dock.classList.add('news-only');
document.documentElement.style.setProperty('--dock-h', '32px');
}
}
/* ═══════════════ NEWS FEED + TICKER ═══════════════ */
/* ═══════════════ NEWS FEED + TICKER ═══════════════ */
@ -2135,37 +2152,38 @@ function resetMapSettings() {
try { localStorage.removeItem('osint.mapDefaults'); } catch (e) {}
try { localStorage.removeItem('osint.mapDefaults'); } catch (e) {}
document.getElementById('set-fires-since').value = '24h';
document.getElementById('set-fires-since').value = '24h';
document.getElementById('set-blips-since').value = '24h';
document.getElementById('set-blips-since').value = '24h';
document.getElementById('set-cams-on').checked = tru e;
document.getElementById('set-cams-on').checked = fals e;
document.getElementById('set-blips-on').checked = tru e;
document.getElementById('set-blips-on').checked = fals e;
document.getElementById('set-fires-on').checked = true;
document.getElementById('set-fires-on').checked = true;
applyMapSettings({firesSince:'24h', blipsSince:'24h', camsOn:true, blipsOn:tru e, firesOn:true}, true);
applyMapSettings({firesSince:'24h', blipsSince:'24h', camsOn:false, blipsOn:fals e, firesOn:true}, true);
document.getElementById('map-saved').textContent = 'Defaults restored.';
document.getElementById('map-saved').textContent = 'Defaults restored.';
}
}
function applyMapSettings(prefs, live) {
function applyMapSettings(prefs, live) {
prefs = prefs || {};
const fs = document.getElementById('lp-fires-since');
const fs = document.getElementById('lp-fires-since');
const bs = document.getElementById('lp-blips-since');
const bs = document.getElementById('lp-blips-since');
if (fs) fs.value = prefs.firesSince || '24h' ;
if (fs & & prefs.firesSince ) fs.value = prefs.firesSince;
if (bs) bs.value = prefs.blipsSince || '24h' ;
if (bs & & prefs.blipsSince ) bs.value = prefs.blipsSince;
const cf = document.getElementById('lp-cams-on');
const cf = document.getElementById('lp-cams-on');
const bf = document.getElementById('lp-blips-on');
const bf = document.getElementById('lp-blips-on');
const ff = document.getElementById('lp-fires-on');
const ff = document.getElementById('lp-fires-on');
if (cf) cf.checked = prefs.camsOn !== false ;
if (cf & & Object.prototype.hasOwnProperty.call(prefs, 'camsOn') ) cf.checked = !! prefs.camsOn;
if (bf) bf.checked = prefs.blipsOn !== false ;
if (bf & & Object.prototype.hasOwnProperty.call(prefs, 'blipsOn') ) bf.checked = !! prefs.blipsOn;
if (ff) ff.checked = prefs.firesOn !== false ;
if (ff & & Object.prototype.hasOwnProperty.call(prefs, 'firesOn') ) ff.checked = !! prefs.firesOn;
if (!live) return;
if (!live) return;
if (map) {
if (map) {
if (prefs.camsOn === false & & camsGroup) { map.removeLayer(camsGroup); camsGroup = null; }
if (prefs.camsOn === false & & camsGroup) { map.removeLayer(camsGroup); camsGroup = null; }
if (prefs.blipsOn === false & & blipsGroup) { map.removeLayer(blipsGroup); blipsGroup = 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 & & firesHeat) { map.removeLayer(firesHeat); firesHeat = null; }
if (prefs.firesOn !== false & & firesOn) loadFires();
if (prefs.firesOn & & firesOn) loadFires();
if (prefs.camsOn !== false & & camsOn) loadCams();
if (prefs.camsOn & & camsOn) loadCams();
if (prefs.blipsOn !== false & & blipsOn) loadBlips();
if (prefs.blipsOn & & blipsOn) loadBlips();
}
}
firesSince = prefs.firesSince || firesSince;
if (prefs.firesSince) firesSince = prefs. firesSince;
blipsSince = prefs.blipsSince || blipsSince;
if (prefs.blipsSince) blipsSince = prefs. blipsSince;
camsOn = prefs.camsOn !== false ;
if (Object.prototype.hasOwnProperty.call(prefs, 'camsOn')) camsOn = !!prefs.camsOn ;
blipsOn = prefs.blipsOn !== false ;
if (Object.prototype.hasOwnProperty.call(prefs, 'blipsOn')) blipsOn = !!prefs.blipsOn ;
firesOn = prefs.firesOn !== false ;
if (Object.prototype.hasOwnProperty.call(prefs, 'firesOn')) firesOn = !!prefs.firesOn ;
syncHud();
syncHud();
}
}
function initSettings() {
function initSettings() {
@ -2246,17 +2264,17 @@ let mapDomain = null;
let mapLatest = null;
let mapLatest = null;
let mapAttributionAdded = null;
let mapAttributionAdded = null;
let extraAttribs = new Set();
let extraAttribs = new Set();
let radarLayer = null, radarOn = tru e, radarOpacity = 0.7, radarMeta = null, radarTimer = null;
let radarLayer = null, radarOn = fals e, radarOpacity = 0.7, radarMeta = null, radarTimer = null;
let thermalLayer = null, thermalOn = false;
let thermalLayer = null, thermalOn = false;
let sentinelLayer = null, sentinelOn = false, sentinelOpacity = 0.8;
let sentinelLayer = null, sentinelOn = false, sentinelOpacity = 0.8;
let sentinelItemId = null, sentinelBounds = null;
let sentinelItemId = null, sentinelBounds = null;
let wxAlertsGroup = null, wxAlertsOn = tru e;
let wxAlertsGroup = null, wxAlertsOn = fals e;
let perimGroup = null, perimOn = tru e;
let perimGroup = null, perimOn = fals e;
let incidentsGroup = null, incidentsOn = false;
let incidentsGroup = null, incidentsOn = false;
let acGroup = null, acOn = tru e, acMilOn = false, acMilSupported = false;
let acGroup = null, acOn = fals e, acMilOn = false, acMilSupported = false;
let trainsGroup = null, trainsOn = tru e;
let trainsGroup = null, trainsOn = fals e;
let vesselsGroup = null, vesselsOn = false;
let vesselsGroup = null, vesselsOn = false;
let stormsGroup = null, stormsOn = tru e;
let stormsGroup = null, stormsOn = fals e;
let conflictsGroup = null, conflictsOn = false, conflictsCache = null;
let conflictsGroup = null, conflictsOn = false, conflictsCache = null;
let overlayReq = {ac:0, trains:0, vessels:0, alerts:0, perim:0, incidents:0, storms:0, sar:0, conflicts:0};
let overlayReq = {ac:0, trains:0, vessels:0, alerts:0, perim:0, incidents:0, storms:0, sar:0, conflicts:0};
let moveDebounce = null;
let moveDebounce = null;
@ -2416,7 +2434,9 @@ async function initMap() {
sel.innerHTML = mapLayers.map(l =>
sel.innerHTML = mapLayers.map(l =>
`< option value = "${l.id}" > ${l.title}< / option > `).join('');
`< option value = "${l.id}" > ${l.title}< / option > `).join('');
const preferred =
const preferred =
mapLayers.find(l => l.id === 'BlueMarble_ShadedRelief_Bathymetry')
mapLayers.find(l => l.id === 'VIIRS_SNPP_CorrectedReflectance_TrueColor')
|| mapLayers.find(l => l.id === 'MODIS_Terra_CorrectedReflectance_TrueColor')
|| mapLayers.find(l => l.id === 'BlueMarble_ShadedRelief_Bathymetry')
|| mapLayers.find(l => !l.has_time)
|| mapLayers.find(l => !l.has_time)
|| mapLayers[0];
|| mapLayers[0];
if (preferred) sel.value = preferred.id;
if (preferred) sel.value = preferred.id;
@ -2529,7 +2549,7 @@ async function initMap() {
});
});
// Static Blue Marble has no time-domain fetch — don't block overlays on GIBS.
// Static Blue Marble has no time-domain fetch — don't block overlays on GIBS.
mapLayerChanged();
mapLayerChanged();
// Overlays default ON (checkboxes in the layer panel + saved settings)
// Overlays follow layer-panel checkboxes (basemap + FIRMS default).
applyMapSettings(readMapPrefs(), false);
applyMapSettings(readMapPrefs(), false);
firesOn = document.getElementById('lp-fires-on').checked;
firesOn = document.getElementById('lp-fires-on').checked;
camsOn = document.getElementById('lp-cams-on').checked;
camsOn = document.getElementById('lp-cams-on').checked;
@ -2571,16 +2591,14 @@ function readMapPrefs() {
} catch (e) { return {}; }
} catch (e) { return {}; }
}
}
function syncHud() {
function syncHud() {
const set = (id, n, on) => {
const set = (id, on) => {
const chip = document.getElementById('hud-' + id);
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);
if (chip) chip.classList.toggle('off', on === false);
};
};
set('fires', firesOn ? (hudFiresCount) : '—', firesOn);
set('fires', firesOn);
set('cams', camsOn ? (hudCamsCount) : '—', camsOn);
set('cams', camsOn);
set('blips', blipsOn ? (hudBlipsCount) : '—', blipsOn);
set('blips', blipsOn);
set('news', newsOn ? (hudNewsCount) : '—', newsOn);
set('news', newsOn);
}
}
let hudFiresCount = null, hudCamsCount = null, hudBlipsCount = null, hudNewsCount = null;
let hudFiresCount = null, hudCamsCount = null, hudBlipsCount = null, hudNewsCount = null;
@ -3139,7 +3157,7 @@ async function loadBlips() {
`< / div > `);
`< / div > `);
}));
}));
blipsGroup.addTo(map);
blipsGroup.addTo(map);
document.getElementById('lp-blips-count').textContent = evs.length.toLocaleString( );
setLayerCount('lp-blips-count', evs.length.toLocaleString() );
hudBlipsCount = evs.length;
hudBlipsCount = evs.length;
syncHud();
syncHud();
if (document.getElementById('map-hint').textContent.includes('fire hotspots')) {
if (document.getElementById('map-hint').textContent.includes('fire hotspots')) {
@ -3165,8 +3183,7 @@ async function toggleNewsPins() {
newsReq++;
newsReq++;
if (newsGroup & & map) { map.removeLayer(newsGroup); newsGroup = null; }
if (newsGroup & & map) { map.removeLayer(newsGroup); newsGroup = null; }
hudNewsCount = null;
hudNewsCount = null;
const countEl = document.getElementById('lp-news-count');
setLayerCount('lp-news-count', '0');
if (countEl) countEl.textContent = '0';
syncHud();
syncHud();
}
}
async function loadNewsPins() {
async function loadNewsPins() {
@ -3179,15 +3196,13 @@ async function loadNewsPins() {
if (req !== newsReq) return; // superseded by a newer pan/zoom
if (req !== newsReq) return; // superseded by a newer pan/zoom
const items = Array.isArray(data) ? data : [];
const items = Array.isArray(data) ? data : [];
if (newsGroup) map.removeLayer(newsGroup);
if (newsGroup) map.removeLayer(newsGroup);
const col = '#ff2e97 ';
const col = '#35e0ff ';
const pins = items.filter(it => Number.isFinite(Number(it.lat)) & & Number.isFinite(Number(it.lon)));
const pins = items.filter(it => Number.isFinite(Number(it.lat)) & & Number.isFinite(Number(it.lon)));
newsGroup = L.layerGroup(pins.map(it => {
newsGroup = L.layerGroup(pins.map(it => {
const icon = L.divIcon({
return L.circleMarker([it.lat, it.lon], {
className: '',
radius: 6, weight: 1, color: col, fillColor: col, fillOpacity: 0.7,
html: `< span style = "display:inline-block;width:10px;height:10px;background:${col};box-shadow:0 0 8px ${col},0 0 2px #0f172a;border:1px solid #030609;transform:rotate(45deg);" > < / span > `,
renderer: pointCanvas(),
iconSize: [14, 14], iconAnchor: [7, 7],
})
});
return L.marker([it.lat, it.lon], { icon })
.bindPopup(`< div class = "blip-pop" > ` +
.bindPopup(`< div class = "blip-pop" > ` +
`< div class = "blip-src" > ◈ ${esc(it.importance || '')}${it.category ? ' · ' + esc(it.category) : ''}< / div > ` +
`< div class = "blip-src" > ◈ ${esc(it.importance || '')}${it.category ? ' · ' + esc(it.category) : ''}< / div > ` +
`< b > ${esc(it.headline || '')}< / b > ` +
`< b > ${esc(it.headline || '')}< / b > ` +
@ -3196,7 +3211,7 @@ async function loadNewsPins() {
`< / div > `);
`< / div > `);
}));
}));
newsGroup.addTo(map);
newsGroup.addTo(map);
document.getElementById('lp-news-count').textContent = pins.length.toLocaleString( );
setLayerCount('lp-news-count', pins.length.toLocaleString() );
hudNewsCount = pins.length;
hudNewsCount = pins.length;
syncHud();
syncHud();
} catch(e) {
} catch(e) {