// The Boardroom — app screens. These COMPOSE the committed design-system components
// (window.TheBoardroomDesignSystem_4eda1b) and mirror the structure of the reference
// screens in design/ui_kits/app; they add real state, IDs, auth and persistence.
// The Room answer view is the committed design RoomScreen, rendered unchanged.

const DS = window.TheBoardroomDesignSystem_4eda1b;

// ── Segmented OTP entry — 6 single-digit boxes on the dark gate surface ───────
// Auto-advances on entry, backspace steps back, paste/one-time-code fills all six.
// Exactly 6 boxes (Supabase OTP is 6 digits). Value is the joined string.
const otpCSS = `
.otp-boxes { display:flex; gap:9px; justify-content:center; margin:4px 0 8px; }
.otp-box { flex:1 1 40px; min-width:0; max-width:54px; height:58px; text-align:center;
           font-family:var(--font-mono); font-size:24px; color:#f6efea;
           background:rgba(246,239,234,0.06); border:1px solid rgba(246,239,234,0.22);
           border-radius:10px; outline:none; caret-color:var(--claret-soft); -webkit-appearance:none;
           transition:border-color .15s var(--ease-standard), box-shadow .15s var(--ease-standard); }
.otp-box:focus { border-color:var(--claret-soft); box-shadow:0 0 0 3px rgba(138,65,65,0.38); }
.otp-box:disabled { opacity:0.5; }
`;
function CodeBoxes({ value, onChange, onComplete, disabled }) {
  const N = 6;
  const refs = React.useRef([]);
  const digits = (value || "").split("").concat(Array(N).fill("")).slice(0, N);
  const emit = (arr) => {
    const joined = arr.join("").replace(/[^0-9]/g, "").slice(0, N);
    onChange(joined);
    if (joined.length === N) onComplete && onComplete(joined);
    return joined;
  };
  const fill = (raw, start) => {
    const arr = digits.slice();
    let k = start;
    for (const ch of raw.replace(/\D/g, "")) { if (k >= N) break; arr[k] = ch; k++; }
    emit(arr);
    const next = Math.min(k, N - 1);
    if (refs.current[next]) refs.current[next].focus();
  };
  const handleChange = (i, e) => {
    const raw = e.target.value.replace(/\D/g, "");
    if (!raw) { const arr = digits.slice(); arr[i] = ""; emit(arr); return; }
    if (raw.length === 1) {
      const arr = digits.slice(); arr[i] = raw; emit(arr);
      if (i < N - 1 && refs.current[i + 1]) refs.current[i + 1].focus();
    } else { fill(raw, i); }   // browser SMS-autofill drops all six into one box
  };
  const handleKeyDown = (i, e) => {
    if (e.key === "Backspace") {
      const arr = digits.slice();
      if (digits[i]) { arr[i] = ""; emit(arr); }
      else if (i > 0) { arr[i - 1] = ""; emit(arr); if (refs.current[i - 1]) refs.current[i - 1].focus(); }
    } else if (e.key === "ArrowLeft" && i > 0) { refs.current[i - 1].focus(); }
    else if (e.key === "ArrowRight" && i < N - 1) { refs.current[i + 1].focus(); }
  };
  const handlePaste = (e) => {
    e.preventDefault();
    fill((e.clipboardData.getData("text") || ""), 0);
  };
  return (
    <div className="otp-boxes">
      <style>{otpCSS}</style>
      {Array.from({ length: N }).map((_, i) => (
        <input key={i} ref={(el) => (refs.current[i] = el)} className="otp-box"
          inputMode="numeric" pattern="[0-9]*" maxLength={1} value={digits[i]} disabled={disabled}
          autoComplete={i === 0 ? "one-time-code" : "off"} aria-label={"Digit " + (i + 1)}
          onChange={(e) => handleChange(i, e)} onKeyDown={(e) => handleKeyDown(i, e)} onPaste={handlePaste} />
      ))}
    </div>
  );
}

