/* App shell + router (Bastion-styled) */ const { useState, useEffect, useRef, useMemo, useCallback } = React; const ROUTES = [ { id: "home", label: "首頁", en: "INDEX" }, { id: "courses", label: "課程", en: "PROGRAMS" }, { id: "schedule", label: "課表", en: "SCHEDULE" }, { id: "pricing", label: "費用", en: "MEMBERSHIP"}, { id: "about", label: "關於", en: "MANIFESTO" }, { id: "contact", label: "聯絡", en: "CONTACT" }, ]; function useHashRoute() { const [route, setRoute] = useState(() => (window.location.hash.slice(1) || "home")); useEffect(() => { const onHash = () => { setRoute(window.location.hash.slice(1) || "home"); window.scrollTo({ top: 0, behavior: "instant" }); }; window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, []); const navigate = useCallback((id) => { window.location.hash = id; }, []); return [route, navigate]; } // ============ SHIELD MONOGRAM ============ function Shield({ size = 40 }) { // Hand-drawn shield monogram for "AF" — Attack First return ( ); } // ============ TWEAKS ============ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "gold": "#a8804f", "displayFont": "vanguard" }/*EDITMODE-END*/; function applyTweaks(t) { const root = document.documentElement; root.style.setProperty("--gold", t.gold); // soft variants derived from primary const hex = t.gold.replace("#", ""); const r = parseInt(hex.slice(0, 2), 16), g = parseInt(hex.slice(2, 4), 16), b = parseInt(hex.slice(4, 6), 16); root.style.setProperty("--gold-soft", `rgba(${r},${g},${b},0.5)`); root.style.setProperty("--gold-mute", `rgba(${r},${g},${b},0.3)`); root.style.setProperty("--gold-faint", `rgba(${r},${g},${b},0.12)`); root.style.setProperty("--hairline", t.gold); root.style.setProperty("--hairline-soft", `rgba(${r},${g},${b},0.25)`); if (t.displayFont === "vanguard") { root.style.setProperty("--display", '"Vanguard", "Bebas Neue", "Oswald", "Archivo Narrow", "Noto Sans TC", sans-serif'); } else if (t.displayFont === "bebas") { root.style.setProperty("--display", '"Bebas Neue", "Noto Sans TC", sans-serif'); } else if (t.displayFont === "oswald") { root.style.setProperty("--display", '"Oswald", "Noto Sans TC", sans-serif'); } else if (t.displayFont === "anton") { root.style.setProperty("--display", '"Anton", "Noto Sans TC", sans-serif'); } } function TweaksApp() { const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS); useEffect(() => { applyTweaks(tweaks); }, [tweaks]); return ( setTweak("gold", v)} options={["#a8804f", "#c89860", "#8a6b3f", "#b89968", "#9a7a5a"]} /> setTweak("displayFont", v)} options={[ { value: "vanguard", label: "Vanguard / Narrow (default)" }, { value: "bebas", label: "Bebas Neue" }, { value: "oswald", label: "Oswald" }, { value: "anton", label: "Anton" }, ]} /> ); } // ============ NAV ============ function Nav({ route, navigate, menuOpen, setMenuOpen }) { return ( ); } function MenuOverlay({ open, route, navigate, close }) { return (
{ROUTES.map((r, i) => ( { e.preventDefault(); navigate(r.id); close(); }}> 0{i + 1} / {r.en} {r.label} ))}
ATTACK FIRST CYCLING · 國王單車俱樂部 · EST 2026 台中 · 北屯 · 旱溪東路三段 20 號
); } // ============ FOOTER ============ function Footer({ navigate }) { return ( ); } // ============ MAIN APP ============ function App() { const [route, navigate] = useHashRoute(); const [signupOpen, setSignupOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false); // Close overlay on route change useEffect(() => { setMenuOpen(false); }, [route]); const Page = useMemo(() => { switch (route) { case "courses": return setSignupOpen(true)} />; case "schedule": return setSignupOpen(true)} />; case "pricing": return setSignupOpen(true)} />; case "about": return ; case "contact": return setSignupOpen(true)} />; default: return setSignupOpen(true)} />; } }, [route, navigate]); return ( <>