Two fixes based on feedback after the last commit:
1. The map wasn't following the position at all -- only the first fix
recentered, later updates just moved the marker. Now every fix
recenters (easeTo, not flyTo, after the first one, so it doesn't
repeat that zoom swoop on every update) -- matches how
GeolocateControl's own trackUserLocation always behaved.
2. GNSS should only be used when explicitly turned on via the sidebar
"GNSS" icon, not whenever a receiver happens to have a fix --
simpler to test against the simulator that way, and puts the
visitor in control of whether GNSS is engaged at all. GnssControl
now owns following the map on its own (same flyTo-once/easeTo-after
pattern) whenever it's enabled; LocateControl checks
GnssControl#isEnabled() and, when true, just jumps to
#getPosition()'s current fix and reuses its existing .gnss-marker
instead of polling/marking on its own. Only falls back to the
browser's own geolocation when GNSS is off.
Verified end to end with the GNSS simulator: enabling sidebar GNSS
recenters immediately and keeps following over several polls;
clicking Locate while GNSS is on jumps to the same position without
creating a second marker or touching its own active state; turning
GNSS back off and clicking Locate correctly falls through to the
browser geolocation path instead.
LocateControl (public/js/locate.js) replaces MapLibre's own
GeolocateControl -- same button, reusing its exact CSS/icon so it's a
visual drop-in, but sourced differently. Checks GET /api/gnss/position
once on click: if a fix is available (gpsd, possibly
bin/gnss_simulator.rb's simulated one), tracks that instead of the
browser's separate (and often less accurate) location service, which
is now only a fallback. Only the first fix recenters the map; later
updates just move the marker, unlike GeolocateControl's own default
of recentering on every update, which would fight a pan/zoom made in
the meantime.
Verified end to end with the GNSS simulator running: clicking the
button centered on the simulated position and dropped a marker,
clicking again cleared both, no console errors.