// Shared utilities + small components const { useState, useEffect, useRef, useMemo, useCallback } = React; // ===================================================== // ImageSlot wrapper — React 18 + Babel-standalone doesn't reliably forward // the `src` attribute to custom elements. Set it via ref. // ===================================================== function ImgSlot({ id, src, placeholder, shape, mask, fit }) { const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; if (src) el.setAttribute("src", src); else el.removeAttribute("src"); }, [src]); return React.createElement("image-slot", { ref, id, placeholder, ...(shape ? { shape } : {}), ...(mask ? { mask } : {}), ...(fit ? { fit } : {}), }); } // ===================================================== // i18n hook // ===================================================== function useT(lang) { return useCallback((node) => { if (node == null) return ""; if (typeof node === "string" || typeof node === "number") return node; if (node[lang] != null) return node[lang]; if (node.en != null) return node.en; return ""; }, [lang]); } // ===================================================== // Reveal-on-scroll // ===================================================== function useReveal() { useEffect(() => { const els = document.querySelectorAll(".reveal"); if (!("IntersectionObserver" in window)) { els.forEach((e) => e.classList.add("in")); return; } const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add("in"); io.unobserve(e.target); } }); }, { threshold: 0.1, rootMargin: "0px 0px -40px 0px" }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }); } // ===================================================== // Wolf / Alpha mark — placeholder badge (NOT a recreation of any logo) // ===================================================== function AlphaMark({ size = 34 }) { return ( ); } // ===================================================== // Brand wordmark // ===================================================== function Brand({ onClick }) { return ( Alpha Gym
ALPHA
SPORTS CENTER
); } // ===================================================== // Language switch // ===================================================== function LangSwitch({ lang, setLang }) { return (
{["MK", "SQ", "EN"].map((c) => ( ))}
); } // ===================================================== // Nav // ===================================================== function Nav({ lang, setLang, route }) { const [scrolled, setScrolled] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const [active, setActive] = useState(""); const t = useT(lang); const T = window.I18N; useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 24); onScroll(); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); const sectionIds = ["about", "facilities", "programs", "pricing", "trainers", "contact"]; // Highlight the section currently in view (only on the home route). useEffect(() => { if (route.route !== "home") { setActive(""); return; } const els = sectionIds.map((id) => document.getElementById(id)).filter(Boolean); if (!els.length || !("IntersectionObserver" in window)) return; const io = new IntersectionObserver((entries) => { entries.forEach((e) => { if (e.isIntersecting) setActive(e.target.id); }); }, { rootMargin: "-40% 0px -55% 0px", threshold: 0 }); els.forEach((el) => io.observe(el)); return () => io.disconnect(); }, [route.route]); const links = [ { id: "about", label: t(T.nav.about) }, { id: "facilities", label: t(T.nav.facilities) }, { id: "programs", label: t(T.nav.programs) }, { id: "pricing", label: t(T.nav.pricing) }, { id: "trainers", label: t(T.nav.trainers) }, { id: "contact", label: t(T.nav.contact) }, ]; // Click handler: if we're already on the home route AND on the same section, // re-trigger the scroll (since hashchange won't fire). const goSection = (id) => (e) => { setMenuOpen(false); if (route.route === "home" && (window.location.hash || "").endsWith("/" + id)) { e.preventDefault(); const el = document.getElementById(id); if (el) { const navH = document.querySelector(".nav").offsetHeight || 80; window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - (navH + 8), behavior: "smooth" }); } } }; const goHome = () => { setMenuOpen(false); }; return (
{links.map((l) => ( {l.label} ))}
{t(T.nav.join)} →
); } // ===================================================== // WhatsApp floating button // ===================================================== function WhatsAppFAB({ number = "+38970123456", text = "Hello, I'm interested in Alpha Gym." }) { const num = number.replace(/[^0-9]/g, ""); const url = `https://wa.me/${num}?text=${encodeURIComponent(text)}`; return ( ); } // ===================================================== // Footer // ===================================================== function Footer({ lang }) { const t = useT(lang); const T = window.I18N; return ( ); } // expose Object.assign(window, { useT, useReveal, AlphaMark, Brand, LangSwitch, Nav, WhatsAppFAB, Footer, ImgSlot });