/* A calm, slightly warm neutral palette. Participants read long answers here
   for an hour at a time, so contrast is tuned for sustained reading rather
   than for looking punchy in a screenshot.

   Two palettes, each written once. Which one applies is decided by the
   data-theme attribute alone, never by a media query: theme.js resolves the
   system preference into that attribute before the first paint, so the choice
   lives in one place and "follow the system" is a value it can hold rather
   than a second rule competing with it. The portal is an SPA that shows
   nothing without JavaScript, so there is no reader for a media-query-only
   fallback to serve. */
:root {
  color-scheme: light;
  --bg: #ffffff;
  --bg-soft: #f7f8fa;
  --bg-sunk: #eceef2;
  --fg: #16181d;
  --fg-muted: #656c7a;
  --line: #e4e7ec;
  --accent: #3358d4;
  /* A deliberate hover shade rather than a brightness filter: on a light page
     the accent should deepen under the pointer, and a filter would only ever
     wash it out. */
  --accent-hover: #2b4bbb;
  --accent-fg: #ffffff;
  /* The participant's own bubble: a tint rather than a solid fill. A saturated
     block behind every question fights the answer beside it for attention,
     and long questions become hard to read on it. */
  --bubble: #e9eefb;
  --bubble-fg: #16181d;
  --danger: #b3261e;
  --ok: #1a7f4b;
  /* The eBRIDGE logo's own two colours, as drawn in assets/ebridge-logo.svg.
     They are the brand's, so they sit apart from --accent: the accent is the
     portal's interactive colour and is free to be retuned, while these are
     not ours to change. */
  --brand-blue: #185a96;
  --brand-teal: #0e8a82;
  /* The holes at the centre of the two nodes. The logo file paints them white,
     which is right on paper and on this palette; tying them to the surface
     they sit on keeps them reading as holes on the dark one too, where white
     dots would read as lights. */
  --brand-dot: var(--bg-soft);
  --radius: 10px;
  /* system-ui already resolves to a CJK-capable face on Windows and macOS; the
     named families are the safety net for a Linux desktop where it does not,
     and cost nothing when the first choice hits. */
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
    "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", sans-serif;
}

/* color-scheme is what tells the browser to paint its own furniture — the
   scrollbar troughs, a date picker, the caret — to match. Without it a dark
   page keeps light native widgets. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #15171b;
  --bg-soft: #1c1f25;
  --bg-sunk: #24282f;
  --fg: #e7eaef;
  --fg-muted: #9aa2b1;
  --line: #2c313a;
  --accent: #7b9cf5;
  /* Dark reverses it: lighter is the direction that reads as "raised" here. */
  --accent-hover: #93aef7;
  --accent-fg: #10131a;
  --bubble: #253352;
  --bubble-fg: #e7eaef;
  --danger: #f2857c;
  --ok: #6cc48f;
  /* The logo is drawn for white paper. Its blue lands at about 2.3:1 against
     this background — legible as a shape, but the mark reads as a smudge
     rather than as two colours. Lightened to roughly the contrast it has on
     paper, keeping the blue/teal relationship the logo is built on. */
  --brand-blue: #5aa3e0;
  --brand-teal: #2fbfb2;
}

/* Thin, muted scrollbars so the one at the window edge is not the loudest
   thing on the page. */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; }
*::-webkit-scrollbar-thumb:hover { background: var(--fg-muted); }

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
}

.view { height: 100%; }
.view[hidden] { display: none; }

.centred {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.card {
  width: 100%;
  max-width: 420px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.card h1 { margin: 0 0 4px; font-size: 20px; }

/* The eBRIDGE lockup: symbol on the left, the name stacked beside it, which is
   the arrangement the logo file uses. It appears twice at two sizes, so
   everything here is driven off --mark-h and only that changes between them.

   Left-aligned rather than centred even on the sign-in card, because
   everything under it — heading, labels, fields — is left-aligned, and a
   centred mark over a left-aligned form reads as two columns. */
.brand {
  --mark-h: 26px;
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  line-height: 1.15;
}

/* Both dimensions are set, and the width is derived from the viewBox's 258:128
   rather than left to the intrinsic ratio: an inline <svg> with no width and
   height attributes defaults to 100% of its container, so "height only" would
   let it claim the whole row. */
.brand-mark {
  flex: none;
  height: var(--mark-h);
  width: calc(var(--mark-h) * 2.016);
  overflow: visible;
}

/* Document CSS rather than fill attributes on the elements themselves, so a
   theme switch repaints the mark. */
.brand-mark .b-stroke { fill: none; stroke: var(--brand-blue); }
.brand-mark .b-blue { fill: var(--brand-blue); }
.brand-mark .b-teal { fill: var(--brand-teal); }
.brand-mark .b-hole { fill: var(--brand-dot); }

.brand-name { display: flex; flex-direction: column; min-width: 0; }

/* The Chinese name is the primary one and takes the page's foreground, so it
   stays readable on both palettes; the Latin one carries the logo's colours.
   Tracking on the CJK line only — it is set solid in the logo, and the Latin
   line is already spaced by its own letterforms. */
.brand-cn {
  font-size: calc(var(--mark-h) * 0.5);
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--fg);
}

.brand-en {
  font-size: calc(var(--mark-h) * 0.42);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--brand-blue);
}

.brand-e { color: var(--brand-teal); }

/* The sign-in card, where the mark is the first thing on the screen and is
   doing the work of telling someone which system they are signing in to. It is
   the card's <h1> there, so the margin is written as a shorthand: `.card h1`
   sets one of its own, and a longhand here would lose the other three sides. */
.brand.lg { --mark-h: 44px; gap: 14px; margin: 0 0 6px; }

/* Set apart from the fields above it. Every other row on the card is a step in
   filling the form in, and at the card's own 10px the button read as the row
   after the password rather than as the thing you do once both are filled —
   the more so now that the labels have no hints under them to break the run. */
#login-form button[type="submit"] { margin-top: 10px; }

label { font-size: 13px; font-weight: 600; margin-top: 6px; }

input, textarea, select {
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 9px 11px;
  width: 100%;
}

input:focus, textarea:focus, select:focus, button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

#login-code { font-family: var(--mono); letter-spacing: 0.08em; text-transform: uppercase; }

button {
  font: inherit;
  cursor: pointer;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--fg);
  padding: 9px 14px;
}

button:hover:not(:disabled) { background: var(--bg-sunk); }
button:disabled { opacity: 0.55; cursor: default; }

button.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
}
/* The background has to be restated: the plain button:hover rule above carries
   more specificity than button.primary, so without this the accent fill is
   replaced by the sunk grey on hover and the accent-coloured label is left
   sitting on it. */
button.primary:hover:not(:disabled) {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-fg);
}
button.block { width: 100%; }

button.link {
  background: none;
  border: none;
  color: var(--fg-muted);
  padding: 4px 0;
  text-align: left;
  text-decoration: underline;
}
button.link:hover { color: var(--fg); background: none; }

.muted { color: var(--fg-muted); font-size: 13px; }
.error { color: var(--danger); font-size: 13px; margin: 4px 0 0; }
.ok { color: var(--ok); }

