Alle Templates

Templates/Pools & Gardens

A bold full-page template for pool building and garden design. Custom pools, enclosures and service, calmly planned and cleanly built. The interactive pool finder and the scroll-driven process line are already built in.

Trades & ConstructionKomplette Seite

Die fertige Seite zum Durchscrollen, umschaltbar zwischen Desktop, Tablet und Handy.

Quellcode

Die komplette Seite als eine Datei. Kopiere sie nach ./components und binde die Komponente in deine Route ein.

import { useEffect, useMemo, useRef, useState, type FormEvent, type ReactNode } from "react";
import {
  AnimatePresence,
  motion,
  useReducedMotion,
  useScroll,
  useTransform,
  type Variants,
} from "framer-motion";
import {
  ArrowLeft,
  ArrowRight,
  CalendarCheck,
  CalendarDays,
  Check,
  CheckCircle2,
  Coffee,
  Compass,
  Droplets,
  Gem,
  Hammer,
  Handshake,
  HelpCircle,
  Layers,
  Leaf,
  Mail,
  MapPin,
  Maximize2,
  Menu,
  Minimize2,
  Phone,
  RotateCcw,
  Shield,
  ShieldCheck,
  Sparkles,
  Square,
  Star,
  Triangle,
  Trees,
  Users,
  Waves,
  Wrench,
  X,
  XCircle,
  Zap,
  type LucideIcon,
} from "lucide-react";

// Neutral, fictional pool-building template. Brand: "Blauwerk Pools".
// No real names, addresses or external links. All images live under
// /public/templates/poolbau. Two photos with recognizable people were replaced
// with calm, person-free pool scenes (pool-edge, water-surface).

const ease = [0.22, 1, 0.36, 1] as const;
const BRAND = "Blauwerk Pools";
const IMG = "/templates/poolbau";

const contact = {
  phone: "030 1234 5670",
  phoneHref: "tel:+493012345670",
  phonePretty: "+49 (0) 30 1234 5670",
  email: "info@blauwerk-pools.de",
  addressLine1: "Seestraße 12",
  addressLine2: "12459 Musterstadt",
};

const scopedStyles = `
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500&family=Figtree:wght@400;500&display=swap');

.bw-root {
  font-family: "Figtree", ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
  letter-spacing: -0.005em;
  color: #1c1f22;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
.bw-root h1, .bw-root h2, .bw-root h3, .bw-root h4, .bw-root h5, .bw-root h6 {
  font-family: "Manrope", ui-sans-serif, system-ui, sans-serif;
  font-weight: 400;
  letter-spacing: -0.02em;
}
.bw-root .font-display {
  font-family: "Manrope", ui-sans-serif, system-ui, sans-serif;
}
.bw-root .container-editorial {
  width: 100%;
  margin-inline: auto;
  padding-inline: 1.25rem;
  max-width: 1360px;
}
@media (min-width: 480px) {
  .bw-root .container-editorial { padding-inline: 1.5rem; }
}
@media (min-width: 768px) {
  .bw-root .container-editorial { padding-inline: 2.5rem; }
}
@media (min-width: 1280px) {
  .bw-root .container-editorial { padding-inline: 3.5rem; }
}
@keyframes bwHeroKenBurns {
  0% { transform: scale(1) translate3d(0, 0, 0); }
  100% { transform: scale(1.08) translate3d(0, -1.5%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .bw-root *, .bw-root *::before, .bw-root *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
`;

/* ---------- Helpers: Reveal, RevealWords, useIsMobile ---------- */

function Reveal({
  children,
  delay = 0,
  y = 24,
  className,
  as = "div",
}: {
  children: ReactNode;
  delay?: number;
  y?: number;
  className?: string;
  as?: "div" | "section" | "span" | "li";
}) {
  const reduce = useReducedMotion();
  const MotionTag = motion[as] as typeof motion.div;
  const variants: Variants = {
    hidden: { opacity: 0, y: reduce ? 0 : y },
    show: { opacity: 1, y: 0, transition: { duration: 0.9, ease, delay } },
  };
  return (
    <MotionTag
      className={className}
      initial="hidden"
      whileInView="show"
      viewport={{ once: true, margin: "-80px" }}
      variants={variants}
    >
      {children}
    </MotionTag>
  );
}

function RevealWords({
  text,
  className,
  stagger = 0.05,
}: {
  text: string;
  className?: string;
  stagger?: number;
}) {
  const reduce = useReducedMotion();
  const words = text.split(" ");
  return (
    <motion.span
      className={className}
      initial="hidden"
      whileInView="show"
      viewport={{ once: true, margin: "-80px" }}
      variants={{
        hidden: {},
        show: { transition: { staggerChildren: reduce ? 0 : stagger } },
      }}
    >
      {words.map((w, i) => (
        <motion.span
          key={i}
          className="inline-block whitespace-pre"
          variants={{
            hidden: { opacity: 0, y: reduce ? 0 : 18 },
            show: { opacity: 1, y: 0, transition: { duration: 0.9, ease } },
          }}
        >
          {w + (i < words.length - 1 ? " " : "")}
        </motion.span>
      ))}
    </motion.span>
  );
}

function useIsMobile() {
  const [isMobile, setIsMobile] = useState(false);
  useEffect(() => {
    const mq = window.matchMedia("(max-width: 767px)");
    const onChange = () => setIsMobile(mq.matches);
    onChange();
    mq.addEventListener("change", onChange);
    return () => mq.removeEventListener("change", onChange);
  }, []);
  return isMobile;
}

/* ---------- Mega menu (desktop) ---------- */

const quickLinks = [
  { label: "Home", href: "#top" },
  { label: "Philosophy", href: "#warum" },
  { label: "Pool Finder", href: "#poolfinder" },
  { label: "Projects", href: "#referenzen" },
  { label: "Contact", href: "#kontakt" },
];

const solutions = [
  {
    label: "Polypropylene Pools",
    desc: "Durable basins with precise geometry.",
    image: `${IMG}/polypropylen-pool.jpg`,
    href: "#produkte",
  },
  {
    label: "Pool Enclosures",
    desc: "Protection and a longer season, quietly integrated.",
    image: `${IMG}/megamenu-polypropylen.webp`,
    href: "#produkte",
  },
];

function MegaMenu({ open, onClose }: { open: boolean; onClose: () => void }) {
  return (
    <AnimatePresence>
      {open && (
        <>
          <motion.div
            className="fixed inset-0 z-40 bg-[#1c1f22]/10 backdrop-blur-[2px]"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.4, ease }}
            onClick={onClose}
          />
          <motion.div
            className="fixed inset-x-0 top-0 z-50 origin-top border-b border-[#e6e8eb] bg-white"
            initial={{ y: "-2%", opacity: 0 }}
            animate={{ y: 0, opacity: 1 }}
            exit={{ y: "-2%", opacity: 0 }}
            transition={{ duration: 0.7, ease }}
          >
            <div className="container-editorial pb-16 pt-6">
              <div className="flex items-center justify-between">
                <span className="font-display text-[17px] tracking-tight text-[#1c1f22]">
                  {BRAND}
                </span>
                <button
                  onClick={onClose}
                  aria-label="Close menu"
                  className="flex items-center gap-3 rounded-xl border border-[#e6e8eb] px-3.5 py-2 text-[#1c1f22] transition-colors hover:bg-[#f5f6f7]"
                >
                  <span className="text-[12px] uppercase tracking-[0.18em] text-[#6b7177]">
                    Close
                  </span>
                  <X strokeWidth={1.5} className="h-4 w-4" />
                </button>
              </div>

              <div className="mt-16 grid grid-cols-12 gap-10">
                <motion.div
                  className="col-span-12 lg:col-span-4"
                  initial={{ opacity: 0, y: 14 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.7, ease, delay: 0.15 }}
                >
                  <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">
                    Quick access
                  </p>
                  <ul className="mt-8 space-y-5">
                    {quickLinks.map((l) => (
                      <li key={l.label}>
                        <a
                          href={l.href}
                          onClick={onClose}
                          className="font-display text-[34px] leading-[1.1] text-[#1c1f22] transition-colors hover:text-[#6b7177] md:text-[40px]"
                        >
                          {l.label}
                        </a>
                      </li>
                    ))}
                  </ul>

                  <div className="mt-10 rounded-2xl bg-[#f5f6f7] px-6 py-5">
                    <div className="flex items-center gap-2.5 text-[12px] uppercase tracking-[0.18em] text-[#6b7177]">
                      <Phone strokeWidth={1.5} className="h-3.5 w-3.5" />
                      Give us a call
                    </div>
                    <a
                      href={contact.phoneHref}
                      className="mt-2 block font-display text-[24px] text-[#1c1f22]"
                    >
                      {contact.phonePretty}
                    </a>
                  </div>
                </motion.div>

                <motion.div
                  className="col-span-12 lg:col-span-8"
                  initial={{ opacity: 0, y: 14 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.8, ease, delay: 0.25 }}
                >
                  <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">
                    Explore our solutions
                  </p>
                  <div className="mt-8 grid grid-cols-1 gap-6 sm:grid-cols-2">
                    {solutions.map((s) => (
                      <a
                        key={s.label}
                        href={s.href}
                        onClick={onClose}
                        className="group overflow-hidden rounded-2xl bg-[#f5f6f7] transition-colors"
                      >
                        <div className="overflow-hidden" style={{ aspectRatio: "4/3" }}>
                          <img
                            src={s.image}
                            alt={s.label}
                            loading="lazy"
                            className="h-full w-full object-cover transition-transform duration-700 ease-out group-hover:scale-[1.04]"
                          />
                        </div>
                        <div className="p-6">
                          <h3 className="font-display text-[22px] leading-snug text-[#1c1f22]">
                            {s.label}
                          </h3>
                          <p className="mt-2 text-[14px] leading-relaxed text-[#6b7177]">
                            {s.desc}
                          </p>
                          <span className="mt-5 inline-flex items-center gap-2 text-[13px] text-[#1c1f22]">
                            View details
                            <ArrowRight
                              strokeWidth={1.5}
                              className="h-4 w-4 transition-transform group-hover:translate-x-0.5"
                            />
                          </span>
                        </div>
                      </a>
                    ))}
                  </div>
                </motion.div>
              </div>
            </div>
          </motion.div>
        </>
      )}
    </AnimatePresence>
  );
}

