/* ============================================================
   app.jsx — shell: router, navbar, sidebar, TOC, theme, search
   Mounts the whole site.
   ============================================================ */
const { useState: uS, useEffect: uE, useRef: uR, useMemo: uM, useCallback: uC } = React;

/* ---------------- routing ---------------- */
function useHashRoute() {
  const parse = () => {
    const h = (window.location.hash || "").replace(/^#/, "");
    if (!h || h === "/") return { view: "home" };
    if (/^\/admin/.test(h)) return { view: "admin" };
    if (/^\/devlog/.test(h)) return { view: "home" };   // devlog hidden — redirect to home
    const m = h.match(/^\/docs\/([\w-]+)/);
    if (m) return { view: "docs", id: m[1], anchor: h.split("#")[1] };
    return { view: "home" };
  };
  const [route, setRoute] = uS(parse);
  uE(() => {
    const on = () => setRoute(parse());
    window.addEventListener("hashchange", on);
    return () => window.removeEventListener("hashchange", on);
  }, []);
  return route;
}

/* ---------------- theme ---------------- */
function useTheme() {
  const hadStored = !!localStorage.getItem("konform-theme");
  const [theme, setTheme] = uS(() => localStorage.getItem("konform-theme") || (window.cfg ? window.cfg("defaultTheme") : "light"));
  const appliedDefault = uR(hadStored);
  uE(() => {
    document.documentElement.setAttribute("data-theme", theme);
    localStorage.setItem("konform-theme", theme);
  }, [theme]);
  // First-time visitors (no saved theme) follow the admin's default once settings load.
  uE(() => {
    if (appliedDefault.current) return;
    const apply = () => { if (!appliedDefault.current && window.cfg) { appliedDefault.current = true; setTheme(window.cfg("defaultTheme")); } };
    if (window.__cloud && window.__cloud.loaded) apply();
    window.addEventListener("konform-docs-updated", apply);
    return () => window.removeEventListener("konform-docs-updated", apply);
  }, []);
  return [theme, setTheme];
}

/* ---------------- search index ---------------- */
function buildIndex() {
  const items = [];
  PAGES.forEach((p) => {
    const bodyText = p.blocks.map((b) => b.text || (b.items ? b.items.join(" ") : "") || "").join(" ");
    items.push({ kind: "page", pageId: p.id, title: p.title, group: p.group, icon: PAGE_ICON[p.id], snippet: p.lede || bodyText.slice(0, 120), body: (p.title + " " + (p.lede || "") + " " + bodyText).toLowerCase() });
    p.blocks.forEach((b) => {
      if (b.type === "h2" || b.type === "h3") {
        items.push({ kind: "heading", pageId: p.id, title: b.text, crumb: p.title, anchor: slugify(b.text), icon: PAGE_ICON[p.id], body: (b.text + " " + p.title).toLowerCase() });
      }
    });
  });
  return items;
}

function SearchPalette({ onClose }) {
  const index = uM(buildIndex, []);
  const [q, setQ] = uS("");
  const [sel, setSel] = uS(0);
  const inputRef = uR(null);
  const listRef = uR(null);

  uE(() => { inputRef.current?.focus(); }, []);

  const results = uM(() => {
    const query = q.trim().toLowerCase();
    if (!query) {
      return index.filter((i) => i.kind === "page").slice(0, 8);
    }
    const terms = query.split(/\s+/);
    const scored = [];
    index.forEach((it) => {
      let score = 0;
      const title = it.title.toLowerCase();
      terms.forEach((t) => {
        if (title.startsWith(t)) score += 12;
        else if (title.includes(t)) score += 7;
        else if (it.body.includes(t)) score += 2;
        else score -= 5;
      });
      if (it.kind === "page") score += 1.5;
      if (score > 0) scored.push({ it, score });
    });
    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, 12).map((s) => s.it);
  }, [q, index]);

  uE(() => { setSel(0); }, [q]);

  const choose = (it) => {
    if (!it) return;
    let hash;
    if (it.kind === "heading") hash = `#/docs/${it.pageId}#${it.anchor}`;
    else if (it.kind === "devlog") hash = `#/devlog/${it.pageId}`;
    else hash = `#/docs/${it.pageId}`;
    window.location.hash = hash;
    onClose();
  };

  const onKey = (e) => {
    if (e.key === "ArrowDown") { e.preventDefault(); setSel((s) => Math.min(s + 1, results.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setSel((s) => Math.max(s - 1, 0)); }
    else if (e.key === "Enter") { e.preventDefault(); choose(results[sel]); }
    else if (e.key === "Escape") { onClose(); }
  };

  uE(() => {
    const el = listRef.current?.querySelector(".search-item.active");
    el?.scrollIntoView({ block: "nearest" });
  }, [sel]);

  const hl = (text) => {
    const query = q.trim();
    if (!query) return text;
    const i = text.toLowerCase().indexOf(query.toLowerCase());
    if (i < 0) return text;
    return (<>{text.slice(0, i)}<em>{text.slice(i, i + query.length)}</em>{text.slice(i + query.length)}</>);
  };

  return (
    <div className="search-overlay" onMouseDown={onClose}>
      <div className="search-modal" onMouseDown={(e) => e.stopPropagation()} onKeyDown={onKey}>
        <div className="search-input-row">
          <I.search />
          <input ref={inputRef} className="search-input" placeholder="Search the documentation…" value={q} onChange={(e) => setQ(e.target.value)} />
          <span className="kbd">Esc</span>
        </div>
        <div className="search-results" ref={listRef}>
          {results.length === 0 && <div className="search-empty">No matches for “{q}”.</div>}
          {!q && results.length > 0 && <div className="search-group-label">Jump to</div>}
          {results.map((it, i) => {
            const Icon = resolveIcon(it.icon, "doc");
            return (
              <div key={it.kind + it.pageId + (it.anchor || "") + i} className={"search-item" + (i === sel ? " active" : "")}
                   onMouseEnter={() => setSel(i)} onMouseDown={(e) => { e.preventDefault(); choose(it); }}>
                <div className="si-icon">{it.kind === "heading" ? <I.hash /> : (Icon ? <Icon /> : null)}</div>
                <div className="si-main">
                  <div className="si-title">{hl(it.title)}</div>
                  {it.kind === "heading"
                    ? <div className="si-crumb">{it.crumb}</div>
                    : <div className="si-snip">{it.snippet}</div>}
                </div>
                <div className="si-crumb">{it.kind === "heading" ? "Heading" : it.group}</div>
              </div>
            );
          })}
        </div>
        <div className="search-foot">
          <span className="sf-key"><span className="kbd">↑</span><span className="kbd">↓</span> navigate</span>
          <span className="sf-key"><span className="kbd">↵</span> open</span>
          <span className="sf-key"><span className="kbd">esc</span> close</span>
        </div>
      </div>
    </div>
  );
}

/* ---------------- sidebar ---------------- */
/* Build a parent→children tree from a flat list of pages in one group.
   A page nests under `parent` only if that parent is in the same group. */
function buildPageTree(items) {
  const list = items.filter(Boolean);
  const ids = new Set(list.map((p) => p.id));
  const childrenOf = {};
  const roots = [];
  list.forEach((p) => {
    const par = p.parent && p.parent !== p.id && ids.has(p.parent) ? p.parent : null;
    if (par) (childrenOf[par] = childrenOf[par] || []).push(p);
    else roots.push(p);
  });
  return { roots, childrenOf };
}

/* ids of every ancestor of `id` within a group tree — used to auto-open the active branch */
function ancestorsOf(id, list) {
  const byId = {}; list.forEach((p) => { byId[p.id] = p; });
  const out = [];
  let cur = byId[id];
  while (cur && cur.parent && byId[cur.parent]) { out.push(cur.parent); cur = byId[cur.parent]; }
  return out;
}

function SideNode({ node, childrenOf, depth, activeId, onNavigate, expanded, setExpanded }) {
  const kids = childrenOf[node.id] || [];
  const hasKids = kids.length > 0;
  const open = expanded[node.id] !== false; // default expanded
  return (
    <>
      <a href={"#/docs/" + node.id} onClick={onNavigate}
         className={"side-link" + (node.id === activeId ? " active" : "")}
         style={{ paddingLeft: 31 + depth * 15 }}>
        <span className="sl-text">{node.title.replace(/ —.*$/, "")}</span>
        {node.draft && <span className="badge draft">Draft</span>}
        {hasKids && (
          <button className={"sl-twist" + (open ? " open" : "")} aria-label="Toggle children"
            onClick={(e) => { e.preventDefault(); e.stopPropagation(); setExpanded(node.id, !open); }}>
            <I.chevron style={{ width: 13, height: 13 }} />
          </button>
        )}
      </a>
      {hasKids && open && kids.map((k) => (
        <SideNode key={k.id} node={k} childrenOf={childrenOf} depth={depth + 1}
          activeId={activeId} onNavigate={onNavigate} expanded={expanded} setExpanded={setExpanded} />
      ))}
    </>
  );
}

function Sidebar({ activeId, open, onNavigate }) {
  const [collapsed, setCollapsed] = uS({});
  const [expanded, setExpandedState] = uS({});
  const toggle = (g) => setCollapsed((c) => ({ ...c, [g]: !c[g] }));
  const setExpanded = (id, val) => setExpandedState((e) => ({ ...e, [id]: val }));
  // make sure the branch containing the active page is open
  uE(() => {
    const grp = NAV.find((g) => g.items.some((p) => p && p.id === activeId));
    if (!grp) return;
    const anc = ancestorsOf(activeId, grp.items.filter(Boolean));
    if (anc.length) setExpandedState((e) => { const n = { ...e }; anc.forEach((a) => { n[a] = true; }); return n; });
  }, [activeId]);
  return (
    <aside className={"sidebar" + (open ? " open" : "")}>
      {NAV.map((grp) => {
        const GIcon = resolveIcon(grp.icon, "layers");
        const isCol = collapsed[grp.group];
        const { roots, childrenOf } = buildPageTree(grp.items);
        return (
          <div className="side-group" key={grp.group}>
            <button className={"side-group-head" + (isCol ? " collapsed" : "")} onClick={() => toggle(grp.group)}>
              {GIcon && <span className="grp-icon"><GIcon /></span>}
              {grp.group}
              <span className="chev"><I.chevron style={{ width: 14, height: 14 }} /></span>
            </button>
            {!isCol && (
              <div className="side-items">
                {roots.map((p) => (
                  <SideNode key={p.id} node={p} childrenOf={childrenOf} depth={0}
                    activeId={activeId} onNavigate={onNavigate} expanded={expanded} setExpanded={setExpanded} />
                ))}
              </div>
            )}
          </div>
        );
      })}
    </aside>
  );
}

/* ---------------- TOC with scrollspy ---------------- */
function Toc({ page }) {
  const headings = uM(() => page.blocks.filter((b) => b.type === "h2" || b.type === "h3").map((b) => ({ level: b.type === "h3" ? 3 : 2, text: b.text, id: slugify(b.text) })), [page.id]);
  const [active, setActive] = uS(null);

  uE(() => {
    if (!headings.length) return;
    const els = headings.map((h) => document.getElementById(h.id)).filter(Boolean);
    const obs = new IntersectionObserver((entries) => {
      const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
      if (visible[0]) setActive(visible[0].target.id);
    }, { rootMargin: "-80px 0px -68% 0px", threshold: 0 });
    els.forEach((el) => obs.observe(el));
    return () => obs.disconnect();
  }, [page.id, headings]);

  if (headings.length < 2) return <aside className="toc" />;
  const onClick = (e, id) => {
    e.preventDefault();
    const el = document.getElementById(id);
    if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 78, behavior: "smooth" });
    history.replaceState(null, "", `#/docs/${id ? window.location.hash.match(/docs\/([\w-]+)/)?.[1] : ""}#${id}`);
    setActive(id);
  };
  return (
    <aside className="toc">
      <div className="toc-head">On this page</div>
      <nav className="toc-list">
        {headings.map((h) => (
          <a key={h.id} href={"#" + h.id} onClick={(e) => onClick(e, h.id)}
             className={"toc-link" + (h.level === 3 ? " sub" : "") + (active === h.id ? " active" : "")}>
            {h.text}
          </a>
        ))}
      </nav>
      <div className="toc-foot">
        <a href="#/" onClick={(e) => { e.preventDefault(); window.location.hash = "#/"; }}><I.ring /> Back to home</a>
        <a href="#top" onClick={(e) => { e.preventDefault(); window.scrollTo({ top: 0, behavior: "smooth" }); }}><I.chevron style={{ transform: "rotate(180deg)" }} /> Back to top</a>
      </div>
    </aside>
  );
}

