"use client";

import { useEffect, useMemo, useRef, useState } from "react";
import Link from "next/link";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { AnimatePresence, motion } from "motion/react";
import { useCartCount } from "../lib/cart";
import { useWishlist } from "../lib/useWishlist";
import { SearchButton, SearchOverlay } from "./SearchOverlay";
import { AUTH_CHANGED_EVENT } from "../lib/auth";
import { NewsletterModal } from "./NewsletterModal";
import { useCartFly } from "./CartFly";

const NAVBAR_HEIGHT = 100; // px — matches h-[6.25rem]
const SCROLL_DURATION = 200; // 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-out cubic — not ease-in-out. Ease-in-out's cubic ease-in half
    // stays nearly flat for the animation's first ~150-200ms, which read
    // as a delay before the scroll visibly starts. Ease-out moves
    // immediately and only decelerates into the landing.
    const ease = 1 - (1 - progress) ** 3;
    window.scrollTo(0, startY + distance * ease);
    if (progress < 1) requestAnimationFrame(step);
  }

  requestAnimationFrame(step);
}

// "Shop" becomes an in-page anchor to the homepage's ProductSpotlight
// section (id="spotlight") instead of a real /shop navigation whenever
// exactly 1 product is active — same reasoning as the other anchor links,
// #werkzeuge/#ueber-bjoern already have (a full catalog grid is
// degenerate UX with only 1 item to show). Passed down from
// app/layout.tsx, which is the one place already fetching the product
// catalog for this decision.
function getNavLinks(singleActiveProduct: boolean) {
  return [
    { label: "Werkzeuge", href: "#werkzeuge" },
    { label: "Blog", href: "/blog" },
    { label: "Über mich", href: "/ueber-mich" },
    { label: "Shop", href: singleActiveProduct ? "#spotlight" : "/shop" },
  ];
}

// "Werkzeuge" also covers standalone tool/product pages that live under
// the Home "Werkzeuge" section conceptually — /todo-cards (ToDo-Karten),
// /challenge (7-Tage-Challenge), and /newsletter (Impulse & Tipps) are
// all tools listed there, even though none of these routes is nested
// under /werkzeuge/. Their own Figma sources (page-todo-karten,
// page-weekly-impulses, and the 7-Tage-Challenge card in section-tools)
// mark "Werkzeuge" active in the nav for the same reason — confirmed by
// page-weekly-impulses's own breadcrumb ("Startseite › Werkzeuge ›
// Impulse & Tipps") and active-underline position, same as page-todo-karten's.
// /der-eine and /tasse-die-pause (both bespoke product detail pages, same
// "own route, not nested" shape as /todo-cards) added here for the same
// reason (2026-08-25).
const WERKZEUGE_ROUTES = ["/todo-cards", "/7-tage-klarheits-check", "/newsletter", "/der-eine", "/tasse-die-pause", "/lebensuhr", "/3x3-system"];

function isNavLinkActive(href: string, pathname: string, activeSection: string): boolean {
  if (href === "#werkzeuge") {
    return (
      WERKZEUGE_ROUTES.some((route) => pathname.startsWith(route)) ||
      (pathname === "/" && activeSection === "werkzeuge")
    );
  }
  if (href.startsWith("#")) {
    return pathname === "/" && activeSection === href.slice(1);
  }
  return pathname === href || pathname.startsWith(`${href}/`);
}