/* ---------- Mobile menu ---------- */

const mobileSections: { label: string; links: { label: string; href: string }[] }[] = [
  {
    label: "Products",
    links: [
      { label: "Polypropylene Pools", href: "#produkte" },
      { label: "Fiberglass Pools", href: "#produkte" },
      { label: "Pool Enclosures", href: "#produkte" },
      { label: "Service & Accessories", href: "#produkte" },
    ],
  },
  {
    label: "Company",
    links: [
      { label: "Pool Finder", href: "#poolfinder" },
      { label: `Why ${BRAND}`, href: "#warum" },
      { label: "How It Works", href: "#ablauf" },
      { label: "Projects", href: "#referenzen" },
      { label: "Contact", href: "#kontakt" },
    ],
  },
];

function MobileMenu({ open, onClose }: { open: boolean; onClose: () => void }) {
  return (
    <AnimatePresence>
      {open && (
        <motion.div
          className="fixed inset-0 z-50 bg-white"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 0.35, ease }}
        >
          <div className="flex h-[100svh] flex-col">
            <div className="container-editorial flex h-16 shrink-0 items-center justify-between border-b border-[#e6e8eb]">
              <span className="font-display text-[17px] tracking-tight text-[#1c1f22]">
                {BRAND}
              </span>
              <button
                onClick={onClose}
                aria-label="Close menu"
                className="inline-flex h-11 w-11 items-center justify-center rounded-full bg-[#f5f6f7] text-[#1c1f22] ring-1 ring-[#1c1f22]/10 transition-colors hover:bg-[#eaf1f6]"
              >
                <X strokeWidth={1.5} className="h-5 w-5" />
              </button>
            </div>

            <div className="container-editorial flex-1 overflow-y-auto pb-6 pt-6">
              {mobileSections.map((section, sIdx) => (
                <div key={section.label} className={sIdx === 0 ? "" : "mt-8"}>
                  <motion.p
                    initial={{ opacity: 0, y: 8 }}
                    animate={{ opacity: 1, y: 0 }}
                    transition={{ duration: 0.5, ease, delay: 0.05 + sIdx * 0.1 }}
                    className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]"
                  >
                    {section.label}
                  </motion.p>
                  <ul className="mt-3">
                    {section.links.map((l, i) => (
                      <motion.li
                        key={l.label}
                        initial={{ opacity: 0, y: 10 }}
                        animate={{ opacity: 1, y: 0 }}
                        transition={{ duration: 0.5, ease, delay: 0.1 + sIdx * 0.1 + i * 0.04 }}
                        className="border-b border-[#e6e8eb] last:border-b-0"
                      >
                        <a
                          href={l.href}
                          onClick={onClose}
                          className="flex items-center justify-between py-3.5 font-display text-[20px] leading-tight text-[#1c1f22] transition-colors active:text-[#6b7177]"
                        >
                          {l.label}
                          <ArrowRight strokeWidth={1.5} className="h-4 w-4 text-[#6b7177]" />
                        </a>
                      </motion.li>
                    ))}
                  </ul>
                </div>
              ))}

              <motion.div
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease, delay: 0.45 }}
                className="mt-8 rounded-2xl border border-[#e6e8eb] bg-[#f5f6f7] p-5"
              >
                <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">
                  Reach us directly
                </p>
                <div className="mt-4 space-y-3 text-[14px] text-[#1c1f22]">
                  <a href={contact.phoneHref} className="flex items-center gap-3">
                    <Phone strokeWidth={1.5} className="h-4 w-4 shrink-0 text-[#6b7177]" />
                    {contact.phone}
                  </a>
                  <a href={`mailto:${contact.email}`} className="flex items-center gap-3 break-all">
                    <Mail strokeWidth={1.5} className="h-4 w-4 shrink-0 text-[#6b7177]" />
                    {contact.email}
                  </a>
                  <p className="flex items-start gap-3">
                    <MapPin strokeWidth={1.5} className="mt-0.5 h-4 w-4 shrink-0 text-[#6b7177]" />
                    <span>
                      {contact.addressLine1}, {contact.addressLine2}
                    </span>
                  </p>
                </div>
              </motion.div>
            </div>

            <motion.div
              initial={{ opacity: 0, y: 12 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ duration: 0.6, ease, delay: 0.55 }}
              className="container-editorial shrink-0 border-t border-[#e6e8eb] bg-white py-4"
              style={{ paddingBottom: "max(1rem, env(safe-area-inset-bottom))" }}
            >
              <a
                href="#kontakt"
                onClick={onClose}
                className="flex w-full items-center justify-center gap-2 rounded-xl bg-[#1c1f22] px-5 py-3.5 text-[14px] text-white transition-colors active:bg-[#1c1f22]/90"
              >
                Request a consultation
                <ArrowRight strokeWidth={1.5} className="h-4 w-4" />
              </a>
            </motion.div>
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}

/* ---------- Header ---------- */

function Header() {
  const [open, setOpen] = useState(false);
  const [scrolled, setScrolled] = useState(false);
  const [hidden, setHidden] = useState(false);
  const lastScrollY = useRef(0);
  const isMobile = useIsMobile();

  useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY;
      setScrolled(y > 80);
      const delta = y - lastScrollY.current;
      if (Math.abs(delta) > 6) {
        if (y > 120 && delta > 0) setHidden(true);
        else if (delta < 0) setHidden(false);
        lastScrollY.current = y;
      }
      if (y <= 80) setHidden(false);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  useEffect(() => {
    document.body.style.overflow = open ? "hidden" : "";
    return () => {
      document.body.style.overflow = "";
    };
  }, [open]);

  const onLight = scrolled;

  return (
    <>
      <header
        className={`fixed inset-x-0 top-0 z-40 transition-all duration-500 ease-out ${
          scrolled ? "bg-white/85 backdrop-blur-md" : "bg-transparent"
        } ${hidden && !open ? "-translate-y-full opacity-0" : "translate-y-0 opacity-100"}`}
      >
        <div className="container-editorial flex h-16 items-center justify-between md:h-20">
          <a href="#top" aria-label={`${BRAND} home`} className="flex items-center">
            <span
              className={`font-display text-[19px] tracking-tight transition-colors duration-500 md:text-[21px] ${
                onLight ? "text-[#1c1f22]" : "text-white"
              }`}
            >
              {BRAND}
            </span>
          </a>

          <button
            onClick={() => setOpen(true)}
            aria-label="Open menu"
            className={`inline-flex h-11 w-11 items-center justify-center rounded-full ring-1 transition-colors ${
              onLight
                ? "bg-[#f5f6f7] text-[#1c1f22] ring-[#1c1f22]/10 hover:bg-[#eaf1f6]"
                : "bg-white/15 text-white ring-white/30 backdrop-blur-md hover:bg-white/25"
            }`}
          >
            <Menu strokeWidth={1.5} className="h-5 w-5" />
          </button>
        </div>
      </header>

      {isMobile ? (
        <MobileMenu open={open} onClose={() => setOpen(false)} />
      ) : (
        <MegaMenu open={open} onClose={() => setOpen(false)} />
      )}
    </>
  );
}

/* ---------- Hero ---------- */

