/* The Ghost website — editorial components.
   Square corners, visible rules, crimson reserved for action and intervention.
   Exported to window for the sibling Home.jsx. */

function Kicker({ children, style }) {
  return <div className="gw-kicker" style={style}>{children}</div>;
}

function EditorialButton({ children, variant = "primary", href = "#", onClick }) {
  const base = { display: "inline-flex", alignItems: "center", justifyContent: "center", minHeight: 48, padding: "0 26px", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.16em", textTransform: "uppercase", borderRadius: 0, cursor: "pointer", transition: "transform var(--dur-instant) var(--ease-out), background var(--dur-quick) var(--ease-out)", textDecoration: "none" };
  const skin = variant === "primary"
    ? { background: "var(--crimson)", color: "var(--paper-0)", border: "1px solid var(--crimson)" }
    : { background: "transparent", color: "var(--ink-0)", border: "1px solid var(--ink-0)" };
  return (
    <a href={href} onClick={onClick} style={{ ...base, ...skin }}
      onMouseDown={(e) => (e.currentTarget.style.transform = "translateY(1px)")}
      onMouseUp={(e) => (e.currentTarget.style.transform = "none")}
      onMouseLeave={(e) => (e.currentTarget.style.transform = "none")}>
      {children}
    </a>
  );
}

function MarginNote({ children, style }) {
  return (
    <div style={{ fontFamily: "var(--font-hand)", fontSize: 28, lineHeight: 1.05, color: "var(--ink-2)", transform: "rotate(-2deg)", maxWidth: "26ch", ...style }}>
      {children}
    </div>
  );
}

/* A slot for REAL lamp-lit photography or a physical artefact.
   Never stock imagery, never generated portraiture. With no `src` it shows the
   written brief for the photograph that still needs supplying. */
function ArtefactSlot({ src, alt, label, note, ratio = "4 / 5", style, className, position = "center" }) {
  return (
    <figure className={className} style={{ margin: 0, position: "relative", ...style }}>
      <div style={{ aspectRatio: ratio, background: src ? "var(--ink-0)" : "var(--paper-2)", border: src ? "none" : "1px solid var(--ink-4)", display: "flex", alignItems: "center", justifyContent: "center", padding: src ? 0 : 24, overflow: "hidden" }}>
        {src ? (
          <img src={src} alt={alt || ""} style={{ width: "100%", height: "100%", objectFit: "cover", objectPosition: position, display: "block" }} />
        ) : (
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-3)", textAlign: "center", lineHeight: 1.8 }}>{label}</span>
        )}
      </div>
      {note && <figcaption style={{ fontFamily: "var(--font-typewriter)", fontSize: 15, lineHeight: 1.45, color: "var(--ink-2)", marginTop: 14 }}>{note}</figcaption>}
    </figure>
  );
}

/* An editorial spread, not a card: hard top rule, oversized number, one
   headline, short body, text action. */
function OfferSpread({ number, kicker, headline, body, action, href = "#", note }) {
  const [hover, setHover] = React.useState(false);
  return (
    <div onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ borderTop: "var(--border-slab)", paddingTop: 28, position: "relative" }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 20 }}>
        <span style={{ fontFamily: "var(--font-display)", fontSize: 72, lineHeight: 0.8, color: "var(--ink-4)" }}>{number}</span>
        <Kicker>{kicker}</Kicker>
      </div>
      <h3 style={{ fontFamily: "var(--font-display)", fontSize: 38, lineHeight: 1.08, margin: "20px 0 0", color: "var(--ink-0)", maxWidth: "22ch" }}>{headline}</h3>
      <p style={{ fontFamily: "var(--font-body)", fontSize: 18, lineHeight: 1.58, color: "var(--ink-1)", maxWidth: "48ch", margin: "18px 0 26px" }}>{body}</p>
      <a href={href} style={{ fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.16em", textTransform: "uppercase", color: "var(--ink-0)", borderBottom: `2px solid ${hover ? "var(--crimson)" : "var(--ink-4)"}`, paddingBottom: 4, transition: "border-color var(--dur-quick) var(--ease-out)" }}>{action}</a>
      {note && hover && <MarginNote style={{ position: "absolute", right: 0, bottom: 0, opacity: 0.9 }}>{note}</MarginNote>}
    </div>
  );
}