/* ---------------- docs page ---------------- */
function DocsPage({ page }) {
  const idx = PAGES.findIndex((p) => p.id === page.id);
  const prev = PAGES[idx - 1], next = PAGES[idx + 1];
  const Icon = resolveIcon(PAGE_ICON[page.id]);
  return (
    <div className="content">
      <div className="breadcrumb">
        <a href="#/">Docs</a><span className="sep">/</span>
        <span>{page.group}</span><span className="sep">/</span>
        <span style={{ color: "var(--text-muted)" }}>{page.title.replace(/ —.*$/, "")}</span>
      </div>
      <div className="page-eyebrow">{page.eyebrow || page.group}</div>
      <h1 className="page-title">{page.title}</h1>
      {page.lede && <p className="page-lede">{page.lede}</p>}
      <div className="meta-row">
        <span className="chip">{Icon ? <><Icon /> {page.group}</> : page.group}</span>
        {page.meta?.difficulty && <span className="chip">{page.meta.difficulty}</span>}
        {page.meta?.scripts > 0 && <span className="chip mono">{`${page.meta.scripts} script${page.meta.scripts > 1 ? "s" : ""}`}</span>}
        {page.meta?.time && <span className="chip">{page.meta.time}</span>}
      </div>

      <div className="prose">
        <Blocks blocks={page.blocks} />
      </div>

      <nav className="page-nav">
        {prev ? <a className="prev" href={"#/docs/" + prev.id}><span className="pn-label">← Previous</span><span className="pn-title">{prev.title.replace(/ —.*$/, "")}</span></a> : <span />}
        {next ? <a className="next" href={"#/docs/" + next.id}><span className="pn-label">Next →</span><span className="pn-title">{next.title.replace(/ —.*$/, "")}</span></a> : <span />}
      </nav>
    </div>
  );
}

