diff --git a/index.html b/index.html
index 36c251d..d79c29d 100644
--- a/index.html
+++ b/index.html
@@ -181,7 +181,12 @@
positioning context is the inner wrapper below, so a class change can't
reflow the markers. */
.veh-marker { cursor: pointer; will-change: transform; }
- .veh-inner { position: relative; width: 32px; height: 32px; }
+ /* Scaled by zoom via --veh-scale (set in updateVehScale). transform-origin
+ centre keeps the pin anchored on its coordinate as it grows/shrinks. */
+ .veh-inner {
+ position: relative; width: 32px; height: 32px;
+ transform: scale(var(--veh-scale, 1)); transform-origin: center center;
+ }
.veh-pin {
width: 32px; height: 32px; border-radius: 50%;
background: var(--c, var(--parked));
@@ -385,7 +390,8 @@ function ensureMap() {
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');
popup = new maplibregl.Popup({ closeButton: true, closeOnClick: false, offset: 20 });
popup.on('close', () => { openPopupImei = null; popupStuck = false; });
- map.on('load', () => POIS.forEach(addPoiMarker));
+ map.on('load', () => { POIS.forEach(addPoiMarker); updateVehScale(); });
+ map.on('zoom', updateVehScale);
map.on('click', e => {
if (mode !== 'live') return;
if (!popupStuck) return;
@@ -496,6 +502,9 @@ function renderLive() {
if (still) showLivePopup(still, true);
else { popup.remove(); openPopupImei = null; popupStuck = false; }
}
+ // Honour the current plate/cost-centre filter + size markers for this zoom.
+ applyLiveFilters();
+ updateVehScale();
};
if (map.isStyleLoaded()) drawMarkers(); else map.once('load', drawMarkers);
@@ -714,13 +723,46 @@ function applyVehicleAutoFilter() {
const metas = selected.map(p => VEHICLE_META.get(p)).filter(Boolean);
const sharedCC = collapse(metas.map(m => m.cost_centre));
setSelectValue('f-cc', sharedCC ?? '');
+ applyLiveFilters(); // reflect the plate selection on the live map immediately
}
function collapse(values) { if (!values.length) return null; const f = values[0]; return values.every(v => v === f) ? f : null; }
function setSelectValue(id, value) { const el = document.getElementById(id); const opt = Array.from(el.options).find(o => o.value === value); el.value = opt ? value : ''; }
+// Scale the live markers with the zoom level so they don't bloat at country
+// zoom or vanish when zoomed in. Linear from z5 → z14.
+function updateVehScale() {
+ if (!map) return;
+ const t = Math.max(0, Math.min(1, (map.getZoom() - 5) / 9));
+ document.getElementById('map').style.setProperty('--veh-scale', (0.42 + t * 0.78).toFixed(3));
+}
+
+// Filter the LIVE markers by the selected plate(s) + cost centre, and recompute
+// the header KPIs to match. Time period only applies to trips, not live.
+function applyLiveFilters() {
+ if (mode !== 'live') return;
+ const plates = new Set(Array.from(document.getElementById('f-vehicle').selectedOptions).map(o => o.value).filter(Boolean));
+ const cc = document.getElementById('f-cc').value;
+ let total = 0, moving = 0, parked = 0, offline = 0; const speeds = [];
+ (lastLivePayload?.geojson?.features || []).forEach(f => {
+ const p = f.properties; const m = liveMarkers.get(p.imei); if (!m) return;
+ const pass = (plates.size === 0 || plates.has(p.vehicle_number)) && (!cc || p.cost_centre === cc);
+ m.getElement().style.display = pass ? '' : 'none';
+ if (!pass) return;
+ total++;
+ const st = vehicleState(p);
+ if (st === 'offline') offline++;
+ else if (st === 'active') { moving++; const sp = Number(p.speed || 0); if (sp > 0) speeds.push(sp); }
+ else parked++;
+ });
+ speeds.sort((a, b) => a - b);
+ renderLiveKPIs({ total, moving, parked, offline, median: speeds.length ? speeds[Math.floor(speeds.length / 2)] : null, last_batch_utc: lastLivePayload?.summary?.last_batch_utc });
+}
+
document.getElementById('f-period').addEventListener('change', e => {
document.getElementById('custom').classList.toggle('show', e.target.value === 'custom');
});
+// Selecting a cost centre filters the live map immediately.
+document.getElementById('f-cc').addEventListener('change', applyLiveFilters);
function periodToRange(period, cs, ce) {
const today = new Date(); const fmt = d => d.toISOString().slice(0, 10);
const minus = n => { const x = new Date(today); x.setDate(x.getDate() - n); return x; };