/* Design tokens.
   Defined on bare :root so the light palette is the unconditional default, then
   overridden under prefers-color-scheme. A colour whose only definition lives
   inside a media query has no value at all when that query does not match. */
:root {
  --bg: #fbfaf8;
  --surface: #ffffff;
  --surface-2: #f2f0ec;
  --border: #e2ded7;
  --text: #1a1917;
  --text-dim: #6b6760;
  --accent: #3a5f4a;
  --accent-text: #ffffff;
  --accent-soft: #e8f0ea;
  --danger: #8c2f2f;
  --danger-soft: #fbeaea;
  --tool: #5b4a7a;
  --tool-soft: #f0ecf6;

  --radius: 10px;
  --radius-sm: 6px;
  --gap: 1rem;
  --measure: 68ch;

  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14140f;
    --surface: #1d1d18;
    --surface-2: #26261f;
    --border: #35352c;
    --text: #ececdf;
    --text-dim: #9a9a8c;
    --accent: #8fc0a0;
    --accent-text: #14140f;
    --accent-soft: #23302a;
    --danger: #e78b8b;
    --danger-soft: #322020;
    --tool: #b9a7dd;
    --tool-soft: #272132;
  }
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-text-size-adjust: 100%;
}

/* Scrollbars are styled rather than left native.
   Chrome's default is a wide opaque gutter that reads as browser furniture
   inside an app shell; these sit quietly on the surface they scroll. The
   standard properties come first so Firefox gets them, then the WebKit
   pseudo-elements, which Firefox ignores. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  /* A transparent border plus background-clip is what insets the thumb, so it
     reads as a slim pill rather than a full-width bar. */
  background-color: var(--border);
  border: 3px solid transparent;
  background-clip: content-box;
  border-radius: 99px;
}
::-webkit-scrollbar-thumb:hover { background-color: var(--text-dim); }
::-webkit-scrollbar-corner { background: transparent; }

/* A visible focus ring everywhere, always. Removing it is the single most
   common way a server-rendered app becomes unusable by keyboard. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

a { color: var(--accent); }

/* --- shell ---------------------------------------------------------------- */

/* ⚠ The shell is EXACTLY one viewport tall, not "at least". With min-height
   the page grows past the viewport, the browser adds its own scrollbar, and the
   composer drifts below the fold — so the inner scroll region never gets a
   chance to work. dvh rather than vh because mobile browser chrome changes the
   viewport as it hides, and vh does not follow it. */
