:root {
  /* T8-b: the unused part of the context bar. */
  --free: rgba(147, 169, 195, 0.16);
  color-scheme: dark;
  --bg: #05070d;
  --panel: rgba(9, 16, 29, 0.86);
  --panel-solid: #0b1322;
  --panel-raised: #101b2e;
  --sidebar: rgba(5, 11, 20, 0.94);
  --text: #eef7ff;
  --muted: #93a9c3;
  --line: rgba(122, 155, 194, 0.24);
  --line-strong: rgba(102, 238, 255, 0.38);
  --accent: #49dfff;
  --accent-2: #9c7cff;
  --accent-3: #48f2a4;
  --accent-warm: #f5c95d;
  --accent-soft: rgba(73, 223, 255, 0.16);
  --success: #48f2a4;
  --danger: #ff647c;
  --bubble: rgba(15, 27, 48, 0.78);
  --bubble-strong: rgba(20, 37, 64, 0.92);
  --shadow: 0 24px 70px rgba(0, 0, 0, 0.38);
  /* T2 · report-assistant diff (K2-D8/D9, 2026-08-12). Body-text colours, so
     the bar is 4.5:1 against --panel-solid (#0b1322), not the 3:1 that chrome
     gets. Measured, in parentheses. --accent/--success/--danger are deliberately
     NOT reused: in LIGHT mode they measure 3.46/3.51/4.40, fine for a rule or a
     border and below the line for prose. Muted grey-blue rather than red for
     the old text -- it was superseded, not wrong, and red inside body copy
     reads as "an error is here". */
  --edit-old: #6d8199;      /* 4.6 — superseded text, struck through */
  --edit-new: #8fe9ff;      /* 13.5 — what the assistant wrote */
  --edit-added: #a9f5d1;    /* 14.7 — a block it added */
  --edit-removed: #ff8497;  /* 8.0 — a block it removed */
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  background:
    radial-gradient(circle at 12px 14px, rgba(73, 223, 255, 0.22) 0 1px, transparent 1.4px) 0 0 / 46px 46px,
    radial-gradient(circle at 32px 26px, rgba(156, 124, 255, 0.16) 0 1px, transparent 1.5px) 0 0 / 68px 68px,
    linear-gradient(135deg, #05070d 0%, #07111e 46%, #0b0b18 100%);
  color: var(--text);
  font-family: Inter, "Segoe UI", "Microsoft YaHei", Arial, sans-serif;
  font-size: 15px;
  letter-spacing: 0;
}

body::before {
  background:
    linear-gradient(90deg, rgba(73, 223, 255, 0.05) 1px, transparent 1px) 0 0 / 80px 80px,
    linear-gradient(180deg, rgba(73, 223, 255, 0.045) 1px, transparent 1px) 0 0 / 80px 80px;
  content: "";
  inset: 0;
  pointer-events: none;
  position: fixed;
  z-index: 0;
}

button,
input,
textarea {
  font: inherit;
}

.app-shell {
  display: grid;
  /* preview column follows --preview-width, set by dragging the seam
     (#preview-resizer); default matches the previous fixed 45vw. */
  grid-template-columns: 280px minmax(420px, 1fr) minmax(420px, var(--preview-width, 45vw));
  grid-template-rows: minmax(0, 1fr);
  height: 100vh;
  overflow: hidden;
  position: relative;
  z-index: 1;
}

.sidebar {
  background:
    linear-gradient(180deg, rgba(5, 11, 20, 0.98) 0%, rgba(7, 14, 28, 0.94) 100%),
    var(--sidebar);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 18px 16px;
  box-shadow: inset -1px 0 0 rgba(73, 223, 255, 0.06);
}

.brand {
  align-items: center;
  display: flex;
  margin-bottom: 20px;
}

.brand-name {
  font-size: 19px;
  font-weight: 700;
  background: linear-gradient(90deg, #eef7ff 0%, var(--accent) 46%, var(--accent-3) 100%);
  background-clip: text;
  color: transparent;
  -webkit-background-clip: text;
}

/* Lab intro hover card — top-left brand (DIRC·Report Studio). Pure-CSS: hover or
   keyboard-focus the brand to reveal the platform + author intro; the card is
   a child of the anchor so the homepage link stays clickable. */
.brand.lab-intro-anchor {
  position: relative;
  cursor: help;
  outline: none;
}
.lab-intro-card {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  width: 300px;
  max-width: 78vw;
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--panel-raised);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  backdrop-filter: blur(10px);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s ease;
  z-index: 60;
}
.lab-intro-card::before {
  /* invisible bridge across the 10px gap so :hover stays continuous */
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -12px;
  height: 12px;
}
.brand.lab-intro-anchor:hover .lab-intro-card,
.brand.lab-intro-anchor:focus-within .lab-intro-card {
  opacity: 1;
  visibility: visible;
  transform: none;
}
.lab-intro-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--accent);
  margin-bottom: 8px;
}
.lab-intro-desc {
  font-size: 12px;
  line-height: 1.65;
  color: var(--muted);
  margin: 0 0 12px;
}
.lab-intro-author {
  border-top: 1px solid var(--line);
  padding-top: 10px;
}
.lab-intro-author-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
}
.lab-intro-author-aff {
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}
.lab-intro-home {
  display: inline-block;
  margin-top: 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}
.lab-intro-home:hover {
  text-decoration: underline;
}

.new-button,
.api-key-button,
.composer button {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: 1px solid rgba(143, 236, 255, 0.72);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(73, 223, 255, 0.22);
  color: #021018;
  cursor: pointer;
  font-weight: 650;
  min-height: 40px;
  padding: 0 14px;
}

.new-button:hover,
.composer button:hover,
.review-action-button:hover,
.preview-confirm:hover,
.modal-button:hover {
  filter: brightness(1.08);
}

.new-button {
  margin-bottom: 22px;
  width: 100%;
}

.api-key-button {
  background: rgba(10, 20, 36, 0.72);
  border-color: var(--line-strong);
  box-shadow: none;
  color: var(--text);
  margin-bottom: 10px;
  width: 100%;
}

.api-key-button:hover {
  background: var(--bubble-strong);
}

.sidebar-footer {
  border-top: 1px solid var(--line);
  margin-top: 14px;
  padding-top: 12px;
}

.trash-button {
  background: rgba(10, 20, 36, 0.72);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-weight: 650;
  min-height: 38px;
  width: 100%;
}

.trash-button:hover {
  background: var(--bubble-strong);
  border-color: var(--line-strong);
  color: var(--text);
}

.side-label {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
  margin: 10px 0;
  text-transform: uppercase;
}

.session-list {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
  overflow-y: auto;
}

.session-item {
  align-items: stretch;
  background: rgba(10, 20, 36, 0.28);
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--text);
  display: flex;
  min-width: 0;
  position: relative;
}

.session-item.active {
  background:
    linear-gradient(135deg, rgba(73, 223, 255, 0.2), rgba(156, 124, 255, 0.18)),
    rgba(12, 28, 48, 0.84);
  border-color: rgba(73, 223, 255, 0.46);
  box-shadow: inset 0 0 0 1px rgba(73, 223, 255, 0.08), 0 12px 28px rgba(0, 0, 0, 0.22);
  color: var(--text);
}

.session-main {
  background: transparent;
  border: 0;
  color: inherit;
  cursor: pointer;
  flex: 1;
  min-width: 0;
  padding: 12px 8px 12px 12px;
  text-align: left;
}

.session-main:disabled {
  color: inherit;
  cursor: default;
  opacity: 1;
}

.session-item-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.session-item-time {
  color: #7f95b2;
  display: block;
  font-size: 12px;
  font-weight: 500;
  margin-top: 5px;
  white-space: nowrap;
}

.session-started-at {
  color: #7f95b2;
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
}

.session-item-meta {
  color: var(--muted);
  font-size: 12px;
  margin-top: 4px;
}

.session-actions {
  align-items: center;
  display: flex;
  padding-right: 4px;
  position: relative;
}

.session-menu-button {
  background: transparent;
  border: 0;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  height: 32px;
  line-height: 1;
  width: 32px;
}

.session-menu-button:hover,
.session-actions.open .session-menu-button {
  background: var(--bubble-strong);
  color: var(--text);
}

.session-menu {
  background: rgba(11, 19, 34, 0.98);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  display: none;
  min-width: 150px;
  padding: 6px;
  position: absolute;
  right: 0;
  top: 34px;
  z-index: 10;
}

.session-actions.open .session-menu {
  display: block;
}

.session-menu-action {
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  padding: 8px 10px;
  text-align: left;
  width: 100%;
}

.session-menu-action:hover {
  background: rgba(73, 223, 255, 0.12);
}

.session-menu-action.danger {
  color: var(--danger);
}

.chat-pane {
  background:
    linear-gradient(180deg, rgba(8, 15, 28, 0.86), rgba(8, 13, 23, 0.92)),
    var(--panel);
  display: flex;
  flex-direction: column;
  height: 100vh;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  position: relative;
}

.chat-pane::before,
.preview-pane::before {
  background:
    radial-gradient(circle at 16px 18px, rgba(73, 223, 255, 0.14) 0 1px, transparent 1.45px) 0 0 / 38px 38px,
    radial-gradient(circle at 44px 28px, rgba(72, 242, 164, 0.1) 0 1px, transparent 1.45px) 0 0 / 74px 74px,
    linear-gradient(90deg, rgba(73, 223, 255, 0.04) 1px, transparent 1px) 0 0 / 96px 96px,
    linear-gradient(180deg, rgba(156, 124, 255, 0.035) 1px, transparent 1px) 0 0 / 96px 96px;
  content: "";
  inset: 0;
  opacity: 0.72;
  pointer-events: none;
  position: absolute;
}

.chat-header,
.messages,
.composer,
.preview-header,
.preview-content {
  position: relative;
  z-index: 1;
}

.chat-header {
  align-items: center;
  border-bottom: 1px solid var(--line);
  display: flex;
  gap: 14px;
  justify-content: space-between;
  flex-shrink: 0;
  min-height: 64px;
  padding: 14px 28px;
  backdrop-filter: blur(18px);
}

.session-controls {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}

.chat-header-actions {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}

.platform-home-button {
  align-items: center;
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  display: inline-flex;
  font-weight: 700;
  min-height: 36px;
  padding: 0 12px;
  text-decoration: none;
}

.platform-home-button:hover {
  background: rgba(73, 223, 255, 0.1);
  border-color: var(--accent);
  color: var(--accent);
}

.session-control-button {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  font-weight: 700;
  min-height: 36px;
  padding: 0 12px;
}

.session-control-button:hover {
  background: rgba(73, 223, 255, 0.1);
  border-color: var(--accent);
  color: var(--accent);
}

.session-control-button.danger {
  border-color: rgba(255, 100, 124, 0.45);
  color: var(--danger);
}

.session-control-button:disabled {
  cursor: default;
  opacity: 0.55;
}

.session-title-row {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}

.session-title {
  font-size: 18px;
  font-weight: 700;
}

.session-status {
  color: var(--muted);
  font-size: 13px;
  margin-top: 3px;
}

.messages {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 26px 44px;
}

.message-row {
  display: flex;
  margin-bottom: 18px;
}

.message-row.user {
  justify-content: flex-end;
}

.bubble {
  border-radius: 8px;
  line-height: 1.65;
  max-width: min(760px, 86%);
  padding: 12px 14px;
  white-space: pre-wrap;
  word-break: break-word;
}

.assistant .bubble {
  background: var(--bubble);
  border: 1px solid var(--line);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.2);
}

.pending .bubble {
  color: var(--muted);
}

.running-bubble {
  align-items: center;
  display: inline-flex;
  gap: 9px;
}

.loading-spinner {
  animation: spin 0.8s linear infinite;
  border: 2px solid rgba(147, 169, 195, 0.34);
  border-top-color: var(--accent);
  border-radius: 50%;
  display: inline-block;
  height: 16px;
  width: 16px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.user .bubble {
  background: linear-gradient(135deg, rgba(73, 223, 255, 0.92), rgba(156, 124, 255, 0.9));
  border: 1px solid rgba(183, 243, 255, 0.42);
  box-shadow: 0 16px 42px rgba(73, 223, 255, 0.16);
  color: #06111f;
}

.file-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.file-chip {
  background: rgba(7, 16, 30, 0.78);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  padding: 8px 10px;
}

.file-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}

.review-action-button,
.preview-confirm {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: 1px solid rgba(143, 236, 255, 0.7);
  border-radius: 8px;
  box-shadow: 0 10px 28px rgba(73, 223, 255, 0.18);
  color: #021018;
  cursor: pointer;
  font-weight: 700;
  min-height: 36px;
  padding: 0 14px;
}

.review-action-button.secondary {
  background: rgba(12, 23, 40, 0.88);
  border-color: var(--line);
  color: var(--text);
}

.review-action-button:disabled,
.preview-confirm:disabled {
  cursor: default;
  opacity: 0.55;
}

.intent-chip {
  background: rgba(245, 201, 93, 0.12);
  border-color: rgba(245, 201, 93, 0.36);
  color: var(--accent-warm);
  font-weight: 700;
}

/* ===== 批4.1: Ask-card answer composer (multi-select chips + inline free text) ===== */
.choice-composer {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  margin-top: 12px;
}
.choice-composer .review-actions {
  margin-top: 0;
}
.choice-custom-input {
  width: 100%;
  box-sizing: border-box;
  min-height: 36px;
  max-height: 150px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--accent-soft);
  background: var(--panel-solid);
  color: var(--text);
  font: inherit;
  line-height: 1.5;
  resize: none;
  overflow-y: auto;
}
.choice-custom-input::placeholder {
  color: var(--muted);
}
.choice-custom-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.choice-actions-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.choice-action-button {
  background: transparent;
  border: 1px dashed var(--accent-soft);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-weight: 600;
  min-height: 32px;
  padding: 0 12px;
}
.choice-action-button:hover {
  color: var(--text);
  border-color: var(--accent);
}
/* T4 「不讨论」: same ghost family as its neighbours — quiet at rest so it does
   not compete with the answers — but danger-tinted on hover, because it is the
   one escape that takes something away. The ⊘ is the only always-on signal. */
.choice-action-button-exclude::before {
  content: "\2298";
  margin-right: 6px;
  opacity: 0.8;
}
.choice-action-button-exclude:hover {
  color: var(--danger);
  border-color: var(--danger);
}
.choice-action-button:disabled {
  cursor: default;
  opacity: 0.55;
}

/* ===== T5: the salon panel drawn as a panel =====
   Ten job titles in a row could not answer the one question this card asks —
   who is at the table. The roster answers it with the same avatar vocabulary
   the salon_open card uses moments later, so the faces the user seats are the
   faces that turn up. */
.bubble.has-panel-roster {
  /* Pairs with `container-type` below. A contained element contributes NO
     intrinsic width, and the bubble is shrink-to-fit inside
     `.message-row.has-agent` — measured at 34px/0px without this, the same
     collapse `.salon-card-recap` was added to fix. `.bubble`'s own
     `max-width: min(760px, 86%)` still caps it. */
  container-type: inline-size;
  width: 100%;
}
.panel-roster {
  display: grid;
  /* Five, so ten people read as two even rows. auto-fill gave 8+2 at chat-pane
     width — a ragged tail that reads as "these last two were an afterthought",
     which is the wrong thing to say about experts the user may seat. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 10px;
  width: 100%;
}
/* Queried against the BUBBLE, not the roster: `container-type` establishes a
   context for an element's descendants, so a roster holding its own query can
   never answer it (measured: the narrow rule silently never fired). */
@container (max-width: 560px) {
  .panel-roster { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@container (max-width: 330px) {
  .panel-roster { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.panel-seat {
  align-items: center;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 12px;
  color: var(--text);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  font: inherit;
  gap: 7px;
  padding: 11px 6px 9px;
  position: relative;
  text-align: center;
  transition: border-color 0.12s, background 0.12s;
}
.panel-seat img {
  border-radius: 50%;
  height: 46px;
  object-fit: cover;
  width: 46px;
}
.panel-seat .seat-name {
  font-size: 12px;
  line-height: 1.35;
}
.panel-seat .seat-disc {
  color: var(--muted);
  font-size: 10.5px;
  line-height: 1.3;
}
.panel-seat .seat-standing {
  color: var(--accent-warm);
  font-size: 10px;
  letter-spacing: 0.02em;
}
/* Out: greyed and receded. */
.panel-seat[aria-pressed="false"] { opacity: 0.72; }
.panel-seat[aria-pressed="false"] img { filter: grayscale(1); opacity: 0.42; }
.panel-seat[aria-pressed="false"] .seat-name { color: var(--muted); }
/* In: a ring and a tick, never a fill — a fill that reads well at 2-of-10 is a
   wall of accent at 8-of-10, and the user is free to seat all ten. */
.panel-seat[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--line-strong);
}
.panel-seat[aria-pressed="true"] img { border: 2px solid var(--accent); }
.panel-seat[aria-pressed="true"]::after {
  align-items: center;
  background: var(--accent);
  border-radius: 50%;
  color: #021018;
  content: "\2713";
  display: flex;
  font-size: 11px;
  font-weight: 900;
  height: 18px;
  justify-content: center;
  position: absolute;
  right: 7px;
  top: 7px;
  width: 18px;
}
.panel-seat:hover { border-color: var(--accent); }
.panel-seat:disabled { cursor: default; }
/* The cost of the choice, in the same breath as the choice. */
.panel-cost {
  border-top: 1px dashed var(--line);
  color: var(--muted);
  font-size: 12.5px;
  padding-top: 10px;
  width: 100%;
}

.choice-hint {
  color: var(--muted);
  font-size: 12px;
  margin-bottom: 2px;
}
.choice-custom-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 4px 8px;
  border: 1px solid var(--accent-soft);
  border-radius: 8px;
}
.choice-custom-row.selected {
  background: var(--accent-soft);
  border-color: var(--accent);
}
.choice-custom-row .choice-custom-input {
  width: auto;
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  background: transparent;
}
.choice-custom-row .choice-custom-input:focus {
  border: none;
  box-shadow: none;
}
.choice-custom-check {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  padding: 0;
  border-radius: 4px;
  border: 1.5px solid var(--muted);
  background: transparent;
  color: var(--accent-3);
  font-size: 14px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.choice-custom-check.checked {
  border-color: var(--accent-3);
  background: rgba(72, 242, 164, 0.16);
}

/* ===== Accounts (M5): auth overlay + sidebar account bar ===== */
.account-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 10px;
}
.account-bar:empty { display: none; }
.account-bar .account-name { color: var(--text); font-weight: 700; }
.account-bar .account-badge {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 7px;
  font-size: 11px;
}
/* T7: a private account's deadline ("有效期至 …") or its explicit "永久有效". */
.account-bar .account-validity {
  color: var(--muted);
  font-size: 11px;
  white-space: nowrap;
}
.account-bar button {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  padding: 3px 8px;
}
.account-bar button:hover { border-color: var(--accent); color: var(--accent); }
.preempt-countdown { color: var(--accent-warm); font-size: 12px; font-weight: 700; }

.auth-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at 50% 20%, rgba(20, 37, 64, 0.6), rgba(3, 6, 11, 0.96));
  backdrop-filter: blur(6px);
}
.auth-overlay.closed { display: none; }
.auth-card {
  width: min(420px, 92vw);
  background: var(--panel-solid);
  border: 1px solid var(--line-strong);
  border-radius: 16px;
  box-shadow: var(--shadow);
  padding: 26px 24px;
}
.auth-brand { color: var(--accent); font-weight: 800; letter-spacing: 0.5px; margin-bottom: 18px; }
.auth-tabs { display: flex; gap: 6px; margin-bottom: 16px; }
.auth-tab {
  flex: 1;
  background: rgba(12, 23, 40, 0.7);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  padding: 8px;
}
.auth-tab.active { border-color: var(--accent); color: var(--accent); }
.auth-panel { display: flex; flex-direction: column; gap: 10px; }
.auth-panel.closed { display: none; }
.auth-input {
  background: rgba(5, 12, 24, 0.9);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  min-height: 40px;
  padding: 0 12px;
}
.auth-input:focus { outline: none; border-color: var(--accent); }
.auth-button {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: 1px solid rgba(143, 236, 255, 0.7);
  border-radius: 8px;
  color: #021018;
  cursor: pointer;
  font-weight: 700;
  min-height: 40px;
}
.auth-divider { color: var(--muted); font-size: 12px; margin: 14px 0 8px; text-align: center; }
.public-account-list { display: flex; flex-direction: column; gap: 6px; max-height: 200px; overflow-y: auto; }
.public-account-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 7px 10px;
}
.public-account-row .pa-name { color: var(--text); font-size: 13px; }
.public-account-row .pa-actions { display: flex; gap: 6px; }
.public-account-row button {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  padding: 4px 9px;
}
.public-account-row button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.public-account-row button:disabled { opacity: 0.45; cursor: default; }
.auth-message { color: var(--danger); font-size: 13px; margin-top: 12px; min-height: 18px; }
.auth-admin-link { margin-top: 10px; text-align: center; }
.auth-admin-link a { color: var(--muted); font-size: 12px; text-decoration: none; }
.auth-admin-link a:hover { color: var(--accent); }

