/* global React */
const { useState, useEffect, useRef } = React;
// ============ ICONS ============
const Icon = {
Book: () => ,
Shield: () => ,
Sun: () => ,
Crown: () => ,
People: () => ,
Link: () => ,
Cross: () => ,
Pin: () => ,
Clock: () => ,
Mail: () => ,
Phone: () => ,
Caret: () => ,
Play: () => ,
};
// ============ NAVIGATION CONFIG ============
const NAV = [
{ id: 'home', label: 'Home' },
{ id: 'about', label: 'About', children: [
{ id: 'about-us', label: 'About Us' },
{ id: 'beliefs', label: 'What We Believe' },
{ id: 'leadership', label: 'Leadership' },
{ id: 'new-here', label: 'New Here' },
]},
{ id: 'connect', label: 'Connect', children: [
{ id: 'visit', label: 'Visit Us' },
{ id: 'prayer', label: 'Prayer / Connect' },
{ id: 'contact', label: 'Contact' },
]},
{ id: 'grow', label: 'Grow', children: [
{ id: 'ministries', label: 'Ministries' },
{ id: 'sermons', label: 'Sermons / Teaching' },
{ id: 'events', label: 'Events' },
{ id: 'gallery', label: 'Photo Gallery' },
]},
{ id: 'give', label: 'Give' },
];
// flat lookup of all routes
const ROUTES = (() => {
const r = {};
NAV.forEach(n => {
r[n.id] = { parent: n.id, label: n.label };
if (n.children) n.children.forEach(c => { r[c.id] = { parent: n.id, label: c.label, parentLabel: n.label }; });
});
return r;
})();
// ============ ROUTER (hash-based) ============
function useRoute() {
const [route, setRoute] = useState(() => (location.hash.slice(1) || 'home'));
useEffect(() => {
const onHash = () => {
setRoute(location.hash.slice(1) || 'home');
window.scrollTo({ top: 0, behavior: 'instant' });
};
window.addEventListener('hashchange', onHash);
return () => window.removeEventListener('hashchange', onHash);
}, []);
return route;
}
function nav(to) { location.hash = '#' + to; }
// ============ NAV BAR ============
function Topbar() {
return (
815 Private Road 4717, Kempner, TX 76539
Sundays · 10:00 AM
hello@brokenbarriers.church
);
}
function NavBar({ route }) {
const parent = ROUTES[route]?.parent || 'home';
const [openMenu, setOpenMenu] = useState(null);
const [mobileOpen, setMobileOpen] = useState(false);
const wrapRef = useRef(null);
useEffect(() => { setMobileOpen(false); }, [route]);
useEffect(() => {
document.body.classList.toggle('nav-locked', mobileOpen);
const onKey = e => { if (e.key === 'Escape') setMobileOpen(false); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [mobileOpen]);
useEffect(() => {
const onClick = e => {
if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpenMenu(null);
};
document.addEventListener('click', onClick);
return () => document.removeEventListener('click', onClick);
}, []);
return (
);
}
// ============ FOOTER ============
function Footer() {
return (
<>
>
);
}
// ============ SCROLL REVEAL ============
function useScrollReveal(route) {
useEffect(() => {
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
e.target.classList.add('in');
io.unobserve(e.target);
}
});
}, { threshold: 0.05, rootMargin: '0px 0px -10px 0px' });
const observeAll = () => {
document.querySelectorAll('.reveal:not(.in)').forEach((el, i) => {
if (!el.style.getPropertyValue('--reveal-delay')) {
el.style.setProperty('--reveal-delay', Math.min(i * 60, 360) + 'ms');
}
io.observe(el);
});
};
// initial observe shortly after React paints
const t1 = setTimeout(observeAll, 30);
// mutation observer — pick up reveal elements added after initial paint
const mo = new MutationObserver(() => observeAll());
mo.observe(document.body, { childList: true, subtree: true });
// safety net — after 4s, force-reveal anything still stuck (handles slow
// scrolls, fast scrolls past, or any quirk where IO didn't fire)
const t2 = setTimeout(() => {
document.querySelectorAll('.reveal:not(.in)').forEach(el => el.classList.add('in'));
}, 4000);
return () => {
clearTimeout(t1);
clearTimeout(t2);
mo.disconnect();
io.disconnect();
};
}, [route]);
}
Object.assign(window, { Icon, NAV, ROUTES, useRoute, nav, Topbar, NavBar, Footer, useScrollReveal });