/* global React */
// Top navigation + footer + magic-link sign-in modal.
const { Icon, Wordmark, Btn, Access, useRotatingCTA } = window;

// ---- mega-menu panels ----
function OnBoardMenu({ go, close }) {
  const P = window.PRODUCTS || {};
  const agents = P.agents;
  // Four stops spread across the For Yachts page, in its section order —
  // one short line each.
  const items = [
    { icon: "Bot", name: "See the Agents at Work", desc: "Dedicated agents per department, working to your operational procedures.", act: () => go("onboard", "agents") },
    { icon: "Radio", name: "Your Yacht's Data", desc: "Sensors and NMEA data your agents can act upon.", act: () => go("onboard", "iot") },
    { icon: "SlidersHorizontal", name: "Cloud or Local", desc: "Select the right deployment for your vessel.", act: () => go("onboard", "deployment") }];
  return (
    <div style={{ padding: 14, width: 620, display: "grid", gridTemplateColumns: "1fr 1.05fr", gap: 14 }}>
      {/* featured */}
      <button onClick={() => { go("onboard"); close(); }} className="menu-feature"
        style={{ all: "unset", cursor: "pointer", padding: 18, borderRadius: 15, border: "1px solid rgba(80,134,182,.26)",
          background: "linear-gradient(165deg, rgba(80,134,182,.1), rgba(80,134,182,.02))", display: "flex", flexDirection: "column", justifyContent: "center", transition: "border-color .2s" }}>
        <img src="/assets/images/yacht2.webp" alt="" aria-hidden="true"
          style={{ width: 220, maxWidth: "100%", height: "auto", margin: "0 auto 16px", filter: "drop-shadow(0 0 6px rgba(148,193,218,.4))" }} />
        <div style={{ fontFamily: "var(--font-head)", fontSize: 21, color: "var(--ink)", lineHeight: 1.1 }}>AI Agents, On Board</div>
        <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13, color: "var(--ink-2)", marginTop: 7, lineHeight: 1.5 }}>Dedicated AI agents & dashboards for every crew role. Connected to owner, management, brokers, and suppliers.</div>      </button>
      {/* list */}
      <div>
        <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
          {items.map((m) => (
            <button key={m.name} onClick={() => { m.act(); close(); }} className="menu-item"
              style={{ all: "unset", cursor: "pointer", display: "flex", alignItems: "flex-start", gap: 12, padding: "8px 10px",
                borderRadius: 11, transition: "background .2s" }}>
              <span style={{ width: 34, height: 34, flexShrink: 0, borderRadius: 9, background: "rgba(80,134,182,.07)", border: "1px solid rgba(80,134,182,.15)",
                display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)" }}><Icon name={m.icon} size={16} /></span>
              <span style={{ flex: 1 }}>
                <span style={{ display: "block", fontFamily: "var(--font-ui)", fontSize: 14, fontWeight: 600, color: "var(--ink)" }}>{m.name}</span>
                <span style={{ display: "block", fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 12, color: "var(--ink-3)", marginTop: 2, lineHeight: 1.5 }}>{m.desc}</span>
              </span>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

function OnShoreMenu({ go, close }) {
  const P = window.PRODUCTS || {};
  const custom = P.custom;
  const items = (window.ONSHORE_MENU || []).map((k) => P[k]).filter(Boolean);
  return (
    <div style={{ padding: 14, width: 620, display: "grid", gridTemplateColumns: "1fr 1.05fr", gap: 14 }}>
      {/* featured */}
      <button onClick={() => { go("product:custom"); close(); }} className="menu-feature"
        style={{ all: "unset", cursor: "pointer", padding: 18, borderRadius: 15, border: "1px solid rgba(80,134,182,.26)",
          background: "linear-gradient(165deg, rgba(80,134,182,.1), rgba(80,134,182,.02))", display: "flex", flexDirection: "column", justifyContent: "center", transition: "border-color .2s" }}>
        <img src="/assets/images/yachting-business.webp" alt="" aria-hidden="true"
          style={{ width: 220, maxWidth: "100%", height: "auto", margin: "0 auto 16px", filter: "drop-shadow(0 0 6px rgba(148,193,218,.4))" }} />
        <div style={{ fontFamily: "var(--font-head)", fontSize: 21, color: "var(--ink)", lineHeight: 1.1 }}>{custom.name}</div>
        <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13, color: "var(--ink-2)", marginTop: 7, lineHeight: 1.5 }}>Systems for your yachting business, built with and powered by AI.</div>      </button>
      {/* list */}
      <div>
        <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
          {items.map((p) => (
            <button key={p.key} onClick={() => { go("product:" + p.key); close(); }} className="menu-item"
              style={{ all: "unset", cursor: "pointer", display: "flex", alignItems: "center", gap: 12, padding: "8px 10px",
                borderRadius: 11, transition: "background .2s" }}>
              <span style={{ width: 34, height: 34, flexShrink: 0, borderRadius: 9, background: "rgba(80,134,182,.07)", border: "1px solid rgba(80,134,182,.15)",
                display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)" }}><Icon name={p.icon} size={16} /></span>
              <span style={{ flex: 1 }}>
                <span style={{ display: "block", fontFamily: "var(--font-ui)", fontSize: 14, fontWeight: 600, color: "var(--ink)" }}>{p.name}</span>
                <span style={{ display: "block", fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 12, color: "var(--ink-3)", marginTop: 2 }}>{p.tagline}</span>
              </span>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ---- Resources mega-menu ----
function ResourcesMenu({ go, close }) {
  const items = [
    { icon: "Newspaper", name: "Yachting News", desc: "An AI + yachting digest — the tools, models and developments that matter, in plain terms.", key: "news" },
    { icon: "Workflow", name: "Yachting Simulation", desc: "Test scenarios, generate SOPs and benchmark models before anything goes live.", key: "simulation" }];
  return (
    <div style={{ padding: 14, width: 400 }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 2 }}>
        {items.map((r) => (
          <button key={r.name} onClick={() => { go("product:" + r.key); close(); }} className="menu-item"
            style={{ all: "unset", cursor: "pointer", display: "flex", alignItems: "flex-start", gap: 12, padding: "8px 10px",
              borderRadius: 11, transition: "background .2s" }}>
            <span style={{ width: 34, height: 34, flexShrink: 0, borderRadius: 9, background: "rgba(80,134,182,.07)", border: "1px solid rgba(80,134,182,.15)",
              display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)" }}><Icon name={r.icon} size={16} /></span>
            <span style={{ flex: 1 }}>
              <span style={{ display: "block", fontFamily: "var(--font-ui)", fontSize: 14, fontWeight: 600, color: "var(--ink)" }}>{r.name}</span>
              <span style={{ display: "block", fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 12, color: "var(--ink-3)", marginTop: 3, lineHeight: 1.5 }}>{r.desc}</span>
            </span>
          </button>
        ))}
      </div>
    </div>
  );
}

function Nav({ route, go, solid, onSignIn, progress }) {
  const ctaText = useRotatingCTA();
  const [open, setOpen] = React.useState(null); // 'board' | 'shore' | null
  const [mobileOpen, setMobileOpen] = React.useState(false);
  const [ctaInView, setCtaInView] = React.useState(false);
  const closeT = React.useRef(null);
  const enter = (k) => { clearTimeout(closeT.current); setOpen(k); };
  const leave = () => { clearTimeout(closeT.current); closeT.current = setTimeout(() => setOpen(null), 260); };
  const close = () => { clearTimeout(closeT.current); setOpen(null); };

  // Hide the header's own CTA whenever a page's own "Begin Onboarding" button
  // (marked data-hide-nav-cta) is visible on screen, not just at the very top.
  React.useEffect(() => {
    const els = Array.from(document.querySelectorAll("[data-hide-nav-cta]"));
    if (!els.length) { setCtaInView(false); return; }
    const visible = new Set();
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) visible.add(e.target); else visible.delete(e.target); });
      setCtaInView(visible.size > 0);
    }, { threshold: 0.2 });
    els.forEach((el) => io.observe(el));
    return () => io.disconnect();
  }, [route]);

  React.useEffect(() => { setMobileOpen(false); }, [route]);

  const isBoard = route === "onboard";
  // News and Simulation live in the Resources menu — highlight that trigger,
  // not "For Yachting Businesses", when one of them is open.
  const isResources = route === "product:news" || route === "product:simulation";
  const isShore = route.indexOf("product:") === 0 && !isResources;

  // An embedded product route surfaces a subdomain's live landing (the product
  // carries `embedUrl`). On those pages we brand the header with the product's
  // own wordmark and blend the nav into the dark embed page. Gated here so every
  // other route (home, onboard, non-embed products) is visually untouched.
  const embedProduct = React.useMemo(() => {
    if (route.indexOf("product:") !== 0) return null;
    const p = (window.PRODUCTS || {})[route.slice("product:".length)];
    return p && p.embedUrl ? p : null;
  }, [route]);

  // News and Simulation render natively (no embed) but still brand the header
  // with their own product lockup — their heroes carry no wordmark of their own.
  const lockupProduct = React.useMemo(() => {
    if (embedProduct) return embedProduct;
    if (route === "product:news") return (window.PRODUCTS || {}).news || null;
    if (route === "product:simulation") return (window.PRODUCTS || {}).simulation || null;
    if (route === "product:privacy") return (window.PRODUCTS || {}).privacy || null;
    return null;
  }, [route, embedProduct]);

  const trigger = (label, k, active) => (
    <button onMouseEnter={() => enter(k)} onClick={() => enter(open === k ? null : k)}
      style={{ all: "unset", cursor: "pointer", fontFamily: "var(--font-ui)", fontSize: 13.5, fontWeight: 400, letterSpacing: ".01em",
        color: active || open === k ? "var(--ink)" : "var(--ink-3)", transition: "color .25s", padding: "6px 2px",
        display: "inline-flex", alignItems: "center", gap: 5, whiteSpace: "nowrap" }}>
      {label}<Icon name="ChevronDown" size={14} style={{ opacity: .7, transform: open === k ? "rotate(180deg)" : "none", transition: "transform .25s" }} />
    </button>
  );

  return (
    <nav onMouseLeave={leave} style={{ position: "fixed", top: 0, left: 0, right: 0, zIndex: 60,
      // Embedded product pages keep the header transparent over the landing hero
      // (its own image bleeds up behind it); on scroll it only picks up a light
      // translucent tint + blur for legibility — never the heavy opaque fill, and
      // no bottom hairline. Non-embed routes keep the original lighter fill.
      background: (solid || open)
        ? (embedProduct ? "rgba(7,12,21,.34)" : "rgba(11,31,51,.82)")
        : "transparent",
      borderBottom: `1px solid ${(solid || open) && !embedProduct ? "var(--hairline)" : "transparent"}`,
      backdropFilter: solid || open ? (embedProduct ? "blur(10px)" : "blur(16px) saturate(1.2)") : "none",
      transition: "background .4s, border-color .4s" }}>
      <div style={{ height: 3, background: "var(--wa-04)" }}>
        <div style={{ height: "100%", width: `${progress * 100}%`, background: "linear-gradient(90deg, var(--cyan-deep), var(--cyan))",
          transition: "width .1s linear", opacity: progress > 0.005 ? 1 : 0 }} />
      </div>
      <div style={{ maxWidth: 1240, margin: "0 auto", padding: "0 32px", height: lockupProduct ? 74 : 69, display: "flex",
        alignItems: "center", justifyContent: "space-between" }}>
        <button onClick={() => { go("home"); close(); }} style={{ all: "unset", cursor: "pointer" }}>
          {lockupProduct
            ? (() => {
                // Product lockup: the YachtingStack mark, then the product name set
                // solid ("Yachting" in ink + the rest in the accent, no space so it
                // reads as one word) with a small "by YachtingStack" line beneath.
                const rest = lockupProduct.name.replace(/^Yachting\s*/, "");
                return (
                  <span style={{ display: "inline-flex", alignItems: "center", gap: 9 }}>
                    <img src="/assets/logo/YachtingStack-Logo.png" alt="YachtingStack" style={{ height: 30, width: "auto" }} />
                    <span style={{ display: "inline-flex", flexDirection: "column", lineHeight: 1.02 }}>
                      <span style={{ fontFamily: "var(--font-ui)", fontWeight: 300, letterSpacing: "-0.01em", fontSize: 19 }}>
                        <span style={{ color: "var(--ink)" }}>Yachting</span><span style={{ color: "var(--cyan)" }}>{rest}</span>
                      </span>
                      <span style={{ fontFamily: "var(--font-ui)", fontWeight: 400, fontSize: 10.5, color: "var(--ink-3)", letterSpacing: ".01em", marginTop: 5 }}>
                        by <span style={{ color: "var(--ink)" }}>YachtingStack</span>
                      </span>
                    </span>
                  </span>
                );
              })()
            : <Wordmark />}
        </button>
        <div style={{ display: "flex", alignItems: "center", gap: 30 }}>
          <div className="nav-links" style={{ display: "flex", gap: 28, position: "relative" }}>
            {trigger("For Yachts", "board", isBoard)}
            {trigger("For Yachting Businesses", "shore", isShore)}
            {trigger("Resources", "resources", isResources)}

            {/* dropdown panel — anchored under its trigger. The wrapper's paddingTop
                bridges the visual gap with a hover-active area, so the pointer never
                leaves the nav on the way down (no more vanishing menu). */}
            {open && (
              <div onMouseEnter={() => enter(open)} className="ymdrop"
                style={{ position: "absolute", top: "100%", paddingTop: 10,
                  ...(open === "resources" ? { right: 0 } : { left: "50%", transform: "translateX(-50%)" }) }}>
                <div style={{ borderRadius: 18, background: "rgb(13,29,46)",
                  border: "1px solid var(--hairline-2)", boxShadow: "0 36px 90px -34px rgba(0,0,0,.85)", overflow: "hidden" }}>
                  {open === "board" ? <OnBoardMenu go={go} close={close} /> : open === "shore" ? <OnShoreMenu go={go} close={close} /> : <ResourcesMenu go={go} close={close} />}
                </div>
              </div>
            )}
          </div>
          <div className="nav-desktop-actions" style={{ display: "flex", alignItems: "center" }}>
            <button onClick={onSignIn} title="Sign in" style={{ all: "unset", cursor: "pointer", display: "inline-flex",
              padding: 9, borderRadius: 10, color: "var(--ink-3)", transition: "all .25s" }}
              onMouseEnter={(e) => { e.currentTarget.style.color = "var(--ink)"; }}
              onMouseLeave={(e) => { e.currentTarget.style.color = "var(--ink-3)"; }}>
              <Icon name="Lock" size={15} />
            </button>
            <span style={{ display: "inline-flex", overflow: "hidden",
              maxWidth: (solid && !ctaInView) ? 220 : 0, marginLeft: (solid && !ctaInView) ? 14 : 0, opacity: (solid && !ctaInView) ? 1 : 0,
              pointerEvents: (solid && !ctaInView) ? "auto" : "none",
              transition: "max-width .35s var(--ease), margin-left .35s var(--ease), opacity .25s var(--ease)" }}>
              <Btn size="sm" onClick={() => { go("onboarding"); close(); }} iconRight="ArrowRight">{ctaText}</Btn>
            </span>
          </div>
          <button onClick={() => setMobileOpen((v) => !v)} className="nav-mobile-toggle" title="Menu"
            style={{ all: "unset", cursor: "pointer", padding: 8, borderRadius: 10, color: "var(--ink)", alignItems: "center", justifyContent: "center" }}>
            <Icon name={mobileOpen ? "X" : "Menu"} size={22} />
          </button>
        </div>
      </div>

      {/* mobile panel */}
      {mobileOpen && (
        <div className="ymdrop" style={{ position: "absolute", top: lockupProduct ? 81 : 69, left: 0, right: 0, background: "rgb(11,26,42)",
          borderTop: "1px solid var(--hairline)", padding: "10px 20px 24px", display: "flex", flexDirection: "column", gap: 2 }}>
          {/* primary destinations — one per line, no separator bars */}
          {[
            ["Home", () => go("home")],
            ["For Yachts", () => go("onboard")],
            ["For Yachting Businesses", () => go("product:custom")],
          ].map(([label, fn]) => (
            <button key={label} onClick={() => { fn(); setMobileOpen(false); }} style={{ all: "unset", cursor: "pointer",
              fontFamily: "var(--font-ui)", fontSize: 15, color: "var(--ink-2)", padding: "12px 6px" }}>
              {label}
            </button>
          ))}
          {/* the suite — labelled divider, then a two-column matrix of products */}
          <div style={{ display: "flex", alignItems: "center", gap: 10, margin: "12px 0 10px" }}>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 10, fontWeight: 600, letterSpacing: ".16em", textTransform: "uppercase", color: "var(--ink-4)" }}>The Suite</span>
            <span aria-hidden="true" style={{ flex: 1, height: 1, background: "var(--hairline)" }} />
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            {[
              ["AI Agents On Board", () => go("onboard")],
              ["AI Agents On Shore", () => go("product:agents")],
              ["Yachting Visibility", () => go("product:visibility")],
              ["Yachting Privacy", () => go("product:privacy")],
              ["Yachting AI News", () => go("product:news")],
              ["Yachting Simulation", () => go("product:simulation")],
            ].map(([label, fn]) => (
              <button key={label} onClick={() => { fn(); setMobileOpen(false); }} style={{ all: "unset", boxSizing: "border-box", cursor: "pointer",
                fontFamily: "var(--font-ui)", fontSize: 13.5, color: "var(--ink-2)", padding: "11px 12px",
                border: "1px solid var(--hairline)", borderRadius: 10, background: "var(--wa-02)" }}>
                {label}
              </button>
            ))}
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 16 }}>
            <Btn variant="ghost" full onClick={() => { onSignIn(); setMobileOpen(false); }} icon="Lock">Sign in</Btn>
            <Btn full onClick={() => { go("onboarding"); setMobileOpen(false); }} iconRight="ArrowRight">{ctaText}</Btn>
          </div>
        </div>
      )}

    </nav>
  );
}