/* The ICP filing line, at the foot of the sign-in view.

   Fixed to the viewport rather than laid out under the card, because the card
   is centred and a notice following it would sit in the middle of the screen:
   this is a footnote about the site itself, and it belongs at the bottom of
   the page. It is a child of the view all the same, so it comes and goes with
   it — a fixed element inside a hidden parent is hidden too.

   The extra padding below keeps the two apart on a short window, where a
   centred card would otherwise reach the bottom of the screen. */
#view-login { padding-bottom: 52px; }

.icp {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 14px;
  text-align: center;
  font-size: 12px;
  color: var(--fg-muted);
}
/* Quiet by default and underlined on hover, like the rest of the portal's
   secondary links: it has to be reachable — that is the point of filing it —
   without competing with the sign-in button for attention. */
.icp a { color: inherit; text-decoration: none; }
.icp a:hover { color: var(--fg); text-decoration: underline; }

.consent {
  white-space: pre-wrap;
  font-family: inherit;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 14px;
  margin: 0;
  max-height: 40vh;
  overflow-y: auto;
}

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

.app { display: grid; grid-template-columns: 260px 1fr; height: 100%; }

/* Each pane names its own column rather than relying on auto-placement, which
   counts only the children that are displayed: hiding the sidebar to collapse
   it would otherwise slide the transcript left into the empty track and leave
   it as wide as the sidebar used to be. */
#sidebar { grid-column: 1; }
#chat-main { grid-column: 2; }
#source-panel { grid-column: 3; }

/* The source column takes a share of the window rather than a fixed 380px: an
   article is a document with headings and tables, and it was being read through
   a slot narrower than the transcript beside it. The wide mode is for the ones
   with wide tables, where even this is a scrollbar exercise. */
.app:has(#source-panel:not([hidden])) {
  grid-template-columns: 260px minmax(0, 1fr) minmax(400px, 36%);
}
.app.source-wide:has(#source-panel:not([hidden])) {
  grid-template-columns: 260px minmax(0, 1fr) minmax(520px, 56%);
}

#sidebar, #admin-nav, #docs-nav {
  background: var(--bg-soft);
  border-right: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 14px;
  gap: 8px;
}

/* The mark, then the one button. Stacked here rather than left to #sidebar's
   own gap so the space under the mark can be wider than the space between the
   sidebar's other rows: it is a heading for the whole panel, not the first
   item in the list below it. */
.sidebar-head {
  padding-bottom: 6px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Name on the left, the two controls for the list on the right. */
.sidebar-top { display: flex; align-items: center; gap: 8px; }
.sidebar-top .brand { flex: 1; min-width: 0; padding: 2px 0 0 2px; }

.sidebar-tools { display: flex; align-items: center; gap: 2px; flex: none; }
.sidebar-tools .icon {
  border-radius: 6px;
  padding: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.sidebar-tools .icon svg { width: 18px; height: 18px; display: block; }
/* The search button stays lit while its field is open, so it reads as the thing
   holding the list narrowed rather than as a button that did nothing. Named
   rather than matched on aria-expanded, which its neighbour also carries. */
#search-sessions[aria-expanded="true"] { color: var(--accent); background: var(--bg-sunk); }

#session-filter { font-size: 13px; padding: 7px 10px; }

.conv-empty { padding: 8px 10px; font-size: 13px; }

/* The icon and the words are separate so the rail can keep one and drop the
   other; centred, because the button is a square once the words are gone. */
#new-session { display: flex; align-items: center; justify-content: center; gap: 8px; }
#new-session svg { width: 18px; height: 18px; flex: none; }

/* ---- the collapsed rail ---- */

/* Collapsed to a rail rather than to nothing. What the 260px was paying for is
   the session list; the mark, a new session, the search and the account are
   each one control wide and stay. 64px is a 40px target plus the sidebar's own
   padding, and 40px is the narrowest the mark stays a bridge at. */
.app.nav-collapsed { grid-template-columns: 64px 1fr; }
.app.nav-collapsed:has(#source-panel:not([hidden])) {
  grid-template-columns: 64px minmax(0, 1fr) minmax(400px, 36%);
}
.app.nav-collapsed.source-wide:has(#source-panel:not([hidden])) {
  grid-template-columns: 64px minmax(0, 1fr) minmax(520px, 56%);
}

.app.nav-collapsed #sidebar { padding: 14px 10px; align-items: center; }

/* Everything that needs the width to mean anything: the list, the field, and
   the words beside each icon. */
.app.nav-collapsed #conversation-list,
.app.nav-collapsed #session-filter,
.app.nav-collapsed .brand-name,
.app.nav-collapsed .account-text,
.app.nav-collapsed #new-session span { display: none; }

/* With the list gone there is nothing stretching to push the footer down. */
.app.nav-collapsed .sidebar-foot { margin-top: auto; }

/* The rail is one column in a fixed order — mark, the toggle, a new session,
   search, chats — but when the panel is open two of those sit beside the mark
   and one sits below it, in different flex containers. `display: contents`
   drops both wrappers here so every control becomes a child of the same
   column, which is what lets `order` sequence them; without it no amount of
   ordering can interleave a child of .sidebar-tools with one of its siblings. */
.app.nav-collapsed .sidebar-top,
.app.nav-collapsed .sidebar-tools { display: contents; }

.app.nav-collapsed .sidebar-head { align-items: center; gap: 6px; }
.app.nav-collapsed .brand { order: 1; --mark-h: 21px; flex: none; padding: 0 0 2px; }
.app.nav-collapsed #collapse-sidebar { order: 2; }
.app.nav-collapsed #new-session { order: 3; }
.app.nav-collapsed #search-sessions { order: 4; }
.app.nav-collapsed .rail-flyout-wrap { order: 5; }

/* One size for everything in the rail, or a column of differently sized
   buttons reads as a list of unrelated things. */
.app.nav-collapsed .sidebar-tools .icon,
.app.nav-collapsed #new-session {
  width: 40px;
  height: 40px;
  padding: 0;
}

/* The count moves onto the portrait, the way a badge normally sits: side by
   side it is wider than the rail. */
.app.nav-collapsed #account { justify-content: center; gap: 0; padding: 5px 0; position: relative; }
.app.nav-collapsed #notice-badge { position: absolute; top: 0; right: 0; }

/* The menu keeps its own width and opens over the transcript, rather than being
   squeezed into the rail it is anchored to. */
.app.nav-collapsed #account-menu { right: auto; width: 232px; }

/* ---- the rail's chats flyout ---- */

/* Nothing to peek at while the list itself is on screen. */
.rail-flyout-wrap { display: none; position: relative; }
.app.nav-collapsed .rail-flyout-wrap { display: block; }

.rail-flyout {
  display: none;
  position: absolute;
  left: calc(100% + 8px);
  top: -6px;
  /* Over the composer, which is positioned and would otherwise paint across
     the foot of a long list. */
  z-index: 6;
  width: 244px;
  /* A study can run to more sessions than the window is tall. */
  max-height: min(60vh, 420px);
  overflow-y: auto;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgb(0 0 0 / 0.24);
}

/* Hover for a pointer, focus-within for a keyboard: tabbing onto the button
   opens it and tabbing on walks the rows. */
.rail-flyout-wrap:hover .rail-flyout,
.rail-flyout-wrap:focus-within .rail-flyout { display: flex; }

/* The 8px of daylight between button and panel is real gap, and a pointer
   crossing it would drop the hover and take the panel with it. This carries the
   hoverable area back over the gap without drawing anything in it. */
.rail-flyout::before {
  content: '';
  position: absolute;
  top: 0;
  right: 100%;
  width: 8px;
  height: 100%;
}

.rail-chat {
  border: none;
  background: none;
  border-radius: 6px;
  padding: 7px 9px;
  text-align: left;
  font-size: 14px;
  width: 100%;
  /* One line each. A menu of wrapped titles is a wall to read, and the full one
     is on the row's title attribute. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-chat:hover:not(:disabled) { background: var(--bg-sunk); }
.rail-chat.active { background: var(--bg-sunk); font-weight: 600; }

#conversation-list {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 0;
}

/* Pinned sessions are lifted into their own group, which needs saying only
   once anything is up there. */
.conv-head {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-muted);
  padding: 0 10px;
  margin: 12px 0 2px;
}
.conv-head:first-child { margin-top: 2px; }

/* Small-caps styling is written for an alphabet. Uppercasing does nothing to
   Chinese, and the tracking that makes a Latin label read as a heading just
   pulls the characters apart. */
:root:lang(zh) .conv-head, :root:lang(zh) .menu-label {
  text-transform: none;
  letter-spacing: 0;
  font-size: 12px;
}

/* A row is the title plus its two controls, and the row — not the title
   button — carries the highlight, so the controls sit inside the same lit
   shape rather than on a strip beside it. --row-bg is what the controls fade
   the title out against; it has to follow the row's own state to do that. */
.conv-row {
  --row-bg: var(--bg-soft);
  position: relative;
  display: flex;
  border-radius: 6px;
}
.conv-row:hover, .conv-row:focus-within, .conv-row.active {
  --row-bg: var(--bg-sunk);
  background: var(--row-bg);
}

.conv {
  flex: 1;
  min-width: 0;
  text-align: left;
  border: none;
  background: none;
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The row is already lit; a second highlight the width of the title would cut
   a corner off the first. */
.conv:hover:not(:disabled) { background: none; }
.conv-row.active .conv { font-weight: 600; }

/* Over the end of the title, not beside it. The gradient is what keeps a long
   title from running under the icons: it fades out where they begin instead of
   stopping at a hard edge. */
.conv-actions {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 0 4px 0 20px;
  background: linear-gradient(to right, transparent, var(--row-bg) 20px);
  border-radius: 0 6px 6px 0;
  opacity: 0;
  transition: opacity 90ms ease;
}
/* focus-within as well as hover, or the controls are unreachable by keyboard:
   they would take focus while still invisible. */
.conv-row:hover .conv-actions, .conv-row:focus-within .conv-actions { opacity: 1; }

.conv-action {
  border: none;
  background: none;
  color: var(--fg-muted);
  padding: 4px;
  border-radius: 5px;
  display: flex;
  line-height: 0;
}
.conv-action svg { width: 16px; height: 16px; display: block; }
.conv-action:hover:not(:disabled) { background: var(--line); color: var(--fg); }
/* Deleting is the one thing here that cannot be undone from this sidebar, so
   it colours on approach rather than looking like its neighbour. */
.conv-action.danger:hover:not(:disabled) { color: var(--danger); }

.sidebar-foot {
  border-top: 1px solid var(--line);
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  /* The menu is anchored to this, and it opens upward: the footer is already
     at the bottom of the window. */
  position: relative;
}

/* ---- account ---- */

#account {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 7px 8px;
  border: none;
  background: none;
  border-radius: 8px;
  text-align: left;
  /* The whole row is the target, not just the words in it. */
  min-width: 0;
}
#account:hover:not(:disabled), #account[aria-expanded="true"] { background: var(--bg-sunk); }

/* A stand-in portrait. The study issues codes rather than accounts with
   photographs, so this is a silhouette and stays one — an initial in a
   coloured circle would look like an identity the portal does not have. */
.avatar {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--bg-sunk);
  color: var(--fg-muted);
  display: grid;
  place-items: center;
  overflow: hidden;
}
#account:hover .avatar, #account[aria-expanded="true"] .avatar { background: var(--line); }
.avatar svg { width: 26px; height: 26px; display: block; }

