/* global React */
// YachtingStack — reusable On shore product page.
const { Icon, Eyebrow, Btn, Reveal, useRotatingCTA, DeviceShowcase, CompareGrid } = window;

const pInner = (children, style = {}) => (
  <div style={{ maxWidth: 1080, margin: "0 auto", position: "relative", ...style }}>{children}</div>
);

function RelatedCard({ p, go }) {
  return (
    <button onClick={() => { go("product:" + p.key); }} style={{ all: "unset", cursor: "pointer", display: "block", height: "100%" }}>
      <div className="liftcard" style={{ height: "100%", padding: "20px 20px", borderRadius: 16, background: "var(--surface)",
        border: "1px solid var(--hairline)", transition: "transform .3s var(--ease), border-color .3s" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 11, marginBottom: 10 }}>
          <span style={{ width: 36, height: 36, borderRadius: 10, background: "rgba(80,134,182,.08)", border: "1px solid rgba(80,134,182,.16)",
            display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)" }}><Icon name={p.icon} size={16} /></span>
          <span style={{ fontFamily: "var(--font-ui)", fontSize: 14.5, fontWeight: 600, color: "var(--ink)" }}>{p.name}</span>
        </div>
        <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13, lineHeight: 1.5, color: "var(--ink-3)", margin: 0 }}>{p.tagline}</p>
      </div>
    </button>
  );
}

// ---------------- Custom Development — dedicated narrative page ----------------
// Renders the structured spec at window.CUSTOM_DEV_PAGE (hero + ordered blocks +
// closing). Block types: lead | pillars | contrast | timeline | leverage | governance.
// accent may be a single phrase or an array of phrases; each occurrence is tinted.
function AccentText({ text, accent }) {
  const accents = (Array.isArray(accent) ? accent : [accent]).filter(Boolean);
  if (!accents.length) return text;
  const out = [];
  let rest = text, k = 0;
  while (rest.length) {
    let at = -1, hit = null;
    for (const a of accents) {
      const idx = rest.indexOf(a);
      if (idx !== -1 && (at === -1 || idx < at)) { at = idx; hit = a; }
    }
    if (at === -1) { out.push(rest); break; }
    if (at > 0) out.push(rest.slice(0, at));
    out.push(<span key={k++} style={{ color: "var(--cyan)", whiteSpace: "nowrap" }}>{hit}</span>);
    rest = rest.slice(at + hit.length);
  }
  return <>{out}</>;
}

const cdSection = (children, style = {}, bg = null, bgOpacity = 0.14, scrimX = "30%", bgFilter = null, bgSize = "cover", bgMask = null) => (
  <section className="snap-sec cd-sec" style={{ position: "relative", minHeight: "100vh", padding: "100px 32px",
    display: "flex", flexDirection: "column", justifyContent: "center", ...(bg ? { overflow: "hidden" } : null), ...style }}>
    {bg && <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: 0, backgroundImage: `url(${bg})`, backgroundSize: bgSize,
      backgroundRepeat: "no-repeat", backgroundPosition: "center", opacity: bgOpacity, filter: bgFilter || "none",
      WebkitMaskImage: bgMask || "none", maskImage: bgMask || "none", pointerEvents: "none" }} />}
    {/* local scrim under the text column — the wireframe strokes are bright white
        and run straight through the grey body copy without it */}
    {bg && <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: 0, pointerEvents: "none",
      background: `radial-gradient(ellipse 62% 55% at ${scrimX} 45%, rgba(7,22,37,.75), transparent 72%)` }} />}
    <div style={{ width: "100%", maxWidth: 1080, margin: "0 auto", position: "relative" }}>{children}</div>
  </section>
);

function CDHead({ eyebrow, headline, accentWord, body, align = "left", max = 680, headlineSize }) {
  const centered = align === "center";
  return (
    <Reveal style={{ textAlign: align, maxWidth: centered ? max : "none", marginLeft: centered ? "auto" : 0, marginRight: centered ? "auto" : 0 }}>
      <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: headlineSize || "clamp(28px,3.6vw,44px)", lineHeight: 1.14,
        letterSpacing: "-0.01em", color: "var(--ink)", margin: "14px 0 0", maxWidth: max, whiteSpace: "pre-line" }}>
        <AccentText text={headline} accent={accentWord} />
      </h2>
      {(body || []).map((para, i) => (
        <p key={i} style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16.5, lineHeight: 1.75, color: "var(--ink-3)",
          margin: "18px 0 0", maxWidth: max, marginLeft: centered ? "auto" : 0, marginRight: centered ? "auto" : 0 }}>{para}</p>
      ))}
    </Reveal>
  );
}

function CDCards({ items, cols }) {
  const n = cols || (items.length % 3 === 0 ? 3 : (items.length === 4 ? 2 : 3));
  return (
    <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: `repeat(${n},1fr)`, gap: 16, marginTop: 32 }}>
      {items.map(([icon, title, desc], i) => (
        <Reveal key={title} delay={i * 60}>
          <div style={{ height: "100%", padding: "24px 22px", borderRadius: 16, background: "var(--surface)", border: "1px solid var(--hairline)" }}>
            <span style={{ width: 42, height: 42, borderRadius: 12, background: "rgba(80,134,182,.08)", border: "1px solid rgba(80,134,182,.18)",
              display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)", marginBottom: 15 }}><Icon name={icon} size={19} /></span>
            <div style={{ fontFamily: "var(--font-ui)", fontSize: 15.5, fontWeight: 600, color: "var(--ink)", marginBottom: 6 }}>{title}</div>
            <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13.5, lineHeight: 1.6, color: "var(--ink-3)" }}>{desc}</div>
          </div>
        </Reveal>
      ))}
    </div>
  );
}

// Quiet two-column ruled list — used by the governance block so the page's last
// content section breaks the card rhythm instead of repeating it a third time.
function CDListRow({ icon, title, desc }) {
  const [h, setH] = React.useState(false);
  return (
    <div onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
      style={{ display: "flex", gap: 16, alignItems: "flex-start", padding: "22px 12px", margin: "0 -12px", borderRadius: 12,
        borderTop: "1px solid var(--hairline)",
        background: h ? "rgba(80,134,182,.06)" : "transparent",
        transform: h ? "translateX(5px)" : "none",
        transition: "background .25s var(--ease), transform .25s var(--ease)" }}>
      <Icon name={icon} size={18} color={h ? "var(--cyan)" : "var(--cyan-2)"} style={{ marginTop: 3, flexShrink: 0, transition: "color .25s" }} />
      <div>
        <div style={{ fontFamily: "var(--font-ui)", fontSize: 15.5, fontWeight: 600, color: "var(--ink)", marginBottom: 5 }}>{title}</div>
        <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13.5, lineHeight: 1.6, color: h ? "var(--ink-2)" : "var(--ink-3)", transition: "color .25s" }}>{desc}</div>
      </div>
    </div>
  );
}
function CDList({ items }) {
  return (
    <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", columnGap: 56, marginTop: 28 }}>
      {items.map(([icon, title, desc], i) => (
        <Reveal key={title} delay={i * 60}>
          <CDListRow icon={icon} title={title} desc={desc} />
        </Reveal>
      ))}
    </div>
  );
}

