/* chattie — mobile-first responsive shell. Design §G4 (mobile from day one)
   and §4.6 Tier-1 (client-side history). Custom-property theming per
   design §2 non-goals (CSS variable overrides only, no per-customer
   theming machinery). */

:root {
  --bg:        #ffffff;
  --fg:        #1a1a1a;
  --muted:    #5a5a5a;
  --accent:   #2d5aa7;
  --border:   #e5e5e5;
  --user-bg:  #eef4ff;
  --claim:    #7c3aed;
  --refusal:  #b45309;
  /* The failure banner's pair (#3655). Tokens rather than the literals #3680
     shipped, because #banner was the one surface on the page with no entry in
     the dark block below — unthemed by accident rather than by decision, and
     legible in both schemes only by luck. White on #7f1d1d is 10.0:1, and
     #7f1d1d against the white page is the same 10.0:1, so the banner reads as
     a distinct band and not as tinted body text. */
  --danger-bg: #7f1d1d;
  --danger-fg: #ffffff;
  --pad:      12px;
  /* The overlay drawer's depth cue (#3714). A token rather than a literal
     because it is the one colour on the page that would otherwise not track
     the scheme: a fixed black shadow is correct on white and invisible on
     #121212, and the drawer needs to read as floating over the chat in both. */
  --shadow:   rgba(0, 0, 0, 0.35);

  /* Radius scale (#3652). Three steps assigned by hierarchy rather than the
     four unrelated values that accumulated here — small controls, mid-sized
     controls and inputs, large containers. The literals are gone from the
     rules below: a radius written as a number is the next value that joins
     the scale without being part of it. .spinner keeps its 50%, which is a
     shape and not a corner. */
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 12px;

  /* Typography (#3653). Composite tokens, so a rule states a role and not a
     number. Each carries weight, size, line-height and family together —
     a bare --font-size-* set would leave the other three scattered, which is
     the shape the finding was about. Tracking is the one property the `font`
     shorthand cannot carry (letter-spacing is not part of it), so .brand
     still sets its own; there is exactly one such site.

     Watch the shorthand's reset: `font:` clears font-weight, font-style and
     font-variant. The heading tokens therefore state 700 explicitly — a UA
     h1 is bold, and `font: var(--type-h1)` without the weight would quietly
     unbold it — and any rule pairing a token with font-style or
     font-variant-numeric must declare the token FIRST. */
  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --type-display:  700 24px/1.3 var(--font-sans);
  --type-h1:       700 22px/1.3 var(--font-sans);
  --type-h2:       700 20px/1.3 var(--font-sans);
  --type-h3:       700 17px/1.4 var(--font-sans);
  --type-body:     16px/1.5 var(--font-sans);
  /* The small-viewport body step. Not a general "slightly smaller body": the
     only thing that takes it is turn text below 480px. */
  --type-body-sm:  15px/1.5 var(--font-sans);
  --type-ui:       14px/1.4 var(--font-sans);
  --type-caption:  13px/1.4 var(--font-sans);
  --type-micro:    12px/1.4 var(--font-sans);
  --type-mono:     14px/1.4 var(--font-mono);

  /* The comfortable measure (#3651), in ch so it tracks the type token rather
     than a pixel count computed against one font at one size. 75ch is the top
     of Bringhurst's 45-75 band; at --type-body's 16px it lands near 675px,
     the same order as the 780px .turn cap the user bubble already respects. */
  --measure: 75ch;

  /* The pointer-target floor (#3654). Apple's 44x44pt guideline rather than
     WCAG 2.2 section 2.5.8's 24x24 CSS px, because the lower number is a
     conformance floor and this surface's first-class case is a thumb. */
  --touch-min: 44px;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg:      #121212;
    --fg:      #ececec;
    --muted:   #9a9a9a;
    --accent:  #7ea6ff;
    --border:  #2a2a2a;
    --user-bg: #1a2237;
    --claim:   #b19cd9;
    --refusal: #f0b429;
    /* Lifted off the light value deliberately. #7f1d1d against #121212 is
       1.9:1 — the banner would read as a dark smudge on a dark page rather
       than as an alert, which is below the 3:1 section 1.4.11 asks of a
       component boundary. #dc2626 is 3.9:1 against the page and still 4.8:1
       under white text, so both halves clear their own criterion. */
    --danger-bg: #dc2626;
    /* Restated rather than inherited, though the value is the same white. The
       defect being fixed WAS a missing dark entry, so the pair is defined in
       both blocks on purpose: a half-present pair is the shape that got
       #banner into this state. */
    --danger-fg: #ffffff;
    --shadow:  rgba(0, 0, 0, 0.7);
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font: var(--type-body);
  -webkit-font-smoothing: antialiased;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
}
.brand { font-weight: 700; letter-spacing: 0.02em; }
.who   { font: var(--type-ui); color: var(--muted); }

