Files
einfach-produktiv/app/components/Navbar.tsx
T

154 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 onScroll = () => {
// trigger line = 1/3 from top of viewport (= 2/3 from bottom)
const triggerY = window.scrollY + NAVBAR_HEIGHT + (window.innerHeight - NAVBAR_HEIGHT) / 3;
let current = "";
for (const id of anchorIds) {
const el = document.getElementById(id);
if (!el) continue;
const top = el.getBoundingClientRect().top + window.scrollY;
if (top <= triggerY) current = id;
}
setActiveSection(current);
};
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
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>
);
}