// ════════════════════════════════════════════════════════════════════ // NexFlow · Nav · top fixed bar with blur-on-scroll // ──────────────────────────────────────────────────────────────────── // v9 (June 2026) · Customer-language services dropdown + full-screen // mobile menu. // The single "Services" link fans out into a panel that links to the // six individual service pages (/services//), each labelled in // plain English with a one-line "what it does for you" hint. Labels // match the homepage grid, the footer, and the page s. On // mobile the whole nav becomes a full-screen overlay with large tap // targets and a body scroll-lock so the page underneath can't scroll. // ════════════════════════════════════════════════════════════════════ const { useState: useStateNav, useEffect: useEffectNav, useRef: useRefNav } = React; // Single source of truth for the services dropdown. Keep in sync with // the homepage grid (NF_DATA.SERVICE_PAGES), the footer, and the six // /services/<slug>/ pages. const NF_NAV_SERVICES = [ { href: '/services/ai-receptionist/', label: 'AI Receptionist', hint: 'Answers your calls 24/7' }, { href: '/services/crm-automation/', label: 'CRM Automation', hint: 'Your leads never go cold' }, { href: '/services/invoice-processing/', label: 'Invoice Processing', hint: 'Scans bills straight into Xero' }, { href: '/services/ai-chatbot/', label: 'AI Chatbot', hint: 'Talks to visitors while you work' }, { href: '/services/document-ai/', label: 'Smart Documents', hint: 'Reads & files paperwork for you' }, { href: '/services/workflow-automation/', label: 'Workflow Automation', hint: 'Connects all your apps' }, ]; function Nav() { const [scrolled, setScrolled] = useStateNav(false); const [open, setOpen] = useStateNav(false); const [svcOpen, setSvcOpen] = useStateNav(false); const svcRef = useRefNav(null); const svcCloseTimer = useRefNav(null); useEffectNav(() => { const onScroll = () => setScrolled(window.scrollY > 12); window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => window.removeEventListener('scroll', onScroll); }, []); // Lock the body scroll while the full-screen mobile menu is open so the // page underneath doesn't scroll behind the overlay. useEffectNav(() => { document.body.classList.toggle('nf-menu-open', open); return () => document.body.classList.remove('nf-menu-open'); }, [open]); // Close dropdown + mobile menu on Esc / outside click. The 120ms close // grace timer keeps the panel open while the cursor traverses the gap // between trigger and panel. useEffectNav(() => { function onKey(e) { if (e.key === 'Escape') { setSvcOpen(false); setOpen(false); } } function onDoc(e) { if (svcRef.current && !svcRef.current.contains(e.target)) setSvcOpen(false); } document.addEventListener('keydown', onKey); document.addEventListener('click', onDoc); return () => { document.removeEventListener('keydown', onKey); document.removeEventListener('click', onDoc); }; }, []); function openSvc() { if (svcCloseTimer.current) clearTimeout(svcCloseTimer.current); setSvcOpen(true); } function closeSvc() { if (svcCloseTimer.current) clearTimeout(svcCloseTimer.current); svcCloseTimer.current = setTimeout(() => setSvcOpen(false), 120); } const closeAll = () => { setSvcOpen(false); setOpen(false); }; return ( <header className={'nf-nav' + (scrolled ? ' scrolled' : '') + (open ? ' menu-open' : '')}> <a href="/" className="nf-nav-logo" onClick={closeAll}> <svg viewBox="0 0 32 32" width="30" height="30" aria-hidden="true"> <rect width="32" height="32" rx="8" fill="#0E7C63"/> <circle cx="10" cy="16" r="2.6" fill="#FCFAF4"/> <circle cx="22" cy="10" r="2.6" fill="#D2552B"/> <circle cx="22" cy="22" r="2.6" fill="#FCFAF4"/> <line x1="12.2" y1="15.2" x2="19.8" y2="10.8" stroke="#FCFAF4" strokeWidth="1.6" strokeLinecap="round"/> <line x1="12.2" y1="16.8" x2="19.8" y2="21.2" stroke="#FCFAF4" strokeWidth="1.6" strokeLinecap="round"/> </svg> <span>NexFlow</span> </a> <nav className={'nf-nav-links' + (open ? ' open' : '')} aria-label="Primary"> <a href="/" onClick={closeAll}>Home</a> {/* Services dropdown — click for keyboard/touch, hover for mouse */} <div ref={svcRef} className={'nf-nav-svc' + (svcOpen ? ' open' : '')} onMouseEnter={openSvc} onMouseLeave={closeSvc} > <a href="/services.html" className="nf-nav-svc-trigger" aria-haspopup="menu" aria-expanded={svcOpen} onClick={(e) => { // On desktop, first click opens; second click navigates. // On mobile (burger menu open) we always navigate. if (open) return; if (!svcOpen) { e.preventDefault(); setSvcOpen(true); } }} > Services <span className="nf-nav-svc-caret" aria-hidden="true">▾</span> </a> <div className="nf-nav-svc-panel" role="menu" aria-label="Services"> <div className="nf-nav-svc-grid"> {NF_NAV_SERVICES.map(s => ( <a key={s.href} href={s.href} role="menuitem" className="nf-nav-svc-item" onClick={closeAll}> <span className="nf-nav-svc-item-label">{s.label}</span> <span className="nf-nav-svc-item-hint">{s.hint}</span> </a> ))} </div> <div className="nf-nav-svc-foot"> <a href="/services.html" onClick={closeAll}> See how it all fits together <span aria-hidden="true">→</span> </a> </div> </div> </div> <a href="/#how" onClick={closeAll}>How it works</a> <a href="/pricing.html" onClick={closeAll}>Pricing</a> <a href="/guides/" onClick={closeAll}>Guides</a> <a href="/faq/" onClick={closeAll}>Help</a> <a href="/blog.html" onClick={closeAll}>Field notes</a> <a href="/#contact" onClick={closeAll}>Contact us</a> {/* CTA inside the menu — visible on mobile where the header button is hidden */} <a href="/#contact" className="btn lg nf-nav-cta-mobile" onClick={closeAll}> Have a chat with us <span className="arr" aria-hidden="true">→</span> </a> </nav> <div className="nf-nav-right"> <a href="/#contact" className="btn act sm nf-nav-cta-desktop">Have a chat <span className="arr" aria-hidden="true">→</span></a> <button className="nf-nav-burger" aria-label={open ? 'Close menu' : 'Open menu'} aria-expanded={open} onClick={() => setOpen(o => !o)} > <span/><span/><span/> </button> </div> </header> ); } window.NF_Nav = Nav;