/* global React, window */
/**
 * shared.jsx, site-wide chrome used on every page.
 *
 * Components:
 * - NavShared       Top-of-page floating navigation (logo, page links, contact CTA).
 * - DotFieldShared  Canvas dot grid with hover hotspot, used as hero background.
 * - GeneralHero     Hero block on the "Über uns" homepage.
 * - ModeToggle      Bottom-right light/dark switch.
 *
 * Quirk: DotFieldShared draws to a canvas with rAF, it relies on resize observers
 * and global mousemove listeners. The dot grid stays visible at low alpha even
 * without a cursor; the cursor adds a blue glow on top.
 *
 * ─── Z-INDEX SCALE ─────────────────────────────────────────────────────────
 * Use these constants everywhere a stacking decision is needed. A flat scale
 * with named tiers prevents the recurring "thing X floated over the nav"
 * problem. Sections create their own stacking contexts via `isolation: isolate`
 * (see window.SECTION_BASE_STYLE) so any internal z-index inside a section
 * stays scoped to that section and can never escape above the site chrome.
 *
 *      0–9    Section internals (decorative bg, content layers, inline pills)
 *      Z.RAIL  = 30   Side rail (compact / floating)
 *      Z.NAV   = 50   Top nav (frosted strip + pills)
 *      Z.OVERLAY = 9998   Modal overlays (drawer scrim)
 *      Z.MODAL   = 9999   Modal content (mobile drawer)
 */

