// Main App with hash routing + Tweaks panel const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA } = React; // Parse window.location.hash into a route. // Supported shapes (any leading "#" is fine, with or without a slash): // "" → home, no section // "#/" → home, no section // "#/about" → home, section "about" // "#about" → home, section "about" (legacy) // "#/#about" → home, section "about" (legacy) // "#/trainer/:slug" → trainer detail function parseHash() { const raw = (window.location.hash || "").replace(/^#/, ""); // strip the one leading # if (raw.startsWith("/trainer/")) { return { route: "trainer", slug: raw.split("/")[2] || "", section: null }; } // Tolerate /#about, /about, #about, about — anything ending with letters wins. const m = raw.match(/([a-z]+)$/i); return { route: "home", slug: null, section: m ? m[1].toLowerCase() : null }; } // Smoothly scroll to a section id, accounting for the fixed nav. function scrollToSection(id) { if (!id) return; const el = document.getElementById(id); if (!el) return; const navH = (document.querySelector(".nav") || {}).offsetHeight || 80; const top = el.getBoundingClientRect().top + window.scrollY - (navH + 8); window.scrollTo({ top, behavior: "smooth" }); } function App() { const [route, setRoute] = useStateA(parseHash()); // Track the last section we scrolled to so clicking the same link twice // (or clicking a section while already on home) re-scrolls. const lastScrolled = React.useRef(null); useEffectA(() => { const apply = () => { const next = parseHash(); setRoute(next); if (next.route === "home" && next.section) { // Wait one frame so the section is in the DOM after a route change. setTimeout(() => scrollToSection(next.section), 80); lastScrolled.current = next.section; } else if (next.route === "home" && !next.section) { window.scrollTo({ top: 0, behavior: "smooth" }); lastScrolled.current = null; } }; // Run once for any initial hash (e.g. opening "/#pricing" directly). apply(); window.addEventListener("hashchange", apply); return () => window.removeEventListener("hashchange", apply); }, []); // Tweaks const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#d4a437", "headlineMode": "stencil", "showWaFab": true, "language": "en" }/*EDITMODE-END*/; const tweaks = useTweaks(TWEAK_DEFAULTS); const [tw, setTweak] = tweaks; const [lang, setLang] = useStateA(tw.language || "en"); useEffectA(() => { setLang(tw.language || "en"); }, [tw.language]); const onSetLang = (l) => { setLang(l); setTweak("language", l); }; // Apply accent color useEffectA(() => { document.documentElement.style.setProperty("--gold", tw.accent); // derive a slightly deeper variant document.documentElement.style.setProperty("--gold-2", tw.accent); document.documentElement.style.setProperty("--gold-tint", tw.accent + "20"); }, [tw.accent]); // Apply headline mode (stencil = stroke variant on certain words; solid = filled) useEffectA(() => { document.body.dataset.headline = tw.headlineMode; }, [tw.headlineMode]); // Reveal observer (refresh on route change) useReveal(); return (