function Hero() {
  const sectionRef = useRef<HTMLElement>(null);
  const reduce = useReducedMotion();
  const isMobile = useIsMobile();

  const { scrollYProgress } = useScroll({
    target: sectionRef,
    offset: ["start start", "end start"],
  });
  const yImg = useTransform(scrollYProgress, [0, 1], ["0%", "14%"]);
  const scaleImg = useTransform(scrollYProgress, [0, 1], [1, 1.08]);

  const parallax = !reduce && !isMobile;

  return (
    <section
      ref={sectionRef}
      id="top"
      className="relative h-[100svh] min-h-[640px] w-full overflow-hidden text-white md:h-[115svh] md:min-h-[820px]"
    >
      <motion.img
        className="absolute inset-0 h-full w-full object-cover will-change-transform"
        style={
          parallax
            ? { y: yImg, scale: scaleImg }
            : !reduce && isMobile
              ? { animation: "bwHeroKenBurns 18s ease-in-out infinite alternate" }
              : undefined
        }
        src={`${IMG}/hero-olive-pool.webp`}
        alt=""
        aria-hidden="true"
        decoding="async"
      />
      <div
        aria-hidden
        className="absolute inset-0"
        style={{
          background:
            "linear-gradient(180deg, rgba(10,15,20,0.55) 0%, rgba(10,15,20,0.25) 35%, rgba(10,15,20,0.15) 55%, rgba(10,15,20,0.6) 100%)",
        }}
      />
      <div
        aria-hidden
        className="absolute inset-y-0 left-0 w-[75%] md:w-[70%] lg:w-[65%]"
        style={{
          WebkitMaskImage:
            "linear-gradient(to right, rgba(0,0,0,1) 0%, rgba(0,0,0,0.9) 25%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,0) 100%)",
          maskImage:
            "linear-gradient(to right, rgba(0,0,0,1) 0%, rgba(0,0,0,0.9) 25%, rgba(0,0,0,0.5) 60%, rgba(0,0,0,0) 100%)",
          background:
            "linear-gradient(to right, rgba(10,15,20,0.3), rgba(10,15,20,0.1) 60%, rgba(10,15,20,0) 100%)",
        }}
      />

      <div className="relative z-10 flex h-full flex-col">
        <div className="container-editorial flex flex-1 flex-col items-center justify-center pb-16 pt-24 text-center md:items-start md:pb-16 md:pt-32 md:text-left">
          <motion.a
            href="#referenzen"
            initial={{ opacity: 0, y: 12 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.9, ease }}
            className="inline-flex w-fit items-center gap-3 rounded-full px-4 py-2.5 backdrop-blur-md transition-colors"
          >
            <span className="flex items-center gap-0.5" aria-hidden>
              {Array.from({ length: 5 }).map((_, i) => (
                <Star key={i} strokeWidth={0} className="h-3.5 w-3.5 fill-[#FBBC04]" />
              ))}
            </span>
            <span className="text-[13px] text-white/90">
              <span className="font-medium">5.0</span>
              <span className="mx-1.5 text-white/40">·</span>
              <span className="text-white/75">28 reviews</span>
            </span>
          </motion.a>

          <h1 className="mt-5 max-w-[18ch] font-display text-[34px] leading-[1.05] text-white sm:max-w-[20ch] sm:text-[56px] md:text-[72px] lg:text-[88px]">
            <RevealWords text="Wake up by the" /> <br className="hidden sm:block" />
            <RevealWords text="water every day" stagger={0.06} />
          </h1>

          <motion.p
            initial={{ opacity: 0, y: 14 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 1, ease, delay: 0.4 }}
            className="mt-4 max-w-[42ch] text-[14px] leading-relaxed text-white/85 [text-shadow:0_1px_12px_rgba(0,0,0,0.45)] sm:mt-5 sm:max-w-[52ch] sm:text-[14.5px] md:mt-7 md:text-[17px]"
          >
            We plan and build pools that suit your garden and your daily life. From the first sketch
            to ongoing maintenance, everything from a single team.
          </motion.p>

          <motion.div
            initial={{ opacity: 0, y: 14 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 1, ease, delay: 0.55 }}
            className="mt-7 flex w-full flex-col items-stretch gap-3 sm:w-auto sm:flex-row sm:items-center md:mt-10"
          >
            <a
              href="#kontakt"
              className="group inline-flex items-center justify-center gap-2 rounded-xl bg-white px-6 py-3.5 text-[14px] text-[#1c1f22] transition-all hover:bg-white/90"
            >
              Request a consultation
              <CalendarCheck
                strokeWidth={1.5}
                className="h-4 w-4 transition-transform group-hover:scale-110"
              />
            </a>
            <a
              href="#poolfinder"
              className="group inline-flex items-center justify-center gap-2 rounded-xl bg-white/10 px-6 py-3.5 text-[14px] text-white backdrop-blur-md transition-all hover:bg-white/20"
            >
              Start the Pool Finder
              <ArrowRight
                strokeWidth={1.5}
                className="h-4 w-4 transition-transform group-hover:translate-x-0.5"
              />
            </a>
          </motion.div>
        </div>
      </div>
    </section>
  );
}

/* ---------- Produktwelten ---------- */

const productCards = [
  {
    icon: Droplets,
    title: "Polypropylene Pools",
    text: "Sturdy single-piece basins that are durable and easy to care for. We plan the dimensions and shape individually around your garden.",
  },
  {
    icon: Layers,
    title: "Fiberglass Pools",
    text: "Prefabricated basins in a range of sizes and colors. A good choice when the pool needs to be ready to use quickly.",
  },
  {
    icon: Shield,
    title: "Pool Enclosures",
    text: "Keep the water warmer, the basin cleaner and the season longer. Available in low or high designs.",
  },
  {
    icon: Wrench,
    title: "Service & Accessories",
    text: "Maintenance, control systems and water care. We stay your point of contact well after handover.",
  },
];