(() => {
const T = window.TOKENS;
const Z = { RAIL: 30, NAV: 50, OVERLAY: 9998, MODAL: 9999 };
window.Z = Z;
// Shared style every full-bleed <section> should spread into its style prop.
// `isolation: isolate` creates a fresh stacking context, guaranteeing that no
// internally-positioned element with z-index N can render above the site nav
// or rail (regardless of how high N is). Use:
//   <section style={{ isolation: "isolate", ...someStyle, ...window.SECTION_BASE_STYLE }}>
window.SECTION_BASE_STYLE = { isolation: "isolate", position: "relative" };

// useDark, dark/light state with localStorage persistence so the choice
// follows the user across page navigation. Returns [dark, setDark] just like
// React.useState. setDark accepts either a boolean or an updater function.
// Pre-paint of the body background is handled in i18n.js (runs before React
// mounts) so there's no white flash when navigating into a page in dark mode.
window.useDark = function useDark() {
  const STORAGE_KEY = "klimaone_dark";
  const getInitial = () => {
    if (typeof window === "undefined") return false;
    try { return window.localStorage.getItem(STORAGE_KEY) === "1"; } catch (e) { return false; }
  };
  const [dark, setDarkRaw] = React.useState(getInitial);
  const setDark = (next) => {
    setDarkRaw((prev) => {
      const value = typeof next === "function" ? next(prev) : !!next;
      try { window.localStorage.setItem(STORAGE_KEY, value ? "1" : "0"); } catch (e) {}
      // Keep body bg in sync so pages with full-bleed sections don't flash
      // white at the edges before the React tree's background catches up.
      try {
        if (typeof document !== "undefined" && document.body) {
          document.body.style.background = value ? (window.TOKENS && window.TOKENS.color && window.TOKENS.color.inkDark) || "#0A0F1F" : "#fff";
        }
      } catch (e) {}
      return value;
    });
  };
  return [dark, setDark];
};

// useViewport, single source of truth for layout breakpoints.
// Returns one of "mobile" (<768), "tablet" (<1024), "desktop" (>=1024).
// Components read this to decide column counts, paddings, and stacking.
// SSR-safe via lazy initial state; updates on window resize.
window.useViewport = function useViewport() {
  const getSize = () => {
    if (typeof window === "undefined") return "desktop";
    const w = window.innerWidth;
    if (w < T.bp.md)  return "mobile";   // < 768
    if (w < T.bp.lg)  return "tablet";   // < 1024
    return "desktop";
  };
  const [size, setSize] = React.useState(getSize);
  React.useEffect(() => {
    const onResize = () => setSize(getSize());
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return size;
};

// ───── Pill geometry, shared by ModePill + LangPill ────────────────────────
// Both pills must have identical outer dimensions so they read as a matching
// pair flanking the centre nav. Single source of truth here; if you tweak it,
// both pills update.
const PILL_INSET = 4;
const PILL_SLOT  = 30;        // both width and height of each slot
const PILL_THUMB = PILL_SLOT - 2; // sliding thumb is slightly inset to fit the pill's rounded contour

// Inline SVG icons used in the mode pill.
const SunSvg = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="12" cy="12" r="4" />
    <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
  </svg>
);
// Stroked crescent moon, kept consistent with the sun's stroke style.
const MoonSvg = (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
  </svg>
);

// Common pill chrome shared by ModePill and LangPill: 2-state container with a
// blue thumb that slides between left/right slot. Children are the two slot
// labels/icons; `right` selects the active position.
const TogglePill = ({ dark, right, children, ariaLabel }) => {
  const surface = dark ? "rgba(15,17,20,0.7)" : "rgba(255,255,255,0.7)";
  const border  = dark ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.06)";
  const thumbLeft = right ? PILL_INSET + PILL_SLOT : PILL_INSET;
  return (
    <div role="group" aria-label={ariaLabel} style={{
      position: "relative",
      display: "inline-flex", alignItems: "center", padding: PILL_INSET,
      background: surface, border: `1px solid ${border}`, borderRadius: T.radius.pill,
      backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)",
      pointerEvents: "auto",
    }}>
      <div aria-hidden="true" style={{
        position: "absolute",
        top: PILL_INSET + 1, left: thumbLeft + 1,
        width: PILL_THUMB, height: PILL_THUMB,
        borderRadius: T.radius.pill,
        background: T.color.blue,
        transition: "left 0.28s cubic-bezier(0.4, 0, 0.2, 1)",
        zIndex: 1,
      }} />
      {children}
    </div>
  );
};

// LangPill, DE/EN toggle. Calls window.setLang() to swap language in place
// (no full reload), so the thumb animates and surrounding text re-renders
// without a page repaint. The <a href> still resolves to the explicit ?lang=
// URL so middle-click / cmd-click / right-click still work.
const LangPill = ({ dark }) => {
  const lang = window.useLang ? window.useLang() : "de";
  const t = window.t || ((k) => k);
  const muted = dark ? "rgba(255,255,255,0.55)" : "rgba(0,0,0,0.45)";
  const isEn = lang === "en";

  const onClick = (target) => (e) => {
    if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
    e.preventDefault();
    if (window.setLang) window.setLang(target);
  };
  const Btn = ({ on, label, target, ariaLabel }) => (
    <a
      href={window.langSwitchUrl ? window.langSwitchUrl(target) : "#"}
      onClick={onClick(target)}
      aria-label={ariaLabel}
      aria-pressed={on}
      style={{
        position: "relative", zIndex: 2,
        width: PILL_SLOT, height: PILL_SLOT,
        display: "flex", alignItems: "center", justifyContent: "center",
        borderRadius: T.radius.pill,
        color: on ? "#fff" : muted,
        textDecoration: "none",
        fontFamily: T.font.sans, fontSize: 12, fontWeight: 600, letterSpacing: "0.04em",
        transition: "color 0.18s ease",
      }}
    >{label}</a>
  );
  return (
    <TogglePill dark={dark} right={isEn} ariaLabel={t("nav.langEn", lang) + " / " + t("nav.langDe", lang)}>
      <Btn on={!isEn} label="DE" target="de" ariaLabel={t("nav.langDe", lang)} />
      <Btn on={isEn}  label="EN" target="en" ariaLabel={t("nav.langEn", lang)} />
    </TogglePill>
  );
};

// ModePill, light/dark toggle. Same chrome as LangPill via TogglePill, so the
// two read as a matching pair around the centre nav.
const ModePill = ({ dark, setDark }) => {
  const muted = dark ? "rgba(255,255,255,0.55)" : "rgba(0,0,0,0.45)";
  const Btn = ({ on, onClick, ariaLabel, children }) => (
    <button
      onClick={onClick}
      aria-label={ariaLabel}
      aria-pressed={on}
      style={{
        position: "relative", zIndex: 2,
        width: PILL_SLOT, height: PILL_SLOT, padding: 0, border: "none",
        borderRadius: T.radius.pill,
        background: "transparent",
        color: on ? "#fff" : muted,
        cursor: "pointer",
        display: "flex", alignItems: "center", justifyContent: "center",
        transition: "color 0.18s ease",
      }}
    >{children}</button>
  );
  return (
    <TogglePill dark={dark} right={dark} ariaLabel={(window.t ? window.t("nav.darkMode") : "Hell-/Dunkelmodus")}>
      <Btn on={!dark} onClick={() => setDark(false)} ariaLabel={(window.t ? window.t("nav.lightMode") : "Hellmodus aktivieren")}>{SunSvg}</Btn>
      <Btn on={dark}  onClick={() => setDark(true)}  ariaLabel={(window.t ? window.t("nav.darkMode") : "Dunkelmodus aktivieren")}>{MoonSvg}</Btn>
    </TogglePill>
  );
};

// Page navigation. `active` highlights one of the two top-level routes.
// Renders inline (desktop) or as logo + ModePill + hamburger (mobile).
// Mobile hamburger opens an animated drawer (slide-in from right) over a
// dim overlay; left edge of the page stays partially visible.
window.NavShared = function NavShared({ dark, setDark, active, leftOffset = 0 }) {
  const lang = window.useLang ? window.useLang() : "de";
  const t = window.t || ((k) => k);
  const withLang = window.withLang || ((h) => h);
  // ROUTES kept as an ordered list so we can iterate (active comparison still
  // matches against the German label for backward compatibility with HTML files
  // that pass active="Über uns" / "Jetzt Partner werden").
  const ROUTES = [
    { key: "about",   de: "Über uns",             label: t("nav.about", lang),   href: withLang("/#top", lang) },
    { key: "partner", de: "Jetzt Partner werden", label: t("nav.partner", lang), href: withLang("/Fuer%20Handwerker#top", lang) },
  ];
  const v = window.useViewport();
  const isMobile = v === "mobile";
  const [open, setOpen] = React.useState(false);

  // Lock body scroll while the mobile menu is open.
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.body.style.overflow = prev; };
  }, [open]);

  // Inline color tokens that depend on dark mode.
  const surface = dark ? "rgba(15,17,20,0.7)" : "rgba(255,255,255,0.7)";
  const surfaceSolid = dark ? T.color.inkDark : "#fff";
  const border = dark ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.06)";
  const inkColor = dark ? "#fff" : T.color.ink;

  // Pill link in the desktop nav. Takes a route entry from ROUTES; `active`
  // (passed in by the page) is matched against the German label so existing
  // HTML files don't need to be updated.
  const link = (route) => {
    const isActive = active === route.de;
    return (
      <a key={route.key} href={route.href} style={{
        fontFamily: T.font.sans, fontSize: 14, fontWeight: 500,
        color: isActive ? "#fff" : (dark ? "rgba(255,255,255,0.78)" : T.color.ink),
        textDecoration: "none", padding: "10px 18px", borderRadius: T.radius.pill,
        background: isActive ? T.color.blue : "transparent", transition: "all 0.2s"
      }}>{route.label}</a>
    );
  };

  // ── Mobile: logo + ModePill + hamburger ─────────────────────────────────
  if (isMobile) {
    const hamLine = { width: 18, height: 2, background: inkColor, transition: "transform 0.2s, opacity 0.2s" };
    const drawerWidth = "min(82%, 360px)";
    return (
      <React.Fragment>
        {/* Force GPU compositing (transform: translateZ(0)) to stabilise the
            mobile nav on iOS Safari, which can briefly drift fixed elements
            when the URL bar collapses on scroll. */}
        <div style={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: Z.NAV, padding: "14px 16px", display: "flex", justifyContent: "space-between", alignItems: "center", pointerEvents: "none", transform: "translateZ(0)", WebkitTransform: "translateZ(0)", willChange: "transform", background: dark ? "rgba(15,17,20,0.55)" : "rgba(255,255,255,0.55)", backdropFilter: "blur(20px)", WebkitBackdropFilter: "blur(20px)", borderBottom: dark ? "1px solid rgba(255,255,255,0.06)" : "1px solid rgba(0,0,0,0.04)" }}>
          <a href="/#top" style={{ display: "flex", alignItems: "center", gap: 8, textDecoration: "none", pointerEvents: "auto", padding: "8px 12px", background: surface, borderRadius: T.radius.pill, backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)", border: `1px solid ${border}` }}>
            <div style={{ width: 10, height: 10, borderRadius: 5, background: T.color.blue }} />
            <span style={{ fontFamily: T.font.sans, fontSize: 17, fontWeight: 500, letterSpacing: "-0.04em", color: inkColor }}>klimaone</span>
          </a>
          <div style={{ display: "flex", alignItems: "center", gap: 8, pointerEvents: "auto" }}>
            <LangPill dark={dark} />
            {setDark && <ModePill dark={dark} setDark={setDark} />}
            <button
              aria-label={open ? t("nav.menuClose", lang) : t("nav.menuOpen", lang)}
              aria-expanded={open}
              onClick={() => setOpen(!open)}
              style={{ width: 44, height: 44, padding: 0, background: surface, border: `1px solid ${border}`, borderRadius: T.radius.pill, backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 4, cursor: "pointer" }}
            >
              <span style={{ ...hamLine, transform: open ? "translateY(6px) rotate(45deg)" : "none" }} />
              <span style={{ ...hamLine, opacity: open ? 0 : 1 }} />
              <span style={{ ...hamLine, transform: open ? "translateY(-6px) rotate(-45deg)" : "none" }} />
            </button>
          </div>
        </div>

        {/* Drawer + overlay are portaled to <body> so they sit at the top of the
            stacking order, not inside the hero's z-index:3 wrapper. Without the
            portal, sections later in document flow paint over them. */}
        {ReactDOM.createPortal(
          <React.Fragment>
            <div
              aria-hidden="true"
              onClick={() => setOpen(false)}
              style={{
                position: "fixed", inset: 0, zIndex: Z.OVERLAY,
                background: "rgba(0,0,0,0.5)",
                opacity: open ? 1 : 0,
                pointerEvents: open ? "auto" : "none",
                transition: "opacity 0.3s ease",
              }}
            />
            <div
              role="dialog"
              aria-modal={open}
              aria-hidden={!open}
              style={{
                position: "fixed", top: 0, right: 0, bottom: 0,
                width: drawerWidth, zIndex: Z.MODAL,
                background: surfaceSolid,
                boxShadow: open ? "-12px 0 40px rgba(0,0,0,0.18)" : "none",
                transform: open ? "translateX(0)" : "translateX(100%)",
                transition: "transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
                padding: "20px 24px 32px",
                display: "flex", flexDirection: "column", gap: 6,
                overflowY: "auto",
              }}
            >
              {/* Close button: drawer sits above the page nav, so render its own
                  close affordance at top-right of the drawer. */}
              <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 20 }}>
                <button
                  aria-label={t("nav.menuClose", lang)}
                  onClick={() => setOpen(false)}
                  style={{ width: 44, height: 44, padding: 0, border: `1px solid ${border}`, background: "transparent", borderRadius: T.radius.pill, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", color: inkColor }}
                >
                  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
                    <path d="M3 3l10 10M13 3L3 13" />
                  </svg>
                </button>
              </div>
              {ROUTES.map((route) => {
                const isActive = active === route.de;
                return (
                  <a key={route.key} href={route.href} onClick={() => setOpen(false)} style={{
                    fontFamily: T.font.display, fontSize: 28, fontWeight: 700, letterSpacing: "-0.025em", textTransform: "uppercase",
                    color: isActive ? T.color.blue : inkColor, textDecoration: "none", padding: "14px 0",
                    borderBottom: `1px solid ${border}`,
                  }}>{route.label}</a>
                );
              })}
              <a href="#kontakt" onClick={() => setOpen(false)} style={{ marginTop: 24, background: T.color.blue, color: "#fff", borderRadius: T.radius.sm, padding: "16px 22px", fontFamily: T.font.sans, fontSize: 16, fontWeight: 600, textDecoration: "none", textAlign: "center" }}>{t("nav.contact", lang)}</a>
            </div>
          </React.Fragment>,
          document.body
        )}
      </React.Fragment>
    );
  }

  // ── Tablet / desktop: 3-block bar.
  //   left:   logo
  //   centre: LangPill, page-link pill, ModePill (centred together)
  //   right:  Kontakt CTA
  return (
    <div style={{ position: "fixed", top: 0, left: leftOffset, right: 0, zIndex: Z.NAV, padding: "20px 32px", display: "flex", justifyContent: "space-between", alignItems: "center", pointerEvents: "none", transition: "left 0.25s ease", gap: 12, background: dark ? "rgba(15,17,20,0.55)" : "rgba(255,255,255,0.55)", backdropFilter: "blur(20px)", WebkitBackdropFilter: "blur(20px)", borderBottom: dark ? "1px solid rgba(255,255,255,0.06)" : "1px solid rgba(0,0,0,0.04)" }}>
      <a href="/#top" style={{ display: "flex", alignItems: "center", gap: 10, textDecoration: "none", pointerEvents: "auto", padding: "8px 14px", background: surface, borderRadius: T.radius.pill, backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)", border: `1px solid ${border}` }}>
        <div style={{ width: 12, height: 12, borderRadius: 6, background: T.color.blue }} />
        <span style={{ fontFamily: T.font.sans, fontSize: 20, fontWeight: 500, letterSpacing: "-0.04em", color: inkColor }}>klimaone</span>
      </a>
      <div style={{ display: "flex", alignItems: "center", gap: 8, pointerEvents: "auto" }}>
        <LangPill dark={dark} />
        <div style={{ display: "flex", gap: 4, padding: 6, background: surface, borderRadius: T.radius.pill, border: `1px solid ${border}`, backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)" }}>
          {ROUTES.map(link)}
        </div>
        {setDark && <ModePill dark={dark} setDark={setDark} />}
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 10, pointerEvents: "auto" }}>
        <a href="#kontakt" style={{ background: dark ? "#fff" : T.color.ink, color: dark ? T.color.ink : "#fff", borderRadius: T.radius.pill, padding: "12px 22px", fontFamily: T.font.sans, fontSize: 14, fontWeight: 600, cursor: "pointer", textDecoration: "none" }}>{t("nav.contact", lang)}</a>
      </div>
    </div>
  );
};