function CDContrast({ before, after }) {
  const panel = (data, isAfter) => {
    const accent = isAfter ? "var(--cyan-2)" : "var(--bad)";
    const tintBg = isAfter ? "rgba(80,134,182,.05)" : "rgba(248,113,113,.05)";
    const tintBorder = isAfter ? "rgba(80,134,182,.3)" : "rgba(248,113,113,.28)";
    const chipBg = isAfter ? "rgba(80,134,182,.12)" : "rgba(248,113,113,.12)";
    return (
      <div style={{ height: "100%", padding: "28px 26px", borderRadius: 18, boxSizing: "border-box",
        background: tintBg, border: `1px solid ${tintBorder}` }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 20 }}>
          <span style={{ width: 34, height: 34, borderRadius: 10, display: "flex", alignItems: "center", justifyContent: "center",
            background: chipBg, color: accent }}>
            <Icon name={isAfter ? "Rocket" : "History"} size={16} /></span>
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, fontWeight: 600, letterSpacing: ".12em", textTransform: "uppercase",
            color: accent }}>{data.title}</span>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 13 }}>
          {data.points.map((pt, i) => (
            <div key={i} style={{ display: "flex", gap: 11, alignItems: "flex-start" }}>
              <Icon name={isAfter ? "Check" : "X"} size={15} color={accent} style={{ marginTop: 3, flexShrink: 0 }} />
              <span style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14.5, lineHeight: 1.55, color: isAfter ? "var(--ink-2)" : "var(--ink-3)" }}>{pt}</span>
            </div>
          ))}
        </div>
      </div>
    );
  };
  return (
    <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 18, marginTop: 44, alignItems: "stretch" }}>
      <Reveal style={{ height: "100%" }}>{panel(before, false)}</Reveal>
      <Reveal delay={120} style={{ height: "100%" }}>{panel(after, true)}</Reveal>
    </div>
  );
}

// Horizontal stage timeline — a connecting track with numbered nodes, chevrons
// between stages, and a discreet cyan pulse that flows to the right along the track.
function CDTimeline({ steps }) {
  const n = steps.length;
  return (
    <div className="cd-tl" style={{ overflowX: "auto", marginTop: 56, paddingBottom: 4 }}>
      <div className="cd-tl-inner" style={{ position: "relative", minWidth: 620 }}>
        {/* connecting track between the first and last node centres */}
        <div className="cd-tl-track" style={{ position: "absolute", top: 19, left: `${50 / n}%`, right: `${50 / n}%`, height: 2, borderRadius: 2, background: "var(--hairline-2)" }}>
          {/* chevrons at the mid-points between nodes */}
          {Array.from({ length: n - 1 }).map((_, i) => (
            <span key={i} style={{ position: "absolute", top: "50%", left: `${((i + 0.5) / (n - 1)) * 100}%`,
              transform: "translate(-50%,-50%)", color: "var(--ink-4)", lineHeight: 0 }}>
              <Icon name="ChevronRight" size={15} />
            </span>
          ))}
          {/* discreet pulse flowing to the right, ducking behind each node */}
          <span className="travel" style={{ position: "absolute", top: "50%", marginTop: -3, width: 6, height: 6, borderRadius: "50%",
            background: "var(--cyan)", boxShadow: "0 0 10px var(--cyan)" }} />
        </div>
        {/* stages */}
        <div className="cd-tl-grid" style={{ display: "grid", gridTemplateColumns: `repeat(${n},1fr)`, gap: 16, position: "relative" }}>
          {steps.map(([marker, title, desc], i) => (
            <Reveal key={title} delay={i * 110} style={{ textAlign: "center" }}>
              <div style={{ position: "relative", zIndex: 1, width: 40, height: 40, margin: "0 auto 14px", borderRadius: "50%",
                background: "var(--bg)", border: "1px solid rgba(80,134,182,.45)", boxShadow: "0 0 0 5px var(--bg)",
                display: "flex", alignItems: "center", justifyContent: "center",
                fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600, color: "var(--cyan)" }}>{String(i + 1).padStart(2, "0")}</div>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, fontWeight: 600, letterSpacing: ".1em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 6 }}>{marker}</div>
              <div style={{ fontFamily: "var(--font-ui)", fontSize: 15.5, fontWeight: 600, color: "var(--ink)", marginBottom: 6 }}>{title}</div>
              <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13.5, lineHeight: 1.6, color: "var(--ink-3)", maxWidth: 220, margin: "0 auto" }}>{desc}</div>
            </Reveal>
          ))}
        </div>
      </div>
    </div>
  );
}

// Vertical bullet list — a centered, ruled column used where a block wants its
// points read as a list rather than a grid of cards.
// One bullet row — hover nudges it across and lights up its icon.
function CDBulletRow({ icon, title, desc, first }) {
  const [h, setH] = React.useState(false);
  return (
    <div onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
      style={{ display: "flex", gap: 14, alignItems: "flex-start", padding: "15px 12px", margin: "0 -12px", borderRadius: 12,
        borderTop: first ? "none" : "1px solid var(--hairline)",
        background: h ? "rgba(80,134,182,.06)" : "transparent",
        transform: h ? "translateX(5px)" : "none",
        transition: "background .25s var(--ease), transform .25s var(--ease)" }}>
      <span style={{ width: 34, height: 34, flexShrink: 0, borderRadius: 10,
        background: h ? "var(--cyan)" : "rgba(80,134,182,.08)",
        border: h ? "1px solid var(--cyan)" : "1px solid rgba(80,134,182,.18)",
        color: h ? "var(--cyan-ink)" : "var(--cyan-2)",
        boxShadow: h ? "0 0 18px rgba(80,134,182,.4)" : "none",
        transform: h ? "scale(1.08)" : "none",
        display: "flex", alignItems: "center", justifyContent: "center", transition: "all .25s var(--ease)" }}>
        <Icon name={icon} size={16} />
      </span>
      <p style={{ margin: 0, fontFamily: "var(--font-ui)", fontSize: 15.5, lineHeight: 1.6 }}>
        <span style={{ fontWeight: 600, color: "var(--ink)" }}>{title}</span>
        <span style={{ fontWeight: 300, color: h ? "var(--ink-2)" : "var(--ink-3)", transition: "color .25s" }}> — {desc}</span>
      </p>
    </div>
  );
}

function CDBullets({ items, contained = true }) {
  return (
    <div style={contained ? { maxWidth: 640, margin: "36px auto 0", textAlign: "left" } : { textAlign: "left" }}>
      {items.map(([icon, title, desc], i) => (
        <Reveal key={title} delay={i * 70}>
          <CDBulletRow icon={icon} title={title} desc={desc} first={i === 0} />
        </Reveal>
      ))}
    </div>
  );
}

