"use strict"; // Shared cross-view navigation + last-location memory. Loaded on all three pages // (map, calendar, day) before that page's own script. It remembers the most // recently selected spot in localStorage and keeps the header's view links // (Map · Calendar · Day) pointing at that spot — so switching views, or coming // back to the map, lands you where you were instead of an empty US-wide map. const LOC_KEY = "thermograph:loc"; function readStored() { try { const o = JSON.parse(localStorage.getItem(LOC_KEY)); if (o && typeof o.lat === "number" && typeof o.lon === "number") return o; } catch (e) {} return null; } // Where should this page start? A hash (a shared/opened link) always wins; // otherwise fall back to the last place the user looked at. function loadLastLocation() { const p = new URLSearchParams(location.hash.slice(1)); const lat = parseFloat(p.get("lat")), lon = parseFloat(p.get("lon")); if (!isNaN(lat) && !isNaN(lon)) return { lat, lon, date: p.get("date") || null }; return readStored(); } // Remember a spot. Passing no `date` preserves whatever date was stored before, // so hopping through the calendar (which has no date) doesn't wipe the day you // were on elsewhere. function saveLastLocation(lat, lon, date) { let d = date; if (d === undefined) { const prev = readStored(); d = prev ? prev.date : null; } try { localStorage.setItem(LOC_KEY, JSON.stringify({ lat, lon, date: d || null })); } catch (e) {} refreshNav(lat, lon, d); } function locHash(lat, lon, date) { let h = `#lat=${lat.toFixed(5)}&lon=${lon.toFixed(5)}`; if (date) h += `&date=${date}`; return h; } // Point every header view-link at the current spot so a tap keeps your place. function refreshNav(lat, lon, date) { if (lat == null || lon == null) return; const h = locHash(lat, lon, date); document.querySelectorAll(".view-nav a[data-view]").forEach((a) => { a.href = (a.dataset.base || "") + h; }); } (function initNav() { document.querySelectorAll(".view-nav a[data-view]").forEach((a) => { a.dataset.base = a.getAttribute("href"); }); const loc = loadLastLocation(); if (loc) refreshNav(loc.lat, loc.lon, loc.date); })(); // ---- shared response cache + cross-view prefetch ---- // Cached API JSON lets the three views (a full page load each) reuse data instead // of refetching, and the server caches upstream so a client miss is still cheap. // The calendar cache is *persistent* (localStorage) so a page refresh — or coming // back to a location viewed earlier — repaints instantly instead of regrading. const TTL = { grade: 600000, forecast: 1800000, calendar: 21600000, day: 600000 }; // calendar: 6h // Evict the oldest N "tg:" entries from a Storage (ordered by stored timestamp) to // make room when it hits quota. function evictOldestCache(store, n) { const items = []; for (let i = 0; i < store.length; i++) { const k = store.key(i); if (!k || !k.startsWith("tg:")) continue; let t = 0; try { t = JSON.parse(store.getItem(k)).t || 0; } catch (e) {} items.push([k, t]); } items.sort((a, b) => a[1] - b[1]); for (let i = 0; i < Math.min(n, items.length); i++) store.removeItem(items[i][0]); } function putCache(store, key, s) { try { store.setItem(key, s); } catch (e) { evictOldestCache(store, 8); try { store.setItem(key, s); } catch (e2) {} } } // `persist` picks localStorage (survives tab close / navigating back to a prior // spot); otherwise sessionStorage (per-tab). The read TTL governs freshness. async function getJSON(url, ttlMs = 600000, persist = false) { const store = persist ? localStorage : sessionStorage; const key = "tg:" + url; try { const raw = store.getItem(key); if (raw) { const o = JSON.parse(raw); if (Date.now() - o.t < ttlMs) return o.d; } } catch (e) {} const res = await fetch(url); const d = await res.json(); if (!res.ok) { const err = new Error(d.detail || "request failed"); err.status = res.status; throw err; } try { const s = JSON.stringify({ t: Date.now(), d }); if (s.length < 1500000) putCache(store, key, s); // skip caching very large payloads } catch (e) {} return d; } // After the landing page's own data is up, warm the OTHER two views (server- and // session-cached, so cheap) so switching to them is instant. Deduped per spot. let _prefetched = ""; function prefetchViews(lat, lon, skip) { const tag = `${lat.toFixed(4)},${lon.toFixed(4)},${skip}`; if (_prefetched === tag) return; _prefetched = tag; const q = `lat=${lat}&lon=${lon}`; const warm = () => { if (skip !== "grade") getJSON(`api/grade?${q}`, TTL.grade).catch(() => {}); if (skip !== "forecast") getJSON(`api/v2/forecast?${q}`, TTL.forecast).catch(() => {}); if (skip !== "calendar") getJSON(`api/v2/calendar?${q}&months=24`, TTL.calendar, true).catch(() => {}); }; // Yield first so the landing view finishes rendering before we fetch the rest. setTimeout(warm, 350); } window.Thermograph = { loadLastLocation, saveLastLocation, getJSON, prefetchViews, TTL };