/* The hidden attribute only carries display:none from the user agent's own
   stylesheet, so any author rule that sets display beats it. That is why
   .panel, .synth-draft and .zoom-controls each spell out their own [hidden]
   rule below — and why .workspace, which was missed, stayed laid out under the
   landing card for as long as it existed. The same trap caught #chat-dock in
   the whiteboard, so it is closed here once rather than per element.
   !important because the point is to win against a rule nobody has written
   yet. body.read-only #recent-sessions, the one deliberate exception in this
   file, still outranks it on specificity. */
[hidden] { display: none !important; }

:root {
  --bg: #0e1218;
  --surface: #161c25;
  --surface-2: #1d2531;
  --border: #2a3340;
  --text: #e6ecf3;
  --muted: #8a96a8;
  --accent: #69b1ff;
  --accent-hover: #8cc3ff;
  --planner: #ffd166;
  --dp: #69b1ff;
  --researcher: #a0e5a0;
  --synthesizer: #c79bff;
  --error: #ff7b7b;
  --running: #ffd166;
  --ready: #a0e5a0;
  --header-h: 52px;
}

* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }

body {
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0 1.25rem;
  height: var(--header-h);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  flex: 0 0 auto;
}
header h1 { margin: 0; font-size: 1rem; font-weight: 600; letter-spacing: 0.02em; }
.session-info { color: var(--muted); font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.78rem; margin-right: auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 50vw; }
.session-picker { color: var(--muted); font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.78rem; margin-right: auto; max-width: 50vw; background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 0.2rem 0.4rem; cursor: pointer; }
.session-picker:hover { border-color: var(--accent); }
.back-link { color: var(--muted); font-size: 0.85rem; text-decoration: none; }
.back-link:hover { color: var(--accent); }

.status { padding: 0.15rem 0.55rem; border-radius: 999px; font-size: 0.72rem; }
.status--idle    { background: var(--surface-2); color: var(--muted); }
.status--running { background: rgba(255,209,102,0.12); color: var(--running); }
.status--ready   { background: rgba(160,229,160,0.12); color: var(--ready); }
.status--failed  { background: rgba(255,123,123,0.12); color: var(--error); }

.zoom-controls { display: flex; gap: 0.25rem; }
.zoom-controls[hidden] { display: none; }
.zoom-controls button {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  width: 28px;
  height: 28px;
  font-size: 0.9rem;
  margin: 0;
  padding: 0;
  border-radius: 4px;
  cursor: pointer;
}
.zoom-controls button:hover { border-color: var(--accent); }

main { flex: 1 1 auto; display: flex; min-height: 0; }

.start-card {
  background: var(--surface);
  padding: 1.5rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  width: 720px;
  max-width: 90vw;
  margin: 4rem auto;
}
.start-card label { display: block; margin-bottom: 0.5rem; color: var(--muted); }
.start-card textarea { width: 100%; }

.recent-sessions { margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1rem; }
.recent-sessions h2 { font-size: 0.95rem; color: var(--muted); margin: 0 0 0.5rem; font-weight: 600; }
.recent-sessions ul { list-style: none; padding: 0; margin: 0; }
.recent-sessions li { margin: 0; }
.recent-sessions a {
  display: block;
  padding: 0.5rem 0.6rem;
  border-radius: 4px;
  color: inherit;
  text-decoration: none;
  border: 1px solid transparent;
}
.recent-sessions a:hover { background: var(--surface-2, rgba(255,255,255,0.04)); border-color: var(--border); }
.recent-sessions .topic { display: block; }
.recent-sessions .meta { display: block; font-size: 0.8rem; color: var(--muted); margin-top: 0.15rem; }

.workspace { flex: 1 1 auto; display: flex; min-height: 0; }
/* Without this the display above quietly wins against the hidden attribute —
   an author rule beats the user agent's [hidden] { display: none } — so
   $("#workspace").hidden = true did nothing and the empty graph was laid out
   underneath the landing card the whole time. The same rule is spelled out
   for .panel, .synth-draft and .zoom-controls; .workspace was the one missed. */