/* Name over role, both clipped rather than wrapped: a two-line footer that
   becomes three when the code is long makes the sidebar jump. */
.account-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
.account-text > * {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#account-name { font-size: 14px; font-weight: 600; }
#account-role { font-size: 12px; color: var(--fg-muted); }

#account-menu {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  right: 0;
  /* Above the composer, which is positioned and so paints over anything that
     is not. It never mattered while the menu stayed inside a 260px sidebar and
     the composer began where the sidebar ended; from the collapsed rail the
     menu opens out across the transcript, and its last row was being painted
     over halfway through the words. */
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgb(0 0 0 / 0.24);
}
#account-menu[hidden] { display: none; }

#account-menu [role="menuitem"] {
  border: none;
  background: none;
  border-radius: 6px;
  padding: 8px 10px;
  text-align: left;
  font-size: 14px;
}
/* Restated because the ID in the selector above outranks the global button
   hover, which would otherwise leave these rows dead under the pointer. */
#account-menu [role="menuitem"]:hover:not(:disabled) { background: var(--bg-sunk); }
#account-menu [role="menuitem"][hidden] { display: none; }

/* ---- appearance ---- */

/* Sits above the menu's actions, separated by a rule: it is a setting, and the
   things below it sign you out or open the study's tooling. */
#theme-switch, #lang-switch {
  padding: 2px 4px 8px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
#lang-switch { padding-top: 6px; }

.menu-label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fg-muted);
  padding: 2px 6px 6px;
}

/* One track holding three, so the choice reads as one control with a position
   rather than as three buttons that happen to be adjacent. */
.segmented {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--bg-sunk);
  border-radius: 8px;
}

.segmented button {
  flex: 1 1 0;
  min-width: 0;
  border: none;
  background: none;
  border-radius: 6px;
  padding: 5px 4px;
  font-size: 12px;
  color: var(--fg-muted);
  text-align: center;
  /* `flex: 1 1 0` gives every segment the width of the widest, and a label a
     few pixels over that wraps rather than widening the track — 简体中文 came
     out as two lines beside English, in a control twice as tall as the rest of
     the corner. A segment is a label, so it is one line and the track grows to
     hold it. */
  white-space: nowrap;
}
.segmented button:hover:not(:disabled) { background: var(--line); color: var(--fg); }

/* The selected segment is a raised tile on the sunken track. Colour alone
   would have to survive both palettes and a participant's own screen; a
   filled, lifted shape reads at a glance in either. */
.segmented button[aria-checked="true"] {
  background: var(--bg);
  color: var(--fg);
  font-weight: 600;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.16);
}
.segmented button[aria-checked="true"]:hover { background: var(--bg); }

/* Appearance and language, for anyone with no account row to reach them from.
   They float over the centred views rather than sitting inside each of them,
   because all three — sign-in, consent, setup — need them in the same place,
   and a participant who cannot read the sign-in screen has no way past it. */
#view-corner {
  position: fixed;
  top: 14px;
  right: 16px;
  z-index: 5;
  display: none;
  gap: 8px;
  /* Stacked rather than pushed off the edge once the two no longer fit side by
     side, which on a phone in Chinese is most of the time. */
  flex-wrap: wrap;
  justify-content: flex-end;
  max-width: calc(100vw - 32px);
}
/* Only while a centred view is up. Inside the chat the account menu carries
   both, and two of each on one screen is one too many. */