/* Trade label, outcome headline, three facts, one interpretation.
   Never numbers without saying what moved. */
function ProofCase({ trade, headline, facts = [], interpretation, index, growth, growthNote, views = [], viewsMax = 0 }) {
  const max = viewsMax || Math.max(1, ...views.map((v) => v.value));
  return (
    <div style={{ borderTop: "var(--border-rule)", paddingTop: 24 }}>
      <div style={{ display: "flex", alignItems: "baseline", gap: 16 }}>
        {index ? <span aria-hidden="true" style={{ fontFamily: "var(--font-display)", fontSize: 34, lineHeight: 1, color: "var(--crimson)" }}>{index}</span> : null}
        <Kicker>{trade}</Kicker>
      </div>
      <h4 style={{ fontFamily: "var(--font-display)", fontSize: 30, lineHeight: 1.1, margin: "16px 0 22px", color: "var(--ink-0)", maxWidth: "24ch" }}>{headline}</h4>
      {growth != null ? (
        <div style={{ display: "flex", alignItems: "flex-end", gap: 18, margin: "0 0 22px" }}>
          <span style={{ fontFamily: "var(--font-display)", fontSize: 46, lineHeight: 0.9, color: "var(--ink-0)" }}>+{growth}%</span>
          <span style={{ flex: "1 1 auto" }}>
            <span style={{ display: "block", fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: "0.2em", textTransform: "uppercase", color: "var(--ink-3)", marginBottom: 8 }}>{growthNote || "follower growth, 6 months"}</span>
            <span style={{ display: "block", height: 8, background: "rgba(20,17,14,0.1)" }}>
              <span style={{ display: "block", height: "100%", width: `${Math.min(100, growth)}%`, background: "var(--crimson)" }} />
            </span>
          </span>
        </div>
      ) : null}
      {views.length ? (
        <div style={{ display: "flex", flexDirection: "column", gap: 9, margin: "0 0 22px" }} aria-hidden="true">
          {views.map((v) => (
            <div key={v.label} style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <span style={{ flex: "1 1 auto", height: 14, background: "rgba(20,17,14,0.06)" }}>
                <span style={{ display: "block", height: "100%", width: `${Math.max(4, (v.value / max) * 100)}%`, background: "var(--ink-1)" }} />
              </span>
              <span style={{ flex: "0 0 auto", minWidth: 78, textAlign: "right", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.1em", color: "var(--ink-2)" }}>{v.label}</span>
            </div>
          ))}
        </div>
      ) : null}
      <div style={{ display: "flex", flexDirection: "column", gap: 10, marginBottom: 22 }}>
        {facts.map((f, i) => (
          <div key={i} style={{ display: "flex", gap: 14, fontFamily: "var(--font-typewriter)", fontSize: 15, color: "var(--ink-2)" }}>
            <span style={{ color: "var(--crimson)" }}>—</span><span>{f}</span>
          </div>
        ))}
      </div>
      <p style={{ fontFamily: "var(--font-body)", fontSize: 18, lineHeight: 1.58, color: "var(--ink-0)", margin: 0, maxWidth: "42ch" }}>{interpretation}</p>
    </div>
  );
}

function Testimonial({ quote, name, role, avatar }) {
  const initials = String(name || "").split(/\s+/).map((w) => w[0]).join("").slice(0, 2).toUpperCase();
  return (
    <blockquote style={{ margin: 0, maxWidth: "34ch" }}>
      <p style={{ fontFamily: "var(--font-body)", fontSize: 23, lineHeight: 1.5, color: "var(--ink-0)", margin: 0, textIndent: "-0.42em" }}>
        <span style={{ color: "var(--crimson)" }}>&ldquo;</span>{quote}&rdquo;
      </p>
      <footer style={{ marginTop: 18, display: "flex", alignItems: "center", gap: 14 }}>
        <span aria-hidden="true" style={{ flex: "0 0 auto", width: 46, height: 46, borderRadius: "50%", overflow: "hidden", background: "var(--ink-1)", color: "var(--paper-1)", display: "inline-flex", alignItems: "center", justifyContent: "center", fontFamily: "var(--font-mono)", fontSize: 13, letterSpacing: "0.06em", border: "1px solid var(--ink-2)" }}>
          {avatar ? <img src={avatar} alt="" style={{ width: "100%", height: "100%", objectFit: "cover", display: "block", filter: "grayscale(1) contrast(1.06)" }} /> : initials}
        </span>
        <span>
          <span style={{ display: "block", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-0)" }}>{name}</span>
          <span style={{ display: "block", fontFamily: "var(--font-body)", fontSize: 16, color: "var(--ink-3)", marginTop: 4 }}>{role}</span>
        </span>
      </footer>
    </blockquote>
  );
}

/* Horizontal ledger on desktop, vertical sequence on mobile. */
function ProcessLedger({ steps = [] }) {
  return (
    <div className="gw-ledger" style={{ borderTop: "var(--border-rule)" }}>
      {steps.map((s, i) => (
        <div key={i} style={{ paddingTop: 24, borderRight: i < steps.length - 1 ? "var(--border-hair)" : "none", paddingRight: 24 }}>
          <div style={{ fontFamily: "var(--font-display)", fontSize: 56, lineHeight: 0.9, color: "var(--ink-4)" }}>{String(i + 1).padStart(2, "0")}</div>
          <h5 style={{ fontFamily: "var(--font-display)", fontSize: 26, margin: "16px 0 12px", color: "var(--ink-0)" }}>{s.title}</h5>
          <p style={{ fontFamily: "var(--font-body)", fontSize: 17, lineHeight: 1.58, color: "var(--ink-2)", margin: 0 }}>{s.body}</p>
        </div>
      ))}
    </div>
  );
}

function EssayCard({ title, standfirst, date, category, href = "https://theanaloguethinker.substack.com" }) {
  const [hover, setHover] = React.useState(false);
  return (
    <a href={href} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ display: "block", borderTop: `2px solid ${hover ? "var(--crimson)" : "var(--ink-4)"}`, paddingTop: 20, color: "inherit", textDecoration: "none", transition: "border-color var(--dur-quick) var(--ease-out)" }}>
      <Kicker>{category}</Kicker>
      <h4 style={{ fontFamily: "var(--font-display)", fontSize: 26, lineHeight: 1.12, margin: "14px 0 12px", color: "var(--ink-0)" }}>{title}</h4>
      <p style={{ fontFamily: "var(--font-body)", fontSize: 17, lineHeight: 1.5, color: "var(--ink-2)", margin: "0 0 16px" }}>{standfirst}</p>
      <div style={{ fontFamily: "var(--font-typewriter)", fontSize: 14, color: "var(--ink-3)" }}>{date}</div>
    </a>
  );
}

/* An embedded client video. Captions and a transcript are required by the
   accessibility rules, so the transcript link is part of the component. */
function VideoEmbed({ src, title, note, transcriptHref, ratio = "16 / 9", className, style }) {
  return (
    <figure className={className} style={{ margin: 0, ...style }}>
      <div style={{ aspectRatio: ratio, background: "var(--ink-0)", border: "1px solid var(--ink-4)", overflow: "hidden" }}>
        <iframe src={src} title={title} allow="fullscreen; clipboard-write" allowFullScreen
          style={{ width: "100%", height: "100%", border: 0, display: "block" }} />
      </div>
      <figcaption style={{ marginTop: 14, display: "flex", flexWrap: "wrap", gap: 14, alignItems: "baseline" }}>
        {note && <span style={{ fontFamily: "var(--font-typewriter)", fontSize: 15, lineHeight: 1.45, color: "var(--ink-2)" }}>{note}</span>}
        {transcriptHref && <a href={transcriptHref} style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase" }}>Read the transcript</a>}
      </figcaption>
    </figure>
  );
}

/* Fit a heading to its container. Sentence separation is already guaranteed by
   the .gw-line blocks, so shrinking is ONLY worth doing when it actually buys a
   single line and stays within the spec's headline range. A long sentence is
   left at its design size and allowed to wrap. */
function useFitLines(ref, max) {
  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;
    const fit = () => {
      const lines = el.querySelectorAll(".gw-line");
      if (!lines.length) return;
      el.style.fontSize = max + "px";
      const avail = el.clientWidth;
      if (!avail) return;
      let widest = 0;
      lines.forEach((l) => {
        const prev = l.style.whiteSpace;
        l.style.whiteSpace = "nowrap";
        widest = Math.max(widest, l.scrollWidth);
        l.style.whiteSpace = prev;
      });
      if (widest <= avail) return;
      // Never take a heading below the spec's section-headline minimum.
      const floor = window.matchMedia("(max-width: 560px)").matches ? 36 : 42;
      const required = Math.floor(max * (avail / widest) * 0.995);
      if (required >= floor) { el.style.fontSize = required + "px"; return; }
      /* Shrinking cannot win a single line at a readable size, so keep the
         design size and let the sentence wrap — but not so large that a single
         word would spill out of the column. */
      const cs = getComputedStyle(el);
      const ctx = (fit._ctx = fit._ctx || document.createElement("canvas").getContext("2d"));
      ctx.font = `${cs.fontWeight} ${max}px ${cs.fontFamily}`;
      let widestWord = 0;
      (el.textContent || "").split(/\s+/).forEach((w) => {
        if (w) widestWord = Math.max(widestWord, ctx.measureText(w).width);
      });
      el.style.fontSize = (widestWord > avail
        ? Math.max(24, Math.floor(max * (avail / widestWord) * 0.98))
        : max) + "px";
    };
    fit();
    const ro = new ResizeObserver(fit);
    ro.observe(el);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(fit).catch(() => {});
    return () => ro.disconnect();
  }, [max]);
}