.workspace[hidden] { display: none; }
.graph-pane {
  flex: 1 1 auto;
  position: relative;
  background:
    radial-gradient(circle at 1px 1px, #1a2230 1px, transparent 0) 0 0 / 24px 24px,
    var(--bg);
  overflow: hidden;
}
#graph { width: 100%; height: 100%; cursor: grab; }
#graph.panning { cursor: grabbing; }
/* Sits along the bottom edge rather than centred. Centred on inset:0 put it
   straight through the middle of the graph, which is exactly where the first
   node is drawn — the hint covered the one thing it was pointing at. */
/* The hint floats over the canvas, so it lands on top of whatever node
   happens to be at the bottom of the view. Transparent, the two texts
   interleave and neither is readable — seen on the published example, where a
   deep tree puts the synthesis node exactly there.

   The span carries the background rather than the bar: a full-width backdrop
   would occlude a whole strip of the graph to solve a collision that is only
   ever the width of this sentence. */
.graph-hint { position: absolute; left: 0; right: 0; bottom: 10px; display: flex;
              justify-content: center; color: var(--muted); font-size: .85rem;
              pointer-events: none; }
.graph-hint span { background: var(--surface); border: 1px solid var(--border);
                   border-radius: 999px; padding: .3rem .8rem;
                   max-width: calc(100% - 2rem); text-align: center; }
.graph-hint[hidden] { display: none; }

/* Two buttons that belong together, side by side rather than stacked. */
.row { display: flex; gap: .5rem; }

/* The retry controls. A checkbox needs its label on the same line or the
   two read as unrelated. */
.check { display: flex; align-items: center; gap: .45rem; margin: .5rem 0;
         color: var(--muted); font-size: .85rem; cursor: pointer; }
.check input { width: auto; margin: 0; cursor: pointer; }

textarea, input[type="text"] {
  width: 100%;
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem 0.75rem;
  font: inherit;
  resize: vertical;
}
textarea:focus, input:focus { outline: none; border-color: var(--accent); }

button {
  background: var(--accent);
  color: #0a1220;
  border: 0;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  margin-top: 0.5rem;
}
button:hover { background: var(--accent-hover); }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.secondary { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); }
button.secondary:hover { border-color: var(--accent); background: var(--surface-2); }

.panel {
  flex: 0 0 420px;
  background: var(--surface);
  border-left: 1px solid var(--border);
  overflow-y: auto;
  padding: 1.25rem;
}
.panel[hidden] { display: none; }
/* Wider only while the panel is being written in rather than read from — see
   renderBriefOption. It is a flex-basis rather than a width so the graph pane
   takes what is left.

   The cap is the interesting number. The graph re-fits whenever this pane
   resizes, and the fit is width-bound, so every pixel taken here shrinks the
   whole tree proportionally: at 46vw the cards became too small to read, which
   is a poor trade for an editor that was already big enough at 40. Two thirds
   of the window still on the graph keeps it legible while the brief is being
   rewritten beside it. */
.panel.panel-editor { flex: 0 0 min(560px, 40vw); }
/* Tall enough to hold a whole brief. The reader is rewriting a paragraph, and
   the graph is still there to the left of it, so the height comes from the
   viewport rather than from a row count that means nothing at this width. */
.brief-editor { min-height: 48vh; line-height: 1.55; }
.brief-actions { display: flex; align-items: center; gap: 0.75rem; margin-top: 0.75rem; flex-wrap: wrap; }
.brief-actions .hint { margin: 0; }

.panel-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.panel-kind { color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.7rem; }
.panel-id   { color: var(--muted); font-family: ui-monospace, monospace; font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; max-width: 14ch; }
.panel-status { margin-left: auto; }
/* Attribution sits quietly beside the status: it says who asked, which
   is context rather than state, so it must not compete with it. */
.panel-actor { color: var(--muted); font-size: 0.72rem; }
.panel-open { color: var(--accent); font-size: 0.78rem; text-decoration: none; padding: 0.15rem 0.5rem; border: 1px solid var(--border); border-radius: 4px; }
.panel-open:hover { border-color: var(--accent); background: rgba(105,177,255,0.08); }
.panel-section { margin-bottom: 1.25rem; }
/* The node's own words. Capped and scrollable rather than unbounded: the
   excerpt is already cut to a few hundred characters, but a finding full of
   short lines can still run past the actions below it, and the actions are
   what the panel is for. */