// Account icon — no Figma source exists for this yet (added outside the
// normal Figma-first workflow, see the assistant's own project notes on
// why: without it there was no reachable way to log in at all once the
// cart was empty and no recent order existed — /checkout's own login
// toggle never even renders in that state, see CheckoutContent.tsx's
// early "Warenkorb ist leer" return). Fetches auth state client-side via
// /api/account/me rather than through the server-rendered layout — this
// component's parent (app/layout.tsx) is otherwise static/ISR-cacheable,
// and reading the session cookie there (next/headers' cookies()) would
// force the entire site into per-request dynamic rendering just for this.
// `loggedIn === null` is the brief "not checked yet" state on first paint.
function AccountLink() {
  const [loggedIn, setLoggedIn] = useState<boolean | null>(null);

  useEffect(() => {
    function checkAuth() {
      fetch("/api/account/me")
        .then((res) => setLoggedIn(res.ok))
        .catch(() => setLoggedIn(false));
    }
    checkAuth();
    // Navbar lives in the root layout and never unmounts across
    // navigations, so this effect only ever runs once on its own —
    // router.refresh() (called after login/logout) re-fetches Server
    // Component data but doesn't re-run an already-mounted Client
    // Component's effects. AUTH_CHANGED_EVENT is dispatched explicitly by
    // every login/logout call site (see dispatchAuthChanged() in
    // ../lib/auth) so this stays in sync without a hard reload.
    window.addEventListener(AUTH_CHANGED_EVENT, checkAuth);
    return () => window.removeEventListener(AUTH_CHANGED_EVENT, checkAuth);
  }, []);

  const href = loggedIn ? "/konto/bestellungen" : "/konto/login";

  return (
    <Link
      href={href}
      aria-label={loggedIn ? "Mein Konto (eingeloggt)" : "Anmelden"}
      className="relative flex h-11 w-11 items-center justify-center shrink-0 active:scale-[0.9] transition-transform"
    >
      {/* -translate-y-0.5 — the glyph's own bounding box centers fine
          mathematically, but the round head (light, isolated) versus the
          wide shoulders (heavier, at the bottom) reads as optically
          bottom-heavy next to the cart icon, sitting visibly lower.
          Nudged up to match (fixed 2026-07-24). */}
      <svg viewBox="0 0 24 24" className="h-7 w-7 text-text-primary -translate-y-0.5" fill="none" aria-hidden="true">
        <circle cx="12" cy="8" r="3.6" stroke="currentColor" strokeWidth="1.8" />
        <path d="M4.5 20c1.2-4 4-6 7.5-6s6.3 2 7.5 6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
      </svg>
      {/* Only real "am I logged in?" signal on the site outside /konto
          itself — same brand-colored underline language as the desktop
          nav links' active-state indicator, so it reads as consistent
          rather than a new visual idiom. */}
      {loggedIn && <span aria-hidden className="absolute bottom-1 h-[2px] w-4 bg-brand rounded-full" />}
    </Link>
  );
}

// Wishlist icon + count badge — only rendered by the caller when
// `wishlistEnabled` (CompanySettings). Visible at every width, alongside
// Search/Account/Cart — all 4 stay full 44px touch targets even on the
// smallest phones (shrinking them was tried and reverted: it made them
// hard to hit accurately). The logo shrinks further below 375px instead
// to make room — confirmed via an actual Playwright viewport sweep down
// to 320px that this fits without wrapping/overflow (see git history).
function WishlistLink() {
  const { count } = useWishlist();
  const [pulse, setPulse] = useState(false);
  const prevCountRef = useRef(0);
  // Same guard as CartLink's own — useWishlist() starts from an empty
  // cached/SSR-safe list and only fills in the real count once its own
  // fetch resolves client-side, so without this the badge pulsed on every
  // page load/reload the instant that first real count arrived, not just
  // on an actual add/remove during the session.
  const hasMountedRef = useRef(false);

  useEffect(() => {
    if (!hasMountedRef.current) {
      hasMountedRef.current = true;
      prevCountRef.current = count;
      return;
    }
    if (count !== prevCountRef.current) {
      setPulse(true);
      const t = setTimeout(() => setPulse(false), 350);
      prevCountRef.current = count;
      return () => clearTimeout(t);
    }
  }, [count]);

  return (
    <Link
      href="/konto/merkliste"
      aria-label={count > 0 ? `Merkliste, ${count} Artikel` : "Merkliste"}
      className="relative flex h-11 w-11 items-center justify-center shrink-0 active:scale-[0.9] transition-transform"
    >
      <svg
        viewBox="0 0 20 18"
        className={"h-6 w-6 text-text-primary transition-transform duration-300 " + (pulse ? "scale-110" : "scale-100")}
        fill="none"
        aria-hidden="true"
      >
        <path
          d="M10 17S1 11.5 1 5.8C1 2.6 3.4 1 5.8 1c1.6 0 3.2.9 4.2 2.4C11 1.9 12.6 1 14.2 1 16.6 1 19 2.6 19 5.8 19 11.5 10 17 10 17Z"
          stroke="currentColor"
          strokeWidth="1.6"
          strokeLinejoin="round"
        />
      </svg>
      {count > 0 && (
        <span
          className={
            "absolute top-0 right-0 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-brand px-1 text-[0.6875rem] font-bold leading-none text-white transition-transform duration-300 " +
            (pulse ? "scale-125" : "scale-100")
          }
        >
          {count > 99 ? "99+" : count}
        </span>
      )}
    </Link>
  );
}