body:has(.view.centred:not([hidden])) #view-corner { display: flex; }
/* Each track is as wide as its own segments, not half the pair — and each
   segment as wide as its own label.

   `flex: 1 1 0` equalises segments across a track of known width, which is
   what the account menu gives them. A track sized to its own content has no
   width to divide, and the pair resolves by under-serving the longest label:
   跟随系统 was handed 52px for the 58 it needs and spilled over its segment.
   Here they size to their labels and the track adds up, which costs the even
   thirds and buys three segments that hold what is written in them. */
#view-corner .segmented { width: max-content; }
#view-corner .segmented button { flex: 0 0 auto; }
#view-corner button { padding: 5px 10px; }

/* ---- chat ---- */

/* One cell, two layers: the scroller fills it and the composer sits on top at
   the bottom edge. The scroller must reach the window edge so its scrollbar
   lands there, and stacking rather than stacking-in-rows is what lets the
   transcript run under the composer instead of stopping above it. */
#chat-main {
  display: grid;
  grid-template-rows: 1fr;
  min-height: 0;
  padding: 0;
}

#messages {
  grid-area: 1 / 1;
  overflow-y: auto;
  /* Reserve the bar's width so the column does not shift sideways the moment
     a conversation becomes long enough to scroll. */
  scrollbar-gutter: stable;
  padding: 24px;
  /* Room for the composer floating over the bottom, so the last answer can be
     scrolled clear of it rather than ending underneath. */
  padding-bottom: 132px;
  min-height: 0;
}

/* The reading column lives inside the scroller, so text stays a comfortable
   width while the scrollbar stays at the far right. */
#message-list {
  display: flex;
  flex-direction: column;
  gap: 22px;
  max-width: 760px;
  margin: 0 auto;
}

/* With nothing asked yet the column has no content to give it height, so it
   is stretched to fill the scroller and the note's auto margins take the
   free space above and below it. The scroller's own bottom padding keeps the
   note clear of the composer floating there. */
#message-list:has(> .empty) { min-height: 100%; }

/* ---- prompt rail ---- */

/* Sits in the same cell as the transcript, pinned to the right edge and
   centred vertically, just inside the scrollbar.
   The rail is a layer over the transcript, so it takes no pointer of its own —
   only the ticks and the panel do. Otherwise a strip down the right of the
   window would quietly eat clicks and text selection meant for the answer
   behind it. There is no left padding, so the panel's right edge meets the
   ticks with nothing in between: a dead gap there would close the panel as the
   pointer crossed it. */
#prompt-rail {
  grid-area: 1 / 1;
  justify-self: end;
  align-self: center;
  position: relative;
  display: flex;
  padding: 10px 12px 10px 0;
  pointer-events: none;
}
#prompt-rail[hidden] { display: none; }
#rail-ticks, #rail-list { pointer-events: auto; }

#rail-ticks {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* A long session outgrows the window; the rail scrolls rather than running
     off both ends, and does it without a scrollbar of its own beside the
     transcript's.
     The second term keeps it clear of the composer: the rail is centred, so
     every 2px of height costs 1px at each end, and a tick that ends up behind
     the input bar is one that cannot be clicked at all. */
  max-height: min(62vh, calc(100vh - 220px));
  overflow-y: auto;
  scrollbar-width: none;
}
#rail-ticks::-webkit-scrollbar { display: none; }

.rail-tick {
  width: 18px;
  height: 3px;
  padding: 0;
  border: none;
  border-radius: 2px;
  background: var(--fg-muted);
  opacity: 0.45;
  flex: 0 0 auto;
}
.rail-tick:hover:not(:disabled) { background: var(--fg-muted); opacity: 0.85; }
/* Colour alone marks the current one. Widening it as well made the rail's
   edge ripple as you scrolled, which drew the eye to the movement rather than
   to where you are. */
.rail-tick[aria-current="true"] {
  background: var(--fg);
  opacity: 1;
}

/* The panel opens on the transcript side of the rail, vertically centred on
   it, and only while the pointer is there. It is a way back to a question,
   not a second sidebar. */
#rail-list {
  position: absolute;
  right: 100%;
  top: 50%;
  transform: translateY(-50%);
  /* Hidden until the rail is opened, which is the only rule that sets a
     display on this element. flex-direction below is inert while it is none
     and applies the moment .open turns it into a flex column — declaring the
     flex here as well is what left the panel permanently on screen. */
  display: none;
  flex-direction: column;
  /* Sized to the longest question it holds, up to a ceiling — past which the
     entries are elided rather than the panel growing across the answer it is
     supposed to be helping you get back to. A fixed width instead would make
     a session of short questions open a panel mostly full of nothing. */
  width: max-content;
  min-width: min(220px, 40vw);
  max-width: min(360px, 40vw);
  max-height: 66vh;
  overflow-y: auto;
  padding: 8px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 14px 36px rgb(0 0 0 / 0.3);
}
/* Opened by script rather than by :hover. A tick keeps DOM focus after it is
   clicked, so any selector involving focus left the panel sitting over the
   answer the click had just jumped to — and "close once the click that used
   you has landed" is not something CSS can say. */
#prompt-rail.open #rail-list { display: flex; }

.rail-item {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  border-radius: 9px;
  padding: 9px 11px;
  font-size: 13px;
  color: var(--fg-muted);
  /* One line each: the panel is a list of handles, and a wrapped paragraph per
     entry would make it the thing to read rather than the way back. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Same treatment as the rest of the list — only the colour says which one you
   are in, exactly as on the ticks. A filled row or a heavier weight made the
   panel look like it had a selection to act on. */
.rail-item[aria-current="true"] { color: var(--fg); }

/* Floats over the transcript rather than sitting in a row of its own. The
   background stays opaque under the pill and the notice — text sliding
   underneath must not show through the words you are about to read — and the
   seam is softened by a fade above it instead of a rule. */
#composer {
  grid-area: 1 / 1;
  align-self: end;
  position: relative;
  /* Over the rail, not under it. The bar is opaque, so a tick showing through
     it would be a tick nobody can see the meaning of — and worse, one that
     takes the click meant for the box you are typing into. */
  z-index: 1;
  background: var(--bg);
  padding: 6px 24px 18px;
}
/* The fade is the one part of the composer that sits over live transcript, so
   it is the one part that must not take a pointer. */
#composer::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: 32px;
  background: linear-gradient(to top, var(--bg), transparent);
  pointer-events: none;
}

/* Above the composer, as the last thing read before typing. Small and muted:
   it is a standing caveat, not a warning about this particular answer. */
.composer-note {
  max-width: 760px;
  margin: 0 auto 8px;
  text-align: center;
  font-size: 12px;
  color: var(--fg-muted);
}

/* Side carries the speaker, so no "You" / "eBRIDGE" labels are needed:
   the participant's own words sit right, eBRIDGE sits left. */
