// The Boardroom — the consultation room. Three-layer answer architecture, mobile-first.
//   Layer 1  The board's view    — one synthesised answer
//   Layer 2  The members who spoke — cited cards (Watch link, on the record)
//   Layer 3  The rest of the board — inferred cards, generated on request (no source)

const roomCSS = `
.rm-scroll { display:block; }
.rm-wrap { max-width:720px; margin:0 auto; padding:28px 20px 40px; }
/* The question is a subordinate reference line, not the headline — the board's
   answer (below) is the largest thing on screen. Muted ink, modest scale. */
/* Adaptive question — font-size set inline by length (see qSize); stays muted + subordinate
   to the board's view. Long/spoken questions clamp to 4 lines with a "Show full question". */
.rm-q { font-family:var(--font-serif); font-weight:400; letter-spacing:-0.01em;
        color:var(--ink-500); margin:14px 0 0; text-wrap:balance; line-height:1.25; }
.rm-q.clamp { display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }
.rm-qmore { margin-top:8px; background:transparent; border:0; padding:0; cursor:pointer;
            font-family:var(--font-mono); font-size:11px; letter-spacing:0.08em; text-transform:uppercase;
            color:var(--ink-500); }
.rm-qmore:hover { color:var(--claret); }

/* On-the-record source strip — one claret play-chip per cited member, jump-to-moment. */
.rm-srclabel { font-family:var(--font-mono); font-size:10.5px; letter-spacing:0.14em; text-transform:uppercase;
               color:var(--ink-500); margin-top:18px; }
.rm-sources { display:flex; flex-wrap:wrap; gap:8px; margin-top:11px; }
.rm-srcchip { display:inline-flex; align-items:center; gap:8px; text-decoration:none;
              padding:9px 12px 9px 7px; border:1px solid var(--line-strong); border-radius:var(--radius-1);
              background:var(--paper-raised); font-family:var(--font-sans); font-size:13px; color:var(--ink-700);
              transition:border-color var(--dur-fast) var(--ease-standard); }
.rm-srcchip:hover { border-color:var(--claret); }
.rm-srcplay { width:20px; height:20px; flex:none; border:1px solid var(--claret-line); border-radius:3px;
              display:flex; align-items:center; justify-content:center; color:var(--claret); background:var(--claret-wash); }
.rm-srcstamp { font-family:var(--font-mono); font-size:11.5px; letter-spacing:0.04em; color:var(--ink-500); }
.rm-srcchip:hover .rm-srcstamp { color:var(--claret); }

/* Two distinct layouts: mobile = collapsed tap-to-open rows (default); desktop = inline cards. */
.rm-desktop { display:none; }
.rm-mobile { display:block; }
.rm-list { display:flex; flex-direction:column; margin-top:14px; }
.rm-item { border-top:1px solid var(--line); }
.rm-item:last-child { border-bottom:1px solid var(--line); }
.rm-row { display:flex; align-items:center; gap:12px; width:100%; text-align:left; background:transparent;
          border:0; cursor:pointer; padding:16px 2px; min-height:56px; box-sizing:border-box; }
.rm-row-name { font-family:var(--font-serif); font-size:17px; color:var(--ink-900); flex:1; min-width:0;
               overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rm-row-right { display:flex; align-items:center; gap:10px; flex:none; }
.rm-row-hint { display:inline-flex; align-items:center; gap:5px; font-family:var(--font-mono); font-size:10.5px;
               letter-spacing:0.04em; color:var(--claret); }
.rm-row-modelled { font-family:var(--font-mono); font-size:10px; letter-spacing:0.1em; text-transform:uppercase;
                   color:var(--ink-500); border:1px dashed var(--line-strong); border-radius:var(--radius-1); padding:3px 7px; }
.rm-chev { color:var(--ink-300); display:flex; transition:transform var(--dur-fast) var(--ease-standard); }
.rm-item.open .rm-chev { transform:rotate(180deg); }
.rm-row-body { padding:2px 2px 20px; }
.rm-row-body.cited { border-left:2px solid var(--claret); padding-left:14px; margin-left:2px; }
.rm-row-body.modelled { border-left:2px dashed var(--line-strong); padding-left:14px; margin-left:2px; }
.rm-row-body.modelled .rm-card-answer { color:var(--ink-500); font-style:italic; }

/* Layer 1 — board's view */
.rm-view { margin-top:22px; padding-top:22px; border-top:2px solid var(--ink-900); }
.rm-view-body { font-family:var(--font-serif); font-size:clamp(17px,4.4vw,20px);
                line-height:1.6; color:var(--ink-900); margin:14px 0 0; max-width:64ch; text-wrap:pretty; }
.rm-view-body p { margin:0; }
.rm-view-body p + p { margin-top:0.85em; }
.rm-view-meta { font-family:var(--font-sans); font-size:13px; color:var(--ink-500); margin-top:16px; }

.rm-section { margin-top:38px; }
.rm-cards { display:flex; flex-direction:column; gap:16px; margin-top:18px; }

/* Streaming: convening note (shown until the board's shared view arrives) + pending
   member placeholders that fill in as each response streams in. */
.rm-convening { display:flex; align-items:center; gap:10px; margin:22px 0 0;
                font-family:var(--font-sans); font-size:14px; color:var(--ink-500);
                background:var(--paper-raised); border:1px solid var(--line);
                border-radius:var(--radius-2); padding:14px 16px; }
.rm-convening-dot { flex:none; width:9px; height:9px; border-radius:50%; background:var(--claret);
                    animation:rm-pulse 1.1s ease-in-out infinite; }
@keyframes rm-pulse { 0%,100% { opacity:0.35; transform:scale(0.85); } 50% { opacity:1; transform:scale(1); } }
@media (prefers-reduced-motion: reduce) { .rm-convening-dot { animation:none; } }
.rm-pending { background:var(--paper-raised); border:1px dashed var(--line-strong); opacity:0.72; }
.rm-pending .rm-card-name { color:var(--ink-500); }
.rm-pending-wait { font-family:var(--font-sans); font-size:13px; color:var(--ink-500); font-style:italic; margin-top:6px; }
.rm-prow { display:flex; align-items:center; justify-content:space-between; gap:12px;
           padding:16px; border:1px dashed var(--line-strong); border-radius:var(--radius-2);
           opacity:0.72; }
.rm-prow-name { font-family:var(--font-serif); font-size:17px; color:var(--ink-500); }
.rm-prow-wait { font-family:var(--font-sans); font-size:12.5px; color:var(--ink-500); font-style:italic; }

/* Answer card — shared */
.rm-card { border-radius:var(--radius-2); padding:20px; box-sizing:border-box; }
.rm-card-head { display:flex; align-items:center; gap:12px; }
.rm-card-name { font-family:var(--font-serif); font-size:19px; color:var(--ink-900); line-height:1.2; flex:1; min-width:0; }
.rm-card-answer { font-family:var(--font-serif); font-size:17px; line-height:1.6; margin:14px 0 0; text-wrap:pretty; }
.rm-card-answer p { margin:0; }
.rm-card-answer p + p { margin-top:0.8em; }

/* Cited treatment — on the record */
.rm-cited { background:var(--paper-raised); border:1px solid var(--line);
            border-left:2px solid var(--claret); }
.rm-cited .rm-card-answer { color:var(--ink-700); }
.rm-watch { display:flex; align-items:center; gap:10px; margin-top:18px; padding-top:14px;
            border-top:1px solid var(--line-faint); text-decoration:none;
            font-family:var(--font-mono); font-size:11.5px; color:var(--ink-500);
            transition:color var(--dur-fast) var(--ease-standard); }
.rm-watch:hover { color:var(--claret); }
.rm-watch:hover .rm-play { border-color:var(--claret); color:var(--claret); }
.rm-play { width:26px; height:26px; flex:none; border:1px solid var(--line-strong);
           border-radius:var(--radius-1); display:flex; align-items:center; justify-content:center;
           color:var(--ink-700); transition:border-color var(--dur-fast), color var(--dur-fast); }
.rm-watch-label { color:var(--ink-700); letter-spacing:0.04em; flex:0 1 auto; min-width:0;
                  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.rm-watch-stamp { flex:none; padding-left:4px; color:var(--ink-700); letter-spacing:0.06em; }

/* Inferred treatment — modelled, not on the record. Shown inline by default; a COMPACT
   "Modelled" flag carries the at-a-glance distinction, full wording is one tap/hover away. */
.rm-inferred { background:var(--paper-sunken); border:1px dashed var(--line-strong); }
.rm-inferred .rm-card-answer { color:var(--ink-500); font-style:italic; }
/* Neutral, NOT claret — claret is reserved for on-the-record (cited keyline + active
   stance badge). A neutral dashed flag keeps the cited/modelled semantics clean; the
   sunken+dashed+italic card already carries the at-a-glance distinction. */
.rm-flag { display:inline-flex; align-items:center; gap:6px; flex:none; padding:4px 9px;
           border:1px dashed var(--line-strong); border-radius:var(--radius-1);
           background:transparent; color:var(--ink-500);
           font-family:var(--font-mono); font-size:11px; font-weight:500; letter-spacing:0.1em;
           text-transform:uppercase; cursor:pointer; white-space:nowrap;
           transition:background var(--dur-fast) var(--ease-standard); }
.rm-flag:hover { background:var(--paper-raised); color:var(--ink-700); }
.rm-flag-note { font-family:var(--font-sans); font-size:12.5px; line-height:1.5; color:var(--ink-500);
                margin:14px 0 0; padding-top:12px; border-top:1px dashed var(--line-strong); }

/* Press-further — a per-member 1:1 thread anchored to their answer card. */
.rm-pf { margin-top:16px; padding-top:14px; border-top:1px solid var(--line-faint); }
.rm-pf-btn { display:inline-flex; align-items:center; gap:7px; background:transparent; border:0; cursor:pointer;
             padding:0; font-family:var(--font-mono); font-size:11px; letter-spacing:0.06em; text-transform:uppercase;
             color:var(--claret); transition:color var(--dur-fast) var(--ease-standard); }
.rm-pf-btn:hover { color:var(--claret-deep); }
.rm-th { margin-top:16px; display:flex; flex-direction:column; gap:14px; }
.rm-th-user { align-self:flex-end; max-width:88%; background:var(--paper-sunken); border:1px solid var(--line);
              border-radius:var(--radius-2); padding:11px 14px; font-family:var(--font-sans); font-size:14px;
              line-height:1.5; color:var(--ink-700); }
.rm-th-ulabel { display:block; font-family:var(--font-mono); font-size:9.5px; letter-spacing:0.14em; text-transform:uppercase;
                color:var(--ink-300); margin-bottom:4px; }
.rm-th-reply { border-left:2px solid var(--claret); padding-left:14px; }
.rm-th-reply.modelled { border-left:2px dashed var(--line-strong); }
.rm-th-reply.modelled .rm-card-answer { color:var(--ink-500); font-style:italic; }
.rm-th-by { display:flex; align-items:center; gap:10px; font-family:var(--font-serif); font-size:16px; color:var(--ink-900); }
.rm-th-flag { display:inline-flex; align-items:center; gap:5px; font-family:var(--font-mono); font-size:9.5px; font-weight:500;
              letter-spacing:0.1em; text-transform:uppercase; color:var(--ink-500);
              border:1px dashed var(--line-strong); border-radius:var(--radius-1); padding:2px 7px; }
.rm-th-wait { font-family:var(--font-sans); font-size:13px; color:var(--ink-500); font-style:italic; }
.rm-th-err { font-family:var(--font-sans); font-size:13px; color:var(--critical); }
.rm-th-input { display:flex; gap:10px; align-items:flex-end; margin-top:2px; }
.rm-th-input > :first-child { flex:1; }

/* Layer 3 unspoken rows */

@media (min-width:721px){
  .rm-wrap { padding:44px 40px 48px; }
  .rm-card { padding:24px 26px; }
  .rm-desktop { display:block; }
  .rm-mobile { display:none; }
}
`;