/* Login card */
#login {
  max-width: 420px;
  margin: 40px auto;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
#login h1 { margin: 0 0 8px; font: var(--type-h1); }
#login .subtle { color: var(--muted); margin: 0 0 20px; }
.devmode { margin-top: 24px; font: var(--type-ui); color: var(--muted); }
.devmode input { display: block; margin: 8px 0; padding: 8px; width: 100%; border: 1px solid var(--border); border-radius: var(--radius-md); background: transparent; color: var(--fg); }
/* min-height rather than more vertical padding (#3654): padding would move the
   label off centre the moment the button wrapped, and the floor is about the
   box a thumb has to hit, not about the space around the text. */
.devmode button { padding: 8px 16px; min-height: var(--touch-min); min-width: var(--touch-min); border: 1px solid var(--accent); background: transparent; color: var(--accent); border-radius: var(--radius-md); cursor: pointer; }

/* Overstory tab strip (#3737). Drawn from the enabled-tab list, so the number
   of tabs here is a deploy fact, not a layout constant.

   `flex-shrink: 0` and `overflow-y: hidden` are load-bearing on Safari
   (#4002). Body is a flex column and the strip is one of its items, so the
   default flex-shrink of 1 lets a squeezed column pinch the strip below its
   content — and `#workspace { flex: 1 1 auto }` on the Chattie panel will do
   that the moment any child of it reports a min-content taller than the
   viewport. `overflow-x` on its own is the second edge: the CSS overflow
   module coerces `overflow-y` to `auto` whenever the other axis is not
   `visible`, so leaving it unstated makes a vertical scrollbar the fallback.
   Safari activates that scrollbar earlier than Blink — the subpixel gap
   between the tab's intrinsic height and the strip's flex-basis is enough —
   and once it does the strip's own `border-bottom` clips inside the promoted
   scroll box, so the active tab's 2px accent underline draws across the
   letterforms of "Chattie" instead of below the word. Both properties
   together pin the strip to exactly its content height on every user agent,
   so no subpixel drift can relocate a 2px accent line onto a 20px glyph. */
#tabstrip {
  display: flex;
  flex-shrink: 0;
  gap: 4px;
  padding: 0 14px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  overflow-y: hidden;
}
#tabstrip[hidden] { display: none; }
#tabstrip .tab {
  padding: 10px 14px;
  font: var(--type-ui);
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  /* Transparent rather than absent, so marking a tab active changes the
     colour and not the height — the strip must not reflow on a switch. */
  border-bottom: 2px solid transparent;
}
#tabstrip .tab:hover { color: var(--fg); }
#tabstrip .tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* Tenant context bar (#3738). Three read-only fields; nothing in here is a
   control, so nothing in here has a hover, focus or pressed state to style. */
#contextbar {
  padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  font: var(--type-caption);
}
/* `display: flex` on the dl below would beat the user agent's
   `[hidden] { display: none }` on the section, so the section's own hidden
   state is written out — the same trap #workspace hit. */