/* A heading whose sentences each sit on their own line, always. Pass `lines`. */
function FitHeading({ lines = [], level = 1, max = 96, className, style }) {
  const ref = React.useRef(null);
  useFitLines(ref, max);
  const Tag = level === 1 ? "h1" : level === 3 ? "h3" : "h2";
  return (
    <Tag ref={ref} className={className || (level === 1 ? "gw-h1" : "gw-h2")} style={style}>
      {lines.map((l, i) => <span key={i} className="gw-line" style={{ display: "block" }}>{l}</span>)}
    </Tag>
  );
}

/* Page shell section. Dark sections are acts in the story, not stripes. */
function Section({ children, dark = false, tint, style, id }) {
  return (
    <section id={id} className="gw-section" style={{ background: dark ? "var(--ink-0)" : (tint || "var(--paper-0)"), color: dark ? "var(--paper-0)" : "var(--ink-0)", ...style }}>
      {children}
    </section>
  );
}

/* Numbered deliverables, set as a typewritten schedule. */
function DeliverablesList({ items = [], start = 0 }) {
  return (
    <ol start={start + 1} style={{ listStyle: "none", margin: 0, padding: 0 }}>
      {items.map((it, i) => (
        <li key={i} style={{ display: "flex", gap: 16, alignItems: "baseline", padding: "14px 0", borderTop: "var(--border-hair)" }}>
          <span style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", color: "var(--crimson)", flex: "none" }}>{String(i + 1 + start).padStart(2, "0")}</span>
          <span style={{ fontFamily: "var(--font-body)", fontSize: 18, lineHeight: 1.5 }}>{it}</span>
        </li>
      ))}
    </ol>
  );
}