// Split a plain-text blob into readable paragraphs (presentation only — never edits the
// words). Honours explicit line breaks if present; otherwise groups sentences into short
// paragraphs and starts a fresh paragraph at a convergence→divergence discourse shift so the
// synthesis structure shows. (CC2 emits unstructured single blobs today — see the amber in
// DECISIONS-PENDING about lightly-structured output.)
function splitParas(text) {
  text = String(text == null ? "" : text).trim();
  if (!text) return [];
  if (/\n/.test(text)) return text.split(/\n+/).map((s) => s.trim()).filter(Boolean);
  const sents = text.match(/[^.!?]+[.!?]+["'’)\]]*\s*/g);
  if (!sents || sents.length < 2) return [text];
  const SHIFT = /^(But|However|Yet|Still|That said|On the other hand|By contrast|In contrast|Meanwhile|Where they (?:diverge|differ|part)|The \w+ (?:diverges?|differ|disagree|part)|The (?:one )?(?:point|area|place) of|There is (?:also|broad)|The (?:divergence|disagreement)|What (?:divides|separates|unites))/i;
  const paras = [];
  let buf = [], chars = 0;
  for (let i = 0; i < sents.length; i++) {
    const s = sents[i].trim();
    if (buf.length && (SHIFT.test(s) || (buf.length >= 2 && chars >= 170))) {
      paras.push(buf.join(" ")); buf = []; chars = 0;
    }
    buf.push(s); chars += s.length;
  }
  if (buf.length) paras.push(buf.join(" "));
  return paras;
}

// Render a text blob as spaced paragraphs inside `className`.
function Prose({ text, className }) {
  return (
    <div className={className}>
      {splitParas(text).map((p, i) => <p key={i}>{p}</p>)}
    </div>
  );
}

function WatchLink({ source }) {
  const url = `https://www.youtube.com/watch?v=${source.videoId}&t=${source.start}s`;
  const label = source.label ? "Watch — " + source.label : "Watch the moment";
  return (
    <a className="rm-watch" href={url} target="_blank" rel="noopener noreferrer"
       title={"Jump to " + source.stamp + " in the video"}>
      <span className="rm-play"><Icon name="play" size={13} /></span>
      <span className="rm-watch-label">{label}</span>
      <span className="rm-watch-stamp"> · {source.stamp}</span>
    </a>
  );
}

// One member reply inside a 1:1 thread. Cited → keyline + Watch link; modelled → dashed
// keyline + a "Modelled" flag. The honesty signal lives in the UI treatment, never in the
// prose (the answer text is shown exactly as returned).
function ThreadReply({ member, resp }) {
  const first = member.name.split(" ")[0];
  const note = first + " hasn't spoken directly on this — a modelled view, not on the record.";
  return (
    <div className={"rm-th-reply " + (resp.cited ? "cited" : "modelled")}>
      <div className="rm-th-by">
        {member.name}
        {!resp.cited && <span className="rm-th-flag"><Icon name="feather" size={11} stroke="currentColor" /> Modelled</span>}
      </div>
      <Prose className="rm-card-answer" text={resp.answer} />
      {resp.cited && resp.source && <WatchLink source={resp.source} />}
      {!resp.cited && <div className="rm-flag-note" style={{ borderTop: 0, paddingTop: 0, marginTop: 10 }}>{note}</div>}
    </div>
  );
}

// The 1:1 thread: press one member further, in the context of their answer. Calls the
// answer seam's followUp (self + room context). Each reply is honestly cited or modelled.
function MemberThread({ member, question, seedAnswer, roster, room }) {
  const { Textarea, Button } = window.TheBoardroomDesignSystem_4eda1b;
  const [turns, setTurns] = React.useState([]);   // {role:'user',text} | {role:'member',resp}
  const [text, setText] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(false);
  const first = member.name.split(" ")[0];

  const send = () => {
    const msg = text.trim();
    if (!msg || busy) return;
    setErr(false); setBusy(true); setText("");
    // self-context = the member's LAST reply (or their seed answer); room-context = the
    // OTHER members' answers, so they can react. Exclude self from the room map.
    const lastReply = turns.filter((t) => t.role === "member").slice(-1)[0];
    const priorAnswer = lastReply ? lastReply.resp.answer : seedAnswer;
    const roomCtx = {};
    Object.keys(room || {}).forEach((k) => { if (k !== member.id) roomCtx[k] = room[k]; });
    setTurns((ts) => ts.concat({ role: "user", text: msg }));
    window.BR_ANSWER.followUp(member.id, { question: question, priorAnswer: priorAnswer, room: roomCtx, press: msg }, roster)
      .then((resp) => setTurns((ts) => ts.concat({ role: "member", resp: resp })))
      .catch(() => setErr(true))
      .finally(() => setBusy(false));
  };

  return (
    <div className="rm-th">
      {turns.map((t, i) => t.role === "user"
        ? <div key={i} className="rm-th-user"><span className="rm-th-ulabel">You</span>{t.text}</div>
        : <ThreadReply key={i} member={member} resp={t.resp} />)}
      {busy && <div className="rm-th-wait">{first} is considering…</div>}
      {err && <div className="rm-th-err">That didn't reach {first}. Try again.</div>}
      <div className="rm-th-input">
        <Textarea rows={1} autoGrow maxHeight={160} value={text} onChange={(e) => setText(e.target.value)}
          placeholder={"Press " + first + " further…"} />
        <Button size="sm" onClick={send} disabled={!text.trim() || busy}>{busy ? "Sending…" : "Send"}</Button>
      </div>
    </div>
  );
}

// Toggle that opens the per-member thread beneath their answer card.
function PressFurther({ member, question, seedAnswer, roster, room }) {
  const [open, setOpen] = React.useState(false);
  const first = member.name.split(" ")[0];
  return (
    <div className="rm-pf">
      <button type="button" className="rm-pf-btn" aria-expanded={open} onClick={() => setOpen((v) => !v)}>
        <Icon name="quote" size={12} stroke="currentColor" /> {open ? "Close thread" : "Press " + first + " further"}
      </button>
      {open && <MemberThread member={member} question={question} seedAnswer={seedAnswer} roster={roster} room={room} />}
    </div>
  );
}

function CitedCard({ member, resp, question, roster, room }) {
  const { Badge } = window.TheBoardroomDesignSystem_4eda1b;
  return (
    <article className="rm-card rm-cited">
      <div className="rm-card-head">
        <div className="rm-card-name">{member.name}</div>
        <Badge tone={resp.stanceTone}>{resp.stance}</Badge>
      </div>
      <Prose className="rm-card-answer" text={resp.answer} />
      <WatchLink source={resp.source} />
      {question && <PressFurther member={member} question={question} seedAnswer={resp.answer} roster={roster} room={room} />}
    </article>
  );
}

function InferredCard({ member, resp, question, roster, room }) {
  const [open, setOpen] = React.useState(false);
  const first = member.name.split(" ")[0];
  const note = first + " hasn't spoken directly on this — a modelled view, not on the record.";
  return (
    <article className="rm-card rm-inferred">
      <div className="rm-card-head">
        <div className="rm-card-name">{member.name}</div>
        {/* compact flag: at-a-glance "modelled"; hover title + tap reveals the full wording */}
        <button type="button" className="rm-flag" title={note}
          aria-expanded={open} onClick={() => setOpen((v) => !v)}>
          <Icon name="feather" size={12} stroke="currentColor" /> Modelled
        </button>
      </div>
      <Prose className="rm-card-answer" text={resp.answer} />
      {open && <div className="rm-flag-note">{note}</div>}
      {question && <PressFurther member={member} question={question} seedAnswer={resp.answer} roster={roster} room={room} />}
    </article>
  );
}

// Mobile — one collapsed row per member (cited + modelled in one list). Tap to expand the
// full answer. Row shows: full name + a cited/modelled marker + (cited) a timestamp hint.
// NO preview line of the answer text.
function MobileRow({ member, resp, open, onToggle, question, roster, room }) {
  const cited = resp.cited;
  const first = member.name.split(" ")[0];
  const note = first + " hasn't spoken directly on this — a modelled view, not on the record.";
  return (
    <div className={"rm-item" + (open ? " open" : "")}>
      <button type="button" className="rm-row" aria-expanded={open} onClick={() => onToggle(member.id)}>
        <span className="rm-row-name">{member.name}</span>
        <span className="rm-row-right">
          {/* marker only — name + marker (+ cited timestamp hint), no stance wall */}
          {cited
            ? (!open && <span className="rm-row-hint"><Icon name="play" size={10} stroke="var(--claret)" /> {resp.source.stamp}</span>)
            : <span className="rm-row-modelled" title={note}>Modelled</span>}
          <span className="rm-chev"><Icon name="chevronDown" size={18} /></span>
        </span>
      </button>
      {open && (
        <div className={"rm-row-body " + (cited ? "cited" : "modelled")}>
          <Prose className="rm-card-answer" text={resp.answer} />
          {cited && <WatchLink source={resp.source} />}
          {!cited && <div className="rm-flag-note" style={{ borderTop: 0, paddingTop: 0, marginTop: 12 }}>{note}</div>}
          {question && <PressFurther member={member} question={question} seedAnswer={resp.answer} roster={roster} room={room} />}
        </div>
      )}
    </div>
  );
}

// Placeholder shown while a member's response is still streaming in. Desktop = card,
// mobile = collapsed row — same footprint as the real thing so cards don't jump.
function PendingCard({ member }) {
  const first = member.name.split(" ")[0];
  return (
    <article className="rm-card rm-pending" aria-busy="true">
      <div className="rm-card-head"><div className="rm-card-name">{member.name}</div></div>
      <div className="rm-pending-wait">{first} is considering…</div>
    </article>
  );
}
function PendingRow({ member }) {
  const first = member.name.split(" ")[0];
  return (
    <div className="rm-prow" aria-busy="true">
      <span className="rm-prow-name">{member.name}</span>
      <span className="rm-prow-wait">{first} is considering…</span>
    </div>
  );
}

function RoomScreen({ data, onAskBoard, streaming }) {
  const { Kicker, Button, Textarea } = window.TheBoardroomDesignSystem_4eda1b;
  const R = data.responses || {};
  const [boardFollow, setBoardFollow] = React.useState("");
  const [boardBusy, setBoardBusy] = React.useState(false);
  // Room-context for per-member threads: every member's answer, keyed by slug. A thread
  // excludes the pressed member (their own answer is carried as self-context).
  const roomAnswers = {};
  data.board.forEach((b) => { if (R[b.id]) roomAnswers[b.id] = R[b.id].answer; });

  // When the server has sent a final display order (boardView.order), honour it; before
  // that (mid-stream) fall back to coverage for cited, board order for the rest.
  const orderIdx = data.order ? data.order.reduce((m, s, i) => { m[s] = i; return m; }, {}) : null;
  const byOrder = (a, b) => (orderIdx[a.id] == null ? 999 : orderIdx[a.id]) - (orderIdx[b.id] == null ? 999 : orderIdx[b.id]);
  // Whole seated board: cited (ranked by coverage, or the server order) then modelled.
  const spoke = data.board
    .filter((b) => R[b.id] && R[b.id].cited)
    .sort((a, b) => orderIdx ? byOrder(a, b) : R[b.id].coverage - R[a.id].coverage);
  const rest = data.board.filter((b) => R[b.id] && !R[b.id].cited).sort((a, b) => orderIdx ? byOrder(a, b) : 0);
  const ordered = spoke.concat(rest);
  // Members still awaited while streaming (no response yet) — shown as "considering…".
  const pending = streaming ? data.board.filter((b) => !R[b.id]) : [];
  // A 1:1 consult (one member) skips the "board's view of one" — just show their answer.
  // (Never treat a mid-stream single arrival as a consult: only when the seated board is 1.)
  const single = data.board.length === 1;
  const solo = single ? data.board[0] : null;
  const soloResp = single ? R[solo.id] : null;

  // Adaptive question — scales DOWN with length, stays muted/subordinate; long ones clamp.
  const qText = data.sessions[0].title;
  const qLen = qText.length;
  const qSize = qLen > 240 ? 18 : qLen > 150 ? 19 : qLen > 90 ? 21 : qLen > 50 ? 22 : 24;
  const longQ = qLen > 170;
  const [qExpanded, setQExpanded] = React.useState(false);

  // Mobile collapsed-row open/closed state.
  const [open, setOpen] = React.useState(() => new Set());
  const toggle = (id) => setOpen((p) => { const n = new Set(p); n.has(id) ? n.delete(id) : n.add(id); return n; });

  return (
    <React.Fragment>
      <style>{roomCSS}</style>
      <div style={{ display: "flex", alignItems: "center", padding: "16px 20px", borderBottom: "1px solid var(--line)", position: "sticky", top: 0, background: "rgba(246,244,239,0.92)", backdropFilter: "blur(6px)", zIndex: 5 }}>
        <div style={{ fontFamily: "var(--font-sans)", fontSize: 13, color: "var(--ink-500)", display: "flex", alignItems: "center", gap: 8 }}>
          <Icon name="gavel" size={15} stroke="var(--claret)" /> {single ? "Consult" : "Session"} {data.sessions[0].index}
        </div>
      </div>

      <div className="rm-scroll">
        <div className="rm-wrap">
          <Kicker index={data.sessions[0].index}>The Question</Kicker>
          <h1 className={"rm-q" + (longQ && !qExpanded ? " clamp" : "")} style={{ fontSize: qSize }}>{qText}</h1>
          {longQ && <button className="rm-qmore" onClick={() => setQExpanded((v) => !v)}>{qExpanded ? "Show less" : "Show full question"}</button>}

          {/* Layer 1 — the board's view + source strip (a synthesis of the whole board;
              skipped for a 1:1 consult, which shows one member's answer directly). The
              boardView arrives LAST when streaming, so until then show a convening note. */}
          {!single && !data.boardView && streaming && (
            <div className="rm-convening">
              <span className="rm-convening-dot" /> The board is convening — members appear below as each responds. Their shared view lands last.
            </div>
          )}
          {!single && data.boardView && (
            <section className="rm-view">
              <Kicker rule={true} tone="accent">The board's view</Kicker>
              <Prose className="rm-view-body" text={data.boardView.summary} />
              <div className="rm-view-meta">{data.boardView.spokeCount} of {data.boardView.boardSize} members spoke to this directly.</div>
              {spoke.length > 0 && (
                <React.Fragment>
                  <div className="rm-srclabel">On the record — jump to source</div>
                  <div className="rm-sources">
                    {spoke.map((b) => {
                      const s = R[b.id].source;
                      return (
                        <a key={b.id} className="rm-srcchip" title={"Watch " + b.name + " — jump to " + s.stamp}
                           href={`https://www.youtube.com/watch?v=${s.videoId}&t=${s.start}s`} target="_blank" rel="noopener noreferrer">
                          <span className="rm-srcplay"><Icon name="play" size={10} /></span>
                          {b.name.split(" ")[0]}<span className="rm-srcstamp">{s.stamp}</span>
                        </a>
                      );
                    })}
                  </div>
                </React.Fragment>
              )}
            </section>
          )}

          {/* The members */}
          <section className="rm-section">
            <Kicker rule={true} tone="muted">{single ? (soloResp ? (soloResp.cited ? "In their words" : "A modelled view") : "Consulting") : "The board"}</Kicker>

            {single ? (
              /* 1:1 consult — the one member's answer, expanded on every width */
              <div className="rm-cards" style={{ marginTop: 14 }}>
                {!soloResp
                  ? <PendingCard member={solo} />
                  : soloResp.cited
                    ? <CitedCard member={solo} resp={soloResp} question={qText} roster={data.board} room={roomAnswers} />
                    : <InferredCard member={solo} resp={soloResp} question={qText} roster={data.board} room={roomAnswers} />}
              </div>
            ) : (
              <React.Fragment>
                {/* DESKTOP: members expanded inline */}
                <div className="rm-desktop">
                  {spoke.length > 0 && (
                    <div className="rm-cards">
                      {spoke.map((b) => <CitedCard key={b.id} member={b} resp={R[b.id]} question={qText} roster={data.board} room={roomAnswers} />)}
                    </div>
                  )}
                  {rest.length > 0 && (
                    <div className="rm-cards" style={{ marginTop: 16 }}>
                      {rest.map((b) => <InferredCard key={b.id} member={b} resp={R[b.id]} question={qText} roster={data.board} room={roomAnswers} />)}
                    </div>
                  )}
                  {pending.length > 0 && (
                    <div className="rm-cards" style={{ marginTop: (spoke.length || rest.length) ? 16 : 0 }}>
                      {pending.map((b) => <PendingCard key={b.id} member={b} />)}
                    </div>
                  )}
                </div>

                {/* MOBILE: collapsed rows, tap to open (cited + modelled in one list) */}
                <div className="rm-mobile">
                  <div className="rm-list">
                    {ordered.map((b) => (
                      <MobileRow key={b.id} member={b} resp={R[b.id]} open={open.has(b.id)} onToggle={toggle}
                        question={qText} roster={data.board} room={roomAnswers} />
                    ))}
                    {pending.map((b) => <PendingRow key={b.id} member={b} />)}
                  </div>
                </div>
              </React.Fragment>
            )}
          </section>
        </div>
      </div>

      {/* Whole-board follow-up. To press ONE member, use "Press further" on their card.
          Hidden on a 1:1 consult (no board to convene — the card thread is the path). */}
      {!single && (
        <div style={{ position: "sticky", bottom: 0, background: "var(--paper)", borderTop: "1px solid var(--line)", padding: "14px 20px calc(14px + env(safe-area-inset-bottom))" }}>
          <div style={{ maxWidth: 720, margin: "0 auto", display: "flex", gap: 10, alignItems: "flex-end" }}>
            <div style={{ flex: 1 }}>
              <Textarea rows={1} autoGrow maxHeight={160} value={boardFollow} onChange={(e) => setBoardFollow(e.target.value)}
                placeholder="Ask the whole board a follow-up…" />
            </div>
            <Button disabled={!boardFollow.trim() || boardBusy} onClick={() => {
              const q = boardFollow.trim();
              if (!q || !onAskBoard) return;
              setBoardBusy(true);
              Promise.resolve(onAskBoard(q, data.board.map((b) => b.id)))
                .then(() => setBoardFollow("")).finally(() => setBoardBusy(false));
            }}>{boardBusy ? "Convening…" : "Ask the board"}</Button>
          </div>
        </div>
      )}
    </React.Fragment>
  );
}

Object.assign(window, { RoomScreen, CitedCard, InferredCard, MemberThread, PressFurther });