.shell {
  display: flex;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

.sidebar {
  width: 260px;
  flex: 0 0 auto;
  border-right: 1px solid var(--border);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.sidebar__head {
  padding: var(--gap);
  border-bottom: 1px solid var(--border);
}

.sidebar__brand {
  font-weight: 650;
  letter-spacing: -0.01em;
  text-decoration: none;
  color: var(--text);
  display: block;
  margin-bottom: 0.75rem;
}

.sidebar__list {
  list-style: none;
  margin: 0;
  padding: 0.5rem;
  overflow-y: auto;
  flex: 1 1 auto;
}

.sidebar__link {
  display: block;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-size: 0.9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-height: 44px;
  line-height: 1.9;
}
.sidebar__link:hover { background: var(--surface-2); }
.sidebar__link[aria-current="page"] { background: var(--accent-soft); font-weight: 600; }

.sidebar__foot {
  padding: var(--gap);
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  color: var(--text-dim);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* min-height: 0 is load-bearing and easy to miss. A flex item defaults to
   min-height:auto, which refuses to shrink below its content — so a long
   transcript stretches this box instead of scrolling inside it, and the
   overflow lands on the page. Every ancestor of a scroll region needs it. */
.main {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The chat column: transcript grows, composer stays put. */
.chat {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* Below 820px the sidebar becomes a compact BAR, not a stacked column.
   Stacked, its three sections (brand, conversation list, account) took about
   60% of a phone viewport and left a third of the screen for the conversation
   itself — which is the one thing the page is for. Laid out as a bar it costs
   roughly a tenth of that. */
@media (max-width: 820px) {
  .shell { flex-direction: column; }

  .sidebar {
    width: auto;
    flex: 0 0 auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem var(--gap);
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }

  .sidebar__head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex: 0 0 auto;
    padding: 0;
    border-bottom: 0;
  }
  .sidebar__brand { margin: 0; font-size: 0.95rem; }

  /* Account controls sit at the far end of the same row as the brand. */
  .sidebar__foot {
    order: 1;
    margin-left: auto;
    flex: 0 0 auto;
    flex-direction: row;
    align-items: center;
    gap: 0.4rem;
    padding: 0;
    border-top: 0;
  }
  /* The address is the first thing to go: it is available on the admin
     screen, and it is the only item here that is not an action. */
  .sidebar__foot > span { display: none; }

  /* Conversations get their own full-width row, scrolling sideways. */
  .sidebar__list {
    order: 2;
    flex: 1 0 100%;
    display: flex;
    gap: 0.5rem;
    overflow-x: auto;
    padding: 0;
  }
  .sidebar__link {
    white-space: nowrap;
    flex: 0 0 auto;
    max-width: 55vw;
    min-height: 36px;
    line-height: 1.6;
    padding: 0.35rem 0.6rem;
  }
}

/* Narrower still: shrink the actions so the bar stays one row plus the list. */
@media (max-width: 480px) {
  .sidebar__brand { font-size: 0.9rem; }
  .sidebar .btn--sm { padding: 0 0.6rem; font-size: 0.8rem; }
}

/* --- transcript ----------------------------------------------------------- */

.transcript {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 2rem var(--gap);
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  scroll-behavior: smooth;
}

.turn { max-width: var(--measure); width: 100%; margin: 0 auto; }

.turn__role {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
  margin-bottom: 0.35rem;
}

.turn__body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  background: var(--surface);
  border: 1px solid var(--border);
}

/* Assistant answers are server-rendered markdown, so the body holds real block
   elements. pre-wrap would then double every gap the markup already provides. */
.prose { white-space: normal; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose p { margin: 0 0 0.75rem; }
.prose h1, .prose h2, .prose h3, .prose h4 {
  margin: 1.25rem 0 0.5rem;
  line-height: 1.3;
  letter-spacing: -0.01em;
}
.prose h1 { font-size: 1.3rem; }
.prose h2 { font-size: 1.15rem; }
.prose h3 { font-size: 1.02rem; }
.prose h4 { font-size: 0.95rem; color: var(--text-dim); }
.prose ul, .prose ol { margin: 0 0 0.75rem; padding-left: 1.35rem; }
.prose li { margin: 0.15rem 0; }
.prose li > ul, .prose li > ol { margin: 0.15rem 0; }
.prose blockquote {
  margin: 0 0 0.75rem;
  padding: 0.1rem 0 0.1rem 0.9rem;
  border-left: 3px solid var(--border);
  color: var(--text-dim);
}
.prose hr { border: 0; border-top: 1px solid var(--border); margin: 1.25rem 0; }
.prose a { color: var(--accent); text-underline-offset: 2px; }

.prose code {
  font-family: var(--mono);
  font-size: 0.86em;
  background: var(--surface-2);
  padding: 0.12em 0.35em;
  border-radius: 4px;
}
/* A fenced block already has its own frame; the inline chrome would double it. */
.prose pre {
  margin: 0 0 0.75rem;
  padding: 0.8rem 0.9rem;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: auto;
  /* Generous, because this is the answer rather than machinery — but bounded,
     so one long listing cannot push the rest of the reply off the screen. */
  max-height: 32rem;
  overscroll-behavior: contain;
}
.prose pre code { background: none; padding: 0; font-size: 0.84rem; }

/* Wide tables scroll inside the bubble rather than widening the page. */
.prose table {
  display: block;
  width: max-content;
  max-width: 100%;
  max-height: 32rem;
  overflow: auto;
  overscroll-behavior: contain;
  border-collapse: collapse;
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
}
.prose th, .prose td {
  border: 1px solid var(--border);
  padding: 0.35rem 0.6rem;
  text-align: left;
}
.prose th { background: var(--surface-2); font-weight: 600; }

.prose img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }

.turn--user .turn__body { background: var(--accent-soft); border-color: transparent; }

/* One disclosure widget for reasoning and tool machinery alike. They are the
   same thing to a reader — supporting detail behind the answer — so they get
   the same affordance and differ only in accent colour. */
.fold {
  max-width: var(--measure);
  width: 100%;
  margin: 0 auto 0.4rem;
}

.fold__summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 44px;
  padding-right: 0.5rem;
  font-size: 0.82rem;
  color: var(--text-dim);
  user-select: none;
}
/* Safari draws its own triangle unless this is suppressed too. */
.fold__summary::-webkit-details-marker { display: none; }
.fold__summary:hover { color: var(--text); }

.fold--think .fold__summary { color: var(--text-dim); }
.fold--tool .fold__summary { color: var(--tool); }

.fold__chevron { display: inline-block; transition: transform 120ms ease; }
.fold[open] .fold__chevron { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .fold__chevron { transition: none; } }

.fold__marker { opacity: 0.7; }
.fold__name { font-family: var(--mono); font-weight: 600; }

/* ⚠ An opened fold is BOUNDED and scrolls inside itself.
   Measured with twelve large tool results: unbounded, one fold rendered 5893px
   tall, so opening it meant scrolling thousands of pixels of machinery to get
   back to the answer — the transcript scrolled fine, but the fold had stretched
   the thing it was supposed to be a footnote to. A fold is supporting detail;
   it must never be able to dominate the conversation it sits in. */
.fold__body {
  border-left: 2px solid var(--border);
  padding-left: 0.8rem;
  margin: 0.2rem 0 0.6rem;
  max-height: 55vh;
  overflow-y: auto;
  /* Do not chain to the transcript: reaching the end of a fold should stop,
     not jump the page somewhere else. */
  overscroll-behavior: contain;
}
.fold--tool .fold__body { border-left-color: var(--tool); }

/* Reasoning: prose, wrapped. */
.fold__text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text-dim);
  font-size: 0.9rem;
}