function CDBlock({ block }) {
  const alt = block.type === "contrast" || block.type === "leverage";
  // Two-column split: heading + single paragraph on the left, bullets on the right.
  if (block.layout === "split") {
    return cdSection(
      <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 56, alignItems: block.valign || "center" }}>
        <CDHead eyebrow={block.eyebrow} headline={block.headline} accentWord={block.accentWord} body={block.body} align="left" max={520} headlineSize={block.headlineSize} />
        <CDBullets items={block.items} contained={false} />
      </div>,
      { background: alt ? "var(--bg-deep)" : "transparent",
        borderTop: alt ? "1px solid var(--hairline)" : "none", borderBottom: alt ? "1px solid var(--hairline)" : "none" },
      block.bg ?? null,
      block.bgOpacity ?? 0.14,
      "50%",
      block.bgFilter ?? null,
      block.bgSize ?? "cover",
      block.bgMask ?? null
    );
  }
  return cdSection(
    <>
      <CDHead eyebrow={block.eyebrow} headline={block.headline} accentWord={block.accentWord} body={block.body}
        align={block.type === "lead" ? "center" : "left"} max={block.type === "lead" ? 720 : 680} />
      {block.type === "contrast" && block.before && block.after && <CDContrast before={block.before} after={block.after} />}
      {block.type === "timeline" && block.steps && <CDTimeline steps={block.steps} />}
      {block.stat && (
        <Reveal>
          <div style={{ display: "flex", alignItems: "baseline", gap: 16, marginTop: 34, flexWrap: "wrap" }}>
            <span style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(46px,6.5vw,72px)", color: "var(--cyan)", lineHeight: 1 }}>{block.stat[0]}</span>
            <span style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16, lineHeight: 1.5, color: "var(--ink-3)", maxWidth: 280 }}>{block.stat[1]}</span>
          </div>
        </Reveal>
      )}
      {(() => {
        // A "suite" block draws its cards straight from the live suite catalogue.
        const items = block.suite
          ? (window.SUITE || []).filter((s) => s.key !== "news").map((s) => [s.icon, s.name.replace(/^Yachting /, ""), s.blurb])
          : block.items;
        if (!items || !items.length) return null;
        return block.layout === "bullets" ? <CDBullets items={items} />
          : block.type === "governance" ? <CDList items={items} />
            : <CDCards items={items} />;
      })()}
    </>,
    { background: alt ? "var(--bg-deep)" : "transparent",
      borderTop: alt ? "1px solid var(--hairline)" : "none", borderBottom: alt ? "1px solid var(--hairline)" : "none" },
    block.bg ?? null,
    block.bgOpacity ?? 0.14,
    block.type === "lead" ? "50%" : "30%",
    block.bgFilter ?? null,
    block.bgSize ?? "cover",
    block.bgMask ?? null
  );
}

function CustomDevPage({ go, from }) {
  const ctaText = useRotatingCTA();
  const spec = window.CUSTOM_DEV_PAGE;
  // Back always leads home — route-dependent targets bounced users to Onboard.
  const backRoute = "home";
  const backLabel = "Home";
  return (
    <div style={{ background: "var(--bg)" }}>
      {/* hero */}
      <section className="snap-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "150px 32px 96px",
        display: "flex", flexDirection: "column", justifyContent: "center", background: "var(--bg-deep)" }}>
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/yachting-business.webp)", backgroundSize: "cover", backgroundPosition: "center", opacity: 0.16, pointerEvents: "none" }} />
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none", background: "linear-gradient(90deg, var(--bg-deep) 6%, rgba(7,20,33,.55) 52%, transparent 100%)" }} />
        <div style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse 70% 80% at 82% 10%, rgba(80,134,182,.12), transparent 58%)" }} />
        <div aria-hidden="true" style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: "42%", pointerEvents: "none", background: "linear-gradient(to bottom, transparent, var(--bg))" }} />
        <div style={{ maxWidth: 1080, margin: "0 auto", position: "relative" }}>
          <button onClick={() => go(backRoute)} style={{ all: "unset", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 7,
            fontFamily: "var(--font-ui)", fontSize: 13, color: "var(--ink-3)", marginBottom: 26 }}
            onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"} onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>
            <Icon name="ArrowLeft" size={15} /> {backLabel}
          </button>
          <div style={{ maxWidth: 620 }}>
            {spec.hero.eyebrow && <div className="rise" style={{ animationDelay: ".1s" }}><Eyebrow>{spec.hero.eyebrow}</Eyebrow></div>}
            <h1 className="rise" style={{ animationDelay: ".16s", fontFamily: "var(--font-head)", fontWeight: 400,
              fontSize: "clamp(34px,4.8vw,58px)", lineHeight: 1.05, letterSpacing: "-0.01em", color: "#fff", margin: "14px 0 0" }}>
              <AccentText text={spec.hero.title} accent={spec.hero.accentWord} />
            </h1>
            <p className="rise" style={{ animationDelay: ".24s", fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: "clamp(17px,1.9vw,21px)",
              lineHeight: 1.55, color: "var(--ink-2)", margin: "20px 0 0", maxWidth: 540 }}>{spec.hero.tagline}</p>
            <div className="rise" style={{ animationDelay: ".34s", marginTop: 34 }}>
              <Btn data-hide-nav-cta="true" size="lg" onClick={() => go("onboarding")} iconRight="ArrowRight">{spec.hero.ctaLabel || ctaText}</Btn>
            </div>
          </div>
        </div>
      </section>

      {spec.blocks.map((block, i) => <CDBlock key={i} block={block} />)}

      {/* closing CTA */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "120px 32px 130px", textAlign: "center",
        display: "flex", flexDirection: "column", justifyContent: "center" }}>
        <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/passarelle-boarding.webp)",
          backgroundSize: "cover", backgroundPosition: "center", opacity: 0.12, pointerEvents: "none" }} />
        <div style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse 60% 70% at 50% 50%, rgba(80,134,182,.08), transparent 70%)" }} />
        <Reveal style={{ position: "relative", maxWidth: 620, margin: "0 auto" }}>
          <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(28px,3.6vw,44px)", color: "var(--ink)", margin: 0 }}>{spec.closing.headline}</h2>
          <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16.5, lineHeight: 1.7, color: "var(--ink-3)", margin: "18px auto 32px", maxWidth: 480 }}>{spec.closing.body}</p>
          <Btn data-hide-nav-cta="true" size="lg" onClick={() => go("onboarding")} iconRight="ArrowRight">{spec.closing.ctaLabel || ctaText}</Btn>
        </Reveal>
      </section>
    </div>
  );
}

// ---------------- Yachting AI Agents — dedicated narrative page ----------------
// Renders window.AGENTS_PAGE: a passerelle hero that blends into the body via a
// tall gradient, the same auto-cycling laptop + phone showcase as For Yachts
// (DeviceShowcase, atoms.jsx — captures in /assets/images/agents/showcase/),
// three capability sections (integration / intelligence / leverage) and a
// closing CTA. No eyebrows on this page — the headings carry the accent alone.
// Like home / custom dev / news / simulation, the page scroll-snaps section by
// section (route listed in index.html's snapRoute): every section must be a
// full-height .snap-sec — a short strip between two snap stops is unreachable
// under mandatory snapping, which is why "More on shore" lives inside the
// closing CTA screen instead of standing alone.