.progress-bubble {
  min-width: min(620px, 86%);
  white-space: normal;
}

.progress-title {
  font-size: 15px;
  font-weight: 700;
  margin-bottom: 12px;
}

.progress-title:empty {
  display: none;
}

.progress-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(122, 155, 194, 0.18);
}

.progress-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.progress-item {
  display: grid;
  gap: 12px;
  grid-template-columns: 24px minmax(0, 1fr);
  margin: 0;
  min-height: 42px;
  padding: 0 0 12px;
  position: relative;
}

.progress-item.level-1 {
  margin-left: 28px;
}

.progress-item.level-2 {
  margin-left: 56px;
}

.progress-item::after {
  border-left: 2px dashed rgba(122, 155, 194, 0.24);
  bottom: -1px;
  content: "";
  left: 11px;
  position: absolute;
  top: 25px;
}

.progress-item:last-child::after {
  display: none;
}

.progress-marker {
  align-items: center;
  border-radius: 999px;
  display: inline-flex;
  height: 24px;
  justify-content: center;
  position: relative;
  width: 24px;
  z-index: 1;
}

.progress-item.done .progress-marker {
  background: var(--success);
  box-shadow: 0 0 18px rgba(72, 242, 164, 0.36);
  color: #02150d;
  font-size: 15px;
  font-weight: 800;
}

.progress-item.pending .progress-marker {
  background: rgba(147, 169, 195, 0.28);
  box-shadow: inset 0 0 0 7px rgba(8, 15, 28, 0.92);
}

.progress-item.running .progress-marker {
  background: transparent;
}

/* 返回改拓扑 freezes the abandoned run: unfinished steps read as 已中止 */
.progress-item.aborted .progress-marker {
  background: rgba(147, 169, 195, 0.16);
  box-shadow: none;
  color: var(--muted);
  font-size: 15px;
}

.progress-item.aborted .progress-label,
.progress-item.aborted .progress-detail {
  color: var(--muted);
  opacity: 0.75;
}

.progress-item.failed .progress-marker {
  background: rgba(255, 100, 124, 0.18);
  box-shadow: 0 0 18px rgba(255, 100, 124, 0.22);
  color: var(--danger);
  font-size: 14px;
  font-weight: 900;
}

.progress-spinner {
  animation: spin 0.8s linear infinite;
  border: 2px solid rgba(147, 169, 195, 0.34);
  border-top-color: var(--accent);
  border-radius: 50%;
  height: 18px;
  width: 18px;
}

.progress-copy {
  min-width: 0;
  padding-top: 1px;
}

.progress-label {
  color: var(--muted);
  font-size: 15px;
}

.progress-item.done .progress-label {
  color: var(--text);
  font-weight: 800;
}

.progress-item.running .progress-label {
  animation: progress-scan 1.4s ease-in-out infinite alternate;
  background: linear-gradient(90deg, var(--text) 0%, var(--accent) 48%, var(--text) 100%);
  background-clip: text;
  background-size: 220% 100%;
  color: transparent;
  font-weight: 700;
  -webkit-background-clip: text;
}

.progress-item.failed .progress-label,
.progress-item.failed .progress-detail {
  color: var(--danger);
}

.progress-detail {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
  margin-top: 2px;
}

.progress-meter {
  background: rgba(147, 169, 195, 0.2);
  border-radius: 999px;
  height: 8px;
  margin-top: 8px;
  overflow: hidden;
  width: min(360px, 100%);
}

.progress-meter-bar {
  background: linear-gradient(90deg, var(--accent-3) 0%, var(--accent) 55%, var(--accent-2) 100%);
  border-radius: inherit;
  height: 100%;
  transition: width 0.25s ease;
}

.progress-count {
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  margin-top: 3px;
}

.progress-timer {
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 13px;
  font-weight: 800;
  margin-top: 6px;
  padding-top: 10px;
}

@keyframes progress-scan {
  from {
    background-position: 100% 50%;
  }

  to {
    background-position: 0% 50%;
  }
}

.composer {
  align-items: flex-end;
  border-top: 1px solid var(--line);
  display: flex;
  flex-shrink: 0;
  gap: 10px;
  padding: 16px 28px;
  backdrop-filter: blur(18px);
}

/* ⑤ #3: the composer field is a textarea that auto-grows from a single line up
   to a cap, then scrolls internally (height driven by JS autoGrowComposer).
   min-height keeps the single-line floor; box-sizing keeps min/max honest;
   font: inherit avoids the UA textarea monospace so it matches the input look. */
.composer textarea {
  background: rgba(5, 12, 24, 0.9);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-sizing: border-box;
  color: var(--text);
  flex: 1;
  font-family: inherit;
  font-size: inherit;
  line-height: 1.5;
  min-height: 42px;
  max-height: 150px;
  min-width: 0;
  outline: none;
  overflow-y: auto;
  padding: 9px 14px;
  resize: none;
}

.composer textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.composer textarea::placeholder {
  color: rgba(147, 169, 195, 0.76);
}

.preview-pane {
  background:
    linear-gradient(180deg, rgba(9, 16, 29, 0.92), rgba(7, 11, 21, 0.96)),
    var(--panel);
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  height: 100vh;
  min-height: 0;
  min-width: 0;
  overflow: hidden;
  position: relative;
}

.preview-pane.closed {
  display: none;
}

.preview-resizer {
  bottom: 0;
  cursor: col-resize;
  left: 0;
  position: absolute;
  top: 0;
  width: 8px;
  z-index: 30;
}

.preview-resizer:hover {
  background: rgba(147, 169, 195, 0.18);
}

body.preview-resizing {
  cursor: col-resize;
  user-select: none;
}

/* while dragging: keep the pointer ours (the mind-map canvas must not eat
   pointermove) and highlight the seam */
body.preview-resizing .preview-content {
  pointer-events: none;
}

body.preview-resizing .preview-resizer {
  background: rgba(147, 169, 195, 0.28);
}

.app-shell:has(.preview-pane.closed) {
  grid-template-columns: 280px minmax(420px, 1fr) 0;
}

.preview-header {
  align-items: center;
  border-bottom: 1px solid var(--line);
  display: flex;
  gap: 12px;
  justify-content: space-between;
  min-height: 64px;
  padding: 12px 20px;
  backdrop-filter: blur(18px);
}

.preview-heading {
  min-width: 0;
}

.preview-actions {
  align-items: center;
  display: flex;
  gap: 8px;
  margin-left: auto;
}

.preview-product {
  background: linear-gradient(90deg, var(--text), var(--accent));
  background-clip: text;
  color: transparent;
  font-size: 17px;
  font-weight: 800;
  -webkit-background-clip: text;
}

.preview-title {
  color: var(--muted);
  font-size: 13px;
  margin-top: 2px;
}

.icon-button {
  background: transparent;
  border: 0;
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  font-size: 30px;
  height: 42px;
  line-height: 1;
  width: 42px;
}

.icon-button:hover {
  background: rgba(73, 223, 255, 0.1);
  color: var(--accent);
}

.preview-content {
  flex: 1;
  line-height: 1.8;
  min-height: 0;
  overflow: auto;
  padding: 30px 36px;
}

.preview-editable {
  border: 1px solid transparent;
  border-radius: 8px;
  min-height: calc(100vh - 150px);
  outline: none;
  padding: 4px;
}

.preview-editable:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.preview-content h1,
.preview-content h2,
.preview-content h3,
.preview-content h4,
.preview-content h5,
.preview-content h6 {
  line-height: 1.35;
}

/* h4..h6: renderMarkdown emits these for #### and deeper headings; keep
   them heading-like and never smaller than body text. */
.preview-content h4 {
  font-size: 1.05em;
}

.preview-content h5,
.preview-content h6 {
  font-size: 1em;
}

.preview-content .reference-line {
  margin: 0 0 8px;
}

.preview-figure {
  margin: 18px 0;
}

.preview-figure img {
  background: #ffffff;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  display: block;
  height: auto;
  max-width: 100%;
  padding: 8px;
}

.preview-content table {
  border-collapse: collapse;
  margin: 14px 0 22px;
  width: 100%;
}

.preview-content th,
.preview-content td {
  border: 1px solid var(--line);
  padding: 8px 10px;
  text-align: left;
  vertical-align: top;
}

.preview-content th {
  background: rgba(73, 223, 255, 0.09);
  color: var(--text);
}

.preview-content blockquote {
  border-left: 4px solid var(--accent);
  color: var(--muted);
  margin: 14px 0;
  padding-left: 14px;
}

.modal-backdrop {
  align-items: center;
  background: rgba(3, 7, 14, 0.72);
  display: flex;
  inset: 0;
  justify-content: center;
  padding: 24px;
  position: fixed;
  z-index: 20;
}

.modal-backdrop.closed {
  display: none;
}

.modal-panel {
  background: rgba(10, 18, 32, 0.98);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  max-width: 520px;
  padding: 22px;
  width: min(520px, 100%);
  /* The backdrop is `position: fixed; inset: 0` and centres its child, so a
     panel taller than the viewport spills out of BOTH ends by half the excess
     and neither end can be scrolled to — the buttons simply become
     unclickable. That is what a third key section did to this dialog
     (2026-08-05). Contain the overflow here instead: `100%` resolves against
     the backdrop's content box, i.e. the viewport minus its 24px padding, and
     is preferred over `100vh` because mobile browsers count the URL bar in the
     latter. `contain` stops the page behind from scrolling once the panel's
     own scroll hits its end. */
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.modal-title {
  color: var(--text);
  font-size: 18px;
  font-weight: 800;
  line-height: 1.35;
}

.modal-copy {
  color: var(--muted);
  line-height: 1.65;
  margin-top: 10px;
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 20px;
}

.modal-button {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: 1px solid rgba(143, 236, 255, 0.7);
  border-radius: 8px;
  color: #021018;
  cursor: pointer;
  font-weight: 800;
  min-height: 38px;
  padding: 0 14px;
}

.modal-button.secondary {
  background: rgba(12, 23, 40, 0.88);
  border-color: var(--line);
  color: var(--text);
}

.modal-button:disabled {
  cursor: default;
  opacity: 0.55;
}

.api-key-panel {
  max-width: 640px;
  width: min(640px, 100%);
}

.api-key-section {
  border-top: 1px solid var(--line);
  margin-top: 16px;
  padding-top: 16px;
}

.api-key-section-title {
  color: var(--text);
  font-size: 14px;
  font-weight: 800;
  margin-bottom: 10px;
  text-transform: uppercase;
}

.api-key-section-note {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.5;
  margin: -4px 0 10px;
}

.api-key-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
}

.api-key-empty {
  color: var(--muted);
  font-size: 13px;
}

.api-key-row {
  align-items: center;
  background: rgba(12, 23, 40, 0.74);
  border: 1px solid var(--line);
  border-radius: 8px;
  display: flex;
  gap: 8px;
  padding: 10px;
}

.api-key-row.active {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px rgba(73, 223, 255, 0.12);
}

.api-key-meta {
  flex: 1;
  min-width: 0;
}

.api-key-label {
  color: var(--text);
  font-weight: 750;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.api-key-tail,
.api-key-message {
  color: var(--muted);
  font-size: 13px;
  margin-top: 3px;
}

.api-key-input {
  background: rgba(5, 10, 18, 0.88);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  display: block;
  margin-bottom: 8px;
  min-height: 38px;
  padding: 0 10px;
  width: 100%;
}

.api-key-input:focus {
  border-color: var(--accent);
  outline: none;
}

.api-key-small-button {
  background: rgba(10, 20, 36, 0.72);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  font-weight: 700;
  min-height: 32px;
  padding: 0 10px;
}

.api-key-small-button.danger {
  border-color: rgba(255, 100, 124, 0.6);
  color: var(--danger);
}

@media (max-width: 920px) {
  .app-shell,
  .app-shell:has(.preview-pane.closed) {
    grid-template-columns: 1fr;
  }

  .sidebar {
    display: none;
  }

  .preview-resizer {
    display: none;
  }

  .preview-pane {
    inset: 0;
    position: fixed;
    z-index: 5;
  }

  .messages {
    padding: 18px;
  }

  .chat-header {
    align-items: flex-start;
    flex-direction: column;
  }

  .chat-header-actions {
    justify-content: flex-start;
    width: 100%;
  }

  .preview-editable {
    min-height: calc(100vh - 150px);
  }
}

/* T3 stage-1 topology (拓扑结构图) editor panel */
.topology-shell {
  display: flex;
  flex-direction: column;
  gap: 10px;
  height: 100%;
  min-height: 480px;
}

.topology-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.topology-toolbar-label {
  color: var(--muted);
  font-size: 13px;
}

.topology-tag-button {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  cursor: pointer;
  font-size: 13px;
  min-height: 30px;
  padding: 0 12px;
}

.topology-tag-button:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.topology-tag-button.clear {
  color: var(--muted);
}

.topology-map {
  flex: 1 1 auto;
  min-height: 420px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #f6f7fb;
  overflow: hidden;
}

.topology-hint {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.6;
}

.topology-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 420px;
  color: var(--muted);
  font-size: 14px;
}

/* canvas pan affordance: grab on empty canvas, grabbing while dragging */
.topology-map .map-container {
  cursor: grab;
}

.topology-map.topology-panning,
.topology-map.topology-panning .map-container {
  cursor: grabbing;
}

/* The parsing-gate bubble keeps rewriting its own numbers (D20), so it wears a
   spinner while anything is still pending. Reuses the thinking-spin keyframe
   below; theme vars rather than the hardcoded cyan, since this bubble is read
   in light mode too. Applied as a modifier so no other bubble turns into a
   flex container. */
.corpus-gate-bubble { align-items: center; display: flex; gap: 10px; }
.corpus-gate-text { flex: 1 1 auto; min-width: 0; }
.corpus-gate-spinner {
  animation: thinking-spin 0.9s linear infinite;
  border: 2px solid var(--line-strong);
  border-radius: 50%;
  border-top-color: var(--accent-3);
  /* inline-block like .thinking-spinner: width/height do not apply to a plain
     inline element, so without this the ring collapses to its 4px border and
     only looks right because its parent happens to blockify it. */
  display: inline-block;
  flex: 0 0 auto;
  height: 14px;
  width: 14px;
}
@media (prefers-reduced-motion: reduce) {
  /* Slowed, not stopped: "still working" is the information it carries. */
  .corpus-gate-spinner { animation-duration: 3s; }
}

/* waiting spinner bubble (topology generating / auto-completing) */
.thinking-bubble {
  align-items: center;
  display: inline-flex;
  padding: 12px 16px;
}

.thinking-spinner {
  animation: thinking-spin 0.8s linear infinite;
  border: 2px solid rgba(73, 223, 255, 0.25);
  border-radius: 50%;
  border-top-color: rgba(73, 223, 255, 0.9);
  display: inline-block;
  height: 18px;
  width: 18px;
}

@keyframes thinking-spin {
  to {
    transform: rotate(360deg);
  }
}

/* superseded one-shot action buttons: visible history, no interaction */
.review-action-button.stale {
  cursor: not-allowed;
  opacity: 0.45;
}

