/* ============================================================================
   睡前消息知识库 — solid chatbot palette (ChatGPT / Claude-like).

   Semantic tokens:
     --bg / --surface / --line
     --text / --text-dim / --muted
     --accent   primary action, links, focus
     --accent-2 live / in-progress
   Dark is the token default; [data-theme="light"] overrides.
   Layout breakpoint: 900px — desktop dual-pane vs mobile full-screen stack.
   No manual layout override; width media queries decide automatically.
   ============================================================================ */

:root {
  color-scheme: dark;
  --bg: #212121;
  --bg-rgb: 33, 33, 33;
  --surface: #2f2f2f;
  --surface-2: #303030;
  --line: #3e3e3e;
  --text: #ececec;
  --text-dim: #b4b4b4;
  --muted: #8e8e8e;
  --accent: #10a37f;
  --accent-strong: #1a7f64;
  --accent-rgb: 16, 163, 127;
  --accent-2: #10a37f;
  --accent2-rgb: 16, 163, 127;
  --on-accent: #ffffff;
  --user-bubble: #2f2f2f;
  --max-col: 48rem;
  --font-cjk: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
    "Microsoft YaHei", "Noto Sans SC", "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Menlo", "Consolas", monospace;
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --touch: 44px;
  --tabbar-h: calc(3.25rem + env(safe-area-inset-bottom, 0px));
  --mast-h: 3.25rem;
}

[data-theme="light"] {
  color-scheme: light;
  --bg: #ffffff;
  --bg-rgb: 255, 255, 255;
  --surface: #f4f4f4;
  --surface-2: #ececec;
  --line: #e5e5e5;
  --text: #0d0d0d;
  --text-dim: #5d5d5d;
  --muted: #8f8f8f;
  --accent: #0d8c6c;
  --accent-strong: #0a6b53;
  --accent-rgb: 13, 140, 108;
  --accent-2: #0d8c6c;
  --accent2-rgb: 13, 140, 108;
  --on-accent: #ffffff;
  --user-bubble: #f4f4f4;
}

* {
  box-sizing: border-box;
}

html {
  height: 100%;
  /* pan-only: blocks browser pinch-zoom and double-tap zoom where the viewport
     meta's user-scalable=no is ignored (iOS Safari). Scrolling is unaffected;
     descendants can only narrow this, never widen it. */
  touch-action: pan-x pan-y;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-cjk);
  font-size: 16px;
  line-height: 1.65;
  display: flex;
  flex-direction: column;
  height: 100%;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  z-index: 100;
  background: var(--accent);
  color: var(--on-accent);
  padding: 0.5rem 1rem;
  font-size: 0.8rem;
}

.skip-link:focus {
  left: 0;
}

/* ----------------------------------------------------------------- masthead */
.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: var(--mast-h);
  padding: 0.55rem clamp(0.85rem, 3vw, 1.5rem);
  padding-top: max(0.55rem, env(safe-area-inset-top, 0px));
  background: var(--bg);
  border-bottom: 1px solid var(--line);
}

.masthead-brand {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.6rem;
  color: var(--text);
  text-decoration: none;
}

.app-version {
  white-space: nowrap;
  /* Moved out of .composer-note; keep the same small muted look it had there. */
  font-family: var(--font-cjk);
  font-size: 0.72rem;
  letter-spacing: 0.01em;
  color: var(--muted);
}

.masthead-brand:hover {
  opacity: 0.85;
}

.brand-logo {
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 6px;
  object-fit: cover;
  display: block;
}

.wordmark-cn {
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.02em;
}

.masthead-links {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.mast-link {
  font-size: 0.8rem;
  color: var(--text-dim);
  text-decoration: none;
}

.mast-link:hover,
.mast-link:focus-visible {
  color: var(--text);
}

.mast-icon {
  display: inline-grid;
  place-items: center;
  width: var(--touch);
  height: var(--touch);
  min-width: var(--touch);
  min-height: var(--touch);
  letter-spacing: normal;
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text-dim);
  text-decoration: none;
  box-sizing: border-box;
}

.mast-icon:hover,
.mast-icon:focus-visible {
  color: var(--text);
  border-color: var(--text-dim);
  outline: none;
}

.mast-icon svg {
  display: block;
}

.masthead-right {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 0.35rem;
}

.theme-toggle {
  appearance: none;
  display: inline-grid;
  place-items: center;
  width: var(--touch);
  height: var(--touch);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  color: var(--text-dim);
  padding: 0;
  cursor: pointer;
  box-sizing: border-box;
}

