/* Site header for every Kgotla page: the drawn 88px desktop row, plus the hamburger, slide-in drawer and scroll-away compact bar that take over below 1024px. One component so all five page types share one behaviour. Loaded before chrome.jsx / parts-hero.jsx, so it keeps its own constants. */ const NAVBRAND = "assets/brand/"; const NAV_LINKS = [ { label: "Services", href: "Kgotla Services.html" }, { label: "Impact", href: "Kgotla Coming Soon.html" }, { label: "Books", href: "Kgotla Books.html" }, { label: "Insights", href: "Kgotla Coming Soon.html" }, { label: "About us", href: "Kgotla About.html" }, { label: "Contact", href: "Kgotla Contact.html" }, ]; /* Which nav item is the page we are on — Books also covers the 8 book pages, Services covers the boats pages reached from its Locations cards. */ const NAV_FAMILY = { "Kgotla Services.html": ["Kgotla Services.html", "Kgotla Boats.html", "Kgotla Boat - "], "Kgotla Books.html": ["Kgotla Books.html", "Kgotla Book - "] }; function currentPage() { try { return decodeURIComponent(location.pathname.split("/").pop() || ""); } catch (e) { return ""; } } function isActive(href) { const here = currentPage(); if (!here) return false; const family = NAV_FAMILY[href] || [href]; return family.some((p) => (p.endsWith(" - ") ? here.startsWith(p) : here === p)); } const NAV_GRADIENT = "linear-gradient(160deg, #0051FF 4%, #009780 96%)"; const NAV_INK = "#0F172B"; const NAV_EASE = "cubic-bezier(.4,0,.2,1)"; const navFont = "var(--font-body)"; const { LogoWhite: NavLogoWhite, LogoColor: NavLogoColor, ButtonPrimaryDark: NavBtnDark, ButtonPrimaryLight: NavBtnLight, ButtonSecondaryDark: NavBtnSecDark } = window.KgotlaDesignSystem_4cd0b1; function MenuDrawer({ open, onClose, cta, ctaHref }) { const [shown, setShown] = React.useState(false); const panelRef = React.useRef(null); React.useEffect(() => { if (!open) { setShown(false); return; } const t = setTimeout(() => setShown(true), 20); const prevHtml = document.documentElement.style.overflow; const prev = document.body.style.overflow; document.documentElement.style.overflow = "hidden"; document.body.style.overflow = "hidden"; const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); const first = panelRef.current && panelRef.current.querySelector("a,button"); if (first) first.focus({ preventScroll: true }); return () => { clearTimeout(t); document.documentElement.style.overflow = prevHtml; document.body.style.overflow = prev; window.removeEventListener("keydown", onKey); }; }, [open, onClose]); if (!open) return null; const reduced = !!window.REDUCED; return ReactDOM.createPortal(
, document.body); } /* Compact bar that arrives once the page has scrolled and steps out of the way while the reader is moving down. Phones and tablets only. */ function StickyBar({ onOpen }) { const [visible, setVisible] = React.useState(false); React.useEffect(() => { let last = window.scrollY, raf = 0; const paint = () => { raf = 0; const y = window.scrollY; if (y < 260) setVisible(false); else if (y < last - 4) setVisible(true); else if (y > last + 8) setVisible(false); last = y; }; const onScroll = () => { if (!raf) raf = requestAnimationFrame(paint); }; window.addEventListener("scroll", onScroll, { passive: true }); return () => { window.removeEventListener("scroll", onScroll); if (raf) cancelAnimationFrame(raf); }; }, []); return ReactDOM.createPortal( , document.body); } /* Reading progress — a hairline at the very top on phones and tablets. */ function ScrollProgress() { const ref = React.useRef(null); React.useEffect(() => { let raf = 0; const paint = () => { raf = 0; const h = document.documentElement.scrollHeight - window.innerHeight; const p = h > 40 ? Math.max(0, Math.min(1, window.scrollY / h)) : 0; if (ref.current) ref.current.style.transform = `scaleX(${p.toFixed(4)})`; }; const on = () => { if (!raf) raf = requestAnimationFrame(paint); }; paint(); window.addEventListener("scroll", on, { passive: true }); window.addEventListener("resize", on); const t = setInterval(paint, 900); return () => { clearInterval(t); window.removeEventListener("scroll", on); window.removeEventListener("resize", on); if (raf) cancelAnimationFrame(raf); }; }, []); return ReactDOM.createPortal(, document.body); } /* tone="dark" over photography and gradient, "light" on white pages. */ function SiteHeader({ tone = "dark", cta = "Get started", ctaHref, style }) { const [open, setOpen] = React.useState(false); const narrow = window.useNarrow ? window.useNarrow() : false; const dark = tone === "dark"; const Logo = dark ? NavLogoWhite : NavLogoColor; const Cta = dark ? NavBtnDark : NavBtnLight; const href = ctaHref || (window.KGLINKS || {}).contact || "Kgotla Contact.html"; const close = React.useCallback(() => setOpen(false), []); return (