#contextbar[hidden] { display: none; }
#contextbar dl {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  /* Generous between fields and tight within one, so the eye groups a label
     with its value rather than a value with the next label. */
  gap: 4px 20px;
}
.ctxfield { display: flex; gap: 6px; min-width: 0; }
.ctxfield[hidden] { display: none; }
.ctxfield dt { color: var(--muted); }
.ctxfield dd {
  margin: 0;
  color: var(--fg);
  /* A project label or bundle id is a single token that must not be broken
     mid-word; on a narrow viewport the flex-wrap above moves the whole field
     to the next line instead. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Bodies for the non-Chattie tabs. */
.tabview { padding: 24px 14px; }
.viewfail { color: var(--muted); }

/* Phase-1 stub bodies (#3739). A column of prose and nothing else, because a
   stub carries no controls — there is no filter row, no disabled button and no
   dead link to lay out, so there is no layout here beyond a readable measure.

   No breakpoint of its own, and that is the design rather than an omission.
   G7's desktop exemption covers Observatory's eventual CHARTS, not its chrome:
   a "Coming soon" body that overflows on a phone reads as broken, which is
   exactly what the Phase-1 exit gate forbids. min() caps the measure on a desk
   and yields the whole column on a phone in one declaration, so there is no
   second rule at a second breakpoint to keep in step with this one. */
.stub { max-width: min(46rem, 100%); }
.stub h2 { margin: 0 0 4px; font: var(--type-h2); }
.stub .stub-lede { margin: 0 0 16px; color: var(--accent); font-weight: 600; }
.stub .stub-detail {
  margin: 0 0 10px;
  color: var(--muted);
  line-height: 1.55;
  /* Prose wraps. The context bar's `white-space: nowrap` is for single tokens
     that must not break mid-word; applied here it would push a sentence past
     the viewport on a phone. */
  overflow-wrap: break-word;
}

/* Workspace: the rail and the chat, side by side. */
#workspace {
  flex: 1 1 auto;
  display: flex;
  min-height: 0;
}
/* Explicit, because the rule above would otherwise win. `display: flex` beats
   the user agent's `[hidden] { display: none }`, so without this the shell
   would set hidden on the workspace when another tab is active and Chattie's
   view — socket, rail and all — would stay on screen underneath it. */
#workspace[hidden] { display: none; }

/* Chat main */
#chat {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Flex items floor at min-content width, so without this a long
     unbroken token in a turn widens #chat and squeezes the rail rather
     than wrapping. */
  min-width: 0;
}

/* The chatbar exists to hold the drawer toggle, and the toggle only means
   anything in overlay mode — inline, the rail is already on screen. So the
   bar is absent from the desk layout entirely rather than sitting there as
   an empty strip above the log. */
.chatbar {
  flex: 0 0 auto;
  display: none;
  justify-content: flex-start;
  padding: 8px var(--pad) 0;
}

/* Conversation rail (#3714)
   ------------------------------------------------------------------------
   Two layouts, one element. On a desk the rail is a column beside the chat;
   below the breakpoint it leaves the flow entirely and comes back as an
   overlay drawer. Shrinking it into a narrow column instead would be the
   worst of both: a conversation list too narrow to read a title in, taking
   width from the only thing on the page anyone came for.

   --rail-mode is how chattie.js knows which of the two it is in. The
   breakpoint is a media query here and nowhere else; script reads the
   resolved value back rather than carrying a second copy of the number that
   would drift the first time either was touched. */
#rail {
  --rail-mode: inline;
  /* Wide enough for a title plus the two row controls, which are painted
     always rather than on hover — see .conv-action. */
  flex: 0 0 280px;
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-right: 1px solid var(--border);
  background: var(--bg);
}

/* `display: flex` above outranks the UA's `[hidden] { display: none }`, so
   the attribute that hides the rail until sign-in needs saying again. */
#rail[hidden] { display: none; }

.rail-head {
  flex: 0 0 auto;
  padding: 8px var(--pad);
  border-bottom: 1px solid var(--border);
}

#new-chat {
  width: 100%;
  padding: 8px 14px;
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  border-radius: var(--radius-md);
  cursor: pointer;
  font: inherit;
}

