From 279469cd65ff4e74a22c5d36fe452fd01a0cc348 Mon Sep 17 00:00:00 2001
From: Vincent van der Wal
Date: Wed, 22 Jul 2026 15:54:19 +0200
Subject: [PATCH] synced chart hover, fixed map position across pages
---
src/lib/components/ElevationChart.svelte | 29 +-
src/lib/components/MapSlot.svelte | 22 +-
src/lib/components/SpeedChart.svelte | 28 +-
src/lib/map-state.svelte.ts | 7 +
src/routes/+layout.svelte | 18 +
src/routes/+page.svelte | 3 +-
src/routes/activities/[id]/+page.svelte | 544 ++++++++++++-----------
src/routes/peaks/+page.svelte | 2 +-
8 files changed, 357 insertions(+), 296 deletions(-)
diff --git a/src/lib/components/ElevationChart.svelte b/src/lib/components/ElevationChart.svelte
index 44ebc62..7f5d93e 100644
--- a/src/lib/components/ElevationChart.svelte
+++ b/src/lib/components/ElevationChart.svelte
@@ -1,10 +1,13 @@
-
-
-
-
diff --git a/src/lib/components/SpeedChart.svelte b/src/lib/components/SpeedChart.svelte
index 47c3732..e8c3918 100644
--- a/src/lib/components/SpeedChart.svelte
+++ b/src/lib/components/SpeedChart.svelte
@@ -3,10 +3,13 @@
let {
timed,
+ hoverD = null,
onhover,
onselect
}: {
timed: { d: number; t: number }[];
+ /** shared hover distance controlled by the page, so charts stay in sync */
+ hoverD?: number | null;
onhover?: (d: number | null) => void;
onselect?: (d: number) => void;
} = $props();
@@ -82,21 +85,23 @@
: ''
);
- let hover = $state<{ d: number; v: number } | null>(null);
-
- function onmove(event: PointerEvent) {
- const rect = (event.currentTarget as SVGRectElement).getBoundingClientRect();
- const frac = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
- const target = frac * totalDist;
+ // snap the shared hover distance to this chart's nearest point
+ const hover = $derived.by(() => {
+ if (hoverD === null || series.length === 0) return null;
let lo = 0;
let hi = series.length - 1;
while (hi - lo > 1) {
const mid = (lo + hi) >> 1;
- if (series[mid].d < target) lo = mid;
+ if (series[mid].d < hoverD) lo = mid;
else hi = mid;
}
- hover = target - series[lo].d < series[hi].d - target ? series[lo] : series[hi];
- onhover?.(hover.d);
+ return hoverD - series[lo].d < series[hi].d - hoverD ? series[lo] : series[hi];
+ });
+
+ function onmove(event: PointerEvent) {
+ const rect = (event.currentTarget as SVGRectElement).getBoundingClientRect();
+ const frac = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
+ onhover?.(frac * totalDist);
}
const tooltipLeft = $derived(hover ? Math.min(Math.max(x(hover.d), 70), width - 70) : 0);
@@ -144,10 +149,7 @@
fill="transparent"
onpointermove={onmove}
onclick={() => hover && onselect?.(hover.d)}
- onpointerleave={() => {
- hover = null;
- onhover?.(null);
- }}
+ onpointerleave={() => onhover?.(null)}
/>
{#if hover}
diff --git a/src/lib/map-state.svelte.ts b/src/lib/map-state.svelte.ts
index 2881978..3255b52 100644
--- a/src/lib/map-state.svelte.ts
+++ b/src/lib/map-state.svelte.ts
@@ -13,6 +13,7 @@ let detailTrack = $state(null);
let hoverPoint = $state<{ lat: number; lon: number } | null>(null);
let carrier = $state(null);
let active = $state(false);
+let wanted = $state(false);
let home: HTMLElement | null = null;
let peakClickHandler: ((id: number) => void) | null = null;
@@ -42,6 +43,12 @@ export const mapState = {
get active() {
return active;
},
+ get wanted() {
+ return wanted;
+ },
+ setWanted(value: boolean) {
+ wanted = value;
+ },
setPeaks(value: MapPeak[]) {
peaks = value;
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index 923999b..99c4804 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -9,11 +9,20 @@
let mapHome: HTMLDivElement;
let mapCarrier: HTMLDivElement;
+ let layoutSlot: HTMLDivElement;
$effect(() => {
if (mapCarrier && mapHome) mapState.registerCarrier(mapCarrier, mapHome);
});
+ // show the persistent map in its fixed spot whenever the page asks for it
+ $effect(() => {
+ if (mapState.wanted && layoutSlot && mapState.carrier) {
+ mapState.attach(layoutSlot);
+ return () => mapState.detach();
+ }
+ });
+
let mapComponent: Map | undefined = $state();
$effect(() => {
if (mapComponent) {
@@ -136,6 +145,11 @@
+
{@render children()}
@@ -378,4 +392,8 @@
width: 100%;
height: 100%;
}
+ .layout-map-slot {
+ height: min(440px, 52vh);
+ margin-bottom: 1.75rem;
+ }
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 7f83d3f..075ff39 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -30,8 +30,7 @@
-Worldmap
-
+
Recent activities
{#if data.activities.length === 0}
diff --git a/src/routes/activities/[id]/+page.svelte b/src/routes/activities/[id]/+page.svelte
index 6e92c05..c34e983 100644
--- a/src/routes/activities/[id]/+page.svelte
+++ b/src/routes/activities/[id]/+page.svelte
@@ -1,282 +1,328 @@
- {a.name} · Streba
+ {a.name} · Streba
← Overview
- {#if editing}
-
- {:else}
-
-
- {a.name}
- {#if data.canDelete}
- (editing = true)}>
- ✎
-
- {/if}
-
-
- {a.username} · {fmtDate(a.date)} ·
- {a.type}
-
-
- {/if}
- {#if data.canDelete && !editing}
-
- {/if}
+ {#if editing}
+
+ {:else}
+
+
+ {a.name}
+ {#if data.canDelete}
+ (editing = true)}
+ >
+ ✎
+
+ {/if}
+
+
+ {a.username} · {fmtDate(
+ a.date,
+ )} ·
+ {a.type}
+
+
+ {/if}
+ {#if data.canDelete && !editing}
+
+ {/if}
-
-
-
- {#if avgSpeed}
-
- {:else}
-
- {/if}
+
+
+
+ {#if avgSpeed}
+
+ {:else}
+
+ {/if}
{#if data.bagged.length > 0}
-
- ⛰ Peaks reached:
- {#each data.bagged as peak, i (peak.id)}{i > 0 ? ', ' : ' '}{peak.name} ({peak.elevation_m} m){/each}
-
+
+ ⛰ Peaks reached:
+ {#each data.bagged as peak, i (peak.id)}{i > 0 ? ", " : " "}{peak.name}
+ ({peak.elevation_m} m){/each}
+
{/if}
-Map
-
+
{#if data.profile.length > 1}
- Elevation profile
-
-
-
- Low {fmtElevation(a.elev_min_m)}
- High {fmtElevation(a.elev_max_m)}
-
-
+ Elevation profile
+
+
+
+ Low {fmtElevation(a.elev_min_m)}
+ High {fmtElevation(a.elev_max_m)}
+
+
{/if}
{#if data.timed.length > 2}
- Speed
-
-
-
+ Speed
+
+
+
{/if}
diff --git a/src/routes/peaks/+page.svelte b/src/routes/peaks/+page.svelte
index 2ab12ec..4b3df0e 100644
--- a/src/routes/peaks/+page.svelte
+++ b/src/routes/peaks/+page.svelte
@@ -158,7 +158,7 @@
Zoom in to reveal less prominent summits; click a peak to cross it off.
-
+