/* CrossCurrent Racing — shared site shell. Exports nav / footer / hero / discord beacon / band helpers + site data onto window so every page (each its own Babel scope) can use them. */ (function () { const D = window.CrossCurrentRacingDesignSystem_c4f6ec; const { CurrentStripe, Button, TextLink, Tag } = D; const { useState, useEffect, useRef } = React; const LOGO = "assets/logos/crosscurrent-logo.jpg"; const DISCORD = "https://discord.gg/RUucJh26u7"; const GUILD_ID = "1478477879926067335"; /* ---- Global navigation (real hyperlinks between pages) ---- */ const PAGES = [ { label: "Home", href: "index.html" }, { label: "About", href: "about.html" }, { label: "Drivers", href: "drivers.html" }, { label: "Races", href: "races.html" }, { label: "Results", href: "results.html" }, { label: "Gallery", href: "gallery.html" }, { label: "Apps", href: "apps.html" }, { label: "Contact", href: "contact.html" }, ]; /* ---- Shared Discord auth (real OAuth2 via /api/auth/*, see HANDOFF.md seam 1) ---- Identity/permissions live server-side (signed httpOnly session cookie + a live Discord role check on every /api/auth/me call, so a role change in Discord takes effect immediately without forcing a re-login). The client just mirrors whatever the backend reports; there is nothing to trust on this side. */ const SIGNED_OUT_AUTH = { authenticated: false, name: null, isMember: false, isManager: false, isAdmin: false, canManageRaces: false }; let authFetchPromise = null; // de-dupe: pages that call useAuth() more than once share one request function fetchAuth() { if (!authFetchPromise) { authFetchPromise = fetch("/api/auth/me", { credentials: "include" }) .then((r) => (r.ok ? r.json() : SIGNED_OUT_AUTH)) .catch(() => SIGNED_OUT_AUTH) .finally(() => { authFetchPromise = null; }); } return authFetchPromise; } function useAuth() { const [state, setState] = useState(SIGNED_OUT_AUTH); useEffect(() => { let alive = true; const load = () => fetchAuth().then((d) => { if (alive) setState(d); }); load(); window.addEventListener("cc-auth-change", load); return () => { alive = false; window.removeEventListener("cc-auth-change", load); }; }, []); return { auth: state.authenticated ? { name: state.name } : null, isMember: state.isMember, isManager: state.isManager, isAdmin: state.isAdmin, canManageRaces: state.canManageRaces, signIn: () => { window.location.href = "/api/auth/discord/login?returnTo=" + encodeURIComponent(location.pathname); }, signOut: () => { fetch("/api/auth/logout", { method: "POST", credentials: "include" }) .finally(() => window.dispatchEvent(new CustomEvent("cc-auth-change"))); }, }; } /* ---- Shared data store (races/results/drivers/tracks/stats, see HANDOFF.md seam 2). Reads go through /api/data/all so all visitors see the same admin-edited content; falls back to the static data.js constants if the backend is briefly unreachable, so the site still renders something. ---- */ function liveFallback() { const D = window.CCData || {}; return { roster: D.ROSTER || [], calendar: D.CALENDAR || [], results: D.RESULTS || [], tracks: D.TRACKS || [], stats: { racesEntered: "0", enduranceKm: "0" }, }; } let liveDataPromise = null; function fetchLiveData() { if (!liveDataPromise) { liveDataPromise = fetch("/api/data/all") .then((r) => (r.ok ? r.json() : null)) .catch(() => null) .finally(() => { liveDataPromise = null; }); } return liveDataPromise; } function useLiveData() { const [state, setState] = useState(liveFallback); const [loading, setLoading] = useState(true); const load = () => { setLoading(true); fetchLiveData().then((d) => { if (d) setState(d); setLoading(false); }); }; useEffect(() => { load(); }, []); return { ...state, loading, refresh: load }; } function navAuthBtn(primary) { return { font: "var(--type-body-sm)", fontSize: "12px", letterSpacing: "1.5px", textTransform: "uppercase", cursor: "pointer", padding: "8px 14px", border: "1px solid " + (primary ? "var(--cc-red)" : "var(--cc-hairline)"), background: primary ? "var(--cc-red)" : "transparent", color: primary ? "#fff" : "var(--cc-body)", }; } function AuthModal({ onClose, onSignIn }) { const mbtn = { font: "var(--type-body-sm)", fontWeight: 700, fontSize: "13px", letterSpacing: "1.5px", textTransform: "uppercase", cursor: "pointer", padding: "14px 18px", border: "none", background: "var(--cc-red)", color: "#fff", textAlign: "left", }; return (
We check your role in the CrossCurrent Discord to unlock members-only tools.
{subtitle}
} {meta &&