function AgHead({ headline, accentWord, body, align = "left", max = 640 }) {
  const centered = align === "center";
  const m = centered ? "auto" : 0;
  return (
    <Reveal style={{ textAlign: align }}>
      <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(28px,3.6vw,44px)", lineHeight: 1.14,
        letterSpacing: "-0.01em", color: "var(--ink)", margin: "14px 0 0", maxWidth: max, whiteSpace: "pre-line",
        marginLeft: m, marginRight: m }}>
        <AccentText text={headline} accent={accentWord} />
      </h2>
      {(body || []).map((para, i) => (
        <p key={i} style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16.5, lineHeight: 1.75, color: "var(--ink-3)",
          margin: "18px 0 0", maxWidth: Math.min(max, 620), marginLeft: m, marginRight: m }}>{para}</p>
      ))}
    </Reveal>
  );
}

// Corpus → agents flow: two nodes joined by a track that carries the same
// travelling pulse as the Custom Development timeline.
function AgFlow({ flow }) {
  const node = ([icon, title, sub], delay) => (
    <Reveal delay={delay} style={{ height: "100%" }}>
      <div style={{ height: "100%", boxSizing: "border-box", padding: "20px 20px", borderRadius: 16, background: "var(--surface)",
        border: "1px solid rgba(80,134,182,.24)", display: "flex", alignItems: "center", gap: 14 }}>
        <span style={{ width: 44, height: 44, flexShrink: 0, borderRadius: 12, background: "rgba(80,134,182,.1)",
          border: "1px solid rgba(80,134,182,.24)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)" }}>
          <Icon name={icon} size={20} />
        </span>
        <span>
          <span style={{ display: "block", fontFamily: "var(--font-ui)", fontSize: 15, fontWeight: 600, color: "var(--ink)" }}>{title}</span>
          <span style={{ display: "block", fontFamily: "var(--font-mono)", fontSize: 10.5, color: "var(--ink-4)", marginTop: 4, letterSpacing: ".02em" }}>{sub}</span>
        </span>
      </div>
    </Reveal>
  );
  return (
    <div style={{ marginTop: 28 }}>
      <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 130px 1fr", gap: 14, alignItems: "stretch" }}>
        {node(flow.from, 0)}
        <div className="ag-flow-track" aria-hidden="true" style={{ position: "relative", alignSelf: "center", height: 2, borderRadius: 2, background: "var(--hairline-2)" }}>
          <span style={{ position: "absolute", top: "50%", left: "50%", transform: "translate(-50%,-50%)", color: "var(--ink-4)", lineHeight: 0 }}>
            <Icon name="ChevronRight" size={15} />
          </span>
          <span className="travel" style={{ position: "absolute", top: "50%", marginTop: -3, width: 6, height: 6, borderRadius: "50%",
            background: "var(--cyan)", boxShadow: "0 0 10px var(--cyan)" }} />
        </div>
        {node(flow.to, 120)}
      </div>
    </div>
  );
}