function Produktwelten() {
  return (
    <section id="produkte" className="py-16 md:py-40">
      <div className="container-editorial">
        <div className="grid grid-cols-12 gap-y-10 gap-x-6 md:gap-x-10">
          <Reveal className="col-span-12 lg:col-span-5">
            <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">Product ranges</p>
            <h2 className="mt-5 font-display text-[28px] leading-[1.1] text-[#1c1f22] sm:text-[34px] md:text-[44px] lg:text-[52px]">
              Four solutions, one fits your project.
            </h2>
            <p className="mt-5 max-w-[44ch] text-[15px] leading-relaxed text-[#6b7177] md:mt-6 md:text-[16px]">
              We recommend what suits your garden and your budget. Which type of build makes sense
              is something we discuss together on site.
            </p>
          </Reveal>

          <div className="col-span-12 grid gap-4 sm:grid-cols-2 sm:gap-5 lg:col-span-7">
            {productCards.map((c, i) => (
              <Reveal key={c.title} delay={i * 0.06}>
                <article className="group relative h-full overflow-hidden rounded-2xl bg-[#f5f6f7] p-6 transition-all duration-500 hover:bg-[#eaf1f6] hover:-translate-y-0.5 sm:p-7">
                  <c.icon
                    strokeWidth={1.25}
                    className="h-7 w-7 text-[#1c1f22] transition-transform duration-500 group-hover:translate-x-0.5"
                  />
                  <h3 className="mt-6 font-display text-[20px] leading-snug text-[#1c1f22] sm:mt-8 sm:text-[22px]">
                    {c.title}
                  </h3>
                  <p className="mt-3 text-[14px] leading-relaxed text-[#6b7177] sm:text-[14.5px]">
                    {c.text}
                  </p>
                  <a
                    href="#kontakt"
                    className="mt-6 inline-flex items-center gap-1.5 text-[13px] text-[#1c1f22] sm:mt-7"
                  >
                    Learn more
                    <ArrowRight
                      strokeWidth={1.5}
                      className="h-3.5 w-3.5 transition-transform group-hover:translate-x-1"
                    />
                  </a>
                </article>
              </Reveal>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- Pool Finder ---------- */

type StepKey = "nutzung" | "groesse" | "optik" | "ueberdachung" | "zeit";
type Option = { label: string; icon: LucideIcon; hint?: string };

const steps: { key: StepKey; q: string; sub?: string; options: Option[] }[] = [
  {
    key: "nutzung",
    q: "How do you mainly want to use your pool?",
    sub: "Pick the option that fits best. We can combine several aspects later during the consultation.",
    options: [
      { label: "Relaxation", icon: Leaf, hint: "Calm, warmth, long evenings" },
      { label: "Family", icon: Users, hint: "For everyone in the household" },
      { label: "Fitness", icon: Waves, hint: "Laps, movement, training" },
      { label: "Design & Lifestyle", icon: Sparkles, hint: "Architectural, calm" },
    ],
  },
  {
    key: "groesse",
    q: "Which pool size suits your garden better?",
    options: [
      {
        label: "Compact",
        icon: Minimize2,
        hint: "approx. 6 x 3 m, for smaller gardens or clean layouts",
      },
      {
        label: "Medium",
        icon: Square,
        hint: "approx. 8 x 4 m, balanced for family and relaxation",
      },
      {
        label: "Generous",
        icon: Maximize2,
        hint: "from approx. 10 x 4 m, more presence and room to move",
      },
    ],
  },
  {
    key: "optik",
    q: "Which look do you prefer?",
    options: [
      { label: "Modern and clean", icon: Triangle, hint: "Clean lines" },
      { label: "Natural and subtle", icon: Trees, hint: "Embedded in the garden" },
      { label: "Especially premium and elegant", icon: Gem, hint: "Architecturally prominent" },
    ],
  },
  {
    key: "ueberdachung",
    q: "Is an enclosure of interest to you?",
    options: [
      { label: "Yes", icon: CheckCircle2 },
      { label: "No", icon: XCircle },
      { label: "Maybe", icon: HelpCircle },
    ],
  },
  {
    key: "zeit",
    q: "When would you like to carry out your project?",
    options: [
      { label: "As soon as possible", icon: Zap },
      { label: "In the coming months", icon: CalendarDays },
      { label: "Just looking into it for now", icon: Coffee },
    ],
  },
];

type Recommendation = {
  title: string;
  text: string;
  highlights: { label: string; value: string }[];
  reasons: string[];
  alternative?: { title: string; text: string };
};

function recommend(answers: Partial<Record<StepKey, string>>): Recommendation {
  const nutzung = answers.nutzung;
  const groesse = answers.groesse;
  const optik = answers.optik;
  const ueberdachung = answers.ueberdachung;
  const zeit = answers.zeit;

  let pp = 0;
  let gfk = 0;
  let folie = 0;

  if (nutzung === "Fitness") pp += 2;
  if (nutzung === "Family") gfk += 1;
  if (nutzung === "Relaxation") gfk += 1;
  if (nutzung === "Design & Lifestyle") folie += 2;

  if (optik === "Modern and clean") pp += 2;
  if (optik === "Natural and subtle") gfk += 1;
  if (optik === "Especially premium and elegant") folie += 2;

  if (groesse === "Compact") gfk += 2;
  if (groesse === "Medium") pp += 1;
  if (groesse === "Generous") {
    pp += 1;
    folie += 1;
  }

  if (zeit === "As soon as possible") gfk += 1;

  const ranking = (
    [
      { kind: "pp", score: pp },
      { kind: "gfk", score: gfk },
      { kind: "folie", score: folie },
    ] as const
  )
    .slice()
    .sort((a, b) => b.score - a.score);

  const winner = ranking[0].kind;

  const bauartLabel: Record<typeof winner, string> = {
    pp: "Polypropylene Pool",
    gfk: "Fiberglass Pool",
    folie: "Premium Liner Pool",
  };

  const bauartText: Record<typeof winner, string> = {
    pp: "Custom-made from polypropylene, with shape, size and depth freely planned. A sturdy build with a long lifespan, ideal for swimming laps or a clean, modern look.",
    gfk: "A prefabricated basin made from glass-fiber-reinforced plastic. Quick to build, with a smooth surface and a sensible choice when the pool needs to be usable soon.",
    folie:
      "A premium liner pool with a custom shape and a refined interior lining. Architecturally prominent, with a calm waterline and plenty of design options.",
  };

  const groessenText: Record<string, string> = {
    Compact: "approx. 6 x 3 m, compact layout",
    Medium: "approx. 8 x 4 m, balanced size",
    Generous: "from approx. 10 x 4 m, generously sized",
  };

  const ueberdachungText =
    ueberdachung === "Yes"
      ? "Recommended, extends the season noticeably"
      : ueberdachung === "No"
        ? "Not planned"
        : "Optional, to review during the consultation";

  const zeitText: Record<string, string> = {
    "As soon as possible": "Start possible within a few weeks",
    "In the coming months": "Planning for the upcoming season",
    "Just looking into it for now": "Plan at your own pace, no time pressure",
  };

  const reasons: string[] = [];

  if (nutzung) {
    const nutzungReason: Record<typeof winner, Partial<Record<string, string>>> = {
      pp: {
        Fitness:
          "For swimming laps, a custom length and even depth matter, and polypropylene offers both without standard sizes.",
        "Design & Lifestyle":
          "Polypropylene allows clean edges and precise geometries that suit calm architecture.",
        Relaxation: "Custom benches and lounging ledges can be built right into the basin.",
        Family: "Shape and depth can be chosen so that children and adults enjoy the pool equally.",
      },
      gfk: {
        Family:
          "A prefabricated basin is sturdy, easy to care for and quick to use, which suits everyday family life.",
        Relaxation: "Smooth surfaces and integrated lounging zones make for pleasant comfort.",
        Fitness: "For the occasional swim, a proven standard basin in a fitting length is plenty.",
        "Design & Lifestyle":
          "Fiberglass basins also come in a modern design language, with a short build time as a bonus.",
      },
      folie: {
        "Design & Lifestyle":
          "A liner pool can be shaped freely in form and size and reads as very calm architecturally.",
        Relaxation:
          "The refined interior lining and calm waterline create a particularly relaxed atmosphere.",
        Family: "Generous shapes with shallow entry zones can be planned without standard sizes.",
        Fitness: "Length and depth are freely selectable, including for even laps.",
      },
    };
    const r = nutzungReason[winner][nutzung];
    if (r) reasons.push(r);
  }

  if (optik) {
    const optikReason: Record<typeof winner, Partial<Record<string, string>>> = {
      pp: {
        "Modern and clean":
          "Polypropylene delivers sharp edges and even surfaces with no visible seams.",
        "Especially premium and elegant":
          "The matte surface reads as understated and high quality.",
        "Natural and subtle":
          "With nature-inspired tones, the basin settles calmly into the garden.",
      },
      gfk: {
        "Natural and subtle":
          "Fiberglass basins in soft tones read as understated and blend well into a natural garden.",
        "Modern and clean":
          "There are clean, straight-lined fiberglass models with a modern presence.",
        "Especially premium and elegant":
          "A smooth surface and clean edges look very refined in fiberglass too.",
      },
      folie: {
        "Especially premium and elegant":
          "The custom liner and free form make for a particularly elegant presence.",
        "Modern and clean":
          "Clean geometries can be executed precisely, with a consistently calm interior surface.",
        "Natural and subtle": "Earthy tones and organic shapes are easy to plan.",
      },
    };
    const r = optikReason[winner][optik];
    if (r) reasons.push(r);
  }

  if (groesse) {
    const groesseReason: Record<typeof winner, Partial<Record<string, string>>> = {
      pp: {
        Medium: "For a medium size, the basin can be tailored precisely to the garden.",
        Generous: "Even large spans are achievable without standard sizes.",
        Compact: "In tight spaces, every centimeter can be put to good use.",
      },
      gfk: {
        Compact:
          "For compact gardens, a prefabricated basin is especially efficient and quick to install.",
        Medium: "Proven models in medium sizes often fit directly without special planning.",
        Generous: "Sturdy fiberglass solutions are available in larger dimensions too.",
      },
      folie: {
        Generous: "With larger basins, the freedom of shape really pays off.",
        Medium: "In a medium size, a calm, architectural form can be fine-tuned.",
        Compact: "Even on a small footprint, a custom, premium look is possible.",
      },
    };
    const r = groesseReason[winner][groesse];
    if (r) reasons.push(r);
  }

  if (zeit === "As soon as possible" && winner === "gfk") {
    reasons.push(
      "Since you want to start soon, the short build time of a prefabricated fiberglass basin plays a special role.",
    );
  }
  if (ueberdachung === "Yes") {
    reasons.push(
      "With the enclosure you want, the season extends noticeably, and we plan the basin accordingly.",
    );
  }

  const second = ranking[1];
  const altText: Record<typeof winner, string> = {
    pp: "A custom-made polypropylene basin, free in shape, size and depth.",
    gfk: "A prefabricated fiberglass basin with a short build time and smooth surface.",
    folie: "A premium liner pool with a custom shape and refined interior lining.",
  };
  const showAlternative = second && second.score > 0 && second.score >= ranking[0].score - 1;

  return {
    title: bauartLabel[winner],
    text: bauartText[winner],
    highlights: [
      { label: "Type", value: bauartLabel[winner] },
      ...(groesse ? [{ label: "Size", value: groessenText[groesse] }] : []),
      { label: "Enclosure", value: ueberdachungText },
      ...(zeit ? [{ label: "Timeframe", value: zeitText[zeit] }] : []),
    ],
    reasons,
    alternative: showAlternative
      ? { title: bauartLabel[second.kind], text: altText[second.kind] }
      : undefined,
  };
}

function PoolFinder() {
  const [stepIndex, setStepIndex] = useState(0);
  const [answers, setAnswers] = useState<Partial<Record<StepKey, string>>>({});
  const [done, setDone] = useState(false);

  const step = steps[stepIndex];
  const result = useMemo(() => recommend(answers), [answers]);

  const choose = (value: string) => {
    const next = { ...answers, [step.key]: value };
    setAnswers(next);
    if (stepIndex < steps.length - 1) {
      setTimeout(() => setStepIndex(stepIndex + 1), 260);
    } else {
      setTimeout(() => setDone(true), 260);
    }
  };

  const reset = () => {
    setAnswers({});
    setStepIndex(0);
    setDone(false);
  };

  return (
    <section id="poolfinder" className="bg-[#eaf1f6] py-16 md:py-40">
      <div className="container-editorial">
        <div className="mb-10 max-w-2xl md:mb-16">
          <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">Pool Finder</p>
          <h2 className="mt-5 font-display text-[28px] leading-[1.1] text-[#1c1f22] sm:text-[34px] md:text-[44px] lg:text-[52px]">
            Which pool suits you?
          </h2>
          <p className="mt-5 max-w-[44ch] text-[15px] leading-relaxed text-[#6b7177] md:mt-6 md:text-[16px]">
            Answer a few short questions and we will show you which type of build is generally a
            good fit for your project.
          </p>
        </div>

        <div className="overflow-hidden rounded-3xl bg-white">
          {!done ? (
            <div className="grid grid-cols-1 lg:grid-cols-2">
              <div className="relative isolate min-h-[360px] overflow-hidden sm:min-h-[420px] lg:min-h-[640px]">
                <img
                  src={`${IMG}/pool-garden.webp`}
                  alt=""
                  aria-hidden
                  className="absolute inset-0 -z-20 h-full w-full object-cover"
                />
                <div
                  aria-hidden
                  className="absolute inset-0 -z-10"
                  style={{
                    background:
                      "linear-gradient(180deg, rgba(15,20,25,0.78) 0%, rgba(15,20,25,0.62) 60%, rgba(15,20,25,0.78) 100%)",
                  }}
                />

                <div className="flex h-full flex-col justify-between p-6 sm:p-8 md:p-12">
                  <AnimatePresence mode="wait">
                    <motion.div
                      key={step.key + "-q"}
                      initial={{ opacity: 0, y: 16 }}
                      animate={{ opacity: 1, y: 0 }}
                      exit={{ opacity: 0, y: -10 }}
                      transition={{ duration: 0.55, ease }}
                    >
                      <h3 className="font-display text-[24px] leading-[1.15] text-white sm:text-[30px] md:text-[40px] lg:text-[44px]">
                        {step.q}
                      </h3>
                      {step.sub && (
                        <p className="mt-4 max-w-[40ch] text-[13.5px] leading-relaxed text-white/70 sm:mt-5 sm:text-[14px]">
                          {step.sub}
                        </p>
                      )}
                    </motion.div>
                  </AnimatePresence>

                  <div className="mt-10 flex items-center justify-between sm:mt-12">
                    <button
                      onClick={() => stepIndex > 0 && setStepIndex(stepIndex - 1)}
                      disabled={stepIndex === 0}
                      className="inline-flex items-center gap-1.5 text-[13px] text-white/70 transition-colors hover:text-white disabled:opacity-0"
                    >
                      <ArrowLeft strokeWidth={1.5} className="h-3.5 w-3.5" />
                      Back
                    </button>
                    <div className="flex items-center gap-1.5">
                      {steps.map((_, i) => (
                        <span
                          key={i}
                          className={`h-1 rounded-full transition-all duration-500 ${
                            i === stepIndex
                              ? "w-6 bg-white"
                              : i < stepIndex
                                ? "w-3 bg-white/60"
                                : "w-3 bg-white/20"
                          }`}
                        />
                      ))}
                    </div>
                  </div>
                </div>
              </div>

              <div className="flex flex-col justify-center p-5 sm:p-8 md:p-10">
                <motion.div
                  key={step.key + "-opts"}
                  initial={{ opacity: 0 }}
                  animate={{ opacity: 1 }}
                  transition={{ duration: 0.4, ease }}
                  className="mx-auto flex w-full max-w-[440px] flex-col"
                >
                  {step.options.map((o, idx) => {
                    const active = answers[step.key] === o.label;
                    const Icon = o.icon;
                    return (
                      <button
                        key={o.label}
                        onClick={() => choose(o.label)}
                        className={`group flex items-center gap-4 py-3.5 text-left transition-colors duration-300 sm:gap-5 sm:py-4 ${
                          idx !== 0 ? "border-t border-[#1c1f22]/10" : ""
                        }`}
                      >
                        <Icon
                          strokeWidth={1.4}
                          className={`h-6 w-6 shrink-0 transition-colors sm:h-7 sm:w-7 ${
                            active ? "text-[#1c1f22]" : "text-[#6b7177] group-hover:text-[#1c1f22]"
                          }`}
                        />
                        <div className="flex flex-1 flex-col">
                          <span className="block text-[16px] font-medium leading-tight text-[#1c1f22] sm:text-[17px] md:text-[19px]">
                            {o.label}
                          </span>
                          {o.hint && (
                            <span className="mt-1 block text-[12.5px] leading-snug text-[#6b7177] sm:mt-1.5 sm:text-[13px]">
                              {o.hint}
                            </span>
                          )}
                        </div>
                        <ArrowRight
                          strokeWidth={1.5}
                          className={`h-4 w-4 shrink-0 transition-all duration-300 ${
                            active
                              ? "translate-x-1 text-[#1c1f22] opacity-100"
                              : "text-[#6b7177] opacity-0 group-hover:translate-x-1 group-hover:opacity-100"
                          }`}
                        />
                      </button>
                    );
                  })}

                  <div className="mt-8 flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-[#1c1f22]/10 pt-5 text-[12px] text-[#6b7177] sm:mt-10">
                    <span className="inline-flex items-center gap-1.5">
                      <Check strokeWidth={1.6} className="h-3.5 w-3.5" />
                      Takes just 1 minute
                    </span>
                    <span className="inline-flex items-center gap-1.5">
                      <Check strokeWidth={1.6} className="h-3.5 w-3.5" />
                      No-obligation recommendation
                    </span>
                    <span className="inline-flex items-center gap-1.5">
                      <Check strokeWidth={1.6} className="h-3.5 w-3.5" />
                      Personal advice on request
                    </span>
                  </div>
                </motion.div>
              </div>
            </div>
          ) : (
            <motion.div
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              transition={{ duration: 0.6, ease }}
              className="flex flex-col items-center justify-center px-6 py-16 text-center sm:px-12 sm:py-20 md:py-28"
            >
              <motion.div
                initial={{ scale: 0, opacity: 0 }}
                animate={{ scale: 1, opacity: 1 }}
                transition={{ duration: 0.7, ease, delay: 0.15 }}
                className="relative flex h-16 w-16 items-center justify-center rounded-full bg-[#1c1f22]"
              >
                <motion.span
                  initial={{ scale: 1, opacity: 0.5 }}
                  animate={{ scale: 1.6, opacity: 0 }}
                  transition={{ duration: 1.2, ease, delay: 0.4, repeat: 1, repeatDelay: 0.3 }}
                  className="absolute inset-0 rounded-full bg-[#1c1f22]/30"
                />
                <motion.div
                  initial={{ scale: 0, rotate: -45 }}
                  animate={{ scale: 1, rotate: 0 }}
                  transition={{ duration: 0.5, ease, delay: 0.45 }}
                >
                  <Check strokeWidth={2} className="h-7 w-7 text-white" />
                </motion.div>
              </motion.div>

              <motion.p
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease, delay: 0.65 }}
                className="mt-4 max-w-[40ch] text-[15px] leading-relaxed text-[#6b7177] md:text-[16px]"
              >
                This is likely the best fit for your home.
              </motion.p>

              <motion.h3
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease, delay: 0.7 }}
                className="mt-3 font-display text-[28px] leading-snug text-[#1c1f22] md:text-[40px]"
              >
                {result.title}
              </motion.h3>

              <motion.p
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease, delay: 0.8 }}
                className="mt-5 max-w-[55ch] text-[15px] leading-relaxed text-[#6b7177] md:text-[16px]"
              >
                {result.text}
              </motion.p>

              <motion.dl
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease, delay: 0.85 }}
                className="mt-10 grid w-full max-w-2xl grid-cols-1 gap-px overflow-hidden rounded-2xl border border-[#1c1f22]/10 bg-[#1c1f22]/10 text-left sm:grid-cols-2"
              >
                {result.highlights.map((h) => (
                  <div key={h.label} className="bg-white p-5">
                    <dt className="text-[11px] uppercase tracking-[0.18em] text-[#6b7177]">
                      {h.label}
                    </dt>
                    <dd className="mt-2 text-[14px] leading-snug text-[#1c1f22]">{h.value}</dd>
                  </div>
                ))}
              </motion.dl>

              {result.alternative && (
                <motion.div
                  initial={{ opacity: 0, y: 12 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ duration: 0.6, ease, delay: 0.95 }}
                  className="mt-6 w-full max-w-2xl rounded-2xl border border-dashed border-[#1c1f22]/15 p-6 text-left sm:p-7"
                >
                  <p className="text-[11px] uppercase tracking-[0.18em] text-[#6b7177]">
                    Also worth considering
                  </p>
                  <p className="mt-3 font-display text-[18px] leading-snug text-[#1c1f22] md:text-[20px]">
                    {result.alternative.title}
                  </p>
                  <p className="mt-2 text-[13.5px] leading-relaxed text-[#6b7177]">
                    {result.alternative.text}
                  </p>
                </motion.div>
              )}

              <motion.div
                initial={{ opacity: 0, y: 12 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ duration: 0.6, ease, delay: 1.1 }}
                className="mt-6 flex flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-center"
              >
                <a
                  href="#kontakt"
                  className="inline-flex items-center justify-center gap-2 rounded-xl bg-[#1c1f22] px-6 py-3.5 text-[14px] text-white transition-colors hover:bg-[#1c1f22]/90"
                >
                  Personal consultation
                  <ArrowRight strokeWidth={1.5} className="h-4 w-4" />
                </a>
                <button
                  onClick={reset}
                  className="inline-flex items-center justify-center gap-2 rounded-xl bg-[#f5f6f7] px-6 py-3.5 text-[14px] text-[#1c1f22] transition-colors hover:bg-[#eaf1f6]"
                >
                  <RotateCcw strokeWidth={1.5} className="h-4 w-4" />
                  Start over
                </button>
              </motion.div>
            </motion.div>
          )}
        </div>
      </div>
    </section>
  );
}