/* ==================== salon: hero (B1 M1) ==================== */
/* Empty-session welcome: .chat-pane.hero-mode vertically centers a big title +
   the existing composer + shortcut pills; #messages stays in the DOM (hidden)
   so send logic is untouched. .hero-leaving fades the copy before the layout
   snaps back to the working state. */

.hero-block,
.hero-pills {
  display: none;
}

.chat-pane.hero-mode .messages {
  display: none;
}

.chat-pane.hero-mode .hero-block {
  align-items: center;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: auto;
  padding: 0 44px;
  position: relative;
  text-align: center;
  transition: opacity 0.26s ease, transform 0.26s ease;
  z-index: 1;
}

.hero-title {
  background: linear-gradient(90deg, #eef7ff 0%, var(--accent) 52%, var(--accent-3) 100%);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  font-size: 34px;
  font-weight: 700;
  letter-spacing: 0.4px;
  margin: 0;
}

.hero-subtitle {
  color: var(--muted);
  font-size: 15px;
  line-height: 1.7;
  margin: 0;
  max-width: 620px;
}

.chat-pane.hero-mode .composer {
  backdrop-filter: none;
  background: transparent;
  border-top: none;
  margin: 30px auto 0;
  padding: 0;
  transition: opacity 0.26s ease, transform 0.26s ease;
  width: min(720px, 88%);
}

.chat-pane.hero-mode .composer textarea {
  border-radius: 16px;
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.32);
  font-size: 16px;
  min-height: 58px;
  padding: 16px 20px;
}

.chat-pane.hero-mode .composer button {
  border-radius: 14px;
  min-height: 58px;
  padding: 0 22px;
}

.chat-pane.hero-mode .hero-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  /* The bottom `auto` that used to live here is what balances `margin-top:auto`
     on .hero-block to centre the whole group. It moved to .doc-upload, which is
     now the last element — leaving it here would strand the upload box against
     the bottom of the pane. */
  margin: 20px auto 0;
  padding: 0 44px;
  position: relative;
  transition: opacity 0.26s ease, transform 0.26s ease;
  z-index: 1;
}

.chat-pane.hero-mode .doc-upload {
  display: flex;
  flex-direction: column;
  height: var(--hero-doc-upload-height, auto);
  margin: 18px auto auto;
  min-height: 88px;
  position: relative;
  width: min(720px, 88%);
  z-index: 1;
}
.chat-pane.hero-mode .doc-upload[hidden] { display: none; }
.chat-pane.hero-mode .doc-upload-cards {
  flex: 1;
  max-height: none;
  min-height: 0;
}

.hero-pill {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  padding: 9px 16px;
}

.hero-pill:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.chat-pane.hero-mode.hero-leaving .hero-block,
.chat-pane.hero-mode.hero-leaving .composer,
.chat-pane.hero-mode.hero-leaving .hero-pills {
  opacity: 0;
  transform: translateY(-14px);
}

/* ==================== salon: agent head (B1 M2) ==================== */
/* Avatar + role name above the bubble for messages carrying B0 agent fields;
   anonymous messages keep the legacy row layout untouched. */

.message-row.has-agent {
  align-items: flex-start;
  flex-direction: column;
  gap: 6px;
}

.agent-head {
  align-items: center;
  display: flex;
  gap: 8px;
  padding-left: 2px;
}

.agent-avatar {
  border: 1px solid var(--line);
  border-radius: 50%;
  height: 28px;
  object-fit: cover;
  width: 28px;
}

.agent-name {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
}

/* ==================== salon: cards + tool-use (B1 M3/M4) ==================== */
/* Structured salon bubbles: DOM children instead of pre-wrapped text. */

.bubble.salon-card,
.bubble.tool-use-bubble {
  white-space: normal;
}

.salon-card-text {
  line-height: 1.65;
  white-space: pre-wrap;
}

.salon-card-title {
  font-weight: 700;
  margin-bottom: 8px;
}

.salon-card-salon_open {
  border-left: 3px solid var(--accent);
}

.salon-card-expert_joined {
  border-left: 3px solid var(--accent-2);
  color: var(--muted);
  padding-top: 8px;
  padding-bottom: 8px;
}

.salon-card-expert_speech {
  border-left: 3px solid var(--accent-3);
}

.salon-card-salon_summary {
  border-left: 3px solid var(--accent-warm);
}

.salon-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.salon-chip {
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--accent);
  font-size: 12px;
  padding: 3px 10px;
}

/* T4-M5: the recap is a ledger — question / answer / who decided it — so it
   is laid out as one. Grid, because grid columns are shared by every row;
   the flex-wrap row it replaces put the tag beside the answer or under the
   question depending on that row's text length. */
/* The bubble has to carry a definite width, because the card inside it is a
   container-query context and a size-contained element contributes NO
   intrinsic width. `.message-row.has-agent` is a column flex with
   align-items:flex-start, so the bubble is shrink-to-fit — without this the
   bubble measured 30px with the card at 0 (2026-08-12). `width` rather than
   `align-self: stretch` so it does not depend on the row's flex direction;
   `.bubble`'s own max-width: min(760px, 86%) still caps it. */
.bubble.salon-card-recap {
  width: 100%;
}
.recap-card {
  /* The card's width depends on whether the report preview is open, which the
     viewport cannot see — hence a container query, not a media query. */
  container-type: inline-size;
}
.recap-list {
  align-items: stretch;
  column-gap: 0;
  display: grid;
  /* The question keeps a floor so a long expert answer wraps instead of
     starving it. The answer track may shrink below max-content for the same
     reason (expert answers are free text, unlike the pool's short values). */
  grid-template-columns: minmax(120px, 1fr) minmax(0, auto) auto;
}
/* column-gap stays 0 on purpose: the hairline is painted on the CELLS, and a
   grid gap is painted by nobody — any column-gap cuts every rule into three
   pieces with daylight between them. Horizontal rhythm is cell padding. */
.recap-item {
  display: contents;
}
.recap-item > * {
  padding-block: 10px;
  padding-inline: 8px;
}
.recap-item > :first-child {
  padding-left: 0;
}
.recap-item > :last-child {
  padding-right: 0;
}
.recap-item + .recap-item > * {
  border-top: 1px solid var(--line);
}

/* 22px line box on both, so the 13px question and the 15px answer make cells
   of equal height. With `align-items: baseline` the boxes would start at
   different y and the hairline would come out stepped. */
.recap-question {
  color: var(--muted);
  font-size: 13px;
  line-height: 22px;
}
.recap-answer {
  line-height: 22px;
  text-align: right;
}
.recap-tagcell {
  line-height: 22px;
}

@container (max-width: 520px) {
  /* Three columns do not fit an English row in a squeezed chat pane — the
     question column starves to one word per line. Stack instead: question on
     its own row, answer and tag beneath it. */
  .recap-list {
    grid-template-columns: minmax(0, 1fr) auto;
  }
  .recap-question {
    grid-column: 1 / -1;
    padding-bottom: 2px;
  }
  .recap-answer {
    grid-column: 1;
    padding-top: 0;
    text-align: left;
  }
  .recap-tagcell {
    grid-column: 2;
    padding-top: 0;
    text-align: right;
  }
  /* the rule moves to the question, the one cell that spans the row */
  .recap-item + .recap-item > * {
    border-top: 0;
  }
  .recap-item + .recap-item > .recap-question {
    border-top: 1px solid var(--line);
    padding-top: 10px;
  }
}

/* ===== T4: 「不讨论」 marks and secretary-settled answers ===== */

.recap-answer-none {
  color: var(--muted);
}
.recap-answer-inferred {
  color: var(--accent);
}
/* One shape, four weights. SOLID = the committed path, which is what an answer
   the user gave is. Dashed already means "not the committed path" in this
   stylesheet — the escape buttons, the settled card's left rule — so the three
   departures inherit it rather than inventing a second vocabulary. */
.recap-tag {
  border: 1px dashed var(--accent-soft);
  border-radius: 999px;
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 2px 9px;
  white-space: nowrap;
}
.recap-tag-user {
  border-style: solid;
  border-color: var(--line);
  color: var(--muted);
}
.recap-tag-inferred {
  color: var(--accent);
}
.recap-tag-skipped {
  border-color: var(--line);
  color: var(--muted);
}
.recap-tag-excluded {
  border-color: rgba(255, 100, 124, 0.4);
  color: var(--danger);
}
/* The glyph off the escape button the user actually clicked, carried into the
   record of what it did. */
.recap-tag-excluded::before {
  content: "⊘ ";
  font-weight: 400;
}

/* The settled card. Dashed left rule = inferred rather than stated; dashed
   already reads as "not the committed path" here (the escape buttons are
   dashed), so this reuses the vocabulary rather than inventing one. */
.settled-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 10px;
}
.settled-item {
  border-left: 2px dashed var(--accent-soft);
  padding: 7px 0 7px 12px;
}
.settled-question {
  color: var(--muted);
  font-size: 13px;
}
.settled-answer-row {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.settled-answer {
  color: var(--accent);
  font-weight: 700;
}
.settled-why {
  color: var(--muted);
  font-size: 12.5px;
  opacity: 0.85;
}
.settled-fix {
  background: none;
  border: 0;
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  padding: 0;
  text-decoration: underline dotted;
}
.settled-fix:hover {
  color: var(--accent);
}
.settled-editor {
  align-items: center;
  display: flex;
  flex: 1 1 280px;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 3px;
}
.settled-edit-input {
  background: var(--panel);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  color: var(--text);
  flex: 1 1 190px;
  font: inherit;
  min-width: 150px;
  padding: 7px 9px;
}
.settled-edit-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
  outline: none;
}
.settled-edit-save,
.settled-edit-cancel {
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  padding: 6px 10px;
}
.settled-edit-save {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.settled-edit-cancel {
  background: transparent;
  color: var(--muted);
}
.settled-edit-save:disabled,
.settled-edit-cancel:disabled {
  cursor: wait;
  opacity: 0.6;
}
.settled-edit-error {
  color: var(--danger);
  flex-basis: 100%;
  font-size: 12.5px;
  min-height: 1.2em;
}

.salon-details {
  border-top: 1px dashed var(--line);
  margin-top: 10px;
  padding-top: 8px;
}

.salon-details summary {
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  user-select: none;
}

.salon-details summary:hover {
  color: var(--accent);
}

.salon-details-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}

.salon-details-group {
  color: var(--accent);
  font-size: 12px;
  font-weight: 700;
  margin-top: 6px;
}

.salon-details-line {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.55;
}

.tool-use-card summary {
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  user-select: none;
}

.tool-use-card summary::after {
  content: " ⌄";
  opacity: 0.7;
}

.tool-use-card[open] summary::after {
  content: " ⌃";
}

.tool-use-card summary:hover {
  color: var(--accent);
}

.tool-use-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 8px;
}

.tool-use-query {
  font-size: 13px;
  line-height: 1.5;
  padding-left: 14px;
  position: relative;
}

.tool-use-query::before {
  color: var(--accent);
  content: "›";
  left: 2px;
  position: absolute;
}

.tool-use-gists {
  border-top: 1px dashed var(--line);
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
  padding-top: 8px;
}

.tool-use-gist {
  color: var(--muted);
  font-size: 13px;
  line-height: 1.55;
}

.thinking-copy {
  color: var(--muted);
  font-size: 13px;
  margin-left: 9px;
}

/* ==================== SYS-H2: a session view on its way out ==============
   Deliberately NOT a variant of .read-only below — that is a permanent
   property of a frozen archive, this lasts exactly one round trip. From the
   moment the user picks another session until its payload commits, the pane
   still shows the OLD session: visible so the switch does not flash, but not
   clickable, because a click landing here now would act on the session the
   user believes they have just left. The sidebar stays live, so picking yet
   another session still works. Removing the class restores everything. */
.chat-pane.switching {
  pointer-events: none;
}

.chat-pane.switching .messages {
  opacity: 0.7;
  transition: opacity 120ms ease-out;
}

/* ==================== salon: legacy read-only (B8) ==================== */
/* Frozen pre-salon archives: no composer, greyed one-shot actions (handled in
   JS), file chips (preview / DOCX download) stay fully clickable. */

.chat-pane.read-only .composer {
  display: none;
}

/* M8 (2026-07-22): the same drop-in shape, for the phases where the bottom box
   is provably unused (see composerIsUnused in app.js). Deliberately a parallel
   class rather than a variant of .read-only — those are different states, and a
   frozen archive must not be confused with a live session that simply answers
   through chips. Removing the class restores everything: the form, sendMessage
   and every handler are untouched. */
.chat-pane.composer-hidden .composer {
  display: none;
}

.legacy-banner {
  background: rgba(245, 201, 93, 0.08);
  border: 1px solid rgba(245, 201, 93, 0.3);
  border-radius: 8px;
  color: var(--accent-warm);
  font-size: 13px;
  line-height: 1.6;
  margin: 0 auto 18px;
  max-width: min(760px, 92%);
  padding: 10px 14px;
}

.session-badge {
  border-radius: 999px;
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  margin-left: 6px;
  padding: 3px 8px;
  vertical-align: middle;
}

/* Renamed from .salon: the badge reports the session's STAGE now, and the old
   class was keyed on ui_version, which said "沙龙" from the first second of
   every session regardless of where it had got to. */
.session-badge.stage {
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  color: var(--accent);
}

.session-badge.frozen {
  background: rgba(245, 201, 93, 0.1);
  border: 1px solid rgba(245, 201, 93, 0.32);
  color: var(--accent-warm);
}

/* ==================== salon: thought-library canvas (B4) ==================== */
/* Dark card wall grouped by the six primitive categories; each group carries
   its own accent via --group-color (set inline per group). */

.bubble.canvas-bubble {
  max-width: 96%;
  white-space: normal;
  width: 100%;
}

/* F2 (批1) + F64/F66 (批1.1): 思维基元面板常驻中列顶部（原「思维库画布」，每轮快照气泡取消）；max-height + 内滚，不挤压下方消息流 */
.library-canvas-pinned {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  max-height: 19vh; /* F66 (批1.1): 占用面积减半，was 38vh */
  overflow-y: auto;
  padding: 14px 44px;
  border-bottom: 1px solid var(--line);
}
.library-canvas-pinned[hidden] {
  display: none;
}
.chat-pane.hero-mode .library-canvas-pinned {
  display: none;
}
/* F65 (批1.1): 展开完整库抽屉时面板放宽，向下拉取；再点收起回 19vh */
.library-canvas-pinned.drawer-open {
  max-height: 62vh;
}

.library-canvas-head {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 10px;
}

.library-canvas-title {
  font-size: 15px;
  font-weight: 700;
}

.library-canvas-meta {
  color: var(--muted);
  font-size: 12px;
}

.library-open-full {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  padding: 5px 12px;
}

.library-open-full:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* F66 (批1.1): 颜色图例行——色点映射到 6 大类名，替代原每类分行的组头 */
.library-legend {
  align-items: center;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  font-size: 11px;
  gap: 6px 14px;
  margin-bottom: 8px;
}

.library-legend-item {
  align-items: center;
  display: inline-flex;
  gap: 5px;
}

/* F66: 单一扁平网格——所有基元排在一起、按类着色（无组头），更密更矮 */
.library-flat-grid {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  margin-top: 0;
}

/* F65 (批1.1): 底部「展开完整基元库」按钮行 + 原地向下展开的完整库抽屉 */
.library-canvas-foot {
  display: flex;
  justify-content: center;
  margin-top: 10px;
}

.library-full-drawer[hidden] {
  display: none;
}

.library-full-drawer {
  border-top: 1px dashed var(--line);
  margin-top: 10px;
  padding-top: 10px;
}

.library-full-title {
  color: var(--muted);
  font-size: 12px;
  font-weight: 700;
  margin-bottom: 6px;
}

.library-group {
  border-top: 1px solid var(--line);
  padding: 8px 0;
}

.library-group summary {
  align-items: center;
  cursor: pointer;
  display: flex;
  gap: 8px;
  user-select: none;
}

.library-group summary::after {
  color: var(--muted);
  content: "⌄";
  margin-left: auto;
}

.library-group[open] summary::after {
  content: "⌃";
}

.library-dot {
  background: var(--group-color, var(--accent));
  border-radius: 50%;
  display: inline-block;
  height: 9px;
  width: 9px;
}

.library-group-name {
  font-size: 13px;
  font-weight: 700;
}

.library-group-count {
  background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  font-size: 11px;
  line-height: 1;
  padding: 3px 8px;
}

.library-grid {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  margin-top: 8px;
}

.library-card {
  background: rgba(7, 16, 30, 0.78);
  border: 1px solid var(--line);
  border-left: 3px solid var(--group-color, var(--accent));
  border-radius: 8px;
  padding: 5px 8px;
}

.library-card.new {
  box-shadow: 0 0 0 1px var(--group-color, var(--accent));
}

.library-card-name {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
}

.library-card-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.library-card-cat {
  border-radius: 999px;
  font-size: 10px;
  line-height: 1;
  padding: 2px 6px;
}

.library-card-cat {
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  color: var(--accent);
}

/* 收敛动效（拍板）：先淡出变灰再移除；animationend 后 JS remove()。 */
.library-card.removing {
  animation: library-card-out 1s ease forwards;
  pointer-events: none;
}

@keyframes library-card-out {
  0% {
    opacity: 0.9;
  }
  45% {
    border-left-color: var(--muted);
    filter: grayscale(1);
    opacity: 0.55;
  }
  100% {
    filter: grayscale(1);
    opacity: 0;
    transform: scale(0.95);
  }
}

/* 历史轮次收成一行 */
.library-history summary {
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  user-select: none;
}

.library-history summary:hover {
  color: var(--accent);
}

.library-history[open] summary {
  margin-bottom: 8px;
}

/* 右侧「完整基元库」弹层复用 preview 面板；面板里组间距稍收紧 */
.library-full .library-group:first-child {
  border-top: none;
}

/* ==================== salon: thought chain + topology attribution (B5→S5) ==================== */