/* Tool payloads: monospace, and wide content scrolls inside its own box so the
   page body never scrolls sideways. */
.fold__pre {
  font-family: var(--mono);
  font-size: 0.8rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--surface-2);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.7rem;
  margin: 0;
  max-height: 14rem;
  overflow: auto;
  overscroll-behavior: contain;
}

/* Steps inside a tool group: one call paired with its result. */
.steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; }
.step { padding-left: 0.1rem; }
.step__head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; }
.step__name { font-family: var(--mono); font-size: 0.8rem; font-weight: 600; color: var(--tool); }
.step__state { font-size: 0.72rem; color: var(--text-dim); }
.step__state--error { color: var(--danger); }
.step--error .step__name { color: var(--danger); }

.fold__pre--result { background: transparent; border: 1px solid var(--border); }

/* Three dots, shown only while a fold's content is still arriving, so a long
   quiet phase does not look like nothing happening. */
.fold__dots { display: inline-flex; gap: 2px; }
.fold__dots i {
  width: 3px; height: 3px; border-radius: 50%;
  background: currentColor;
  animation: fold-dot 1.2s ease-in-out infinite;
}
.fold__dots i:nth-child(2) { animation-delay: 0.15s; }
.fold__dots i:nth-child(3) { animation-delay: 0.3s; }
@keyframes fold-dot { 0%, 100% { opacity: 0.2; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .fold__dots i { animation: none; opacity: 0.6; } }

/* The caret marks the message being generated right now. */
.caret {
  display: inline-block;
  width: 0.55em;
  height: 1.05em;
  vertical-align: -0.18em;
  margin-left: 0.1em;
  background: var(--accent);
  animation: blink 1.05s steps(2, start) infinite;
}
@keyframes blink { to { visibility: hidden; } }
@media (prefers-reduced-motion: reduce) {
  .caret { animation: none; opacity: 0.6; }
  .transcript { scroll-behavior: auto; }
}

/* --- states --------------------------------------------------------------- */

.empty {
  margin: auto;
  text-align: center;
  color: var(--text-dim);
  max-width: 42ch;
}
.empty h2 { color: var(--text); font-size: 1.15rem; margin-bottom: 0.4rem; }

.notice {
  max-width: var(--measure);
  width: 100%;
  margin: 0 auto;
  border-radius: var(--radius-sm);
  padding: 0.7rem 0.9rem;
  font-size: 0.9rem;
  border: 1px solid var(--border);
}
.notice--error { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
.notice--warn { background: var(--surface-2); color: var(--text-dim); }

.working {
  max-width: var(--measure);
  width: 100%;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--text-dim);
  font-size: 0.88rem;
}
.working__dot {
  width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: var(--accent);
  animation: pulse 1.2s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 0.25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .working__dot { animation: none; } }

/* --- composer ------------------------------------------------------------- */

.composer {
  flex: 0 0 auto;
  border-top: 1px solid var(--border);
  background: var(--surface);
  padding: var(--gap);
}
.composer__inner {
  max-width: var(--measure);
  margin: 0 auto;
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
}
.composer textarea {
  flex: 1 1 auto;
  resize: vertical;
  min-height: 52px;
  max-height: 40vh;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem;
}
.composer__foot {
  max-width: var(--measure);
  margin: 0.45rem auto 0;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.composer__hint {
  margin: 0;
  font-size: 0.75rem;
  color: var(--text-dim);
  flex: 1 1 18ch;
}

/* Context-window meter. Empty until the first reply reports a figure — an
   empty bar reading 0% is worse than no bar, because it looks like data. */
.usage {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.72rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim);
  margin-left: auto;
}
.usage:empty { display: none; }

.usage__meter {
  width: 72px;
  height: 5px;
  border-radius: 99px;
  background: var(--surface-2);
  overflow: hidden;
  flex: 0 0 auto;
}
.usage__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
  transition: width 200ms ease;
  /* A conversation using 0.008% of a million-token window rounds to 0% and
     would render as an empty bar — which reads as "nothing", not as "a
     little". The sliver is only ever drawn when the count is non-zero, since
     the meter is not rendered at all before the first reply. */
  min-width: 3px;
}
.usage__meter--warn .usage__fill { background: #b8860b; }
.usage__meter--full .usage__fill { background: var(--danger); }
@media (prefers-reduced-motion: reduce) { .usage__fill { transition: none; } }

.usage__text { white-space: nowrap; }
.usage__dim { opacity: 0.7; }

/* On a phone the hint and the meter each get their own line rather than
   fighting for one. */
@media (max-width: 560px) {
  .usage { margin-left: 0; }
}

/* --- controls ------------------------------------------------------------- */

.btn {
  font: inherit;
  font-weight: 550;
  min-height: 44px;
  padding: 0 1.1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: var(--accent);
  color: var(--accent-text);
  cursor: pointer;
}
.btn:hover:not(:disabled) { filter: brightness(1.08); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn--ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn--ghost:hover:not(:disabled) { background: var(--surface-2); filter: none; }
.btn--danger { background: transparent; color: var(--danger); border-color: var(--border); }
.btn--danger:hover:not(:disabled) { background: var(--danger-soft); filter: none; }
.btn--sm { min-height: 36px; padding: 0 0.7rem; font-size: 0.85rem; }

/* --- forms and pages ------------------------------------------------------ */

/* Non-chat screens scroll here instead of on the page, keeping the rule
   "exactly one scroller, and it is never the document". */
.page {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 2rem var(--gap);
  max-width: 60rem;
  margin: 0 auto;
  width: 100%;
}
.page h1 { font-size: 1.4rem; letter-spacing: -0.02em; margin: 0 0 0.25rem; }
.page__lede { color: var(--text-dim); margin: 0 0 1.5rem; max-width: 62ch; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem;
  margin-bottom: 1rem;
}
.card__head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.card__title { font-weight: 600; margin: 0; }
.card__meta { color: var(--text-dim); font-size: 0.85rem; margin: 0.15rem 0 0; }
.card__actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.field { display: block; margin-bottom: 1rem; }
.field > span { display: block; font-size: 0.85rem; font-weight: 550; margin-bottom: 0.3rem; }
.field > small { display: block; color: var(--text-dim); font-size: 0.78rem; margin-top: 0.25rem; }
.field input, .field textarea, .field select {
  width: 100%;
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.7rem;
  min-height: 44px;
}
.field textarea { min-height: 9rem; resize: vertical; font-family: var(--mono); font-size: 0.88rem; }
.field--check { display: flex; align-items: center; gap: 0.5rem; }
.field--check input { width: auto; min-height: auto; }
.field--check > span { margin: 0; }

.auth { max-width: 26rem; margin: 12vh auto; padding: 0 var(--gap); }
.auth h1 { text-align: center; }
.auth__alt { text-align: center; color: var(--text-dim); font-size: 0.9rem; margin-top: 1.25rem; }

.badge {
  display: inline-block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0.12rem 0.45rem;
  border-radius: 99px;
  background: var(--surface-2);
  color: var(--text-dim);
}
.badge--on { background: var(--accent-soft); color: var(--accent); }

.tabs { display: flex; gap: 0.4rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
.tabs a {
  padding: 0.5rem 0.85rem;
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--text);
  border: 1px solid var(--border);
  font-size: 0.9rem;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}
.tabs a[aria-current="page"] { background: var(--accent-soft); border-color: transparent; font-weight: 600; }

/* Wide content scrolls inside its own container; the page body never scrolls
   sideways. */
.scroll-x { overflow-x: auto; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
