f3d16d5187
Build pipeline (scripts/build-hikes.ts) parses per-hike GPX, encodes images via sharp, reverse-geocodes the centroid against Swisstopo and emits a typed manifest under src/lib/data/hikes.generated.ts (gitignored). Track JSON + image binaries live outside /static; served in dev by a small hike-images plugin in vite.config.ts, in prod by nginx (private/ images proxied through Node + X-Accel-Redirect for auth-gating). /hikes overview: full-bleed Swisstopo hero map (HikesOverviewMap) sits under the sticky nav, drawing one polyline per route coloured by SAC tier (T1 yellow Wegweiser, T2/T3 white-red-white, T4-T6 white-blue- white). Click navigates, hover thickens + tooltips. Layer toggle, recenter, GPS controls mirror the detail map (minus images toggle). Cards drop the trail SVG, gain a per-route icon + SAC marker pictogram on the cover, altitude range, season label, and "Neu" badge for recently-published hikes. Filter bar + totals strip recompute over the currently-visible set. /hikes/[slug]: hero map with elevation profile, photo strip with map sync, scroll-position pin, GPX download, SAC marker stats + min/max altitude + season. Route-builder (/hikes/route-builder): client-side draft persisted to localStorage, EXIF-driven image placement, snap-to-route via BRouter (OSRM + linear fallback) and Swisstopo profile.json elevation enrichment that handles degenerate same-coord segments via the height endpoint. Filter init switched from a script-time snapshot of data.hikes (which sporadically returned a one-hike subset during dev hydration and locked the page to that single hike) to a post-mount \$effect. Content under src/content/hikes/ intentionally not included (WIP).
75 lines
1.8 KiB
Svelte
75 lines
1.8 KiB
Svelte
<script lang="ts">
|
|
import Toggle from '$lib/components/Toggle.svelte';
|
|
|
|
interface Props {
|
|
enabled?: boolean;
|
|
}
|
|
|
|
let { enabled = $bindable(false) }: Props = $props();
|
|
|
|
const STORAGE_KEY = 'hikes:gpsEnabled';
|
|
|
|
let permissionError = $state<string | null>(null);
|
|
|
|
// Initialise from localStorage on mount (browser only).
|
|
$effect(() => {
|
|
if (typeof window === 'undefined') return;
|
|
const saved = window.localStorage.getItem(STORAGE_KEY);
|
|
if (saved === '1') enabled = true;
|
|
});
|
|
|
|
$effect(() => {
|
|
if (typeof window === 'undefined') return;
|
|
window.localStorage.setItem(STORAGE_KEY, enabled ? '1' : '0');
|
|
});
|
|
|
|
function onChange() {
|
|
if (!enabled) {
|
|
permissionError = null;
|
|
return;
|
|
}
|
|
// Light pre-flight: confirm the API exists. The actual permission grant
|
|
// happens lazily inside HikeMap so users see the marker appear immediately
|
|
// once they accept.
|
|
if (typeof window === 'undefined') return;
|
|
const hasTauri = '__TAURI_INTERNALS__' in window;
|
|
const hasWebGeo = 'geolocation' in navigator;
|
|
if (!hasTauri && !hasWebGeo) {
|
|
enabled = false;
|
|
permissionError = 'Geolocation steht in diesem Browser nicht zur Verfügung.';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="user-loc">
|
|
<Toggle bind:checked={enabled} label="Eigenen Standort auf der Karte anzeigen" onchange={onChange} />
|
|
<p class="hint">
|
|
Dein Standort wird auf deinem Gerät berechnet und nicht an Dritte gesendet.
|
|
</p>
|
|
{#if permissionError}
|
|
<p class="err">{permissionError}</p>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.user-loc {
|
|
margin-top: 1rem;
|
|
padding: 0.75rem 1rem;
|
|
background: var(--color-surface);
|
|
border-radius: var(--radius-md);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.hint {
|
|
margin: 0.4rem 0 0;
|
|
font-size: 0.8rem;
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
.err {
|
|
margin: 0.4rem 0 0;
|
|
font-size: 0.85rem;
|
|
color: var(--red);
|
|
}
|
|
</style>
|