.theme-toggle:hover {
  color: var(--text);
  border-color: var(--text-dim);
}

.theme-toggle .icon {
  display: block;
  grid-area: 1 / 1;
}

/* Dark UI → sun (tap for light). Light UI → moon (tap for dark). */
.theme-toggle .icon-moon {
  display: none;
}

[data-theme="light"] .theme-toggle .icon-sun {
  display: none;
}

[data-theme="light"] .theme-toggle .icon-moon {
  display: block;
}

/* --------------------------------------------------------------- channel bar */
/* Channel sections share the masthead row with the wordmark. Same sans family
   as the rest of the UI — weight and color do the hierarchy, not a second face. */
.channel-bar {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: 0.15rem;
  min-width: 0;
  margin-left: 0.85rem;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

.channel-bar::-webkit-scrollbar {
  display: none;
}

/* [hidden] must beat the flex display above, including any later !important. */
.channel-bar[hidden] {
  display: none !important;
}

.channel-chip {
  appearance: none;
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-family: var(--font-cjk);
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  line-height: 1.2;
  white-space: nowrap;
  min-height: 2rem;
  padding: 0.35rem 0.65rem;
  cursor: pointer;
  text-decoration: none;
}

.channel-chip:hover,
.channel-chip:focus-visible {
  color: var(--text);
}

/* Inset rather than a border, so lighting a section cannot nudge the row. */
.channel-chip[aria-current="true"] {
  color: var(--text);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* -------------------------------------------------------------------- stage */
.stage {
  flex: 1;
  width: 100%;
  max-width: var(--max-col);
  margin: 0 auto;
  padding: clamp(1.5rem, 4vw, 3rem) clamp(1rem, 4vw, 2rem) 2rem;
}

/* ---------------------------------------------------------------------- hero */
.hero {
  padding-top: clamp(0.5rem, 3vh, 2rem);
}

.hero.is-hidden {
  display: none;
}

/* ----------------------------------------------------------- sample questions */
.sample-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem 0.9rem;
  padding-bottom: 0.7rem;
  margin-bottom: 1.4rem;
  border-bottom: 1px solid var(--line);
}

.sample-title {
  font-weight: 650;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
}

.sample-lede {
  flex: 1 1 16rem;
  margin: 0;
  font-size: 0.84rem;
  line-height: 1.55;
  color: var(--text-dim);
}

.sample-reshuffle {
  appearance: none;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--text-dim);
  font-family: var(--font-cjk);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  padding: 0.25rem 0.6rem;
  cursor: pointer;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.sample-reshuffle:hover,
.sample-reshuffle:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}

/* Sample questions grouped under their category. Two columns where there is
   room, so all eight subject areas are visible at once without scrolling —
   seeing the breadth is the point. */
.sample-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
  gap: 1.1rem 1.8rem;
  align-items: start;
}

.sample-group {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.sample-label {
  margin: 0 0 0.3rem;
  font-family: var(--font-cjk);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.sample-error {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.topic {
  appearance: none;
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--line);
  color: var(--text);
  font-family: var(--font-cjk);
  font-size: 0.92rem;
  line-height: 1.4;
  padding: 0.4rem 0 0.4rem 0.75rem;
  cursor: pointer;
  transition: border-color 0.18s var(--ease), color 0.18s var(--ease),
    padding-left 0.18s var(--ease);
}

.topic:hover,
.topic:focus-visible {
  border-left-color: var(--accent);
  color: var(--text);
  padding-left: 1rem;
  outline: none;
}

.topic::before {
  content: "▸ ";
  color: var(--muted);
}

.topic:hover::before,
.topic:focus-visible::before {
  color: var(--accent);
}

.browse-archive {
  appearance: none;
  display: flex;
  align-items: center;
  justify-content: center;
  grid-column: 1 / -1;
  width: min(100%, 26rem);
  min-height: 4.75rem;
  margin: 1.75rem auto 0;
  padding: 1.25rem 1.75rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-cjk);
  font-size: 1.35rem;
  font-weight: 650;
  letter-spacing: 0.1em;
  cursor: pointer;
}

.browse-archive:hover,
.browse-archive:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
  outline: none;
}


/* ----------------------------------------------------------------------- log */
.log {
  display: flex;
  flex-direction: column;
  gap: 2.2rem;
}

.turn {
  display: flex;
  flex-direction: column;
}

/* user query — right aligned dispatch slip */
.turn-query {
  align-items: flex-end;
}