.panel-excerpt { max-height: 40vh; overflow-y: auto; font-size: 0.9rem; }
.see-more { display: inline-block; margin-top: 0.5rem; color: var(--accent); font-size: 0.82rem; text-decoration: none; }
.see-more:hover { text-decoration: underline; }
.panel-section h3 { font-size: 0.78rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; margin: 0 0 0.5rem; }
.running-banner { display: flex; align-items: center; gap: 0.6rem; color: var(--running); font-size: 0.95rem; }
.spinner { width: 14px; height: 14px; border-radius: 50%; border: 2px solid rgba(255,209,102,0.25); border-top-color: var(--running); animation: spin 0.9s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.directions-list { list-style: decimal; padding-left: 1.25rem; margin: 0; }
.directions-list li { margin-bottom: 0.4rem; }
.option-text { font-size: 1.05rem; line-height: 1.5; margin: 0; }
.hint { color: var(--muted); font-size: 0.78rem; margin: 0.5rem 0 0; }

.messages { list-style: none; padding: 0; margin: 0; }
.messages li { padding: 0.6rem 0.75rem; border-radius: 6px; margin-bottom: 0.4rem; white-space: pre-wrap; }
.messages li.user { background: rgba(105,177,255,0.08); }
.messages li.assistant { background: var(--surface-2); }
.messages .role { display: block; color: var(--muted); font-size: 0.7rem; text-transform: uppercase; margin-bottom: 0.25rem; }

.findings {
  background: var(--surface-2);
  padding: 0.85rem 1rem;
  border-radius: 6px;
  white-space: pre-wrap;
  margin: 0;
  max-height: 360px;
  overflow-y: auto;
  font-family: inherit;
}

.input-text {
  background: var(--surface-2);
  padding: 0.75rem;
  border-radius: 6px;
  white-space: pre-wrap;
  color: var(--muted);
  max-height: 200px;
  overflow-y: auto;
  margin: 0;
}

/* Graph nodes */
.gnode { cursor: grab; }
/* Set on the SVG for the duration of a move, so the cursor does not flicker
   back to grab when the pointer outruns the node it is dragging. */
#graph.moving-node, #graph.moving-node .gnode { cursor: grabbing; }
.gnode rect { stroke: var(--border); stroke-width: 1.5; fill: var(--surface); transition: stroke 0.15s, filter 0.15s; }
.gnode.kind-planner rect           { stroke: var(--planner); }
.gnode.kind-direction_planner rect { stroke: var(--dp); }
.gnode.kind-researcher rect        { stroke: var(--researcher); }
.gnode.kind-synthesizer rect       { stroke: var(--synthesizer); }
/* A broken line means not finished. How it is broken says who it is waiting
   for, which is the only distinction on this canvas that asks the reader to do
   something:

     solid            finished, for better (ready) or worse (failed)
     dashed, marching running right now
     dashed, still    queued — it will start on its own, sit back
     dotted, still    offered — nothing happens until you pick it

   The last two were one look until it was pointed out that they are opposites.
   The engine's lifecycle is pending -> running -> ready, so a pending node is
   a job that has been accepted and will run itself. An option card is not a
   node at all: it exists only in this render, the server has never heard of
   it, and it will never become anything unless somebody clicks it. Showing
   both as "not started" hid the one bit the reader can act on.

   Colour still carries what kind of thing it is, and fill still carries
   whether it is real yet, so nothing was lost by taking the dashes back. The
   same states apply to the edge leading into a node, so a branch reads as in
   progress along its whole length rather than only at the tip. */
.gnode.kind-option rect            { stroke: var(--muted); fill: transparent;
                                     stroke-dasharray: 1 5; stroke-linecap: round; }
/* Queued: dashed, because it has not finished, but still, because nothing is
   happening yet. Left solid it would have claimed to be done. */
.gnode.status-pending rect         { stroke-dasharray: 4 4; }
.gnode.kind-option.opt-brief rect  { stroke: var(--researcher); }
.gnode.kind-researcher.in-draft rect { stroke: var(--synthesizer); stroke-width: 2.5; }
.gnode.selected rect { stroke-width: 2.5; filter: drop-shadow(0 0 6px rgba(105,177,255,0.55)); }
.gnode.status-running rect { stroke-dasharray: 6 4; animation: dash 1.2s linear infinite; }
/* Failed has finished, so it is solid — and red, which is the only thing on
   this canvas that means something went wrong.

   The dasharray reset is defensive and, today, unreachable: the only other
   rule that dashes a rect without being a status is .kind-option, and an
   option's status is assigned as either "option" or "running", never
   "failed". It is kept because that rule sits above this one at equal
   specificity, so the day an option can fail is the day a failure starts
   looking like work in progress — and the failure would be silent. The
   mutation test for this file cannot kill these two declarations, which is
   the honest reason to say so here rather than to let a future reader assume
   they are load-bearing. */
.gnode.status-failed rect  { stroke: var(--error); stroke-dasharray: none; animation: none;
                             fill: rgba(255,123,123,0.10); }
@keyframes dash { to { stroke-dashoffset: -10; } }

.gcard { width: 100%; height: 100%; padding: 10px 12px; display: flex; flex-direction: column; justify-content: center; color: var(--text); font: 12px/1.4 inherit; overflow: hidden; }
/* -webkit-line-clamp must be small enough that the clamped block still fits
   inside .gcard, or the clamp never fires and .gcard's overflow:hidden slices
   the last line through the middle of the glyphs — truncation with no visible
   sign of it. Six lines x 16.9px = 101px against the 104px content box of a
   124px-tall node (NODE_H in graph.js). Change one and change the other. */
/* The clamp is a backstop now rather than the summariser. cardText already
   cuts to a sentence's worth on a word boundary; this catches the case it
   cannot, a single word longer than the card. Four lines rather than six so
   that a summary and its "See more" both sit inside the card without the
   clamp biting the last line in half. */
.gcard-body { font-size: 12.5px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; }
/* Not a button: the whole card is already the click target, and giving the
   affordance its own would mean two things to aim at for one outcome. It is
   a label saying the card is an excerpt. */
.gcard-more { font-size: 11px; color: var(--accent); margin-top: 4px; flex: 0 0 auto; }
/* The two marks on an untaken option. A line style can say "not finished" and
   a second can say "not started", but neither can say "and you are the one it
   is waiting for" — a reader has to already know the vocabulary to decode
   that, and this one is worth knowing without being taught. */
.gcard { position: relative; }
/* Static, and not yellow. The first version of this pulsed in var(--planner),
   which is the same hex as var(--running) — so a badge meaning "this one is
   waiting for you" was saying "this one is working" twice over, once in the
   colour and once in the motion. Motion belongs to running and nothing else on
   this canvas, and the badge cannot borrow it.

   Accent blue instead, which everywhere else in this UI marks the thing you
   can act on: links, buttons, "See more". A reader who has learned that much
   has already learned this. */
.gcard-attention {
  position: absolute; top: 6px; left: 8px;
  color: var(--accent); font-size: 9px; line-height: 1;
}
.gcard-dismiss {
  position: absolute; top: 2px; right: 4px;
  background: none; border: none; padding: 0 4px; margin: 0;
  color: var(--muted); font-size: 15px; line-height: 1.2; cursor: pointer;
  /* Invisible until the card is under the cursor: it is a tidy-up, and one
     that is always on screen invites being pressed by accident on a canvas
     where the whole card is a click target. */
  opacity: 0; transition: opacity 0.12s, color 0.12s;
}
.gnode:hover .gcard-dismiss { opacity: 1; }
.gcard-dismiss:hover { color: var(--error); }
.gcard-dismiss:focus-visible { opacity: 1; outline: 1px solid var(--accent); }
.gnode.kind-option .gcard-body { color: var(--text); font-style: italic; }

.gedge { stroke: #3a4555; stroke-width: 1.5; fill: none; }
/* These two say what kind of link it is, and no longer borrow the dashes to
   do it: an option edge is fainter, a synthesizer's source edge is the
   synthesizer's colour. Both were previously dashed, which made a settled
   graph look busy. */
.gedge.gedge-option { opacity: 0.7; }
.gedge.gedge-source { stroke: var(--synthesizer); opacity: 0.8; }
/* State, taken from the node the edge arrives at, so a branch reads as
   in-progress along its whole length. Same four as the nodes: dotted is
   waiting for the reader, dashed is waiting for the machine, marching is
   running, and unbroken is finished. */
.gedge.gedge-offered { stroke-dasharray: 1 5; stroke-linecap: round; }
.gedge.gedge-queued  { stroke-dasharray: 4 4; }
.gedge.gedge-running { stroke-dasharray: 6 4; animation: dash 1.2s linear infinite; }
.gedge.gedge-failed  { stroke: var(--error); stroke-dasharray: none; animation: none; }

/* Synthesizer draft bar (anchored bottom-left of the graph pane) ---------- */
.synth-draft {
  position: absolute;
  left: 1rem;
  bottom: 1rem;
  width: 360px;
  max-width: calc(100% - 2rem);
  background: var(--surface);
  border: 1px solid var(--synthesizer);
  border-radius: 8px;
  padding: 0.85rem 1rem;
  box-shadow: 0 6px 20px rgba(0,0,0,0.35);
  z-index: 10;
}
.synth-draft[hidden] { display: none; }
.synth-draft-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.6rem; flex-wrap: wrap; }
.synth-draft-head .panel-kind { color: var(--synthesizer); }
.synth-draft-head button { margin: 0; padding: 0.25rem 0.6rem; font-size: 0.78rem; }
.synth-draft-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.6rem; }
.synth-chip {
  display: inline-flex; align-items: center; gap: 0.35rem;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 0.15rem 0.45rem 0.15rem 0.6rem;
  font-family: ui-monospace, monospace; font-size: 0.74rem;
}
.synth-chip button {
  background: transparent; color: var(--muted); border: 0;
  margin: 0; padding: 0 0.15rem; font-weight: 700; cursor: pointer;
}
.synth-chip button:hover { color: var(--error); }
.synth-draft textarea { font-size: 0.85rem; }
.synth-draft #synth-draft-go { width: 100%; }


