(function (ns) { 'use strict'; const SETTINGS_KEY = 'wegwichtel.privacy.v1'; const PROGRESS_KEY = 'wegwichtel.route-progress.v1'; const COOKIE_PREFIX = 'wegwichtel_'; const MAX_PROGRESS_AGE_MS = 24 * 60 * 60 * 1000; const STORE_VERSION = 1; function storage() { try { return globalThis.localStorage ?? null; } catch { return null; } } function nowIso() { return new Date().toISOString(); } function readJson(key, fallback) { const backend = storage(); if (!backend) return fallback; try { const raw = backend.getItem(key); return raw ? JSON.parse(raw) : fallback; } catch { return fallback; } } function writeJson(key, value) { const backend = storage(); if (!backend) return false; try { backend.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } function removeItem(key) { const backend = storage(); if (!backend) return; try { backend.removeItem(key); } catch { // Private browsing modes may reject storage access. The app remains usable without persistence. } } function defaultSettings() { return { progressStorage: false, updatedAt: null }; } function settings() { const value = readJson(SETTINGS_KEY, defaultSettings()); return { progressStorage: value?.progressStorage === true, updatedAt: typeof value?.updatedAt === 'string' ? value.updatedAt : null }; } function saveSettings(progressStorage) { return writeJson(SETTINGS_KEY, { progressStorage: progressStorage === true, updatedAt: nowIso() }); } function defaultProgressStore() { return { version: STORE_VERSION, currentRouteId: null, routes: {} }; } function progressStore() { const value = readJson(PROGRESS_KEY, defaultProgressStore()); if (value?.version !== STORE_VERSION || typeof value?.routes !== 'object' || value.routes === null) { return defaultProgressStore(); } return { version: STORE_VERSION, currentRouteId: value.currentRouteId ?? null, routes: value.routes }; } function persistProgressStore(value) { if (!settings().progressStorage) return false; return writeJson(PROGRESS_KEY, value); } function uniqueIds(values) { return [...new Set((Array.isArray(values) ? values : []) .map(value => Number(value)) .filter(Number.isFinite))]; } function routeRecord(route, previous = {}) { const timestamp = nowIso(); return { routeId: route.id, routeName: String(route.name ?? previous.routeName ?? ''), routeState: previous.routeState ?? 'idle', routeProgressIndex: Number.isInteger(previous.routeProgressIndex) ? previous.routeProgressIndex : null, visitedPoiIds: uniqueIds(previous.visitedPoiIds), viewedPoiIds: uniqueIds(previous.viewedPoiIds), playedAudioPoiIds: uniqueIds(previous.playedAudioPoiIds), startedAt: previous.startedAt ?? timestamp, updatedAt: timestamp }; } function updateRoute(routeId, updater) { if (!settings().progressStorage || routeId == null) return null; const store = progressStore(); const key = String(routeId); const current = store.routes[key]; if (!current) return null; const updated = updater({ ...current }); updated.updatedAt = nowIso(); store.currentRouteId = routeId; store.routes[key] = updated; persistProgressStore(store); return updated; } function markPoi(routeId, field, poiId) { const normalizedId = Number(poiId); if (!Number.isFinite(normalizedId)) return null; return updateRoute(routeId, record => { record[field] = uniqueIds([...(record[field] ?? []), normalizedId]); return record; }); } function clearApplicationCookies() { const cookieText = globalThis.document?.cookie; if (!cookieText) return; cookieText.split(';').forEach(item => { const name = item.split('=', 1)[0].trim(); if (!name.startsWith(COOKIE_PREFIX)) return; globalThis.document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax`; }); } function setProgressStorageEnabled(enabled) { const active = enabled === true; const saved = saveSettings(active); if (!active) removeItem(PROGRESS_KEY); return saved && settings().progressStorage; } function activateRoute(route) { if (!settings().progressStorage || route?.id == null) return null; const store = progressStore(); const key = String(route.id); const record = routeRecord(route, store.routes[key]); store.currentRouteId = route.id; store.routes[key] = record; persistProgressStore(store); return record; } function saveRouteState(route, routeState, routeProgressIndex, visitedPoiIds, viewedPoiIds, playedAudioPoiIds) { if (route?.id == null) return null; return updateRoute(route.id, record => ({ ...record, routeName: String(route.name ?? record.routeName ?? ''), routeState: String(routeState ?? 'idle'), routeProgressIndex: Number.isInteger(routeProgressIndex) ? routeProgressIndex : null, visitedPoiIds: uniqueIds(visitedPoiIds), viewedPoiIds: uniqueIds(viewedPoiIds), playedAudioPoiIds: uniqueIds(playedAudioPoiIds) })); } function loadRoute(routeId) { if (!settings().progressStorage || routeId == null) return null; return progressStore().routes[String(routeId)] ?? null; } function nextTurn() { return new Promise(resolve => globalThis.setTimeout(resolve, 0)); } async function cleanupExpired(onProgress = () => {}) { if (!settings().progressStorage) { removeItem(PROGRESS_KEY); return { removed: 0, total: 0 }; } const store = progressStore(); const cutoff = Date.now() - MAX_PROGRESS_AGE_MS; const expiredKeys = Object.entries(store.routes) .filter(([, route]) => { const timestamp = Date.parse(route?.updatedAt ?? ''); return !Number.isFinite(timestamp) || timestamp <= cutoff; }) .map(([key]) => key); if (!expiredKeys.length) return { removed: 0, total: 0 }; let removed = 0; onProgress(removed, expiredKeys.length); for (const key of expiredKeys) { delete store.routes[key]; if (String(store.currentRouteId) === key) store.currentRouteId = null; removed += 1; persistProgressStore(store); onProgress(removed, expiredKeys.length); await nextTurn(); } return { removed, total: expiredKeys.length }; } clearApplicationCookies(); ns.PrivacyStorage = Object.freeze({ SETTINGS_KEY, PROGRESS_KEY, MAX_PROGRESS_AGE_MS, cookiesEnabled: () => false, clearApplicationCookies, isProgressStorageEnabled: () => settings().progressStorage, setProgressStorageEnabled, cleanupExpired, activateRoute, loadRoute, saveRouteState, markVisited: (routeId, poiId) => markPoi(routeId, 'visitedPoiIds', poiId), markViewed: (routeId, poiId) => markPoi(routeId, 'viewedPoiIds', poiId), markAudioPlayed: (routeId, poiId) => markPoi(routeId, 'playedAudioPoiIds', poiId), clearProgress: () => removeItem(PROGRESS_KEY) }); }(globalThis.Wegwichtel ??= {}));