// Animated dot grid background. Two states per dot: a subtle base alpha (always visible)
// and a blue "hot" state when the cursor is near. Smoothed via small lerps for both
// the cursor position and per-dot intensity.
window.DotFieldShared = function DotFieldShared({ dark }) {
  const canvasRef = React.useRef(null);
  const mouse = React.useRef({ x: -9999, y: -9999, sx: -9999, sy: -9999, active: false });

  React.useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    let raf, dpr = window.devicePixelRatio || 1;
    let dotT = null, cols = 0, rows = 0;

    // Dot grid: tight spacing (matches the old dotted background).
    const SPACING = 24, RADIUS_BASE = 1.2, RADIUS_MAX = 5.6;
    const INFLUENCE = 180;
    const CURSOR_LERP = 0.12, DOT_LERP = 0.08;

    const resize = () => {
      const r = canvas.getBoundingClientRect();
      canvas.width = r.width * dpr; canvas.height = r.height * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      cols = Math.ceil(r.width / SPACING) + 1; rows = Math.ceil(r.height / SPACING) + 1;
      dotT = new Float32Array(cols * rows);
    };
    resize();
    const ro = new ResizeObserver(resize); ro.observe(canvas);

    const onMove = (e) => {
      const r = canvas.getBoundingClientRect();
      mouse.current.x = e.clientX - r.left; mouse.current.y = e.clientY - r.top;
      if (!mouse.current.active) { mouse.current.sx = mouse.current.x; mouse.current.sy = mouse.current.y; }
      mouse.current.active = true;
    };
    const onLeave = () => { mouse.current.active = false; mouse.current.x = -9999; mouse.current.y = -9999; };
    window.addEventListener("mousemove", onMove); window.addEventListener("mouseleave", onLeave);

    // Base dot color: light grey (light mode) or dark grey (dark mode), always
    // visible, even without a cursor. Hover hotspot overlays a blue glow.
    const baseRGB = dark ? "255,255,255" : "10,10,10";
    const baseAlphaMax = dark ? 0.32 : 0.28;     // alpha at cursor (peak)
    const baseAlphaMin = dark ? 0.10 : 0.10;     // alpha everywhere else (base visibility)
    const FADE_RADIUS = 520;                     // px, cursor glow radius
    const hotRGB = "15,79,255";

    const draw = () => {
      const w = canvas.clientWidth, h = canvas.clientHeight;
      ctx.clearRect(0, 0, w, h);
      const m = mouse.current;
      m.sx += (m.x - m.sx) * CURSOR_LERP; m.sy += (m.y - m.sy) * CURSOR_LERP;
      const mx = m.sx, my = m.sy, inv2 = INFLUENCE * INFLUENCE;
      const cursorActive = m.active;
      const acx = cursorActive ? mx : w / 2;
      const acy = cursorActive ? my : h / 2;

      for (let cy = 0; cy < rows; cy++) for (let cx = 0; cx < cols; cx++) {
        const x = SPACING/2 + cx*SPACING, y = SPACING/2 + cy*SPACING;
        let target = 0;
        if (cursorActive) {
          const dx = x-mx, dy = y-my, d2 = dx*dx + dy*dy;
          if (d2 < inv2) { const t = 1 - Math.sqrt(d2)/INFLUENCE; target = t*t; }
        }
        const idx = cy*cols + cx; dotT[idx] += (target - dotT[idx]) * DOT_LERP;
        const t = dotT[idx], r = RADIUS_BASE + (RADIUS_MAX - RADIUS_BASE) * t;

        const ddx = x - acx, ddy = y - acy;
        const distFromCursor = Math.sqrt(ddx*ddx + ddy*ddy);
        let radial = 1 - (distFromCursor / FADE_RADIUS);
        radial = Math.max(0, Math.min(1, radial));
        radial = radial * radial;

        const baseA = baseAlphaMin + (baseAlphaMax - baseAlphaMin) * radial;
        const hotA = 0.5 + 0.5 * radial;

        ctx.beginPath();
        ctx.fillStyle = t > 0.04
          ? `rgba(${hotRGB}, ${hotA.toFixed(3)})`
          : `rgba(${baseRGB}, ${baseA.toFixed(3)})`;
        ctx.arc(x, y, r, 0, Math.PI*2); ctx.fill();
      }
      raf = requestAnimationFrame(draw);
    };
    draw();
    return () => { cancelAnimationFrame(raf); ro.disconnect(); window.removeEventListener("mousemove", onMove); window.removeEventListener("mouseleave", onLeave); };
  }, [dark]);

  return <canvas ref={canvasRef} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", display: "block", background: dark ? T.color.inkDark : T.color.surface }} />;
};

