● MKT
@@ -1483,10 +1477,20 @@ const STATS_COUNT_IDS = {
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) {
- if (statsRailActive && Object.values(STATS_COUNT_IDS).indexOf(id) !== -1) return;
const el = document.getElementById(id);
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() {
@@ -1498,11 +1502,19 @@ async function pollLayerStats() {
const s = await r.json();
if (!s || typeof s !== 'object') return;
statsRailActive = true;
+ const skipIfOn = {
+ fires: firesOn,
+ cameras: camsOn,
+ aircraft: acOn,
+ vessels: vesselsOn,
+ trains: trainsOn,
+ alerts: wxAlertsOn,
+ };
Object.keys(STATS_COUNT_IDS).forEach((k) => {
+ if (skipIfOn[k]) return;
const n = s[k];
if (typeof n !== 'number' || !isFinite(n)) return;
- const el = document.getElementById(STATS_COUNT_IDS[k]);
- if (el) el.textContent = n.toLocaleString();
+ setLayerCount(STATS_COUNT_IDS[k], n.toLocaleString());
});
} catch (e) {
// Never block the map on stats failure — keep last viewport lengths.
@@ -1758,6 +1770,11 @@ function initMarketTicker() {
buildMarketTrack(true);
setMarketTag('standby');
// 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 ═══════════════ */
@@ -2135,37 +2152,38 @@ function resetMapSettings() {
try { localStorage.removeItem('osint.mapDefaults'); } catch (e) {}
document.getElementById('set-fires-since').value = '24h';
document.getElementById('set-blips-since').value = '24h';
- document.getElementById('set-cams-on').checked = true;
- document.getElementById('set-blips-on').checked = true;
+ document.getElementById('set-cams-on').checked = false;
+ document.getElementById('set-blips-on').checked = false;
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.';
}
function applyMapSettings(prefs, live) {
+ prefs = prefs || {};
const fs = document.getElementById('lp-fires-since');
const bs = document.getElementById('lp-blips-since');
- if (fs) fs.value = prefs.firesSince || '24h';
- if (bs) bs.value = prefs.blipsSince || '24h';
+ if (fs && prefs.firesSince) fs.value = prefs.firesSince;
+ if (bs && prefs.blipsSince) bs.value = prefs.blipsSince;
const cf = document.getElementById('lp-cams-on');
const bf = document.getElementById('lp-blips-on');
const ff = document.getElementById('lp-fires-on');
- if (cf) cf.checked = prefs.camsOn !== false;
- if (bf) bf.checked = prefs.blipsOn !== false;
- if (ff) ff.checked = prefs.firesOn !== false;
+ if (cf && Object.prototype.hasOwnProperty.call(prefs, 'camsOn')) cf.checked = !!prefs.camsOn;
+ if (bf && Object.prototype.hasOwnProperty.call(prefs, 'blipsOn')) bf.checked = !!prefs.blipsOn;
+ if (ff && Object.prototype.hasOwnProperty.call(prefs, 'firesOn')) ff.checked = !!prefs.firesOn;
if (!live) return;
if (map) {
if (prefs.camsOn === false && camsGroup) { map.removeLayer(camsGroup); camsGroup = null; }
if (prefs.blipsOn === false && blipsGroup) { map.removeLayer(blipsGroup); blipsGroup = null; }
if (prefs.firesOn === false && firesHeat) { map.removeLayer(firesHeat); firesHeat = null; }
- if (prefs.firesOn !== false && firesOn) loadFires();
- if (prefs.camsOn !== false && camsOn) loadCams();
- if (prefs.blipsOn !== false && blipsOn) loadBlips();
+ if (prefs.firesOn && firesOn) loadFires();
+ if (prefs.camsOn && camsOn) loadCams();
+ if (prefs.blipsOn && blipsOn) loadBlips();
}
- firesSince = prefs.firesSince || firesSince;
- blipsSince = prefs.blipsSince || blipsSince;
- camsOn = prefs.camsOn !== false;
- blipsOn = prefs.blipsOn !== false;
- firesOn = prefs.firesOn !== false;
+ if (prefs.firesSince) firesSince = prefs.firesSince;
+ if (prefs.blipsSince) blipsSince = prefs.blipsSince;
+ if (Object.prototype.hasOwnProperty.call(prefs, 'camsOn')) camsOn = !!prefs.camsOn;
+ if (Object.prototype.hasOwnProperty.call(prefs, 'blipsOn')) blipsOn = !!prefs.blipsOn;
+ if (Object.prototype.hasOwnProperty.call(prefs, 'firesOn')) firesOn = !!prefs.firesOn;
syncHud();
}
function initSettings() {
@@ -2246,17 +2264,17 @@ let mapDomain = null;
let mapLatest = null;
let mapAttributionAdded = null;
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 sentinelLayer = null, sentinelOn = false, sentinelOpacity = 0.8;
let sentinelItemId = null, sentinelBounds = null;
-let wxAlertsGroup = null, wxAlertsOn = true;
-let perimGroup = null, perimOn = true;
+let wxAlertsGroup = null, wxAlertsOn = false;
+let perimGroup = null, perimOn = false;
let incidentsGroup = null, incidentsOn = false;
-let acGroup = null, acOn = true, acMilOn = false, acMilSupported = false;
-let trainsGroup = null, trainsOn = true;
+let acGroup = null, acOn = false, acMilOn = false, acMilSupported = false;
+let trainsGroup = null, trainsOn = false;
let vesselsGroup = null, vesselsOn = false;
-let stormsGroup = null, stormsOn = true;
+let stormsGroup = null, stormsOn = false;
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 moveDebounce = null;
@@ -2416,7 +2434,9 @@ async function initMap() {
sel.innerHTML = mapLayers.map(l =>
`
${l.title} `).join('');
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[0];
if (preferred) sel.value = preferred.id;
@@ -2529,7 +2549,7 @@ async function initMap() {
});
// Static Blue Marble has no time-domain fetch — don't block overlays on GIBS.
mapLayerChanged();
- // Overlays default ON (checkboxes in the layer panel + saved settings)
+ // Overlays follow layer-panel checkboxes (basemap + FIRMS default).
applyMapSettings(readMapPrefs(), false);
firesOn = document.getElementById('lp-fires-on').checked;
camsOn = document.getElementById('lp-cams-on').checked;
@@ -2571,16 +2591,14 @@ function readMapPrefs() {
} catch (e) { return {}; }
}
function syncHud() {
- const set = (id, n, on) => {
+ const set = (id, on) => {
const chip = document.getElementById('hud-' + id);
- const num = document.getElementById('hud-' + id + '-n');
- if (num) num.textContent = n == null ? '—' : Number(n).toLocaleString();
if (chip) chip.classList.toggle('off', on === false);
};
- set('fires', firesOn ? (hudFiresCount) : '—', firesOn);
- set('cams', camsOn ? (hudCamsCount) : '—', camsOn);
- set('blips', blipsOn ? (hudBlipsCount) : '—', blipsOn);
- set('news', newsOn ? (hudNewsCount) : '—', newsOn);
+ set('fires', firesOn);
+ set('cams', camsOn);
+ set('blips', blipsOn);
+ set('news', newsOn);
}
let hudFiresCount = null, hudCamsCount = null, hudBlipsCount = null, hudNewsCount = null;
@@ -3139,7 +3157,7 @@ async function loadBlips() {
`
`);
}));
blipsGroup.addTo(map);
- document.getElementById('lp-blips-count').textContent = evs.length.toLocaleString();
+ setLayerCount('lp-blips-count', evs.length.toLocaleString());
hudBlipsCount = evs.length;
syncHud();
if (document.getElementById('map-hint').textContent.includes('fire hotspots')) {
@@ -3165,8 +3183,7 @@ async function toggleNewsPins() {
newsReq++;
if (newsGroup && map) { map.removeLayer(newsGroup); newsGroup = null; }
hudNewsCount = null;
- const countEl = document.getElementById('lp-news-count');
- if (countEl) countEl.textContent = '0';
+ setLayerCount('lp-news-count', '0');
syncHud();
}
async function loadNewsPins() {
@@ -3179,15 +3196,13 @@ async function loadNewsPins() {
if (req !== newsReq) return; // superseded by a newer pan/zoom
const items = Array.isArray(data) ? data : [];
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)));
newsGroup = L.layerGroup(pins.map(it => {
- const icon = L.divIcon({
- className: '',
- html: `
`,
- iconSize: [14, 14], iconAnchor: [7, 7],
- });
- return L.marker([it.lat, it.lon], { icon })
+ return L.circleMarker([it.lat, it.lon], {
+ radius: 6, weight: 1, color: col, fillColor: col, fillOpacity: 0.7,
+ renderer: pointCanvas(),
+ })
.bindPopup(`
` +
`
◈ ${esc(it.importance || '')}${it.category ? ' · ' + esc(it.category) : ''}
` +
`
${esc(it.headline || '')} ` +
@@ -3196,7 +3211,7 @@ async function loadNewsPins() {
`
`);
}));
newsGroup.addTo(map);
- document.getElementById('lp-news-count').textContent = pins.length.toLocaleString();
+ setLayerCount('lp-news-count', pins.length.toLocaleString());
hudNewsCount = pins.length;
syncHud();
} catch(e) {
diff --git a/tests/test_hud_chrome.py b/tests/test_hud_chrome.py
new file mode 100644
index 0000000..9be2d5d
--- /dev/null
+++ b/tests/test_hud_chrome.py
@@ -0,0 +1,113 @@
+"""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("
", 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_ticker_fills_news_only_dock():
+ css = HTML.split("", 1)[0]
+ compact = css.replace(" ", "").replace("\n", "")
+ assert ".dock.news-only{height:32px;}" in compact
+ assert ".dock.news-only.ticker{height:100%;}" in compact
+ assert ".ticker{display:flex;align-items:stretch;height:50%;" in compact
+
+
+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("", 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">//' in HTML