function AgentsPage({ go, from, related }) {
  const ctaText = useRotatingCTA();
  const spec = window.AGENTS_PAGE;
  const backRoute = "home";
  const backLabel = "Home";
  return (
    <div style={{ background: "var(--bg)" }}>
      {/* 1 ── hero — the passerelle boarding image (same as home / custom dev),
             dissolving into the body through a tall bottom gradient. NOT cd-sec:
             that class drops the 100vh floor on mobile, and a landing hero must
             fill the first screen there — .ag-hero restores it (index.html). */}
      <section className="snap-sec ag-hero" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "150px 32px 170px",
        display: "flex", flexDirection: "column", justifyContent: "center", background: "var(--bg-deep)" }}>
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/passarelle-boarding.webp)",
          backgroundSize: "cover", backgroundPosition: "center", opacity: 0.22, pointerEvents: "none" }} />
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none",
          background: "linear-gradient(90deg, var(--bg-deep) 8%, rgba(7,12,21,.6) 48%, transparent 100%)" }} />
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none",
          background: "radial-gradient(ellipse 70% 80% at 82% 8%, rgba(80,134,182,.13), transparent 58%)" }} />
        {/* the gradient hand-off into the sections below */}
        <div aria-hidden="true" style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: "44%", pointerEvents: "none",
          background: "linear-gradient(to bottom, transparent, rgba(11,17,30,.72) 58%, var(--bg) 100%)" }} />
        <div style={{ maxWidth: 1080, margin: "0 auto", width: "100%", position: "relative" }}>
          <button onClick={() => go(backRoute)} style={{ all: "unset", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 7,
            fontFamily: "var(--font-ui)", fontSize: 13, color: "var(--ink-3)", marginBottom: 26 }}
            onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"} onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>
            <Icon name="ArrowLeft" size={15} /> {backLabel}
          </button>
          <div style={{ maxWidth: 640 }}>
            <h1 className="rise" style={{ animationDelay: ".16s", fontFamily: "var(--font-head)", fontWeight: 400, whiteSpace: "pre-line",
              fontSize: "clamp(36px,5vw,60px)", lineHeight: 1.06, letterSpacing: "-0.01em", color: "#fff", margin: "14px 0 0" }}>
              <AccentText text={spec.hero.title} accent={spec.hero.accentWord} />
            </h1>
            <p className="rise" style={{ animationDelay: ".24s", fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: "clamp(17px,1.9vw,21px)",
              lineHeight: 1.55, color: "var(--ink-2)", margin: "20px 0 0", maxWidth: 560 }}>{spec.hero.tagline}</p>
            <div className="rise" style={{ animationDelay: ".32s", display: "flex", gap: 13, marginTop: 34, flexWrap: "wrap" }}>
              <Btn data-hide-nav-cta="true" size="lg" onClick={() => go("onboarding")} iconRight="ArrowRight">{ctaText}</Btn>
              <Btn size="lg" variant="ghost" onClick={() => go("contact-sales")}>Contact sales</Btn>
            </div>
          </div>
        </div>
      </section>

      {/* 2 ── the solution showcase — the same auto-cycling laptop / phone
             mockups as For Yachts, top-anchored like there so the heading stays
             above the fold when the section snaps in */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "110px 32px 84px",
        display: "flex", flexDirection: "column", justifyContent: "flex-start" }}>
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none",
          background: "radial-gradient(ellipse 74% 46% at 50% 0%, rgba(80,134,182,.09), transparent 62%)" }} />
        {pInner(
          <>
            <AgHead align="center" max={720} headline={spec.devices.headline}
              accentWord={spec.devices.accentWord} body={spec.devices.body} />
            <DeviceShowcase desk={spec.devices.showcaseDesk} mob={spec.devices.showcaseMob} />
          </>
        )}
      </section>

      {/* 3 ── integration — text left, bullet rows right, over a faint office image */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "100px 32px",
        display: "flex", flexDirection: "column", justifyContent: "center", background: "var(--bg-deep)",
        borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/yachting-business.webp)",
          backgroundSize: "cover", backgroundPosition: "center", opacity: 0.07, pointerEvents: "none" }} />
        {pInner(
          <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 56, alignItems: "center" }}>
            <AgHead headline={spec.integration.headline}
              accentWord={spec.integration.accentWord} body={spec.integration.body} max={520} />
            <CDBullets items={spec.integration.items} contained={false} />
          </div>
        )}
      </section>

      {/* 3.5 ── why not a generic chatbot — the same comparison grid as For
             Yachts, with business-flavoured rows (spec.whatIs) */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "110px 32px 84px",
        display: "flex", flexDirection: "column", justifyContent: "flex-start" }}>
        {pInner(
          <>
            <AgHead headline={spec.whatIs.headline} accentWord={spec.whatIs.accentWord} body={[spec.whatIs.body]} max={720} />
            <Reveal delay={100}>
              <div style={{ maxWidth: 780, margin: "36px auto 0" }}>
                <CompareGrid w={spec.whatIs} />
              </div>
            </Reveal>
          </>
        )}
      </section>

      {/* 4 ── yachting intelligence — cards + corpus→agents flow */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "100px 32px",
        display: "flex", flexDirection: "column", justifyContent: "center" }}>
        <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/world.webp)",
          backgroundSize: "cover", backgroundPosition: "center", opacity: 0.06, pointerEvents: "none" }} />
        {pInner(
          <>
            <AgHead headline={spec.intelligence.headline}
              accentWord={spec.intelligence.accentWord} body={spec.intelligence.body} max={680} />
            {/* one 4-card row, not 2×2 — the section must fit a 768px-tall snap
                viewport with the corpus→agents flow still above the fold */}
            <CDCards items={spec.intelligence.items} cols={4} />
            <AgFlow flow={spec.intelligence.flow} />
          </>
        )}
      </section>

      {/* 5 ── leverage — stats left, platform-foundation cards right */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "100px 32px",
        display: "flex", flexDirection: "column", justifyContent: "center", background: "var(--bg-deep)",
        borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/harbor.webp)",
          backgroundSize: "cover", backgroundPosition: "center", opacity: 0.06, pointerEvents: "none" }} />
        {pInner(
          <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 56, alignItems: "center" }}>
            <div>
              <AgHead headline={spec.leverage.headline}
                accentWord={spec.leverage.accentWord} body={spec.leverage.body} max={480} />
              <Reveal delay={100}>
                <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "26px 32px", marginTop: 40, maxWidth: 440 }}>
                  {spec.leverage.stats.map(([n, label]) => (
                    <div key={label}>
                      <div style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(34px,3.6vw,46px)", color: "var(--cyan)", lineHeight: 1 }}>{n}</div>
                      <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13.5, color: "var(--ink-3)", marginTop: 7 }}>{label}</div>
                    </div>
                  ))}
                </div>
              </Reveal>
            </div>
            <CDCards items={spec.leverage.cards} cols={2} />
          </div>
        )}
      </section>

      {/* 6 ── closing CTA + more on shore — one full-height snap stop: a short
             related-cards strip can't rest between two mandatory snap sections,
             so it rides at the bottom of the closing screen instead */}
      <section className="snap-sec cd-sec" style={{ position: "relative", overflow: "hidden", minHeight: "100vh", padding: "100px 32px",
        display: "flex", flexDirection: "column", justifyContent: "center", textAlign: "center" }}>
        {/* softly blurred so it sits as quietly as the other section backdrops;
            the negative inset hides the blur's transparent edge bleed */}
        <div aria-hidden="true" className="cd-bg" style={{ position: "absolute", inset: -16, backgroundImage: "url(/assets/images/yacht-shore.webp)",
          backgroundSize: "cover", backgroundPosition: "center", opacity: 0.1, filter: "blur(4px)", pointerEvents: "none" }} />
        <div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none",
          background: "radial-gradient(ellipse 60% 70% at 50% 50%, rgba(80,134,182,.09), transparent 70%)" }} />
        <div aria-hidden="true" style={{ position: "absolute", left: 0, right: 0, top: 0, height: "30%", pointerEvents: "none",
          background: "linear-gradient(to top, transparent, var(--bg))" }} />
        <Reveal style={{ position: "relative", maxWidth: 620, margin: "0 auto" }}>
          <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(28px,3.6vw,44px)", color: "var(--ink)", margin: 0 }}>
            <AccentText text={spec.closing.headline} accent="at your service." />
          </h2>
          <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16.5, lineHeight: 1.7, color: "var(--ink-3)", margin: "18px auto 32px", maxWidth: 500 }}>{spec.closing.body}</p>
          <div style={{ display: "flex", gap: 13, justifyContent: "center", flexWrap: "wrap" }}>
            <Btn size="lg" onClick={() => go("contact-sales")} iconRight="ArrowRight">{spec.closing.ctaLabel}</Btn>
            <Btn data-hide-nav-cta="true" size="lg" variant="ghost" onClick={() => go("onboarding")}>{ctaText}</Btn>
          </div>
        </Reveal>
        {pInner(
          <Reveal delay={120} style={{ textAlign: "left", marginTop: 88 }}>
            <Eyebrow>More on shore</Eyebrow>
            <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: `repeat(${related.length},1fr)`, gap: 14, marginTop: 20 }}>
              {related.map((rp) => <RelatedCard key={rp.key} p={rp} go={go} />)}
            </div>
          </Reveal>
        )}
      </section>
    </div>
  );
}

// ---------------- Simulation — public KPI + Model Benchmark sections ----------------
// Rendered on any product whose data carries `kpis` / `benchmark` (today: simulation).
// Replicates the simulator's Simulation-page KPI surface and Models page so the
// numbers are visible without login.

function SimKpiSection({ kpis }) {
  return (
    <section style={{ padding: "0 32px 104px" }}>
      {pInner(
        <Reveal>
          <Eyebrow>Key KPIs — the live simulation page</Eyebrow>
          <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(26px,3.2vw,38px)", lineHeight: 1.15,
            letterSpacing: "-0.01em", color: "var(--ink)", margin: "14px 0 0", maxWidth: 640 }}>
            Fourteen numbers, tracked on every run.
          </h2>
          <p style={{ fontFamily: "var(--font-mono)", fontSize: 12, color: "var(--ink-4)", margin: "14px 0 0", letterSpacing: ".01em" }}>{kpis.note}</p>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 14, marginTop: 28 }}>
            {kpis.items.map(([label, value, unit, color, explain]) => (
              <div key={label} style={{ padding: "18px 18px 16px", borderRadius: 15, background: "var(--surface)", border: "1px solid var(--hairline)" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
                  <span aria-hidden="true" style={{ width: 8, height: 8, borderRadius: 3, background: color, flexShrink: 0 }} />
                  <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--ink-3)" }}>{label}</span>
                </div>
                <div style={{ display: "flex", alignItems: "baseline", gap: 4 }}>
                  <span style={{ fontFamily: "var(--font-head)", fontSize: value.length > 8 ? 22 : 30, fontWeight: 500, color, lineHeight: 1 }}>{value}</span>
                  {unit && <span style={{ fontFamily: "var(--font-mono)", fontSize: 13, color: "var(--ink-3)" }}>{unit}</span>}
                </div>
                <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 12.5, lineHeight: 1.55, color: "var(--ink-3)", margin: "10px 0 0" }}>{explain}</p>
              </div>
            ))}
          </div>
        </Reveal>
      )}
    </section>
  );
}