/* Prices stay visible. Large Caslon figures, plain terms, typewriter inclusions. */
function PriceBlock({ label, price, terms, includes = [], footnote }) {
  return (
    <div style={{ borderTop: "var(--border-slab)", paddingTop: 28 }}>
      <Kicker>{label}</Kicker>
      <div style={{ fontFamily: "var(--font-display)", fontSize: 72, lineHeight: 1, margin: "18px 0 8px" }}>{price}</div>
      {terms && <div style={{ fontFamily: "var(--font-body)", fontSize: 20, color: "var(--ink-2)" }}>{terms}</div>}
      {includes.length > 0 && (
        <ul style={{ listStyle: "none", margin: "26px 0 0", padding: 0, display: "flex", flexDirection: "column", gap: 10 }}>
          {includes.map((i) => (
            <li key={i} style={{ display: "flex", gap: 12, fontFamily: "var(--font-typewriter)", fontSize: 15, lineHeight: 1.45, color: "var(--ink-1)" }}>
              <span style={{ color: "var(--crimson)" }}>&mdash;</span><span>{i}</span>
            </li>
          ))}
        </ul>
      )}
      {footnote && <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-3)", marginTop: 26 }}>{footnote}</div>}
    </div>
  );
}

/* Questions open in text, never behind decorative chrome. */
function FAQ({ items = [] }) {
  const [open, setOpen] = React.useState(null);
  return (
    <div>
      {items.map((it, i) => (
        <div key={i} style={{ borderTop: "var(--border-hair)" }}>
          <button onClick={() => setOpen(open === i ? null : i)} aria-expanded={open === i}
            style={{ width: "100%", display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 24, minHeight: 48, padding: "20px 0", background: "none", border: 0, cursor: "pointer", textAlign: "left", borderRadius: 0 }}>
            <span style={{ fontFamily: "var(--font-display)", fontSize: 26, lineHeight: 1.15, color: "var(--ink-0)" }}>{it.q}</span>
            <span style={{ fontFamily: "var(--font-mono)", fontSize: 20, color: "var(--crimson)", flex: "none" }}>{open === i ? "\u2212" : "+"}</span>
          </button>
          {open === i && (
            <p style={{ fontFamily: "var(--font-body)", fontSize: 18, lineHeight: 1.58, color: "var(--ink-1)", margin: "0 0 24px", maxWidth: "62ch" }}>{it.a}</p>
          )}
        </div>
      ))}
    </div>
  );
}