.query-card {
  max-width: min(40rem, 92%);
  background: var(--user-bubble);
  border: 1px solid var(--line);
  border-radius: 18px;
  padding: 0.75rem 1rem 0.85rem;
}

.query-tag {
  display: block;
  font-family: var(--font-cjk);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.35rem;
}

.query-text {
  margin: 0;
  color: var(--text);
  line-height: 1.55;
}

/* ----------------------------------------------------- signal acquisition */
.signal {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
  margin-bottom: 1rem;
}

.signal-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  background: transparent;
  border: 0;
  color: var(--text-dim);
  font-family: var(--font-cjk);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  padding: 0.6rem 0.9rem;
  cursor: pointer;
}

.signal-bar {
  width: 0.5rem;
  height: 0.9rem;
  background: var(--accent-2);
  display: inline-block;
  animation: scan 1s steps(2) infinite;
}

.signal[data-state="locked"] .signal-bar {
  background: var(--accent);
  animation: none;
}

@keyframes scan {
  50% {
    opacity: 0.2;
  }
}

.signal-label {
  color: var(--text);
  font-weight: 550;
}

.signal-status {
  margin-left: auto;
  color: var(--accent-2);
  font-size: 0.66rem;
}

.signal[data-state="locked"] .signal-status {
  color: var(--accent);
}

.signal-toggle {
  width: 0.7rem;
  height: 0.7rem;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(45deg);
  transition: transform 0.2s var(--ease);
}

.signal[data-collapsed="true"] .signal-toggle {
  transform: rotate(-45deg);
}

.signal-stages {
  list-style: none;
  margin: 0;
  padding: 0.2rem 0.9rem 0.8rem;
  display: grid;
  gap: 0.1rem;
  overflow: hidden;
  transition: max-height 0.3s var(--ease), opacity 0.2s var(--ease),
    padding 0.3s var(--ease);
  max-height: 30rem;
}

.signal[data-collapsed="true"] .signal-stages {
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  opacity: 0;
}

.stage-item {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  align-items: baseline;
  gap: 0.6rem;
  font-size: 0.8rem;
  color: var(--muted);
  padding: 0.18rem 0;
}

.stage-name {
  font-family: var(--font-cjk);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
  position: relative;
  padding-left: 0.9rem;
}

.stage-name::before {
  content: "○";
  position: absolute;
  left: 0;
  color: var(--line);
}

.stage-item[data-status="active"] .stage-name::before {
  content: "◉";
  color: var(--accent-2);
}

.stage-item[data-status="done"] .stage-name::before {
  content: "●";
  color: var(--accent);
}

.stage-item[data-status="active"] .stage-name,
.stage-item[data-status="done"] .stage-name {
  color: var(--text-dim);
}

.stage-line {
  color: var(--text-dim);
  font-size: 0.8rem;
  line-height: 1.45;
}

.stage-item[data-status="active"] .stage-line::after {
  content: "▮";
  margin-left: 0.15rem;
  color: var(--accent-2);
  animation: caret 0.9s steps(2) infinite;
}

@keyframes caret {
  50% {
    opacity: 0;
  }
}

/* ------------------------------------------------------------------- answer */
.answer-body {
  color: var(--text);
  font-size: 1.02rem;
  line-height: 1.78;
}

.answer-body>*:first-child {
  margin-top: 0;
}

.answer-body>*:last-child {
  margin-bottom: 0;
}

.answer-body p {
  margin: 0 0 1rem;
}

.answer-body h1,
.answer-body h2,
.answer-body h3,
.answer-body h4 {
  font-size: 1.08rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin: 1.6rem 0 0.7rem;
  color: var(--text);
}

/* Un-numbered, marker-less lists: hierarchy shown by indent, not bullets/numbers.
   The indent is what separates a list item from a paragraph, so it has to be
   there — at padding-left:0 the two render identically. */
.answer-body ul,
.answer-body ol {
  list-style: none;
  margin: 0 0 1rem;
  padding-left: 1.15rem;
}

.answer-body li {
  margin-bottom: 0.4rem;
}

/* nested sub-lists: indent with a hairline rule to signal a level, no marker */
.answer-body li>ul,
.answer-body li>ol {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
  border-left: 1px solid var(--line);
}

/* horizontal rule "---" -> subtle section splitter */
.answer-body hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 1.5rem 0;
}

.answer-body strong {
  color: var(--text);
  font-weight: 700;
}

.answer-body code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.05rem 0.3rem;
}