/* ---------- Why us ---------- */

const whyFeatures = [
  {
    icon: Compass,
    title: "Individual planning",
    text: "We plan your pool to suit your garden, home and how you use it.",
  },
  {
    icon: Hammer,
    title: "In-house installation teams",
    text: "From excavation to commissioning, our own people do the work.",
  },
  {
    icon: Sparkles,
    title: "Tested materials",
    text: "We rely on manufacturers we have worked with for years.",
  },
  {
    icon: Handshake,
    title: "One point of contact",
    text: "From the first appointment to maintenance, you always have the same contact person.",
  },
];

function WhyUs() {
  const wrapRef = useRef<HTMLDivElement>(null);
  const reduce = useReducedMotion();
  const { scrollYProgress } = useScroll({
    target: wrapRef,
    offset: ["start end", "end start"],
  });
  const y = useTransform(scrollYProgress, [0, 1], ["-6%", "6%"]);

  return (
    <section id="warum" className="py-16 md:py-40">
      <div className="container-editorial">
        <div className="grid grid-cols-12 gap-y-10 gap-x-6 md:gap-y-12 md:gap-x-10">
          <div className="col-span-12 lg:col-span-6">
            <Reveal>
              <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">Why {BRAND}</p>
              <h2 className="mt-5 font-display text-[28px] leading-[1.1] text-[#1c1f22] sm:text-[34px] md:text-[44px] lg:text-[52px]">
                Pools that still impress years later.
              </h2>
              <p className="mt-5 max-w-[50ch] text-[15px] leading-relaxed text-[#6b7177] md:mt-6 md:text-[16px]">
                We have planned and built pools in the region for over twenty years. Each year we
                take on only as many projects as we can see through properly, and we work with fixed
                suppliers and a well-established team.
              </p>
            </Reveal>

            <div className="mt-10 grid gap-x-6 gap-y-7 sm:grid-cols-2 sm:gap-x-8 sm:gap-y-8 md:mt-12">
              {whyFeatures.map((f, i) => (
                <Reveal key={f.title} delay={i * 0.06}>
                  <div>
                    <f.icon strokeWidth={1.25} className="h-6 w-6 text-[#1c1f22]" />
                    <h3 className="mt-4 font-display text-[18px] text-[#1c1f22] sm:mt-5">
                      {f.title}
                    </h3>
                    <p className="mt-2 text-[14px] leading-relaxed text-[#6b7177]">{f.text}</p>
                  </div>
                </Reveal>
              ))}
            </div>
          </div>

          <div className="col-span-12 lg:col-span-6">
            <div className="overflow-hidden rounded-2xl">
              <motion.div
                ref={wrapRef}
                className="will-change-transform"
                style={{ aspectRatio: "4/5", y: reduce ? 0 : y }}
              >
                <img
                  src={`${IMG}/pool-edge.webp`}
                  alt="Clear water at the pool edge with light natural stone and soft shadows"
                  loading="lazy"
                  className="h-full w-full object-cover"
                />
              </motion.div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- Process ---------- */

const processSteps = [
  {
    n: "01",
    title: "Consultation",
    text: "A first conversation on site or at our office. We clarify your wishes and the local conditions.",
  },
  {
    n: "02",
    title: "Planning",
    text: "You receive a concrete quote with a sketch, materials, technology and a fixed price.",
  },
  {
    n: "03",
    title: "Delivery & Installation",
    text: "Our own teams handle excavation, installation and connection. You have a dedicated site manager.",
  },
  {
    n: "04",
    title: "Support",
    text: "After handover there is a thorough walkthrough. Maintenance and service stay with us.",
  },
];

function Process() {
  const trackRef = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({
    target: trackRef,
    offset: ["start 80%", "end 40%"],
  });
  const lineProgress = useTransform(scrollYProgress, [0, 1], ["0%", "100%"]);

  return (
    <section id="ablauf" className="bg-[#f5f6f7] py-16 md:py-40">
      <div className="container-editorial">
        <Reveal>
          <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">How it works</p>
          <h2 className="mt-5 max-w-[20ch] font-display text-[28px] leading-[1.1] text-[#1c1f22] sm:text-[34px] md:text-[44px] lg:text-[52px]">
            Four steps to your pool.
          </h2>
        </Reveal>

        <div ref={trackRef} className="relative mt-12 md:mt-16">
          <div
            aria-hidden
            className="absolute left-[18px] top-0 h-full w-px bg-[#1c1f22]/10 md:left-0 md:right-0 md:top-[18px] md:h-px md:w-auto"
          />
          <motion.div
            aria-hidden
            style={{ height: lineProgress }}
            className="absolute left-[18px] top-0 w-px bg-[#1c1f22] md:hidden"
          />
          <motion.div
            aria-hidden
            style={{ width: lineProgress }}
            className="absolute left-0 top-[18px] hidden h-px bg-[#1c1f22] md:block"
          />

          <div className="relative grid grid-cols-1 gap-8 md:grid-cols-4 md:gap-8">
            {processSteps.map((s, i) => (
              <motion.div
                key={s.n}
                initial={{ opacity: 0, y: 24 }}
                whileInView={{ opacity: 1, y: 0 }}
                viewport={{ once: true, margin: "-15%" }}
                transition={{ duration: 0.7, ease, delay: i * 0.12 }}
                className="relative pl-12 md:pl-0"
              >
                <motion.span
                  initial={{ scale: 0.6, opacity: 0 }}
                  whileInView={{ scale: 1, opacity: 1 }}
                  viewport={{ once: true, margin: "-15%" }}
                  transition={{ duration: 0.5, ease, delay: i * 0.12 + 0.15 }}
                  className="absolute left-0 top-0 z-10 inline-flex h-9 w-9 items-center justify-center rounded-full bg-white text-[11px] tracking-wide text-[#1c1f22] ring-1 ring-[#1c1f22]/10 md:relative"
                >
                  {s.n}
                </motion.span>
                <h3 className="font-display text-[20px] leading-snug text-[#1c1f22] sm:text-[22px] md:mt-8">
                  {s.title}
                </h3>
                <p className="mt-2.5 max-w-[32ch] text-[14px] leading-relaxed text-[#6b7177] sm:text-[14.5px]">
                  {s.text}
                </p>
              </motion.div>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

/* ---------- References ---------- */

const testimonials = [
  {
    quoteHighlight: "The consultation was pleasantly relaxed",
    quoteRest:
      " and in the end we got exactly the pool we had pictured. Deadlines were kept and the site was tidy.",
    name: "The Brenner Family",
    region: "Lake Constance region",
    image: `${IMG}/testimonial-pool-new.webp`,
    alt: "Premium pool in a modern garden design with a seating area",
  },
  {
    quoteHighlight: "Planning, construction and handover went smoothly.",
    quoteRest:
      " Even two years on, we get a quick answer whenever we have a question. Exactly what we were hoping for.",
    name: "M. Hoffmann",
    region: "Allgäu",
    image: `${IMG}/water-surface.webp`,
    alt: "Calm, sunlit water surface of a clear pool",
  },
  {
    quoteHighlight: "We had quotes from several companies.",
    quoteRest: ` ${BRAND} took the time to answer our questions and told us honestly what was possible and what was not. That tipped the scales.`,
    name: "K. & T. Vogel",
    region: "Upper Swabia",
    image: `${IMG}/testimonial-pool-3.webp`,
    alt: "Private garden pool with planting and a seating area",
  },
];

function References() {
  const [currentTestimonialIndex, setCurrentTestimonialIndex] = useState(0);
  const reduce = useReducedMotion();
  const t = testimonials[currentTestimonialIndex];

  return (
    <section id="referenzen" className="bg-[#eaf1f6] py-16 md:py-40">
      <div className="container-editorial">
        <div className="flex items-center justify-between">
          <Reveal>
            <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">
              What our clients say
            </p>
          </Reveal>
          <Reveal delay={0.05}>
            <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">{BRAND}</p>
          </Reveal>
        </div>

        <Reveal delay={0.15} y={32}>
          <div className="group/card mt-10 grid grid-cols-1 overflow-hidden rounded-2xl bg-white shadow-[0_1px_0_0_rgba(0,0,0,0.04)] transition-shadow duration-700 ease-out hover:shadow-[0_30px_80px_-40px_rgba(20,40,60,0.25)] lg:grid-cols-2">
            <div className="flex min-h-[440px] flex-col justify-between p-7 sm:p-10 md:min-h-[580px] md:p-14 lg:min-h-[620px] lg:p-16">
              <div className="relative flex-1">
                <AnimatePresence mode="wait" initial={false}>
                  <motion.div
                    key={`testimonial-${currentTestimonialIndex}`}
                    initial={{ opacity: 0, y: reduce ? 0 : 14 }}
                    animate={{ opacity: 1, y: 0 }}
                    exit={{ opacity: 0, y: reduce ? 0 : -10 }}
                    transition={{ duration: 0.7, ease }}
                  >
                    <p className="font-display text-[22px] leading-[1.25] text-[#1c1f22] sm:text-[28px] sm:leading-[1.2] md:text-[34px] lg:text-[38px]">
                      <span className="text-[#1c1f22]">„{t.quoteHighlight}</span>
                      <span className="text-[#6b7177]">{t.quoteRest}"</span>
                    </p>
                    <p className="mt-8 text-[13px] text-[#6b7177] sm:mt-10">{t.name}</p>
                    <p className="text-[12.5px] text-[#6b7177]/80">{t.region}</p>
                  </motion.div>
                </AnimatePresence>
              </div>

              <div className="mt-10 flex items-center gap-3 sm:mt-12">
                {testimonials.map((item, i) => {
                  const isActive = currentTestimonialIndex === i;
                  return (
                    <button
                      key={item.name}
                      type="button"
                      onClick={() => setCurrentTestimonialIndex(i)}
                      aria-label={`Testimonial from ${item.name}`}
                      aria-pressed={isActive}
                      className="group/dot relative flex h-12 w-12 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-[#1c1f22]/30"
                    >
                      <span
                        className={`absolute -inset-1 rounded-full border transition-all duration-500 ease-out ${
                          isActive
                            ? "scale-100 border-[#1c1f22]/40"
                            : "scale-90 border-transparent group-hover/dot:scale-100 group-hover/dot:border-[#1c1f22]/15"
                        }`}
                      />
                      <img
                        src={item.image}
                        alt={item.name}
                        loading="lazy"
                        decoding="async"
                        className={`h-full w-full rounded-full object-cover transition-all duration-500 ease-out ${
                          isActive ? "opacity-100" : "opacity-60 group-hover/dot:opacity-90"
                        }`}
                      />
                    </button>
                  );
                })}
              </div>
            </div>

            <div className="relative min-h-[320px] overflow-hidden lg:min-h-[620px]">
              <AnimatePresence mode="wait" initial={false}>
                <motion.img
                  key={`testimonial-image-${currentTestimonialIndex}`}
                  src={t.image}
                  alt={t.alt}
                  loading="lazy"
                  decoding="async"
                  initial={{ scale: reduce ? 1 : 1.08, opacity: 0 }}
                  animate={{ scale: 1, opacity: 1 }}
                  exit={{ opacity: 0 }}
                  transition={{ duration: 1.2, ease }}
                  className="absolute inset-0 h-full w-full object-cover will-change-transform"
                />
              </AnimatePresence>
              <div className="pointer-events-none absolute inset-0 bg-gradient-to-tr from-black/15 via-transparent to-transparent opacity-0 transition-opacity duration-700 ease-out group-hover/card:opacity-100" />
            </div>
          </div>
        </Reveal>
      </div>
    </section>
  );
}

/* ---------- Pool metrics ---------- */

const metrics: { k: string; v: string; x: number; y: number }[] = [
  { k: "20+", v: "Years of experience", x: 22, y: 30 },
  { k: "1:1", v: "Personal support", x: 70, y: 22 },
  { k: "A+", v: "Premium materials", x: 80, y: 68 },
  { k: "DE", v: "Rooted in the region", x: 30, y: 74 },
];

function PoolMetrics() {
  const [currentMetricIndex, setCurrentMetricIndex] = useState<number | null>(null);
  const reduce = useReducedMotion();

  return (
    <section id="kennzahlen" className="bg-white py-16 md:py-40">
      <div className="container-editorial">
        <div className="flex items-center justify-between">
          <Reveal>
            <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">
              What defines us
            </p>
          </Reveal>
          <Reveal delay={0.05}>
            <p className="text-[11px] uppercase tracking-[0.22em] text-[#6b7177]">In numbers</p>
          </Reveal>
        </div>

        <div className="mt-8 md:hidden">
          <Reveal y={24}>
            <div className="relative overflow-hidden rounded-2xl" style={{ aspectRatio: "4/3" }}>
              <motion.img
                src={`${IMG}/pool-waterfall.webp`}
                alt="Pool with a waterfall, planting and a seating area"
                loading="lazy"
                decoding="async"
                initial={{ scale: reduce ? 1 : 1.06, opacity: 0 }}
                whileInView={{ scale: 1, opacity: 1 }}
                viewport={{ once: true, margin: "-80px" }}
                transition={{ duration: 1.4, ease }}
                className="absolute inset-0 h-full w-full object-cover will-change-transform"
              />
              <div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/30 via-transparent to-transparent" />
            </div>
          </Reveal>

          <div className="mt-6 grid grid-cols-2 gap-3">
            {metrics.map((m, i) => (
              <Reveal key={m.v} delay={i * 0.05} y={16}>
                <div className="flex h-full flex-col rounded-2xl border border-[#e6e8eb] bg-[#f5f6f7] p-5">
                  <span className="font-display text-[32px] leading-none text-[#1c1f22]">
                    {m.k}
                  </span>
                  <span className="mt-3 text-[12px] uppercase tracking-[0.16em] text-[#6b7177]">
                    {m.v}
                  </span>
                </div>
              </Reveal>
            ))}
          </div>
        </div>

        <Reveal delay={0.15} y={32} className="hidden md:block">
          <div
            className="relative mt-10 overflow-hidden rounded-2xl"
            style={{ aspectRatio: "21/9" }}
          >
            <motion.img
              src={`${IMG}/pool-waterfall.webp`}
              alt="Pool with a waterfall, planting and a seating area"
              loading="lazy"
              decoding="async"
              initial={{ scale: reduce ? 1 : 1.06, opacity: 0 }}
              whileInView={{ scale: 1, opacity: 1 }}
              viewport={{ once: true, margin: "-80px" }}
              transition={{ duration: 1.6, ease }}
              className="absolute inset-0 h-full w-full object-cover will-change-transform"
            />
            <div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/45 via-black/10 to-transparent" />

            {metrics.map((m, i) => {
              const isActive = currentMetricIndex === i;
              return (
                <button
                  key={m.v}
                  type="button"
                  onMouseEnter={() => setCurrentMetricIndex(i)}
                  onMouseLeave={() => setCurrentMetricIndex(null)}
                  onFocus={() => setCurrentMetricIndex(i)}
                  onBlur={() => setCurrentMetricIndex(null)}
                  onClick={() => setCurrentMetricIndex(isActive ? null : i)}
                  className="group absolute -translate-x-1/2 -translate-y-1/2 outline-none"
                  style={{ left: `${m.x}%`, top: `${m.y}%` }}
                  aria-label={`${m.k} ${m.v}`}
                >
                  <span className="relative flex h-4 w-4 items-center justify-center">
                    <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-white/70 opacity-75 motion-reduce:hidden" />
                    <span className="relative inline-flex h-3 w-3 rounded-full bg-white shadow-[0_0_0_4px_rgba(255,255,255,0.18)] transition-transform duration-500 ease-out group-hover:scale-110" />
                  </span>
                  <span
                    className={`pointer-events-none absolute left-1/2 top-full mt-4 -translate-x-1/2 whitespace-nowrap text-center text-white transition-all duration-500 ease-out ${
                      isActive ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0"
                    }`}
                  >
                    <span className="block font-display text-[28px] leading-none">{m.k}</span>
                    <span className="mt-2 block text-[11px] uppercase tracking-[0.18em] text-white/85">
                      {m.v}
                    </span>
                  </span>
                </button>
              );
            })}
          </div>
        </Reveal>
      </div>
    </section>
  );
}

/* ---------- Contact ---------- */

const labelCls = "block text-[11px] uppercase tracking-[0.2em] text-[#1c1f22]/55 mb-3";
const inputCls =
  "w-full border-0 border-b border-[#1c1f22]/15 bg-transparent px-0 py-2.5 text-[16px] text-[#1c1f22] outline-none transition-colors placeholder:text-[#1c1f22]/35 focus:border-[#1c1f22]/50";

function Contact() {
  const [submitted, setSubmitted] = useState(false);

  const onSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setSubmitted(true);
  };

  return (
    <section id="kontakt" className="relative isolate overflow-hidden py-16 md:py-40">
      <div className="absolute inset-0 -z-20">
        <img
          src={`${IMG}/hero-pool.webp`}
          alt=""
          aria-hidden
          className="h-full w-full object-cover"
        />
      </div>
      <div
        aria-hidden
        className="absolute inset-0 -z-10"
        style={{
          background:
            "linear-gradient(180deg, rgba(10,15,20,0.35) 0%, rgba(10,15,20,0.45) 60%, rgba(10,15,20,0.55) 100%)",
        }}
      />

      <div className="container-editorial">
        <div className="grid grid-cols-12 gap-y-12 gap-x-6 md:gap-y-16 md:gap-x-10">
          <Reveal className="col-span-12 lg:col-span-5">
            <div className="flex h-full flex-col">
              <h2 className="font-display text-[32px] leading-[1.05] text-white sm:text-[40px] md:text-[56px] lg:text-[64px]">
                Ready for
                <br />
                your project?
              </h2>
              <p className="mt-5 max-w-[36ch] text-[15px] leading-relaxed text-white/70 md:mt-6 md:text-[16px]">
                Get in touch with our team for a first, no-obligation assessment of your options.
              </p>

              <div className="mt-auto space-y-4 pt-10 sm:space-y-5 md:pt-16">
                <a
                  href={`mailto:${contact.email}`}
                  className="group flex items-center gap-4 text-white/90 transition-colors hover:text-white sm:gap-5"
                >
                  <span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-white/20 sm:h-12 sm:w-12">
                    <Mail strokeWidth={1.5} className="h-[18px] w-[18px]" />
                  </span>
                  <span className="break-all text-[15px] sm:text-[16px]">{contact.email}</span>
                </a>
                <a
                  href={contact.phoneHref}
                  className="group flex items-center gap-4 text-white/90 transition-colors hover:text-white sm:gap-5"
                >
                  <span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-white/20 sm:h-12 sm:w-12">
                    <Phone strokeWidth={1.5} className="h-[18px] w-[18px]" />
                  </span>
                  <span className="text-[15px] sm:text-[16px]">{contact.phone}</span>
                </a>
                <div className="flex items-start gap-4 text-white/90 sm:gap-5">
                  <span className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-white/20 sm:h-12 sm:w-12">
                    <MapPin strokeWidth={1.5} className="h-[18px] w-[18px]" />
                  </span>
                  <span className="text-[15px] leading-snug sm:text-[16px]">
                    {contact.addressLine1}
                    <br />
                    {contact.addressLine2}
                  </span>
                </div>
              </div>
            </div>
          </Reveal>

          <Reveal className="col-span-12 lg:col-span-7" delay={0.1}>
            <div className="rounded-2xl border border-black/5 bg-white p-5 shadow-2xl sm:rounded-3xl sm:p-8 md:p-12">
              {submitted ? (
                <div className="flex min-h-[460px] flex-col items-center justify-center text-center">
                  <div className="flex h-14 w-14 items-center justify-center rounded-full border border-[#1c1f22]/20">
                    <ShieldCheck strokeWidth={1.25} className="h-6 w-6 text-[#1c1f22]" />
                  </div>
                  <h3 className="mt-6 font-display text-[24px] text-[#1c1f22]">
                    Thank you for your message.
                  </h3>
                  <p className="mt-3 max-w-[40ch] text-[15px] leading-relaxed text-[#1c1f22]/70">
                    We usually get back to you within one business day.
                  </p>
                </div>
              ) : (
                <form onSubmit={onSubmit} className="space-y-7 sm:space-y-9">
                  <div className="grid grid-cols-2 gap-x-6 gap-y-7 sm:gap-x-8 sm:gap-y-9">
                    <div className="col-span-2 sm:col-span-1">
                      <label className={labelCls} htmlFor="bw-name">
                        Full name
                      </label>
                      <input
                        id="bw-name"
                        name="name"
                        required
                        maxLength={100}
                        className={inputCls}
                        placeholder="Jane Doe"
                      />
                    </div>
                    <div className="col-span-2 sm:col-span-1">
                      <label className={labelCls} htmlFor="bw-email">
                        Email address
                      </label>
                      <input
                        id="bw-email"
                        name="email"
                        type="email"
                        required
                        maxLength={255}
                        className={inputCls}
                        placeholder="jane@example.com"
                      />
                    </div>
                    <div className="col-span-2">
                      <label className={labelCls} htmlFor="bw-ort">
                        Project location{" "}
                        <span className="normal-case tracking-normal text-[#1c1f22]/40">
                          (optional)
                        </span>
                      </label>
                      <input
                        id="bw-ort"
                        name="ort"
                        maxLength={120}
                        className={inputCls}
                        placeholder="Postal code or city"
                      />
                    </div>
                    <div className="col-span-2">
                      <label className={labelCls} htmlFor="bw-nachricht">
                        Your message
                      </label>
                      <textarea
                        id="bw-nachricht"
                        name="nachricht"
                        rows={3}
                        maxLength={1000}
                        className={inputCls + " resize-none"}
                        placeholder="How can we help you?"
                      />
                    </div>
                  </div>

                  <div className="pt-4">
                    <button
                      type="submit"
                      className="group inline-flex items-center justify-center gap-2 rounded-full bg-[#1c1f22] px-7 py-4 text-[14px] text-white transition-all hover:bg-[#1c1f22]/90"
                    >
                      Send request
                      <ArrowRight
                        strokeWidth={1.5}
                        className="h-4 w-4 transition-transform group-hover:translate-x-0.5"
                      />
                    </button>
                  </div>
                </form>
              )}
            </div>
          </Reveal>
        </div>
      </div>
    </section>
  );
}

/* ---------- Footer ---------- */

const footerCols = [
  {
    title: "Products",
    links: ["Polypropylene Pools", "Fiberglass Pools", "Pool Enclosures", "Service & Accessories"],
  },
  {
    title: "Company",
    links: ["About us", "Projects", "Careers", "Contact"],
  },
];

const legalLinks = ["Imprint", "Privacy", "Terms"];

function Footer() {
  return (
    <footer className="bg-[#f5f6f7]">
      <div className="container-editorial py-16 md:py-20">
        <div className="grid grid-cols-12 gap-y-12 gap-x-8 md:gap-x-10">
          <div className="col-span-12 md:col-span-4">
            <span className="font-display text-[22px] tracking-tight text-[#1c1f22]">{BRAND}</span>
            <p className="mt-6 max-w-[34ch] text-[14px] leading-relaxed text-[#6b7177]">
              Planning, construction and service for pools and enclosures, based in Musterstadt.
            </p>
          </div>

          {footerCols.map((c) => (
            <div key={c.title} className="col-span-6 md:col-span-2">
              <p className="text-[11px] uppercase tracking-[0.2em] text-[#6b7177]">{c.title}</p>
              <ul className="mt-5 space-y-3">
                {c.links.map((l) => (
                  <li key={l}>
                    <a
                      href="#"
                      className="text-[14px] text-[#1c1f22] transition-colors hover:text-[#6b7177]"
                    >
                      {l}
                    </a>
                  </li>
                ))}
              </ul>
            </div>
          ))}

          <div className="col-span-12 md:col-span-4">
            <p className="text-[11px] uppercase tracking-[0.2em] text-[#6b7177]">Contact</p>
            <ul className="mt-5 space-y-4 text-[14px] text-[#1c1f22]">
              <li>
                <a
                  href={contact.phoneHref}
                  className="group flex items-start gap-3 transition-colors hover:text-[#6b7177]"
                >
                  <Phone strokeWidth={1.5} className="mt-0.5 h-4 w-4 shrink-0 text-[#6b7177]" />
                  <span>{contact.phone}</span>
                </a>
              </li>
              <li>
                <a
                  href={`mailto:${contact.email}`}
                  className="group flex items-start gap-3 transition-colors hover:text-[#6b7177]"
                >
                  <Mail strokeWidth={1.5} className="mt-0.5 h-4 w-4 shrink-0 text-[#6b7177]" />
                  <span>{contact.email}</span>
                </a>
              </li>
              <li className="flex items-start gap-3 leading-relaxed text-[#6b7177]">
                <MapPin strokeWidth={1.5} className="mt-0.5 h-4 w-4 shrink-0" />
                <span>
                  {contact.addressLine1}
                  <br />
                  {contact.addressLine2}
                </span>
              </li>
            </ul>
          </div>
        </div>

        <div className="mt-16 flex flex-col items-start justify-between gap-4 border-t border-[#1c1f22]/10 pt-8 text-[12.5px] text-[#6b7177] sm:flex-row sm:items-center">
          <p>
            © {new Date().getFullYear()} {BRAND}. All rights reserved.
          </p>
          <ul className="flex flex-wrap items-center gap-x-6 gap-y-2">
            {legalLinks.map((l) => (
              <li key={l}>
                <a href="#" className="transition-colors hover:text-[#1c1f22]">
                  {l}
                </a>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </footer>
  );
}

/* ---------- Smooth in-page scrolling for hash links ---------- */

function useSmoothAnchors(rootRef: React.RefObject<HTMLDivElement | null>) {
  useEffect(() => {
    const root = rootRef.current;
    if (!root) return;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    const onClick = (e: MouseEvent) => {
      const target = e.target as HTMLElement | null;
      const anchor = target?.closest?.('a[href^="#"]') as HTMLAnchorElement | null;
      if (!anchor) return;
      const href = anchor.getAttribute("href");
      if (!href || href === "#") return;
      const id = href.slice(1);
      const el = document.getElementById(id);
      if (!el) return;
      e.preventDefault();
      el.scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "start" });
    };

    root.addEventListener("click", onClick);
    return () => root.removeEventListener("click", onClick);
  }, [rootRef]);
}

/* ---------- Page ---------- */

export function PoolbauPage() {
  const rootRef = useRef<HTMLDivElement>(null);
  useSmoothAnchors(rootRef);

  return (
    <div ref={rootRef} className="bw-root min-h-screen bg-white text-[#1c1f22]">
      <style>{scopedStyles}</style>
      <Header />
      <Hero />
      <div className="relative z-10 -mt-10 rounded-t-[28px] bg-white md:-mt-16 md:rounded-t-[40px]">
        <Produktwelten />
        <PoolFinder />
        <WhyUs />
        <Process />
        <References />
        <PoolMetrics />
        <Contact />
        <Footer />
      </div>
    </div>
  );
}

export default PoolbauPage;
FAQ

Häufige Fragen

Eine kuratierte Landingpage aus aufeinander abgestimmten Sections der Library, in sinnvoller Reihenfolge von Hero bis Footer. Du kopierst die Sections einzeln und setzt sie mit dem Kompositions-Snippet zusammen.

Deine Frage war nicht dabei? Wir helfen gern weiter.

Kontakt aufnehmen