// Decodes a Supabase JWT's payload (base64url) without needing a JWT library.
function decodeJwtPayload(token) {
  try {
    const b64 = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
    const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
    const json = decodeURIComponent(atob(padded).split("").map((c) => "%" + c.charCodeAt(0).toString(16).padStart(2, "0")).join(""));
    return JSON.parse(json);
  } catch (e) { return null; }
}

function SignInModal({ open, onClose }) {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState("");
  const [systems, setSystems] = React.useState(null); // null until signed in, then an array (possibly empty)
  const [notice, setNotice] = React.useState("");

  // Systems the signed-in JWT entitles the user to — shared shape for restore + fresh login.
  const systemsFromToken = (accessToken) => {
    const payload = decodeJwtPayload(accessToken);
    const entitlements = (payload && payload.app_metadata && payload.app_metadata.entitlements) || {};
    return Object.keys(entitlements).map((key) => window.ENTITLEMENT_SYSTEMS[key] && { key, ...window.ENTITLEMENT_SYSTEMS[key] }).filter(Boolean);
  };

  React.useEffect(() => {
    if (!open) return;
    setEmail(""); setPassword(""); setError(""); setLoading(false); setSystems(null); setNotice("");
    // SSO: an existing shared-cookie session (from here or any *.yachtingstack.ai app)
    // skips the form and goes straight to the systems list.
    if (window.YM_AUTH) {
      window.YM_AUTH.auth.getSession().then(({ data }) => {
        if (data && data.session) setSystems(systemsFromToken(data.session.access_token));
      }).catch(() => {});
    }
  }, [open]);
  if (!open) return null;
  const valid = /.+@.+\..+/.test(email) && password.length > 0;

  const submit = async (e) => {
    e.preventDefault();
    if (!valid || loading) return;
    setLoading(true); setError("");
    try {
      if (!window.YM_AUTH) throw new Error("Sign-in is unavailable right now. Please try again shortly.");
      // supabase-js with the shared cookie storage — this WRITES the .yachtingstack.ai
      // session cookie, so the login carries into every suite system (SSO).
      const { data, error } = await window.YM_AUTH.auth.signInWithPassword({ email, password });
      if (error) throw new Error(error.message === "Invalid login credentials" ? "Invalid email or password." : error.message);
      setSystems(systemsFromToken(data.session.access_token));
    } catch (err) {
      setError(err.message || "Something went wrong. Try again.");
    } finally {
      setLoading(false);
    }
  };

  const signOut = async () => {
    setLoading(true);
    try { if (window.YM_AUTH) await window.YM_AUTH.auth.signOut(); } catch (e) { /* local cookie is cleared regardless */ }
    setSystems(null); setEmail(""); setPassword(""); setLoading(false);
  };

  const sendReset = async () => {
    if (!/.+@.+\..+/.test(email)) { setError("Enter your email above, then tap reset."); setNotice(""); return; }
    setLoading(true); setError(""); setNotice("");
    try {
      if (!window.YM_AUTH) throw new Error("Reset is unavailable right now. Please try again shortly.");
      // Central reset page (served at /reset); it sends the user back here after they set a new password.
      const resetPage = `${window.location.origin}/reset?return=${encodeURIComponent(window.location.origin)}`;
      const { error } = await window.YM_AUTH.auth.resetPasswordForEmail(email, { redirectTo: resetPage });
      if (error) throw new Error(error.message || "Could not send the reset email.");
      setNotice("Password reset link sent — check your inbox.");
    } catch (err) {
      setError(err.message || "Could not send the reset email.");
    } finally {
      setLoading(false);
    }
  };

  return (
    <div onClick={onClose} className="ymfade" style={{ position: "fixed", inset: 0, zIndex: 90, background: "rgba(4,12,20,.72)",
      backdropFilter: "blur(6px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
      {/* translateZ(0): own compositor layer — without it Chromium paints the
          overlay's blurred backdrop OVER this opaque card on mobile */}
      <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 440, background: "var(--surface)", transform: "translateZ(0)",
        border: "1px solid var(--hairline-2)", borderRadius: 20, padding: 32, boxShadow: "0 40px 120px -30px rgba(0,0,0,.8)" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 18 }}>
          <Wordmark />
          <button onClick={onClose} style={{ all: "unset", cursor: "pointer", color: "var(--ink-3)" }}><Icon name="X" size={18} /></button>
        </div>
        {!systems ? (
          <>
            <h3 style={{ fontFamily: "var(--font-head)", fontSize: 23, color: "var(--ink)", margin: "0 0 6px" }}>Sign in</h3>
            <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14, color: "var(--ink-3)", lineHeight: 1.6, margin: "0 0 20px" }}>
              Sign in with your YachtingStack ID to see the systems you're authorised for.
            </p>
            <form onSubmit={submit}>
              <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@yourcompany.yachts" autoFocus
                style={{ width: "100%", boxSizing: "border-box", fontFamily: "var(--font-ui)", fontSize: 14.5, color: "var(--ink)", padding: "13px 15px",
                  borderRadius: 11, border: "1px solid var(--hairline-2)", background: "var(--panel)", outline: "none", marginBottom: 10 }}
                onFocus={(e) => e.target.style.borderColor = "var(--cyan)"} onBlur={(e) => e.target.style.borderColor = "var(--hairline-2)"} />
              <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Password"
                style={{ width: "100%", boxSizing: "border-box", fontFamily: "var(--font-ui)", fontSize: 14.5, color: "var(--ink)", padding: "13px 15px",
                  borderRadius: 11, border: "1px solid var(--hairline-2)", background: "var(--panel)", outline: "none", marginBottom: 8 }}
                onFocus={(e) => e.target.style.borderColor = "var(--cyan)"} onBlur={(e) => e.target.style.borderColor = "var(--hairline-2)"} />
              <div style={{ display: "flex", justifyContent: "flex-end", marginBottom: 14 }}>
                <button type="button" onClick={sendReset} disabled={loading}
                  style={{ all: "unset", cursor: loading ? "default" : "pointer", fontFamily: "var(--font-ui)", fontSize: 12.5, color: "var(--cyan-2)" }}>Forgot password?</button>
              </div>
              {error && <div style={{ fontFamily: "var(--font-ui)", fontSize: 13, color: "var(--bad)", margin: "-4px 0 14px" }}>{error}</div>}
              {notice && <div style={{ fontFamily: "var(--font-ui)", fontSize: 13, color: "var(--cyan-2)", margin: "-4px 0 14px" }}>{notice}</div>}
              <Btn full disabled={!valid || loading} iconRight="ArrowRight">{loading ? "Signing in…" : "Sign in"}</Btn>
            </form>
          </>
        ) : (
          <>
            <h3 style={{ fontFamily: "var(--font-head)", fontSize: 23, color: "var(--ink)", margin: "0 0 6px" }}>Your systems</h3>
            {!systems.length && <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14, color: "var(--ink-3)", lineHeight: 1.6, margin: "0 0 20px" }}>
              No systems are assigned to your account yet — contact your administrator.
            </p>}
            {(() => {
              const ORDER = ["central", "dev", "agents", "ainews", "intelligence", "privacy", "simulation", "visibility"];
              const rank = (k) => { const i = ORDER.indexOf(k); return i === -1 ? ORDER.length : i; };
              const ordered = [...systems].sort((a, b) => rank(a.key) - rank(b.key));
              return (
                <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                  {ordered.map((s) => (
                    <a key={s.url} href={s.url} target="_blank" rel="noreferrer" style={{ all: "unset", cursor: "pointer", display: "flex", alignItems: "center",
                      gap: 12, padding: "13px 14px", borderRadius: 12, border: "1px solid var(--hairline-2)", background: "var(--panel)", boxSizing: "border-box" }}>
                      <span style={{ width: 32, height: 32, borderRadius: 9, background: "rgba(80,134,182,.1)", display: "flex", alignItems: "center",
                        justifyContent: "center", color: "var(--cyan-2)", flexShrink: 0 }}><Icon name={s.icon} size={15} /></span>
                      <span style={{ fontFamily: "var(--font-ui)", fontSize: 14, fontWeight: 500, color: "var(--ink)", flex: 1, minWidth: 0 }}>{s.name}</span>
                      <Icon name="ArrowUpRight" size={15} color="var(--ink-3)" />
                    </a>
                  ))}
                </div>
              );
            })()}
            <div style={{ marginTop: 18, display: "flex", gap: 10 }}>
              <Btn full variant="ghost" onClick={onClose}>Done</Btn>
              <Btn full variant="ghost" onClick={signOut} disabled={loading}>Sign out</Btn>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// Legal content — grounded in the facts stated on yachtingstack.ai + Metamaxis Analytics (Ibiza, ES).