/* The closing ask, repeated after each major decision point. */
function FinalAction({ headline, body, action = "Book a Vibe Check", href = "book.html" }) {
  const lines = Array.isArray(headline) ? headline : [headline];
  return (
    <Section dark style={{ textAlign: "center" }}>
      <div className="gw-wrap gw-measure" style={{ maxWidth: 900 }}>
        <FitHeading lines={lines} level={2} max={72} style={{ margin: 0 }} />
        {body && <p style={{ fontFamily: "var(--font-body)", fontSize: 23, lineHeight: 1.5, color: "var(--paper-2)", margin: "28px auto 40px", maxWidth: "46ch" }}>{body}</p>}
        <EditorialButton href={href}>{action}</EditorialButton>
      </div>
    </Section>
  );
}

/* A page hero: kicker, headline, lead, one action. `headline` is an array of
   sentences — each gets its own line and the type is fitted to the column. */
function PageHero({ kicker, headline, lead, action, href = "book.html", secondary, secondaryHref, art }) {
  const lines = Array.isArray(headline) ? headline : [headline];
  return (
    <Section dark>
      <div className={art ? "gw-wrap gw-grid gw-grid--hero" : "gw-wrap"}>
        <div className="gw-measure">
          <div className="gw-kicker" style={{ color: "var(--ink-4)" }}>{kicker}</div>
          <FitHeading lines={lines} level={1} max={96} style={{ margin: "32px 0 0" }} />
          {lead && <p style={{ fontFamily: "var(--font-body)", fontSize: 23, lineHeight: 1.5, color: "var(--paper-2)", maxWidth: "46ch", margin: "34px 0 40px" }}>{lead}</p>}
          <div className="gw-actions" style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
            {action && <EditorialButton href={href}>{action}</EditorialButton>}
            {secondary && (
              <a href={secondaryHref || "#"} style={{ display: "inline-flex", alignItems: "center", minHeight: 48, padding: "0 26px", border: "1px solid var(--paper-2)", color: "var(--paper-0)", fontFamily: "var(--font-mono)", fontSize: 12, letterSpacing: "0.16em", textTransform: "uppercase", textDecoration: "none" }}>{secondary}</a>
            )}
          </div>
        </div>
        {art}
      </div>
    </Section>
  );
}

