feat(map): quiet HUD chrome — VIIRS default, collapsed rail
Satellite-first chrome: daily VIIRS preferred, layer rail collapsed on load, geofence after basemap, FIRMS-only default overlays, hide MKT standby ticker, Strait select instead of buttons, IBM Plex instead of Orbitron, news as 6px circleMarkers.
This commit is contained in:
parent
cb20473119
commit
beb457c382
2 changed files with 289 additions and 170 deletions
|
|
@ -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,14 @@
|
||||||
.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; }
|
||||||
.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 +697,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 +721,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 +737,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: 56px; }
|
:root { --topbar-h: 42px; --dock-h: 32px; }
|
||||||
.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 +860,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 +909,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 +918,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="true"
|
<div class="lp-head" onclick="toggleLayerPanel()" role="button" tabindex="0" aria-expanded="false"
|
||||||
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 +931,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 +973,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 +994,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 +1023,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 +1042,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 +1114,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 +1348,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 +1380,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 +1476,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 +1501,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 +1769,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 +2151,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 = true;
|
document.getElementById('set-cams-on').checked = false;
|
||||||
document.getElementById('set-blips-on').checked = true;
|
document.getElementById('set-blips-on').checked = false;
|
||||||
document.getElementById('set-fires-on').checked = true;
|
document.getElementById('set-fires-on').checked = true;
|
||||||
applyMapSettings({firesSince:'24h', blipsSince:'24h', camsOn:true, blipsOn:true, firesOn:true}, true);
|
applyMapSettings({firesSince:'24h', blipsSince:'24h', camsOn:false, blipsOn:false, 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 +2263,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 = true, radarOpacity = 0.7, radarMeta = null, radarTimer = null;
|
let radarLayer = null, radarOn = false, 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 = true;
|
let wxAlertsGroup = null, wxAlertsOn = false;
|
||||||
let perimGroup = null, perimOn = true;
|
let perimGroup = null, perimOn = false;
|
||||||
let incidentsGroup = null, incidentsOn = false;
|
let incidentsGroup = null, incidentsOn = false;
|
||||||
let acGroup = null, acOn = true, acMilOn = false, acMilSupported = false;
|
let acGroup = null, acOn = false, acMilOn = false, acMilSupported = false;
|
||||||
let trainsGroup = null, trainsOn = true;
|
let trainsGroup = null, trainsOn = false;
|
||||||
let vesselsGroup = null, vesselsOn = false;
|
let vesselsGroup = null, vesselsOn = false;
|
||||||
let stormsGroup = null, stormsOn = true;
|
let stormsGroup = null, stormsOn = false;
|
||||||
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 +2433,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 +2548,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 +2590,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 +3156,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 +3182,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 +3195,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 +3210,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) {
|
||||||
|
|
|
||||||
105
tests/test_hud_chrome.py
Normal file
105
tests/test_hud_chrome.py
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
"""Quiet HUD chrome: VIIRS default, collapsed rail, no Orbitron/MKT dashes."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
HTML = (ROOT / "app/static/index.html").read_text()
|
||||||
|
|
||||||
|
|
||||||
|
def _attr(html: str, elem_id: str) -> str:
|
||||||
|
chunk = html.split(f'id="{elem_id}"', 1)[1].split(">", 1)[0]
|
||||||
|
return chunk
|
||||||
|
|
||||||
|
|
||||||
|
def test_initmap_prefers_viirs_true_color():
|
||||||
|
init = HTML.split("async function initMap", 1)[1].split("function readMapPrefs", 1)[0]
|
||||||
|
assert "VIIRS_SNPP_CorrectedReflectance_TrueColor" in init
|
||||||
|
assert init.index("VIIRS_SNPP_CorrectedReflectance_TrueColor") < init.index(
|
||||||
|
"MODIS_Terra_CorrectedReflectance_TrueColor"
|
||||||
|
)
|
||||||
|
assert init.index("MODIS_Terra_CorrectedReflectance_TrueColor") < init.index(
|
||||||
|
"BlueMarble_ShadedRelief_Bathymetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_orbitron_gone():
|
||||||
|
assert "Orbitron" not in HTML
|
||||||
|
assert "IBM Plex Sans" in HTML
|
||||||
|
assert "IBM Plex Mono" in HTML
|
||||||
|
|
||||||
|
|
||||||
|
def test_lp_note_stripped_from_layer_list():
|
||||||
|
assert 'class="lp-note"' not in HTML
|
||||||
|
body = HTML.split('class="lp-body"', 1)[1].split("lp-legend", 1)[0]
|
||||||
|
assert "lp-note" not in body
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_overlays_basemap_and_firms_only():
|
||||||
|
fires = _attr(HTML, "lp-fires-on")
|
||||||
|
assert "checked" in fires
|
||||||
|
for eid in (
|
||||||
|
"lp-cams-on",
|
||||||
|
"lp-blips-on",
|
||||||
|
"lp-news-on",
|
||||||
|
"lp-radar-on",
|
||||||
|
"lp-alerts-on",
|
||||||
|
"lp-perim-on",
|
||||||
|
"lp-ac-on",
|
||||||
|
"lp-trains-on",
|
||||||
|
"lp-storms-on",
|
||||||
|
):
|
||||||
|
assert "checked" not in _attr(HTML, eid), eid
|
||||||
|
|
||||||
|
|
||||||
|
def test_geofence_markup_before_cameras():
|
||||||
|
assert 'id="gf-draw"' in HTML
|
||||||
|
assert HTML.index('id="gf-draw"') < HTML.index('id="lp-cams-on"')
|
||||||
|
assert HTML.index('id="lp-base-on"') < HTML.index('id="gf-draw"')
|
||||||
|
|
||||||
|
|
||||||
|
def test_parent_geofence_hud_survives():
|
||||||
|
assert "watch_geofences" in HTML
|
||||||
|
assert "function deleteGeofence" in HTML
|
||||||
|
assert 'id="gf-finish"' in HTML
|
||||||
|
assert 'id="gf-cancel"' in HTML
|
||||||
|
assert 'id="gf-inbox"' in HTML
|
||||||
|
|
||||||
|
|
||||||
|
def test_layer_rail_collapsed_on_load():
|
||||||
|
head = HTML.split('class="lp-head"', 1)[1].split("</div>", 1)[0]
|
||||||
|
assert 'aria-expanded="false"' in head
|
||||||
|
assert 'id="layer-panel" class="collapsed"' in HTML
|
||||||
|
|
||||||
|
|
||||||
|
def test_market_ticker_hidden_no_poll():
|
||||||
|
mkt = HTML.split('class="ticker market"', 1)[1].split(">", 1)[0]
|
||||||
|
assert "hidden" in mkt
|
||||||
|
assert "setInterval(probeMarket" not in HTML
|
||||||
|
assert "setInterval(loadMarket" not in HTML
|
||||||
|
init = HTML.split("function initMarketTicker", 1)[1].split("function ", 1)[0]
|
||||||
|
assert "/api/market" in init or "404-poll" in init
|
||||||
|
assert "setInterval" not in init
|
||||||
|
|
||||||
|
|
||||||
|
def test_news_pins_are_circle_markers():
|
||||||
|
js = HTML.split("async function loadNewsPins", 1)[1].split("function refreshLiveOverlays", 1)[0]
|
||||||
|
assert "L.circleMarker" in js
|
||||||
|
assert "fillOpacity: 0.7" in js or "fillOpacity:0.7" in js
|
||||||
|
assert "rotate(45deg)" not in js
|
||||||
|
assert "L.divIcon" not in js
|
||||||
|
|
||||||
|
|
||||||
|
def test_chokepoint_buttons_not_in_toolbar_flow():
|
||||||
|
assert 'id="chokepoint-select"' in HTML
|
||||||
|
css = HTML.split("</style>", 1)[0]
|
||||||
|
assert ".chokepoint-btns { display: none; }" in css or ".chokepoint-btns{display:none" in css.replace(
|
||||||
|
" ", ""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_brand_is_osint_slash():
|
||||||
|
assert "GLOBAL SITUATIONAL AWARENESS TERMINAL" not in HTML
|
||||||
|
assert "OSINT" in HTML
|
||||||
|
assert 'class="accent">//</span>' in HTML
|
||||||
Loading…
Add table
Reference in a new issue