// NOTE: summaries to make the footer functional; replace `body` with the authoritative full text.
const LEGAL = {
  privacy: {
    title: "Privacy Policy",
    updated: "Last updated: 30 July 2026",
    body: [
      { h: "1. Introduction", p: [
        "YachtingStack (\"we\", \"us\", \"our\") is committed to protecting your privacy and ensuring the security of your Personal Data. This Privacy Policy explains how we collect, use, store, and protect your information in compliance with the EU General Data Protection Regulation (GDPR), the ePrivacy Directive, and other applicable data protection laws.",
        "The Data Controller for the Personal Data we process through our Service is:"],
        box: ["Metamaxis Analytics SL", "Ibiza, Balearic Islands, Spain", "CIF: ESB16994972", "Email: privacy@YachtingStack.ai"] },
      { h: "2. Data We Collect", p: ["We collect the following categories of Personal Data:"], sub: [
        { h: "2.1 Information You Provide", items: [
          ["Account Data", "Name and email address when you create an account. Authentication is handled securely via our centralised identity provider (Supabase Auth)."],
          ["Business Data", "Business name, website URL, industry segment, geographic regions, and services offered."],
          ["Competitor Data", "Names and websites of competitors you choose to track."],
          ["Preference Data", "Language preferences and subscription tier selection."]] },
        { h: "2.2 Information We Generate", items: [
          ["AI Analysis Data", "Results from AI visibility checks, including brand mentions, sentiment analysis, competitor mentions, and positioning data."],
          ["Analytics Data", "Aggregated visibility scores and trend data derived from AI responses."]] },
        { h: "2.3 Automatically Collected Data", items: [
          ["Session Data", "Session identifiers for authentication purposes."],
          ["Usage Data", "Pages visited, features used, and timestamps of interactions."],
          ["Analytics Data (with consent)", "Page views, navigation patterns and key events, collected via Google Analytics 4. Google Analytics is not loaded at all unless you accept analytics cookies — if you decline, or simply dismiss the banner, no analytics script runs and no analytics cookies are set."]] }] },
      { h: "3. Legal Basis for Processing", p: ["Under GDPR Article 6, we process your Personal Data on the following legal bases:"], items: [
        ["Contractual Necessity (Art. 6(1)(b))", "Processing your account and business data is necessary to provide the Service you have subscribed to."],
        ["Legitimate Interest (Art. 6(1)(f))", "We process usage and analytics data to improve the Service, ensure security, and prevent fraud. We have conducted a balancing test to ensure our interests do not override your rights."],
        ["Consent (Art. 6(1)(a))", "Where required, we obtain your explicit consent before processing (e.g., for marketing communications). You may withdraw consent at any time."],
        ["Legal Obligation (Art. 6(1)(c))", "We may process data to comply with legal requirements such as tax regulations or court orders."]],
        sub: [{ h: "3.1 Special Categories of Data (Art. 9)", p: [
          "Some parts of the suite handle special-category data. Onboard products can hold crew medical records, dietary requirements and allergy information, which are data concerning health. We process these only where an Article 9(2) condition applies in addition to an Article 6 basis — normally Article 9(2)(b), because processing is necessary to carry out obligations in the field of employment law, including duties owed to seafarers under the Maritime Labour Convention and flag-state requirements, or Article 9(2)(c) where processing is necessary to protect someone's vital interests and they cannot give consent.",
          "Access to this data is restricted by role: medical records are visible only to the roles that need them, such as the Captain or a designated medical officer. We do not use special-category data for analytics, for product improvement, or to train any model."] }] },
      { h: "4. How We Use Your Data", p: ["YachtingStack is a suite of products, and what we do with your data depends on which of them you use:"], items: [
        "To authenticate your identity and manage your account across the suite.",
        "To operate onboard and onshore workspaces — crew, guest, vessel, charter, maintenance and compliance records that you or your crew enter.",
        "To run AI agents that answer questions and draft work on your instruction, using the data in your workspace.",
        "To run AI visibility checks and competitor comparisons using your business information.",
        "To run privacy-exposure scans across public sources, where you have asked us to and verified your relationship to the vessel.",
        "To compile market and regulatory intelligence, and to produce editorial content.",
        "To improve and develop new features for the Service.",
        "To communicate with you about your account, service updates, and (with consent) marketing.",
        "To ensure the security and integrity of the Service."],
        sub: [{ h: "4.1 Artificial Intelligence", p: [
          "Several products use AI, and we want to be plain about how. Where you interact with an AI assistant, it is labelled as one — you are always told when you are talking to a machine rather than a person. Where we publish content that was generated or assisted by AI, we say so.",
          "AI outputs are advisory. They can be wrong, and they are not a substitute for professional judgement — the system prepares work, a person decides. We do not make decisions producing legal or similarly significant effects about you by automated means alone within the meaning of Article 22 GDPR; where an AI-assisted process informs a decision about a person, a human reviews it and remains accountable for the outcome.",
          "Before any request is sent to a model provider, personal identifiers belonging to crew and guests are replaced with pseudonyms. Your data is not used to train any provider's models, and it is not shared with other customers. Each customer's workspace is isolated from every other."] }] },
      { h: "5. Data Sharing and Third Parties", p: ["We share your data with the following categories of third parties:"], items: [
        ["AI Model Providers", "We route model requests through OpenRouter, which brokers access to models from Anthropic, OpenAI, Google and others, and we use fal.ai for image generation. Before any request leaves our systems, personal identifiers belonging to crew and guests are replaced with pseudonyms; the mapping stays under our control and is never transmitted. These providers process data under their respective terms and do not use our tenants' data to train their models."],
        ["Analytics Provider (with consent only)", "If — and only if — you accept analytics cookies, we load Google Analytics 4 to collect page views and key events. Declining or ignoring the banner means the script is never loaded."],
        ["Email Service Provider", "We use Mailjet to send transactional and notification emails. Mailjet processes your email address and name according to their privacy policy."],
        ["Infrastructure Providers", "Applications run on Railway; databases, file storage and authentication are provided by Supabase. Both may process data in data centres inside and outside the EEA, under the safeguards described in section 6."],
        ["Payment Processors", "If you purchase a paid subscription, payment data is processed by our payment provider. We do not store full payment card details."]],
        note: "We do not sell your Personal Data to any third party. We do not share your data for advertising purposes." },
      { h: "6. International Data Transfers", p: ["Some of our third-party service providers (including AI providers) may process data outside the European Economic Area (EEA). Where this occurs, we ensure appropriate safeguards are in place, including:"], items: [
        "EU Standard Contractual Clauses (SCCs) approved by the European Commission.",
        "Adequacy decisions by the European Commission where applicable.",
        "Binding Corporate Rules where applicable."],
        note: "You may request a copy of the safeguards we use by contacting us." },
      { h: "7. Data Retention", p: ["We retain your Personal Data only for as long as necessary to fulfil the purposes for which it was collected:"], items: [
        ["Account Data", "Retained for the duration of your account. Deleted within 30 days of account deletion request."],
        ["AI Analysis Data", "Retained for 24 months to provide historical trend analysis, unless you request earlier deletion."],
        ["Usage Data", "Retained in anonymised or aggregated form for up to 24 months for service improvement purposes."],
        ["Billing Data", "Retained for 7 years as required by applicable tax and accounting regulations."]] },
      { h: "8. Your Rights Under GDPR", p: ["Under the GDPR, you have the following rights regarding your Personal Data:"], items: [
        ["Right of Access (Art. 15)", "You can request a copy of the Personal Data we hold about you."],
        ["Right to Rectification (Art. 16)", "You can request correction of inaccurate or incomplete data."],
        ["Right to Erasure (Art. 17)", "You can request deletion of your Personal Data (\"right to be forgotten\"), subject to legal retention requirements."],
        ["Right to Restrict Processing (Art. 18)", "You can request that we limit how we process your data."],
        ["Right to Data Portability (Art. 20)", "You can request your data in a structured, commonly used, machine-readable format."],
        ["Right to Object (Art. 21)", "You can object to processing based on legitimate interests or for direct marketing."],
        ["Right to Withdraw Consent (Art. 7(3))", "Where processing is based on consent, you can withdraw it at any time without affecting the lawfulness of prior processing."],
        ["Right to Lodge a Complaint", "You have the right to lodge a complaint with your local Data Protection Authority (DPA)."]],
        sub: [{ p: ["You can exercise several of these rights directly within the application:"], items: [
          ["Data Export", "Go to Settings and click \"Export Data\" to download all your Personal Data in a machine-readable JSON format."],
          ["Account Deletion", "Go to Settings and click \"Delete Account\" to permanently erase your account and all associated data."],
          ["Email Preferences", "Manage your email notification preferences in Settings to control marketing and digest communications."]] }],
        note: "For any other requests, please contact us at privacy@YachtingStack.ai. We will respond within 30 days." },
      { h: "9. Data Security", p: ["We implement appropriate technical and organisational measures to protect your Personal Data, including:"], items: [
        "Encryption of data in transit using TLS/SSL.",
        "JWT-based authentication with cryptographic signature verification (JWKS).",
        "Centralised identity management via Supabase Auth with secure token handling.",
        "Regular security assessments and monitoring.",
        "Access controls limiting data access to authorised personnel only."] },
      { h: "10. Cookies and Tracking", p: [
        "We use essential cookies that are strictly necessary for the functioning of the Service (for example, session cookies for authentication). Under the ePrivacy Directive these do not require consent, because they are necessary to deliver the service you asked for.",
        "We use one category of non-essential cookie: analytics, via Google Analytics 4. These are only ever set after you actively accept them. The analytics script is not loaded on page load — it is fetched only once you press Accept, so declining means nothing is downloaded and nothing is stored. Dismissing the banner without choosing counts as a refusal, not as consent.",
        "Refusing costs you nothing: every part of the site works identically either way. You can change your decision at any time using the Cookie preferences link in the footer, and if you switch from accept to reject we delete the analytics cookies already on your device.",
        "We do not use advertising cookies, tracking pixels, cross-site trackers, session recording or heatmaps, and we do not share analytics data for advertising purposes."] },
      { h: "11. Children's Privacy", p: ["The Service is not intended for individuals under the age of 16. We do not knowingly collect Personal Data from children. If we become aware that we have collected data from a child under 16, we will take steps to delete it promptly."] },
      { h: "12. Privacy Contact", p: [
        "We are not required to appoint a Data Protection Officer under Article 37(1) GDPR: we are not a public authority, and our core activities do not involve regular and systematic monitoring of data subjects on a large scale, nor large-scale processing of special categories of data. We have assessed this and keep a written record of the assessment, which we review as the business changes.",
        "We have instead named a single point of contact for every data protection matter. Write to this address to exercise any of the rights in section 8, or with any question about this policy:"],
        box: ["Privacy Contact", "Metamaxis Analytics SL", "Ibiza, Balearic Islands, Spain", "Email: privacy@YachtingStack.ai"] },
      { h: "13. Changes to This Policy", p: ["We may update this Privacy Policy from time to time. We will notify you of material changes by email or through the Service at least 30 days before the changes take effect. Your continued use of the Service after the effective date constitutes acceptance. We encourage you to review this page periodically."] },
      { h: "14. Contact Us", p: ["If you have any questions about this Privacy Policy or wish to exercise your data protection rights, please contact us at:"],
        box: ["Metamaxis Analytics SL", "Ibiza, Balearic Islands, Spain", "CIF: ESB16994972", "Email: privacy@YachtingStack.ai"],
        note: "You also have the right to lodge a complaint with your national Data Protection Authority. A list of EEA DPAs can be found on the European Data Protection Board website." },
    ],
  },
  terms: {
    title: "Terms & Conditions",
    updated: "Last updated: 30 July 2026",
    body: [
      { h: "1. Introduction", p: [
        "Welcome to YachtingStack (the \"Service\"). YachtingStack is a trading name of Metamaxis Analytics SL, a company registered in Spain (CIF ESB16994972) with its registered office in Ibiza, Balearic Islands — referred to here as \"we\", \"us\" and \"our\". Metamaxis Analytics SL is the contracting party for these Terms and the data controller for personal data processed through the Service.",
        "These Terms and Conditions (\"Terms\") govern your use of our website and services. By accessing or using the Service, you agree to be bound by these Terms. If you do not agree, please do not use the Service."] },
      { h: "2. Definitions", items: [
        "\"User\" refers to any individual or entity that registers for or uses the Service.",
        "\"Personal Data\" has the meaning given under the EU General Data Protection Regulation (GDPR).",
        "\"Data Controller\" means YachtingStack, which determines the purposes and means of processing Personal Data.",
        "\"Processing\" means any operation performed on Personal Data, as defined by the GDPR."] },
      { h: "3. Account Registration", p: ["To use certain features of the Service, you must create an account. You agree to provide accurate, current, and complete information during registration. You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account."] },
      { h: "4. Service Description", p: [
        "YachtingStack is a suite of AI products for yachts and yachting businesses. Depending on what you subscribe to, the Service may include onboard and onshore operational workspaces, AI agents that assist with day-to-day work, AI visibility tracking, privacy-exposure scanning, market and regulatory intelligence, and editorial content.",
        "Parts of the Service are powered by third-party AI models. Their output is advisory and may be inaccurate, incomplete or out of date. It is not legal, financial, medical, safety or navigational advice, and it does not replace the judgement of a qualified professional or the Master's authority. You remain responsible for decisions taken in reliance on it, and for meeting your own regulatory obligations."] },
      { h: "5. Subscription and Payment", p: ["Access to certain features may require a paid subscription. Subscription fees, billing cycles, and payment terms will be clearly displayed at the time of purchase. You authorise us to charge your selected payment method for recurring subscription fees. You may cancel your subscription at any time, with cancellation taking effect at the end of the current billing period."] },
      { h: "6. Acceptable Use", p: ["You agree not to:"], items: [
        "Use the Service for any unlawful purpose or in violation of any applicable laws or regulations.",
        "Attempt to gain unauthorised access to the Service or its related systems.",
        "Interfere with or disrupt the integrity or performance of the Service.",
        "Reproduce, duplicate, copy, sell, or exploit any portion of the Service without express permission.",
        "Use automated scripts or bots to access the Service beyond its intended functionality."] },
      { h: "7. Intellectual Property", p: ["All content, features, and functionality of the Service, including but not limited to text, graphics, logos, and software, are the exclusive property of YachtingStack and are protected by international copyright, trademark, and other intellectual property laws. You retain ownership of your business data that you provide to the Service."] },
      { h: "8. Data Protection and GDPR Compliance", p: ["We process Personal Data in accordance with the EU General Data Protection Regulation (GDPR) and applicable data protection laws. Key points include:"], items: [
        ["Legal Basis", "We process your data based on contractual necessity (to provide the Service), legitimate interests (to improve our Service), and your consent where required."],
        ["Data Minimisation", "We collect only the Personal Data necessary to provide and improve the Service."],
        ["Your Rights", "You have the right to access, rectify, erase, restrict processing, port your data, and object to processing. See our Privacy Policy for full details."],
        ["Data Transfers", "If we transfer data outside the EEA, we ensure appropriate safeguards are in place (e.g., Standard Contractual Clauses)."],
        ["Data Breach", "We will notify the relevant supervisory authority within 72 hours of becoming aware of a personal data breach, and affected users without undue delay where required."]],
        note: "For full details on how we handle your data, please refer to our Privacy Policy." },
      { h: "9. Third-Party Services", p: ["The Service integrates with third-party AI platforms — including models reached through OpenRouter (Anthropic, OpenAI, Google and others) and image generation via fal.ai — as well as hosting, database and email providers. We are not responsible for the accuracy, availability, or content of third-party services. Your use of such third-party services is subject to their respective terms and privacy policies. The providers we rely on are listed in section 5 of our Privacy Policy."] },
      { h: "10. Disclaimers and Limitation of Liability", p: ["The Service is provided \"as is\" and \"as available\" without warranties of any kind, either express or implied. We do not guarantee that:"], items: [
        "AI-generated results will be accurate, complete, or current.",
        "The Service will be uninterrupted, secure, or error-free.",
        "Results will meet your specific requirements or expectations."],
        note: "To the fullest extent permitted by law, YachtingStack shall not be liable for any indirect, incidental, special, consequential, or punitive damages arising from your use of the Service." },
      { h: "11. Indemnification", p: ["You agree to indemnify and hold harmless YachtingStack and its officers, directors, employees, and agents from any claims, damages, losses, or expenses (including reasonable legal fees) arising from your use of the Service or violation of these Terms."] },
      { h: "12. Termination", p: ["We may terminate or suspend your access to the Service immediately, without prior notice, if you breach these Terms. Upon termination, your right to use the Service ceases immediately. You may request deletion of your account and associated Personal Data at any time, in accordance with your rights under the GDPR."] },
      { h: "13. Changes to Terms", p: ["We reserve the right to modify these Terms at any time. We will notify you of material changes by email or through the Service. Your continued use of the Service after such changes constitutes acceptance of the updated Terms. If you do not agree to the updated Terms, you must stop using the Service."] },
      { h: "14. Governing Law and Jurisdiction", p: ["These Terms are governed by Spanish law. The courts of Spain have jurisdiction over any dispute arising from them. If you are a consumer, this does not deprive you of the protection of the mandatory rules of the country where you live, and you keep the right to bring proceedings there; nor does it affect your right under the GDPR to lodge a complaint with your local supervisory authority or to seek a remedy in your own member state."] },
      { h: "15. Contact Information", p: ["If you have any questions about these Terms, please contact us at:"],
        box: ["Metamaxis Analytics SL", "Ibiza, Balearic Islands, Spain", "CIF: ESB16994972", "Email: legal@YachtingStack.ai"] },
    ],
  },
  gdpr: {
    title: "GDPR (AEPD)",
    updated: "Last updated: 30 July 2026",
    body: [
      ["Lawful basis", "We process personal data under the GDPR on the bases of consent, legitimate interest and the performance of a contract, depending on the activity."],
      ["Data residency", "We are established in Spain and the AEPD is our supervisory authority. Your workspace data is held in the EU. Some processing necessarily happens outside the EEA — AI model providers and parts of our hosting — and those transfers rely on the safeguards set out in section 6 of the Privacy Policy. Local deployments keep vessel and business data on owner-controlled infrastructure."],
      ["Exercising your rights", "To exercise any GDPR right, contact privacy@yachtingstack.ai. You also have the right to lodge a complaint with the AEPD (www.aepd.es)."],
    ],
  },
  intelligence: {
    title: "Business Directory Privacy Notice",
    updated: "Last updated: 20 August 2026",
    body: [
      { h: "1. What This Notice Covers", p: [
        "YachtingStack Intelligence maintains a directory of businesses, vessels and charter services in the yachting industry, compiled from publicly available sources. Some records include personal data about named business contacts — typically a name, a business role, and a business email address, phone number or address. Article 14 GDPR requires us to tell you about this processing because the data was not collected from you directly; this notice does that.",
        "The Data Controller is:"],
        box: ["Metamaxis Analytics SL", "Ibiza, Balearic Islands, Spain", "CIF: ESB16994972", "Email: privacy@YachtingStack.ai"] },
      { h: "2. Where the Data Comes From", p: [
        "Records are compiled from publicly accessible sources: yachting-industry directories and the websites businesses publish about themselves. Every record keeps a reference to the source it came from, so if you ask, we can tell you exactly where we found your details."] },
      { h: "3. Why We Process It, and on What Basis", p: [
        "We process this data on the basis of legitimate interest (Article 6(1)(f) GDPR): maintaining an accurate, professionally useful directory of the yachting industry, served through our products. We have carried out and recorded a balancing assessment. The directory holds ordinary business contact details only — no special-category data — and no decision producing legal or similarly significant effects is taken about anyone by automated means."] },
      { h: "4. Who Receives It", p: [
        "Directory records are available to users of our products. The data is hosted by our processors: Supabase (database) and Railway (application hosting)."] },
      { h: "5. How Long We Keep It", p: [
        "A record is kept for no more than 24 months from the date we last verified it against its source; records that cannot be re-verified are removed. A record removed at your request is suppressed permanently — our collection pipeline is built so an erased record cannot re-enter the directory through a future crawl of the same sources."] },
      { h: "6. Your Rights", items: [
        ["Right to Object (Art. 21)", "Because this processing rests on legitimate interest, you can object at any time on grounds relating to your situation. For personal contact details we will normally simply remove the record."],
        ["Access (Art. 15)", "Ask what we hold about you and where it came from."],
        ["Rectification (Art. 16)", "Have inaccurate details corrected."],
        ["Erasure (Art. 17)", "Have your record deleted and permanently suppressed."],
        ["Restriction (Art. 18)", "Have processing paused while a request is examined."]],
        note: "Write to privacy@YachtingStack.ai — we respond within 30 days. You also have the right to lodge a complaint with the Spanish supervisory authority, the AEPD (www.aepd.es)." },
    ],
  },
};