// Cart icon + count badge — traced from the Figma Navbar/Default component's
// btn-cart (icon-cart 32x30 + cart-count-badge, node 4849:24). Visible at
// every breakpoint tier (unlike the nav links / CTA buttons, which move into
// the hamburger drawer below lg) since the cart is a primary nav affordance.
// Badge only renders once something is actually in the cart.
function CartLink() {
  const count = useCartCount();
  const { registerCartIcon, pendingCount } = useCartFly();
  const anchorRef = useRef<HTMLAnchorElement>(null);
  const [pulse, setPulse] = useState(false);
  const prevVisibleRef = useRef(0);
  // useCartCount's getServerSnapshot is always 0 (see cart.ts) so hydration
  // always transitions 0 -> the real count on first render — without this
  // guard that transition alone satisfied "visibleCount > prevVisibleRef"
  // and pulsed the badge on every single page load/reload, not just an
  // actual in-session cart change.
  const hasMountedRef = useRef(false);
  const pathname = usePathname();

  // Held back by pendingCount while a ball is mid-flight, so the badge only
  // "counts up" once the ball visually lands here — not the instant the
  // button is clicked (real cart data itself updates instantly elsewhere,
  // this is purely the badge's own display value).
  const visibleCount = Math.max(0, count - pendingCount);

  useEffect(() => {
    registerCartIcon(anchorRef.current);
    return () => registerCartIcon(null);
  }, [registerCartIcon]);

  useEffect(() => {
    if (!hasMountedRef.current) {
      hasMountedRef.current = true;
      prevVisibleRef.current = visibleCount;
      return;
    }
    if (visibleCount > prevVisibleRef.current) {
      setPulse(true);
      const t = setTimeout(() => setPulse(false), 350);
      prevVisibleRef.current = visibleCount;
      return () => clearTimeout(t);
    }
    prevVisibleRef.current = visibleCount;
  }, [visibleCount]);

  return (
    <Link
      ref={anchorRef}
      href="/cart"
      onClick={(e) => {
        // Already on /cart — a real navigation here is a no-op, so treat
        // the click as "take me back to the top" instead of doing nothing.
        if (pathname === "/cart") {
          e.preventDefault();
          smoothScrollTo(0);
        }
      }}
      aria-label={visibleCount > 0 ? `Warenkorb, ${visibleCount} Artikel` : "Warenkorb"}
      className="relative flex h-11 w-11 items-center justify-center shrink-0 active:scale-[0.9] transition-transform"
    >
      <svg
        viewBox="0 0 32 30"
        className={"h-7 w-7 text-text-primary transition-transform duration-300 " + (pulse ? "scale-110" : "scale-100")}
        fill="none"
        aria-hidden="true"
      >
        <path
          d="M2,2 H6 L9.2,17.7 a2.4,2.4 0 0 0 2.4,2 h11.6 a2.4,2.4 0 0 0 2.4,-1.9 L28,7.5 H8"
          stroke="currentColor"
          strokeWidth="2"
          strokeLinecap="round"
          strokeLinejoin="round"
        />
        <circle cx="12" cy="25" r="1.6" fill="currentColor" />
        <circle cx="24" cy="25" r="1.6" fill="currentColor" />
      </svg>
      {visibleCount > 0 && (
        <span
          className={
            "absolute top-0 right-0 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-brand px-1 text-[0.6875rem] font-bold leading-none text-white transition-transform duration-300 " +
            (pulse ? "scale-125" : "scale-100")
          }
        >
          {visibleCount > 99 ? "99+" : visibleCount}
        </span>
      )}
    </Link>
  );
}