/* Detail page (detail.html) ---------------------------------------------- */

.detail-body { overflow: auto; }
.detail-body main { display: block; }
/* 80ch is the right measure for prose read straight through, and the wrong one
   for this page: findings are dense, full of long URLs and quoted fragments,
   and on a wide window the rule left 705px of text between two 397px gutters.
   Reading is not the only thing happening here — the reader is scanning a
   report against a brief — so the column grows with the window and only stops
   where a line genuinely becomes hard to track back. */
.detail-main {
  max-width: min(110ch, 92vw);
  margin: 0 auto;
  padding: 2rem 2rem 4rem;
}
.detail-doc { color: var(--text); }
.detail-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.5rem;
}
.detail-section { margin-bottom: 2rem; }
.detail-section h2 {
  font-size: 0.78rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin: 0 0 0.6rem;
  font-weight: 600;
}
.detail-section .section-body { color: var(--text); }

.failure-text {
  background: rgba(255,123,123,0.08);
  border: 1px solid rgba(255,123,123,0.25);
  color: var(--error);
  padding: 0.75rem 1rem;
  border-radius: 6px;
  white-space: pre-wrap;
  word-wrap: break-word;
  margin: 0;
}

/* Markdown content ------------------------------------------------------- */

.markdown { line-height: 1.6; word-wrap: break-word; overflow-wrap: anywhere; }
.markdown > :first-child { margin-top: 0; }
.markdown > :last-child  { margin-bottom: 0; }
.markdown h1, .markdown h2, .markdown h3, .markdown h4 {
  margin: 1.4em 0 0.5em;
  line-height: 1.25;
  font-weight: 600;
}
.markdown h1 { font-size: 1.4rem; }
.markdown h2 { font-size: 1.2rem; }
.markdown h3 { font-size: 1.05rem; color: var(--text); }
.markdown h4 { font-size: 0.95rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.markdown p  { margin: 0.6em 0; }
.markdown a  { color: var(--accent); text-decoration: none; border-bottom: 1px dotted var(--accent); }
.markdown a:hover { color: var(--accent-hover); border-bottom-style: solid; }

.markdown ul, .markdown ol { padding-left: 1.4rem; margin: 0.5em 0; }
.markdown li { margin: 0.25em 0; }
.markdown li > p { margin: 0.25em 0; }

.markdown code {
  background: var(--surface-2);
  padding: 0.1em 0.35em;
  border-radius: 4px;
  font-size: 0.92em;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}
.markdown pre {
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 0.85rem 1rem;
  border-radius: 6px;
  overflow-x: auto;
  font-size: 0.88rem;
  line-height: 1.45;
  margin: 0.6em 0;
}
.markdown pre code { background: transparent; padding: 0; font-size: inherit; }

.markdown blockquote {
  border-left: 3px solid var(--border);
  margin: 0.6em 0;
  padding: 0.1em 0 0.1em 0.9rem;
  color: var(--muted);
}
.markdown hr { border: 0; border-top: 1px solid var(--border); margin: 1.4em 0; }

.markdown table {
  width: 100%;
  border-collapse: collapse;
  margin: 0.8em 0;
  font-size: 0.92em;
}
.markdown th, .markdown td {
  border: 1px solid var(--border);
  padding: 0.4rem 0.6rem;
  text-align: left;
  vertical-align: top;
}
.markdown th { background: var(--surface-2); color: var(--text); font-weight: 600; }
.markdown tr:nth-child(even) td { background: rgba(255,255,255,0.015); }

/* DP conversation list (used on the detail page too) */
.detail-doc .messages { list-style: none; padding: 0; margin: 0; }
.detail-doc .messages li {
  padding: 0.7rem 0.9rem;
  border-radius: 6px;
  margin-bottom: 0.5rem;
}
.detail-doc .messages li.user      { background: rgba(105,177,255,0.08); }
.detail-doc .messages li.assistant { background: var(--surface-2); }
.detail-doc .messages .role {
  display: block;
  color: var(--muted);
  font-size: 0.7rem;
  text-transform: uppercase;
  margin-bottom: 0.4rem;
  letter-spacing: 0.04em;
}

.detail-doc .directions-list { padding-left: 1.4rem; margin: 0; }
.detail-doc .directions-list > li { margin-bottom: 0.6rem; }

.source-list { list-style: none; padding: 0; margin: 0; }
.source-list li { padding: 0.4rem 0.5rem; border-radius: 4px; margin-bottom: 0.25rem; background: var(--surface-2); font-size: 0.9rem; }
.source-list code { background: transparent; padding: 0; color: var(--accent); }

/* Presence: who is watching this session. Live connections only. */
.watchers { display: inline-flex; gap: 4px; align-items: center; }
.watchers__chip {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%;
  background: #2a3548; color: #cfe3ff; font-size: 10px; font-weight: 600;
  border: 1px solid #3a4a63;
}
.watchers__chip--self { background: #1f3a2a; color: #7fd1a3; border-color: #2f5a42; }
/* The minted name is a placeholder: a roster of guest-4f2a and
   guest-9c1d says two people are here but not who. */
.rename-watcher { background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); border-radius: 6px; font-size: 0.72rem; padding: 0.15rem 0.5rem; cursor: pointer; font-family: inherit; }
.rename-watcher:hover { border-color: var(--accent); color: var(--text); }

/* Read-only: a recorded session, shown before anybody is asked for a key.
   The controls that cannot work are hidden rather than disabled, because a
   greyed-out button still reads as "this app is broken for you" where an
   absent one reads as "this is a recording". */
body.read-only #start-form,
body.read-only .rename-watcher { display: none; }
/* The recorded session is the point of the page, so it is never hidden and
   never collapsed behind a disclosure. */
body.read-only #recent-sessions { display: block !important; }
.example-tag {
  display: inline-block; margin-right: .5rem; padding: .05rem .4rem;
  border: 1px solid #6c7bd9; border-radius: 4px;
  font-size: .68rem; letter-spacing: .06em; font-weight: 700; color: #a9b4ee;
  vertical-align: middle;
}
body.read-only .recorded-note { margin: 0 0 .6rem; color: #8b93b8; font-size: .92rem; }
/* The way out of a recording, in the header so it survives entering a
   session, and LAST in it so it reads as the page's one outward action
   rather than as another status chip beside the watchers.
   `order` rather than margin-left:auto — the auto margin only pushed the
   elements after it, which put the CTAs mid-header with the zoom controls
   shoved to the far right. */
.recorded-cta { order: 99; margin-left: auto; display: inline-flex; align-items: center; gap: .5rem; }
/* Matched to the header's other controls, so a read-only page is not taller
   than a live one — the CTAs' own padding was setting the header height. */
body.read-only .recorded-cta .cta { padding-top: .25rem; padding-bottom: .25rem; margin-top: 0; }
body.read-only .cta {
  display: inline-block; margin-top: .4rem; padding: .5rem .9rem;
  border: 1px solid #6c7bd9; border-radius: 8px; color: #a9b4ee;
  text-decoration: none; font-weight: 600;
}
body.read-only .cta:hover { background: rgba(108,123,217,.14); }
/* Continuing from the recording is the primary move, so it is filled where
   "Run your own" is outlined — the two sit side by side and a reader should
   not have to compare labels to see which one carries this tree forward. */
body.read-only .cta--primary { background: #6c7bd9; color: #0d1020; }
body.read-only .cta--primary:hover { background: #808eea; }
body.read-only .cta + .cta { margin-left: .5rem; }

/* Citations. The marker is deliberately small and quiet: it is a pointer to
   the list below, not a thing to read, and a finding can carry a dozen of
   them in a paragraph. */
.markdown sup.cite { font-size: 0.68em; line-height: 0; vertical-align: super; margin-left: 1px; }
.markdown sup.cite a { color: var(--accent); text-decoration: none; padding: 0 1px; }
.markdown sup.cite a:hover { text-decoration: underline; }
.sources-head { font-size: 0.78rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em;
                margin: 1.75rem 0 0.5rem; border-top: 1px solid var(--border); padding-top: 1rem; }
.sources { margin: 0; padding-left: 1.5rem; color: var(--muted); font-size: 0.85rem; }
.sources li { margin: 0.2rem 0; }
.sources a { color: var(--muted); text-decoration: none; overflow-wrap: anywhere; }
.sources a:hover { color: var(--accent); text-decoration: underline; }

/* --- panel disclosures and provenance ------------------------------------
   A node's proposed options live behind a summary so the panel leads with a
   count rather than four paragraphs, and the brief that produced a set of
   findings sits under them, visibly secondary. */
/* One light rule between consecutive sections, so a panel holding a question,
   its directions and an action reads as three things rather than one column
   of text. Applied to the sibling rather than as a bottom border, so the last
   section never trails a line under nothing. */
.panel-section + .panel-section {
  border-top: 1px solid var(--border);
  padding-top: 1.1rem;
}
.panel-list-items {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.panel-list-items > li {
  display: flex;
  gap: 9px;
  align-items: baseline;
}
.panel-list-items .list-n {
  flex: 0 0 auto;
  font-size: 11px;
  opacity: 0.55;
  min-width: 1.1em;
}
.panel-provenance h3 { opacity: 0.75; }
.panel-provenance .option-text { opacity: 0.85; }


/* The controls where the arrows meet, between a node and the options it
   fanned out into. Small and quiet: they are next to the thing they change,
   so they do not need to announce themselves the way a panel button does. */
.gfan-row { display: flex; gap: 6px; align-items: center; justify-content: center;
  height: 100%; font-family: 'JetBrains Mono', ui-monospace, monospace; }
.gfan-btn { cursor: pointer; width: 26px; height: 26px; line-height: 1; padding: 0;
  border-radius: 50%; border: 1px solid rgba(255,255,255,0.16);
  background: #14141c; color: #9A9AB4; font-size: 15px; }
.gfan-btn:hover { color: #E8B84B; border-color: rgba(212,175,55,0.5); background: #1b1b26; }
.gfan-btn.selected { color: #E8B84B; border-color: rgba(212,175,55,0.8); background: rgba(212,175,55,0.14); }

/* The card's own ↻, beside its ×. Same size and weight as the dismiss it
   sits next to, because they are two halves of one decision: not this one,
   and here is what to do about it. */
.gcard-replace { position: absolute; top: 4px; right: 26px; width: 18px; height: 18px;
  line-height: 1; padding: 0; border: 0; border-radius: 50%; cursor: pointer;
  background: transparent; color: #666680; font-size: 13px; }
.gcard-replace:hover { color: #E8B84B; background: rgba(212,175,55,0.12); }

/* The go control, beside the two that change the set. Marked out because it
   is the one gesture at the fan-out that multiplies: the others add or
   re-ask, this starts everything. */
.gfan-btn--go { color: #8fd6b0; border-color: rgba(120,200,160,0.30); }
.gfan-btn--go:hover { color: #a8e8c6; border-color: rgba(120,200,160,0.60); background: rgba(120,200,160,0.10); }
.option-list { margin: 0 0 10px; padding-left: 18px; color: #A0A0B8; font-size: 13px; line-height: 1.5; }
.option-list li { margin: 3px 0; }