function Header({ current }) {
  const items = [
    { label: "The Work", href: "index.html" },
    { label: "Known for Something", href: "known-for-something.html" },
    { label: "Ghostwriting", href: "ghostwriting.html" },
    { label: "The Analogue Thinker", href: "analogue-thinker.html" },
    { label: "Who is The Ghost", href: "who-is-the-ghost.html" },
  ];
  const [open, setOpen] = React.useState(false);
  /* compact === null means "not measured yet": render the nav so it can be
     measured, but keep it invisible to avoid a flash of overflowing nav. */
  const [compact, setCompact] = React.useState(null);
  const sheetRef = React.useRef(null);
  const btnRef = React.useRef(null);
  const innerRef = React.useRef(null);
  const navRef = React.useRef(null);
  const brandRef = React.useRef(null);
  const neededRef = React.useRef(0);

  /* The nav is swapped for a Menu control when it genuinely does not fit,
     measured from live layout. No breakpoint can know this: the required width
     depends on the label text and on whether the webfont has loaded. */
  React.useLayoutEffect(() => {
    const inner = innerRef.current;
    if (!inner) return;

    /* Measure the nav's true required width. If the nav is currently hidden it
       reports scrollWidth 0, so force it measurable (off-screen, no paint)
       for the read and restore afterwards. Cache the result. */
    const readNeeded = () => {
      const nav = navRef.current;
      const brand = brandRef.current;
      if (!nav || !brand) return;
      const hidden = nav.offsetParent === null || nav.scrollWidth === 0;
      let saved;
      if (hidden) {
        saved = { display: nav.style.display, visibility: nav.style.visibility, position: nav.style.position };
        nav.style.display = "flex";
        nav.style.visibility = "hidden";
        nav.style.position = "absolute";
      }
      const navW = nav.scrollWidth;
      if (hidden) {
        nav.style.display = saved.display;
        nav.style.visibility = saved.visibility;
        nav.style.position = saved.position;
      }
      if (navW > 0) {
        const cs = getComputedStyle(inner);
        const chrome = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight) + (parseFloat(cs.columnGap) || 0);
        neededRef.current = chrome + brand.offsetWidth + navW + 8;
      }
    };

    const apply = () => {
      if (!neededRef.current) readNeeded();
      if (!neededRef.current) return;
      setCompact(inner.getBoundingClientRect().width < neededRef.current);
    };

    readNeeded();
    apply();
    const ro = new ResizeObserver(apply);
    ro.observe(inner);
    if (document.fonts && document.fonts.ready) {
      document.fonts.ready.then(() => { readNeeded(); apply(); }).catch(() => {});
    }
    return () => ro.disconnect();
  }, []);

  React.useEffect(() => {
    if (!open) return;
    const sheet = sheetRef.current;
    const focusables = () => sheet.querySelectorAll('a[href], button');
    const first = focusables()[0];
    if (first) first.focus();
    const onKey = (e) => {
      if (e.key === "Escape") { setOpen(false); if (btnRef.current) btnRef.current.focus(); return; }
      if (e.key !== "Tab") return;
      const f = focusables();
      if (!f.length) return;
      const a = f[0], z = f[f.length - 1];
      if (e.shiftKey && document.activeElement === a) { e.preventDefault(); z.focus(); }
      else if (!e.shiftKey && document.activeElement === z) { e.preventDefault(); a.focus(); }
    };
    document.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => { document.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
  }, [open]);

  return (
    <header style={{ position: "relative", borderBottom: "var(--border-ink)", background: "var(--paper-0)" }}>
      <div className="gw-header-inner" ref={innerRef}>
        <a href="index.html" ref={brandRef} style={{ display: "inline-flex", alignItems: "center", gap: 12, fontFamily: "var(--font-display)", fontSize: 30, color: "var(--ink-0)", textDecoration: "none", letterSpacing: "-0.01em", whiteSpace: "nowrap", flex: "none" }}>
          <img src="assets/ghost-mascot.png" alt="" style={{ height: 38, display: "block" }} />
          <span>The Ghost<span style={{ color: "var(--crimson)" }}>.</span></span>
        </a>
        <nav className="gw-nav" ref={navRef} aria-hidden={compact ? "true" : undefined}
          style={{ display: compact ? "none" : "flex", visibility: compact === null ? "hidden" : "visible", flex: "none" }}>
          {items.map((it) => (
            <a key={it.label} href={it.href} aria-current={current === it.label ? "page" : undefined}
              style={current === it.label ? { color: "var(--ink-0)", borderBottom: "2px solid var(--crimson)", paddingBottom: 3 } : undefined}>{it.label}</a>
          ))}
          <EditorialButton href="book.html">Book a Vibe Check</EditorialButton>
        </nav>
        <div style={{ flex: "none", marginLeft: compact ? "auto" : -8 }}>
          <SocialMarks />
        </div>
        <button ref={btnRef} className="gw-menu-btn" aria-expanded={open} aria-controls="gw-menu-sheet" onClick={() => setOpen(true)}
          style={{ display: compact ? "inline-flex" : "none", alignItems: "center", gap: 10, minHeight: 48, padding: "0 18px", background: "transparent", border: "1px solid var(--ink-0)", color: "var(--ink-0)", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", cursor: "pointer", borderRadius: 0, flex: "none" }}>
          Menu
        </button>
      </div>
      {open && (
        <div className="gw-sheet" id="gw-menu-sheet" role="dialog" aria-modal="true" aria-label="Menu" ref={sheetRef}>
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 24 }}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 10, fontFamily: "var(--font-display)", fontSize: 28, color: "var(--paper-0)" }}>
              <img src="assets/ghost-mascot.png" alt="" style={{ height: 34, display: "block" }} />
              <span>The Ghost<span style={{ color: "var(--crimson)" }}>.</span></span>
            </span>
            <button onClick={() => { setOpen(false); if (btnRef.current) btnRef.current.focus(); }}
              style={{ minHeight: 48, padding: "0 18px", background: "transparent", border: "1px solid var(--paper-2)", color: "var(--paper-0)", fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", cursor: "pointer", borderRadius: 0 }}>Close</button>
          </div>
          {items.map((it) => (
            <a key={it.label} className="gw-sheet-link" href={it.href} onClick={() => setOpen(false)}>{it.label}</a>
          ))}
          <div style={{ marginTop: 32 }}>
            <EditorialButton href="book.html">Book a Vibe Check</EditorialButton>
          </div>
          <div style={{ marginTop: 28, paddingTop: 20, borderTop: "1px solid var(--ink-2)" }}>
            <SocialMarks color="var(--paper-0)" />
          </div>
        </div>
      )}
    </header>
  );
}

