// Shared nav, footer, helper components for Janel Safaris const { useState, useEffect, useRef } = React; // — Tiny icon set (minimal stroke, no slop) const Ico = { Arrow: (p) => , ArrowDown: (p) => , Plus: (p) => , Minus: (p) => , Star: (p) => , Map: (p) => , Clock: (p) => , Users: (p) => , Shield: (p) => , Compass: (p) => , Leaf: (p) => , Phone: (p) => , Mail: (p) => , Wa: (p) => , Search: (p) => , Menu: (p) => , X: (p) => , Mountain: (p) => , Beach: (p) => , Binoc: (p) => , Heart: (p) => , Check: (p) => , }; // Logo — wordmark with thin diamond mark function Logo({ light }) { const c = light ? '#f4efe6' : '#1a1814'; return ( Janel Safaris ); } function Nav({ variant = 'transparent', current }) { const [scrolled, setScrolled] = useState(false); const [megaOpen, setMegaOpen] = useState(null); const [mobOpen, setMobOpen] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 30); onScroll(); window.addEventListener('scroll', onScroll); return () => window.removeEventListener('scroll', onScroll); }, []); const isLight = variant === 'transparent' && !scrolled; const navStyle = { position: 'fixed', top: 0, left: 0, right: 0, zIndex: 100, transition: 'all 0.35s cubic-bezier(0.2,0.6,0.2,1)', background: isLight ? 'transparent' : 'rgba(244, 239, 230, 0.92)', backdropFilter: scrolled ? 'blur(14px) saturate(1.2)' : 'none', color: isLight ? '#f4efe6' : '#1a1814', borderBottom: scrolled ? '1px solid rgba(26,24,20,0.08)' : '1px solid transparent', }; const links = [ { name: 'Safaris', mega: 'safaris', href: 'safaris.html' }, { name: 'Kilimanjaro', href: 'kilimanjaro.html' }, { name: 'Zanzibar', href: 'zanzibar.html' }, { name: 'Destinations', mega: 'dest', href: '#' }, { name: 'Journal', href: '#' }, { name: 'About', href: '#' }, ]; return ( <>
Plan your safari
{/* Mega menu */} {megaOpen && (
setMegaOpen(megaOpen)} onMouseLeave={() => setMegaOpen(null)} style={{ position: 'absolute', left: 0, right: 0, top: 76, background: 'rgba(244,239,230,0.98)', backdropFilter: 'blur(20px)', color: 'var(--ink)', borderTop: '1px solid var(--line-soft)', borderBottom: '1px solid var(--line-soft)', padding: '40px 0', }}>
{megaOpen === 'safaris' ? <> : <> }
Featured
Great Migration · 2026 calendar
See month-by-month guide
)}
{/* Mobile drawer */} {mobOpen && (
)} ); } function MegaCol({ title, items }) { return (
{title}
); } // — Sticky inquiry / WhatsApp dock function StickyDock() { return (
); } function Footer() { return ( ); } function FooterCol({ title, items }) { return (
{title}
); } // — Animated counter function Counter({ to, suffix = '', duration = 1400 }) { const [n, setN] = useState(0); const ref = useRef(null); useEffect(() => { const el = ref.current; if (!el) return; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { const start = performance.now(); const tick = (now) => { const t = Math.min(1, (now - start) / duration); const eased = 1 - Math.pow(1 - t, 3); setN(Math.round(eased * to)); if (t < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); io.disconnect(); } }, { threshold: 0.4 }); io.observe(el); return () => io.disconnect(); }, [to, duration]); return {n}{suffix}; } // Reveal on scroll wrapper function Reveal({ children, delay = 0 }) { const ref = useRef(null); useEffect(() => { const el = ref.current; const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) setTimeout(() => el.classList.add('in'), delay); }, { threshold: 0.15 }); io.observe(el); return () => io.disconnect(); }, [delay]); return
{children}
; } Object.assign(window, { Ico, Logo, Nav, Footer, StickyDock, Counter, Reveal });