.answer-body a {
  color: var(--accent);
  text-decoration-color: rgba(var(--accent-rgb), 0.45);
  text-underline-offset: 0.2em;
}

/* Citations are markdown links into the transcript site — `[[标准化标题]](url)` —
   so they are picked out by destination and set as compact reference chips
   rather than inline prose links. */
.answer-body a[data-transcript-link] {
  border: 1px solid rgba(var(--accent-rgb), 0.4);
  border-radius: 6px;
  padding: 0.02em 0.3em;
  margin: 0 0.1em;
  white-space: nowrap;
  text-decoration: none;
  transition: background 0.18s var(--ease), border-color 0.18s var(--ease);
}

.answer-body a[data-transcript-link]:hover,
.answer-body a[data-transcript-link]:focus-visible {
  background: rgba(var(--accent-rgb), 0.12);
  border-color: var(--accent);
}

/* Suggested next questions. Set apart from the answer by a rule rather than a
   panel: they are an invitation to continue, not part of what was found. */
.followups {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 1.4rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
}

.followups-label {
  font-family: var(--font-cjk);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
  margin-right: 0.15rem;
}

.followup {
  appearance: none;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text-dim);
  font-family: var(--font-cjk);
  font-size: 0.85rem;
  line-height: 1.35;
  text-align: left;
  padding: 0.3rem 0.75rem;
  cursor: pointer;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease),
    background 0.18s var(--ease);
}

.followup:hover,
.followup:focus-visible {
  color: var(--text);
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.08);
}

/* Fallback copy shown when the pipeline produced no answer, and the inline
   error appended when the stream breaks. */
.answer-empty {
  color: var(--text-dim);
}

.answer-error {
  color: var(--accent);
}

/* Streaming cursor while tokens arrive. It hangs off the last rendered block
   rather than .answer-body itself: the body's children are block elements once
   markdown is rendering live, and an ::after on the container would sit on its
   own line below the text instead of trailing it. */
.answer.is-streaming .answer-body> :last-child::after {
  content: "▌";
  color: var(--accent-2);
  animation: caret 0.9s steps(2) infinite;
  margin-left: 0.1rem;
}

/* ---------------------------------------------------------------- composer */
.composer {
  position: relative;
  flex: 0 0 auto;
  z-index: 20;
  width: 100%;
  max-width: var(--max-col);
  margin: 0 auto;
  padding: 0.65rem clamp(0.85rem, 3vw, 1.5rem) 0.85rem;
  background: var(--bg);
  border-top: 1px solid var(--line);
}

.composer-form {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: 0.45rem 0.45rem 0.45rem 1rem;
}

.composer-form:focus-within {
  border-color: var(--text-dim);
}

.composer-prompt {
  display: none;
}

.composer-input {
  flex: 1;
  resize: none;
  background: transparent;
  border: 0;
  outline: none;
  color: var(--text);
  font-family: var(--font-cjk);
  font-size: 1rem;
  line-height: 1.5;
  max-height: 8rem;
  padding: 0.45rem 0;
  min-height: 1.5rem;
}

.composer-input::placeholder {
  color: var(--muted);
}

.composer-send {
  flex-shrink: 0;
  appearance: none;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-cjk);
  font-weight: 600;
  font-size: 0.85rem;
  min-width: 2.5rem;
  min-height: 2.5rem;
  padding: 0 0.85rem;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
}

.composer-send:hover:not(:disabled) {
  background: var(--accent-strong);
}

.composer-send:disabled {
  background: var(--line);
  color: var(--muted);
  cursor: not-allowed;
}

/* Stop state. Deliberately not red — stopping is a normal thing to do, not an
   error — but recessed so it never competes with send as the default action. */
.composer-send.is-stop {
  background: transparent;
  color: var(--text-dim);
  box-shadow: inset 0 0 0 1px var(--line);
}

.composer-send.is-stop::before {
  content: "■";
  font-size: 0.7em;
  line-height: 1;
}

.composer-send.is-stop:hover {
  color: var(--accent-2);
  box-shadow: inset 0 0 0 1px var(--accent-2);
}

/* Send/stop icons are a mobile-only affordance; desktop reads the label. */
.composer-send .icon-send,
.composer-send .icon-stop {
  display: none;
}

.composer-note {
  margin: 0.6rem 0 0;
  text-align: center;
  font-family: var(--font-cjk);
  font-size: 0.72rem;
  letter-spacing: 0.01em;
  color: var(--muted);
}

