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);