/* Brand marks for the header and the mobile sheet. */
const SOCIALS = [
  { name: "LinkedIn", href: "https://www.linkedin.com/in/meetsarra/", path: "M4.98 3.5A2.5 2.5 0 1 1 2.5 6 2.49 2.49 0 0 1 4.98 3.5zM3 8.98h4V21H3zM9.5 8.98h3.83v1.64h.05a4.2 4.2 0 0 1 3.78-2.08c4.04 0 4.79 2.66 4.79 6.12V21h-4v-5.53c0-1.32-.02-3.02-1.84-3.02-1.84 0-2.12 1.44-2.12 2.92V21h-4z" },
  { name: "Substack", href: "https://substack.com/@sarrarichmond", path: "M3 3h18v2.6H3zM3 7.6h18v2.6H3zM3 12.2 12 17l9-4.8V21l-9-4.8L3 21z" },
  { name: "All links", href: "https://linktr.ee/homeoftheghost", path: "M4 12a4 4 0 0 1 4-4h3v2H8a2 2 0 0 0 0 4h3v2H8a4 4 0 0 1-4-4zm9-4h3a4 4 0 0 1 0 8h-3v-2h3a2 2 0 0 0 0-4h-3zM8 11h8v2H8z" },
];

function SocialMarks({ size = 16, color = "var(--ink-0)", gap = 0 }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap }}>
      {SOCIALS.map((s) => (
        <a key={s.name} href={s.href} target="_blank" rel="noopener" aria-label={s.name} title={s.name}
          style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 30, height: 44, color, textDecoration: "none" }}>
          <svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" aria-hidden="true" focusable="false"><path d={s.path} /></svg>
        </a>
      ))}
    </div>
  );
}