/* --------------------------------------------------------- focus + a11y */

/* Focus uses the blue accent, not the gold one: gold is reserved for the live
   signal (scanning, streaming caret), and a gold ring around an input reads as
   a validation error — especially on the light theme. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The composer's ring is drawn by .composer-form:focus-within instead. */
.composer-input:focus-visible {
  outline: none;
}

/* Announced by screen readers, never shown. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Placed after the component transitions so its !important wins: someone who
   asks for less motion should not get animated chrome either. */
@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

@media (max-width: 520px) {
  .composer-send {
    padding: 0.5rem 0.7rem;
  }
}

/* ================================================ chat + reading desk shell */
/* Two columns of one window. The conversation is centred while the pane is
   shut; opening the pane narrows the column and the reading desk grows beside
   it, in equal halves. Nothing here changes the conversation's own metrics, so
   the squeeze moves it sideways and does not jog it vertically. */
.app-shell {
  flex: 1 1 auto;
  display: flex;
  align-items: stretch;
  min-width: 0;
  min-height: 0;
}

/* Reached by the skip link, so the ring belongs inside the viewport. */
.app-shell:focus-visible {
  outline-offset: -3px;
}

.chat-pane {
  flex: 1 1 0%;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.chat-pane .stage {
  flex: 1 1 0;
  min-height: 0;
  overflow-x: hidden;
  overflow-y: auto;
}

/* Clipped to zero width rather than display:none, because a display switch
   cannot be animated. app.js marks it inert while it is shut, so it stays out
   of the tab order and the accessibility tree. */
.reading-pane {
  flex: 0 0 0%;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  border-left: 0 solid var(--line);
  transition: flex-basis 0.46s var(--ease), border-left-width 0.46s var(--ease);
  /* Shared inset so chrome, list, and article share one vertical edge. */
  --reading-inline: clamp(1rem, 2.4vw, 1.5rem);
  --reading-max: 52rem;
}

body[data-view="browse"] .reading-pane {
  flex-basis: 50%;
  border-left-width: 1px;
}

.reading-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden auto;
  opacity: 0;
  background: var(--bg);
  transition: opacity 0.2s var(--ease);
}

body[data-view="browse"] .reading-scroll {
  opacity: 1;
  transition-delay: 0s;
}

.reading-bar {
  /* Fixed chrome height — back/edit visibility must not resize this row or the
     border under it will jump when switching archive ↔ article. */
  flex: 0 0 auto;
  box-sizing: border-box;
  height: calc(var(--touch) + 1.1rem);
  min-height: calc(var(--touch) + 1.1rem);
  max-height: calc(var(--touch) + 1.1rem);
  display: grid;
  grid-template-columns: var(--touch) var(--touch) minmax(0, 1fr) var(--touch) var(--touch) var(--touch);
  align-items: center;
  column-gap: 0.5rem;
  padding: 0 var(--reading-inline);
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}

.reading-bar .reading-back {
  grid-column: 1;
}

.reading-bar .reading-doc-back {
  grid-column: 2;
}

.reading-bar .reading-share {
  grid-column: 4;
}

.reading-bar .reading-edit {
  grid-column: 5;
}

.reading-bar .reading-close {
  grid-column: 6;
}

/* Back / edit / close — same box as theme toggle. */
.reading-bar button,
.reading-bar .reading-edit {
  appearance: none;
  display: grid;
  place-items: center;
  width: var(--touch);
  height: var(--touch);
  border: 1px solid var(--line);
  border-radius: 10px;
  background: transparent;
  color: var(--text-dim);
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  box-sizing: border-box;
}

.reading-bar button .icon,
.reading-bar .reading-edit .icon {
  display: block;
}

.reading-bar button:hover,
.reading-bar button:focus-visible,
.reading-bar .reading-edit:hover,
.reading-bar .reading-edit:focus-visible {
  color: var(--text);
  border-color: var(--text-dim);
  outline: none;
}