/* ---------------- navbar ---------------- */
function Navbar({ route, theme, setTheme, onOpenSearch, onToggleSidebar, sidebarOpen }) {
  const hasSidebar = route.view === "docs" || route.view === "devlog-post";
  return (
    <header className="navbar">
      <div className="nav-left">
        {hasSidebar && (
          <button className="icon-btn nav-burger" onClick={onToggleSidebar} aria-label="Toggle menu">
            {sidebarOpen ? <I.x /> : <I.menu />}
          </button>
        )}
        <a className="brand" href="#/">
          <span className="brand-mark"><I.check /></span>
          <span className="brand-text">{window.cfg ? window.cfg("siteTitle") : "Konform"} <span className="sub">{window.cfg ? window.cfg("siteTitleAccent") : "Docs"}</span></span>
        </a>
      </div>
      <nav className="nav-links-inline" style={{ display: "flex", gap: 2, marginLeft: 8 }}>
        <a className={"nav-link" + (route.view === "home" ? " active" : "")} href="#/">Home</a>
        <a className={"nav-link" + (route.view === "docs" ? " active" : "")} href="#/docs/introduction">Docs</a>
      </nav>
      <div className="nav-spacer" />
      <button className="nav-search" onClick={onOpenSearch} aria-label="Search">
        <I.search style={{ width: 16, height: 16 }} />
        <span className="ns-text">Search docs…</span>
        <span className="kbd ns-kbd-main">Ctrl&nbsp;K</span>
      </button>
      <div className="nav-actions">
        <button className="icon-btn" onClick={() => setTheme(theme === "dark" ? "light" : "dark")} aria-label="Toggle theme">
          {theme === "dark" ? <I.sun /> : <I.moon />}
        </button>
        <a className="icon-btn" href="#/admin" aria-label="Developer console" title="Developer console"><I.lock /></a>
        {(window.cfg ? window.cfg("githubUrl") : "") && <a className="icon-btn" href={window.cfg("githubUrl")} target="_blank" rel="noreferrer" aria-label="GitHub" title="GitHub"><I.github /></a>}
      </div>
    </header>
  );
}