#new-chat:disabled { opacity: 0.5; cursor: not-allowed; }

#rail-toggle {
  padding: 6px 14px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  border-radius: var(--radius-md);
  cursor: pointer;
  font: var(--type-ui);
}

#conversation-list {
  flex: 1 1 auto;
  overflow-y: auto;
  margin: 0;
  padding: 6px;
  list-style: none;
}

#rail-empty {
  margin: 0;
  padding: 4px var(--pad) var(--pad);
  color: var(--muted);
  font: var(--type-ui);
}

.conv {
  display: flex;
  align-items: center;
  gap: 4px;
  border-radius: var(--radius-md);
  padding: 2px;
}
.conv[data-active="true"] { background: var(--user-bg); }

.conv-open {
  flex: 1 1 auto;
  /* Same floor as #chat, and for the same reason: without it the button is
     as wide as its longest word and the ellipsis below never triggers. */
  min-width: 0;
  display: block;
  text-align: left;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  padding: 8px 10px;
  border-radius: var(--radius-md);
  cursor: pointer;
}

.conv-title {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: var(--type-ui);
}
.conv-date {
  display: block;
  color: var(--muted);
  font: var(--type-micro);
}

/* Rename and delete are painted always, not on :hover or :focus-within. A
   control revealed by a pointer is a control a keyboard cannot find and a
   touchscreen has no way to summon — and this rail's first-class case is a
   touchscreen. */
.conv-action {
  flex: 0 0 auto;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: var(--type-caption);
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.conv-action:hover { color: var(--fg); }
.conv-action[data-action="delete"]:hover { color: var(--refusal); }

.conv-rename-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--fg);
  font: var(--type-ui);
}

/* The confirm step, in the row being deleted rather than in a dialog, so the
   thing about to be destroyed stays on screen while the question is asked. */
.conv-confirm {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  font: var(--type-caption);
  color: var(--refusal);
}
.conv-confirm .conv-confirm-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.conv-confirm button {
  flex: 0 0 auto;
  border: 1px solid currentColor;
  background: transparent;
  color: inherit;
  font: var(--type-caption);
  padding: 4px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.conv-confirm button[data-action="cancel"] {
  border-color: var(--border);
  color: var(--muted);
}

/* Inline mode has nothing to dim: the rail is not covering anything. */
#rail-scrim { display: none; }

/* Overlay mode (#3714)
   ------------------------------------------------------------------------
   The breakpoint, written once. Everything below it is the drawer layout,
   and --rail-mode is the flag chattie.js reads to know it is in it.

   700px rather than the 480px the tightening block below uses: the question
   here is not "is this a phone" but "is there room for a 260px column and a
   readable measure beside it", and the answer turns negative well before a
   phone. */
@keyframes rail-in {
  from { transform: translateX(-100%); }
  to   { transform: none; }
}

@media (max-width: 700px) {
  #rail {
    --rail-mode: overlay;
    /* Out of the flow, so #chat gets the whole viewport whether the drawer
       is open or shut. This is the half that makes the collapse a collapse
       and not a 30%-wide column. */
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 20;
    width: min(84vw, 320px);
    border-right: 1px solid var(--border);
    box-shadow: 0 0 24px var(--shadow);
  }

  /* A shut drawer is `display: none`, not translated off the side of the
     screen. Off-screen is still in the tab order, so the translate version
     hands a keyboard a list of invisible buttons to walk through and a
     screen reader a rail that is on the page but not on the page. */
  body:not([data-rail="open"]) #rail { display: none; }
  /* :not([hidden]) so this never outranks the pre-auth hide above — it is
     the more specific selector, and without the guard a rail that has not
     been revealed yet would appear the moment the drawer state was set. */
  body[data-rail="open"] #rail:not([hidden]) {
    display: flex;
    animation: rail-in 140ms ease-out;
  }

  .chatbar { display: flex; }

  body[data-rail="open"] #rail-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 10;
    background: rgba(0, 0, 0, 0.45);
  }

  /* Thumbs, not cursors. WCAG 2.2 §2.5.8 puts the floor at 24x24 CSS px;
     these land above it with the row's own padding on top. */
  #new-chat { padding: 12px 14px; }
  .conv-open { padding: 12px 10px; }
  .conv-action { padding: 12px 10px; }
  .conv-confirm button { padding: 10px 12px; }
}