/* Keep layout slots; UA [hidden] is display:none !important — override it. */
.reading-bar .is-slot-hidden,
.reading-bar button[hidden],
.reading-bar .reading-edit[hidden] {
  display: grid !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

.content-pane {
  min-width: 0;
  padding: 1.5rem var(--reading-inline) 4rem;
}

.archive-view,
.reader-view {
  width: 100%;
  max-width: var(--reading-max);
  margin: 0 auto;
}

.archive-head {
  max-width: none;
  margin-bottom: 1.25rem;
  padding-bottom: 0;
  border-bottom: 0;
}

.archive-search {
  width: 100%;
  margin-top: 0.45rem;
  margin-bottom: 0;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 1.08rem;
  padding: 0.55rem 0;
}

.archive-head h1,
.reader-head h1 {
  margin: 0.15rem 0 0.55rem;
  font-size: clamp(1.35rem, 2.8vw, 2rem);
  font-weight: 650;
  line-height: 1.25;
  letter-spacing: -0.015em;
}


.archive-search:focus {
  border-bottom-color: var(--accent);
  outline: 0;
}


.archive-groups {
  display: grid;
}

.archive-group {
  position: relative;
  padding-left: 0;
}

/* No side rail — list text shares the same left edge as the article body. */
.archive-group::before {
  content: none;
}

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

.archive-list li {
  border-bottom: 1px solid var(--line);
  content-visibility: auto;
  contain-intrinsic-size: auto 3.5rem;
}

.archive-list a {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.15rem 0.75rem;
  align-items: baseline;
  padding: 0.72rem 0;
  color: var(--text);
  text-decoration: none;
}

.archive-list a:hover .archive-item-title,
.archive-list a:focus-visible .archive-item-title {
  color: var(--accent);
}

.archive-item-title {
  font-weight: 600;
  font-size: 0.98rem;
  line-height: 1.4;
  transition: color 0.18s var(--ease);
}

.archive-item-source {
  grid-column: 1 / -1;
  grid-row: 2;
  overflow: hidden;
  color: var(--text-dim);
  font-size: 0.86rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.archive-list time {
  grid-column: 2;
  grid-row: 1;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--muted);
  text-align: right;
}

.archive-state,
.reader-state {
  color: var(--text-dim);
}

.reader-view {
  max-width: var(--reading-max);
}

.reader-head {
  padding-bottom: 0.75rem;
  margin-bottom: 1rem;
  border-bottom: 0;
}

.reader-head h1 {
  font-size: clamp(1.35rem, 2.8vw, 2rem);
  text-wrap: balance;
}

.reader-body {
  font-size: 1.04rem;
  line-height: 1.88;
  overflow-wrap: anywhere;
}

.reader-body h2,
.reader-body h3,
.reader-body h4,
.reader-body h5,
.reader-body h6 {
  margin: 1.75rem 0 0.85rem;
  line-height: 1.35;
  color: var(--text);
}

.reader-body h2 {
  padding-top: 0;
  border-top: 0;
  font-size: 1.42rem;
}

.reader-body hr {
  display: none;
}

.reader-body p,
.reader-body ul,
.reader-body ol,
.reader-body blockquote,
.reader-body pre,
.reader-body table {
  margin: 0 0 1.3rem;
}

.reader-body blockquote {
  margin-inline: 0;
  padding-left: 1rem;
  border-left: 2px solid var(--accent-2);
  color: var(--text-dim);
}

.reader-body code,
.reader-body pre {
  font-family: var(--font-mono);
}

.reader-body pre {
  max-width: 100%;
  padding: 1rem;
  overflow-x: auto;
  border: 1px solid var(--line);
  background: var(--surface);
}

.reader-body table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
}

.reader-body th,
.reader-body td {
  padding: 0.45rem 0.65rem;
  border: 1px solid var(--line);
  text-align: left;
}

.reader-body a {
  color: var(--accent);
  text-underline-offset: 0.2em;
}

/* A new document arrives in the window the way a page does: replaced, not
   reflowed in place. app.js re-adds the class for each article. */
.reader-view.is-entering {
  animation: reading-in 0.34s var(--ease) both;
}

@keyframes reading-in {
  from {
    opacity: 0;
    transform: translateY(0.6rem);
  }
}


.reader-body .footnote-ref a,
.reader-body a.transcript-fragment-link {
  color: var(--accent);
  text-decoration: none;
}

.reader-body .footnote-ref a:hover,
.reader-body .footnote-backref:hover {
  text-decoration: underline;
}

.reader-body .footnotes {
  margin-top: 2rem;
  padding-top: 0.5rem;
  border-top: 0;
  font-size: 0.92rem;
  color: var(--text-dim);
}

.reader-body .footnotes-list {
  padding-left: 1.25rem;
}

.reader-body .footnote-item:target,
.reader-body .footnote-ref:has(a:target),
.reader-body [id]:target {
  background: rgba(var(--accent-rgb), 0.12);
  border-radius: 4px;
  outline: 2px solid rgba(var(--accent-rgb), 0.35);
  outline-offset: 2px;
}

