feat: highlight active section in header nav on scroll

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 13:15:22 +03:00
parent 96081ccfe3
commit 0b2d3310af

View File

@@ -9,6 +9,7 @@ import { HeroLogo } from "@/components/ui/HeroLogo";
export function Header() {
const [menuOpen, setMenuOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const [activeSection, setActiveSection] = useState("");
useEffect(() => {
function handleScroll() {
@@ -18,6 +19,42 @@ export function Header() {
return () => window.removeEventListener("scroll", handleScroll);
}, []);
useEffect(() => {
const sectionIds = NAV_LINKS.map((l) => l.href.replace("#", ""));
const observers: IntersectionObserver[] = [];
// Observe hero — when visible, clear active section
const hero = document.querySelector("section:first-of-type");
if (hero) {
const heroObserver = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) setActiveSection("");
},
{ rootMargin: "-20% 0px -70% 0px" },
);
heroObserver.observe(hero);
observers.push(heroObserver);
}
sectionIds.forEach((id) => {
const el = document.getElementById(id);
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setActiveSection(id);
}
},
{ rootMargin: "-40% 0px -55% 0px" },
);
observer.observe(el);
observers.push(observer);
});
return () => observers.forEach((o) => o.disconnect());
}, []);
return (
<header
className={`fixed top-0 z-50 w-full transition-all duration-500 ${
@@ -46,15 +83,22 @@ export function Header() {
</Link>
<nav className="hidden items-center gap-8 md:flex">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
className="relative py-1 text-sm font-medium text-neutral-400 transition-all duration-300 after:absolute after:bottom-0 after:left-0 after:h-[2px] after:w-0 after:bg-[#c9a96e] after:transition-all after:duration-300 hover:text-white hover:after:w-full"
>
{link.label}
</a>
))}
{NAV_LINKS.map((link) => {
const isActive = activeSection === link.href.replace("#", "");
return (
<a
key={link.href}
href={link.href}
className={`relative py-1 text-sm font-medium transition-all duration-300 after:absolute after:bottom-0 after:left-0 after:h-[2px] after:bg-[#c9a96e] after:transition-all after:duration-300 ${
isActive
? "text-[#d4b87a] after:w-full"
: "text-neutral-400 after:w-0 hover:text-white hover:after:w-full"
}`}
>
{link.label}
</a>
);
})}
</nav>
<div className="flex items-center gap-2 md:hidden">
@@ -75,16 +119,23 @@ export function Header() {
}`}
>
<nav className="border-t border-white/[0.06] bg-black/40 px-6 py-4 backdrop-blur-xl sm:px-8">
{NAV_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
onClick={() => setMenuOpen(false)}
className="block py-3 text-base text-neutral-400 transition-colors hover:text-white"
>
{link.label}
</a>
))}
{NAV_LINKS.map((link) => {
const isActive = activeSection === link.href.replace("#", "");
return (
<a
key={link.href}
href={link.href}
onClick={() => setMenuOpen(false)}
className={`block py-3 text-base transition-colors ${
isActive
? "text-[#d4b87a]"
: "text-neutral-400 hover:text-white"
}`}
>
{link.label}
</a>
);
})}
</nav>
</div>
</header>