/* A drawer sliding in from the edge is exactly the large-area movement
   vestibular disorders react to, and it is decoration — the drawer is just
   as usable arriving instantly. */
@media (prefers-reduced-motion: reduce) {
  body[data-rail="open"] #rail:not([hidden]) { animation: none; }
}

#log {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--pad);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Turn bubble shapes */
.turn { max-width: 780px; width: 100%; margin: 0 auto; }
.turn.user { display: flex; justify-content: flex-end; }
.turn.user .body {
  background: var(--user-bg);
  border-radius: var(--radius-lg);
  padding: 10px 14px;
  max-width: 88%;
  white-space: pre-wrap;
}
/* The assistant turn is the only block of continuous prose on the surface, and
   it was the only one with no measure (#3651): .turn caps at 780px but the
   user bubble takes 88% of that and the assistant body took all of it, so on a
   desk the answer ran to roughly 95 characters a line. The user bubble reads
   as bounded and the answer — the thing anyone came for — did not.

   Capped, not centred. The turn column is already centred by .turn's auto
   margins, so the body stays left-aligned within it and the left edge of every
   assistant line stays where the eye returns to after a wrap. */
.turn.assistant .body {
  max-width: var(--measure);
  padding: 0 4px;
  white-space: pre-wrap;
}
.turn.refusal .body {
  color: var(--refusal);
  font-style: italic;
  padding: 6px 10px;
  border-left: 3px solid var(--refusal);
}
.turn.error .body {
  color: var(--refusal);
  font: var(--type-mono);
}

/* Citation surface */
.cite {
  display: inline;
  color: var(--claim);
  border-bottom: 1px dotted currentColor;
  cursor: help;
  padding: 0 2px;
}
.cite-list {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  font: var(--type-caption);
  color: var(--muted);
}
.cite-list li { display: inline; margin-right: 12px; }

/* Truncation notice — an answer that stopped on the output cap (#3846). */
.truncated-note {
  margin: 8px 0 0;
  font: var(--type-caption);
  /* After the token, never before it: `font:` resets font-style, so the two
     declarations swapped round would silently un-italicise this. */
  font-style: italic;
  color: var(--muted);
}

/* Composer */
#composer {
  display: flex;
  gap: 8px;
  padding: 12px;
  border-top: 1px solid var(--border);
  background: var(--bg);
  position: sticky;
  bottom: 0;
}
#composer textarea {
  flex: 1 1 auto;
  min-height: 40px;
  max-height: 140px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--fg);
  font: inherit;
  resize: none;
}
#composer button {
  padding: 0 18px;
  /* The send control had no height of its own (#3654) — it took whatever the
     line box gave it, around 24-27px, under both Apple's 44pt guideline and
     WCAG 2.2 section 2.5.8's 24x24 CSS px floor on a short line. It is the
     single most-tapped target on the surface and the one sitting closest to
     the bottom edge of a phone, where thumb accuracy is worst. */
  min-height: var(--touch-min);
  /* Width too, and not left to the padding. "Send" plus 18px either side
     clears 44px comfortably today, which means the guarantee currently rests
     on the label — and an icon-only send button is exactly the change that
     would quietly drop it back under the floor. */
  min-width: var(--touch-min);
  border: 1px solid var(--accent);
  color: var(--accent);
  background: transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  font: inherit;
}
#composer button:disabled { opacity: 0.5; cursor: not-allowed; }

.statusline {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 12px 12px;
  min-height: 1.4em;
}

#status {
  margin: 0;
  color: var(--muted);
  font: var(--type-caption);
}