.msg { display: flex; flex-direction: column; gap: 6px; }
.msg.user { align-items: flex-end; }
.msg.assistant { align-items: flex-start; }
.msg .body { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---- rendered markdown ---- */

/* pre-wrap is right for a participant's literal text but wrong once the answer
   is real elements: it would double every blank line between paragraphs. */
.msg .body.markdown { white-space: normal; }

.markdown > :first-child { margin-top: 0; }
.markdown > :last-child { margin-bottom: 0; }

.markdown .md-p { margin: 0 0 0.75em; }
.markdown .md-h { margin: 1.1em 0 0.45em; line-height: 1.3; }
.markdown h2.md-h { font-size: 17px; }
.markdown h3.md-h { font-size: 15px; }
.markdown h4.md-h, .markdown h5.md-h, .markdown h6.md-h { font-size: 14px; }

.markdown .md-list { margin: 0 0 0.75em; padding-left: 1.4em; }
.markdown .md-list li { margin: 0.15em 0; }

.markdown .md-quote {
  margin: 0 0 0.75em;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--line);
  color: var(--fg-muted);
}

.markdown .md-inline-code {
  font-family: var(--mono);
  font-size: 0.9em;
  background: var(--bg-sunk);
  border-radius: 4px;
  padding: 1px 5px;
}

.markdown .md-code {
  font-family: var(--mono);
  font-size: 12.5px;
  background: var(--bg-sunk);
  border-radius: 6px;
  padding: 10px 12px;
  margin: 0 0 0.75em;
  /* Wide code scrolls inside its own box rather than stretching the column. */
  overflow-x: auto;
  white-space: pre;
}
.markdown .md-code code { background: none; padding: 0; }

.markdown .md-link { color: var(--accent); }

/* Wide tables scroll inside their own box rather than stretching the reading
   column, which would push the answer text off screen. */
.markdown .md-table-wrap { overflow-x: auto; margin: 0 0 0.85em; }

.markdown .md-table {
  border-collapse: collapse;
  font-size: 13.5px;
  width: auto;
  min-width: 100%;
}
.markdown .md-table th,
.markdown .md-table td {
  border: 1px solid var(--line);
  padding: 7px 11px;
  text-align: left;
  vertical-align: top;
  white-space: normal;
}
.markdown .md-table th {
  background: var(--bg-soft);
  color: var(--fg);
  font-weight: 600;
  white-space: nowrap;
}

.markdown hr { border: none; border-top: 1px solid var(--line); margin: 1em 0; }

/* A wikilink that resolved to a citation — same affordance as the citation
   chips below the answer, so it is obviously the same kind of thing. */
.markdown .md-wikilink {
  font: inherit;
  background: none;
  border: none;
  border-bottom: 1px dashed var(--accent);
  border-radius: 0;
  color: var(--accent);
  padding: 0;
  cursor: pointer;
}
.markdown .md-wikilink:hover { background: var(--bg-sunk); }

.msg.user .body {
  background: var(--bubble);
  color: var(--bubble-fg);
  border-radius: var(--radius);
  padding: 10px 14px;
  max-width: 78%;
}

/* The answer is long-form and needs the column width; a bubble around it would
   only narrow the text for no gain. */
.msg.assistant .body { max-width: 100%; }

.msg.error .body { color: var(--danger); }

/* The in-bubble progress line. Sits where the answer will appear, so a long
   silent retrieval reads as "working" rather than "hung". */
.thinking { color: var(--fg-muted); font-style: italic; }

.rewritten {
  font-size: 12px;
  color: var(--fg-muted);
  border-right: 2px solid var(--line);
  padding-right: 8px;
  text-align: right;
  max-width: 78%;
}

.citations { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }

.cite {
  font-size: 12px;
  border: 1px solid var(--line);
  background: var(--bg-soft);
  color: var(--fg-muted);
  border-radius: 999px;
  padding: 3px 11px;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cite:hover { color: var(--accent); border-color: var(--accent); background: var(--bg-soft); }

.meta { font-size: 11px; color: var(--fg-muted); }

.empty {
  color: var(--fg-muted);
  text-align: center;
  margin: auto;
  max-width: 420px;
}

/* The composer is one pill: the textarea has no chrome of its own and the
   send button sits inside the same rounded box, which is why the focus ring
   is drawn on the form rather than on the field. */
#ask-form {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  max-width: 760px;
  width: 100%;
  margin: 0 auto;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  /* Enough to round a single row into a capsule, and still a sane corner once
     a long question has grown the box to several lines. */
  border-radius: 26px;
  padding: 6px 6px 6px 18px;
}
#ask-form:focus-within { border-color: var(--accent); }

#question {
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  /* The field's own line box is shorter than the button beside it, so it is
     centred against the button rather than sat on the same baseline. */
  margin: 8px 0;
  resize: none;
  max-height: 200px;
  overflow-y: auto;
}
#question:focus { outline: none; }

/* The round send button. 38px is the smallest circle that still reads as a
   button beside 15px text and stays above the 24px touch floor. */
#ask-button {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-fg);
  display: grid;
  place-items: center;
}
#ask-button svg { width: 20px; height: 20px; display: block; }
#ask-button:hover:not(:disabled) { background: var(--accent); filter: brightness(1.08); }
/* Nothing to send is the resting state, so it dims rather than looking
   broken — the ordinary disabled treatment is for the seconds while an
   answer streams. */
#ask-button:disabled { opacity: 0.4; }

/* Used by the setup wizard's own progress line; chat progress lives inside the
   answer bubble instead. */
.status {
  max-width: 760px;
  width: 100%;
  margin: 8px auto 0;
  font-size: 13px;
  color: var(--fg-muted);
}

/* The boot placeholder stays invisible for a beat before fading in. A reload
   that resolves in 80ms should look like nothing happened; only a genuinely
   slow one earns a spinner, and by then the participant wants to see it. */
.boot {
  animation: boot-in 200ms ease-out 400ms both;
}
@keyframes boot-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .boot { animation: none; opacity: 1; }
}

.dots::after {
  content: "";
  animation: dots 1.2s steps(4, end) infinite;
}
@keyframes dots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }

/* ---- knowledge view ---- */

#source-panel {
  border-left: 1px solid var(--line);
  background: var(--bg-soft);
  display: grid;
  /* Head pinned, document scrolling under it: the title of what is being read
     must stay visible in an article that runs to several screens. */
  grid-template-rows: auto 1fr;
  min-height: 0;
  min-width: 0;
}
#source-panel[hidden] { display: none; }
#source-panel:focus { outline: none; }
#source-panel:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--line);
  padding: 10px 12px;
  min-width: 0;
}

/* The name and the file are one unit: the name is what the participant asked
   for, the path is how they would find it again outside the portal. */
.panel-heading { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.panel-heading strong { font-size: 14.5px; line-height: 1.35; }

.panel-path {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Square, quiet buttons for the panel's own controls: they sit beside a title
   and should not compete with it. */
button.icon {
  border: none;
  background: none;
  color: var(--fg-muted);
  padding: 4px 7px;
  line-height: 1;
  font-size: 14px;
  flex: 0 0 auto;
}
button.icon:hover:not(:disabled) { background: var(--bg-sunk); color: var(--fg); }
button.icon[hidden] { display: none; }
button.icon[aria-pressed="true"] { color: var(--accent); }

#source-scroll {
  overflow-y: auto;
  min-height: 0;
  padding: 14px 16px 40px;
}

/* A document reads at a slightly larger size than a chat bubble and needs the
   extra leading; it is prose someone stays inside for a few minutes. */
#source-body.doc { font-size: 14px; line-height: 1.62; overflow-wrap: break-word; }
#source-body.doc .md-h { margin-top: 1.5em; }
/* An article's own H1 becomes the panel title, so its sections arrive as h3
   after the renderer's demotion: those are the rules a reader scans for. */
#source-body.doc h3.md-h {
  font-size: 15px;
  border-bottom: 1px solid var(--line);
  padding-bottom: 4px;
}
#source-body.doc h4.md-h { font-size: 14px; }

