/* global React */ /* ========================================================= BSpoke — Navigation Three styles: classic, minimal, editorial-rule ========================================================= */ /* ========================================================= Site-wide config — single source of truth. ▸ CALENDAR_URL: swap this string with your real Google Calendar appointment-booking link when you have it. Every "Schedule a Call" button across the site reads from here. ▸ NAV_LINKS: the seven top-level pages. ========================================================= */ const CALENDAR_URL = "https://calendar.app.google/BGBSYqcsmRPJpk8XA"; window.BSPOKE_CALENDAR_URL = CALENDAR_URL; const NAV_LINKS = [ { label: "Home", href: "#home" }, { label: "Services", href: "#services" }, { label: "About", href: "#about" }, { label: "Why Us", href: "#why-us" }, { label: "Reviews", href: "#reviews" }, { label: "Blog", href: "#blog" }, { label: "Contact", href: "#contact" }]; /* Active section tracking — observes which #section is in the viewport so the matching nav link can be highlighted. Falls back to "home" pre-scroll. */ function useActiveSection() { const [active, setActive] = React.useState("#home"); React.useEffect(() => { const sectionIds = NAV_LINKS.map((l) => l.href.slice(1)); const sections = sectionIds. map((id) => document.getElementById(id)). filter(Boolean); if (sections.length === 0) return; const observer = new IntersectionObserver( (entries) => { // Prefer the top-most intersecting entry. const visible = entries. filter((e) => e.isIntersecting). sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); if (visible[0]) setActive("#" + visible[0].target.id); }, { rootMargin: "-30% 0px -60% 0px", threshold: 0 } ); sections.forEach((s) => observer.observe(s)); return () => observer.disconnect(); }, []); return active; } function Logo({ variant = "dark", size = 44, backing = "white" }) { const titleColor = variant === "light" ? "var(--inverse)" : "var(--navy)"; const subColor = variant === "light" ? "rgba(245,240,235,0.62)" : "var(--ink-3)"; const ruleColor = variant === "light" ? "rgba(245,240,235,0.25)" : "var(--line-strong)"; /* Logo backing — keep the colored mark legible on dark navs by giving it a small light tile to sit on. */ const tile = { none: null, white: { bg: "#FFFFFF", ring: "transparent", shape: 4 }, cream: { bg: "var(--cream)", ring: "rgba(184,150,62,0.35)", shape: 4 }, circle: { bg: "#FFFFFF", ring: "transparent", shape: 999 }, rule: { bg: "transparent", ring: "rgba(184,150,62,0.6)", shape: 4 } }[backing] || null; const imgEl = BSpoke Hospitality & Asset Group; return ( {tile ? {imgEl} : imgEl} BSpoke Hospitality & Asset Group ); } /* ---------- Style A: Classic horizontal bar ---------- */ function NavClassic({ variant = "dark", logoBacking = "white" }) { const isLight = variant === "light"; const active = useActiveSection(); return ( ); } /* ---------- Style B: Minimal (small logo, very light) ---------- */ function NavMinimal({ variant = "dark" }) { const isLight = variant === "light"; const active = useActiveSection(); return ( ); } /* ---------- Style C: Editorial-rule (advisor firm vibe) ---------- */ function NavEditorial({ variant = "dark", logoBacking = "white" }) { const isLight = variant === "light"; const active = useActiveSection(); return ( ); } function Nav({ style = "classic", variant = "dark", logoBacking = "white" }) { if (style === "minimal") return ; if (style === "editorial") return ; return ; } window.BSPNav = Nav;