Replaces the floating filter + trip-list cards (which drifted/overlapped the
map) with fixed chrome: filters dock on the right (full body height, same
controls), and trips render as horizontally-scrolling cards in a fixed bottom
bar that mirrors the top bar. Body is a grid (map | filter dock / trips bar);
used minmax(0,1fr) + min-width:0 + .app overflow:hidden so the 150-card scroller
can't widen the page. map.resize() on mode switch. Verified: no overflow
(1440=1170+270), filters right of map, trips bar below map, cards scroll, click
animates. No errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zoomed out, vehicles group into amber count-bubbles (tiered by size); click to
zoom-expand; clusters disband into individual pins at ~z11. Implemented with
supercluster while KEEPING the DOM pin/square/arrow look — the filtered+deduped
fleet is loaded into a cluster index, re-queried on moveend per viewport/zoom.
Clustering is live-mode only; honours plate/cost-centre/city filters; KPIs track
the filtered set. Verified: z6 → 4 bubbles summing to fleet total, z13 → pins,
cluster click zooms in. No console errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every vehicle has a GPS tracker (X3/GT06E/AT4) and a JC400P dashcam sharing the
same plate. dedupeLiveFeatures() collapses the pair to one device per normalised
plate: functioning (<24h) beats offline → tracker beats camera → freshest fix.
So a vehicle shows once — tracker by default, camera only when the tracker is
dark. Dropdown + live filter also dedup by normalised plate (merges stray-space
variants like 'KDS 453 Y' vs 'KDS 453Y'). Unit-verified all precedence cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trips view now shows a second bar under the header: the active filter
(vehicle/cost centre/city) plus first-trip and last-trip bookends, each with the
server's reverse-geocoded location + timestamp (first_trip_start_*, last_trip_end_*).
Pinned grid rows so a hidden tripbar doesn't collapse the map.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parked square scale 0.66 → 0.5 so it renders ~9px against the 18px moving
circle (still compounds with zoom scaling). README legend updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parked squares now scale to ~0.66 of the moving-now circle (compounding with
the existing zoom scaling) so they read as quieter 'recent activity' markers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per feedback: recently-parked vehicles now render as a pastel tint of their
cost-centre colour (blended toward white) as a plain square — arrow removed.
Moving-now keeps the full-colour circle + arrow; offline stays grey.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
High-level fleet-activity read: parked vehicles (reported within 24h) now take
their cost-centre colour as a SQUARE (same 32px as the circle) with the heading
arrow, instead of a flat grey circle. Moving-now stays a coloured circle; only
offline (>24h silent) stays grey. Verified per-state: active=circle, parked=
square+arrow, offline=grey circle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Number-plate dropdown now sorted alphabetically (natural/numeric, placeholder
pinned), re-sorted as live-discovered plates are added.
- New 'Assigned city' filter: populated from the API + live feed, filters the
live map instantly (with cost centre + plate), auto-fills from a picked
vehicle, and is passed to the trips query (assigned_city).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Markers now scale with zoom (--veh-scale, ~0.42 at z5 → 1.20 at z14) via a
transform on .veh-inner, so they no longer bloat at country zoom; pins stay
anchored on their coordinate (verified 0px drift).
- Selecting a plate or cost centre now filters the LIVE markers immediately and
recomputes the header KPIs (previously the filter card only fed Show trips, so
selections didn't reflect on the live map). Time period still applies to trips.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-setting el.className each render dropped the maplibregl-marker class
(position:absolute), so live markers fell into document flow and stacked
+32px each — vehicles drifted south, worse when zoomed out, dragging their
plate labels with them. Use classList (preserve maplibregl-marker) and wrap
pin+plate in a .veh-inner positioning context so a class change can't reflow.
Verified: every marker now projects to dX=0,dY=0 on its coordinate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Single-file MapLibre SPA merging live vehicle positions and historical
trips into one console. Reads the existing dashboard read-API
(fleetapi.rahamafresh.com); served as a static nginx image for Coolify.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>