.doc-status { color: var(--fg-muted); font-style: italic; }
.doc-status.error { color: var(--danger); font-style: normal; }

/* A link that led nowhere. Said in the panel, above the article still on
   screen, rather than by replacing what the participant was reading. Sticky
   because the click that earned it happened wherever they had scrolled to, and
   an answer painted off the top of the panel is no answer. */
.doc-note {
  position: sticky;
  top: 0;
  z-index: 1;
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--fg);
  background: var(--bg-sunk);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  padding: 8px 10px;
}
.doc-note[hidden] { display: none; }

/* What the wiki knows about the article, as the wiki's own labels. */
.doc-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }

.doc-chip {
  font-size: 11.5px;
  color: var(--fg-muted);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 9px;
}

/* Says what kind of document is on screen — article, summary, or the original
   behind it. Weighted differently from the wiki's own fields beside it because
   it is the one a reader needs before reading a word of the body. */
.doc-chip.kind { color: var(--fg); border-color: var(--fg-muted); font-weight: 600; }

.doc-actions { margin-bottom: 12px; }

/* The trail between documents: a source path, or the way to a summary. Reads as
   a link because that is what it is, without borrowing the accent colour that
   marks a [[wikilink]] inside prose. */
button.doc-link {
  font: inherit;
  font-size: inherit;
  text-align: left;
  background: none;
  border: none;
  border-radius: 0;
  padding: 0;
  color: var(--accent);
  text-decoration: underline;
  overflow-wrap: anywhere;
}
button.doc-link:hover:not(:disabled) { background: none; filter: brightness(1.15); }
.doc-actions button.doc-link { font-size: 12.5px; }

.doc-details { font-size: 12px; color: var(--fg-muted); margin-bottom: 14px; }
.doc-details summary { cursor: pointer; }
.doc-details dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  margin: 8px 0 0;
}
.doc-details dt { font-weight: 600; }
.doc-details dd { margin: 0; overflow-wrap: anywhere; }

/* ---- admin ---- */

/* The dialog is sized as a share of the window with a ceiling: the tables
   inside want room, but a modal stretched to a 27" screen stops reading as a
   panel over the chat and starts reading as a screen change, which is the
   thing it replaced. */
#admin-dialog, #docs-dialog {
  width: min(1040px, 94vw);
  height: min(720px, 88vh);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--fg);
  box-shadow: 0 18px 48px rgb(0 0 0 / 0.28);
  overflow: hidden;
}
/* Not on the element itself: a closed <dialog> is display:none, and a flex
   display here would override that and leave the panel on screen. */
/* Documents get more room than the admin tools do. Those are read —
   a table of participants and a status list — while this one is written in,
   and an editor sized for a table is a cramped page to compose on. */
#docs-dialog {
  width: min(1280px, 96vw);
  height: min(880px, 92vh);
}

/* Maximised: the whole window, for writing. The max-* overrides are not
   optional — a <dialog> carries a UA max-width and max-height of roughly the
   viewport minus its own margins, and without lifting those the width below
   would be clamped just short of the edge. */
#docs-dialog.maximised {
  width: 100vw;
  height: 100vh;
  max-width: 100vw;
  max-height: 100vh;
  margin: 0;
  border: none;
  border-radius: 0;
}

#admin-dialog[open], #docs-dialog[open] { display: flex; flex-direction: column; }
#admin-dialog::backdrop, #docs-dialog::backdrop { background: rgb(0 0 0 / 0.42); }

#admin-dialog .dialog-head, #docs-dialog .dialog-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px 12px 18px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-soft);
}
#admin-dialog .dialog-head strong,
#docs-dialog .dialog-head strong { flex: 1; font-size: 15px; }

/* The tab rail keeps the sidebar shape the full-screen view had, so the tools
   are in the same place they always were — only the frame changed. */
#admin-dialog .dialog-body, #docs-dialog .dialog-body {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  flex: 1;
  min-height: 0;
}

#admin-main, #docs-main { overflow-y: auto; padding: 24px 28px; min-height: 0; }
#admin-main h2, #docs-main h2 { margin-top: 0; font-size: 18px; }
#admin-main section, #docs-main section { margin-bottom: 32px; }

.tab { text-align: left; border: none; background: none; padding: 8px 10px; border-radius: 6px; }
.tab.active { background: var(--bg-sunk); font-weight: 600; }

table { border-collapse: collapse; width: 100%; font-size: 13px; }
th, td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
th { color: var(--fg-muted); font-weight: 600; white-space: nowrap; }
td.code { font-family: var(--mono); }
/* Rows that carry a control. A button is taller than the text beside it, so
   top alignment leaves every other cell floating above its own row. Asked for
   by name rather than applied to all tables: the activity log's detail column
   wraps to several lines, and a wrapped paragraph still reads from the top. */
table.rows-centred td { vertical-align: middle; }

.row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 14px; }
.row > * { flex: 0 0 auto; }
/* A cluster inside a row that shows and hides as one — the activity log's
   Advanced dates and their button, beside the period chooser they belong to.
   The display rule below beats the browser's own for the attribute, which a
   bare [hidden] would lose to. */
.field-group { display: flex; gap: 10px; align-items: flex-end; }
.field-group[hidden] { display: none; }
/* The activity log's page turner. Centred rather than bottom-aligned like a
   field row, so the count between the buttons sits level with their labels. */
.row.pager { align-items: center; margin: 12px 0 0; }
.field { display: flex; flex-direction: column; gap: 4px; }
.field input, .field select { width: 180px; }

.pill { font-size: 12px; padding: 2px 9px; border-radius: 999px; border: 1px solid var(--line); }
.pill.ok { color: var(--ok); border-color: var(--ok); }
.pill.bad { color: var(--danger); border-color: var(--danger); }

/* ---- one account, and your own password ---- */

/* Narrower than the tool dialogs: these are forms with four fields, and a
   1000px-wide form makes each label a long way from the box it names. */
#user-dialog, #password-dialog {
  width: min(520px, 92vw);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--fg);
  box-shadow: 0 18px 48px rgb(0 0 0 / 0.28);
}
#user-dialog[open], #password-dialog[open] { display: flex; flex-direction: column; }
#user-dialog::backdrop, #password-dialog::backdrop { background: rgb(0 0 0 / 0.42); }
#user-dialog .dialog-head, #password-dialog .dialog-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px 12px 18px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-soft);
}
#user-dialog .dialog-head strong, #password-dialog .dialog-head strong {
  flex: 1;
  font-size: 15px;
}

.dialog-form { padding: 18px; overflow-y: auto; }
.dialog-form .field.wide { margin-bottom: 16px; }
.dialog-form input, .dialog-form select { width: 100%; }
/* The sentence under a field, for the things a label cannot say: what a
   username may contain, what leaving a password blank actually does. */
.field-hint { font-size: 12px; color: var(--fg-muted); line-height: 1.45; }
.dialog-actions { margin: 4px 0 0; }

