From 23a2b4b3746d3bbf2f375005256f57f6a51db15d Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Wed, 22 Jul 2026 17:17:08 +0200 Subject: [PATCH] start and finish markers, stronger segment glow, edit form spacing --- src/lib/components/Map.svelte | 78 ++++++++++++++++++++++++- src/routes/activities/[id]/+page.svelte | 1 + 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/lib/components/Map.svelte b/src/lib/components/Map.svelte index d79ee04..512c836 100644 --- a/src/lib/components/Map.svelte +++ b/src/lib/components/Map.svelte @@ -217,6 +217,35 @@ ); }); + // start/finish markers for the highlighted route or the live selection + function endpointsGeojson(): FeatureCollection { + const line = + selection && selection.length > 1 + ? selection + : highlightId !== null + ? tracks.find((t) => t.id === highlightId)?.latlngs + : undefined; + if (!line || line.length < 2) return { type: 'FeatureCollection', features: [] }; + const make = (kind: string, [lat, lon]: [number, number]) => ({ + type: 'Feature' as const, + properties: { kind }, + geometry: { type: 'Point' as const, coordinates: [lon, lat] } + }); + return { + type: 'FeatureCollection', + features: [make('start', line[0]), make('end', line[line.length - 1])] + }; + } + $effect(() => { + void selection; + void tracks; + void highlightId; + if (!loaded) return; + (map?.getSource('endpoints') as import('maplibre-gl').GeoJSONSource | undefined)?.setData( + endpointsGeojson() + ); + }); + // animate the camera when a page requests a new focus; remember the // unfocused camera so returning to the overview restores it instead of // zooming all the way back out @@ -279,6 +308,15 @@ ); map.addSource('tracks', { type: 'geojson', data: tracksGeojson(dark) }); + // soft glow behind the highlighted route so it pops off the basemap + map.addLayer({ + id: 'tracks-halo', + type: 'line', + source: 'tracks', + filter: ['==', ['get', 'highlight'], true], + layout: { 'line-join': 'round', 'line-cap': 'round' }, + paint: { 'line-color': ['get', 'color'], 'line-width': 14, 'line-opacity': 0.22 } + }); map.addLayer({ id: 'tracks-casing', type: 'line', @@ -287,7 +325,7 @@ paint: { 'line-color': dark ? '#1a1a19' : '#ffffff', 'line-width': ['case', ['get', 'highlight'], 8, 5], - 'line-opacity': ['case', ['get', 'dim'], 0.08, ['get', 'highlight'], 0.9, 0.6] + 'line-opacity': ['case', ['get', 'dim'], 0.08, ['get', 'highlight'], 0.95, 0.6] } }); map.addLayer({ @@ -352,6 +390,44 @@ } }); + // start / finish markers + map.addSource('endpoints', { type: 'geojson', data: endpointsGeojson() }); + map.addLayer({ + id: 'endpoints-circles', + type: 'circle', + source: 'endpoints', + paint: { + 'circle-radius': 9, + 'circle-color': [ + 'case', + ['==', ['get', 'kind'], 'start'], + '#0ca30c', + dark ? '#eef2f0' : '#0b0b0b' + ], + 'circle-stroke-width': 2, + 'circle-stroke-color': dark ? '#1a1a19' : '#ffffff' + } + }); + map.addLayer({ + id: 'endpoints-glyphs', + type: 'symbol', + source: 'endpoints', + layout: { + 'text-field': ['case', ['==', ['get', 'kind'], 'start'], 'S', 'F'], + 'text-size': 10, + 'text-allow-overlap': true, + 'text-ignore-placement': true + }, + paint: { + 'text-color': [ + 'case', + ['==', ['get', 'kind'], 'start'], + '#ffffff', + dark ? '#0b0b0b' : '#ffffff' + ] + } + }); + // chart-hover position marker, matching the chart's crosshair dot map.addSource('hover-point', { type: 'geojson', data: hoverGeojson() }); map.addLayer({ diff --git a/src/routes/activities/[id]/+page.svelte b/src/routes/activities/[id]/+page.svelte index bfd9ea0..01b8b08 100644 --- a/src/routes/activities/[id]/+page.svelte +++ b/src/routes/activities/[id]/+page.svelte @@ -349,6 +349,7 @@ align-items: center; flex-wrap: wrap; width: 100%; + margin-bottom: 1.25rem; } .edit-form input { padding: 0.5rem 0.7rem;