feat(hikes): forgiving map selection, photo lightbox, detail polish
Map interaction: - Overview map: widen the canvas renderer hit-test (tolerance) so a route can be hovered/clicked from a comfortable margin instead of demanding a pixel-perfect click on the thin line. - Detail map: drive the elevation cursor from a whole-map mousemove that snaps to the nearest track point within ~70 px (track cached in layer-point space, refreshed on zoom/move), instead of requiring the pointer to ride exactly on the trail. The hover pin now renders for map-sourced hovers too, and is recoloured to nord red as a distinct "you are here" marker. Trail polyline made non-interactive. Detail page: - Move the photo strip above the stats row and trim it (3:2 cards). - Add a fullscreen lightbox: an expand button on each card opens the full-res image with prev/next, arrow keys, Esc, backdrop-close and a body-scroll lock; opening/stepping syncs the map + strip. The card's existing click (map-position sync) is preserved. - Cap inline prose images at 680 px (centered) so they don't blow up to full width in the single-column layout on wider screens; the desktop two-column layout is unaffected.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "homepage",
|
||||
"version": "1.78.0",
|
||||
"version": "1.79.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -79,7 +79,13 @@
|
||||
<style>
|
||||
.hike-image {
|
||||
position: relative;
|
||||
margin: 2rem 0;
|
||||
/* Cap the width so that in the single-column (mobile/tablet) layout the
|
||||
* photo doesn't blow up to the full content width on wider screens.
|
||||
* On the desktop two-column layout the prose column is already narrower
|
||||
* than this, so it stays full-bleed-in-column there. Centered via
|
||||
* auto inline margins. */
|
||||
max-width: 680px;
|
||||
margin: 2rem auto;
|
||||
border-radius: var(--radius-card);
|
||||
overflow: hidden;
|
||||
background: #14181f;
|
||||
|
||||
@@ -223,10 +223,14 @@
|
||||
(getComputedStyle(document.documentElement).getPropertyValue('--red').trim() ||
|
||||
'#bf616a');
|
||||
|
||||
// Non-interactive: hover is driven by the whole-map `mousemove`
|
||||
// handler below (snap-to-nearest), so the line itself needn't grab
|
||||
// the pointer cursor or events.
|
||||
const polyline = L.polyline(latLngs, {
|
||||
color: trailColor,
|
||||
weight: 4,
|
||||
opacity: 0.95
|
||||
opacity: 0.95,
|
||||
interactive: false
|
||||
}).addTo(map);
|
||||
|
||||
L.circleMarker(latLngs[0], {
|
||||
@@ -373,7 +377,6 @@
|
||||
// before the pin actually hits the edge.
|
||||
const stopHoverEffect = $effect.root(() => {
|
||||
$effect(() => {
|
||||
if (hover.source === 'map') return;
|
||||
if (hover.index === null || hover.index < 0 || hover.index >= latLngs.length) {
|
||||
hoverMarker.remove();
|
||||
return;
|
||||
@@ -381,6 +384,9 @@
|
||||
const ll = latLngs[hover.index];
|
||||
hoverMarker.setLatLng(ll);
|
||||
hoverMarker.addTo(map);
|
||||
// Only auto-pan for cursors driven from elsewhere (chart /
|
||||
// scroll tracker). A map-sourced hover means the user is
|
||||
// already pointing here, so panning would fight them.
|
||||
if (hover.source === 'chart' || hover.source === 'scroll') {
|
||||
const inner = map.getBounds().pad(-0.12);
|
||||
if (!inner.contains(ll)) {
|
||||
@@ -435,23 +441,44 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Polyline hover → write to store.
|
||||
polyline.on('mousemove', (e: { latlng: { lat: number; lng: number } }) => {
|
||||
// Elevation tracking: rather than requiring the pointer to be exactly
|
||||
// on the thin trail, snap the chart cursor to the nearest track point
|
||||
// whenever the mouse is anywhere within HOVER_SNAP_PX of the route.
|
||||
// The track is cached in layer-point (pixel) space so each pointer
|
||||
// move is just cheap distance maths; the cache is rebuilt on zoom/
|
||||
// move (layer points are pan-invariant, but rebuilding on moveend
|
||||
// keeps it correct regardless of how the view changed).
|
||||
const HOVER_SNAP_PX = 70;
|
||||
let projected: { x: number; y: number }[] = [];
|
||||
function reproject() {
|
||||
projected = latLngs.map((ll) => map.latLngToLayerPoint(ll));
|
||||
}
|
||||
reproject();
|
||||
map.on('zoomend moveend', reproject);
|
||||
|
||||
map.on('mousemove', (e: { layerPoint: { x: number; y: number } }) => {
|
||||
if (projected.length === 0) return;
|
||||
const { x, y } = e.layerPoint;
|
||||
let bestIdx = 0;
|
||||
let bestSq = Infinity;
|
||||
const { lat, lng } = e.latlng;
|
||||
for (let i = 0; i < latLngs.length; i++) {
|
||||
const dLat = latLngs[i][0] - lat;
|
||||
const dLng = latLngs[i][1] - lng;
|
||||
const sq = dLat * dLat + dLng * dLng;
|
||||
for (let i = 0; i < projected.length; i++) {
|
||||
const dx = projected[i].x - x;
|
||||
const dy = projected[i].y - y;
|
||||
const sq = dx * dx + dy * dy;
|
||||
if (sq < bestSq) {
|
||||
bestSq = sq;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
if (bestSq <= HOVER_SNAP_PX * HOVER_SNAP_PX) {
|
||||
setHover(bestIdx, 'map');
|
||||
} else if (hover.source === 'map') {
|
||||
clearHover();
|
||||
}
|
||||
});
|
||||
map.on('mouseout', () => {
|
||||
if (hover.source === 'map') clearHover();
|
||||
});
|
||||
polyline.on('mouseout', () => clearHover());
|
||||
|
||||
// User location (opt-in).
|
||||
let userMarker: ReturnType<typeof L.circleMarker> | null = null;
|
||||
@@ -799,7 +826,10 @@
|
||||
:global(.hike-hover-pin) {
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
color: var(--color-primary);
|
||||
/* Nord red — deliberately off the primary palette so the cursor pin
|
||||
* reads as a distinct "you are here" marker against the blue-ish
|
||||
* trail / UI accents. `currentColor` drives the SVG fill. */
|
||||
color: var(--red);
|
||||
filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.25));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition';
|
||||
import type { HikeTrackPoint, ImagePoint } from '$types/hikes';
|
||||
import { focused, setFocused } from './focusedImageStore.svelte';
|
||||
import MapPin from '@lucide/svelte/icons/map-pin';
|
||||
import Lock from '@lucide/svelte/icons/lock';
|
||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
import Expand from '@lucide/svelte/icons/expand';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
|
||||
interface Props {
|
||||
images: ImagePoint[];
|
||||
@@ -29,9 +32,58 @@
|
||||
return m === 0 ? `${h} h` : `${h} h ${m} min`;
|
||||
}
|
||||
|
||||
const cardEls: Array<HTMLButtonElement | null> = $state([]);
|
||||
const cardEls: Array<HTMLElement | null> = $state([]);
|
||||
let scrollEl = $state<HTMLDivElement | undefined>(undefined);
|
||||
|
||||
// Fullscreen lightbox. Independent of `focused` (which drives the map),
|
||||
// but opening / navigating also syncs `focused` so the map + strip follow
|
||||
// whatever is being viewed full-screen.
|
||||
let lightboxIndex = $state<number | null>(null);
|
||||
const lightboxOpen = $derived(lightboxIndex !== null);
|
||||
let closeBtn = $state<HTMLButtonElement | undefined>(undefined);
|
||||
|
||||
function openLightbox(i: number): void {
|
||||
lightboxIndex = i;
|
||||
setFocused(i, 'strip');
|
||||
}
|
||||
|
||||
function closeLightbox(): void {
|
||||
lightboxIndex = null;
|
||||
}
|
||||
|
||||
function lightboxStep(dir: -1 | 1): void {
|
||||
if (lightboxIndex === null) return;
|
||||
const n = lightboxIndex + dir;
|
||||
if (n < 0 || n >= images.length) return;
|
||||
lightboxIndex = n;
|
||||
setFocused(n, 'strip');
|
||||
}
|
||||
|
||||
// While open: Esc closes, arrows navigate, body scroll is locked, and focus
|
||||
// moves into the dialog. Keyed on `lightboxOpen` (not the index) so stepping
|
||||
// between images doesn't re-run the setup or steal focus back to close.
|
||||
$effect(() => {
|
||||
if (!lightboxOpen) return;
|
||||
closeBtn?.focus();
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
else if (e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
lightboxStep(-1);
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
lightboxStep(1);
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKey);
|
||||
const prevOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
document.body.style.overflow = prevOverflow;
|
||||
};
|
||||
});
|
||||
|
||||
// Recenter the active card horizontally inside the strip on focus change.
|
||||
// We scroll only the strip's own X axis — `scrollIntoView` would also
|
||||
// pull the page Y to bring the strip into the viewport, which is not
|
||||
@@ -122,12 +174,11 @@
|
||||
? formatElapsed(ip.timestamp - startTimestamp)
|
||||
: null}
|
||||
{@const active = focused.index === i}
|
||||
<div class="card-wrap" class:active bind:this={cardEls[i]}>
|
||||
<button
|
||||
type="button"
|
||||
class="card"
|
||||
class:active
|
||||
class:private={ip.visibility === 'private'}
|
||||
bind:this={cardEls[i]}
|
||||
onclick={() => onCardClick(i)}
|
||||
aria-label={`Foto ${i + 1} von ${images.length}${elapsed ? `, nach ${elapsed}` : ''}`}
|
||||
role="option"
|
||||
@@ -147,6 +198,16 @@
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="expand"
|
||||
aria-label={`Foto ${i + 1} im Vollbild öffnen`}
|
||||
title="Vollbild"
|
||||
onclick={() => openLightbox(i)}
|
||||
>
|
||||
<Expand size={15} strokeWidth={2} aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -161,6 +222,52 @@
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if lightboxIndex !== null}
|
||||
{@const ip = images[lightboxIndex]}
|
||||
{@const elapsed =
|
||||
ip.timestamp != null && startTimestamp != null
|
||||
? formatElapsed(ip.timestamp - startTimestamp)
|
||||
: null}
|
||||
<div
|
||||
class="lightbox"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`Foto ${lightboxIndex + 1} von ${images.length}`}
|
||||
transition:fade={{ duration: 150 }}
|
||||
>
|
||||
<button class="lb-backdrop" aria-label="Schließen" onclick={closeLightbox}></button>
|
||||
|
||||
<button
|
||||
class="lb-btn lb-close"
|
||||
aria-label="Schließen"
|
||||
bind:this={closeBtn}
|
||||
onclick={closeLightbox}
|
||||
>
|
||||
<X size={22} strokeWidth={2} aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
{#if lightboxIndex > 0}
|
||||
<button class="lb-btn lb-prev" aria-label="Vorheriges Bild" onclick={() => lightboxStep(-1)}>
|
||||
<ChevronLeft size={26} strokeWidth={2.25} aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
{#if lightboxIndex < images.length - 1}
|
||||
<button class="lb-btn lb-next" aria-label="Nächstes Bild" onclick={() => lightboxStep(1)}>
|
||||
<ChevronRight size={26} strokeWidth={2.25} aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<figure class="lb-figure">
|
||||
<img src={ip.src} alt={ip.alt} />
|
||||
<figcaption class="lb-caption">
|
||||
<span class="lb-count">{lightboxIndex + 1} / {images.length}</span>
|
||||
{#if elapsed}<span class="lb-elapsed">nach {elapsed}</span>{/if}
|
||||
{#if ip.alt}<span class="lb-alt">{ip.alt}</span>{/if}
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@@ -228,27 +335,46 @@
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.card {
|
||||
/* The wrapper is the flex item: it carries the size, scroll-snap and the
|
||||
* lift/scale transform. The card button and the expand button live inside
|
||||
* it as siblings (a button can't be nested in a button). */
|
||||
.card-wrap {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: 232px;
|
||||
scroll-snap-align: center;
|
||||
border-radius: var(--radius-lg);
|
||||
transform: translateY(0) scale(1);
|
||||
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.card-wrap:hover,
|
||||
.card-wrap:focus-within {
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
}
|
||||
|
||||
/* Active card stands out via a much heavier, tinted drop shadow rather
|
||||
* than dimming everything else — keeps every photo legible. */
|
||||
.card-wrap.active {
|
||||
transform: translateY(-6px) scale(1.05);
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: var(--color-surface);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
scroll-snap-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transform: translateY(0) scale(1);
|
||||
transition:
|
||||
transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 220ms ease;
|
||||
transition: box-shadow 220ms ease;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus-visible {
|
||||
transform: translateY(-2px) scale(1.02);
|
||||
.card-wrap:hover .card,
|
||||
.card-wrap:focus-within .card {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
@@ -257,19 +383,72 @@
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Active card stands out via a much heavier, tinted drop shadow rather
|
||||
* than dimming everything else — keeps every photo legible. */
|
||||
.card.active {
|
||||
transform: translateY(-6px) scale(1.05);
|
||||
.card-wrap.active .card {
|
||||
box-shadow:
|
||||
0 18px 32px -8px color-mix(in oklab, var(--color-primary) 55%, transparent),
|
||||
0 6px 14px -6px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
/* Fullscreen trigger — a circular badge in the top-right of each card.
|
||||
* Hidden until the card is hovered/focused/active (always shown on touch
|
||||
* devices, which have no hover). */
|
||||
.expand {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
z-index: 3;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: rgb(0 0 0 / 0.5);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transform: scale(0.85);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
transition:
|
||||
opacity var(--transition-fast),
|
||||
transform var(--transition-fast),
|
||||
background var(--transition-fast);
|
||||
}
|
||||
|
||||
.card-wrap:hover .expand,
|
||||
.card-wrap:focus-within .expand,
|
||||
.card-wrap.active .expand {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.expand:hover {
|
||||
background: rgb(0 0 0 / 0.72);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.expand:focus-visible {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.expand {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.card img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 4 / 3;
|
||||
/* 3:2 — a touch shorter than the old 4:3 so the strip sits compactly
|
||||
* above the stats row without dominating the page. */
|
||||
aspect-ratio: 3 / 2;
|
||||
object-fit: cover;
|
||||
background: var(--color-bg-elevated);
|
||||
}
|
||||
@@ -367,7 +546,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.card {
|
||||
.card-wrap {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
@@ -386,11 +565,153 @@
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.card-wrap,
|
||||
.card,
|
||||
.strip-scroll,
|
||||
.chev {
|
||||
.chev,
|
||||
.expand {
|
||||
transition: none;
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Fullscreen lightbox ─────────────────────────────────────────────── */
|
||||
.lightbox {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9000;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1.5rem;
|
||||
background: rgb(0 0 0 / 0.92);
|
||||
}
|
||||
|
||||
.lb-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.lb-figure {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
max-width: 92vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.lb-figure img {
|
||||
max-width: 92vw;
|
||||
max-height: 82vh;
|
||||
object-fit: contain;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 12px 48px rgb(0 0 0 / 0.55);
|
||||
}
|
||||
|
||||
.lb-caption {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.4rem 0.85rem;
|
||||
max-width: 92vw;
|
||||
color: rgb(255 255 255 / 0.88);
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lb-count {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lb-elapsed {
|
||||
color: rgb(255 255 255 / 0.7);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.lb-alt {
|
||||
color: rgb(255 255 255 / 0.6);
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.lb-btn {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: rgb(255 255 255 / 0.12);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
transition:
|
||||
background var(--transition-fast),
|
||||
transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.lb-btn:hover {
|
||||
background: rgb(255 255 255 / 0.24);
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.lb-btn:focus-visible {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.lb-close {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.lb-prev {
|
||||
left: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.lb-next {
|
||||
right: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.lb-prev:hover,
|
||||
.lb-next:hover {
|
||||
transform: translateY(-50%) scale(1.08);
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.lb-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.lb-close {
|
||||
top: 0.6rem;
|
||||
right: 0.6rem;
|
||||
}
|
||||
|
||||
.lb-prev {
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.lb-next {
|
||||
right: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -98,10 +98,15 @@
|
||||
const L = await import('leaflet');
|
||||
if (cancelled || !node.isConnected) return;
|
||||
|
||||
// `tolerance` widens the canvas renderer's hit-test radius around
|
||||
// every polyline (hit = weight/2 + tolerance), so a route can be
|
||||
// hovered/clicked from a comfortable margin instead of demanding a
|
||||
// pixel-perfect click on the 4 px line.
|
||||
const map = L.map(node, {
|
||||
attributionControl: true,
|
||||
zoomControl: true,
|
||||
preferCanvas: true
|
||||
preferCanvas: true,
|
||||
renderer: L.canvas({ tolerance: 12 })
|
||||
});
|
||||
// Sensible default centre (mid-Switzerland) while the polyline
|
||||
// layer is built up; `fitBounds` below overrides it once the
|
||||
|
||||
@@ -372,6 +372,12 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if track && track.length > 0 && visibleImagePoints.length > 0}
|
||||
<section class="strip-area">
|
||||
<HikePhotoStrip images={visibleImagePoints} {track} />
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="metrics" aria-label="Tourendaten">
|
||||
{#if hike.icon}
|
||||
<img class="route-icon" src={hike.icon} alt="" aria-hidden="true" />
|
||||
@@ -445,12 +451,6 @@
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if track && track.length > 0 && visibleImagePoints.length > 0}
|
||||
<section class="strip-area">
|
||||
<HikePhotoStrip images={visibleImagePoints} {track} />
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="scroll-area">
|
||||
<aside class="trail-col">
|
||||
{#if track && track.length > 0}
|
||||
|
||||
Reference in New Issue
Block a user