#busy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font: var(--type-caption);
  /* So a rising count does not shove the status text sideways once a second.
     After the token for the same reason .truncated-note is: `font:` resets
     font-variant, and these two swapped round would drop tabular figures. */
  font-variant-numeric: tabular-nums;
}

/* Needed explicitly: the display above outranks the UA's [hidden] rule, and
   without this the indicator would still be drawn while hidden. */
#busy[hidden] { display: none; }

.spinner {
  width: 11px;
  height: 11px;
  border: 2px solid var(--muted);
  border-top-color: transparent;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

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

/* The ring itself stays — it is the "a request is outstanding" half of the
   signal — and the count still rises, so stopping the rotation costs nothing
   a reader needs. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; }
}

/* Focus (#3650)
   ------------------------------------------------------------------------
   One rule for every interactive element, present and future. Before this
   the stylesheet said nothing about focus at all, so the ring was whatever
   the browser drew against its own assumptions about the page — and this
   page repaints the palette underneath it, to #121212 in dark mode, with
   two transparent-background controls in the composer. That is the least
   reliable case for a default ring, on a surface whose entire keyboard
   interaction is moving between a small number of controls.

   :focus-visible, never :focus. :focus paints on mouse clicks too, which
   is the behaviour browsers moved away from deliberately, and a ring that
   appears on every click trains people to stop seeing it.

   Nothing here suppresses anything. The common way to get this wrong is to
   pair a :focus-visible rule with `outline: none` on :focus, which removes
   the ring entirely in any engine that does not support :focus-visible —
   for exactly the users who need it. This rule only adds.

   The ring inherits --accent, so it follows the theme: #2d5aa7 on white is
   6.7:1 and #7ea6ff on #121212 is 7.8:1, both well past the 3:1 that WCAG
   2.2 §2.4.11 (Focus Appearance) asks of an indicator — and past §1.4.11
   (Non-text Contrast), which is the one usually cited here and is really
   about component boundaries. Against the two tinted surfaces a ring is
   actually drawn beside — --user-bg in either scheme — it is 6.1:1 and
   6.6:1. Computed rather than eyeballed, because "it picks up the theme
   for free" is an argument about hue, not about contrast. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Accessibility helpers */
.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;
}

/* Small-viewport tightening */
@media (max-width: 480px) {
  header { padding: 10px 12px; font: var(--type-ui); }
  #log { padding: 8px; gap: 10px; }
  /* `font:` is a shorthand and resets weight/style to the token's own. Safe
     here because neither body sets a weight, but a weight added to either
     rule above would be silently dropped at this breakpoint only. */
  .turn.user .body, .turn.assistant .body { font: var(--type-body-sm); }
  #composer { padding: 8px; }
}

/* Legal pages (/privacy, /terms). Prose, not chat: the shell is the same
   header and theming, but the body is a readable measure rather than a
   scrolling log. No JS on these pages, which is why their CSP is tighter. */
body.legal main {
  max-width: 42rem;
  margin: 0 auto;
  padding: 24px var(--pad) 48px;
  line-height: 1.6;
}
body.legal h1 { font: var(--type-display); margin: 0 0 16px; }
body.legal h2 { font: var(--type-h3); margin: 28px 0 6px; }
body.legal p  { margin: 0 0 12px; }
body.legal .brand a { color: inherit; text-decoration: none; }
body.legal .muted { color: var(--muted); font: var(--type-ui); margin-top: 32px; }

/* Failure banner. Deliberately loud and at the top of the flow: it is the
   only surface that reports a failure occurring before the chat pane is
   revealed, and on a phone there is no console to fall back to. */
#banner {
  margin: 0;
  padding: 10px var(--pad);
  background: var(--danger-bg);
  color: var(--danger-fg);
  font: var(--type-ui);
}

/* ---------------------------------------------------------------------- */
/* Answer body: rendered markdown + the numbered citation surface.
   #3773 (structure), #3695 (resolvable markers), #3693 (entry shape).

   Appended as one block at the end of the file on purpose. The rules above
   were being retokenised concurrently, and everything here is additive — new
   selectors for elements the answer renderer did not previously emit, plus
   two narrow overrides that are called out where they appear. */

