/* ============================================================= * Shared components: Navbar, Footer, Buttons, Placeholders, Hooks * ============================================================= */ const { useState, useEffect, useRef, useCallback, useMemo } = React; /* ---------- Routing (hash based, lightweight) ---------- */ function useHashRoute() { const [route, setRoute] = useState(() => window.location.hash.replace(/^#\/?/, "") || "home"); useEffect(() => { const onHash = () => { const r = window.location.hash.replace(/^#\/?/, "") || "home"; setRoute(r); window.scrollTo({ top: 0, behavior: "instant" in window ? "instant" : "auto" }); }; window.addEventListener("hashchange", onHash); return () => window.removeEventListener("hashchange", onHash); }, []); return [route, (r) => {window.location.hash = "/" + r;}]; } function Link({ to, children, className = "", onClick, ...rest }) { const handleClick = (e) => { e.preventDefault(); if (onClick) onClick(e); window.location.hash = "/" + to; }; return ( {children} ); } /* ---------- IntersectionObserver hook for in-view animations ---------- */ function useInView(options = { threshold: 0.25, rootMargin: "0px 0px -10% 0px" }) { const ref = useRef(null); const [inView, setInView] = useState(false); useEffect(() => { if (!ref.current) return; const io = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) {setInView(true);io.disconnect();} }, options); io.observe(ref.current); return () => io.disconnect(); }, []); return [ref, inView]; } /* ---------- Animated counter ---------- */ function CountUp({ to = 0, duration = 1400, suffix = "", className = "" }) { const [ref, inView] = useInView(); const [val, setVal] = useState(0); useEffect(() => { if (!inView) return; const start = performance.now(); let raf; const tick = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setVal(Math.round(eased * to)); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [inView, to, duration]); return {val.toLocaleString()}{suffix}; } /* ---------- Logo ---------- */ function Logo({ className = "h-10 w-auto" }) { const src = typeof window !== "undefined" && window.__resources && window.__resources.logo || "assets/logo.png"; return Fathers Arise; } /* ---------- Buttons ---------- */ function Button({ as = "button", variant = "navy", size = "md", className = "", children, to, ...rest }) { const sizes = { sm: "px-4 py-2 text-sm", md: "px-6 py-3 text-[15px]", lg: "px-8 py-4 text-base" }; const variants = { gold: "btn-gold", sky: "btn-sky", navy: "btn-navy", ghost: "bg-white/10 text-white border border-white/30 hover:bg-white/20", outline: "bg-white text-navy border border-navy/15 hover:border-navy/40" }; const cls = `inline-flex items-center justify-center gap-2 font-display font-bold rounded-full transition-all uppercase tracking-wide ${sizes[size]} ${variants[variant]} ${className}`; if (to) return {children}; const Tag = as; return {children}; } /* ---------- Placeholder image (striped, with caption) ---------- */ function Placeholder({ label = "image", className = "", tone = "navy", ratio = "16/9" }) { const tones = { navy: "ph-stripes", warm: "ph-stripes-warm", sky: "ph-stripes-sky", dark: "ph-stripes-dark text-white/80" }; return (
{label}
); } /* ---------- Top utility bar ---------- */ function UtilityBar() { const [lang, setLang] = useLang(); // Apply selected language to Google Translate (with retry until the widget is ready) const applyGoogleLang = (target) => { let tries = 0; const attempt = () => { const ok = typeof window.faSetGoogleLang === "function" && window.faSetGoogleLang(target); if (!ok && tries++ < 30) setTimeout(attempt, 250); }; attempt(); }; const choose = (next) => { setLang(next); // updates locally-translated strings (navbar etc.) applyGoogleLang(next); // triggers Google Translate for the rest of the page }; // On first mount, restore the saved language in the Google Translate widget too useEffect(() => { applyGoogleLang(lang); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
{/* Contact info removed */}
{t('restore.cta', lang)}
| |
); } /* ---------- Navbar ---------- */ function Navbar({ route }) { const [lang] = useLang(); const [open, setOpen] = useState(false); const [progOpen, setProgOpen] = useState(false); const [scrolled, setScrolled] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 12); window.addEventListener("scroll", onScroll, { passive: true }); return () => window.removeEventListener("scroll", onScroll); }, []); const navItems = [ { label: t('nav.about', lang), to: "about" }, { label: "Be A Man", to: "be-a-man" }, { label: "Nurturing Bonds", to: "nurturing-bonds" }, { label: "Mobile Kanaabe", to: "mobile-kanaabe" }, { label: "Champion Virtues", to: "champion-virtues" }, { label: t('nav.stories', lang), to: "stories" }, { label: t('nav.getInvolved', lang), to: "get-involved" }, { label: t('nav.contact', lang), to: "contact" }]; const programsMenu = [ { title: t('programs.beAMan', lang), desc: t('programs.beAMan.desc', lang), to: "be-a-man" }, { title: t('programs.nurturingBonds', lang), desc: t('programs.nurturingBonds.desc', lang), to: "nurturing-bonds" }, { title: t('programs.mobileKanaabe', lang), desc: t('programs.mobileKanaabe.desc', lang), to: "mobile-kanaabe" } ]; const isActive = (to) => route === to || to === "programs" && ["be-a-man", "nurturing-bonds", "mobile-kanaabe"].includes(route); return ( <>
{open &&
{navItems.map((it) => setOpen(false)} className={`block px-3 py-3 rounded-lg font-display font-semibold ${isActive(it.to) ? "bg-paper text-navy" : "text-ink/80"}`}> {it.label} )} setOpen(false)} className="block mt-2 text-center btn-gold rounded-full px-6 py-3 font-display font-bold uppercase">Donate
}
); } /* ---------- Footer ---------- */ function Footer() { return ( ); } function SocialIcon({ name }) { const props = { width: 16, height: 16, viewBox: "0 0 24 24", fill: "currentColor" }; switch (name) { case "facebook":return ; case "instagram":return ; case "youtube":return ; case "x":return ; case "whatsapp":return ; default:return null; } } /* ---------- Floating WhatsApp ---------- */ function WhatsAppFloat() { return ( ); } /* ---------- Section title ---------- */ function SectionTitle({ eyebrow, title, lede, light = false, center = false, className = "" }) { return (
{eyebrow &&
{eyebrow}
}

{title}

{lede &&

{lede}

}
); } /* ---------- Page hero (sub-page header) ---------- */ function PageHero({ eyebrow, title, lede, breadcrumb, bgImage }) { return (
{bgImage ? ( <>
) : ( <>
)}
{breadcrumb &&
{breadcrumb.map((b, i) => {i > 0 && /} {b.to ? {b.label} : {b.label}} )}
} {eyebrow &&
{eyebrow}
}

{title}

{lede &&

{lede}

}
); } /* ---------- Field components for forms ---------- */ function Field({ label, children, hint, error, id }) { return ( ); } function Input(props) { return ( ); } function Textarea(props) { return (