/* ============================================================================
   睡前消息知识库
   Color theme derived from the show logo: a deep navy-black night sky, a
   royal-blue title (primary accent), and a golden-yellow moon (live accent).

   Semantic tokens (themeable):
     --bg / --surface / --line   background, raised panels, borders
     --text / --text-dim / --muted   primary, secondary, label text
     --accent (blue)   brand, primary action, links, citations, "done"
     --accent-2 (gold) live signal — scanning / in-progress / streaming cursor
   Dark is the token default; [data-theme="light"] overrides. The effective
   theme follows the live OS preference until the masthead toggle creates a
   persistent user override.
   ============================================================================ */

/* The page background is a gradient built from custom properties. Swapping a
   theme changes those properties, and a gradient assembled through var() is
   *recomputed* rather than interpolated — so background-image snaps however long
   its transition is. Registering the colours as typed properties makes them
   animatable, and the gradient is then rebuilt each frame from values that are
   themselves mid-transition. Only the properties the full-page gradient is made
   of need this; everything else is a plain colour that transitions on its own. */
@property --bg {
  syntax: "<color>";
  inherits: true;
  initial-value: #0f121c;
}

@property --bg-grad-top {
  syntax: "<color>";
  inherits: true;
  initial-value: #171b2b;
}

@property --bg-grad-bottom {
  syntax: "<color>";
  inherits: true;
  initial-value: #0a0c14;
}

@property --glow {
  syntax: "<color>";
  inherits: true;
  initial-value: rgba(74, 125, 240, 0.12);
}

:root {
  color-scheme: dark;
  --bg: #0f121c;
  --bg-grad-top: #171b2b;
  --bg-grad-bottom: #0a0c14;
  --bg-rgb: 15, 18, 28;
  --surface: #1a1e2e;
  --line: #2a3043;
  --text: #eaeef8;
  --text-dim: #9aa4bc;
  --muted: #616c85;
  --accent: #4a7df0;
  --accent-strong: #5f8ef5;
  --accent-rgb: 74, 125, 240;
  --accent-2: #f2c14e;
  --accent2-rgb: 242, 193, 78;
  --on-accent: #ffffff;

  /* Moonlight wash at the top of the page. Themed, because the same alpha that
     reads as a glow against a night sky is invisible against daylight. */
  --glow: rgba(var(--accent-rgb), 0.12);

  --max-col: 50rem;

  --font-cjk: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
    "Noto Sans SC", "Heiti SC", sans-serif;
  --font-mono: "SF Mono", "JetBrains Mono", "IBM Plex Mono", ui-monospace,
    "Menlo", "Consolas", monospace;

  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* How long a theme swap cross-fades. Long enough to read as a dimmer being
     turned rather than a light being flicked; short enough not to feel sluggish
     on a deliberate toggle. */
  --theme-fade: 320ms;
}

/* Daylight over the same sky, rather than white with a blue rinse: the tint is
   deepest overhead and clears toward the horizon where the reading happens, so
   panels sit on colour and white surfaces read as raised. */
[data-theme="light"] {
  color-scheme: light;
  --bg: #e7eef9;
  --bg-grad-top: #cbd9f1;
  --bg-grad-bottom: #f6f9fd;
  --bg-rgb: 231, 238, 249;
  --surface: #ffffff;
  --line: #c4d0e6;
  --text: #0e1420;
  --text-dim: #46536b;
  --muted: #647089;
  --accent: #2a5fe0;
  --accent-strong: #1e4fd0;
  --accent-rgb: 42, 95, 224;
  --accent-2: #b1770f;
  --accent2-rgb: 177, 119, 15;
  --on-accent: #ffffff;
  --glow: rgba(var(--accent-rgb), 0.18);
}

* {
  box-sizing: border-box;
}

html {
  height: 100%;
}

body {
  margin: 0;
  background-color: var(--bg);
  /* faint blue "moonlight" glow at the top over a deep night-sky gradient */
  background-image: radial-gradient(120% 80% at 50% -12%,
      var(--glow),
      transparent 60%),
    linear-gradient(180deg,
      var(--bg-grad-top) 0%,
      var(--bg) 42%,
      var(--bg-grad-bottom) 100%);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-cjk);
  font-size: 16px;
  line-height: 1.7;
  display: flex;
  flex-direction: column;
  min-height: 100%;
  -webkit-font-smoothing: antialiased;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  z-index: 100;
  background: var(--accent);
  color: var(--text);
  padding: 0.5rem 1rem;
  font-family: var(--font-mono);
  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: 1rem;
  padding: 0.85rem clamp(1rem, 4vw, 2.5rem);
  background: rgba(var(--bg-rgb), 0.82);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.masthead-brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  color: var(--text);
  text-decoration: none;
  transition: opacity 0.18s var(--ease);
}

.app-version {
  white-space: nowrap;
}

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

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

.wordmark-cn {
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: 0.12em;
}

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