/* The renderer now emits blocks, so it owns its own vertical rhythm. Margins
   are collapsed at the edges: an answer should not open with a gap the
   surrounding bubble did not ask for. */
.turn.assistant .body > :first-child { margin-top: 0; }
.turn.assistant .body > :last-child { margin-bottom: 0; }
.turn.assistant .body p { margin: 0 0 10px; }
.turn.assistant .body h3,
.turn.assistant .body h4,
.turn.assistant .body h5,
.turn.assistant .body h6 {
  margin: 16px 0 6px;
}
/* Answers are dense with headings — retrieval depth drives how many sections
   the model writes — so these stay close to body size. A heading scale that
   stepped up per level would let a well-grounded answer shout.
   The `font:` shorthand resets weight and line-height, so both are restated
   after each token rather than hoisted into the shared rule above. */
.turn.assistant .body h3 {
  font: var(--type-h3);
  font-weight: 600;
  line-height: 1.3;
}
.turn.assistant .body h4,
.turn.assistant .body h5,
.turn.assistant .body h6 {
  font: var(--type-body);
  font-weight: 600;
  line-height: 1.3;
}
.turn.assistant .body h5,
.turn.assistant .body h6 { color: var(--muted); }

.turn.assistant .body ul,
.turn.assistant .body ol {
  margin: 0 0 10px;
  padding-left: 1.5em;
}
.turn.assistant .body li { margin: 0 0 4px; }

.turn.assistant .body blockquote {
  margin: 0 0 10px;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--border);
  color: var(--muted);
}

.turn.assistant .body hr {
  margin: 16px 0;
  border: 0;
  border-top: 1px solid var(--border);
}

/* Neutral grey rather than a token: it has to read as a recessed surface
   against both the light and the dark background, and an alpha over whatever
   is behind it does that without a second variable to keep in sync. */
.turn.assistant .body code {
  font: var(--type-mono);
  background: rgba(127, 127, 127, 0.14);
  border-radius: var(--radius-sm);
  padding: 1px 4px;
}
.turn.assistant .body pre {
  margin: 0 0 10px;
  padding: 10px 12px;
  background: rgba(127, 127, 127, 0.12);
  border-radius: var(--radius-md);
  /* Long lines scroll the block rather than the page. A code sample is the
     one thing in an answer that must not be reflowed to fit a phone. */
  overflow-x: auto;
}
.turn.assistant .body pre code {
  background: none;
  padding: 0;
  white-space: pre;
}

/* Inline references. The base .cite rule above styles the old hover chip, so
   these two lines correct what a link needs and nothing else. */
a.cite {
  cursor: pointer;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
}
a.cite:hover { background: rgba(127, 127, 127, 0.14); }
/* The first keyboard-focusable elements inside an answer body arrive with
   this change, and the stylesheet had no focus indicator to inherit (#3650).
   Shipping references a keyboard user cannot see themselves land on would
   make the grounding surface the least navigable part of the page. */
a.cite:focus-visible,
.cite-list li:target {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* A marker naming a source the turn did not return. Styled apart from a
   resolved reference deliberately — it means "this points at nothing", which
   is the opposite of what a citation asserts. */
.cite-unresolved {
  color: var(--refusal);
  /* Width and colour spelled out rather than left to currentColor and the UA
     default: the mark has to stay a visible mark wherever it lands. */
  border-bottom: 1px dashed var(--refusal);
  cursor: default;
}

/* Citation list entries. Overrides the inline run-on above: an entry is now
   several fields and a link target, and a row of them on one line was the
   reason the list read as a wall. */
.cite-list li {
  display: block;
  margin: 0 0 4px;
}
.cite-num {
  color: var(--claim);
  font-variant-numeric: tabular-nums;
  margin-right: 6px;
}
.cite-anchor {
  font: var(--type-mono);
  color: var(--fg);
  margin-right: 8px;
  overflow-wrap: anywhere;
}
.cite-id { overflow-wrap: anywhere; }