const BENCH_COLORS = ["#33DCC4", "#9B7ED8", "#D8A24A", "#34D399", "#E5634D", "#18C3EE"];

function SimBenchmarkSection({ b }) {
  return (
    <section id="benchmark" style={{ position: "relative", overflow: "hidden", padding: "104px 32px", background: "var(--bg-deep)" }}>
      <div aria-hidden="true" style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse 70% 60% at 50% 0%, rgba(80,134,182,.07), transparent 60%)" }} />
      {pInner(
        <div>
          <Reveal>
            <Eyebrow>Model benchmark — open to everyone</Eyebrow>
            <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(28px,3.6vw,44px)", lineHeight: 1.14,
              letterSpacing: "-0.01em", color: "var(--ink)", margin: "14px 0 0", maxWidth: 680 }}>{b.headline}</h2>
            {b.intro.map((para, i) => (
              <p key={i} style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16, lineHeight: 1.75, color: "var(--ink-2)",
                margin: "18px 0 0", maxWidth: 680 }}>{para}</p>
            ))}
          </Reveal>

          {/* headline stats — mirrors the Models page KPI strip */}
          <Reveal delay={100}>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(170px, 1fr))", gap: 12, marginTop: 36 }}>
              {b.stats.map(([label, value, sub]) => (
                <div key={label} style={{ padding: "16px 16px 14px", borderRadius: 13, background: "var(--surface)", border: "1px solid var(--hairline)" }}>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 8 }}>{label}</div>
                  <div style={{ fontFamily: "var(--font-head)", fontSize: 22, fontWeight: 500, color: "var(--cyan-2)", lineHeight: 1.1 }}>{value}</div>
                  <div style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 12, color: "var(--ink-3)", marginTop: 6 }}>{sub}</div>
                </div>
              ))}
            </div>
          </Reveal>

          {/* per-session leaderboards */}
          {b.sessions.map((s, si) => (
            <Reveal key={s.group} delay={120}>
              <div style={{ marginTop: 34, borderRadius: 15, background: "var(--surface)", border: "1px solid var(--hairline)", overflow: "hidden" }}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 12, flexWrap: "wrap", padding: "14px 20px", borderBottom: "1px solid var(--hairline)" }}>
                  <span style={{ fontFamily: "var(--font-ui)", fontSize: 15, fontWeight: 600, color: "var(--ink)" }}>{s.group}</span>
                  <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-4)" }}>{s.date} · {s.setup} · winner by highest score</span>
                </div>
                <div style={{ overflowX: "auto" }}>
                  <table style={{ width: "100%", minWidth: 640, borderCollapse: "collapse", fontFamily: "var(--font-mono)", fontSize: 12 }}>
                    <thead>
                      <tr>
                        {["Model", "Score", "Range", "P50 latency", "Cost / season", "Tokens out", "Success"].map((h, i) => (
                          <th key={h} style={{ textAlign: i === 0 ? "left" : "right", padding: "10px 16px", color: "var(--ink-4)",
                            fontSize: 10, letterSpacing: ".08em", textTransform: "uppercase", fontWeight: 500, whiteSpace: "nowrap" }}>{h}</th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {s.rows.map(([model, score, range, p50, cost, tokens, success], ri) => {
                        const color = BENCH_COLORS[ri % BENCH_COLORS.length];
                        const winner = ri === 0;
                        return (
                          <tr key={model} style={{ borderTop: "1px solid var(--hairline)", background: winner ? "rgba(80,134,182,.05)" : "transparent" }}>
                            <td style={{ padding: "11px 16px", color: winner ? "var(--ink)" : "var(--ink-2)", whiteSpace: "nowrap" }}>
                              {winner && <span style={{ color: "var(--cyan)", marginRight: 6 }}>★</span>}{model}
                            </td>
                            <td style={{ padding: "11px 16px", textAlign: "right", whiteSpace: "nowrap" }}>
                              <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
                                <span aria-hidden="true" style={{ width: 64, height: 5, borderRadius: 3, background: "var(--wa-07)", overflow: "hidden", display: "inline-block" }}>
                                  <span style={{ display: "block", width: `${score}%`, height: "100%", background: color, borderRadius: 3 }} />
                                </span>
                                <span style={{ color, fontWeight: 700 }}>{score}%</span>
                              </span>
                            </td>
                            <td style={{ padding: "11px 16px", textAlign: "right", color: "var(--ink-3)" }}>{range}</td>
                            <td style={{ padding: "11px 16px", textAlign: "right", color: "var(--ink-2)" }}>{p50}</td>
                            <td style={{ padding: "11px 16px", textAlign: "right", color: "var(--ink-2)" }}>{cost}</td>
                            <td style={{ padding: "11px 16px", textAlign: "right", color: "var(--ink-3)" }}>{tokens}</td>
                            <td style={{ padding: "11px 16px", textAlign: "right", color: "#34D399" }}>{success}</td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              </div>
            </Reveal>
          ))}

          {/* takeaway + methodology */}
          <Reveal delay={140}>
            <div style={{ marginTop: 30, padding: "18px 20px", borderRadius: 13, border: "1px solid rgba(80,134,182,.22)", background: "rgba(80,134,182,.05)" }}>
              <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 14.5, lineHeight: 1.7, color: "var(--ink-2)", margin: 0 }}>{b.insight}</p>
            </div>
            <div style={{ marginTop: 22, display: "grid", gap: 8 }}>
              <span style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, letterSpacing: ".08em", textTransform: "uppercase", color: "var(--ink-4)" }}>How to read this</span>
              {b.notes.map((n, i) => (
                <p key={i} style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 13, lineHeight: 1.6, color: "var(--ink-3)", margin: 0 }}>· {n}</p>
              ))}
            </div>
          </Reveal>
        </div>
      )}
    </section>
  );
}