/* ---------------- report-an-issue widget ---------------- */
function ReportWidget({ pageId, pageTitle }) {
  const [enabled, setEnabled] = uS(() => window.getSetting ? window.getSetting("reportsEnabled", true) : true);
  const [open, setOpen] = uS(false);
  const [type, setType] = uS("");
  const [message, setMessage] = uS("");
  const [hp, setHp] = uS("");
  const [busy, setBusy] = uS(false);
  const [err, setErr] = uS(null);
  const [done, setDone] = uS(false);

  uE(() => {
    const on = () => setEnabled(window.getSetting ? window.getSetting("reportsEnabled", true) : true);
    window.addEventListener("konform-docs-updated", on);
    return () => window.removeEventListener("konform-docs-updated", on);
  }, []);

  uE(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open]);

  const close = () => { setOpen(false); setTimeout(() => { setType(""); setMessage(""); setHp(""); setErr(null); setDone(false); }, 200); };
  const types = (window.REPORT_TYPES || ["Incorrect info", "Outdated", "Typo", "Broken link", "Other"]);

  const submit = async () => {
    setErr(null); setBusy(true);
    const res = await window.submitReport({ pageId, pageTitle, type, message, hp });
    setBusy(false);
    if (res && res.ok) setDone(true);
    else setErr((res && res.error) || "Something went wrong.");
  };

  if (!enabled) return null;

  return (
    <>
      <button className="report-fab" onClick={() => setOpen(true)} title="Report a problem on this page">
        <I.flag /><span>Report an issue</span>
      </button>
      {open && (
        <div className="report-overlay" onMouseDown={close}>
          <div className="report-modal" onMouseDown={(e) => e.stopPropagation()}>
            <button className="report-x" onClick={close} aria-label="Close"><I.x /></button>
            {done ? (
              <div className="report-done">
                <span className="report-done-mark"><I.check /></span>
                <h3>Thank you</h3>
                <p>Your report was sent. We'll take a look.</p>
                <button className="report-submit" onClick={close}>Done</button>
              </div>
            ) : (
              <>
                <h3 className="report-title">Report a problem</h3>
                <p className="report-sub">Found something wrong on <strong>{pageTitle || "this page"}</strong>? Let us know.</p>
                <div className="report-field">
                  <label>What's the issue?</label>
                  <div className="report-types">
                    {types.map((t) => (
                      <button key={t} type="button" className={"report-type" + (type === t ? " active" : "")} onClick={() => setType(t)}>{t}</button>
                    ))}
                  </div>
                </div>
                <div className="report-field">
                  <label>Details</label>
                  <textarea className="report-ta" rows={4} value={message} maxLength={1500}
                    placeholder="Describe what's wrong — a wrong value, a broken link, outdated info…"
                    onChange={(e) => setMessage(e.target.value)} />
                </div>
                {/* honeypot — hidden from humans, catches naive bots */}
                <input className="report-hp" tabIndex={-1} autoComplete="off" value={hp} onChange={(e) => setHp(e.target.value)} aria-hidden="true" />
                {err && <div className="report-err">{err}</div>}
                <div className="report-foot">
                  <button className="report-cancel" onClick={close}>Cancel</button>
                  <button className="report-submit" disabled={busy || !type || message.trim().length < 5} onClick={submit}>{busy ? "Sending…" : "Send report"}</button>
                </div>
              </>
            )}
          </div>
        </div>
      )}
    </>
  );
}