/* A username is a value that gets typed and compared, so it is set in the same
   face as the code blocks rather than in prose. */
.mono { font-family: var(--mono); font-size: 13px; }

pre.codes {
  font-family: var(--mono);
  background: var(--bg-sunk);
  border-radius: 6px;
  padding: 12px;
  overflow-x: auto;
}

@media (max-width: 800px) {
  .app,
  .app:has(#source-panel:not([hidden])),
  .app.source-wide:has(#source-panel:not([hidden])),
  /* The collapsed rail's own templates are more specific than the bare .app
     above, so they have to be named here or a window narrowed while collapsed
     keeps a 64px track for a sidebar this width does not show. */
  .app.nav-collapsed,
  .app.nav-collapsed:has(#source-panel:not([hidden])),
  .app.nav-collapsed.source-wide:has(#source-panel:not([hidden])) { grid-template-columns: 1fr; }

  /* Auto-placement again. The pinned columns are for the two- and three-pane
     layout; here there is one column for everything, and a #chat-main still
     pinned to the second would be placed in an implicit track beside an empty
     one — the transcript in a strip down the right of a blank window. */
  #sidebar, #chat-main, #source-panel { grid-column: auto; }

  #sidebar, #admin-nav { display: none; }
  .app.show-sidebar #sidebar { display: flex; }

  /* The tab rail becomes a strip across the top rather than disappearing:
     these tabs are the only way to a document waiting for review, and a
     dialog that opens on one list with no way to the others is a dead end. */
  #docs-dialog .dialog-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  #docs-nav {
    flex-direction: row;
    overflow-x: auto;
    border-right: none;
    border-bottom: 1px solid var(--line);
    padding: 8px 10px;
  }
  #docs-nav .tab { white-space: nowrap; }
  #docs-main { padding: 18px 16px; }

  /* No room for a column beside the chat, so the document covers it. Squeezing
     both into stacked rows left a few lines of each. */
  #source-panel {
    position: fixed;
    inset: 0;
    z-index: 20;
    border-left: none;
    background: var(--bg);
  }
  #source-wide { display: none; }
}

/* ---- setup wizard ---- */

.card.wide { max-width: 560px; }

.steps {
  display: flex;
  gap: 18px;
  list-style: none;
  padding: 0;
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--fg-muted);
  counter-reset: step;
}
.steps .step { counter-increment: step; }
.steps .step::before {
  content: counter(step);
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  margin-right: 7px;
  border-radius: 50%;
  border: 1px solid var(--line);
  font-size: 11px;
  vertical-align: middle;
}
.steps .step.active { color: var(--fg); font-weight: 600; }
.steps .step.active::before { border-color: var(--accent); color: var(--accent); }
.steps .step.done::before { content: "✓"; border-color: var(--ok); color: var(--ok); }

#setup-admin-form, #setup-wiki-form { display: flex; flex-direction: column; gap: 10px; }
#setup-admin-form[hidden], #setup-wiki-form[hidden] { display: none; }

/* ---- documents ---- */

/* The count of things waiting on you, pinned to the account row.
   Red and small: it has to be findable from across the sidebar without
   competing with the transcript, and it is the only red thing on this screen
   that is not an error. */
.badge {
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 20px;
  text-align: center;
  /* A badge is a label, not a target, but it sits inside a button and people
     will press it — app.js makes it open the review list. */
  cursor: pointer;
}
.badge[hidden] { display: none; }

/* The same count carried inward: on the menu entry that opens Documents, and
   on the tab the work is sitting on. Quieter than the sidebar badge — that one
   has to be found from across the page, while these are read once you are
   already looking at them, and four loud red dots would be an alarm. */
.count-badge {
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--danger);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}

/* Their hosts have to make room at the end of the line for them. */
#account-menu #open-docs, #docs-nav .tab {
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The tab's own count is about that tab, so it sits with the label rather than
   pinned to the far edge of a 190px rail. */
#docs-nav .tab .count-badge { margin-left: 6px; }

/* A form field that spans the pane, for the title and the editor. The .field
   default is a 180px column, which is right for a code and wrong for a
   document. */
.field.wide { width: 100%; margin-bottom: 14px; }
.field.wide > input, .doc-title-input { width: 100%; font-size: 15px; }

/* The picker and the button that acts on what it holds. Stacked and touching,
   "Submit for review" read as a caption under the chosen name rather than as
   the control that sends it there. */
.send-row {
  align-items: flex-end;
  gap: 20px;
  margin-top: 22px;
  margin-bottom: 6px;
}
.send-row > button { margin-bottom: 1px; }

/* ---- writing a document ---- */

/* The compose tab is a column the height of the dialog rather than a block
   that scrolls: the editor takes whatever the fixed rows leave, so the page
   being written on is as large as the window allows. The alternative — a
   fixed-height box inside a scrolling pane — puts Submit somewhere below the
   fold that moves further away with every paragraph. */
#docs-main.compose {
  display: flex;
  flex-direction: column;
  /* The editor is the scroller now; a second one around it would give a long
     document two nested bars and a Submit button that scrolls out of reach. */
  overflow: hidden;
}
#docs-main.compose > h2, #docs-main.compose > p { flex: 0 0 auto; }

#docs-main.compose .field.grow {
  flex: 1;
  min-height: 0;
  margin-bottom: 0;
}
#docs-main.compose .field.grow .md-editor {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#docs-main.compose .field.grow .md-toolbar { flex: 0 0 auto; }
#docs-main.compose .field.grow .md-input,
#docs-main.compose .field.grow .md-preview {
  flex: 1;
  /* min-height beats the 260px floor these carry elsewhere, which would
     otherwise stop the box shrinking on a short window. */
  min-height: 0;
  overflow-y: auto;
  /* The height is the column's to decide; a drag handle here would fight it. */
  resize: none;
}

#docs-main.compose .send-row { flex: 0 0 auto; margin-bottom: 0; }
#docs-main.compose > p:last-child { margin-bottom: 0; }

/* ---- the person picker ---- */

/* A <select> renders its options as single-styled strings, so a name over a
   grey role is not something it can be asked for. This is the replacement;
   picker.js carries the keyboard behaviour that came free with the native one. */
.picker { position: relative; width: 260px; }

.picker-button, .picker-option {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  text-align: left;
  border-radius: 8px;
}
.picker-button {
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--fg);
  padding: 8px 10px;
}
.picker-button:hover:not(:disabled) { border-color: var(--fg-muted); background: var(--bg); }
.picker-button[aria-expanded="true"] { border-color: var(--accent); }

.picker-name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}
/* What this person is, under the name that identifies them. Smaller and grey
   so the list still scans as a list of people rather than of pairs. */
.picker-note {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  color: var(--fg-muted);
}
.picker-caret { flex: 0 0 auto; margin-left: auto; color: var(--fg-muted); font-size: 11px; }

.picker-list {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 5;
  max-height: 280px;
  overflow-y: auto;
  padding: 4px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 28px rgb(0 0 0 / 0.24);
}
.picker-list[hidden] { display: none; }

/* Opening upward, when picker.js finds there is no room below. The send row
   lives at the foot of the compose tab, so this is the usual case there. */
.picker.up .picker-list {
  top: auto;
  bottom: calc(100% + 4px);
}