// ---------------- Embedded subdomain landing ----------------
// For products whose data carries `embedUrl`, the product body IS the live
// subdomain's marketing landing, iframed in headerless (?embed=1). The suite
// header (chrome.jsx) sits above, and we keep a back link, the "More on shore"
// related cards and the site footer around it — so the copy is maintained once
// in the subdomain app and stays in sync here automatically.
function EmbeddedProductPage({ go, p, backRoute, backLabel, related }) {
  // Start a little taller than one viewport to avoid a visible grow-in; the
  // frame reports its true height on load and on every resize (see the
  // reportEmbedHeight handshake in the visibility app's landing).
  const [height, setHeight] = React.useState(2400);

  const frameOrigin = React.useMemo(() => {
    try { return new URL(p.embedUrl).origin; } catch { return null; }
  }, [p.embedUrl]);

  React.useEffect(() => {
    function onMessage(e) {
      // Only trust height messages from the embedded subdomain itself.
      if (frameOrigin && e.origin !== frameOrigin) return;
      const d = e.data;
      // Suite-wide height handshake — any embedded app (yv-/ys-/… -embed-height).
      if (d && typeof d.type === "string" && d.type.endsWith("-embed-height") && typeof d.height === "number" && d.height > 0) {
        setHeight(Math.ceil(d.height));
      }
    }
    window.addEventListener("message", onMessage);
    return () => window.removeEventListener("message", onMessage);
  }, [frameOrigin]);

  return (
    <div style={{ position: "relative", background: "var(--bg)" }}>
      {/* "On shore" back link — a plain, borderless text link sitting right below
          the header wordmark: same centered 1240 / 32px gutter as the nav, so its
          arrow lines up under the logo at any width. Scrolls away with the page.
          NOTE: `all:unset` must come BEFORE the positioning props on the button,
          or it resets position back to static. */}
      <div className="embed-back" style={{ position: "absolute", top: 82, left: 0, right: 0, zIndex: 55, pointerEvents: "none" }}>
        <div style={{ maxWidth: 1240, margin: "0 auto", padding: "0 32px" }}>
          <button onClick={() => go(backRoute)} style={{ all: "unset", cursor: "pointer", pointerEvents: "auto",
            display: "inline-flex", alignItems: "center", gap: 6, fontFamily: "var(--font-ui)", fontSize: 13, color: "var(--ink-3)" }}
            onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"} onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>
            <Icon name="ArrowLeft" size={14} /> {backLabel}
          </button>
        </div>
      </div>

      {/* the live subdomain landing, headerless — starts at the very top so its
          hero image bleeds up behind the transparent suite header (chrome.jsx). */}
      <iframe
        src={p.embedUrl}
        title={p.name}
        scrolling="no"
        style={{ display: "block", width: "100%", height, border: "none", background: "var(--bg)" }}
      />

      {/* related — native suite framing around the embed */}
      <section style={{ padding: "56px 32px 40px" }}>
        {pInner(
          <Reveal>
            <Eyebrow>More on shore</Eyebrow>
            <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: `repeat(${related.length},1fr)`, gap: 14, marginTop: 20 }}>
              {related.map((rp) => <RelatedCard key={rp.key} p={rp} go={go} />)}
            </div>
          </Reveal>
        )}
      </section>
    </div>
  );
}

