feat(hikes): show elapsed-since-start time on prose photos

Each <HikeImage> 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.
This commit is contained in:
2026-05-23 16:13:26 +02:00
parent 169f8798f3
commit 4114b0109f
+58
View File
@@ -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
</span>
{/if}
{#if elapsedLabel}
<span class="shot-time" title="Zeit seit Start">
<Clock size={11} strokeWidth={2.25} aria-hidden="true" />
nach {elapsedLabel}
</span>
{/if}
{#if caption}
<figcaption>{caption}</figcaption>
{/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;