// ── Auth: email allowlist → OTP (or dev code) ────────────────────────────────
function AuthScreen({ store, onAuthed }) {
  const { Button, Input } = DS;
  const [email, setEmail] = React.useState("");
  const [stage, setStage] = React.useState("email");   // email | code | refused | invite | invited
  const [code, setCode] = React.useState("");
  const [err, setErr] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [hint, setHint] = React.useState("");
  const [secs, setSecs] = React.useState(0);   // resend countdown

  const goEmail = () => { setStage("email"); setErr(""); setHint(""); setCode(""); };
  const sendCode = () => {
    if (!email.trim()) { setErr("Enter your email to continue."); return; }
    setErr(""); setBusy(true);
    store.requestCode(email).then(() => {
      setStage("code"); setCode(""); setSecs(24);
      setHint(store.mode === "dev" ? "Dev mode — enter code 000000 to continue." : "");
    }).catch((e) => {
      if (e && e.message === "not-allowed") { setStage("refused"); }
      else { setErr("Couldn't start sign-in. Check the address and try again."); }
    }).finally(() => setBusy(false));
  };
  // Resend countdown — ticks while on the code stage; resend enables at 0.
  React.useEffect(() => {
    if (stage !== "code" || secs <= 0) return;
    const t = setTimeout(() => setSecs((s) => s - 1), 1000);
    return () => clearTimeout(t);
  }, [stage, secs]);
  const fmtSecs = (s) => "0:" + (s < 10 ? "0" : "") + s;
  const verify = (c) => {
    const theCode = (c == null ? code : c);
    if (!theCode || busy) return;
    setErr(""); setBusy(true);
    store.verifyCode(email, theCode).then((u) => { onAuthed(u); })
      .catch((e) => {
        setErr(e && e.message === "not-allowed" ? "This address isn't on the list." : "That code didn't match. Try again.");
      }).finally(() => setBusy(false));
  };
  const requestInvite = () => {
    setErr(""); setBusy(true);
    Promise.resolve(store.requestInvite(email)).then(() => setStage("invited"))
      .catch(() => setErr("That didn't go through. Try again.")).finally(() => setBusy(false));
  };

  const link = { background: "transparent", border: 0, color: "#b9b2a6", fontFamily: "var(--font-sans)", fontSize: 14, cursor: "pointer", padding: 0 };
  const sub = { background: "transparent", border: 0, color: "#8f877b", fontFamily: "var(--font-sans)", fontSize: 13, cursor: "pointer", padding: 0 };

  return (
    <div style={{ minHeight: "100vh", background: "var(--paper-inverse)", color: "var(--paper)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24 }}>
      <div style={{ width: 400, maxWidth: "100%", textAlign: "center" }}>
        <div style={{ fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.24em", textTransform: "uppercase", color: "var(--claret-soft)" }}>The</div>
        <div style={{ fontFamily: "var(--font-serif)", fontSize: 52, letterSpacing: "-0.02em", margin: "6px 0 0" }}>Boardroom</div>

        {stage === "email" && (
          <React.Fragment>
            {/* hairline divider between the wordmark and the subhead */}
            <div style={{ height: 1, background: "rgba(246,239,234,0.14)", width: "100%", margin: "22px 0" }} />
            <h1 style={{ fontFamily: "var(--font-serif)", fontWeight: 500, fontSize: 25, lineHeight: 1.15, letterSpacing: "-0.01em", color: "#f3ede3", margin: "0 0 8px" }}>
              Create your own board
            </h1>
            <p style={{ font: "var(--type-body-s)", color: "#b9b2a6", margin: "0 auto 24px", maxWidth: "34ch" }}>
              Choose from the industry's most respected thinkers and leaders.
            </p>
            <div style={{ marginBottom: 12, textAlign: "left" }}>
              <Input type="email" fullWidth placeholder="you@example.com" value={email}
                onChange={(e) => setEmail(e.target.value)} />
            </div>
            <Button size="lg" fullWidth disabled={busy} onClick={sendCode}>
              {busy ? "One moment…" : "Continue"}
            </Button>
            <div style={{ marginTop: 18 }}>
              <button style={link} onClick={() => setStage("invite")}>Request an invitation →</button>
            </div>
          </React.Fragment>
        )}

        {stage === "code" && (
          <React.Fragment>
            <div style={{ fontFamily: "var(--font-mono)", fontSize: 10.5, letterSpacing: "0.22em", textTransform: "uppercase", color: "var(--claret-soft)", margin: "22px 0 12px" }}>Verify it's you</div>
            <div style={{ fontFamily: "var(--font-serif)", fontSize: 26, letterSpacing: "-0.01em", margin: "0 0 6px" }}>Enter the code we emailed</div>
            <p style={{ font: "var(--type-body-s)", color: "#8f877b", margin: "0 auto 18px", maxWidth: "34ch" }}>
              Sent to {email}
            </p>
            <CodeBoxes value={code} onChange={setCode} onComplete={(v) => verify(v)} disabled={busy} />
            <div style={{ marginTop: 14 }}>
              <Button size="lg" fullWidth disabled={busy || code.length < 6} onClick={() => verify()}>
                {busy ? "Checking…" : "Enter the room"}
              </Button>
            </div>
            <div style={{ marginTop: 16 }}>
              {secs > 0
                ? <span style={{ ...sub, cursor: "default", color: "#6f675c" }}>Resend code in {fmtSecs(secs)}</span>
                : <button style={link} onClick={sendCode} disabled={busy}>Resend code</button>}
            </div>
            <div style={{ marginTop: 10 }}><button style={sub} onClick={goEmail}>Use a different address</button></div>
          </React.Fragment>
        )}

        {stage === "refused" && (
          <React.Fragment>
            <p style={{ font: "var(--type-body)", color: "#e7e2d8", margin: "0 auto 22px", maxWidth: "34ch" }}>
              This address isn't on the list yet. Membership is by invitation.
            </p>
            <Button size="lg" fullWidth onClick={() => setStage("invite")}>Request an invitation</Button>
            <div style={{ marginTop: 14 }}><button style={sub} onClick={goEmail}>Try a different address</button></div>
          </React.Fragment>
        )}

        {stage === "invite" && (
          <React.Fragment>
            <p style={{ font: "var(--type-body-s)", color: "#b9b2a6", margin: "0 auto 22px", maxWidth: "34ch" }}>Leave your email and we'll consider a seat for you.</p>
            <div style={{ marginBottom: 12, textAlign: "left" }}>
              <Input type="email" fullWidth placeholder="you@example.com" value={email}
                onChange={(e) => setEmail(e.target.value)} />
            </div>
            <Button size="lg" fullWidth disabled={busy || !email} onClick={requestInvite}>
              {busy ? "Sending…" : "Request a seat"}
            </Button>
            <div style={{ marginTop: 14 }}><button style={sub} onClick={goEmail}>Back to sign in</button></div>
          </React.Fragment>
        )}

        {stage === "invited" && (
          <p style={{ font: "var(--type-body)", color: "#e7e2d8", margin: "8px auto 0", maxWidth: "34ch" }}>
            Request received. Membership is reviewed — we'll be in touch.
          </p>
        )}

        {err ? <p style={{ font: "var(--type-body-s)", color: "#E7A99B", marginTop: 18 }}>{err}</p> : null}
        {hint && !err ? <p style={{ font: "var(--type-body-s)", color: "#8f877b", marginTop: 18 }}>{hint}</p> : null}
      </div>
    </div>
  );
}

// ── The Record: the ledger of sessions ───────────────────────────────────────
function RecordScreen({ sessions, onOpen, onNew }) {
  const { Button } = DS;
  const S = {
    top: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "18px 20px", borderBottom: "1px solid var(--line)" },
    topTitle: { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)" },
    wrap: { maxWidth: 880, margin: "0 auto", padding: "36px 20px 56px", width: "100%", boxSizing: "border-box" },
    head: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", marginBottom: 26, gap: 16 },
    h1: { font: "var(--type-h2)", fontSize: "clamp(30px,8vw,40px)", letterSpacing: "-0.015em", margin: 0 },
    row: { display: "flex", alignItems: "center", gap: 16, padding: "20px 4px", cursor: "pointer", textAlign: "left", background: "transparent", border: 0, borderTop: "1px solid var(--line)", width: "100%" },
    idx: { fontFamily: "var(--font-mono)", fontSize: 13, color: "var(--claret)", width: 26, flex: "none" },
    main: { flex: 1, minWidth: 0 },
    title: { font: "var(--type-h5)", fontSize: 19, color: "var(--ink-900)", margin: 0, fontWeight: 500 },
    meta: { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)", marginTop: 5, display: "flex", gap: 10, alignItems: "center" },
    empty: { textAlign: "center", padding: "60px 20px", color: "var(--ink-500)", font: "var(--type-body)" },
  };
  return (
    <React.Fragment>
      <div style={S.top}><div style={S.topTitle}>Past Consults</div></div>
      <div style={S.wrap}>
        <div style={S.head}>
          <h1 style={S.h1}>Past Consults</h1>
          <Button onClick={onNew}><Icon name="plus" size={16} stroke="currentColor" /> Ask the board</Button>
        </div>
        {sessions.length === 0 ? (
          <div style={S.empty}>No consults yet. Ask the board.</div>
        ) : (
          <div style={{ display: "flex", flexDirection: "column" }}>
            {sessions.map((s) => (
              <button key={s.id} style={S.row} onClick={() => onOpen(s.id)}>
                <span style={S.idx}>{s.index}</span>
                <div style={S.main}>
                  <h3 style={S.title}>{s.question}</h3>
                  <div style={S.meta}>
                    <span>{s.meta}</span>
                  </div>
                </div>
                <Icon name="arrowRight" size={18} stroke="var(--ink-300)" />
              </button>
            ))}
          </div>
        )}
      </div>
    </React.Fragment>
  );
}

// ── Speech-to-text: browser Web Speech API (free, no key). Hidden if unsupported. ──
function speechSupported() {
  return typeof window !== "undefined" && (window.SpeechRecognition || window.webkitSpeechRecognition);
}
function Mic({ onStart, onText }) {
  const [listening, setListening] = React.useState(false);
  const recRef = React.useRef(null);
  // stop recognition if the composer unmounts mid-dictation
  React.useEffect(() => () => { try { recRef.current && recRef.current.stop(); } catch (e) {} }, []);
  if (!speechSupported()) return null;   // graceful fallback — typing still works
  const stop = () => { try { recRef.current && recRef.current.stop(); } catch (e) {} setListening(false); };
  const start = () => {
    const Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
    const rec = new Rec();
    rec.lang = "en-US"; rec.interimResults = true; rec.continuous = true;
    if (onStart) onStart();               // snapshot the text present before dictation
    // Rebuild the WHOLE transcript each event and REPLACE (never append) so the
    // spoken text appears exactly once and stays editable. Iterating from 0 (not
    // resultIndex) gives the full cumulative transcript; the composer sets
    // question = base + transcript, so repeated events can't duplicate words.
    rec.onresult = (e) => {
      let t = "";
      for (let i = 0; i < e.results.length; i++) t += e.results[i][0].transcript;
      onText(t.trim());
    };
    rec.onend = () => setListening(false);
    rec.onerror = () => setListening(false);
    recRef.current = rec;
    try { rec.start(); setListening(true); } catch (e) { setListening(false); }
  };
  return (
    <button type="button" onClick={() => (listening ? stop() : start())}
      title={listening ? "Stop" : "Speak your question"} aria-label={listening ? "Stop dictation" : "Speak your question"}
      style={{
        display: "inline-flex", alignItems: "center", gap: 8, minHeight: 44, padding: "0 14px",
        borderRadius: "var(--radius-1)", cursor: "pointer", fontFamily: "var(--font-sans)", fontSize: 13,
        border: "1px solid " + (listening ? "var(--claret)" : "var(--line-strong)"),
        background: listening ? "var(--claret-wash)" : "var(--paper-raised)",
        color: listening ? "var(--claret-deep)" : "var(--ink-700)",
      }}>
      <Icon name="mic" size={16} stroke={listening ? "var(--claret)" : "currentColor"} />
      {listening ? "Listening…" : "Speak"}
    </button>
  );
}

// ── Ask composer — landing (whole board) or a single-member 1:1 consult ───────
function AskComposer({ experts, seated, target, onSubmit, onBack }) {
  const { Kicker, Textarea, Button } = DS;
  const [question, setQuestion] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const baseRef = React.useRef("");   // text present when dictation started (for replace-not-append)
  // ONE source of truth for who's asked: the seated set (defaults to all six).
  // A 1:1 consult overrides it with just the target. No silent fallback to the
  // full roster — the count and the board toggles read the same `seated`.
  const roster = (experts && experts.length) ? experts : (window.BR ? window.BR.ROSTER : []);
  const byId = {};
  roster.forEach((e) => { byId[e.id] = e; });
  const firstName = (id) => { const m = byId[id]; return m ? m.name.split(" ")[0] : "them"; };
  const memberIds = target ? [target.id] : (seated ? Array.from(seated) : []);
  // Label reflects who's actually being asked: whole board, or a single name.
  const single = !target && memberIds.length === 1;
  const who = target ? target.name.split(" ")[0] : (single ? firstName(memberIds[0]) : null);

  const S = {
    top: { display: "flex", alignItems: "center", gap: 12, padding: "16px 20px", borderBottom: "1px solid var(--line)" },
    back: { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)", display: "flex", alignItems: "center", gap: 6, background: "transparent", border: 0, cursor: "pointer" },
    wrap: { maxWidth: 720, margin: "0 auto", padding: "48px 20px 56px", width: "100%", boxSizing: "border-box" },
    kick: { marginBottom: 14 },
    tools: { display: "flex", alignItems: "center", gap: 10, marginTop: 14, flexWrap: "wrap" },
    hint: { fontFamily: "var(--font-sans)", fontSize: 12.5, color: "var(--ink-500)" },
    foot: { display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 26, paddingTop: 22, borderTop: "1px solid var(--line)", gap: 16 },
    count: { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)" },
  };
  const canAsk = question.trim().length > 0 && memberIds.length > 0 && !busy;
  const submit = () => {
    if (!canAsk) return;
    setBusy(true);
    Promise.resolve(onSubmit(question.trim(), memberIds)).catch(() => setBusy(false));
  };
  const onMicStart = () => { baseRef.current = question; };
  const onMicText = (full) => {
    const base = baseRef.current ? baseRef.current.replace(/\s+$/, "") + " " : "";
    setQuestion(base + full);
  };
  const sendLabel = busy ? "Convening…" : ((target || single) ? "Ask " + who : "Ask the board");
  const countText = (target || single) ? "Asking " + who
    : "Asking " + memberIds.length + (memberIds.length === 1 ? " member" : " members");

  return (
    <React.Fragment>
      {onBack && (
        <div style={S.top}>
          <button style={S.back} onClick={onBack}><Icon name="chevronLeft" size={16} /> Back</button>
        </div>
      )}
      <div style={S.wrap}>
        {/* Stripped to essentials: (a 1:1 keeps a small who-line), question box, Speak, one send button. */}
        {target && <div style={S.kick}><Kicker>Consult {target.name}</Kicker></div>}
        <Textarea rows={5} autoGrow maxHeight={320} value={question} onChange={(e) => setQuestion(e.target.value)}
          placeholder={target ? "Ask " + who + " a question…" : "Ask the board a question…"} />

        <div style={S.tools}>
          <Mic onStart={onMicStart} onText={onMicText} />
          {!speechSupported()
            ? <span style={S.hint}>Voice input isn't available in this browser — typing works.</span>
            : null}
        </div>

        <div style={S.foot}>
          <span style={S.count}>{countText}</span>
          <Button onClick={submit} disabled={!canAsk}>
            {sendLabel} <Icon name="arrowRight" size={16} stroke="currentColor" />
          </Button>
        </div>
      </div>
    </React.Fragment>
  );
}

// ── Request a board member — one field (name), emails Mike via the notifier ───
function RequestMember({ onRequest }) {
  const { Kicker, Input, Button } = DS;
  const [name, setName] = React.useState("");
  const [sent, setSent] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(false);
  const submit = () => {
    if (!name.trim() || busy) return;
    setErr(false); setBusy(true);
    Promise.resolve(onRequest(name.trim()))
      .then(() => { setSent(true); setName(""); })
      .catch(() => setErr(true))
      .finally(() => setBusy(false));
  };
  const S = {
    wrap: { marginTop: 40, paddingTop: 26, borderTop: "1px solid var(--line)" },
    lede: { font: "var(--type-body-s)", color: "var(--ink-500)", margin: "12px 0 16px", maxWidth: "52ch" },
    row: { display: "flex", gap: 10, alignItems: "flex-end", flexWrap: "wrap" },
    field: { flex: 1, minWidth: 220 },
  };
  if (sent) {
    return (
      <div style={S.wrap}>
        <Kicker rule={true} tone="muted">Request a board member</Kicker>
        <p style={{ font: "var(--type-body)", color: "var(--ink-700)", margin: "14px 0 0", maxWidth: "46ch" }}>
          Request received. New members take about seven days to seat — we'll be in touch.
        </p>
      </div>
    );
  }
  return (
    <div style={S.wrap}>
      <Kicker rule={true} tone="muted">Request a board member</Kicker>
      <p style={S.lede}>Someone you'd want at the table? Give us the name.</p>
      <div style={S.row}>
        <div style={S.field}>
          <Input type="text" fullWidth placeholder="Their name" value={name}
            onChange={(e) => setName(e.target.value)} />
        </div>
        <Button onClick={submit} disabled={!name.trim() || busy}>{busy ? "Sending…" : "Request"}</Button>
      </div>
      {err && <p style={{ font: "var(--type-body-s)", color: "var(--critical)", marginTop: 12 }}>That didn't go through. Try again.</p>}
    </div>
  );
}

// ── Library / board-builder: the six experts, seat a standing board ──────────
function LibraryScreen({ experts, seated, onToggle, onSave, saved, onConsult, onRequestMember }) {
  const { Card, Switch, Button } = DS;
  const S = {
    top: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "18px 20px", borderBottom: "1px solid var(--line)" },
    topTitle: { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)" },
    wrap: { maxWidth: 960, margin: "0 auto", padding: "36px 20px 96px", width: "100%", boxSizing: "border-box" },
    h1: { font: "var(--type-h2)", fontSize: "clamp(30px,8vw,40px)", letterSpacing: "-0.015em", margin: "0 0 8px" },
    lede: { font: "var(--type-body)", color: "var(--ink-500)", maxWidth: "52ch", margin: "0 0 30px" },
    grid: { display: "grid", gridTemplateColumns: "1fr", gap: 16 },
    head: { marginBottom: 14 },
    name: { font: "var(--type-h5)", fontSize: 20, color: "var(--ink-900)" },
    seat: { fontFamily: "var(--font-mono)", fontSize: 11, letterSpacing: "0.14em", textTransform: "uppercase", color: "var(--claret)", marginTop: 6 },
    count: { fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--ink-500)", marginTop: 3 },
    note: { font: "var(--type-body-s)", color: "var(--ink-700)", margin: 0 },
    foot: { display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 16, flexWrap: "wrap", gap: "12px 10px" },
    bar: { position: "sticky", bottom: 0, background: "var(--paper)", borderTop: "1px solid var(--line)", padding: "14px 20px calc(14px + env(safe-area-inset-bottom))", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 14 },
    barText: { fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)" },
    gridCSS: "@media (min-width:721px){ .br-lib-grid { grid-template-columns:repeat(2,1fr) !important; } }",
  };
  return (
    <React.Fragment>
      <style>{S.gridCSS}</style>
      <div style={S.top}><div style={S.topTitle}>The board · {seated.size} seated</div></div>
      <div style={S.wrap}>
        <h1 style={S.h1}>The board</h1>
        <p style={S.lede}>Six minds, chosen for how differently they think. Seat the ones this question deserves.</p>
        <BoardSummary experts={experts} seated={seated} max={experts.length || 6} emptyText="No one seated — your board is empty." />
        <div className="br-lib-grid" style={S.grid}>
          {experts.map((b) => (
            <Card key={b.id}>
              <div style={S.head}>
                {/* Name is the single identifier — no monogram (it doubled the name). */}
                <div style={S.name}>{b.name}</div>
                <div style={S.seat}>{b.seat}</div>
                <div style={S.count}>{b.videoCount} talks in the corpus</div>
              </div>
              <p style={S.note}>{b.note}</p>
              <div style={S.foot}>
                <Switch checked={seated.has(b.id)} onChange={() => onToggle(b.id)}
                  label={seated.has(b.id) ? "Seated" : "Available"} />
                {onConsult && (
                  <Button variant="ghost" size="sm" onClick={() => onConsult(b)}>
                    Consult 1:1 <Icon name="arrowRight" size={14} stroke="currentColor" />
                  </Button>
                )}
              </div>
            </Card>
          ))}
        </div>
        {onRequestMember && <RequestMember onRequest={onRequestMember} />}
      </div>
      <div style={S.bar}>
        <span style={S.barText}>{saved ? "Board saved to the record." : "Seat your standing board."}</span>
        <Button onClick={onSave} disabled={seated.size === 0}>Save board</Button>
      </div>
    </React.Fragment>
  );
}

// ── Room states: the board is convening (answer building), or it failed ───────
function RoomShell({ children }) {
  return (
    <div style={{ maxWidth: 720, margin: "0 auto", padding: "56px 20px", width: "100%", boxSizing: "border-box", textAlign: "center" }}>
      {children}
    </div>
  );
}
const conveningCSS = `
.cv-root { flex:1; display:flex; flex-direction:column; min-height:0; width:100%;
           max-width:760px; margin:0 auto; box-sizing:border-box; padding:26px 20px 34px; }
/* Question = subordinate context, top-left, muted. NOT a headline. */
.cv-qlabel { font-family:var(--font-mono); font-size:10.5px; letter-spacing:0.18em;
             text-transform:uppercase; color:var(--claret); }
.cv-q { font-family:var(--font-serif); font-weight:400; color:var(--ink-500); line-height:1.32;
        margin:8px 0 0; max-width:52ch; text-wrap:balance;
        display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }
/* The rotating quote = the one anchor on screen. */
.cv-stage { flex:1; display:flex; align-items:center; justify-content:center; padding:30px 0; min-height:0; }
.cv-quote { max-width:60ch; text-align:center; transition:opacity 620ms var(--ease-standard); }
.cv-quote.out { opacity:0; } .cv-quote.in { opacity:1; }
.cv-rule { width:30px; height:2px; background:var(--claret); margin:0 auto 22px; opacity:0.7; }
.cv-quote-text { font-family:var(--font-serif); font-weight:400; font-size:clamp(22px,4.6vw,30px);
                 line-height:1.34; letter-spacing:-0.01em; color:var(--ink-900); margin:0; text-wrap:balance; }
.cv-quote-by { font-family:var(--font-mono); font-size:11.5px; letter-spacing:0.12em; text-transform:uppercase;
               color:var(--ink-500); margin-top:22px; }
.cv-quote-src { text-transform:none; letter-spacing:0; color:var(--ink-300); font-family:var(--font-serif); font-style:italic; }
.cv-fallback { max-width:34ch; text-align:center; font-family:var(--font-serif); font-size:clamp(20px,4vw,24px);
               line-height:1.4; color:var(--ink-700); margin:0 auto; }
/* Quiet, subordinate status. */
.cv-status { display:flex; align-items:center; justify-content:center; gap:9px;
             font-family:var(--font-sans); font-size:13px; color:var(--ink-500); }
.cv-dot { width:7px; height:7px; border-radius:50%; background:var(--claret); animation:cvpulse 1.6s ease-in-out infinite; }
@keyframes cvpulse { 0%,100%{ opacity:0.25; transform:scale(0.78);} 50%{ opacity:1; transform:scale(1);} }
@media (prefers-reduced-motion: reduce){ .cv-quote{ transition:none; } .cv-dot{ animation:none; opacity:0.7; } }
`;

// The convening state: while the board deliberates, real (verified) lines from the
// SEATED members cross-fade one at a time. The question sits small + muted as context;
// a quiet claret-pulse status sits beneath. No "in session" pill.
function RoomLoading({ question, members, experts }) {
  const reduce = typeof window !== "undefined" && window.matchMedia
    && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const quotes = (window.BR_QUOTES ? window.BR_QUOTES.forMembers(members || [], experts || (window.BR ? window.BR.ROSTER : [])) : []);
  const [i, setI] = React.useState(0);
  const [vis, setVis] = React.useState(true);

  React.useEffect(() => {
    if (quotes.length <= 1) return;   // nothing to rotate
    if (reduce) {
      const t = setInterval(() => setI((x) => (x + 1) % quotes.length), 5200);
      return () => clearInterval(t);
    }
    let live = true;
    const cycle = setInterval(() => {
      setVis(false);                  // cross-fade out
      setTimeout(() => { if (!live) return; setI((x) => (x + 1) % quotes.length); setVis(true); }, 640);
    }, 4800);
    return () => { live = false; clearInterval(cycle); };
  }, [quotes.length, reduce]);

  const q = quotes.length ? quotes[i % quotes.length] : null;
  const qLen = (question || "").length;
  const qSize = qLen > 160 ? 15 : qLen > 90 ? 16 : 17;

  return (
    <div className="cv-root">
      <style>{conveningCSS}</style>
      {question ? (
        <div>
          <div className="cv-qlabel">The question</div>
          <p className="cv-q" style={{ fontSize: qSize }}>{question}</p>
        </div>
      ) : null}

      <div className="cv-stage">
        {q ? (
          <figure className={"cv-quote " + (vis ? "in" : "out")}>
            <div className="cv-rule" />
            <blockquote className="cv-quote-text">{q.text}</blockquote>
            <figcaption className="cv-quote-by">
              — {q.name}{q.source ? <span className="cv-quote-src"> · {q.source}</span> : null}
            </figcaption>
          </figure>
        ) : (
          <p className="cv-fallback">The board is weighing your question.</p>
        )}
      </div>

      <div className="cv-status"><span className="cv-dot" /> The board is convening</div>
    </div>
  );
}
function RoomError({ onRetry, onBack }) {
  const { Button, Kicker } = DS;
  return (
    <RoomShell>
      <div style={{ display: "flex", justifyContent: "center", marginBottom: 22 }}>
        <Kicker tone="muted">Not convened</Kicker>
      </div>
      <p style={{ font: "var(--type-body)", color: "var(--ink-700)", margin: "0 auto 24px", maxWidth: "36ch" }}>
        The board couldn't be convened. Nothing was saved.
      </p>
      <div style={{ display: "flex", gap: 10, justifyContent: "center" }}>
        {onRetry ? <Button onClick={onRetry}>Try again</Button> : null}
        {onBack ? <Button variant="secondary" onClick={onBack}>Back to the record</Button> : null}
      </div>
    </RoomShell>
  );
}

// ── Live board summary — the running list of chosen members + count ──────────
// Persistent so the user always sees who they've added. Category-agnostic: it reads the
// flat seated set, so a board can span categories. Reused on onboarding + board-edit.
const boardSummaryCSS = `
.br-summary { position:sticky; top:0; z-index:6; display:flex; align-items:baseline; gap:12px; flex-wrap:wrap;
              background:rgba(246,244,239,0.94); backdrop-filter:blur(6px);
              border:1px solid var(--line); border-radius:var(--radius-2); padding:13px 16px; margin:0 0 28px; }
.br-summary-label { font-family:var(--font-mono); font-size:10.5px; letter-spacing:0.16em; text-transform:uppercase; color:var(--claret); flex:none; }
.br-summary-names { font-family:var(--font-serif); font-size:16px; color:var(--ink-900); flex:1; min-width:0; }
.br-summary-count { font-family:var(--font-mono); font-size:12px; color:var(--ink-500); flex:none; }
`;
function BoardSummary({ experts, seated, max, emptyText }) {
  const cap = max || 6;
  const chosen = experts.filter((e) => seated.has(e.id));
  const names = chosen.map((e) => e.name.split(" ")[0]);
  return (
    <div className="br-summary">
      <style>{boardSummaryCSS}</style>
      <span className="br-summary-label">Your board</span>
      <span className="br-summary-names">
        {names.length ? names.join(", ") : (emptyText || "No one seated yet — choose below.")}
      </span>
      <span className="br-summary-count">{names.length} of {cap}</span>
    </div>
  );
}

// ── First-run onboarding: choose your board ──────────────────────────────────
// Reuses the board-picker card (full name, seat, corpus count, bio — no monogram). The
// selection model is a FLAT set of ids, never scoped to a category, so a board can mix
// categories once more are populated. Mike-confirmed taxonomy: the six current experts sit
// under "Psychology & Marketing" (live); the four below show as "coming soon". A board is
// NOT category-locked — cross-category selection already works, so nothing changes here when
// the coming-soon categories are populated.
const OB_LIVE_CATEGORY = "Psychology & Marketing";
const OB_CATEGORIES = [
  { name: "Business & Finance" },
  { name: "Technology" },
  { name: "Science & Health" },
  { name: "Politics & Economics" },
];
const onboardingCSS = `
.br-ob-wrap { max-width:960px; margin:0 auto; padding:44px 20px 120px; width:100%; box-sizing:border-box; }
.br-ob-h1 { font:var(--type-h2); font-size:clamp(30px,8vw,44px); letter-spacing:-0.015em; margin:0 0 8px; }
.br-ob-lede { font:var(--type-body); color:var(--ink-500); margin:0 0 22px; }
.br-ob-cat { font-family:var(--font-mono); font-size:11px; letter-spacing:0.16em; text-transform:uppercase;
             color:var(--ink-700); margin:8px 0 14px; display:flex; align-items:center; gap:10px; }
.br-ob-cat::after { content:""; flex:1; height:1px; background:var(--line); }
.br-ob-grid { display:grid; grid-template-columns:1fr; gap:14px; }
.br-pick { text-align:left; background:var(--paper-raised); border:1px solid var(--line); border-radius:var(--radius-2);
           padding:18px 18px 20px; cursor:pointer; position:relative; transition:border-color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard); }
.br-pick:hover { border-color:var(--line-strong); }
.br-pick.sel { border-color:var(--claret); background:var(--claret-wash); }
.br-pick-badge { position:absolute; top:16px; right:16px; display:inline-flex; align-items:center; gap:5px;
                 font-family:var(--font-mono); font-size:10px; letter-spacing:0.08em; text-transform:uppercase;
                 border-radius:var(--radius-1); padding:4px 9px; }
.br-pick-badge.on { color:var(--claret); border:1px solid var(--claret-line); background:var(--paper-raised); }
.br-pick-badge.off { color:var(--ink-500); border:1px solid var(--line-strong); background:transparent; }
.br-pick-name { font:var(--type-h5); font-size:20px; color:var(--ink-900); padding-right:96px; }
.br-pick-seat { font-family:var(--font-mono); font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--claret); margin-top:6px; }
.br-pick-count { font-family:var(--font-mono); font-size:11px; color:var(--ink-500); margin-top:3px; }
.br-pick-note { font:var(--type-body-s); color:var(--ink-700); margin:12px 0 0; }
.br-ob-soon { display:flex; flex-wrap:wrap; gap:10px; margin:30px 0 4px; }
.br-ob-chip { font-family:var(--font-sans); font-size:13px; color:var(--ink-300); border:1px dashed var(--line-strong);
              border-radius:var(--radius-1); padding:8px 12px; display:inline-flex; align-items:center; gap:8px; }
.br-ob-chip-soon { font-family:var(--font-mono); font-size:9px; letter-spacing:0.12em; text-transform:uppercase; color:var(--ink-300); }
.br-ob-bar { position:fixed; left:0; right:0; bottom:0; z-index:20; background:var(--paper); border-top:1px solid var(--line);
             padding:14px 20px calc(14px + env(safe-area-inset-bottom)); }
.br-ob-bar-in { max-width:960px; margin:0 auto; display:flex; align-items:center; justify-content:space-between; gap:14px; }
.br-ob-bar-count { font-family:var(--font-sans); font-size:13px; color:var(--ink-500); }
@media (min-width:721px){ .br-ob-grid { grid-template-columns:repeat(2,1fr); } }
`;
function BoardPickerCard({ b, selected, onToggle }) {
  return (
    <button type="button" className={"br-pick" + (selected ? " sel" : "")}
      aria-pressed={selected} onClick={() => onToggle(b.id)}>
      <span className={"br-pick-badge " + (selected ? "on" : "off")}>
        {selected ? <React.Fragment><Icon name="check" size={12} stroke="currentColor" /> Chosen</React.Fragment> : "Choose"}
      </span>
      <div className="br-pick-name">{b.name}</div>
      <div className="br-pick-seat">{b.seat}</div>
      <div className="br-pick-count">{b.videoCount} talks in the corpus</div>
      <p className="br-pick-note">{b.note}</p>
    </button>
  );
}
function OnboardingScreen({ experts, seated, onToggle, onConfirm, onRequestMember, max }) {
  const { Button } = DS;
  const cap = max || 6;
  const [busy, setBusy] = React.useState(false);
  const confirm = () => {
    if (!seated.size || busy) return;
    setBusy(true);
    Promise.resolve(onConfirm()).catch(() => setBusy(false));
  };
  return (
    <div style={{ minHeight: "100vh", background: "var(--paper)", color: "var(--ink-900)" }}>
      <style>{onboardingCSS}</style>
      <div className="br-ob-wrap">
        <h1 className="br-ob-h1">Choose your board</h1>
        <p className="br-ob-lede">Choose up to {cap} members. You can change your board any time.</p>

        <BoardSummary experts={experts} seated={seated} max={cap} />

        <div className="br-ob-cat">{OB_LIVE_CATEGORY}</div>
        <div className="br-ob-grid">
          {experts.map((b) => (
            <BoardPickerCard key={b.id} b={b} selected={seated.has(b.id)} onToggle={onToggle} />
          ))}
        </div>

        <div className="br-ob-soon">
          {OB_CATEGORIES.map((c) => (
            <span key={c.name} className="br-ob-chip">{c.name} <span className="br-ob-chip-soon">Coming soon</span></span>
          ))}
        </div>

        {onRequestMember && <RequestMember onRequest={onRequestMember} />}
      </div>

      <div className="br-ob-bar">
        <div className="br-ob-bar-in">
          <span className="br-ob-bar-count">{seated.size} of {cap} chosen</span>
          <Button size="lg" onClick={confirm} disabled={!seated.size || busy}>
            {busy ? "Seating…" : "Enter the Boardroom"} <Icon name="arrowRight" size={16} stroke="currentColor" />
          </Button>
        </div>
      </div>
    </div>
  );
}

// ── Request a board member — dedicated screen (left-nav route) ────────────────
function RequestMemberScreen({ onRequest }) {
  const { Kicker } = DS;
  return (
    <div style={{ maxWidth: 720, margin: "0 auto", padding: "40px 20px 72px", width: "100%", boxSizing: "border-box" }}>
      <Kicker>The Board</Kicker>
      <h1 style={{ font: "var(--type-h2)", fontSize: "clamp(30px,8vw,40px)", letterSpacing: "-0.015em", margin: "10px 0 6px" }}>
        Request a board member
      </h1>
      <p style={{ font: "var(--type-body)", color: "var(--ink-500)", margin: "0 0 8px", maxWidth: "48ch" }}>
        Someone you'd want at the table who isn't seated yet? Tell us who — that's all we need.
      </p>
      <RequestMember onRequest={onRequest} />
    </div>
  );
}

Object.assign(window, { AuthScreen, RecordScreen, AskComposer, LibraryScreen, RoomLoading, RoomError, Mic, RequestMember, RequestMemberScreen, OnboardingScreen, BoardSummary, CodeBoxes, speechSupported });