function Footer() {
  return (
    <footer className="gw-footer" style={{ background: "var(--ink-0)", color: "var(--paper-1)" }}>
      <div className="gw-footer-inner">
        <div>
          <div style={{ display: "flex", alignItems: "center", gap: 16, fontFamily: "var(--font-display)", fontSize: 44, lineHeight: 1 }}>
            <img src="assets/ghost-mascot.png" alt="" style={{ height: 54, display: "block" }} />
            <span>The Ghost<span style={{ color: "var(--crimson)" }}>.</span></span>
          </div>
          <p style={{ fontFamily: "var(--font-body)", fontSize: 17, color: "var(--ink-4)", maxWidth: "34ch", marginTop: 18 }}>Positioning and ghostwriting for senior experts. Registered in England.</p>
          <address style={{ fontStyle: "normal", fontFamily: "var(--font-body)", fontSize: 17, lineHeight: 1.6, color: "var(--paper-1)", marginTop: 26, display: "flex", flexDirection: "column", gap: 4 }}>
            <span style={{ color: "var(--ink-4)" }}>2 Blackworth Court, Highworth, SN6 7UN</span>
            <a href="mailto:sarra@theghost.rocks" style={{ color: "var(--paper-1)", textDecoration: "none" }}>sarra@theghost.rocks</a>
            <a href="tel:+447743129035" style={{ color: "var(--paper-1)", textDecoration: "none" }}>+44 (0) 7743 129035</a>
          </address>
        </div>
        <div className="gw-footer-cols" style={{ display: "flex", gap: 60 }}>
          {[["Offers", [["Known for Something", "known-for-something.html"], ["Ghostwriting", "ghostwriting.html"], ["Book a Vibe Check", "book.html"]]], ["Elsewhere", [["The Analogue Thinker", "analogue-thinker.html"], ["Who is The Ghost", "who-is-the-ghost.html"], ["Privacy", "privacy.html"], ["Terms", "terms.html"]]], ["Find me", [["LinkedIn", "https://www.linkedin.com/in/meetsarra/"], ["Substack", "https://substack.com/@sarrarichmond"], ["All links", "https://linktr.ee/homeoftheghost"]]]].map(([h, links]) => (
            <div key={h}>
              <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--ink-4)", marginBottom: 16 }}>{h}</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
                {links.map(([l, href]) => <a key={l} href={href} style={{ fontFamily: "var(--font-body)", fontSize: 17, color: "var(--paper-1)", textDecoration: "none" }}>{l}</a>)}
              </div>
            </div>
          ))}
        </div>
      </div>
    </footer>
  );
}

Object.assign(window, { Section, FitHeading, Kicker, EditorialButton, MarginNote, ArtefactSlot, VideoEmbed, OfferSpread, ProofCase, Testimonial, ProcessLedger, EssayCard, DeliverablesList, PriceBlock, FAQ, FinalAction, PageHero, Header, Footer, SocialMarks });
