diff --git a/index.html b/index.html index 7cac91b..89f4a17 100644 --- a/index.html +++ b/index.html @@ -269,6 +269,15 @@ } .veh-marker.offline .veh-plate { color: var(--muted); } + /* Specialist vehicle icon (crane / motorbike / pick-up) — white silhouette + centred in the department-coloured pin. */ + .veh-type { display: grid; place-items: center; width: 22px; height: 22px; } + .veh-type svg { width: 20px; height: 20px; display: block; } + /* Keep specialists full-size + circular even when parked so the icon stays + legible (overrides the parked half-size square). */ + .veh-marker.has-type.parked .veh-pin { transform: scale(1); border-radius: 50%; } + .veh-marker.has-type.offline .veh-type svg { opacity: .85; } + /* ── Persistent POI marker (Fireside HQ) ───────────────────────────── */ /* Cluster bubble (zoomed-out): amber circle + white count, tiered by size. Click zooms to expand into the individual pins. */ @@ -753,15 +762,29 @@ function upsertClusterMarker(id, count, coords) { } } +// Specialist vehicle types get their own marker icon (white silhouette inside +// the department-coloured pin). All other types (field-service + unassigned) +// fall through to the default arrow/square/dot marker, unchanged. Keys must +// match fn_live_positions' `vehicle_type` exactly (migration 16). +const SPECIALIST_ICONS = { + 'Crane': '', + 'Motorbike': '', + 'Pick-Up': '', +}; + function upsertLiveMarker(p, coords, feature) { const state = vehicleState(p); // Active (moving now) = full department colour. Parked (reported within 24h) // = a PASTEL of that department colour. Offline (>24h silent) = grey. Lets // high-level viewers read fleet activity by department at a glance. const ccColor = colorForCostCentre(p.cost_centre); - const color = state === 'offline' ? OFFLINE_COLOR - : state === 'parked' ? pastelColor(ccColor) - : ccColor; + const typeIcon = SPECIALIST_ICONS[p && p.vehicle_type]; + let color = state === 'offline' ? OFFLINE_COLOR + : state === 'parked' ? pastelColor(ccColor) + : ccColor; + // Specialists keep the full department colour (not the parked pastel) so the + // white icon stays legible and they stand out from the field-service swarm. + if (typeIcon && state !== 'offline') color = ccColor; const speed = Number(p.speed || 0); const dir = Number(p.direction || 0); let m = liveMarkers.get(p.imei); @@ -788,19 +811,28 @@ function upsertLiveMarker(p, coords, feature) { el.classList.add('veh-marker'); el.classList.remove('active', 'parked', 'offline'); el.classList.add(state); + el.classList.toggle('has-type', !!typeIcon); const pin = el.querySelector('.veh-pin'); pin.style.setProperty('--c', color); - // Direction arrow only for vehicles moving now. Parked = a clean pastel - // square (no arrow, no dot). Idling/offline keep the neutral dot. + // Specialist vehicles (crane/motorbike/pick-up) show their own icon instead of + // the heading arrow. Everything else: direction arrow only when moving now, + // a clean pastel square when parked, a neutral dot when idling/offline. const glyph = el.querySelector('.glyph'); - if (state === 'active' && speed > 0) { + if (typeIcon) { + glyph.className = 'glyph veh-type'; + glyph.style.removeProperty('--dir'); + glyph.innerHTML = typeIcon; + } else if (state === 'active' && speed > 0) { glyph.className = 'glyph veh-arrow'; + glyph.innerHTML = ''; glyph.style.setProperty('--dir', dir + 'deg'); } else if (state === 'parked') { glyph.className = 'glyph'; // empty — just the pastel square + glyph.innerHTML = ''; glyph.style.removeProperty('--dir'); } else { glyph.className = 'glyph idle-dot'; + glyph.innerHTML = ''; glyph.style.removeProperty('--dir'); } el.querySelector('.veh-plate').textContent = plateTail(p.vehicle_number);