diff --git a/src/world_intel_mcp/dashboard/index.html b/src/world_intel_mcp/dashboard/index.html
index 0f1c0a1..163cb33 100644
--- a/src/world_intel_mcp/dashboard/index.html
+++ b/src/world_intel_mcp/dashboard/index.html
@@ -94,13 +94,14 @@ a { color: var(--accent); text-decoration: none; }
#topBar {
position: fixed; top: 10px; left: 10px; right: 10px; z-index: 50;
display: flex; align-items: center; justify-content: space-between;
- padding: 8px 16px; gap: 12px;
+ padding: 8px 16px; gap: 8px;
+ flex-wrap: wrap;
}
.hud-logo { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.logo-mark { font-size: 1.1rem; line-height: 1; }
.logo-text { font-weight: 700; font-size: 0.9rem; letter-spacing: 2px; color: var(--bright); }
.logo-dim { font-weight: 400; color: var(--dim); }
-.hud-stats { display: flex; gap: 5px; flex-wrap: wrap; justify-content: center; flex: 1; min-width: 0; }
+.hud-stats { display: flex; gap: 5px; flex-wrap: wrap; justify-content: center; flex: 1; min-width: 0; max-height: 52px; overflow: hidden; }
.stat-pill {
padding: 3px 10px; border-radius: 6px;
background: rgba(0,0,0,0.35); border: 1px solid var(--border);
@@ -126,8 +127,9 @@ a { color: var(--accent); text-decoration: none; }
/* ═══════════════ LAYER PANEL ═══════════════ */
#layers {
- position: fixed; top: 66px; left: 10px; z-index: 40;
+ position: fixed; top: var(--layers-top, 66px); left: 10px; z-index: 40;
width: 200px; padding: 12px 14px;
+ transition: top 0.2s;
}
.panel-title {
font-size: 0.62rem; font-weight: 600; letter-spacing: 2.5px;
@@ -433,6 +435,20 @@ a { color: var(--accent); text-decoration: none; }
cursor: pointer; transition: transform 0.15s;
}
.mk-cam:hover { transform: scale(1.8); }
+.mk-climate {
+ border-radius: 50%;
+ cursor: pointer; transition: transform 0.15s;
+}
+.mk-climate:hover { transform: scale(1.3); }
+.mk-climate.hot { background: radial-gradient(circle, rgba(255,68,68,0.8) 20%, rgba(255,68,68,0) 70%); border: 2px solid rgba(255,68,68,0.6); }
+.mk-climate.warm { background: radial-gradient(circle, rgba(255,159,28,0.7) 20%, rgba(255,159,28,0) 70%); border: 2px solid rgba(255,159,28,0.5); }
+.mk-climate.cool { background: radial-gradient(circle, rgba(100,181,246,0.7) 20%, rgba(100,181,246,0) 70%); border: 2px solid rgba(100,181,246,0.5); }
+.mk-climate.cold { background: radial-gradient(circle, rgba(77,168,255,0.8) 20%, rgba(77,168,255,0) 70%); border: 2px solid rgba(77,168,255,0.6); }
+.mk-climate.sig { animation: glow-climate 2s ease-in-out infinite; }
+@keyframes glow-climate {
+ 0%,100% { box-shadow: 0 0 4px rgba(255,107,107,0.3); }
+ 50% { box-shadow: 0 0 18px rgba(255,107,107,0.7); }
+}
@keyframes glow-gold {
0%,100% { box-shadow: 0 0 4px rgba(240,184,64,0.3); }
50% { box-shadow: 0 0 16px rgba(240,184,64,0.7); }
@@ -462,7 +478,12 @@ a { color: var(--accent); text-decoration: none; }
#drawer.closed { transform: translateY(100%); }
.drawer-tab { left: 50%; top: -30px; transform: translateX(-50%); border-radius: var(--radius) var(--radius) 0 0; border: 1px solid var(--border); border-bottom: none; width: 56px; height: 26px; }
#detail { width: 100%; }
- #layers { width: 160px; }
+ #layers { width: 140px; max-height: 36px; overflow: hidden; transition: max-height 0.3s var(--ease); }
+ #layers.layers-open { max-height: 600px; }
+ .panel-title { cursor: pointer; position: relative; }
+ .panel-title::after { content: '\25BE'; position: absolute; right: 0; opacity: 0.4; font-size: 0.5rem; }
+ #layers.layers-open .panel-title::after { content: '\25B4'; }
+ .layer-row { padding: 6px 0; }
.hud-stats { display: none; }
#alertBanner { left: 10px; right: 10px; }
}
@@ -614,6 +635,12 @@ a { color: var(--accent); text-decoration: none; }
0
+
@@ -660,7 +687,7 @@ function ago(ts) { if (!ts) return '\u2014'; var s = Math.floor((Date.now() - ne
var latestData = null;
var map = null;
var mapLayers = {};
-var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true, nuclear: true, infra: true, exposure: true, airtraffic: true, traffic: true, navwarnings: true, webcams: true };
+var layerState = { quakes: true, military: true, conflict: true, fires: true, convergence: true, nuclear: true, infra: true, exposure: true, airtraffic: true, traffic: true, navwarnings: true, webcams: true, climate: true };
// Restore saved layer toggles from localStorage
try { var _saved = JSON.parse(localStorage.getItem('phoenix-layers')); if (_saved) { for (var _k in _saved) { if (layerState.hasOwnProperty(_k)) layerState[_k] = _saved[_k]; } } } catch(e) {}
var drawerOpen = false;
@@ -694,6 +721,7 @@ function initMap() {
mapLayers.traffic = L.layerGroup().addTo(map);
mapLayers.navwarnings = L.markerClusterGroup(clusterOpts(null, 40)).addTo(map);
mapLayers.webcams = L.layerGroup().addTo(map);
+ mapLayers.climate = L.layerGroup().addTo(map);
}
// ════════════ LAYER TOGGLES ════════════
@@ -899,6 +927,19 @@ function showDetail(type, d) {
['Authority', d.authority || '\u2014'],
['Text', d.text || '\u2014']
];
+ } else if (type === 'climate') {
+ var ta = d.temp_anomaly_c || 0;
+ title = (d.name || d.zone || 'Climate Zone');
+ subtitle = (ta > 0 ? '+' : '') + ta.toFixed(1) + '\u00B0C anomaly';
+ fields = [
+ ['Zone', d.name || d.zone || '\u2014'],
+ ['Temp Anomaly', (ta > 0 ? '+' : '') + ta.toFixed(1) + '\u00B0C', Math.abs(ta) > 2 ? 'crit' : Math.abs(ta) > 1 ? 'high' : ''],
+ ['Current Avg', d.current_avg_temp_c != null ? d.current_avg_temp_c.toFixed(1) + '\u00B0C' : '\u2014'],
+ ['Baseline Avg', d.baseline_avg_temp_c != null ? d.baseline_avg_temp_c.toFixed(1) + '\u00B0C' : '\u2014'],
+ ['Precip Anomaly', d.precip_anomaly_pct != null ? (d.precip_anomaly_pct > 0 ? '+' : '') + d.precip_anomaly_pct.toFixed(0) + '%' : '\u2014', Math.abs(d.precip_anomaly_pct || 0) > 50 ? 'high' : ''],
+ ['Significant', d.is_significant ? 'YES' : 'No', d.is_significant ? 'crit' : 'ok'],
+ ['Coordinates', d.lat != null ? d.lat.toFixed(3) + ', ' + d.lon.toFixed(3) : '\u2014']
+ ];
}
var hdr = '' + esc(LABELS[type] || type) + '
' +
@@ -936,6 +977,7 @@ $('#detailClose').addEventListener('click', closeDetail);
// ════════════ DRAWER DETAIL TYPES ════════════
// Extend showDetail to handle drawer item types
+COLORS.climate = '#ff6b6b'; LABELS.climate = 'CLIMATE ZONE';
COLORS.news = 'var(--accent)'; LABELS.news = 'NEWS ARTICLE';
COLORS.cyber = 'var(--red)'; LABELS.cyber = 'CYBER THREAT';
COLORS.health = 'var(--pink)'; LABELS.health = 'HEALTH ALERT';
@@ -1544,6 +1586,30 @@ function updateMapWebcams(data) {
});
}
+function updateMapClimate(data) {
+ mapLayers.climate.clearLayers();
+ if (!data || data.error) { $('#cntClimate').textContent = '0'; return; }
+ var zones = data.anomalies || data.zones || data.data || [];
+ if (!Array.isArray(zones)) zones = Object.values(zones);
+ $('#cntClimate').textContent = zones.length;
+ zones.forEach(function(z) {
+ if (z.lat == null || z.lon == null) return;
+ var ta = z.temp_anomaly_c != null ? z.temp_anomaly_c : (z.temperature_anomaly || z.temp_anomaly || 0);
+ var abs = Math.abs(ta);
+ var cls = ta > 2 ? 'hot' : ta > 0 ? 'warm' : ta < -2 ? 'cold' : 'cool';
+ if (z.is_significant) cls += ' sig';
+ var size = Math.max(16, Math.min(32, abs * 6 + 14));
+ var mk = L.marker([z.lat, z.lon], {
+ icon: L.divIcon({ className: 'mk-climate ' + cls, iconSize: [size, size], iconAnchor: [size/2, size/2] })
+ });
+ var tip = esc(z.name || z.zone || '?') + ' ' + (ta > 0 ? '+' : '') + ta.toFixed(1) + '\u00B0C';
+ if (z.is_significant) tip += ' \u26A0';
+ mk.bindTooltip(tip, { className: 'mk-tip', direction: 'top', offset: [0, -size/2] });
+ mk.on('click', function() { showDetail('climate', z); });
+ mk.addTo(mapLayers.climate);
+ });
+}
+
function updateAlertBanner(data) {
var el = $('#alertBanner');
if (!data || data.error || !data.alerts || !data.alerts.length) {
@@ -2371,7 +2437,8 @@ function updateAll(data) {
['airtraffic', function() { updateMapAirTraffic(data.domestic_flights); }],
['traffic', function() { updateMapTraffic(data.traffic_flow); }],
['navwarnings', function() { updateMapNavWarnings(data.nav_warnings); }],
- ['webcams', function() { updateMapWebcams(data.webcams); }]
+ ['webcams', function() { updateMapWebcams(data.webcams); }],
+ ['climate', function() { updateMapClimate(data.climate_anomalies); }]
];
mapUpdates.forEach(function(pair) {
try { pair[1](); } catch(e) { console.warn('updateAll: ' + pair[0] + ' failed:', e); }
@@ -2395,6 +2462,9 @@ function updateAll(data) {
$('#feedCount').textContent = okCount + '/' + feedKeys.length + ' feeds';
if (data.timestamp) { $('#lastUpdate').textContent = new Date(data.timestamp).toLocaleTimeString(); }
+
+ // Reposition layer panel after HUD stats may have changed topbar height
+ if (typeof _syncLayersTop === 'function') setTimeout(_syncLayersTop, 50);
}
// ════════════ SSE CONNECTION ════════════
@@ -2456,7 +2526,7 @@ $('#drawerBody').addEventListener('click', function(e) {
});
// HUD pill click-to-zoom
-var HUD_LAYER_MAP = { 'Quakes': 'quakes', 'Aircraft': 'military', 'Conflict': 'conflict', 'Fires': 'fires', 'Exposed': 'exposure', 'Airborne': 'airtraffic', 'Traffic': 'traffic', 'Cams': 'webcams', 'Nuke Flag': 'nuclear' };
+var HUD_LAYER_MAP = { 'Quakes': 'quakes', 'Aircraft': 'military', 'Conflict': 'conflict', 'Fires': 'fires', 'Exposed': 'exposure', 'Airborne': 'airtraffic', 'Traffic': 'traffic', 'Cams': 'webcams', 'Nuke Flag': 'nuclear', 'Climate': 'climate' };
$('#hudStats').addEventListener('click', function(e) {
var pill = e.target.closest('.stat-pill');
if (!pill) return;
@@ -2537,6 +2607,10 @@ function performSearch(q) {
_scan((latestData.strategic_ports || {}).ports, 'lat', 'lon');
_scan((latestData.nuclear_facilities || {}).facilities, 'lat', 'lon');
_scan((latestData.nuclear_monitor || {}).sites, 'lat', 'lon');
+ var climSrc = latestData.climate_anomalies || {};
+ var climZones = climSrc.anomalies || climSrc.zones || climSrc.data || [];
+ if (!Array.isArray(climZones) && typeof climZones === 'object') climZones = Object.values(climZones);
+ _scan(climZones, 'lat', 'lon');
if (coords.length > 0) {
map.fitBounds(L.latLngBounds(coords), { padding: [60, 60], maxZoom: coords.length === 1 ? 10 : 6 });
$('#searchInput').style.borderColor = 'rgba(0,229,255,0.4)';
@@ -2546,6 +2620,22 @@ function performSearch(q) {
setTimeout(function() { $('#searchInput').style.borderColor = ''; }, 2000);
}
+// Mobile: collapsible layer panel
+$('#layers .panel-title').addEventListener('click', function() {
+ $('#layers').classList.toggle('layers-open');
+});
+
+// Auto-position layer panel below topbar
+function _syncLayersTop() {
+ var tb = $('#topBar');
+ if (tb) {
+ var h = tb.offsetHeight;
+ document.documentElement.style.setProperty('--layers-top', (h + 16) + 'px');
+ }
+}
+_syncLayersTop();
+window.addEventListener('resize', _syncLayersTop);
+
// Show loading indicator until SSE data arrives
$('#hudStats').innerHTML = 'LOADING LIVE FEEDS
';
$('#drawerBody').innerHTML = 'LOADING INTELLIGENCE FEEDS
';