.picker-option { border: none; background: none; padding: 7px 9px; }
.picker-option:hover:not(:disabled) { background: var(--bg-sunk); }
.picker-option[aria-selected="true"] { background: var(--bg-soft); font-weight: 600; }
.picker-option[aria-selected="true"] .picker-note { font-weight: 400; }
.picker-empty { margin: 6px 9px; font-size: 13px; color: var(--fg-muted); }

.md-editor {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg);
}
.md-toolbar {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 5px 6px;
  background: var(--bg-soft);
  border-bottom: 1px solid var(--line);
}
.md-toolbar-gap { flex: 1; }
.md-tool {
  border: none;
  background: none;
  border-radius: 6px;
  padding: 4px 8px;
  font-size: 13px;
  min-width: 28px;
  color: var(--fg-muted);
}
.md-tool:hover:not(:disabled) { background: var(--bg-sunk); color: var(--fg); }
.md-preview-toggle[aria-pressed="true"] { background: var(--bg-sunk); color: var(--fg); }

.md-input {
  display: block;
  width: 100%;
  min-height: 260px;
  resize: vertical;
  border: none;
  border-radius: 0;
  padding: 12px 14px;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 13.5px;
  line-height: 1.6;
}
.md-input:focus { outline: none; }
.md-input[hidden] { display: none; }
/* Matched to the textarea so toggling between the two does not resize the
   dialog under the reader. */
.md-preview { min-height: 260px; padding: 12px 16px; }
.md-preview[hidden] { display: none; }

/* ---- one document ---- */

/* The detail view is a fixed bar over a scrolling body, so the two panes of
   the history can each own their height rather than growing the page. */
#docs-main.detail {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
}

/* Which document this is, what state it is in, and whose desk it is on —
   still there after scrolling through a long one and its whole history. */
.doc-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-soft);
}
.doc-back {
  flex: 0 0 auto;
  border: 1px solid var(--line);
  background: var(--bg);
  border-radius: 8px;
  width: 30px;
  height: 30px;
  line-height: 1;
}
.doc-bar-main { min-width: 0; flex: 1; }
.doc-bar-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
.doc-bar-title strong {
  font-size: 15px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* One line, clipped: the bar is furniture, and every row it takes is a row off
   the document. The whole thing is in the title attribute. */
.doc-bar-meta {
  font-size: 12px;
  color: var(--fg-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.doc-bar-meta.filed { color: var(--ok); }

.doc-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 20px; }

.doc-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.doc-head h2 { margin: 0; }

.doc-review, .doc-current { margin-top: 24px; }
.doc-review h3, .doc-current h3, #docs-main h3 { font-size: 15px; margin-bottom: 6px; }
/* The reviewer's workspace is lifted off the page: it is the one region here
   that acts rather than reports. */
.doc-review {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
}
.doc-rendered {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 4px 16px;
  background: var(--bg);
  max-height: 460px;
  overflow-y: auto;
}

/* ---- history ---- */

/* The steps down one side, the version each produced beside them. Rows that
   expanded in place buried the list under whichever one was open, so reaching
   the next step meant scrolling back past a whole document. */
.doc-history {
  display: grid;
  grid-template-columns: minmax(220px, 300px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.doc-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 62vh;
  overflow-y: auto;
}
.doc-event { border-bottom: 1px solid var(--line); }
.doc-event-head {
  /* Stacked rather than in a row: this column is narrow now, and a summary
     elided to three words is not a summary. */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  width: 100%;
  border: none;
  background: none;
  padding: 9px 10px;
  text-align: left;
  border-radius: 6px;
}
.doc-event-head:hover:not(:disabled) { background: var(--bg-soft); }
/* The step being read. A left edge rather than a fill, so the selected row
   reads as a position in a list rather than as a warning. */
.doc-event-head[aria-current="true"] {
  background: var(--bg-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* The pane beside the list: what that step produced, scrolling on its own. */
.doc-version {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
  max-height: 62vh;
  overflow-y: auto;
  padding: 0 14px 14px;
}
/* Sticky inside the pane, so a scrolled version still says which one it is. */
.doc-version-head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 -14px 10px;
  padding: 10px 14px;
  background: var(--bg-soft);
  border-bottom: 1px solid var(--line);
}
.doc-version-step {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
:root:lang(zh) .doc-version-step { text-transform: none; letter-spacing: 0; font-size: 12px; }
.doc-version-text { flex: 1; min-width: 0; font-size: 13px; }

/* Inside the pane the boxes fill it instead of scrolling within a scroller:
   two nested bars for one document is one too many. */
.doc-version .doc-rendered {
  border: none;
  border-radius: 0;
  padding: 0;
  max-height: none;
  overflow: visible;
}
.doc-version .diff-lines { max-height: none; }

@media (max-width: 900px) {
  .doc-history { grid-template-columns: minmax(0, 1fr); }
  .doc-timeline { max-height: 220px; }
  .doc-version { max-height: 50vh; }
}
/* The kind of event, as a fixed-width lead-in so the sentences beside them
   line up down the column and the shape of a review is readable at a glance. */
.doc-event-type {
  flex: 0 0 auto;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
:root:lang(zh) .doc-event-type { text-transform: none; letter-spacing: 0; font-size: 12px; }
.doc-event.confirm .doc-event-type { color: var(--ok); }
.doc-event-text { min-width: 0; font-size: 13.5px; }
.doc-event-when { flex: 0 0 auto; font-size: 11.5px; color: var(--fg-muted); }

/* The reviewer's own controls: save, hand on, confirm — in the order a review
   runs and the order of how final each step is. */
.review-actions { align-items: flex-end; gap: 12px; margin-top: 4px; }
.review-actions .picker { width: 200px; }

/* Two buttons in one table cell. */
.cell-actions { display: flex; gap: 6px; }

/* ---- comparison ---- */

.doc-diff-title { font-size: 13px; }
.diff-lines {
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: auto;
  max-height: 420px;
  background: var(--bg);
}
.diff-line {
  padding: 0 10px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* Tinted rather than filled, and every line already carries a +/− marker: the
   two shades of a diff are exactly the pair a red-green reader cannot
   separate, so colour is the second signal here and never the only one. */
.diff-line.add { background: color-mix(in srgb, var(--ok) 16%, transparent); }
.diff-line.remove { background: color-mix(in srgb, var(--danger) 15%, transparent); }
.diff-line.gap {
  color: var(--fg-muted);
  background: var(--bg-sunk);
  font-family: var(--sans);
  font-size: 11.5px;
  text-align: center;
}

/* ---- notifications ---- */

.notice-list { display: flex; flex-direction: column; gap: 2px; }
.notice {
  display: flex;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  border: none;
  background: none;
  border-radius: 8px;
  padding: 10px;
  text-align: left;
  border-bottom: 1px solid var(--line);
}
.notice:hover:not(:disabled) { background: var(--bg-soft); }
.notice-text { flex: 1; min-width: 0; font-size: 14px; }
.notice-when { flex: 0 0 auto; font-size: 12px; color: var(--fg-muted); }
/* Unread is carried by weight and a marker rather than by a background: the
   list is read top to bottom and a block of tinted rows reads as a warning. */
.notice.unread .notice-text { font-weight: 600; }
.notice.unread .notice-text::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 8px;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: middle;
}

pre.logs {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--bg-sunk);
  border-radius: 6px;
  padding: 10px;
  max-height: 260px;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
