// site-shell.jsx — hash router that ties the public site together. // ── Stable visitor / session IDs ────────────────────────────────────────── function getOrCreate(key, storage, ttlDays) { try { const raw = storage.getItem(key); if (raw) { const obj = JSON.parse(raw); if (!obj.exp || Date.now() < obj.exp) return obj.id; } const id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); const exp = ttlDays ? Date.now() + ttlDays * 864e5 : null; storage.setItem(key, JSON.stringify({ id, exp })); return id; } catch (_) { return 'anon'; } } function getVisitorIds() { const visitor_id = getOrCreate('ljmu_vid', localStorage, 90); // 90-day persistent const session_id = getOrCreate('ljmu_sid', sessionStorage, null); // browser-session return { visitor_id, session_id }; } function useRoute() { const [route, setRoute] = React.useState(() => window.location.hash.slice(1) || "/"); const track = (page) => { const ids = getVisitorIds(); fetch((window.__API_BASE__ || "/api") + "/track.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ page, ...ids }), }).catch(() => {}); }; React.useEffect(() => { track(window.location.hash.slice(1) || "/"); const h = () => { const page = window.location.hash.slice(1) || "/"; setRoute(page); track(page); }; window.addEventListener("hashchange", h); return () => window.removeEventListener("hashchange", h); }, []); const navigate = React.useCallback((to) => { if (("#" + to) === window.location.hash) return; window.location.hash = to; window.scrollTo({ top: 0, behavior: "auto" }); }, []); return [route, navigate]; } function SiteShell() { const [route, navigate] = useRoute(); const d = useSite(); const seg = route.split("/").filter(Boolean); const top = seg[0] || "home"; const awardsLive = !!(d.festival && d.festival.awardsPage && d.festival.awardsPage.live); React.useEffect(() => { window.scrollTo({ top: 0 }); }, [route]); let page; if (!top || top === "home") page = ; else if (top === "programme") page = ; else if (top === "film") page = ; else if (top === "about") page = ; else if (top === "jury") page = ; else if (top === "archive") page = ; else if (top === "news") page = seg[1] ?
: ; else if (top === "tickets") page = ; else if (top === "submit") page = ; else if (top === "contact") page = ; else if (top === "awards") page = awardsLive ? : ; else page = ; return ( <>