Refactor the metric gradient code into shared _computeProgress +
_buildGradient helpers. Hovering any of speed/HR/power/elevation/cadence
stats switches the track to a blue→green→yellow→red gradient for that
metric. A small legend (min ·gradient bar· max + label) appears in the
bottom-left corner of the map while active. Absolute elevation used
(not slope), so blue=valleys, red=peaks.
Hovering avg/max speed in the stats panel switches the track from the
default gradient to a blue→green→yellow→red speed gradient. Progress
along the track is computed from cumulative distance (speed × time) so
fast sections appear proportionally long rather than time-weighted.
Reverts to default on mouse-leave. No-op when timeseries has no speed
data or the activity has no GPS track.
FIT parser: try enhanced_altitude before altitude. Barometric altimeters
on modern Garmins (Edge 540, 840, etc.) write enhanced_altitude in
record messages and total_ascent in lap messages. The old code read only
altitude, producing null elevation_m per point → null elevation_gain_m
at the activity root while laps had correct values from total_ascent.
ActivityMap: use preview_coords (passed from ActivitySummary) to
initialise the map at the activity's location on mount, eliminating the
flash of world-view before the async detail JSON / bbox arrives.