/* ---------------- root ---------------- */
function App() {
  const route = useHashRoute();
  const [theme, setTheme] = useTheme();
  const [searchOpen, setSearchOpen] = uS(false);
  const [sidebarOpen, setSidebarOpen] = uS(false);
  const [, setDataVersion] = uS(0);

  // re-merge live data whenever the console saves
  uE(() => {
    const on = () => { window.rebuildLive && window.rebuildLive(); setDataVersion((v) => v + 1); };
    window.addEventListener("konform-docs-updated", on);
    window.addEventListener("storage", on);
    return () => { window.removeEventListener("konform-docs-updated", on); window.removeEventListener("storage", on); };
  }, []);

  // global ⌘K
  uE(() => {
    const on = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setSearchOpen((o) => !o); }
      else if (e.key === "/" && !/input|textarea/i.test(document.activeElement?.tagName || "")) { e.preventDefault(); setSearchOpen(true); }
    };
    window.addEventListener("keydown", on);
    return () => window.removeEventListener("keydown", on);
  }, []);

  // scroll to top / anchor on route change
  uE(() => {
    setSidebarOpen(false);
    if ((route.view === "docs" || route.view === "devlog-post") && route.anchor) {
      setTimeout(() => {
        const el = document.getElementById(route.anchor);
        if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 78, behavior: "auto" });
      }, 60);
    } else {
      window.scrollTo({ top: 0, behavior: "auto" });
    }
  }, [route.view, route.id, route.anchor]);

  const openSearch = uC(() => setSearchOpen(true), []);

  if (route.view === "admin") {
    return <Admin onExit={() => { window.location.hash = "#/"; }} theme={theme} setTheme={setTheme} />;
  }

  if (route.view === "home") {
    return (
      <div className="app-shell">
        <Navbar route={route} theme={theme} setTheme={setTheme} onOpenSearch={openSearch} sidebarOpen={false} />
        <Landing onOpenSearch={openSearch} theme={theme} />
        {searchOpen && <SearchPalette onClose={() => setSearchOpen(false)} />}
      </div>
    );
  }

  if (route.view === "devlog-feed") {
    return (
      <div className="app-shell">
        <Navbar route={route} theme={theme} setTheme={setTheme} onOpenSearch={openSearch} sidebarOpen={false} />
        <main className="devlog-feed-wrap"><DevlogFeed /></main>
        {searchOpen && <SearchPalette onClose={() => setSearchOpen(false)} />}
      </div>
    );
  }

  if (route.view === "devlog-post") {
    const post = DEVLOG_BY_ID[route.id];
    if (!post) {
      return (
        <div className="app-shell">
          <Navbar route={route} theme={theme} setTheme={setTheme} onOpenSearch={openSearch} sidebarOpen={false} />
          <main className="devlog-feed-wrap"><DevlogFeed /></main>
          {searchOpen && <SearchPalette onClose={() => setSearchOpen(false)} />}
        </div>
      );
    }
    return (
      <div className="app-shell">
        <Navbar route={route} theme={theme} setTheme={setTheme} onOpenSearch={openSearch}
                onToggleSidebar={() => setSidebarOpen((o) => !o)} sidebarOpen={sidebarOpen} />
        <div className={"scrim" + (sidebarOpen ? " show" : "")} onClick={() => setSidebarOpen(false)} />
        <div className="docs-layout">
          <DevlogSidebar activeId={post.id} open={sidebarOpen} onNavigate={() => setSidebarOpen(false)} />
          <main className="content-wrap"><DevlogPost key={post.id} post={post} /></main>
          <Toc key={post.id} page={post} />
        </div>
        {searchOpen && <SearchPalette onClose={() => setSearchOpen(false)} />}
        <ReportWidget pageId={"devlog/" + post.id} pageTitle={post.title} />
      </div>
    );
  }

  const page = PAGE_BY_ID[route.id] || PAGE_BY_ID["introduction"] || PAGES[0];
  if (!page) {
    return (
      <div className="app-shell">
        <Navbar route={route} theme={theme} setTheme={setTheme} onOpenSearch={openSearch} sidebarOpen={false} />
        <main className="content-wrap">
          <div className="content" style={{ paddingTop: 40 }}>
            <div className="page-eyebrow">Documentation</div>
            <h1 className="page-title">No pages yet</h1>
            <p className="page-lede">This site doesn't have any documentation pages yet. Open the <a href="#/admin">Developer Console</a> to create the first one.</p>
          </div>
        </main>
        {searchOpen && <SearchPalette onClose={() => setSearchOpen(false)} />}
      </div>
    );
  }
  return (
    <div className="app-shell">
      <Navbar route={route} theme={theme} setTheme={setTheme} onOpenSearch={openSearch}
              onToggleSidebar={() => setSidebarOpen((o) => !o)} sidebarOpen={sidebarOpen} />
      <div className={"scrim" + (sidebarOpen ? " show" : "")} onClick={() => setSidebarOpen(false)} />
      <div className="docs-layout">
        <Sidebar activeId={page.id} open={sidebarOpen} onNavigate={() => setSidebarOpen(false)} />
        <main className="content-wrap"><DocsPage key={page.id} page={page} /></main>
        <Toc key={page.id} page={page} />
      </div>
      {searchOpen && <SearchPalette onClose={() => setSearchOpen(false)} />}
      <ReportWidget pageId={page.id} pageTitle={page.title} />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