.transcript-image-note {
  color: var(--muted);
  font-size: 0.88em;
}

/* Desktop only: channel chips live in the masthead. */
.noscript-note {
  margin: 0;
  padding: 0.55rem 1rem;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.82rem;
  text-align: center;
}

.share-toast {
  position: fixed;
  left: 50%;
  top: 50%;
  z-index: 60;
  margin: 0;
  padding: 1.1rem 1.8rem;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-cjk);
  font-size: 1.15rem;
  font-weight: 500;
  box-shadow: 0 12px 36px rgb(0 0 0 / 0.24);
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.96);
  transition:
    opacity 0.18s var(--ease),
    transform 0.18s var(--ease);
  pointer-events: none;
}

.share-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .share-toast {
    transition: opacity 0.18s linear;
    transform: translate(-50%, -50%);
  }
}

.mobile-tabbar {
  display: none;
}

@media (min-width: 901px) {
  /* Desktop masthead single row fixed height — chips scroll, never wrap. */
  .masthead {
    height: var(--mast-h);
    min-height: var(--mast-h);
    max-height: var(--mast-h);
    box-sizing: border-box;
    flex-wrap: nowrap;
  }

  .channel-bar:not([hidden]) {
    display: flex !important;
    visibility: visible;
    flex-wrap: nowrap;
    overflow-x: auto;
  }

  .archive-head {
    margin-bottom: 2.2rem;
  }

  .archive-head h1 {
    font-size: clamp(1.3rem, 2vw, 1.85rem);
  }

  .reader-head h1 {
    font-size: clamp(1.3rem, 2vw, 1.85rem);
  }
}

/* ============================================================ Mobile layout
   Auto at max-width 900px. Full-screen Chat OR full-screen Archive/Reader.
   Bottom tabs switch modes. No manual desktop/mobile override. */
