2505a87717
Strategy: one layout breakpoint at lg (1024px), clamp() for fluid type. Navbar: hamburger menu (animated ✕) with max-h dropdown, resize-close, h-20 mobile / h-[6.25rem] desktop, blur triggers on scroll or menu-open. Hero: flex-col→lg:flex-row, clamp(2rem,5vw,4rem) heading, background-image scales responsively, gradient mask via .hero-mask only at lg+ in CSS. Divider: flex-wrap + gap-y for smaller screens. Tools: grid-cols-1→md:2→lg:3, removed fixed height and per-card paddingRight. Blog: featured flex-col→lg:flex-row, secondary flex-col→md:flex-row, excerpt hidden on mobile secondary cards to save space. About: flex-col→lg:flex-row, clamp on quote size, min-h-[18rem] for photo. Newsletter: flex-col→lg:flex-row, input+button stack on xs / row on sm+. Footer: flex-col→lg:flex-row, flex-wrap on legal links. globals.css: scroll-padding-top 5rem mobile / 6.25rem desktop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
184 lines
6.4 KiB
TypeScript
184 lines
6.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import Image from "next/image";
|
|
|
|
const SCROLL_DURATION = 800;
|
|
|
|
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);
|
|
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" },
|
|
];
|
|
|
|
export function Navbar() {
|
|
const [scrolled, setScrolled] = useState(false);
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const onScroll = () => setScrolled(window.scrollY > 8);
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
return () => window.removeEventListener("scroll", onScroll);
|
|
}, []);
|
|
|
|
// Close mobile menu on resize to desktop
|
|
useEffect(() => {
|
|
const onResize = () => { if (window.innerWidth >= 1024) setMenuOpen(false); };
|
|
window.addEventListener("resize", onResize);
|
|
return () => window.removeEventListener("resize", onResize);
|
|
}, []);
|
|
|
|
const handleAnchorClick = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
|
|
e.preventDefault();
|
|
setMenuOpen(false);
|
|
const navH = window.innerWidth >= 1024 ? 100 : 80; // desktop / mobile navbar px
|
|
const el = document.getElementById(href.slice(1));
|
|
if (el) smoothScrollTo(el.getBoundingClientRect().top + window.scrollY - navH);
|
|
history.replaceState(null, "", href);
|
|
};
|
|
|
|
const blurred = scrolled || menuOpen;
|
|
|
|
return (
|
|
<header
|
|
className={`sticky top-0 z-50 w-full flex flex-col transition-[background-color,backdrop-filter] duration-300 ${
|
|
blurred ? "bg-[#f5f0e8]/90 backdrop-blur-md" : "bg-[#f5f0e8]"
|
|
}`}
|
|
>
|
|
{/* Main bar */}
|
|
<div className="h-20 lg:h-[6.25rem] flex items-center justify-between px-4 lg:px-8">
|
|
|
|
{/* Logo */}
|
|
<Link href="/" className="shrink-0">
|
|
<Image
|
|
src="/logo.png"
|
|
alt="einfach produktiv"
|
|
width={181}
|
|
height={61}
|
|
priority
|
|
className="w-[130px] lg:w-[181px] h-auto"
|
|
/>
|
|
</Link>
|
|
|
|
{/* Desktop nav */}
|
|
<nav className="hidden lg:flex items-center gap-[3rem]">
|
|
{navLinks.map((link) =>
|
|
link.href.startsWith("#") ? (
|
|
<a
|
|
key={link.href}
|
|
href={link.href}
|
|
onClick={(e) => handleAnchorClick(e, link.href)}
|
|
className="text-[1.125rem] font-semibold text-[#222221] tracking-[0.01125rem] hover:text-[#f6a701] transition-colors cursor-pointer"
|
|
>
|
|
{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>
|
|
|
|
{/* Desktop CTAs */}
|
|
<div className="hidden lg:flex items-center gap-3 p-2">
|
|
<Link
|
|
href="/newsletter"
|
|
className="px-6 py-4 rounded-lg 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-6 py-4 rounded-lg bg-[#f6a701] text-[1.125rem] font-bold text-[#222221] hover:brightness-95 transition-all"
|
|
>
|
|
7-Tage-Challenge
|
|
</Link>
|
|
</div>
|
|
|
|
{/* Mobile hamburger — animated to ✕ */}
|
|
<button
|
|
className="lg:hidden flex flex-col justify-center gap-[5px] p-2 -mr-2"
|
|
onClick={() => setMenuOpen((o) => !o)}
|
|
aria-label="Menü öffnen"
|
|
>
|
|
<span className={`block w-6 h-[2px] bg-[#222221] transition-all duration-300 origin-center ${menuOpen ? "rotate-45 translate-y-[7px]" : ""}`} />
|
|
<span className={`block w-6 h-[2px] bg-[#222221] transition-all duration-300 ${menuOpen ? "opacity-0" : ""}`} />
|
|
<span className={`block w-6 h-[2px] bg-[#222221] transition-all duration-300 origin-center ${menuOpen ? "-rotate-45 -translate-y-[7px]" : ""}`} />
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{/* Mobile dropdown — max-h animation */}
|
|
<div
|
|
className={`lg:hidden overflow-hidden transition-all duration-300 ease-in-out ${
|
|
menuOpen ? "max-h-[32rem] pb-6" : "max-h-0"
|
|
}`}
|
|
>
|
|
<nav className="flex flex-col gap-1 px-4">
|
|
{navLinks.map((link) =>
|
|
link.href.startsWith("#") ? (
|
|
<a
|
|
key={link.href}
|
|
href={link.href}
|
|
onClick={(e) => handleAnchorClick(e, link.href)}
|
|
className="text-[1.125rem] font-semibold text-[#222221] hover:text-[#f6a701] transition-colors py-3 border-b border-[#e8e3db] last:border-0"
|
|
>
|
|
{link.label}
|
|
</a>
|
|
) : (
|
|
<Link
|
|
key={link.href}
|
|
href={link.href}
|
|
onClick={() => setMenuOpen(false)}
|
|
className="text-[1.125rem] font-semibold text-[#222221] hover:text-[#f6a701] transition-colors py-3 border-b border-[#e8e3db] last:border-0"
|
|
>
|
|
{link.label}
|
|
</Link>
|
|
)
|
|
)}
|
|
<div className="flex flex-col gap-3 pt-4">
|
|
<Link
|
|
href="/newsletter"
|
|
className="text-center px-6 py-3 rounded-lg border border-[#868686] text-[1rem] font-bold text-[#222221] hover:bg-[#222221] hover:text-white transition-colors"
|
|
>
|
|
Newsletter
|
|
</Link>
|
|
<Link
|
|
href="/challenge"
|
|
className="text-center px-6 py-3 rounded-lg bg-[#f6a701] text-[1rem] font-bold text-[#222221] hover:brightness-95 transition-all"
|
|
>
|
|
7-Tage-Challenge
|
|
</Link>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|