function LegalList({ items }) {
  return (
    <ul style={{ margin: "8px 0 0", padding: 0, listStyle: "none", display: "flex", flexDirection: "column", gap: 8 }}>
      {items.map((it, i) => (
        <li key={i} style={{ display: "flex", gap: 9, alignItems: "flex-start" }}>
          <span style={{ flexShrink: 0, width: 5, height: 5, borderRadius: "50%", background: "var(--cyan-2)", marginTop: 8 }} />
          <span style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14, lineHeight: 1.65, color: "var(--ink-2)" }}>
            {Array.isArray(it) ? <span><span style={{ fontWeight: 600, color: "var(--ink)" }}>{it[0]}:</span> {it[1]}</span> : it}
          </span>
        </li>
      ))}
    </ul>
  );
}

function LegalModal({ doc, onClose }) {
  if (!doc) return null;
  const d = LEGAL[doc];
  return (
    <div onClick={onClose} className="ymfade" style={{ position: "fixed", inset: 0, zIndex: 95, background: "rgba(4,12,20,.72)",
      backdropFilter: "blur(6px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 620, maxHeight: "82vh", overflow: "auto", background: "var(--surface)",
        border: "1px solid var(--hairline-2)", borderRadius: 20, padding: "30px 34px 34px", boxShadow: "0 40px 120px -30px rgba(0,0,0,.8)" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", position: "sticky", top: 0 }}>
          <div>
            <h3 style={{ fontFamily: "var(--font-head)", fontSize: 25, color: "var(--ink)", margin: 0 }}>{d.title}</h3>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-4)", marginTop: 6, letterSpacing: ".04em" }}>{d.updated}</div>
          </div>
          <button onClick={onClose} style={{ all: "unset", cursor: "pointer", color: "var(--ink-3)" }}><Icon name="X" size={20} /></button>
        </div>
        <div style={{ marginTop: 22, display: "flex", flexDirection: "column", gap: 22 }}>
          {d.body.map((row, si) => {
            const s = Array.isArray(row) ? { h: row[0], p: [row[1]] } : row;
            const lp = { fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14, lineHeight: 1.7, color: "var(--ink-2)", margin: "0 0 8px" };
            return (
              <div key={si}>
                {s.h && <div style={{ fontFamily: "var(--font-ui)", fontSize: 15.5, fontWeight: 600, color: "var(--ink)", marginBottom: 8 }}>{s.h}</div>}
                {s.p && s.p.map((para, i) => <p key={i} style={lp}>{para}</p>)}
                {s.box && (
                  <div style={{ margin: "10px 0", padding: "13px 15px", borderRadius: 11, background: "var(--panel)", border: "1px solid var(--hairline)" }}>
                    {s.box.map((line, i) => <div key={i} style={{ fontFamily: "var(--font-mono)", fontSize: 12.5, color: i === 0 ? "var(--ink)" : "var(--ink-3)", lineHeight: 1.7 }}>{line}</div>)}
                  </div>
                )}
                {s.items && <LegalList items={s.items} />}
                {s.sub && s.sub.map((g, gi) => (
                  <div key={gi} style={{ marginTop: 12 }}>
                    {g.h && <div style={{ fontFamily: "var(--font-ui)", fontSize: 13.5, fontWeight: 600, color: "var(--ink-2)", marginBottom: 6 }}>{g.h}</div>}
                    {g.p && g.p.map((para, i) => <p key={i} style={lp}>{para}</p>)}
                    {g.items && <LegalList items={g.items} />}
                  </div>
                ))}
                {s.note && <p style={{ ...lp, margin: "12px 0 0" }}>{s.note}</p>}
              </div>
            );
          })}
        </div>
        <div style={{ marginTop: 26, paddingTop: 16, borderTop: "1px solid var(--hairline)", display: "flex", gap: 8,
          alignItems: "center", fontFamily: "var(--font-ui)", fontSize: 11.5, color: "var(--ink-4)" }}>
          <Icon name="ShieldCheck" size={14} color="var(--ink-3)" /> Metamaxis Analytics · Ibiza, Spain · GDPR · AEPD
        </div>
      </div>
    </div>
  );
}

function Footer({ go }) {
  // window.__initialLegal is set at boot for /privacy and /terms deep links
  // (see index.html) — open that document immediately, then clear the flag.
  const [legal, setLegal] = React.useState(() => {
    const doc = window.__initialLegal || null;
    window.__initialLegal = null;
    return doc;
  });
  const cols = [
    ["For Yachts", [["AI Agents", () => go("onboard")], ["Vessel Data", () => go("onboard", "iot")], ["Deployment", () => go("onboard", "deployment")]]],
    ["For Yachting Businesses", [["Custom Development", () => go("product:custom")], ["AI Agents", () => go("product:agents")], ["Visibility", () => go("product:visibility")], ["Privacy", () => go("product:privacy")]]],
    ["Resources", [["AI News", () => go("product:news")], ["Simulation", () => go("product:simulation")]]],
    // "Cookie preferences" re-opens the consent banner (window.YSConsent is
    // defined by the consent gate in index.html). The Privacy Policy promises
    // this link exists — if you move it, move the promise in section 10 too.
    ["Legal", [["Privacy Policy", () => setLegal("privacy")], ["Terms & Conditions", () => setLegal("terms")],
      ["Directory Privacy Notice", () => setLegal("intelligence")],
      ["Cookie preferences", () => window.YSConsent && window.YSConsent.reset()]]],
  ];
  return (
    <footer className="snap-foot" style={{ position: "relative", borderTop: "1px solid var(--hairline)", background: "var(--bg-deep)" }}>
      <LegalModal doc={legal} onClose={() => setLegal(null)} />
      <div style={{ position: "absolute", top: 0, left: "50%", transform: "translateX(-50%)", width: 160, height: 1,
        background: "linear-gradient(90deg, transparent, rgba(80,134,182,.4), transparent)" }} />
      <div style={{ maxWidth: 1240, margin: "0 auto", padding: "72px 32px 40px" }}>
        <div className="footer-grid" style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr 1.1fr 1fr 1fr", gap: 32 }}>
          <div className="footer-brand">
            <Wordmark />
            <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13, color: "var(--ink-3)", lineHeight: 1.6, margin: "16px 0 0", maxWidth: 230 }}>
              End-to-end AI for yachts and yachting businesses.
            </p>
          </div>
          {cols.map(([h, links]) => (
            <div key={h}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, letterSpacing: ".16em",
                textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 16 }}>{h}</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 11 }}>
                {links.map(([l, fn]) => (
                  <button key={l} onClick={fn} style={{ all: "unset", cursor: "pointer", fontFamily: "var(--font-ui)",
                    fontSize: 13.5, color: "var(--ink-3)", transition: "color .25s", textAlign: "left" }}
                    onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"}
                    onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>{l}</button>
                ))}
              </div>
            </div>
          ))}
        </div>
        <div style={{ marginTop: 56, paddingTop: 24, borderTop: "1px solid var(--hairline)", display: "flex",
          justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 12 }}>
          <span style={{ fontFamily: "var(--font-ui)", fontSize: 12, color: "var(--ink-4)" }}>© {new Date().getFullYear()} YachtingStack · Ibiza, Spain</span>
        </div>
      </div>
    </footer>
  );
}