// Hero block on the homepage ("Über uns"). Single h1, must remain unique on the page.
// Mobile: tighter padding, full-width buttons, smaller hero clamp.
window.GeneralHero = function GeneralHero({ dark, setDark, leftOffset = 0 }) {
  const v = window.useViewport();
  const isMobile = v === "mobile";
  const lang = window.useLang ? window.useLang() : "de";
  const t = window.t || ((k) => k);
  const withLang = window.withLang || ((h) => h);
  const isEn = lang === "en";
  const padX = isMobile ? T.space[5] : T.space[8];   // 24 vs 48
  const padTop = isMobile ? T.space[10] : T.space[12]; // 80 vs 120
  const padBot = isMobile ? T.space[8] : T.space[10];  // 48 vs 80
  return (
    <section id="top" style={{ position: "relative", overflow: "hidden", color: dark ? "#fff" : T.color.ink, background: dark ? T.color.inkDark : T.color.surface, paddingTop: 72 }}>
      <window.DotFieldShared dark={dark} />
      <div style={{ position: "relative", zIndex: 3, pointerEvents: "none" }}>
        <div style={{ pointerEvents: "auto" }}><window.NavShared dark={dark} setDark={setDark} active="Über uns" leftOffset={isMobile ? 0 : leftOffset} /></div>
        <div style={{ maxWidth: 1600, margin: "0 auto", padding: `${padTop}px ${padX}px ${padBot}px`, pointerEvents: "auto" }}>
          <div style={{ fontFamily: T.font.mono, fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: T.color.blue, marginBottom: T.space[6] }}>{t("hero.eyebrow", lang)}</div>
          <h1 style={{ fontFamily: T.font.display, fontSize: "clamp(40px, 8vw, 112px)", fontWeight: 700, letterSpacing: "-0.045em", lineHeight: 0.95, textTransform: "uppercase", margin: 0, color: dark ? "#fff" : T.color.ink, maxWidth: 1300 }}>
            {t("hero.h1.line1", lang)}<br/><span style={{ color: T.color.blue }}>{t("hero.h1.line2", lang)}</span>
          </h1>
          <div style={{ marginTop: T.space[7], fontFamily: T.font.sans, fontSize: isMobile ? 16 : 20, color: dark ? T.color.mutedDark : T.color.muted, maxWidth: 760, lineHeight: 1.5 }}>
            {t("hero.sub", lang)}
          </div>
          <div style={{ marginTop: T.space[8], display: "flex", gap: 12, alignItems: "stretch", flexDirection: isMobile ? "column" : "row", flexWrap: "wrap" }}>
            <a href={withLang("/Fuer%20Handwerker", lang)} style={{ background: T.color.blue, color: "#fff", border: "none", borderRadius: T.radius.sm, padding: "18px 30px", fontFamily: T.font.sans, fontSize: 16, fontWeight: 600, textDecoration: "none", textAlign: "center" }}>{t("cta.forPartners", lang)}</a>
            <a href={isEn ? "mailto:hello@klima-one.com?subject=Intro%20call" : "mailto:hello@klima-one.com?subject=Erstgespräch"} style={{ background: "transparent", color: dark ? "#fff" : T.color.ink, border: `1.5px solid ${dark ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.25)"}`, borderRadius: T.radius.sm, padding: "16px 26px", fontFamily: T.font.sans, fontSize: 15, fontWeight: 500, cursor: "pointer", textDecoration: "none", textAlign: "center" }}>{t("cta.intro", lang)}</a>
          </div>
        </div>
      </div>
    </section>
  );
};

// ModeToggle is now rendered inline inside NavShared as a pill (see ModePill).
// This stub stays as a no-op so any HTML page that hasn't been updated to remove
// `<window.ModeToggle .../>` keeps working without throwing.
window.ModeToggle = function ModeToggle() { return null; };
})();