function ProductPage({ go, productKey, from }) {
  const ctaText = useRotatingCTA();
  const backRoute = "home";
  const backLabel = "Home";
  const PRODUCTS = window.PRODUCTS || {};
  const p = PRODUCTS[productKey] || PRODUCTS.custom;
  const related = (["custom", ...window.ONSHORE_MENU]).filter((k) => k !== p.key).map((k) => PRODUCTS[k]);
  const [lightbox, setLightbox] = React.useState(null);

  // Custom Development gets its own dedicated narrative page. Gate on the
  // RESOLVED key so stale/unknown route keys (restored from localStorage after
  // a rename) land on the narrative page, not the deprecated generic layout.
  if (p.key === "custom" && window.CUSTOM_DEV_PAGE) {
    return <CustomDevPage go={go} from={from} />;
  }

  // Yachting AI Agents gets its own dedicated narrative page too.
  if (p.key === "agents" && window.AGENTS_PAGE) {
    return <AgentsPage go={go} from={from} related={related} />;
  }

  // Products that surface their live subdomain landing (e.g. visibility) embed
  // it in place of the generic layout, keeping the suite chrome around it.
  if (p.embedUrl) {
    return <EmbeddedProductPage go={go} p={p} backRoute={backRoute} backLabel={backLabel} related={related} />;
  }

  return (
    <div style={{ background: "var(--bg)" }}>
      {/* hero */}
      <section style={{ position: "relative", overflow: "hidden", padding: "150px 32px 70px", background: "var(--bg-deep)" }}>
        {p.key === "custom" && <div aria-hidden="true" style={{ position: "absolute", inset: 0, backgroundImage: "url(/assets/images/yard.webp)", backgroundSize: "cover", backgroundPosition: "center", opacity: 0.16, pointerEvents: "none" }} />}
        {p.key === "custom" && <div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none", background: "linear-gradient(90deg, var(--bg-deep) 6%, rgba(7,20,33,.55) 52%, transparent 100%)" }} />}
        <div style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse 70% 80% at 82% 10%, rgba(80,134,182,.12), transparent 58%)" }} />
        {p.key === "custom" && <div aria-hidden="true" style={{ position: "absolute", left: 0, right: 0, bottom: 0, height: "42%", pointerEvents: "none", background: "linear-gradient(to bottom, transparent, var(--bg))" }} />}
        {pInner(
          <div>
            <button onClick={() => go(backRoute)} style={{ all: "unset", cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 7,
              fontFamily: "var(--font-ui)", fontSize: 13, color: "var(--ink-3)", marginBottom: 26 }}
              onMouseEnter={(e) => e.currentTarget.style.color = "var(--ink)"} onMouseLeave={(e) => e.currentTarget.style.color = "var(--ink-3)"}>
              <Icon name="ArrowLeft" size={15} /> {backLabel}
            </button>
            <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: p.key === "custom" ? "1fr" : "1.15fr .85fr", gap: 48, alignItems: "center" }}>
              <div>
                {p.key !== "custom" && (
                <div className="rise" style={{ animationDelay: ".05s", marginBottom: 22 }}>
                  <span style={{ width: 56, height: 56, borderRadius: 15, background: "rgba(80,134,182,.09)", border: "1px solid rgba(80,134,182,.22)",
                    display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)" }}><Icon name={p.icon} size={27} /></span>
                </div>
                )}
                {p.key !== "custom" && <div className="rise" style={{ animationDelay: ".1s" }}><Eyebrow>{p.cat}</Eyebrow></div>}
                <h1 className="rise" style={{ animationDelay: ".16s", fontFamily: "var(--font-head)", fontWeight: 400,
                  fontSize: "clamp(34px,4.6vw,56px)", lineHeight: 1.05, letterSpacing: "-0.01em", color: "#fff", margin: "14px 0 0" }}>{p.name}</h1>
                <p className="rise" style={{ animationDelay: ".24s", fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: "clamp(17px,1.8vw,21px)",
                  lineHeight: 1.55, color: "var(--ink-2)", margin: "18px 0 0", maxWidth: 460 }}>{p.tagline}</p>
                <div className="rise" style={{ animationDelay: ".34s", display: "flex", gap: 13, marginTop: 34, flexWrap: "wrap" }}>
                  <Btn data-hide-nav-cta={p.cta.label === "Begin Onboarding" ? "true" : undefined} size="lg" onClick={() => go(p.cta.route)} iconRight="ArrowRight">{p.cta.label === "Begin Onboarding" ? ctaText : p.cta.label}</Btn>
                  {p.cta.route !== "onboarding" && <Btn size="lg" variant="ghost" onClick={() => go("onboarding")}>Begin Onboarding</Btn>}
                </div>
              </div>
              {/* visual */}
              {p.key !== "custom" && (
              <div className="rise" style={{ animationDelay: ".2s", position: "relative", aspectRatio: "1 / 1", borderRadius: 22,
                border: "1px solid rgba(80,134,182,.2)", background: "var(--panel)", overflow: "hidden", display: "flex", alignItems: "center", justifyContent: "center" }}>
                <div style={{ position: "absolute", inset: 0, background: "radial-gradient(closest-side at 50% 45%, rgba(80,134,182,.14), transparent 72%)" }} />
                <Icon name={p.icon} size={120} color="var(--cyan-2)" stroke={0.55} style={{ opacity: 0.55 }} />
                <span style={{ position: "absolute", bottom: 16, left: 18, fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--ink-3)", letterSpacing: ".02em" }}>{p.domain}</span>
              </div>
              )}
            </div>
          </div>
        )}
      </section>

      {/* what it is + features */}
      <section style={{ padding: "104px 32px" }}>
        {pInner(
          <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 56, alignItems: "start" }}>
            <Reveal>
              <Eyebrow>What it is</Eyebrow>
              <div style={{ marginTop: 18, display: "flex", flexDirection: "column", gap: 18 }}>
                {p.what.map((para, i) => (
                  <p key={i} style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16, lineHeight: 1.75, color: "var(--ink-2)", margin: 0 }}>{para}</p>
                ))}
              </div>
            </Reveal>
            <Reveal delay={120}>
              <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 14 }}>
                {p.features.map(([ic, t]) => (
                  <div key={t} style={{ padding: "20px 18px", borderRadius: 15, background: "var(--surface)", border: "1px solid var(--hairline)" }}>
                    <span style={{ width: 38, height: 38, borderRadius: 11, background: "rgba(80,134,182,.08)", border: "1px solid rgba(80,134,182,.16)",
                      display: "flex", alignItems: "center", justifyContent: "center", color: "var(--cyan-2)", marginBottom: 13 }}><Icon name={ic} size={18} /></span>
                    <div style={{ fontFamily: "var(--font-ui)", fontSize: 13.5, fontWeight: 400, color: "var(--ink-2)", lineHeight: 1.5 }}>{t}</div>
                  </div>
                ))}
              </div>
            </Reveal>
          </div>
        )}
      </section>

      {/* simulation: live KPIs + public model benchmark (no login needed) */}
      {p.kpis && <SimKpiSection kpis={p.kpis} />}
      {p.benchmark && <SimBenchmarkSection b={p.benchmark} />}

      {/* screenshots */}
      {p.screenshots && p.screenshots.length > 0 && (
        <section style={{ padding: "0 32px 64px" }}>
          {pInner(
            <Reveal>
              <Eyebrow>See it in action</Eyebrow>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))", gap: 16, marginTop: 20 }}>
                {p.screenshots.map((s, i) => (
                  <button key={i} onClick={() => setLightbox(i)} style={{ all: "unset", cursor: "pointer", display: "block" }}>
                    <div className="liftcard" style={{ borderRadius: 14, overflow: "hidden", border: "1px solid var(--hairline)", background: "var(--panel)" }}>
                      <img src={s.src} alt={s.alt} style={{ width: "100%", height: "auto", display: "block" }} />
                    </div>
                  </button>
                ))}
              </div>
            </Reveal>
          )}
        </section>
      )}

      {/* related */}
      <section style={{ padding: "32px 32px 40px" }}>
        {pInner(
          <Reveal>
            <Eyebrow>More on shore</Eyebrow>
            <div className="responsive-grid" style={{ display: "grid", gridTemplateColumns: `repeat(${related.length},1fr)`, gap: 14, marginTop: 20 }}>
              {related.map((rp) => <RelatedCard key={rp.key} p={rp} go={go} />)}
            </div>
          </Reveal>
        )}
      </section>

      {/* CTA */}
      <section style={{ position: "relative", overflow: "hidden", padding: "120px 32px 130px", textAlign: "center", marginTop: 48 }}>
        <div style={{ position: "absolute", inset: 0, background: "radial-gradient(ellipse 60% 70% at 50% 50%, rgba(80,134,182,.08), transparent 70%)" }} />
        <Reveal style={{ position: "relative", maxWidth: 600, margin: "0 auto" }}>
          <h2 style={{ fontFamily: "var(--font-head)", fontWeight: 400, fontSize: "clamp(28px,3.6vw,42px)", color: "var(--ink)", margin: 0 }}>
            Deploy {p.name} with us.
          </h2>
          <p style={{ fontFamily: "var(--font-ui)", fontWeight: 300, fontSize: 16, lineHeight: 1.7, color: "var(--ink-3)", margin: "18px auto 32px", maxWidth: 460 }}>
            It starts with onboarding — we map your operation and deploy what fits, under your supervision.
          </p>
          <Btn data-hide-nav-cta="true" size="lg" onClick={() => go("onboarding")} iconRight="ArrowRight">{ctaText}</Btn>
        </Reveal>
      </section>

      {/* screenshot lightbox */}
      {lightbox !== null && p.screenshots && (
        <div onClick={() => setLightbox(null)} className="ymfade" style={{ position: "fixed", inset: 0, zIndex: 95, background: "rgba(4,12,20,.86)",
          backdropFilter: "blur(6px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
          <div onClick={(e) => e.stopPropagation()} style={{ position: "relative", maxWidth: "min(1100px, 92vw)" }}>
            <img src={p.screenshots[lightbox].src} alt={p.screenshots[lightbox].alt}
              style={{ width: "100%", height: "auto", display: "block", borderRadius: 14, border: "1px solid var(--hairline-2)", boxShadow: "0 40px 120px -30px rgba(0,0,0,.8)" }} />
            <button onClick={() => setLightbox(null)} style={{ position: "absolute", top: -44, right: 0, all: "unset", cursor: "pointer", color: "var(--ink-2)" }}>
              <Icon name="X" size={22} />
            </button>
            {p.screenshots.length > 1 && (
              <>
                <button onClick={() => setLightbox((lightbox - 1 + p.screenshots.length) % p.screenshots.length)}
                  style={{ position: "absolute", left: -50, top: "50%", transform: "translateY(-50%)", all: "unset", cursor: "pointer", color: "var(--ink-2)" }}>
                  <Icon name="ChevronLeft" size={26} />
                </button>
                <button onClick={() => setLightbox((lightbox + 1) % p.screenshots.length)}
                  style={{ position: "absolute", right: -50, top: "50%", transform: "translateY(-50%)", all: "unset", cursor: "pointer", color: "var(--ink-2)" }}>
                  <Icon name="ChevronRight" size={26} />
                </button>
              </>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { ProductPage });