.chain-flow {
  align-items: stretch;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.chain-step {
  background: rgba(7, 16, 30, 0.78);
  border: 1px solid var(--line);
  border-radius: 10px;
  flex: 0 1 auto;
  max-width: 240px;
  padding: 9px 11px;
  position: relative;
}

.chain-step-badge {
  color: var(--accent);
  font-size: 13px;
  font-weight: 700;
  margin-right: 6px;
}

.chain-step-labels {
  display: inline;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.5;
}

.chain-step-experts {
  align-items: center;
  display: flex;
  gap: 4px;
  margin-top: 6px;
}

.chain-mini-avatar {
  border: 1px solid var(--line);
  border-radius: 50%;
  height: 18px;
  object-fit: cover;
  width: 18px;
}

.chain-link {
  align-self: center;
  color: var(--accent-2);
  flex: 0 0 auto;
  font-size: 12px;
  white-space: nowrap;
}

.chain-synthesis {
  border-left: 3px solid var(--accent-warm);
  border-radius: 6px;
  background: rgba(245, 201, 93, 0.06);
  color: var(--text);
  font-size: 13px;
  line-height: 1.65;
  margin-top: 10px;
  padding: 8px 12px;
  white-space: pre-wrap;
}

/* Mind-Elixir 节点上的 tags（逻辑标签 + 步序徽标）
   item 2 (批0.93): 深灰底 + 白字（原淡蓝 accent 底/蓝字）
   item 4 (批0.93): 学科专家短名已从节点移除 */
.topology-map me-tpc .tags span {
  background: #4b5563;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 999px;
  color: #ffffff;
  display: inline-block;
  font-size: 10px;
  line-height: 1;
  margin: 3px 4px 0 0;
  padding: 3px 7px;
}

/* ==== salon: topology viewer entry inside the topology_ready card ====
   批0.7: the button reuses .review-action-button (same family as the
   outline-card buttons); only the in-card spacing lives here. */
.salon-card .review-action-button {
  margin-top: 8px;
}

/* ==== salon: expert roster in salon_open card (F20) + synthesis label (F19) ==== */
.expert-roster {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 18px;
  padding: 10px 12px;
  margin: 10px 0 4px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.02);
}
.expert-roster-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  min-width: 72px;
}
.expert-roster-avatar {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--line-strong);
}
.expert-roster-name {
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}
.chain-synthesis-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  margin-bottom: 6px;
}

/* 批3/F70: secretary 需求速览 — read-only 3-row key/value table */
.need-summary-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 8px;
  font-size: 13px;
}
.need-summary-table tr + tr .need-summary-key,
.need-summary-table tr + tr .need-summary-val {
  border-top: 1px solid var(--line);
}
.need-summary-key {
  width: 5.5em;
  padding: 6px 10px 6px 0;
  color: var(--muted);
  font-weight: 600;
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}
.need-summary-val {
  padding: 6px 0;
  color: var(--text);
  vertical-align: top;
  line-height: 1.5;
}

/* 批3/F6: speaking-status roster — spinner per expert until its speech lands */
.speaking-roster {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 8px;
}
.speaking-roster-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 88px;
  padding: 8px 10px;
  border-radius: 10px;
  background: var(--bubble-strong);
  border: 1px solid var(--line);
}
.speaking-roster-cell.done {
  opacity: 0.72;
}
.speaking-roster-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
}
.speaking-roster-name {
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  text-align: center;
}
.speaking-roster-status {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--muted);
}
.speaking-roster-status.is-done {
  color: var(--accent);
  font-weight: 600;
}
.speaking-spinner {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  border-top-color: var(--accent);
  display: inline-block;
  animation: speaking-spin 0.8s linear infinite;
}
@keyframes speaking-spin {
  to { transform: rotate(360deg); }
}

/* ============================================================================
   批3/F7 · 白天模式 (light theme) —— Master-wei 拍板：暖白底 + 压深强调色；
   报告预览与全站配色统一跟随主题切换。
   实现：深色主题 (:root + 上方所有硬编码值) 保持零改动；此处仅追加
   html[data-theme="light"] 覆盖块 (属性选择器特异度更高，恒胜出)。data-theme
   由 index.html <head> 首屏内联脚本按 localStorage 预置 (无闪烁)，toggle 见 app.js。
   报告预览为内联 DOM (#preview-content innerHTML)，天然继承这些变量；报告内嵌
   图表为白底 publication PNG，浅色下与纸面自然融合，无需后端改动。
   ============================================================================ */

/* toggle 按钮 (两主题通用，随变量自适应)：品牌名右侧的日/月切换 */
.theme-toggle {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 8px;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--muted);
  cursor: pointer;
  position: relative;
  z-index: 61;
}
.theme-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.theme-toggle svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* ⑤ (批4.11): 语言切换 —— 分段滑条(「中」「英」两端常显 + 滑块偏向选中侧)。
   位于右上角最左、白天/黑夜键左侧。仅新建会话(hero)可点; 会话一旦启动即 .is-disabled
   变灰、滑块冻结在该会话语言(报告语言此刻已锁)。滑动动画靠原地切换(不 reload)成立。 */
.lang-toggle {
  flex-shrink: 0;
  position: relative;
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  width: 66px;
  height: 30px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  cursor: pointer;
  user-select: none;
  z-index: 61;
}
.lang-toggle:hover:not(.is-disabled) {
  border-color: var(--accent);
}
.lang-toggle-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 30px;
  height: 24px;
  border-radius: 999px;
  background: var(--accent-soft);
  transform: translateX(0);
  transition: transform 190ms cubic-bezier(0.4, 0, 0.2, 1);
}
.lang-toggle[data-active="en"] .lang-toggle-thumb {
  transform: translateX(32px);
}
.lang-toggle-opt {
  position: relative;
  z-index: 1;
  flex: 1;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  color: var(--muted);
  transition: color 190ms ease;
}
.lang-toggle[data-active="zh"] .lang-toggle-opt[data-lang="zh"],
.lang-toggle[data-active="en"] .lang-toggle-opt[data-lang="en"] {
  color: var(--accent);
}
.lang-toggle.is-disabled {
  opacity: 0.5;
  cursor: default;
}

html[data-theme="light"] {
  --free: rgba(60, 50, 33, 0.08);
  color-scheme: light;
  --bg: #f4f1ea;
  --panel: #fbfaf6;
  --panel-solid: #ffffff;
  --panel-raised: #ffffff;
  --sidebar: #efebe1;
  --text: #2b2620;
  --muted: #6d6459;
  --line: rgba(60, 50, 33, 0.14);
  --line-strong: rgba(13, 148, 168, 0.42);
  --accent: #0d94a8;
  --accent-2: #6f52e0;
  --accent-3: #12995a;
  --accent-warm: #bd7d16;
  --accent-soft: rgba(13, 148, 168, 0.12);
  --success: #12995a;
  --danger: #cf4257;
  --bubble: #ffffff;
  --bubble-strong: #efeadf;
  --shadow: 0 18px 48px rgba(60, 50, 33, 0.16);
  /* T2 diff, translated by ROLE rather than by lightening the dark hues.
     Contrast is against this theme's panel (#fbfaf6). Two asymmetries are
     deliberate, not colour drift:
     1. The old text is WARM grey here. Dark --muted is cool (#93a9c3); light
        --muted is warm (#6d6459) because the whole light theme sits on a beige
        ground. A cool grey-blue on beige reads as a foreign object. Same role
        (recede), hue follows its own ground.
     2. "The new text is brighter" is impossible here. In dark, new text is 13.5
        against body text at 12.5, so it steps forward. Light body text is a
        near-black 14.35 and NO cyan is darker. Here the distinction is carried
        by hue: 5.9, below the body text and far above the 4.5 floor. */
  /* K123-L1 (2026-08-17): was #7d7466, and its own comment said 4.4. Measured
     against the lightest ground it actually lands on — the preview's gradient
     bottoms out at #fbfaf6, not at the page background — that is 4.4075:1,
     under the 4.5:1 floor for normal-size text, and a threshold does not round
     up. Superseded text is struck through, which is decoration and carries no
     contrast of its own, so this colour is the whole legibility budget for a
     whole paragraph of it. Darkened along the same hue: 4.96:1. */
  --edit-old: #756c5b;      /* 5.0 */
  --edit-new: #086b7a;      /* 5.9 */
  --edit-added: #0d7a47;    /* 5.2 */
  --edit-removed: #b3374a;  /* 5.6 */
}