export function Navbar({
  singleActiveProduct,
  wishlistEnabled,
  searchEnabled,
}: {
  singleActiveProduct: boolean;
  wishlistEnabled: boolean;
  searchEnabled: boolean;
}) {
  const pathname = usePathname();
  const [scrolled, setScrolled] = useState(false);
  const [activeSection, setActiveSection] = useState("");
  const [mobileOpen, setMobileOpen] = useState(false);
  const [newsletterOpen, setNewsletterOpen] = useState(false);
  const [searchOpen, setSearchOpen] = useState(false);
  const panelRef = useRef<HTMLDivElement>(null);
  const hamburgerRef = useRef<HTMLButtonElement>(null);

  const navLinks = useMemo(() => getNavLinks(singleActiveProduct), [singleActiveProduct]);
  const anchorIds = useMemo(
    () => navLinks.filter((l) => l.href.startsWith("#")).map((l) => l.href.slice(1)),
    [navLinks]
  );

  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 8);
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  // Handles landing on "/#werkzeuge" etc. after a cross-page nav click
  // (e.g. clicking "Werkzeuge" from /todo-cards) or a direct URL load with
  // a hash. Next.js's own built-in hash-scroll-after-navigation doesn't
  // reliably apply the sticky navbar's height offset and can fire before
  // the just-mounted page's layout has settled — this reimplements it with
  // the exact same smoothScrollTo + NAVBAR_HEIGHT offset as the same-page
  // click handlers below, so behavior is identical either way. Runs
  // whenever pathname becomes "/" (covers both the initial load and a
  // client-side transition landing here), a short delay lets layout
  // settle first.
  //
  // The no-hash branch below matters just as much: since the Navbar lives
  // in the root layout (never unmounts across navigations), Next.js's
  // default Link scroll behavior treats "/" as already-visible and leaves
  // the current scrollY untouched instead of resetting to top (see
  // next/dist/docs .../link.md's "maintain scroll position" default).
  // Landing on Home from a page scrolled halfway down (e.g. clicking the
  // logo from a scrolled /shop) then visually "lands" wherever that old
  // offset happens to fall in Home's layout — often right around the
  // Werkzeuge section — instead of at the top. Forcing scrollTo(0, 0) here
  // makes a plain logo/Home navigation always start at the top, exactly
  // like the same-page click handler below already does.
  useEffect(() => {
    if (pathname !== "/") return;
    const hash = window.location.hash.slice(1);
    if (!anchorIds.includes(hash)) {
      window.scrollTo(0, 0);
      return;
    }
    const timer = setTimeout(() => {
      const el = document.getElementById(hash);
      if (el) {
        const top = el.getBoundingClientRect().top + window.scrollY - NAVBAR_HEIGHT;
        smoothScrollTo(top);
      }
    }, 50);
    return () => clearTimeout(timer);
  }, [pathname, anchorIds]);

  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);
  }, [anchorIds]);

  // Close on viewport resize past the structural breakpoint, so the drawer
  // never lingers open behind the (now visible) desktop nav. The hamburger
  // exists through both sub-768 ("Collapsed") and 768-1023 ("Collapsed-CTA")
  // tiers, and only disappears at lg (1024) — see Navbar section of the plan.
  useEffect(() => {
    const onResize = () => {
      if (window.innerWidth >= 1024) setMobileOpen(false);
    };
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);

  // Focus management: move focus into the panel on open, trap Tab within
  // it, close + return focus to the hamburger button on Escape.
  useEffect(() => {
    if (!mobileOpen) return;

    const panel = panelRef.current;
    const focusables = panel?.querySelectorAll<HTMLElement>(
      'a[href], button:not([disabled])'
    );
    focusables?.[0]?.focus({ preventScroll: true });

    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Escape") {
        e.preventDefault();
        setMobileOpen(false);
        hamburgerRef.current?.focus({ preventScroll: true });
        return;
      }
      if (e.key !== "Tab" || !focusables || focusables.length === 0) return;
      const first = focusables[0];
      const last = focusables[focusables.length - 1];
      if (e.shiftKey && document.activeElement === first) {
        e.preventDefault();
        last.focus({ preventScroll: true });
      } else if (!e.shiftKey && document.activeElement === last) {
        e.preventDefault();
        first.focus({ preventScroll: true });
      }
    };

    document.addEventListener("keydown", onKeyDown);
    return () => document.removeEventListener("keydown", onKeyDown);
  }, [mobileOpen]);

  // Background scroll lock while the fullscreen panel is open — same
  // wheel/touchmove interception as NewsletterModal.tsx (see that
  // component's own comment on why this approach over overflow:hidden or
  // position:fixed on body). Needed now that the panel actually covers the
  // viewport instead of pushing page content down in normal flow.
  useEffect(() => {
    if (!mobileOpen) return;

    const isInsidePanel = (target: EventTarget | null) =>
      target instanceof Node && !!panelRef.current?.contains(target);

    const onWheel = (e: WheelEvent) => {
      if (!isInsidePanel(e.target)) e.preventDefault();
    };
    const onTouchMove = (e: TouchEvent) => {
      if (!isInsidePanel(e.target)) e.preventDefault();
    };

    document.addEventListener("wheel", onWheel, { passive: false });
    document.addEventListener("touchmove", onTouchMove, { passive: false });
    return () => {
      document.removeEventListener("wheel", onWheel);
      document.removeEventListener("touchmove", onTouchMove);
    };
  }, [mobileOpen]);

  const closeMobile = () => setMobileOpen(false);

  return (
    // Fragment, not just the <header> — NewsletterModal must NOT be a
    // descendant of it. `backdrop-blur-md` below is a `backdrop-filter`,
    // which (like `transform`) makes its element a new containing block
    // for `position: fixed` descendants per spec. Since the header only
    // gets that class once `scrolled` is true, the modal's `fixed inset-0`
    // backdrop silently stopped being fixed to the viewport and became
    // fixed to the 100px-tall header instead — centering math then ran
    // against that instead of the viewport, clipping the modal to the top
    // of the page. Exactly reproduced whenever the modal was opened while
    // scrolled (e.g. after clicking an anchor link), never at the very
    // top of the page (scrollY <= 8, no backdrop-blur yet) — which is why
    // it looked tied to "clicked an anchor first" rather than to scroll
    // position itself.
    <>
    <header
      // The mobile panel used to be a child of this element and needed the
      // header itself to be a flexible column that could grow for it — it's
      // now a fixed-position sibling instead (see that panel's own comment
      // on why), so this is back to a plain fixed-height bar.
      //
      // onClick here closes the drawer on any click that lands on the bar
      // itself (logo/cart/account/CTAs already call closeMobile() from
      // their own onClick, so this mainly covers clicking empty space in
      // the bar) — safe as a catch-all specifically because the panel is
      // a sibling, not a descendant, so a click inside the open drawer
      // never bubbles up to this handler. The hamburger's own onClick
      // stops propagation so toggling it open doesn't immediately get
      // undone by this same handler.
      onClick={() => mobileOpen && setMobileOpen(false)}
      className={`sticky top-0 z-50 w-full h-[6.25rem] transition-[background-color,backdrop-filter] duration-300 ${
        scrolled || mobileOpen
          ? "bg-bg-base/80 backdrop-blur-md"
          : "bg-bg-base"
      }`}
    >
      <div className="flex h-[6.25rem] w-full shrink-0 items-center px-2 min-[375px]:px-4 sm:px-8">
        <div className="w-full flex items-center justify-between">

          {/* Logo — real navigation to "/" from anywhere else; only when
              already on "/" does it become a same-page smooth-scroll-to-top
              instead (a full Link navigation there would be pointless).
              Previously always preventDefault()'d and silently
              history.replaceState()'d the URL to "/" without an actual
              navigation — from another page that just rewrote the URL bar
              while leaving that page's content on screen. */}
          <Link
            href="/"
            className="shrink-0 w-[76px] min-[375px]:w-[130px] sm:w-[181px]"
            onClick={
              pathname === "/"
                ? (e) => {
                    e.preventDefault();
                    closeMobile();
                    smoothScrollTo(0);
                    history.replaceState(null, "", "/");
                  }
                : closeMobile
            }
          >
            {/* Fixed width/height are the real file dimensions (Next Image
                needs them for optimization/layout); the wrapping Link's own
                w-[112px] min-[375px]:w-[130px] sm:w-[181px] + h-auto here is
                what actually shrinks the rendered logo below 640px — there
                wasn't enough header width for 4 full-44px icons + hamburger
                otherwise (confirmed via an actual Playwright viewport sweep
                down to 320px, not assumed) — the icons themselves are never
                shrunk (see WishlistLink's own comment on why), so the logo
                is what gives on the very smallest phones instead. */}
            <Image
              src="/logo.png"
              alt="einfach produktiv."
              width={181}
              height={61}
              className="w-full h-auto"
              priority
            />
          </Link>

          {/* Nav links — inline only at lg+ (1024px, full "Navbar/Default").
              Between md and lg ("Collapsed-CTA") they move into the
              hamburger drawer while the CTA buttons stay inline — matches
              Figma's Navbar-Mobile component set exactly, see the plan.
              Matches the Figma NavLink component's hover: text never
              changes color, an underline (brand, 2px x 40px) fades in on
              hover and stays on for the active section.

              Deliberate exception to the site-wide sm: (640px) structural
              consolidation (see the 640px-breakpoint plan): this md:/lg:
              3-tier scheme (hamburger-only <768, hamburger+inline-CTAs
              768-1023, full-inline ≥1024) is untouched by that migration.
              Horizontal nav-link overflow is a different failure mode than
              the vertical grid/flex reflows the rest of the site has —
              there's no fluid token that shrinks link text to make a full
              inline nav fit at 640px, and nothing here was ever tied to
              the fluid token scale the way Hero/About/etc. were.

              The gap itself, though, does scale fluidly — just on its own
              1024-1300px range (this nav's own floor/ceiling, only ever
              relevant while it's actually visible), not the site-wide
              640-1440px scale: clamp(1.5rem, 8.696vw - 4.065rem, 3rem) is
              gap-6 (1.5rem) at exactly 1024px, gap-12 (3rem) from 1300px
              up, and eases linearly between — the fixed 48px gap read as
              too wide right where the nav labels themselves have the
              least room (just above 1024px). */}
          <nav className="hidden lg:flex items-center gap-[clamp(1.5rem,8.696vw_-_4.065rem,3rem)]">
            {navLinks.map((link) => {
              const isActive = isNavLinkActive(link.href, pathname, activeSection);
              const underline = (
                <span
                  className={`h-[2px] bg-brand transition-all duration-200 ${
                    isActive ? "w-10 opacity-100" : "w-full opacity-0 group-hover:w-10 group-hover:opacity-100"
                  }`}
                />
              );
              // Anchor links only get the custom in-page smooth-scroll
              // behavior while actually on "/" — its target section
              // doesn't exist on any other route. From elsewhere (e.g.
              // /todo-cards), a plain navigation to "/#werkzeuge" is correct
              // instead: the global `scroll-padding-top` in globals.css
              // already offsets for the sticky navbar on a native hash
              // landing, so no extra JS is needed for that case.
              const isHomeAnchor = link.href.startsWith("#") && pathname === "/";
              const resolvedHref = link.href.startsWith("#") && pathname !== "/" ? `/${link.href}` : link.href;
              return (
                <Link
                  key={link.href}
                  href={resolvedHref}
                  onClick={
                    isHomeAnchor
                      ? (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);
                        }
                      : undefined
                  }
                  className="group flex flex-col items-center gap-1 cursor-pointer"
                >
                  <span className="text-h4 font-semibold text-text-primary tracking-[0.01125rem] whitespace-nowrap">
                    {link.label}
                  </span>
                  {underline}
                </Link>
              );
            })}
          </nav>

          {/* Trailing controls — CTA buttons (md+), cart (always), hamburger
              (below lg). Grouped so spacing stays consistent as individual
              children hide/show across the three breakpoint tiers. */}
          <div className="flex items-center gap-1 min-[375px]:gap-2">
            {/* No gap between these — each is a full 44px touch target
                (never shrunk, even on the smallest phones — a smaller
                target was tried and reverted for being hard to tap
                accurately) with the icon centered inside, so even gap-0
                here still leaves visual space between the actual glyphs.
                All 4
                icons (Search/Account/Wishlist/Cart) are visible at every
                width, including true mobile — the 375px-and-below size
                step exists specifically so all 4 plus the hamburger fit
                without wrapping/overflow on the narrowest real phone
                viewports (checked at 320px). The outer gap-2 is what
                separates this group from the CTA-buttons/hamburger group
                that follows. */}
            <div className="flex items-center">
              {searchEnabled && <SearchButton onOpen={() => setSearchOpen(true)} />}
              <AccountLink />
              {wishlistEnabled && <WishlistLink />}
              <CartLink />
            </div>

            {/* CTA buttons — inline from md (768px) up, i.e. through both
                "Collapsed-CTA" and full Desktop tiers */}
            <div className="hidden md:flex items-center gap-3 p-2">
              {/* Opens the modal (matches the original Figma prototype:
                  Newsletter-Button → Overlay newsletter-overlay) instead of
                  navigating to /newsletter — that page is reached via the
                  Werkzeuge "Impulse & Tipps" card's "Anmelden" link instead,
                  a different, heavier entry point for a different context. */}
              <button
                type="button"
                onClick={() => setNewsletterOpen(true)}
                className="px-6 py-4 rounded-sm border border-[#868686] text-h4 font-bold text-text-primary whitespace-nowrap hover:border-brand hover:text-brand active:scale-[0.97] transition-all"
              >
                Newsletter
              </button>
              <Link
                href="/3x3-system"
                className="px-6 py-4 rounded-sm bg-brand text-h4 font-bold text-text-primary whitespace-nowrap hover:bg-brand-hover active:scale-[0.97] transition-all"
              >
                Mein 3x3-System
              </Link>
            </div>

            {/* Hamburger — visible below lg (1024px): covers both the fully
                collapsed (<768) and "Collapsed-CTA" (768-1023) tiers, since
                the nav links live in the drawer through both. */}
            <button
              ref={hamburgerRef}
              type="button"
              aria-expanded={mobileOpen}
              aria-controls="mobile-nav-panel"
              aria-label={mobileOpen ? "Menü schließen" : "Menü öffnen"}
              onClick={(e) => {
                e.stopPropagation();
                setMobileOpen((v) => !v);
              }}
              className="lg:hidden flex h-11 w-11 items-center justify-center shrink-0"
            >
              <span className="relative block h-4 w-6">
                <span
                  className={`absolute left-0 top-0 block h-0.5 w-6 bg-text-primary transition-transform duration-200 ${
                    mobileOpen ? "translate-y-[7px] rotate-45" : ""
                  }`}
                />
                <span
                  className={`absolute left-0 top-[7px] block h-0.5 w-6 bg-text-primary transition-opacity duration-200 ${
                    mobileOpen ? "opacity-0" : "opacity-100"
                  }`}
                />
                <span
                  className={`absolute left-0 top-[14px] block h-0.5 w-6 bg-text-primary transition-transform duration-200 ${
                  mobileOpen ? "-translate-y-[7px] -rotate-45" : ""
                }`}
              />
            </span>
            </button>
          </div>
        </div>
      </div>

    </header>

      {/* Fullscreen mobile panel — a sibling of <header>, deliberately NOT
          nested inside it (same reason as NewsletterModal, see the
          top-of-file comment: `mobileOpen` gives the header its own
          backdrop-blur, which would make it a new containing block for any
          `position: fixed` descendant and break the panel's fixed-to-
          viewport positioning). Circular clip-path reveal expanding from
          the hamburger's own corner (top-right) — the growing circle
          naturally sweeps toward the opposite corner (bottom-left) last,
          reading as the diagonal wipe this is going for without needing a
          literal diagonal clip polygon. `vmax` (not %) for the radius so
          full coverage holds regardless of viewport aspect ratio. */}
      <AnimatePresence>
        {mobileOpen && (
          <motion.div
            id="mobile-nav-panel"
            ref={panelRef}
            className="lg:hidden fixed inset-0 z-40 bg-bg-base overflow-y-auto"
            initial={{ clipPath: "circle(0vmax at 100% 0%)" }}
            animate={{ clipPath: "circle(150vmax at 100% 0%)" }}
            // Own (slower, gentler) transition for the exit — the shared
            // ease-out curve above is front-loaded (fast start, slow
            // finish), which reads great for the reveal but meant the
            // close shrank most of the way almost immediately, then
            // lingered on a barely-visible sliver — felt abrupt rather
            // than smooth. easeInOut plus a longer duration spreads the
            // shrink evenly instead.
            exit={{ clipPath: "circle(0vmax at 100% 0%)", transition: { duration: 0.7, ease: "easeInOut" } }}
            transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
          >
            <div className="flex flex-col min-h-full pt-[6.25rem]">
              <nav className="flex flex-col flex-1 items-center justify-center gap-6 px-8 py-10 text-center">
                {navLinks.map((link, i) => {
                  const isActive = isNavLinkActive(link.href, pathname, activeSection);
                  const isHomeAnchor = link.href.startsWith("#") && pathname === "/";
                  const resolvedHref = link.href.startsWith("#") && pathname !== "/" ? `/${link.href}` : link.href;
                  // Staggered fade+rise entrance, timed to land after the
                  // clip-path reveal has visibly opened up — same
                  // "fancy but restrained" register as the rest of this
                  // codebase's motion usage (Reveal.tsx et al.), not a
                  // separate animation language just for this panel.
                  const linkMotionProps = {
                    initial: { opacity: 0, y: 12 },
                    animate: { opacity: 1, y: 0 },
                    transition: { delay: 0.15 + i * 0.05, duration: 0.3, ease: "easeOut" as const },
                  };
                  return link.href.startsWith("#") ? (
                    <motion.div key={link.href} {...linkMotionProps}>
                      <Link
                        href={resolvedHref}
                        onClick={
                          isHomeAnchor
                            ? (e) => {
                                e.preventDefault();
                                closeMobile();
                                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);
                              }
                            : closeMobile
                        }
                        className="min-h-11 flex flex-col justify-center gap-1 text-h-feature font-semibold text-text-primary w-fit"
                        style={{ fontFamily: "var(--font-lora)" }}
                      >
                        {link.label}
                        <span
                          className={`h-[2px] bg-brand transition-opacity duration-200 ${
                            isActive ? "w-10 opacity-100" : "w-10 opacity-0"
                          }`}
                        />
                      </Link>
                    </motion.div>
                  ) : (
                    <motion.div key={link.href} {...linkMotionProps}>
                      <Link
                        href={link.href}
                        onClick={closeMobile}
                        className="min-h-11 flex items-center text-h-feature font-semibold text-text-primary"
                        style={{ fontFamily: "var(--font-lora)" }}
                      >
                        {link.label}
                      </Link>
                    </motion.div>
                  );
                })}
              </nav>
              <motion.div
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ delay: 0.15 + navLinks.length * 0.05, duration: 0.3, ease: "easeOut" }}
                className="flex flex-col gap-3 px-8 pb-16 pt-10"
              >
                {/* md:hidden — these two duplicate the inline CTA pair that's
                    already visible in the header itself from md (768px) up (see
                    "Trailing controls" above); only genuinely missing below
                    that, where the inline pair is hidden and the panel is
                    these buttons' only way to reach them. No login/account CTA
                    here (removed — Nutzer-Entscheidung: that's already reachable
                    via the account icon in the header itself, outside this
                    panel, no need to duplicate it inside). */}
                <button
                  type="button"
                  onClick={() => {
                    closeMobile();
                    setNewsletterOpen(true);
                  }}
                  className="md:hidden min-h-11 flex items-center justify-center px-6 py-4 rounded-sm border border-[#868686] text-h4 font-bold text-text-primary hover:border-brand hover:text-brand active:scale-[0.97] transition-all"
                >
                  Newsletter
                </button>
                <Link
                  href="/3x3-system"
                  onClick={closeMobile}
                  className="md:hidden min-h-11 flex items-center justify-center px-6 py-4 rounded-sm bg-brand text-h4 font-bold text-text-primary hover:bg-brand-hover active:scale-[0.97] transition-all"
                >
                  Mein 3x3-System
                </Link>
              </motion.div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      <NewsletterModal open={newsletterOpen} onClose={() => setNewsletterOpen(false)} />
      {searchEnabled && <SearchOverlay open={searchOpen} onClose={() => setSearchOpen(false)} />}
    </>
  );
}
