162 lines
4.8 KiB
TypeScript
162 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import Image from "next/image";
|
|
|
|
const NAVBAR_HEIGHT = 100; // px — matches h-[6.25rem]
|
|
const SCROLL_DURATION = 800; // ms
|
|
|
|
function smoothScrollTo(targetY: number) {
|
|
const startY = window.scrollY;
|
|
const distance = targetY - startY;
|
|
const startTime = performance.now();
|
|
|
|
function step(now: number) {
|
|
const elapsed = now - startTime;
|
|
const progress = Math.min(elapsed / SCROLL_DURATION, 1);
|
|
// ease-in-out cubic
|
|
const ease = progress < 0.5
|
|
? 4 * progress ** 3
|
|
: 1 - (-2 * progress + 2) ** 3 / 2;
|
|
window.scrollTo(0, startY + distance * ease);
|
|
if (progress < 1) requestAnimationFrame(step);
|
|
}
|
|
|
|
requestAnimationFrame(step);
|
|
}
|
|
|
|
const navLinks = [
|
|
{ label: "Werkzeuge", href: "#werkzeuge" },
|
|
{ label: "Blog", href: "#blog" },
|
|
{ label: "Über Björn", href: "#ueber-bjoern" },
|
|
{ label: "Shop", href: "/shop" },
|
|
];
|
|
|
|
const anchorIds = navLinks
|
|
.filter((l) => l.href.startsWith("#"))
|
|
.map((l) => l.href.slice(1));
|
|
|
|
export function Navbar() {
|
|
const [scrolled, setScrolled] = useState(false);
|
|
const [activeSection, setActiveSection] = useState("");
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setScrolled(window.scrollY > 8);
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const visible = new Set<string>();
|
|
|
|
const updateActive = () => {
|
|
for (const id of anchorIds) {
|
|
if (visible.has(id)) { setActiveSection(id); return; }
|
|
}
|
|
setActiveSection("");
|
|
};
|
|
|
|
const observers = anchorIds.map((id) => {
|
|
const el = document.getElementById(id);
|
|
if (!el) return null;
|
|
const obs = new IntersectionObserver(
|
|
([entry]) => {
|
|
entry.isIntersecting ? visible.add(id) : visible.delete(id);
|
|
updateActive();
|
|
},
|
|
{ rootMargin: "-100px 0px -50% 0px" }
|
|
);
|
|
obs.observe(el);
|
|
return obs;
|
|
});
|
|
|
|
return () => observers.forEach((o) => o?.disconnect());
|
|
}, []);
|
|
|
|
return (
|
|
<header
|
|
className={`sticky top-0 z-50 w-full h-[6.25rem] flex items-center px-[2rem] transition-[background-color,backdrop-filter] duration-300 ${
|
|
scrolled
|
|
? "bg-[#f5f0e8]/80 backdrop-blur-md"
|
|
: "bg-[#f5f0e8]"
|
|
}`}
|
|
>
|
|
<div className="w-full flex items-center justify-between">
|
|
|
|
{/* Logo — smooth scroll to top */}
|
|
<a
|
|
href="/"
|
|
className="shrink-0"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
smoothScrollTo(0);
|
|
history.replaceState(null, "", "/");
|
|
}}
|
|
>
|
|
<Image
|
|
src="/logo.png"
|
|
alt="einfach produktiv"
|
|
width={181}
|
|
height={61}
|
|
priority
|
|
/>
|
|
</a>
|
|
|
|
{/* Nav links */}
|
|
<nav className="flex items-center gap-[3rem]">
|
|
{navLinks.map((link) =>
|
|
link.href.startsWith("#") ? (
|
|
<a
|
|
key={link.href}
|
|
href={link.href}
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
const el = document.getElementById(link.href.slice(1));
|
|
if (el) {
|
|
const top = el.getBoundingClientRect().top + window.scrollY - NAVBAR_HEIGHT;
|
|
smoothScrollTo(top);
|
|
}
|
|
history.replaceState(null, "", link.href);
|
|
}}
|
|
className={`text-[1.125rem] font-semibold tracking-[0.01125rem] transition-colors cursor-pointer ${
|
|
activeSection === link.href.slice(1)
|
|
? "text-[#f6a701]"
|
|
: "text-[#222221] hover:text-[#f6a701]"
|
|
}`}
|
|
>
|
|
{link.label}
|
|
</a>
|
|
) : (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
className="text-[1.125rem] font-semibold text-[#222221] tracking-[0.01125rem] hover:text-[#f6a701] transition-colors"
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
)
|
|
)}
|
|
</nav>
|
|
|
|
{/* CTA buttons */}
|
|
<div className="flex items-center gap-[0.75rem] p-[0.5rem]">
|
|
<Link
|
|
href="/newsletter"
|
|
className="px-[1.5rem] py-[1rem] rounded-[0.5rem] border border-[#868686] text-[1.125rem] font-bold text-[#222221] hover:bg-[#222221] hover:text-white transition-colors"
|
|
>
|
|
Newsletter
|
|
</Link>
|
|
<Link
|
|
href="/challenge"
|
|
className="px-[1.5rem] py-[1rem] rounded-[0.5rem] bg-[#f6a701] text-[1.125rem] font-bold text-[#222221] hover:brightness-95 transition-all"
|
|
>
|
|
7-Tage-Challenge
|
|
</Link>
|
|
</div>
|
|
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|