/* ---- base surfaces: the hardcoded dark gradients / dot patterns ---- */
html[data-theme="light"] body {
  background:
    radial-gradient(circle at 12px 14px, rgba(13, 148, 168, 0.06) 0 1px, transparent 1.4px) 0 0 / 46px 46px,
    radial-gradient(circle at 32px 26px, rgba(111, 82, 224, 0.05) 0 1px, transparent 1.5px) 0 0 / 68px 68px,
    linear-gradient(135deg, #f4f1ea 0%, #f0ede4 46%, #efece6 100%);
}
html[data-theme="light"] body::before {
  background:
    linear-gradient(90deg, rgba(60, 50, 33, 0.035) 1px, transparent 1px) 0 0 / 80px 80px,
    linear-gradient(180deg, rgba(60, 50, 33, 0.03) 1px, transparent 1px) 0 0 / 80px 80px;
}
html[data-theme="light"] .sidebar {
  background:
    linear-gradient(180deg, #efebe1 0%, #eae5da 100%),
    var(--sidebar);
  box-shadow: inset -1px 0 0 rgba(60, 50, 33, 0.05);
}
html[data-theme="light"] .chat-pane {
  background:
    linear-gradient(180deg, #fbfaf6 0%, #f7f5ee 100%),
    var(--panel);
}
html[data-theme="light"] .preview-pane {
  background:
    linear-gradient(180deg, #ffffff 0%, #fbfaf6 100%),
    var(--panel);
}
html[data-theme="light"] .chat-pane::before,
html[data-theme="light"] .preview-pane::before {
  background:
    radial-gradient(circle at 16px 18px, rgba(13, 148, 168, 0.05) 0 1px, transparent 1.45px) 0 0 / 38px 38px,
    radial-gradient(circle at 44px 28px, rgba(18, 153, 90, 0.04) 0 1px, transparent 1.45px) 0 0 / 74px 74px,
    linear-gradient(90deg, rgba(60, 50, 33, 0.028) 1px, transparent 1px) 0 0 / 96px 96px,
    linear-gradient(180deg, rgba(111, 82, 224, 0.022) 1px, transparent 1px) 0 0 / 96px 96px;
  opacity: 0.6;
}

/* ---- brand / hero gradient text: #eef7ff (near-white) start vanishes on paper ---- */
html[data-theme="light"] .brand-name,
html[data-theme="light"] .hero-title {
  background-image: linear-gradient(90deg, var(--text) 0%, var(--accent) 52%, var(--accent-3) 100%);
  -webkit-background-clip: text;
  background-clip: text;
}

/* ---- controls that hardcoded a dark-navy fill (rgba(12,23,40,*)/(10,20,36,*)) ---- */
html[data-theme="light"] .platform-home-button,
html[data-theme="light"] .session-control-button,
html[data-theme="light"] .api-key-button,
html[data-theme="light"] .trash-button,
html[data-theme="light"] .review-action-button.secondary,
html[data-theme="light"] .modal-button.secondary,
html[data-theme="light"] .account-bar button,
html[data-theme="light"] .public-account-row button,
html[data-theme="light"] .auth-tab,
html[data-theme="light"] .topology-tag-button,
html[data-theme="light"] .hero-pill,
html[data-theme="light"] .library-open-full,
html[data-theme="light"] .library-group-count,
html[data-theme="light"] .api-key-small-button,
html[data-theme="light"] .api-key-row {
  background: #f3efe6;
}

/* ---- primary (accent-gradient) buttons: white ink reads cleanly on the
       darkened teal→purple; secondary variants keep dark text via :not() ---- */
html[data-theme="light"] .new-button,
html[data-theme="light"] .composer button,
html[data-theme="light"] .review-action-button:not(.secondary),
html[data-theme="light"] .preview-confirm,
html[data-theme="light"] .modal-button:not(.secondary),
html[data-theme="light"] .auth-button {
  color: #ffffff;
}

/* ---- user (sent) message bubble: follow the theme accents ---- */
html[data-theme="light"] .user .bubble {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border-color: rgba(13, 148, 168, 0.4);
  box-shadow: 0 12px 30px rgba(13, 148, 168, 0.14);
  color: #ffffff;
}

/* ---- session list rows ---- */
html[data-theme="light"] .session-item {
  background: rgba(60, 50, 33, 0.04);
}
html[data-theme="light"] .session-item.active {
  background:
    linear-gradient(135deg, rgba(13, 148, 168, 0.16), rgba(111, 82, 224, 0.14)),
    #ffffff;
  border-color: rgba(13, 148, 168, 0.5);
  box-shadow: inset 0 0 0 1px rgba(13, 148, 168, 0.1), 0 10px 24px rgba(60, 50, 33, 0.1);
}

/* ---- inputs / fields (hardcoded very-dark fills) ---- */
html[data-theme="light"] .composer textarea,
html[data-theme="light"] .auth-input,
html[data-theme="light"] .api-key-input {
  background: #ffffff;
}

/* ---- floating panels / menus (near-opaque dark) ---- */
html[data-theme="light"] .session-menu,
html[data-theme="light"] .modal-panel {
  background: #ffffff;
}

/* ---- cards with a hardcoded dark fill ---- */
html[data-theme="light"] .file-chip,
html[data-theme="light"] .library-card,
html[data-theme="light"] .chain-step {
  background: #ffffff;
}

/* ---- report preview: table header tint on paper ---- */
html[data-theme="light"] .preview-content th {
  background: rgba(13, 148, 168, 0.08);
}

/* ---- auth overlay scrim: warm, lighter ---- */
html[data-theme="light"] .auth-overlay {
  background: radial-gradient(circle at 50% 20%, rgba(120, 108, 90, 0.26), rgba(70, 62, 50, 0.48));
}

/* ---- pending progress marker inner ring (was dark navy) ---- */
html[data-theme="light"] .progress-item.pending .progress-marker {
  box-shadow: inset 0 0 0 7px #f4f1ea;
}

/* ═══ Side chat + notes panel (2026-07-22) ═══════════════════════════════════
   Written in :root design tokens rather than hardcoded navy fills, so the light
   theme follows automatically and these rules need no entries in the
   html[data-theme="light"] override lists above. The few places a token cannot
   carry (near-opaque panel fills) are handled at the end of this block. */

/* ---- floating entry bubble (D4): the first FAB in the app ---- */
.side-chat-fab {
  /* The composer's send button sits at the bottom-right of the chat pane, so
     the old 24px put the entry directly on top of it. Declared here rather
     than in :root because only this rule and its drag handler use it. */
  --side-chat-fab-bottom: 104px;
  --side-chat-fab-right: 24px;
  align-items: center;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: none;
  border-radius: 999px;
  bottom: var(--side-chat-fab-bottom);
  box-shadow: var(--shadow);
  cursor: grab;
  display: flex;
  height: 52px;
  justify-content: center;
  position: fixed;
  right: var(--side-chat-fab-right);
  touch-action: none;
  transition: transform 0.15s ease;
  width: 52px;
  /* Above .preview-resizer (30); below .lab-intro-card (60) and the theme /
     language toggles (61). */
  z-index: 40;
}
.side-chat-fab[hidden] { display: none; }
.side-chat-fab:hover { transform: translateY(-2px); }
.side-chat-fab.is-open { opacity: 0.72; }
.side-chat-fab.is-dragging { cursor: grabbing; transform: none; }
.side-chat-fab-glyph { font-size: 22px; line-height: 1; }

/* ---- entry hint: a balloon with a tail, placed from the FAB's live rect ---- */
.side-chat-intro {
  background: var(--panel-solid);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  color: var(--text);
  font-size: 12px;
  line-height: 1.5;
  max-width: 208px;
  padding: 10px 24px 10px 12px;
  position: fixed;
  /* Just under the FAB, so dragging the FAB never passes under its own hint. */
  z-index: 39;
}
.side-chat-intro[hidden] { display: none; }
.side-chat-intro-tail { position: absolute; bottom: -20px; }
.side-chat-intro-close {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 2px 4px;
  position: absolute;
  right: 3px;
  top: 3px;
}
.side-chat-intro-close:hover { color: var(--text); }

/* ---- floating window ---- */
.side-chat-window {
  backdrop-filter: blur(18px);
  background: var(--panel-solid);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  position: fixed;
  z-index: 40;
}
.side-chat-window[hidden] { display: none; }

/* z-index 40 sits above .modal-backdrop (20), so the window would cover the
   API-key and writer-reuse dialogs. Drop both it and the bubble below whenever
   a modal or the auth overlay is open. */
body:has(.modal-backdrop:not(.closed)) .side-chat-window,
body:has(.modal-backdrop:not(.closed)) .side-chat-fab,
body:has(.modal-backdrop:not(.closed)) .side-chat-intro,
body:has(.auth-overlay:not(.closed)) .side-chat-window,
body:has(.auth-overlay:not(.closed)) .side-chat-fab,
body:has(.auth-overlay:not(.closed)) .side-chat-intro {
  z-index: 10;
}

.side-chat-header {
  align-items: center;
  border-bottom: 1px solid var(--line);
  cursor: grab;
  display: flex;
  flex: 0 0 auto;
  gap: 8px;
  justify-content: space-between;
  padding: 10px 12px;
  user-select: none;
}
body.side-chat-dragging { cursor: grabbing; user-select: none; }
/* Keep children from eating pointermove mid-drag — the same guard
   body.preview-resizing uses. */
body.side-chat-dragging .side-chat-log,
body.side-chat-dragging .side-chat-composer { pointer-events: none; }

/* Avatar + name as one group, so the header's space-between still puts the
   identity left and the icon buttons right rather than spreading three items. */
.side-chat-identity { align-items: center; display: flex; gap: 8px; min-width: 0; }
.side-chat-avatar {
  border: 1px solid var(--line);
  border-radius: 50%;
  flex: 0 0 auto;
  height: 22px;
  object-fit: cover;
  width: 22px;
}
.side-chat-title {
  color: var(--text);
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.side-chat-actions { display: flex; gap: 4px; }
.side-chat-icon {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  height: 26px;
  line-height: 1;
  width: 26px;
}
.side-chat-icon:hover { border-color: var(--line); color: var(--text); }
.side-chat-icon.is-on {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--accent);
}

.side-chat-log {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  padding: 12px;
}
.side-chat-msg {
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.6;
  max-width: 92%;
  padding: 8px 10px;
  white-space: pre-wrap;
  word-break: break-word;
}
.side-chat-msg.assistant { align-self: flex-start; background: var(--bubble); color: var(--text); }
.side-chat-msg.user {
  align-self: flex-end;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  color: #ffffff;
}
/* Notices (degrade markers, compaction, errors) are chrome, not conversation:
   muted and full width so they never read as the secretary speaking. */
.side-chat-msg.notice {
  align-self: stretch;
  background: transparent;
  border: 1px dashed var(--line);
  color: var(--muted);
  font-size: 12px;
  max-width: 100%;
}
.side-chat-msg.is-degraded { border-left: 2px solid var(--accent-warm); }
.side-chat-pending { color: var(--muted); font-style: italic; }
.side-chat-receipt { color: var(--muted); font-size: 11px; margin-bottom: 4px; }

.side-chat-composer {
  align-items: flex-end;
  border-top: 1px solid var(--line);
  display: flex;
  flex: 0 0 auto;
  gap: 8px;
  padding: 10px 12px;
}
.side-chat-composer textarea {
  background: var(--bubble);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text);
  flex: 1 1 auto;
  font-family: inherit;
  font-size: 13px;
  max-height: 120px;
  padding: 8px 10px;
  resize: none;
}
.side-chat-composer textarea:focus { border-color: var(--line-strong); outline: none; }
.side-chat-composer button {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: none;
  border-radius: 10px;
  color: #ffffff;
  cursor: pointer;
  font-size: 12px;
  padding: 9px 14px;
}

/* ---- 8-way resize grips, shared by every floating window ----
   Both windows clip with overflow:hidden, so the grips ride just INSIDE the
   frame rather than straddling it. Corners sit above the edges so the shared
   pixels resize both axes, and each corner carries its own diagonal cursor —
   the single old grip was a bottom-left corner wearing nwse-resize. */
.win-resize { position: absolute; z-index: 6; }
.win-resize-n,
.win-resize-s { cursor: ns-resize; height: 6px; left: 14px; right: 14px; }
.win-resize-e,
.win-resize-w { cursor: ew-resize; bottom: 14px; top: 14px; width: 6px; }
.win-resize-n { top: 0; }
.win-resize-s { bottom: 0; }
.win-resize-w { left: 0; }
.win-resize-e { right: 0; }
.win-resize-nw,
.win-resize-ne,
.win-resize-se,
.win-resize-sw { height: 14px; width: 14px; }
.win-resize-nw { cursor: nwse-resize; left: 0; top: 0; }
.win-resize-ne { cursor: nesw-resize; right: 0; top: 0; }
.win-resize-se { cursor: nwse-resize; bottom: 0; right: 0; }
.win-resize-sw { cursor: nesw-resize; bottom: 0; left: 0; }

/* ---- slash menu (D18): every entry comes from the backend registry ----
   Shared by the side chat and the report assistant since K123-H8; the class
   family is named after the feature rather than either chat. ---- */
.slash-menu {
  border-top: 1px solid var(--line);
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  max-height: 168px;
  overflow-y: auto;
}
.slash-menu[hidden] { display: none; }
.slash-menu-item {
  background: transparent;
  border: none;
  border-left: 2px solid transparent;
  color: var(--text);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 7px 12px;
  text-align: left;
}
.slash-menu-item:hover,
.slash-menu-item.is-active {
  background: var(--accent-soft);
  border-left-color: var(--accent);
}
.slash-menu-name { font-size: 12px; font-weight: 600; }
.slash-menu-desc { color: var(--muted); font-size: 11px; }
.slash-menu-hint { color: var(--muted); font-size: 11px; padding: 7px 12px; }
.slash-menu-hint.is-missing { color: var(--danger); }

/* ---- notes panel (D17), sharing the pinned slot with the thought library ---- */
.panel-switcher { display: flex; gap: 6px; margin-bottom: 8px; }
.panel-switcher[hidden] { display: none; }
.panel-switcher-tab {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
  padding: 3px 12px;
}
.panel-switcher-tab.is-active {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--accent);
}
.library-canvas-slot[hidden],
.notes-canvas[hidden] { display: none; }
.notes-canvas-title {
  color: var(--muted);
  font-size: 11px;
  letter-spacing: 0.04em;
  margin-bottom: 6px;
  text-transform: uppercase;
}
/* Same grid metrics as .library-flat-grid so both canvases read as one system. */
.notes-grid {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.note-card {
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-3);
  border-radius: 8px;
  padding: 5px 8px;
  position: relative;
}
.note-card-name {
  font-size: 12px;
  font-weight: 600;
  overflow: hidden;
  padding-right: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* D10: revealed on hover, but always reachable by keyboard. */
.note-card-delete {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  opacity: 0;
  padding: 0 2px;
  position: absolute;
  right: 4px;
  top: 4px;
}
.note-card:hover .note-card-delete,
.note-card-delete:focus { color: var(--danger); opacity: 1; }

/* ---- M7: note full-text hover card, portalled to <body> ----
   .library-canvas-pinned has overflow-y:auto and would clip an absolutely
   positioned child, so this cannot be the pure-CSS child popover that
   .lab-intro-card uses. It is a body-level element positioned with fixed
   coordinates by JS (which also handles the viewport-edge flip). */
.note-hover-card {
  backdrop-filter: blur(10px);
  background: var(--panel-raised);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  color: var(--text);
  /* Size to the content, then cap. A position:fixed box with only `left` set
     is shrink-to-fit against its containing block, which collapses to a
     one-character column if the viewport ever measures 0 (background tab). */
  width: max-content;
  max-width: 340px;
  padding: 10px 12px;
  position: fixed;
  /* Same layer as .lab-intro-card (60), the app's other hover card, so the two
     read as one mechanism; above the side chat window (40). */
  z-index: 60;
}
.note-hover-card[hidden] { display: none; }
.note-hover-title {
  color: var(--accent-3);
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 6px;
}
.note-hover-body {
  color: var(--text);
  font-size: 12px;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
}
.note-card:focus-visible { outline: 1px solid var(--line-strong); }

/* ---- light theme: only what a token cannot carry ---- */
html[data-theme="light"] .side-chat-window,
html[data-theme="light"] .slash-menu { background: #ffffff; }
html[data-theme="light"] .side-chat-msg.assistant,
html[data-theme="light"] .side-chat-composer textarea { background: #f3efe6; }
html[data-theme="light"] .note-card,
html[data-theme="light"] .note-hover-card { background: #ffffff; }

/* ---- M6 (2026-07-28): the user's own document library ---- */
.documents-canvas[hidden] { display: none; }

/* One box. The cards live INSIDE the drop zone, so "where the papers went" is
   the same place as "where you put them" — not a second strip underneath. */
.doc-upload {
  border: 1.5px dashed var(--line);
  border-radius: 12px;
  margin: 8px 28px 16px;
  max-width: 860px;
  padding: 8px 12px;
}
.doc-upload[hidden] { display: none; }
.doc-upload.is-dragging { background: var(--panel-raised); border-color: var(--accent-3); }
.doc-upload.is-disabled {
  background: rgba(128, 128, 128, 0.12);
  border-color: color-mix(in srgb, var(--muted) 58%, transparent);
  filter: saturate(0.55);
}
.doc-upload.is-disabled .doc-upload-button {
  cursor: not-allowed;
  opacity: 0.5;
  pointer-events: none;
}
.doc-upload.is-locked { opacity: 0.6; }
.doc-upload.is-locked .doc-upload-button { cursor: not-allowed; pointer-events: none; }

/* Panel home: same element, reparented into the documents tab once the run
   starts. Loses its own dashed frame and card area there — the panel already
   is the frame, and the cards already are the panel. */
.doc-upload.in-panel {
  border: none;
  height: auto;
  margin: 0 0 8px;
  max-width: none;
  min-height: 0;
  padding: 0;
}
.doc-upload.in-panel .doc-upload-cards { display: none; }
.doc-upload-head {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.custom-db-control {
  align-items: center;
  display: inline-flex;
  gap: 4px;
  position: relative;
}
.custom-db-toggle {
  align-items: center;
  background: transparent;
  border: 0;
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  font: inherit;
  font-size: 12px;
  gap: 6px;
  padding: 3px 2px;
  white-space: nowrap;
}
.custom-db-toggle:disabled { cursor: not-allowed; opacity: 0.72; }
.custom-db-toggle:focus-visible,
.custom-db-help:focus-visible {
  border-radius: 6px;
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
.custom-db-switch {
  background: color-mix(in srgb, var(--muted) 66%, transparent);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-sizing: border-box;
  display: inline-flex;
  height: 18px;
  padding: 2px;
  transition: background 0.18s ease;
  width: 32px;
}
.custom-db-switch > span {
  background: #ffffff;
  border-radius: 50%;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
  display: block;
  height: 12px;
  transform: translateX(0);
  transition: transform 0.18s ease;
  width: 12px;
}
.custom-db-toggle[aria-checked="true"] .custom-db-switch {
  background: var(--accent-3);
}
.custom-db-toggle[aria-checked="true"] .custom-db-switch > span {
  transform: translateX(14px);
}
.custom-db-help-wrap { display: inline-flex; position: relative; }
.custom-db-help {
  align-items: center;
  background: transparent;
  border: 1px solid var(--muted);
  border-radius: 50%;
  color: var(--muted);
  cursor: help;
  display: inline-flex;
  font: inherit;
  font-size: 10px;
  height: 17px;
  justify-content: center;
  line-height: 1;
  padding: 0;
  width: 17px;
}
.custom-db-tooltip {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  color: var(--text);
  display: none;
  font-size: 11px;
  left: 0;
  line-height: 1.55;
  max-width: min(320px, calc(100vw - 32px));
  padding: 8px 10px;
  position: absolute;
  top: calc(100% + 7px);
  width: max-content;
  z-index: 70;
}
.custom-db-help-wrap:hover .custom-db-tooltip,
.custom-db-help-wrap:focus-within .custom-db-tooltip { display: block; }
/* ── T1a: evidence-source row (K1 block) ─────────────────────────────────── */
/* Deliberately borrows the existing control vocabulary — the 12px label size,
   the tooltip's panel/line/radius — so the external-KB entry reads as the same
   species as the switch beside it rather than a new component system. */
.kb-sources-label {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}
.kb-external { display: inline-flex; position: relative; }
.kb-external-toggle {
  align-items: center;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  font: inherit;
  font-size: 12px;
  gap: 5px;
  padding: 3px 8px;
  white-space: nowrap;
}
.kb-external-toggle:disabled { cursor: not-allowed; opacity: 0.72; }
/* M8b (MD21): citation style sits on the same row as the evidence base. */
.citation-style {
  align-items: center;
  color: var(--muted);
  display: inline-flex;
  font-size: 12px;
  gap: 5px;
  white-space: nowrap;
}
.citation-style-select {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  padding: 3px 6px;
}
.citation-style-select:disabled { cursor: not-allowed; opacity: 0.72; }
.kb-external-toggle[aria-expanded="true"] { border-color: var(--line-strong); }
.kb-external-toggle:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
/* The count, not the names: this slot has to hold five knowledge bases one day
   and a name list stops fitting at two. The names live in the panel and in the
   summary line, which both have room for them. */
.kb-external-state { color: var(--accent-3); }
.kb-external-state.is-empty { color: var(--muted); }
.kb-external-caret { color: var(--muted); font-size: 10px; }
.kb-external-panel {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  left: 0;
  min-width: 232px;
  padding: 7px;
  position: absolute;
  top: calc(100% + 7px);
  z-index: 70;
}
.kb-external-option {
  align-items: flex-start;
  background: transparent;
  border: 0;
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  display: flex;
  font: inherit;
  gap: 9px;
  padding: 6px 7px;
  text-align: left;
  width: 100%;
}
.kb-external-option:hover { background: var(--bubble); }
.kb-external-option:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
.kb-external-check {
  align-items: center;
  border: 1px solid var(--line);
  border-radius: 4px;
  display: inline-flex;
  flex-shrink: 0;
  font-size: 10px;
  height: 14px;
  justify-content: center;
  margin-top: 1px;
  width: 14px;
}
.kb-external-option[aria-pressed="true"] .kb-external-check {
  background: var(--accent-3);
  border-color: var(--accent-3);
  color: #04342c;
}
.kb-external-name { display: block; font-size: 12px; }
.kb-external-desc {
  color: var(--muted);
  display: block;
  font-size: 11px;
  margin-top: 1px;
}
/* The one line that says what the report will actually be built from. Amber,
   not red, for the empty case: choosing no literature source is legal, it is
   just expensive to discover ten minutes into a run. */
.kb-sources-summary {
  color: var(--accent-3);
  font-size: 11px;
  margin-top: 7px;
}
.kb-sources-summary.is-empty { color: var(--accent-warm); }
.doc-upload-controls { display: contents; }
.doc-upload-controls[hidden] { display: none; }
.doc-upload-button {
  align-items: center;
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  display: inline-flex;
  font-size: 12px;
  gap: 4px;
  padding: 3px 10px;
  white-space: nowrap;
}
.doc-upload-button:hover { border-color: var(--accent-3); }
.doc-upload-hint { color: var(--muted); font-size: 11px; }
.doc-upload-count { color: var(--muted); font-size: 11px; margin-left: auto; }
.doc-upload-count.is-complete { color: var(--success); }
.doc-upload-message { color: var(--muted); font-size: 11px; flex-basis: 100%; }
.doc-upload-message.is-error { color: var(--danger); }

/* Height cap + internal scroll: 20 papers is ~7 rows, which would otherwise
   push the composer off screen. ~3.5 rows stay visible so it reads as a list
   that continues rather than one that ended. */
.doc-upload-cards {
  margin-top: 10px;
  max-height: 208px;
  overflow-y: auto;
  padding-right: 2px;
}
.doc-upload-cards[hidden] { display: none; }
.document-grid {
  display: grid;
  gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

.document-card {
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-radius: 10px;
  cursor: pointer;
  min-height: 58px;
  padding: 7px 9px;
  position: relative;
}
.document-card:hover { border-color: var(--accent-3); }
.document-card.is-queued { opacity: 0.6; }
.document-card-name {
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  display: -webkit-box;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  overflow: hidden;
  padding-right: 34px;
}
.document-card-foot { margin-top: 5px; }
.document-card-meta {
  color: var(--muted);
  display: flex;
  font-size: 10px;
  gap: 6px;
}
.document-card.is-ready .document-card-stage { color: var(--success); }
/* MD25:来源 tag(上传 / AceMap)。只是个小标签,颜色跟着 meta 走,不抢状态色。 */
.document-card-source {
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0 6px;
  white-space: nowrap;
}
/* 解析退化 / 未知时才出现的一行后果说明(§0:说后果不说原因)。 */
.document-card-note {
  color: var(--muted);
  font-size: 10px;
  line-height: 1.3;
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.document-card.is-failed .document-card-stage { color: var(--danger); }
/* AD4「存入我的库」:与删除同一档的小按钮,不抢卡片主视觉。 */
.document-card-keep {
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  cursor: pointer;
  font-size: 10px;
  margin-top: 4px;
  padding: 1px 8px;
}
.document-card-keep:disabled { cursor: default; opacity: 0.7; }
.document-card-delete {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  opacity: 0;
  padding: 0 2px;
  position: absolute;
  right: 4px;
  top: 3px;
}
.document-card:hover .document-card-delete,
.document-card-delete:focus { color: var(--danger); opacity: 1; }

/* Retry sits where the progress ring does on a working card — the failed card
   has no ring to compete with, and that corner is where the eye already is. */
.document-card-retry {
  background: rgba(220, 76, 100, 0.14);
  border: 1px solid var(--danger);
  border-radius: 999px;
  bottom: 6px;
  color: var(--danger);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 4px 10px;
  position: absolute;
  right: 6px;
}
.document-card-retry:hover { background: rgba(220, 76, 100, 0.26); }
.document-card-retry:disabled { cursor: default; opacity: 0.5; }

/* Circular progress, bottom-right of each card. The arc jumps between the
   backend's four stages; the transition below is what turns four jumps into
   motion, and the rotation says "working" while the arc is between stages. */
.document-ring {
  bottom: 6px;
  height: 26px;
  position: absolute;
  right: 6px;
  width: 26px;
}
/* Anchored at 12 o'clock and NEVER rotated. Rotation is the universal signal
   for "indeterminate", and a document sits in `digesting` for 20-50s, so a
   spinning ring with a frozen arc reads as a plain spinner no matter what the
   arc length says. The arc position must stay put for its length to mean a
   quantity; the pulse below says "still working" without implying unknown. */
.document-ring-svg { transform: rotate(-90deg); }
.document-ring-fill.is-active { animation: doc-ring-pulse 1.8s ease-in-out infinite; }
@keyframes doc-ring-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
.document-ring-track { fill: none; stroke: var(--line); stroke-width: 2.5; }
.document-ring-fill {
  fill: none;
  stroke: var(--accent-3);
  stroke-linecap: round;
  stroke-width: 2.5;
  transition: stroke-dashoffset 1.2s ease-out;
}
.document-card.is-ready .document-ring-fill { stroke: var(--success); }
.document-card.is-failed .document-ring-fill { stroke: var(--danger); }
/* The number is what makes "determinate" unambiguous — a bare arc at a fixed
   length is indistinguishable from a stalled spinner. 9px fits two digits
   inside the 21px inner diameter; the tick and bang get the larger size. */
.document-ring-glyph {
  align-items: center;
  color: var(--accent-3);
  display: flex;
  font-size: 9px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  inset: 0;
  justify-content: center;
  letter-spacing: -0.02em;
  position: absolute;
}
.document-card.is-ready .document-ring-glyph,
.document-card.is-failed .document-ring-glyph { font-size: 12px; }
.document-card.is-ready .document-ring-glyph { color: var(--success); }
.document-card.is-failed .document-ring-glyph { color: var(--danger); font-weight: 700; }
.doc-upload-button {
  align-items: center;
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  display: inline-flex;
  font-size: 12px;
  gap: 4px;
  padding: 3px 10px;
  white-space: nowrap;
}
.doc-upload-button:hover { border-color: var(--accent-3); }
.doc-upload-hint { color: var(--muted); font-size: 11px; }
.doc-upload-message { color: var(--muted); font-size: 11px; margin-left: auto; }
.doc-upload-message.is-error { color: var(--danger); }

/* Cards share .notes-grid metrics so the three pinned canvases read as one
   system; only the status accent differs. */
.document-card {
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: 8px;
  cursor: pointer;
  padding: 5px 8px;
  position: relative;
}
.document-card:hover { border-color: var(--accent-3); }
.document-card.is-queued { opacity: 0.55; }
.document-card.is-working { border-left-color: var(--accent-3); }
.document-card.is-ready { border-left-color: var(--success); }
.document-card.is-failed { border-left-color: var(--danger); }
.document-card-name {
  font-size: 12px;
  font-weight: 600;
  overflow: hidden;
  padding-right: 14px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.document-card-foot {
  align-items: center;
  color: var(--muted);
  display: flex;
  font-size: 11px;
  justify-content: space-between;
  margin-top: 2px;
}
.document-card.is-ready .document-card-mark { color: var(--success); }
.document-card.is-failed .document-card-mark { color: var(--danger); }
.document-card-delete {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  opacity: 0;
  padding: 0 2px;
  position: absolute;
  right: 4px;
  top: 4px;
}
.document-card:hover .document-card-delete,
.document-card-delete:focus { color: var(--danger); opacity: 1; }

.document-progress {
  background: var(--line);
  border-radius: 999px;
  height: 3px;
  margin-bottom: 8px;
  overflow: hidden;
}
.document-progress-fill { background: var(--accent-3); height: 100%; transition: width 0.3s; }

/* Portalled to <body> for the same reason .note-hover-card is: the pinned
   panel carries overflow-y:auto and clips positioned descendants. */
.document-preview-overlay {
  align-items: center;
  background: rgba(0, 0, 0, 0.45);
  display: flex;
  inset: 0;
  justify-content: center;
  position: fixed;
  z-index: 70;
}
.document-preview-overlay[hidden] { display: none; }
.document-preview-card {
  background: var(--panel-raised);
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 14px 16px;
  /* fixed, not the overlay's flex centring: the window is dragged and resized
     by applyDocumentPreviewGeometry, which owns left/top/width/height. The
     values here are the pre-script fallback only. NO max-width — a JS-set
     width would silently lose to it, and 760px is the clamp being removed
     (Master-wei: 预览窗口要能自由缩放大小). */
  position: fixed;
  height: 86vh;
  left: 5vw;
  top: 7vh;
  width: 90vw;
}
.document-preview-card.is-dragging { user-select: none; }
.document-preview-card.is-dragging .document-preview-frame { pointer-events: none; }

/* ---- source | parsed, split by the window's own width ---- */
.document-preview-panes {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
}
.document-preview-source {
  display: flex;
  flex-direction: column;
  /* --doc-split is the seam's only output; both panes read it, so they cannot
     get out of step the way two independent widths would. */
  flex: 0 0 calc(var(--doc-split, 0.58) * 100%);
  min-width: 0;
}
.document-preview-parsed {
  flex: 1 1 auto;
  min-width: 0;
  overflow: auto;
  padding: 2px 2px 2px 12px;
}
.document-preview-seam {
  cursor: col-resize;
  flex: 0 0 9px;
  position: relative;
}
.document-preview-seam::after {
  background: var(--line);
  content: "";
  inset: 0 4px;
  position: absolute;
}
.document-preview-seam:hover::after { background: var(--line-strong); }

.document-preview-tabs { display: none; gap: 4px; margin-bottom: 8px; }
.document-preview-tab {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  padding: 4px 12px;
}
.document-preview-tab.is-on {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--accent);
}

/* Narrow: one pane at a time, PDF keeps the full width. */
.document-preview-card[data-mode="tabs"] .document-preview-tabs { display: flex; }
.document-preview-card[data-mode="tabs"] .document-preview-seam { display: none; }
.document-preview-card[data-mode="tabs"] .document-preview-source { flex: 1 1 auto; }
.document-preview-card[data-mode="tabs"][data-tab="parsed"] .document-preview-source,
.document-preview-card[data-mode="tabs"]:not([data-tab="parsed"]) .document-preview-parsed {
  display: none;
}
.document-preview-card[data-mode="tabs"] .document-preview-parsed { padding-left: 2px; }

/* ---- the parsed panel ---- */
.doc-parsed-section { margin-bottom: 12px; }
.doc-parsed-label {
  color: var(--muted);
  font-size: 10px;
  letter-spacing: 0.04em;
  margin-bottom: 3px;
}
.doc-parsed-value { color: var(--text); font-size: 12px; line-height: 1.6; }
.doc-parsed-value.is-muted { color: var(--muted); }
.doc-parsed-value.is-error { color: var(--danger); }
.doc-parsed-meta { color: var(--text); font-size: 11px; line-height: 1.7; }
.doc-parsed-note { color: var(--muted); font-size: 11px; line-height: 1.5; }
.doc-parsed-caveat {
  border-left: 2px solid var(--line-strong);
  border-radius: 0;
  margin-bottom: 12px;
  padding-left: 8px;
}
.doc-parsed-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.doc-parsed-chip {
  background: var(--accent-soft);
  border-radius: 999px;
  color: var(--accent);
  font-size: 11px;
  padding: 2px 9px;
}
.doc-parsed-figures {
  display: grid;
  gap: 6px;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
}
/* The box belongs to the CELL, not to the image: an <img> still in flight has
   no box of its own, and a grid of them is indistinguishable from a paper with
   no figures. The cell is there from the first frame. */
.doc-parsed-figure-cell {
  align-items: center;
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: zoom-in;
  display: flex;
  height: 56px;
  justify-content: center;
  overflow: hidden;
  position: relative;
}
.doc-parsed-figure {
  height: 100%;
  object-fit: contain;
  width: 100%;
}
.doc-parsed-figure-note {
  color: var(--muted);
  font-size: 10px;
  line-height: 1.2;
  padding: 0 4px;
  text-align: center;
}
/* A moving stripe, so "slow" reads as slow rather than as empty. */
.doc-parsed-figure-cell.is-loading {
  background: linear-gradient(
    100deg, var(--panel-solid) 30%, var(--accent-soft) 50%, var(--panel-solid) 70%
  );
  background-size: 300% 100%;
  animation: doc-parsed-figure-shimmer 1.4s linear infinite;
}
.doc-parsed-figure-cell.is-loading .doc-parsed-figure { display: none; }
@keyframes doc-parsed-figure-shimmer {
  from { background-position: 150% 0; }
  to { background-position: -150% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .doc-parsed-figure-cell.is-loading { animation: none; }
}
/* Broken is a DIFFERENT square from slow — dashed and static, and it says so.
   Without the distinction every figure that never arrives reads as "still
   coming", which is the report the user cannot act on. */
.doc-parsed-figure-cell.is-broken {
  border-style: dashed;
  cursor: default;
}
.doc-parsed-figure-cell.is-broken .doc-parsed-figure { display: none; }
/* Zoom in place rather than in a second overlay: this window is already the
   thing the user opened to look at something. */
.doc-parsed-figure-cell.is-zoomed {
  cursor: zoom-out;
  grid-column: 1 / -1;
  height: auto;
  max-height: 60vh;
}
.doc-parsed-figure-cell.is-zoomed .doc-parsed-figure { max-height: 60vh; }
.doc-parsed-fulltext { margin: 4px 0 10px; }
.doc-parsed-fulltext > summary {
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
}
.doc-parsed-fulltext > pre {
  color: var(--text);
  font-size: 11px;
  line-height: 1.6;
  margin: 6px 0 0;
  max-height: 40vh;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.doc-parsed-stats {
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 10px;
  margin-top: 10px;
  padding-top: 7px;
}
.document-preview-title {
  cursor: move;
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 8px;
  padding-right: 24px;
  user-select: none;
}
.document-preview-body {
  color: var(--text);
  flex: 1 1 auto;
  font-size: 12px;
  line-height: 1.6;
  margin: 0;
  /* min-height:0 — a flex item's default min-height:auto refuses to shrink
     below its content, so without this the card cannot be resized smaller
     than the document. */
  min-height: 0;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}
.document-preview-body[hidden] { display: none; }
/* The browser's own PDF viewer. It fills the card now that the card has an
   explicit height; the old `min-height: 70vh` existed because the card had
   none, and would now refuse to shrink when the window is resized down. */
.document-preview-frame {
  background: #1b2432;
  border: 1px solid var(--line);
  border-radius: 10px;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
}
.document-preview-frame[hidden] { display: none; }
.document-preview-close {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  position: absolute;
  right: 10px;
  top: 8px;
}
.document-preview-close:hover { color: var(--text); }

/* ═══ T6: topology revision card (what the secretary changed) ═════════════
   Appended, per the app.css sharing discipline. No container queries here —
   this card is ordinary block flow, so the bubble sizes to it naturally and
   the T5 collapse (a contained element contributes no intrinsic width) cannot
   recur. Everything wraps: chapter titles and keyword lists are user data and
   can be any length, and the chat column must never scroll sideways. */
/* Same pairing `.salon-card-recap` and `.has-panel-roster` already need: the
   bubble is shrink-to-fit inside `.message-row`, so without a definite width
   the change list wraps tight around its longest line and reads as a narrow
   column with a lot of empty pane beside it. Measured at 277px in a 760px
   pane AND in a 430px pane — the give-away that it was ignoring the column
   entirely. `.bubble`'s own max-width: min(760px, 86%) still caps it. */
.bubble.salon-card-topology_revision {
  width: 100%;
}
.topo-diff {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
  max-width: 100%;
}
.topo-diff-order {
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 7px 11px;
}
.topo-diff-order-label {
  color: var(--muted);
  font-size: 12.5px;
  margin-bottom: 3px;
}
.topo-diff-order-seq {
  display: flex;
  flex-wrap: wrap;
  font-size: 13.5px;
  gap: 2px 16px;
  line-height: 1.6;
  overflow-wrap: anywhere;
}
.topo-diff-order-step {
  max-width: 100%;
  overflow-wrap: anywhere;
}
.topo-diff-group {
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}
.topo-diff-head {
  align-items: center;
  background: var(--panel-raised);
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 7px 11px;
}
html[data-theme="light"] .topo-diff-head { background: var(--bubble-strong); }
.topo-diff-name {
  font-size: 13.5px;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.topo-diff-logic {
  color: var(--muted);
  font-size: 12px;
}
.topo-diff-chip {
  border: 1px solid;
  border-radius: 4px;
  font-size: 11px;
  padding: 1px 7px;
  white-space: nowrap;
}
.topo-diff-chip-added { border-color: var(--success); color: var(--success); }
.topo-diff-chip-removed { border-color: var(--danger); color: var(--danger); }
.topo-diff-chip-changed { border-color: var(--accent-warm); color: var(--accent-warm); }
.topo-diff-list {
  list-style: none;
  margin: 0;
  padding: 5px 11px 7px;
}
.topo-diff-list li {
  display: flex;
  font-size: 13px;
  gap: 9px;
  line-height: 1.55;
  padding: 2px 0;
}
.topo-diff-list li > span:last-child { overflow-wrap: anywhere; }
.topo-diff-glyph {
  flex: none;
  font-weight: 700;
  text-align: center;
  width: 1.1em;
}
.topo-diff-add { color: var(--success); }
.topo-diff-del { color: var(--danger); }
.topo-diff-edge { color: var(--accent-2); }
.topo-diff-mod { color: var(--accent-warm); }
/* Removed things stay legible rather than being greyed to the edge of
   readability: the user has to be able to check WHAT was dropped. */
.topo-diff-struck {
  opacity: 0.75;
  text-decoration: line-through;
}
.topo-diff-untouched {
  border-top: 1px dashed var(--line);
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.6;
  overflow-wrap: anywhere;
  padding-top: 7px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   T2 · 报告助手 + 报告视图                          (K2 block, 2026-08-12)
   Report assistant (left column, report phase) and the diff marks it leaves in
   the editable report. Appended as one block so K1/K3 edits elsewhere in this
   file and K2 edits do not braid together in the same hunks.
   Colour tokens: --edit-old / --edit-new / --edit-added / --edit-removed,
   defined in BOTH :root and html[data-theme="light"] at the top of this file.
   ══════════════════════════════════════════════════════════════════════════ */

/* The UA stylesheet's `[hidden] { display: none }` is a plain type-less rule
   and LOSES to `.composer { display: flex }`. Without this the pipeline's own
   composer stays on screen underneath the assistant's — two send buttons in one
   column with nothing to say which of them talks to what. Caught in a
   screenshot, not by a boolean: `el.form.hidden = true` was already true.
   `.messages` has no display of its own and does not need the rule; it is here
   so the pair cannot drift if one ever gains one. */
.composer[hidden],
.messages[hidden] { display: none; }

/* ── the two-view switch, always visible in the report phase ───────────── */

.report-view-switch {
  align-items: center;
  border-bottom: 1px solid var(--line);
  display: flex;
  gap: 4px;
  margin: 0 22px;
  padding: 8px 0;
}
.report-view-switch[hidden] { display: none; }
.report-view-tab {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  height: 28px;
  padding: 0 12px;
}
.report-view-tab:hover { color: var(--text); }
.report-view-tab[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--accent);
}
.report-view-switch .report-agent-icon { margin-left: auto; }

/* ── the panel that replaces 流程记录 ───────────────────────────────────── */

.report-agent-panel {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  padding: 0 22px;
}
.report-agent-panel[hidden] { display: none; }

.report-agent-head {
  align-items: center;
  border-bottom: 1px solid var(--line);
  display: flex;
  gap: 10px;
  padding: 10px 2px;
}
.report-agent-title {
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
}
.report-agent-head-actions {
  align-items: center;
  display: flex;
  gap: 6px;
  margin-left: auto;
}
/* Tier switch and the way back to 流程记录. Same 26px icon button the side
   chat uses, so a user who has met one already knows this one. */
.report-agent-icon {
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  height: 26px;
  line-height: 1;
  width: 26px;
}
.report-agent-icon:hover { border-color: var(--line); color: var(--text); }
.report-agent-icon.is-on {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--accent);
}
.report-agent-flow {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  height: 26px;
  padding: 0 10px;
}
.report-agent-flow:hover { border-color: var(--line-strong); color: var(--text); }

.report-agent-log {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
  padding: 14px 2px;
}
.report-agent-msg {
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.65;
  padding: 9px 12px;
  white-space: pre-wrap;
  word-break: break-word;
}
.report-agent-msg.user {
  align-self: flex-end;
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  color: var(--text);
  max-width: 84%;
}
.report-agent-msg.assistant {
  align-self: flex-start;
  background: var(--bubble);
  border: 1px solid var(--line);
  color: var(--text);
  max-width: 92%;
}
.report-agent-msg.notice {
  align-self: center;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  max-width: 96%;
  text-align: center;
}
.report-agent-msg.pending { color: var(--muted); }
/* The receipt on a turn that changed the report: chrome, not the answer. */
.report-agent-receipt {
  color: var(--edit-new);
  font-size: 12px;
  margin-bottom: 4px;
}

.report-agent-tier {
  color: var(--muted);
  font-size: 11px;
  margin-top: 4px;
}

.report-agent-composer {
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 2px 14px;
}
/* T1: the combined chip sits under the input and shares the row with the
   context ring and Send, so the textarea keeps the full width it had before
   the selector existed. Send takes the leftover space; the ring stays
   beside the chip, not centred. */
.report-agent-toolbar {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.report-agent-picker { position: relative; }
#report-agent-choice {
  align-items: center;
  background: var(--bubble);
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  font-family: inherit;
  font-size: 12.5px;
  gap: 6px;
  padding: 6px 10px 6px 12px;
}
#report-agent-choice:hover,
#report-agent-choice[aria-expanded="true"] { border-color: var(--accent); }
#report-agent-choice:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
#report-agent-choice:disabled { cursor: default; opacity: 0.55; }
.report-agent-choice-chev { color: var(--muted); font-size: 10px; }
.report-agent-choice-menu {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 12px;
  bottom: calc(100% + 8px);
  box-shadow: var(--shadow);
  left: 0;
  min-width: 240px;
  padding: 6px;
  position: absolute;
  z-index: 5;
}
.report-agent-choice-row,
.report-agent-choice-opt,
.report-agent-choice-back {
  align-items: center;
  background: transparent;
  border: 0;
  border-radius: 8px;
  color: var(--text);
  cursor: pointer;
  display: flex;
  font: inherit;
  font-size: 13px;
  gap: 8px;
  justify-content: space-between;
  padding: 9px 10px;
  width: 100%;
}
.report-agent-choice-row:hover,
.report-agent-choice-opt:hover,
.report-agent-choice-back:hover { background: var(--accent-soft); }
.report-agent-choice-row .val { color: var(--muted); }
.report-agent-choice-back {
  color: var(--muted);
  font-size: 12px;
  justify-content: flex-start;
}
.report-agent-choice-opt[aria-checked="true"] { font-weight: 650; }
.report-agent-choice-opt .tick { color: var(--accent); width: 14px; }
.report-agent-toolbar #report-agent-send { margin-left: auto; }
.report-agent-composer textarea {
  background: var(--bubble);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text);
  flex: 1 1 auto;
  font-family: inherit;
  font-size: 13px;
  line-height: 1.6;
  max-height: 140px;
  min-height: 38px;
  padding: 9px 12px;
  resize: none;
}
.report-agent-composer textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
.report-agent-composer #report-agent-send {
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  color: var(--accent);
  cursor: pointer;
  font-size: 13px;
  height: 38px;
  padding: 0 14px;
}
.report-agent-composer #report-agent-send:disabled,
#report-agent-choice:disabled { cursor: default; opacity: 0.55; }

/* T5: context ring beside the model-effort chip; the breakdown is a popover. */
.report-agent-context-wrap { position: relative; }
.report-agent-context-ring {
  align-items: center;
  background: transparent;
  border: 0;
  cursor: pointer;
  display: inline-flex;
  height: 32px;
  justify-content: center;
  padding: 0;
  width: 32px;
}
.report-agent-context-ring:focus-visible {
  border-radius: 50%;
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
.report-agent-context-track { stroke: var(--line); }
#report-agent-context-arc { stroke: var(--accent); }
/* T8-a: the report door. The same 32px box as the ring beside it; pressed
   means the report is in the pane. */
.report-agent-report-toggle {
  align-items: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  height: 32px;
  justify-content: center;
  padding: 0;
  width: 32px;
}
.report-agent-report-toggle:hover { border-color: var(--line); color: var(--text); }
.report-agent-report-toggle[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.report-agent-report-toggle:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}
.report-agent-report-toggle svg { display: block; }
.report-agent-context-pop {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 12px;
  bottom: calc(100% + 8px);
  box-shadow: var(--shadow);
  font-size: 12px;
  left: 0;
  padding: 12px 14px 10px;
  position: absolute;
  width: 300px;
  z-index: 6;
}
#report-agent-context-title {
  color: var(--text);
  font-weight: 650;
  margin: 0;
}
.report-agent-context-head {
  align-items: center;
  display: flex;
  gap: 8px;
  justify-content: space-between;
  margin: 0 0 8px;
}
.report-agent-context-info {
  align-items: center;
  background: none;
  border: 0;
  border-radius: 50%;
  color: var(--muted);
  cursor: help;
  display: inline-flex;
  flex: 0 0 auto;
  height: 20px;
  justify-content: center;
  margin: -2px -4px 0 0;
  padding: 0;
  position: relative;
  width: 20px;
}
.report-agent-context-info svg { display: block; height: 15px; width: 15px; }
.report-agent-context-info:hover,
.report-agent-context-info:focus-visible { color: var(--accent); outline: none; }
.report-agent-context-info:focus-visible { box-shadow: 0 0 0 2px var(--accent-soft); }
.report-agent-context-tip {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--shadow);
  color: var(--text);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.6;
  opacity: 0;
  padding: 8px 10px;
  pointer-events: none;
  position: absolute;
  right: 0;
  text-align: left;
  top: calc(100% + 6px);
  transform: translateY(-2px);
  transition: opacity .12s ease, transform .12s ease;
  visibility: hidden;
  width: 236px;
  z-index: 7;
}
.report-agent-context-tip::before {
  background: var(--panel-solid);
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  content: "";
  height: 8px;
  position: absolute;
  right: 5px;
  top: -5px;
  transform: rotate(45deg);
  width: 8px;
}
.report-agent-context-info:hover .report-agent-context-tip,
.report-agent-context-info:focus-visible .report-agent-context-tip {
  opacity: 1;
  transform: none;
  visibility: visible;
}

/* ── the diff marks inside the editable report ─────────────────────────── */

/* Every one of these carries data-agent-diff, and markdownFromEditable keys its
   skip rule on THAT attribute rather than on contenteditable="false" — the
   attribute states the intent, and the other could be borrowed later by some
   unrelated feature and drag the rule along with it. */
[data-agent-diff] { user-select: none; }
[data-agent-diff="new"] { user-select: text; }

.agent-edit {
  border-left: 2px solid var(--accent);
  margin-left: -12px;
  padding-left: 10px;
}
.agent-edit.is-added { border-left-color: var(--accent-3); }
.agent-edit.is-removed { border-left-color: var(--danger); }
/* §2.5: the user typed over the assistant paragraph, so undo can no longer put
   back what was there without eating their own edit along with it. */
.agent-edit.is-stale { border-left-color: var(--accent-warm); }

.agent-edit-label {
  align-items: center;
  color: var(--muted);
  display: flex;
  font-size: 11px;
  gap: 8px;
  letter-spacing: 0.02em;
  margin-bottom: 2px;
}
.agent-edit-undo {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
  line-height: 1;
  padding: 3px 7px;
}
.agent-edit-undo:hover { border-color: var(--line-strong); color: var(--text); }

.agent-edit-old {
  color: var(--edit-old);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
/* Inline (§2.2, ratio <= 0.4): `~~was~~ → now` on the label line. Only the
   fragment that actually changed, and clearly outside the body text — a struck
   word sitting inside the paragraph reads as a typo rather than as history. */
.agent-edit-old.is-inline { margin-right: 0; }
.agent-edit-arrow { color: var(--muted); }
.agent-edit-label .agent-edit-old,
.agent-edit-label .agent-edit-new,
.agent-edit-arrow { font-size: 11px; }
/* Stacked: a whole rewritten paragraph, struck through above the new one. */
.agent-edit-old.is-stacked {
  display: block;
  margin-bottom: 4px;
}
.agent-edit-new { color: var(--edit-new); }
.agent-edit.is-added .agent-edit-new { color: var(--edit-added); }
.agent-edit.is-removed .agent-edit-old { color: var(--edit-removed); }

/* ── the floating selection toolbar (M4) ───────────────────────────────── */

/* Floating and transient (K2-D9): a fixed button parked next to the report
   would be permanent furniture for an occasional action. */
.report-selection-bar {
  background: var(--panel-solid);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  box-shadow: var(--shadow);
  display: flex;
  gap: 4px;
  padding: 4px;
  position: fixed;
  z-index: 60;
}
.report-selection-bar[hidden] { display: none; }
.report-selection-bar button {
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  font-size: 12px;
  padding: 5px 10px;
  white-space: nowrap;
}
.report-selection-bar button:hover { background: var(--accent-soft); color: var(--accent); }

/* The quote the toolbar pushed into the composer. */
.report-agent-quote {
  border-left: 2px solid var(--line-strong);
  color: var(--muted);
  /* The log is the flexible, scrollable item in this column.  The quote is
     composer chrome: allowing its default flex-shrink:1 made a tight window
     squeeze the full text into an 8px strip before overflow hid the evidence. */
  flex: 0 0 auto;
  font-size: 12px;
  line-height: 1.5;
  margin: 0 2px 6px;
  overflow: hidden;
  padding: 4px 22px 4px 8px;
  position: relative;
}
.report-agent-quote[hidden] { display: none; }
.report-agent-quote > div {
  /* Keep long selections available without making the composer unbounded.
     The scroll belongs to the text, so the remove button stays fixed. */
  max-height: 76px;
  overflow-wrap: anywhere;
  overflow-y: auto;
  white-space: pre-wrap;
}
.report-agent-quote button {
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  position: absolute;
  right: 0;
  top: 2px;
}
.report-agent-quote button:hover { color: var(--text); }

/* ── report-mode preview actions ───────────────────────────────────────── */

.preview-save {
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  color: var(--accent);
  cursor: pointer;
  font-size: 12px;
  height: 28px;
  padding: 0 12px;
}
.preview-save:disabled { cursor: default; opacity: 0.5; }
.preview-download {
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  height: 28px;
  padding: 0 12px;
}
.preview-download:hover { border-color: var(--line-strong); color: var(--text); }
.preview-save-note {
  color: var(--muted);
  font-size: 11px;
  margin-right: 4px;
}
.preview-save-note.is-error { color: var(--danger); }

/* ═════════════════════ end T2 report block (K2) ═════════════════════════ */
/* ════════════════════════════════════════════════════════════════════════════
   T3 · account knowledge base (K1). Appended, not interleaved.
   Palette, radii and card shape all come from the tokens above and from
   .document-card — the library must read as the same product, so nothing here
   introduces a color or a shape the app does not already use.
   ══════════════════════════════════════════════════════════════════════════ */
.kb-library-button { display: flex; align-items: center; gap: 8px; justify-content: center; }
/* UIB-2: NOT `margin-left: auto`. An auto margin eats every pixel of free
   space -- including when this span is empty, which is most of the time -- and
   that is what held the label against the left edge while its two neighbours
   in the sidebar (API 密钥, 新建会话) sat centred. The count rides along with
   the label as one centred group instead of being pinned to the far edge. */
.kb-library-count { color: var(--muted); font-size: 11px; }

.kb-import-button {
  align-items: center; background: rgba(12, 23, 40, 0.88);
  border: 1px solid var(--line-strong); border-radius: 999px; color: var(--text);
  cursor: pointer; display: inline-flex; gap: 6px; min-height: 28px; padding: 0 11px;
}
.kb-import-button:hover { border-color: var(--accent); color: var(--accent); }
.kb-import-button[hidden] { display: none; }

/* Composes with .modal-panel, so it inherits that block's hard-won
   max-height/overflow contract (a panel taller than the viewport used to spill
   out of both ends with no way to reach its buttons). Only the width, the
   padding and the internal scroll boundary change: the head and foot stay put
   while the grid scrolls. */
.kb-panel {
  display: flex; flex-direction: column; max-width: 100%; overflow: hidden;
  padding: 0; width: min(880px, 100%);
}
.kb-head { border-bottom: 1px solid var(--line); flex: none; padding: 18px 20px 14px; }
.kb-head-row { align-items: baseline; display: flex; gap: 12px; }
.kb-title { font-size: 17px; font-weight: 800; }
.kb-close {
  background: transparent; border: none; color: var(--muted); cursor: pointer;
  font-size: 20px; line-height: 1; margin-left: auto; padding: 0 2px;
}
.kb-close:hover { color: var(--text); }
.kb-sub { color: var(--muted); font-size: 12px; margin-top: 5px; }
.kb-sub .kb-hot { color: var(--success); }
.kb-tools { align-items: center; display: flex; gap: 10px; margin-top: 12px; }
.kb-filter {
  background: rgba(5, 11, 20, 0.9); border: 1px solid var(--line); border-radius: 8px;
  color: var(--text); font-size: 12.5px; max-width: 260px; min-height: 30px;
  padding: 0 11px; width: 100%;
}
.kb-filter::placeholder { color: var(--muted); }
.kb-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 16px 20px; }
.kb-grid { display: grid; gap: 9px; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); }

.kb-card {
  background: var(--panel-raised); border: 1px solid var(--line); border-radius: 10px;
  color: var(--text); cursor: pointer; min-height: 76px; padding: 9px 10px;
  position: relative; text-align: left;
}
.kb-card:hover { border-color: var(--accent); }
.kb-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-name {
  -webkit-box-orient: vertical; -webkit-line-clamp: 2; display: -webkit-box;
  font-size: 12px; font-weight: 600; line-height: 1.35; overflow: hidden; padding-right: 30px;
}
.kb-meta { color: var(--muted); display: flex; font-size: 10px; gap: 6px; margin-top: 6px; }
/* The third line says something different in each state. The DEFAULT state is
   where it carries information (parse date + how many sessions hold it); a
   label reading "available" on every card would be a word that, because
   everything says it, says nothing. */
.kb-rel { font-size: 10px; margin-top: 4px; }
.kb-rel.is-facts { color: var(--muted); font-variant-numeric: tabular-nums; }

.kb-card.is-here { border-left: 2px solid var(--success); cursor: default; opacity: 0.62; }
.kb-card.is-here:hover { border-color: var(--line); border-left-color: var(--success); }
.kb-card.is-here .kb-rel { color: var(--success); }
.kb-card.is-picked { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.kb-card.is-picked .kb-rel { color: var(--accent); }
/* Quota is a state of the SESSION, not a property of a document: said once in
   the footer, and here it only takes the cards out of reach. Repeated per card
   it would also read as "something is wrong with this paper". */
.kb-grid.is-locked .kb-card:not(.is-here) { cursor: default; opacity: 0.5; }
.kb-grid.is-locked .kb-card:hover { border-color: var(--line); }
.kb-grid.is-locked .kb-corner { display: none; }

.kb-corner {
  align-items: center; background: rgba(5, 11, 20, 0.78); border: 1px solid var(--line);
  border-radius: 7px; color: var(--muted); display: flex; font-size: 12px; height: 22px;
  justify-content: center; position: absolute; right: 8px; top: 8px; width: 22px;
}
.kb-card:hover .kb-corner { border-color: var(--accent); color: var(--accent); }
.kb-card.is-picked .kb-corner {
  background: var(--accent); border-color: var(--accent); color: #021018; font-weight: 800;
}
/* Hover-only, exactly like .document-card-delete in a session. Consistency
   wins over discoverability here: the same gesture in both places. */
.kb-corner.is-trash { border-color: transparent; opacity: 0; }
.kb-card:hover .kb-corner.is-trash,
.kb-corner.is-trash:focus-visible { border-color: var(--danger); color: var(--danger); opacity: 1; }

/* The destructive step happens ON the document. This is the only delete in the
   product that bypasses the recycle bin, and a generic confirm dialog is
   precisely what would hide that. The NAME STAYS: being able to see which of a
   dozen similar filenames you are about to lose is the whole point. */
.kb-card.is-arming {
  background: linear-gradient(180deg, rgba(255, 100, 124, 0.14) 0%, var(--panel-raised) 62%);
  border-color: var(--danger); cursor: default;
}
.kb-arm-name {
  -webkit-box-orient: vertical; -webkit-line-clamp: 1; color: var(--muted);
  display: -webkit-box; font-size: 11px; overflow: hidden;
}
.kb-arm-title { color: var(--danger); font-size: 11.5px; font-weight: 800; margin-top: 3px; }
.kb-arm-copy { color: var(--text); font-size: 10.5px; line-height: 1.5; margin-top: 4px; opacity: 0.86; }
.kb-arm-actions { display: flex; gap: 6px; margin-top: 8px; }
.kb-arm-actions button {
  background: rgba(12, 23, 40, 0.9); border: 1px solid var(--line); border-radius: 7px;
  color: var(--text); cursor: pointer; font-size: 10.5px; min-height: 24px; padding: 0 9px;
}
.kb-arm-actions .kb-arm-go {
  background: rgba(255, 100, 124, 0.2); border-color: var(--danger); color: #ffd7de; font-weight: 700;
}

.kb-empty { padding: 30px 8px 34px; text-align: center; }
.kb-empty-mark { color: var(--line-strong); font-size: 26px; }
.kb-empty-title { font-size: 13.5px; font-weight: 700; margin-top: 10px; }
.kb-empty-copy { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 6px auto 0; max-width: 380px; }

.kb-foot {
  align-items: center; border-top: 1px solid var(--line); display: flex; flex: none;
  gap: 10px; padding: 13px 20px;
}
.kb-foot[hidden] { display: none; }
.kb-foot-count { color: var(--muted); font-size: 12px; }
.kb-foot-count b { color: var(--accent); }
.kb-foot-count.is-full { color: var(--accent-warm); }
.kb-foot-actions { display: flex; gap: 9px; margin-left: auto; }
.kb-btn {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
  border: 1px solid rgba(143, 236, 255, 0.7); border-radius: 8px; color: #021018;
  cursor: pointer; font-weight: 800; min-height: 34px; padding: 0 14px;
}
.kb-btn.secondary { background: rgba(12, 23, 40, 0.88); border-color: var(--line); color: var(--text); }
.kb-btn:disabled { cursor: default; opacity: 0.55; }

@media (max-width: 640px) {
  .kb-grid { grid-template-columns: 1fr 1fr; }
}

/* ── T3 in day mode ────────────────────────────────────────────────────────
   This block was appended AFTER the html[data-theme="light"] section, so none
   of its hardcoded dark surfaces were ever translated: the 导入历史资料 pill and
   the 按文件名筛选 box stayed near-black on a beige page. Everything else in T3
   is token-driven and followed the theme on its own, so only the surfaces that
   were spelled as literal colours appear below.

   #f3efe6 is not a new colour — it is the exact ground the light block already
   gives every other secondary button (.api-key-button, .trash-button, …), so
   the library reads as the same product in both themes. */
html[data-theme="light"] .kb-import-button,
html[data-theme="light"] .kb-filter,
html[data-theme="light"] .kb-corner,
html[data-theme="light"] .kb-arm-actions button,
html[data-theme="light"] .kb-btn.secondary {
  background: #f3efe6;
}
/* Pale pink ink on a pale pink wash is the one pair here that does not survive
   a background swap — on beige it disappears entirely. The danger hue carries
   the meaning instead, which is what it does everywhere else in this theme. */
html[data-theme="light"] .kb-arm-actions .kb-arm-go {
  background: rgba(207, 66, 87, 0.14);
  color: var(--danger);
}
/* Primary button: white ink on the darkened teal→purple, following the
   "primary (accent-gradient) buttons" rule in the light block. The pale-cyan
   rim was drawn for a dark ground and reads as a halo on beige. */
html[data-theme="light"] .kb-btn {
  border-color: var(--accent);
  color: #ffffff;
}


/* ── the writer-restart dialog's option rows (2026-08-19) ──────────────────
   Each option is a button plus an (i) that opens the app's existing hover card
   -- the same one the side chat's notes use, so this introduces no second way
   of showing an explanation. Every colour is a token, so day mode needs no
   override here. */
.writer-reuse-actions { align-items: center; flex-wrap: wrap; gap: 10px; }
.reuse-option { align-items: center; display: flex; gap: 6px; }
.reuse-option[hidden] { display: none; }
.reuse-hint {
  align-items: center; border: 1px solid var(--line-strong); border-radius: 50%;
  color: var(--muted); cursor: help; display: inline-flex; flex: none;
  font-family: Georgia, "Times New Roman", serif; font-size: 12px;
  font-style: italic; font-weight: 700; height: 18px; justify-content: center;
  line-height: 1; user-select: none; width: 18px;
}
.reuse-hint:hover,
.reuse-hint:focus-visible { border-color: var(--accent); color: var(--accent); }

/* T3: the running turn's finished tool calls, under the pending row. */
.report-agent-steps {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
}
.report-agent-steps li {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.7;
}
.report-agent-steps li::before {
  content: "· ";
}
.report-agent-step-risk {
  color: var(--danger);
  font-size: 11px;
  margin-left: 4px;
}
/* 2026-08-20: the same list, pinned to the landed answer behind a fold --
   open on the latest turn, folded on the history (renderReportAgentMessage). */
.report-agent-steps-wrap {
  margin-bottom: 4px;
}
.report-agent-steps-wrap > summary {
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  list-style: none;
  user-select: none;
}
.report-agent-steps-wrap > summary::-webkit-details-marker { display: none; }
.report-agent-steps-wrap > summary::before { content: "▸ "; }
.report-agent-steps-wrap[open] > summary::before { content: "▾ "; }
.report-agent-steps-wrap .report-agent-steps { margin-top: 2px; }

.report-agent-context-bar {
  background: var(--bubble);
  border-radius: 4px;
  display: flex;
  height: 8px;
  margin: 8px 0 6px;
  overflow: hidden;
}
.report-agent-context-bar span {
  display: block;
  height: 100%;
}
/* T8-b: the bar is the limit; the track is what is free. */
.report-agent-context-bar.is-scaled {
  background: var(--free);
  border: 1px solid var(--line);
  height: 10px;
}
.report-agent-context-ticks {
  color: var(--muted);
  display: flex;
  font-size: 10.5px;
  justify-content: space-between;
  margin: -2px 0 4px;
}
.report-agent-context-row.free {
  border-top: 1px dashed var(--line);
  margin-top: 4px;
  padding-top: 4px;
}
.report-agent-context-row .swatch.free {
  background: var(--free);
  border: 1px solid var(--line);
}
.report-agent-context-row {
  color: var(--muted);
  display: flex;
  gap: 8px;
  justify-content: space-between;
  line-height: 1.8;
}
.report-agent-context-row .swatch {
  border-radius: 2px;
  display: inline-block;
  height: 8px;
  margin-right: 6px;
  width: 8px;
}

/* T7: the secretary's face on her own bubbles only. */
.report-agent-avatar {
  border-radius: 50%;
  float: left;
  height: 24px;
  margin: 1px 8px 2px 0;
  width: 24px;
}

/* ── writer 轨 M4b (2026-08-26): background-tasks panel ─────────────────── */

.progress-label-row {
  align-items: center;
  display: flex;
  gap: 10px;
}

.bg-tasks-btn {
  align-items: center;
  background: var(--accent-soft);
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--accent);
  cursor: pointer;
  display: inline-flex;
  flex: none;
  font-size: 12px;
  gap: 5px;
  margin-left: auto;
  padding: 2px 10px;
  transition: background 0.15s;
}

.bg-tasks-btn:hover {
  background: rgba(73, 223, 255, 0.28);
}

.writer-tasks-shell {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.writer-tasks-summary {
  color: var(--muted);
  font-size: 12.5px;
  min-height: 16px;
}

.writer-tasks-grid {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

.writer-tasks-empty {
  border: 1px dashed var(--line);
  border-radius: 12px;
  color: var(--muted);
  font-size: 13px;
  grid-column: 1 / -1;
  line-height: 1.7;
  padding: 18px;
}

.writer-task-card {
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-radius: 12px;
  cursor: pointer;
  padding: 12px 14px;
  transition: border-color 0.15s;
}

.writer-task-card:hover {
  border-color: var(--line-strong);
}

.writer-task-card.expanded {
  border-color: var(--line-strong);
  grid-column: 1 / -1;
}

.writer-task-head {
  align-items: center;
  display: flex;
  gap: 10px;
}

.writer-task-title {
  flex: 1;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.4;
}

.writer-task-chip {
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--muted);
  flex: none;
  font-size: 11px;
  padding: 2px 9px;
  white-space: nowrap;
}

.writer-task-chip.running,
.writer-task-chip.tool {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--accent);
}

.writer-task-chip.retry {
  background: rgba(245, 201, 93, 0.08);
  border-color: rgba(245, 201, 93, 0.45);
  color: var(--accent-warm);
}

.writer-task-chip.done {
  background: rgba(72, 242, 164, 0.08);
  border-color: rgba(72, 242, 164, 0.4);
  color: var(--success);
}

.writer-task-chip.failed {
  background: rgba(255, 100, 124, 0.08);
  border-color: rgba(255, 100, 124, 0.45);
  color: var(--danger);
}

.writer-task-now {
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.6;
  margin-top: 8px;
}

.writer-task-meta {
  color: var(--muted);
  display: flex;
  font-size: 11.5px;
  gap: 14px;
  margin-top: 8px;
}

.writer-task-hint {
  color: var(--accent);
  margin-left: auto;
}

.writer-task-timeline {
  border-top: 1px solid var(--line);
  margin-top: 10px;
  padding-top: 8px;
}

.writer-task-step {
  display: flex;
  font-size: 12.5px;
  gap: 12px;
  line-height: 1.65;
  padding: 4px 0;
}

.writer-task-ts {
  color: var(--muted);
  flex: none;
  font-family: Consolas, monospace;
  font-size: 11px;
  padding-top: 2px;
  width: 60px;
}

/* ── writer 轨 M5 (2026-08-26): evidence panel ── */

.evidence-panel {
  background: var(--panel-raised);
  border-left: 1px solid var(--line-strong);
  bottom: 0;
  box-shadow: -18px 0 50px rgba(0, 0, 0, 0.45);
  display: flex;
  flex-direction: column;
  max-width: 92%;
  overflow-y: auto;
  position: absolute;
  right: 0;
  top: 0;
  transform: translateX(105%);
  transition: transform 0.22s ease;
  width: 420px;
  z-index: 40;
}

.evidence-panel > * {
  flex-shrink: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

.evidence-panel.open {
  transform: translateX(0);
}

.evidence-header {
  align-items: center;
  border-bottom: 1px solid var(--line);
  display: flex;
  justify-content: space-between;
  padding: 14px 18px 10px;
}

.evidence-kicker {
  color: var(--accent);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.evidence-close {
  background: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  height: 26px;
  width: 26px;
}

.evidence-quote-block {
  background: var(--bubble);
  border-left: 3px solid var(--accent);
  border-radius: 0 8px 8px 0;
  color: var(--text);
  font-size: 13px;
  line-height: 1.7;
  margin: 12px 18px 0;
  padding: 10px 12px;
}

.evidence-status {
  display: flex;
  gap: 8px;
  margin: 10px 18px 0;
}

.evidence-chip {
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 12px;
  padding: 2px 10px;
}

.evidence-chip.grounded { background: rgba(72, 242, 164, 0.08); border-color: rgba(72, 242, 164, 0.4); color: var(--success); }
.evidence-chip.self { background: rgba(156, 124, 255, 0.08); border-color: rgba(156, 124, 255, 0.4); color: var(--accent-2); }
.evidence-chip.disputed { background: rgba(255, 100, 124, 0.08); border-color: rgba(255, 100, 124, 0.45); color: var(--danger); }
.evidence-chip.stale,
.evidence-chip.unannotated { background: rgba(245, 201, 93, 0.08); border-color: rgba(245, 201, 93, 0.45); color: var(--accent-warm); }

.evidence-warn {
  border: 1px solid rgba(255, 100, 124, 0.45);
  background: rgba(255, 100, 124, 0.08);
  border-radius: 10px;
  color: var(--text);
  font-size: 12.5px;
  line-height: 1.7;
  margin: 10px 18px 0;
  padding: 10px 12px;
}

.evidence-warn.stale {
  background: rgba(245, 201, 93, 0.07);
  border-color: rgba(245, 201, 93, 0.45);
  color: var(--text);
}

.evidence-self {
  border: 1px solid rgba(156, 124, 255, 0.35);
  background: rgba(156, 124, 255, 0.06);
  border-radius: 12px;
  color: var(--text);
  font-size: 13px;
  line-height: 1.8;
  margin: 10px 18px 0;
  padding: 12px 14px;
}

.evidence-card {
  background: var(--panel-solid);
  border: 1px solid var(--line);
  border-radius: 12px;
  margin: 12px 18px 0;
  padding: 12px 14px;
}

.evidence-head {
  align-items: center;
  display: flex;
  gap: 8px;
  justify-content: space-between;
}

.evidence-kind {
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 2px 8px;
}

.evidence-kind.kb { background: var(--accent-soft); color: var(--accent); }
.evidence-kind.web { background: rgba(72, 242, 164, 0.12); color: var(--accent-3); }
.evidence-kind.record { background: rgba(156, 124, 255, 0.14); color: var(--accent-2); }

.evidence-score { color: var(--muted); font-size: 11px; }

.evidence-source {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
  margin-top: 8px;
}

.evidence-quote {
  background: var(--bubble);
  border: 1px dashed var(--line);
  border-radius: 8px;
  color: var(--text);
  font-size: 12.5px;
  line-height: 1.75;
  margin-top: 8px;
  padding: 8px 10px;
}

.evidence-query { color: var(--muted); font-size: 11.5px; margin-top: 8px; }
.evidence-query code {
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-radius: 5px;
  color: var(--accent);
  font-size: 11px;
  padding: 1px 6px;
}

.evidence-link { margin-top: 8px; }
.evidence-link a { color: var(--accent); font-size: 12px; text-decoration: none; }

.evidence-empty {
  border: 1px dashed var(--line);
  border-radius: 12px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.7;
  margin: 12px 18px;
  padding: 14px;
}

.evidence-ref {
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.7;
  margin: 10px 18px 0;
  padding: 10px 12px;
}

.evidence-ref.hit {
  background: var(--accent-soft);
  border-color: var(--line-strong);
  color: var(--text);
}

.evidence-ref-no { color: var(--accent); font-weight: 700; margin-right: 6px; }

/* (4) M2: a paper citation inside an expert's sentence. Sized to sit inline
   in running prose — a full-height button would break the line rhythm of a
   speech bubble. No `display` declaration: a <button> is blockified to
   inline-block by the UA whatever we ask for (measured in-page 2026-08-27),
   and inline-block is the behaviour we want anyway — a citation should not
   break across two lines. */
.paper-chip {
  padding: 0 4px;
  margin: 0 1px;
  border: none;
  border-bottom: 1px dashed var(--accent);
  border-radius: 3px;
  background: transparent;
  color: var(--accent);
  font: inherit;
  line-height: inherit;
  cursor: pointer;
}
.paper-chip:hover,
.paper-chip:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); }

/* AD10 (writer M11b): a statistics-born figure is highlighted online only. */
.preview-figure img.figure-origin-badge,
img.preview-inline-figure.figure-origin-badge {
  outline: 2px solid rgba(72, 242, 164, 0.55);
  outline-offset: 3px;
  border-radius: 6px;
}
.preview-figure[data-origin-label] {
  position: relative;
}
.preview-figure[data-origin-label]::after {
  content: attr(data-origin-label);
  position: absolute;
  top: 8px;
  right: 8px;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(72, 242, 164, 0.14);
  border: 1px solid rgba(72, 242, 164, 0.5);
  color: var(--success);
  pointer-events: none;
}

/* ── T-WR-7 (MD27 / MD31): the session background-tasks card ──────────────
   Sits directly above the composer and appears only while the session has
   work running async to the chat. Rows are the same shape as the approved
   mockup (writer/mockups/2026-09-13-session-background-tasks.html). */
.background-tasks-card {
  margin: 0 0 8px;
}
.bgcard {
  border: 1px solid var(--line-strong);
  border-radius: 14px;
  background: var(--panel-raised);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.bgcard-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px;
  cursor: pointer;
  user-select: none;
}
.bgcard-head:hover {
  background: var(--accent-soft);
}
.bgcard-dot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  flex: none;
  border: 2px solid var(--line-strong);
}
.bgcard-dot.running {
  border-top-color: var(--accent);
  animation: bgcard-spin 1s linear infinite;
}
.bgcard-dot.done {
  border-color: transparent;
  background: var(--success);
}
@keyframes bgcard-spin {
  to { transform: rotate(360deg); }
}
.bgcard-title {
  font-size: 13px;
  font-weight: 600;
}
.bgcard-sub {
  font-size: 12px;
  color: var(--muted);
  margin-left: auto;
}
.bgcard-chev {
  color: var(--muted);
  font-size: 11px;
  transition: transform 0.15s;
}
.bgcard.collapsed .bgcard-chev {
  transform: rotate(-90deg);
}
.bgcard.collapsed .bgcard-rows {
  display: none;
}
.bgcard-rows {
  border-top: 1px solid var(--line);
}
.bgrow {
  display: grid;
  grid-template-columns: 18px 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 9px 14px;
  cursor: pointer;
  border-bottom: 1px solid rgba(122, 155, 194, 0.1);
}
.bgrow:last-child {
  border-bottom: none;
}
.bgrow:hover {
  background: var(--accent-soft);
}
.bgrow-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-left: 5px;
}
.bgrow-dot.running {
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(73, 223, 255, 0.18);
}
.bgrow-dot.queued {
  background: var(--muted);
  opacity: 0.6;
}
.bgrow-dot.done {
  background: var(--success);
}
.bgrow-dot.failed {
  background: var(--danger);
}
.bgrow-label {
  font-size: 13px;
}
.bgrow-detail {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 2px;
}
/* MD31 A2: which part of the main conversation is blocked on this row. */
.bgrow-wait {
  display: inline-block;
  margin-top: 4px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  color: var(--accent-warm);
  background: rgba(245, 201, 93, 0.1);
  border: 1px solid rgba(245, 201, 93, 0.34);
}
.bgrow-right {
  text-align: right;
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
}
.bgrow-count {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.bgrow-meter {
  width: 92px;
  height: 3px;
  border-radius: 2px;
  background: var(--free);
  margin-top: 5px;
}
.bgrow-meter > i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--accent);
}
.bgrow-meter.failed > i {
  background: var(--danger);
}
.bgrow-meter.done > i {
  background: var(--success);
}

/* the detail page in the right pane */
.bgjob-shell {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Whole sentences, not a field table: the pane explains the job to someone
   who has never heard of the pipeline. */
.bgjob-lines {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.bgjob-line {
  margin: 0;
  font-size: 13px;
  line-height: 1.7;
  color: var(--text);
}
.bgjob-line + .bgjob-line {
  color: var(--muted);
}
.bgjob-item {
  display: grid;
  grid-template-columns: 16px 1fr auto;
  gap: 9px;
  align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid rgba(122, 155, 194, 0.1);
  font-size: 12.5px;
}
.bgjob-item:last-child {
  border-bottom: none;
}
.bgjob-item-title {
  color: var(--text);
  line-height: 1.5;
}
.bgjob-item-note {
  color: var(--muted);
  font-size: 11.5px;
  margin-top: 2px;
}
.bgjob-item-right {
  color: var(--muted);
  font-size: 11.5px;
  white-space: nowrap;
}
.bgjob-state.indexed,
.bgjob-state.parsed {
  color: var(--success);
}
.bgjob-state.running {
  color: var(--accent);
}
.bgjob-state.failed {
  color: var(--danger);
}
.bgjob-state.queued {
  color: var(--muted);
}
/* D-W27: failures that share a reason are one block -- the reason once as a
   head, then every title beneath it. Same grid as .bgjob-item so the mark
   column lines up between grouped and ungrouped rows; the titles indent under
   it rather than starting a second column of their own. */
.bgjob-group {
  padding: 7px 0;
  border-bottom: 1px solid rgba(122, 155, 194, 0.1);
  font-size: 12.5px;
}
.bgjob-group:last-child {
  border-bottom: none;
}
.bgjob-group-head {
  display: grid;
  grid-template-columns: 16px 1fr;
  gap: 9px;
  align-items: baseline;
  color: var(--muted);
}
.bgjob-group-title {
  color: var(--text);
  line-height: 1.5;
  margin-left: 25px;
}
.bgjob-empty {
  color: var(--muted);
  font-size: 13px;
  padding: 24px 0;
  text-align: center;
}

.writer-pause-controls {
  background: var(--panel-raised);
  border: 1px solid var(--line-strong);
  border-radius: 12px;
  margin-bottom: 8px;
  padding: 12px 14px;
  color: var(--text);
}
.writer-pause-controls p { margin: 0 0 10px; line-height: 1.6; }
.writer-pause-controls label { display: block; margin-bottom: 10px; }
.writer-pause-controls input {
  margin-left: 8px;
  width: 90px;
  background: var(--panel-solid);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 4px 6px;
}
.writer-pause-controls button { margin: 0 8px 4px 0; }

.salon-final-recommendations { margin: 10px 0; padding-left: 22px; overflow-wrap: anywhere; }
.salon-final-recommendations li { margin: 8px 0; line-height: 1.6; }
.salon-final-recommendations b { color: var(--muted); font-size: 12px; }

/* Topic requirements: shared theme tokens keep light/dark surfaces consistent. */
.topic-proposal-bubble { min-width: 0; white-space: normal; }
.topic-proposal-intro { margin: 0 0 16px; }
.topic-proposal-field { padding: 14px 0; border-top: 1px solid var(--line); }
.topic-proposal-label { color: var(--muted); font-size: 0.88em; margin-bottom: 8px; }
.topic-proposal-title, .topic-proposal-heading { font-weight: 650; color: var(--text); }
.topic-proposal-title { font-size: 1.12em; }
.topic-proposal-title, .topic-proposal-heading, .topic-proposal-copy, .topic-proposal-idea { white-space: pre-wrap; overflow-wrap: anywhere; }
.topic-proposal-outline { list-style: none; margin: 0; padding: 0; }
.topic-proposal-outline .topic-proposal-outline { margin-top: 12px; padding-left: 18px; border-left: 2px solid var(--line-strong); }
.topic-proposal-chapter + .topic-proposal-chapter { margin-top: 16px; }
.topic-proposal-idea { margin-top: 5px; color: var(--muted); font-size: 0.94em; }
.topic-proposal-notice { color: var(--muted); font-size: 0.9em; margin: 12px 0; }

.bggroup-head { cursor: pointer; padding: 9px 14px; font-weight: 600; border-top: 1px solid var(--line); }
.bggroup-head span { float: right; font-size: 12px; color: var(--muted); }
.bggroup-rows { max-height: 230px; overflow-y: auto; }
.bggroup-empty { margin: 8px 14px 12px; color: var(--muted); font-size: 12px; }
.bgrow:not([data-job-id]) { cursor: default; }
/* Figure provenance and the per-turn tool budget share the existing composer. */
.report-agent-tool-limit { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--muted, #6c706f); white-space:nowrap; }
.report-agent-tool-limit input { width:76px; padding:6px 8px; border:1px solid var(--line); border-radius:8px; background:var(--panel-solid); color:var(--text); font:inherit; }
.report-agent-toolbar { flex-wrap:wrap; }
.evidence-datum { display:flex; flex-wrap:wrap; justify-content:space-between; gap:8px; margin-top:18px; }
.evidence-buckets { white-space:pre-wrap; overflow-wrap:anywhere; font-size:12px; }