.mast-link {
  font-family: var(--font-cjk);
  font-size: 0.82rem;
  letter-spacing: 0.12em;
  color: var(--text-dim);
  text-decoration: none;
  transition: color 0.18s var(--ease);
}

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

/* Icon-only link: no letter-spacing to distort the glyph, and sized to sit on
   the same optical line as the text link beside it. */
.mast-icon {
  display: inline-grid;
  place-items: center;
  letter-spacing: normal;
}

.mast-icon svg {
  display: block;
}

.masthead-right {
  display: flex;
  align-items: center;
  gap: 1.1rem;
}

.theme-toggle {
  appearance: none;
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 1px solid var(--line);
  border-radius: 5px;
  background: transparent;
  color: var(--text-dim);
  font-size: 0.95rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.theme-toggle:hover {
  color: var(--accent-2);
  border-color: var(--accent-2);
}

.theme-toggle-icon::before {
  content: "☀";
}

[data-theme="light"] .theme-toggle-icon::before {
  content: "☾";
}

/* -------------------------------------------------------------------- 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: 700;
  font-size: 1.15rem;
  letter-spacing: 0.08em;
}

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

.sample-reshuffle {
  appearance: none;
  flex-shrink: 0;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 4px;
  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-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  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);
}

/* ----------------------------------------------------------------------- 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, 88%);
  background: var(--surface);
  border: 1px solid var(--line);
  border-right: 2px solid var(--accent);
  border-radius: 6px 6px 2px 6px;
  padding: 0.7rem 1rem 0.85rem;
}

.query-tag {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  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: 6px;
  background: rgba(var(--accent-rgb), 0.05);
  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-mono);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  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);
  letter-spacing: 0.18em;
}

.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-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  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: 3px;
  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 archive — `[[名称]](url)` —
   so they are picked out by destination and set as compact reference chips
   rather than inline prose links. */
.answer-body a[href*="archive.bedtime.news"] {
  border: 1px solid rgba(var(--accent-rgb), 0.35);
  border-radius: 3px;
  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[href*="archive.bedtime.news"]:hover,
.answer-body a[href*="archive.bedtime.news"]: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-mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  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: sticky;
  bottom: 0;
  z-index: 20;
  width: 100%;
  max-width: var(--max-col);
  margin: 0 auto;
  /* Generous top padding so the gradient has room to work: content scrolling
     under the composer fades out over ~2rem instead of being sliced mid-line on
     a short window. */
  padding: 2.2rem clamp(1rem, 4vw, 2rem) 1.1rem;
  background: linear-gradient(180deg, rgba(var(--bg-rgb), 0) 0%, var(--bg) 60%);
}

.composer-form {
  display: flex;
  align-items: flex-end;
  gap: 0.6rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.55rem 0.65rem 0.55rem 0.9rem;
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

/* The whole composer is the focus target, so the ring goes on the form and the
   textarea inside suppresses its own (see .composer-input:focus-visible) —
   otherwise focusing the field draws two nested rings. */
.composer-form:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18);
}

.composer-prompt {
  font-family: var(--font-mono);
  color: var(--accent);
  font-size: 1.1rem;
  line-height: 1.9;
}

.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.6;
  max-height: 9rem;
  padding: 0.35rem 0;
}

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

.composer-send {
  flex-shrink: 0;
  appearance: none;
  border: 0;
  border-radius: 6px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-cjk);
  font-weight: 600;
  font-size: 0.9rem;
  letter-spacing: 0.1em;
  padding: 0.5rem 1rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  transition: background 0.18s var(--ease), opacity 0.18s var(--ease);
}

.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-arrow {
  font-family: var(--font-mono);
}

.composer-note {
  margin: 0.6rem 0 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  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;
}

/* Switching theme repaints the entire page at once, which is harsh — especially
   the jump to light in a dark room. app.js adds this class for the length of the
   swap only, so first paint stays instant and nothing animates during normal
   use. Colour-bearing properties are listed explicitly rather than using `all`,
   which would drag layout and transform into the transition. background-image is
   included for the body's gradient: light and dark differ only in colour stops,
   so browsers interpolate between them. !important overrides the shorter
   component transitions (buttons, links) for these few hundred milliseconds so
   the whole page moves together. */
.theme-fade,
.theme-fade *,
.theme-fade *::before,
.theme-fade *::after {
  transition: background-color var(--theme-fade) var(--ease),
    border-color var(--theme-fade) var(--ease),
    color var(--theme-fade) var(--ease),
    fill var(--theme-fade) var(--ease),
    box-shadow var(--theme-fade) var(--ease),
    outline-color var(--theme-fade) var(--ease),
    /* The registered colours above. Animating these is what carries the
       full-page gradient across, since background-image itself cannot. */
    --bg var(--theme-fade) var(--ease),
    --bg-grad-top var(--theme-fade) var(--ease),
    --bg-grad-bottom var(--theme-fade) var(--ease),
    --glow var(--theme-fade) var(--ease) !important;
}

/* Placed after .theme-fade so its !important wins: someone who asks for less
   motion should not get a cross-fade 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;
  }
}