@media (max-width: 900px) {
  body {
    /* Reserve space for bottom tab bar */
    padding-bottom: 0;
  }

  /* Icon chrome only on desktop — mobile keeps bare glyphs. */
  .mast-icon,
  .theme-toggle {
    border-color: transparent;
    background: transparent;
  }

  .mast-icon:hover,
  .mast-icon:focus-visible,
  .theme-toggle:hover,
  .theme-toggle:focus-visible {
    border-color: transparent;
  }

  .masthead {
    gap: 0.5rem;
  }

  /* Mobile chat landing: hide masthead channels (bottom tab is enough).
     Desktop is outside this media query and always shows the channel bar. */
  body[data-view="chat"] .channel-bar {
    display: none !important;
  }

  /* Fixed two-row masthead on browse so archive↔article does not resize chrome.
     The base rule's 0.55rem block padding would offset the fixed rows (chat's
     52px is border-box, rows are not), so browse zeroes it to keep the brand
     row the same height as the chat landing. */
  body[data-view="browse"] .masthead {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: var(--mast-h) 2.5rem;
    align-items: center;
    column-gap: 0.5rem;
    row-gap: 0;
    flex-wrap: nowrap;
    min-height: calc(var(--mast-h) + 2.5rem);
    height: calc(var(--mast-h) + 2.5rem);
    box-sizing: border-box;
    padding-top: 0;
    padding-bottom: 0;
  }

  body[data-view="browse"] .masthead-brand {
    grid-column: 1;
    grid-row: 1;
  }

  body[data-view="browse"] .masthead-right {
    grid-column: 3;
    grid-row: 1;
  }

  body[data-view="browse"] .channel-bar:not([hidden]) {
    display: flex !important;
    grid-column: 1 / -1;
    grid-row: 2;
    order: unset;
    flex-basis: auto;
    width: 100%;
    height: 2.5rem;
    margin-left: 0;
    padding: 0;
    align-items: center;
  }

  body[data-view="chat"] .masthead {
    min-height: var(--mast-h);
    height: var(--mast-h);
    box-sizing: border-box;
  }

  /* Edit stays desktop-only; keep its grid slot empty on mobile via CSS.
     Share moves in to sit directly left of close. */
  .reading-bar {
    grid-template-columns: var(--touch) var(--touch) minmax(0, 1fr) var(--touch) var(--touch);
  }

  .reading-bar .reading-doc-back {
    grid-column: 2;
  }

  .reading-bar .reading-edit {
    display: none !important;
  }

  .reading-bar .reading-share {
    grid-column: 4;
  }

  .reading-bar .reading-close {
    grid-column: 5;
  }

  /* The tabbar is viewport-fixed; reserve its height on the flex container so
     the panes (absolute within the shell) end above it instead of under it. */
  body {
    padding-bottom: var(--tabbar-h);
  }

  .app-shell {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
  }

  .chat-pane,
  .reading-pane {
    position: absolute;
    inset: 0;
    flex: none;
    width: 100%;
    height: 100%;
    min-height: 0;
    border: 0;
    transition: none;
    background: var(--bg);
  }

  /* Chat is the default full-screen surface. */
  body[data-view="chat"] .chat-pane {
    z-index: 2;
    visibility: visible;
  }

  body[data-view="chat"] .reading-pane {
    z-index: 1;
    visibility: hidden;
    pointer-events: none;
  }

  body[data-view="browse"] .reading-pane {
    z-index: 2;
    flex-basis: auto;
    visibility: visible;
    opacity: 1;
  }

  /* Mobile archive: no reading bar at all. There is nothing to close at the
     list level — the bottom 对话 tab returns to chat — so the row would only
     waste height. An open article still shows the bar (back + close). */
  body[data-view="browse"][data-panel="archive"] .reading-bar {
    display: none;
  }

  body[data-view="browse"] .chat-pane {
    z-index: 1;
    visibility: hidden;
    pointer-events: none;
  }

  body[data-view="browse"] .reading-scroll {
    opacity: 1;
  }

  .chat-pane .stage {
    width: 100%;
    max-width: none;
    padding: 0.85rem 0.85rem 1rem;
  }

  .chat-pane .composer {
    width: 100%;
    max-width: none;
    padding: 0.55rem 0.75rem;
    padding-bottom: calc(0.55rem + var(--keyboard-inset, 0px));
  }

  .mobile-tabbar {
    /* Pinned to the viewport rather than flowing in the body column: after a
       pinch-zoom the layout viewport can scroll, dragging an in-flow bar out
       of view. Fixed keeps 对话/文稿 reachable at any zoom. */
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    flex: 0 0 auto;
    align-items: stretch;
    min-height: var(--tabbar-h);
    padding: 0;
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--bg);
    border-top: 1px solid var(--line);
    z-index: 30;
  }

  .mobile-tab {
    appearance: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: var(--touch);
    margin: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--muted);
    font-family: var(--font-cjk);
    font-size: 1.05rem;
    font-weight: 550;
    letter-spacing: 0.12em;
    cursor: pointer;
    padding: 0.85rem 0.5rem;
    text-decoration: none;
  }

  /* Vertical split between the two halves — reads as two tappable regions. */
  .mobile-tab + .mobile-tab {
    border-left: 1px solid var(--line);
  }

  .mobile-tab[aria-current="page"] {
    /* Same green as the send button — the selected tab is the primary action. */
    background: var(--accent);
    color: var(--on-accent);
    font-weight: 700;
  }


  .composer-send .composer-send-label {
    display: none;
  }

  .composer-send .icon-send {
    display: block;
  }

  .composer-send.is-stop .icon-send {
    display: none;
  }

  .composer-send.is-stop .icon-stop {
    display: block;
  }

  /* The desktop stop square is a ::before glyph; the SVG square replaces it. */
  .composer-send.is-stop::before {
    display: none;
  }

  .composer-send {
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    letter-spacing: 0;
  }

  .content-pane {
    padding: 1rem var(--reading-inline) 2.5rem;
  }

  .reading-pane {
    --reading-inline: 1rem;
  }

  .archive-head h1,
  .reader-head h1 {
    font-size: 1.25rem;
    line-height: 1.3;
  }

  .reading-edit {
    display: none !important;
  }

  .archive-list a {
    padding: 0.9rem 0;
    min-height: var(--touch);
  }

  .reading-bar button {
    width: var(--touch);
    height: var(--touch);
  }

  .log {
    gap: 1.4rem;
  }

  /* The lede's 16rem flex-basis leaves no room for 换一批 on a phone row;
     let it shrink so title + lede + button share one line. */
  .sample-lede {
    flex-basis: auto;
  }

  .sample-group {
    margin-bottom: 0.55rem;
  }

  .sample-label {
    display: none;
  }

  .browse-archive {
    min-height: var(--touch);
    margin-top: 1rem;
  }

  .stage {
    padding-bottom: 1rem;
  }
}

@media (max-width: 520px) {
  .masthead {
    padding-inline: 0.75rem;
  }

  .sample-grid {
    gap: 0.5rem;
  }
}
