From 4114b0109f03389805b3125e41d323d66628ec0e Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Sat, 23 May 2026 16:13:26 +0200 Subject: [PATCH] feat(hikes): show elapsed-since-start time on prose photos Each overlays a "nach X" badge (elapsed from the first timestamped track point), matching the photo strip's formatElapsed wording. Renders only when the photo carries a timestamp; bottom-right, or top-right when a caption is present so it never overlaps. --- src/lib/components/hikes/HikeImage.svelte | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/lib/components/hikes/HikeImage.svelte b/src/lib/components/hikes/HikeImage.svelte index 27ac014b..28b48a2a 100644 --- a/src/lib/components/hikes/HikeImage.svelte +++ b/src/lib/components/hikes/HikeImage.svelte @@ -3,6 +3,7 @@ import { focused } from './focusedImageStore.svelte'; import { addScrollAnchor } from './scrollAnchors'; import Lock from '@lucide/svelte/icons/lock'; + import Clock from '@lucide/svelte/icons/clock'; interface Props { /** Position in the hike's full chronological image list (0-indexed, @@ -42,6 +43,29 @@ return bestDelta === Infinity ? -1 : bestIdx; }); + // Elapsed time since the hike start (first timestamped track point) — same + // "nach X" the photo strip shows, not the absolute wall-clock time. + const elapsedLabel = $derived.by(() => { + const t = ip?.timestamp; + const track = ctx().track; + if (typeof t !== 'number' || !track) return null; + let start: number | null = null; + for (const p of track) { + if (typeof p[3] === 'number') { + start = p[3]; + break; + } + } + if (start === null) return null; + const ms = t - start; + if (!Number.isFinite(ms) || ms < 0) return null; + const totalMin = Math.round(ms / 60000); + if (totalMin < 60) return `${totalMin} min`; + const h = Math.floor(totalMin / 60); + const m = totalMin % 60; + return m === 0 ? `${h} h` : `${h} h ${m} min`; + }); + let figure: HTMLElement | undefined = $state(); // Register this image's DOM element as a scroll anchor. The page reads @@ -70,6 +94,12 @@ privat {/if} + {#if elapsedLabel} + + + {/if} {#if caption}
{caption}
{/if} @@ -135,6 +165,34 @@ -webkit-backdrop-filter: blur(4px); } + /* Capture time, bottom-right so it never collides with the private badge. */ + .shot-time { + position: absolute; + bottom: 0.6rem; + right: 0.6rem; + display: inline-flex; + align-items: center; + gap: 0.28rem; + font-size: 0.7rem; + font-weight: 600; + font-variant-numeric: tabular-nums; + padding: 0.2rem 0.55rem; + border-radius: var(--radius-pill); + background: rgb(0 0 0 / 0.55); + color: #fff; + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + } + + /* Sits within the rounded image; if a caption follows, the figure grows so + * the badge stays over the photo (absolute to the figure, image is the top + * block). */ + .hike-image:has(figcaption) .shot-time { + bottom: auto; + top: 0.6rem; + right: 0.6rem; + } + @media (prefers-reduced-motion: reduce) { .hike-image { transition: none;