// ---- Newsletter invite — timed popup nudging visitors to the free digest ----
// Arms once per visit, 30s after load. The CTA deep-links to the AI News
// signup form (the one canonical form carries the GDPR consent + audience
// profile — no data is collected here). Any dismissal snoozes the invite for
// 30 days, taking the CTA for 180. Suppressed on the news page itself and
// inside the onboarding / contact flows; if the timer fires there, the
// invite waits for the next navigation instead.
const NL_SNOOZE_KEY = "ym_nl_invite_until";
function NewsletterInvite({ route, go }) {
  const [armed, setArmed] = React.useState(false);
  const [closed, setClosed] = React.useState(false);
  React.useEffect(() => {
    try { if (Date.now() < Number(localStorage.getItem(NL_SNOOZE_KEY) || 0)) return; } catch (e) {}
    const t = setTimeout(() => setArmed(true), 30000);
    return () => clearTimeout(t);
  }, []);
  const snooze = (days) => {
    try { localStorage.setItem(NL_SNOOZE_KEY, String(Date.now() + days * 86400000)); } catch (e) {}
    setClosed(true);
  };
  const suppressed = route === "product:news" || route === "onboarding" || route === "contact-sales";
  const open = armed && !closed && !suppressed;
  React.useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") snooze(30); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open]);
  if (!open) return null;
  return (
    <div onClick={() => snooze(30)} className="ymfade" style={{ position: "fixed", inset: 0, zIndex: 80, background: "rgba(4,12,20,.72)",
      backdropFilter: "blur(6px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
      {/* translateZ(0): own compositor layer — without it Chromium paints the
          overlay's blurred backdrop OVER this opaque card on mobile */}
      <div onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="Newsletter invitation"
        style={{ width: "100%", maxWidth: 460, background: "rgb(11,26,42)", border: "1px solid var(--hairline-2)", transform: "translateZ(0)",
          borderRadius: 20, padding: "30px 30px 26px", boxShadow: "0 40px 120px -30px rgba(0,0,0,.8)" }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 18 }}>
          <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
            <span className="aura" style={{ width: 8, height: 8, borderRadius: "50%", background: "var(--cyan)" }} />
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, fontWeight: 700, letterSpacing: ".14em", textTransform: "uppercase", color: "var(--cyan-2)" }}>Free weekly digest</span>
          </span>
          <button onClick={() => snooze(30)} aria-label="Close" style={{ all: "unset", cursor: "pointer", padding: 6, color: "var(--ink-3)" }}><Icon name="X" size={18} /></button>
        </div>
        <h3 style={{ fontFamily: "var(--font-head)", fontSize: 24, color: "var(--ink)", margin: "0 0 8px", lineHeight: 1.25 }}>
          Stay ahead of <span style={{ color: "var(--cyan)" }}>AI in yachting</span>.
        </h3>
        <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14, color: "var(--ink-3)", lineHeight: 1.65, margin: "0 0 20px" }}>
          The week's AI and yachting news, curated into one ranked five-minute read — free in your inbox weekly.
        </p>
        <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
          <Btn onClick={() => { snooze(180); go("product:news", "subscribe"); }} iconRight="ArrowRight" style={{ flex: 1, minWidth: 170 }}>Subscribe free</Btn>
          <Btn variant="ghost" onClick={() => snooze(30)} style={{ minWidth: 110 }}>No thanks</Btn>
        </div>
        <div style={{ display: "flex", gap: 16, flexWrap: "wrap", marginTop: 16, paddingTop: 14, borderTop: "1px solid var(--hairline)" }}>
          {["No spam, ever", "5-min read", "Unsubscribe anytime"].map((t) => (
            <span key={t} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontFamily: "var(--font-ui)", fontSize: 12, color: "var(--ink-3)" }}>
              <Icon name="CheckCircle2" size={13} color="var(--cyan-2)" />{t}
            </span>
          ))}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { Nav, SignInModal, Footer, LegalModal, NewsletterInvite });
