105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { SITE } from "../content";
|
||
|
||
const NAV = [
|
||
{ href: "#services", label: "Services" },
|
||
{ href: "#work", label: "Work" },
|
||
{ href: "#about", label: "About" },
|
||
{ href: "#faq", label: "FAQ" },
|
||
];
|
||
|
||
/** Masthead-style sticky header with a mono "ticker bug" logo. */
|
||
export default function SiteHeader() {
|
||
const [open, setOpen] = useState(false);
|
||
const [scrolled, setScrolled] = useState(false);
|
||
|
||
useEffect(() => {
|
||
const onScroll = () => setScrolled(window.scrollY > 24);
|
||
onScroll();
|
||
window.addEventListener("scroll", onScroll, { passive: true });
|
||
return () => window.removeEventListener("scroll", onScroll);
|
||
}, []);
|
||
|
||
// lock body + escape to close mobile nav
|
||
useEffect(() => {
|
||
document.body.style.overflow = open ? "hidden" : "";
|
||
const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false);
|
||
window.addEventListener("keydown", onKey);
|
||
return () => {
|
||
window.removeEventListener("keydown", onKey);
|
||
document.body.style.overflow = "";
|
||
};
|
||
}, [open]);
|
||
|
||
return (
|
||
<header className={`masthead ${scrolled ? "is-scrolled" : ""}`}>
|
||
<div className="masthead__bar frame">
|
||
<a href="#main" className="logo" aria-label="Feedback Studios — home">
|
||
<span className="logo__bug" aria-hidden="true">
|
||
FS
|
||
</span>
|
||
<span className="logo__name">Feedback Studios</span>
|
||
<span className="logo__tag" aria-hidden="true">
|
||
EST. ’26 · REV
|
||
</span>
|
||
</a>
|
||
|
||
<nav className="masthead__nav" aria-label="Primary">
|
||
<ul>
|
||
{NAV.map((n) => (
|
||
<li key={n.href}>
|
||
<a href={n.href}>{n.label}</a>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</nav>
|
||
|
||
<a
|
||
className="btn btn--accent masthead__cta"
|
||
href={SITE.booking}
|
||
data-cursor="Let’s talk"
|
||
>
|
||
Get a growth audit
|
||
</a>
|
||
|
||
<button
|
||
className="masthead__burger"
|
||
aria-expanded={open}
|
||
aria-controls="mobile-nav"
|
||
onClick={() => setOpen((o) => !o)}
|
||
>
|
||
<span className="sr-only">{open ? "Close menu" : "Open menu"}</span>
|
||
<span className="masthead__burger-box" data-open={open} aria-hidden="true">
|
||
<i /><i /><i />
|
||
</span>
|
||
</button>
|
||
</div>
|
||
<div className="masthead__rule" aria-hidden="true" />
|
||
|
||
{/* mobile drawer */}
|
||
<div id="mobile-nav" className="drawer" data-open={open}>
|
||
<nav aria-label="Mobile">
|
||
<ul>
|
||
{NAV.map((n, i) => (
|
||
<li key={n.href} style={{ transitionDelay: `${i * 50 + 60}ms` }}>
|
||
<a href={n.href} onClick={() => setOpen(false)}>
|
||
<span className="drawer__idx">0{i + 1}</span>
|
||
{n.label}
|
||
</a>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</nav>
|
||
<a
|
||
className="btn btn--accent"
|
||
href={SITE.booking}
|
||
onClick={() => setOpen(false)}
|
||
>
|
||
Get a growth audit
|
||
</a>
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|