/* Design system per claude-design/ (gitignored handoff) — see dev.md's
   "Design handoff review" for what was adopted vs. what got scoped out
   (reactions/threads/edit/delete/etc.). Google Talk (2008) + Mattermost's
   roster/transcript conventions remain the reference (plan.md's own stated
   goal); this file is the token/structure implementation of that direction.
   No build step, no framework beyond vendored Oat — load after Oat's CSS,
   override its variables directly so its own native components (buttons,
   dialogs, tables, popovers) pick up the same palette automatically. */
:root {
  color-scheme: light dark;

  /* --- Oat overrides -------------------------------------- */
  --background: #ffffff;
  --foreground: #18181b;
  --muted: #f4f4f5; /* hover fill */
  --muted-foreground: #52525b; /* secondary text */
  --border: #e6e6e9; /* THE hairline — 1px, never doubled */
  --input: #d4d4d8;
  --primary: #1c5fd6; /* mentions, links, focus */
  --primary-foreground: #ffffff;
  --secondary: #f4f4f5;
  --danger: #c2352d;
  --ring: #1c5fd6;
  --font-sans: var(--font);
  --font-mono: var(--mono);
  /* Oat ships several radius steps; the spec wants exactly one everywhere
     (rows, avatars, badges, inputs, buttons), with dialogs alone at 4px. */
  --radius-small: var(--radius);
  --radius-medium: var(--radius);
  --radius-large: 4px;

  /* --- talk-specific -------------------------------------- */
  --faint: #fafafa; /* hovered message row, table stripe */
  --faint-foreground: #8e8e96; /* timestamps, hints, section labels */
  --chrome: #f7f7f8; /* sidebar + composer well */
  --chrome-active: #e8e8ec; /* selected room row */
  --chrome-hover: #f0f0f2; /* hovered room row */
  --rail: #d4d4d8; /* sidebar/pane divider only */
  --disabled-fg: #a1a1aa; /* muted numerals, ⋯ glyphs at rest */
  --hint: #b4b4bb; /* ⌘K glyph, offline ring, "+" at rest */

  --presence-online: #1a7f37;
  --presence-away: #b8860b;
  --presence-busy: #c2352d;
  --presence-off: #b4b4bb; /* 1.5px ring, no fill */

  --mention-bg: #eaf0fd;
  --danger-rule: #f0d5d3; /* the "New" unread-divider hairline */

  --u: 4px; /* 4 8 12 16 24 32 — that's all */
  --sidebar-w: 248px;
  --row-h: 28px; /* every sidebar row */
  --header-h: 48px; /* workspace bar and room header */
  --gutter: 44px; /* 28px avatar + 16px gap */
  --text-chrome: 13px;
  --text-body: 15px;

  --radius: 3px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Auto dark mode (OS preference) — no manual toggle built (nobody's asked
   for one yet; the spec's own screens don't show a switch either). Every
   functional hue (--primary, --danger, --presence-*) stays identical;
   only the two surfaces invert. */
@media (prefers-color-scheme: dark) {
  :root {
    --background: #17181b;
    --foreground: #e8e8ea;
    --chrome: #131417;
    --chrome-active: #24262b;
    --chrome-hover: #1e2024;
    --muted: #1e2024;
    --muted-foreground: #a0a0a8;
    --faint: #1c1e22;
    --faint-foreground: #8b8b94;
    --border: #2a2c31;
    --input: #33353b;
    --rail: #2a2c31;
    --disabled-fg: #7a7a84;
    --hint: #6a6a74;
    --presence-off: #55555e;
    --mention-bg: #1a2740;
  }
}

body {
  font-family: var(--font);
  font-size: var(--text-chrome);
}

/* Real bug found via live testing: Oat's `[hidden]{display:none}` lives in
   a *layered* rule (@layer base), and this file has no @layer of its own —
   unlayered always beats layered regardless of specificity. So any class
   here that sets `display` (flex/grid rows are everywhere in this file)
   silently wins over the native `hidden` attribute on that same element,
   and JS's `.hidden = true` becomes a no-op — e.g. a non-owner still saw
   the "Add member" form because `.room-settings-add-member{display:flex}`
   overrode it. One defensive rule beats auditing/patching every current
   and future `display`-setting class individually.  */
[hidden] {
  display: none !important;
}

/* Design spec: "the only box-shadow in the entire product" — a 1px --ring
   border plus a soft 3px/.12-alpha halo — tried that first, but Vishnu
   found even the toned-down ring distracting on every plain mouse click
   into a text field, so it's off entirely on `:focus` now (overriding
   Oat's own default, which is the same idea at a heavier .2 alpha/2px).
   Left alone: Oat's separate `:focus-visible{outline:2px solid var(--ring)}`
   rule, which only fires for keyboard navigation — so tabbing through a
   form still shows a clear indicator, just not on every click. */
:where(input, textarea, select):focus {
  border-color: var(--input);
  box-shadow: none;
}

/* ---------- login page ---------- */

.login-page {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--u);
}

.login-card {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 6);
  width: 320px;
}

.login-card h1 {
  color: var(--foreground);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-block-end: 0;
}

.login-domain {
  color: var(--faint-foreground);
  font-family: var(--mono);
  font-size: 12.5px;
  margin-block-start: calc(var(--u) * -4);
}

.login-error {
  color: var(--danger);
  font-size: 12.5px;
}

/* Dialog-contextual errors ("username already taken") get a bit more
   visual weight than a bare line of red text — boxed so it reads as
   "this specific attempt failed," not as stray text loose in the form.
   Login's own auth-failure message stays a plain line per spec ("one
   message above the submit button, never a toast"). */
.login-sso-list {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2.5);
  margin-block-start: calc(var(--u) * 2);
}

.login-sso-divider {
  text-align: center;
  font-size: 12px;
  color: var(--faint-foreground);
}

.login-sso-btn {
  width: 100%;
}

.form-error {
  padding: calc(var(--u) * 1.5) calc(var(--u) * 2.5);
  border-radius: var(--radius);
  border: 1px solid var(--danger);
  background-color: rgba(194, 53, 45, 0.08);
  color: var(--danger);
  font-size: 12.5px;
  line-height: 1.4;
}

/* ---------- admin page ---------- */
/* The one screen allowed to look like a document (design spec §4): full
   page width, no app chrome, plain tables — everything else in the app is
   the sidebar+transcript shell. */

.admin-page {
  max-width: 1120px;
  margin-inline: auto;
  padding: calc(var(--u) * 8) calc(var(--u) * 6) calc(var(--u) * 20);
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 3.5);
}

/* Admin reads as "a document," not chat chrome (see .admin-page's own
   comment above) — sized up from the dense UI-chrome scale shared with
   chat's sidebar/composer, which stays small on purpose (found via live
   testing: 120% browser zoom made admin noticeably easier to read, chat
   didn't need it). Scoped to .admin-scope — a wrapper around admin_base.
   html's <main> *and* its dialogs, which are DOM siblings of <main>, not
   descendants, so a bare `.admin-page …` selector wouldn't reach them —
   rather than editing .field-label/.field-hint/etc. directly, since those
   are shared with chat's own dialogs (create-room, room-settings, …) and
   must stay at their current size there. */
.admin-scope .admin-table {
  font-size: 15px;
}

.admin-scope .admin-table-head {
  font-size: 13px;
}

.admin-scope .field-label {
  font-size: 14px;
}

.admin-scope .field-hint {
  font-size: 13.5px;
}

.admin-scope .admin-tab {
  font-size: 14.5px;
}

.admin-scope .admin-subtitle,
.admin-scope .admin-toolbar-summary {
  font-size: 13.5px;
}

.admin-scope .admin-domain {
  font-size: 13px;
}

.admin-scope .admin-settings-section-head {
  font-size: 14.5px;
}

.admin-head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2.5);
}

.admin-head-row {
  display: flex;
  align-items: baseline;
  gap: calc(var(--u) * 2.5);
}

.admin-back {
  text-decoration: none;
  font-size: 12.5px;
  color: var(--muted-foreground);
}

.admin-domain {
  margin-inline-start: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--disabled-fg);
}

.admin-title {
  font-size: 17px;
  font-weight: 600;
}

.admin-subtitle {
  font-size: 12.5px;
  color: var(--faint-foreground);
}

.admin-tabs {
  display: flex;
  gap: calc(var(--u) * 5);
  padding-block-end: calc(var(--u) * 2.25);
  border-block-end: 1px solid var(--border);
}

.admin-tab {
  text-decoration: none;
  font-size: 13px;
  color: var(--muted-foreground);
  border-block-end: 2px solid transparent;
  margin-block-end: -1px;
  padding-block-end: calc(var(--u) * 2.25);
}

.admin-tab-active {
  font-weight: 600;
  color: var(--foreground);
  border-block-end-color: var(--foreground);
}

.admin-tab-count {
  color: var(--disabled-fg);
  font-weight: 400;
}

.admin-toolbar {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2.5);
}

.admin-toolbar-simple {
  padding-block: calc(var(--u) * 0.5);
}

.admin-toolbar-hint {
  font-size: 12.5px;
  color: var(--faint-foreground);
}

.admin-toolbar-summary {
  font-size: 12.5px;
  color: var(--faint-foreground);
}

.admin-toolbar-primary {
  margin-inline-start: auto;
  height: 30px;
  padding: 0 calc(var(--u) * 3);
  font-size: 12.5px;
}

.admin-filter {
  display: flex;
  align-items: center;
  gap: var(--u);
  width: 260px;
  height: 30px;
  padding: 0 calc(var(--u) * 2.5);
  border: 1px solid var(--input);
  border-radius: var(--radius);
}

.admin-filter-icon {
  font-size: 14px;
  /* An emoji glyph ignores `color` entirely (rendered from its own
     built-in color font, not the text color) — filter+opacity is the way
     to actually mute one. Same fix as the sidebar's private-room lock. */
  filter: grayscale(1);
  opacity: 0.75;
}

.admin-filter input {
  flex: 1;
  height: 100%;
  line-height: 28px;
  border: none;
  outline: none;
  background: transparent;
  font-size: 12.5px;
}

.admin-status-filter {
  flex-shrink: 0;
  width: 148px;
  height: 30px;
  line-height: 28px;
  border: 1px solid var(--input);
  border-radius: var(--radius);
  padding: 0 calc(var(--u) * 2);
  font-size: 12.5px;
  background-color: var(--background);
  color: var(--foreground);
}

.admin-table {
  border: 1px solid var(--input);
  font-size: 13px;
}

.admin-table-head {
  height: 34px;
  align-items: center;
  padding: 0 calc(var(--u) * 6);
  background-color: var(--faint);
  border-block-end: 1px solid var(--border);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint-foreground);
}

.admin-table-head,
.admin-users-grid,
.admin-workspaces-grid,
.admin-archived-grid,
.admin-oidc-providers-grid,
.admin-pending-signups-grid,
.admin-webhooks-grid {
  display: grid;
}

.admin-users-grid {
  grid-template-columns: 220px 1fr 120px 120px 40px;
}

.admin-workspaces-grid {
  grid-template-columns: 200px 1fr 110px 90px 40px;
}

.admin-archived-grid {
  grid-template-columns: 220px 160px 1fr 140px 110px;
}

.admin-oidc-providers-grid {
  grid-template-columns: 160px 200px 1fr 100px 40px;
}

.admin-pending-signups-grid {
  grid-template-columns: 180px 220px 120px 120px 210px;
}

.admin-webhooks-grid {
  grid-template-columns: 200px 1fr 160px 40px;
}

.admin-settings-section {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2.5);
}

.admin-settings-section-head {
  font-size: 13px;
}

.admin-pending-signup-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: calc(var(--u) * 2);
}

.admin-row {
  align-items: center;
  height: 42px;
  padding: 0 calc(var(--u) * 6);
  border-block-end: 1px solid var(--muted);
}

.admin-row:hover {
  background-color: var(--faint);
}

.admin-row-user {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2.25);
}

.admin-row-user .avatar {
  width: 20px;
  height: 20px;
  font-size: 9px;
}

.admin-row-you {
  font-size: 12px;
  color: var(--disabled-fg);
}

.admin-row-sso-badge {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1px calc(var(--u) * 1.75);
  font-size: 11px;
  color: var(--muted-foreground);
}

.admin-row-muted {
  color: var(--muted-foreground);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-status {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 1.5);
  font-size: 12px;
}

.admin-status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: var(--presence-online);
  flex-shrink: 0;
}

.admin-status[data-status="deactivated"] {
  color: var(--faint-foreground);
}

.admin-status[data-status="deactivated"] .admin-status-dot {
  background-color: transparent;
  border: 1.5px solid var(--presence-off);
  box-sizing: border-box;
}

.admin-row-menu-trigger {
  visibility: hidden;
  justify-self: end;
  color: var(--disabled-fg);
  font-size: 15px;
  padding: 0;
}

.admin-row:hover .admin-row-menu-trigger {
  visibility: visible;
  color: var(--foreground);
}

.admin-row-workspace {
  min-height: 46px;
  height: auto;
  padding-block: calc(var(--u) * 2);
}

/* Only the admin workspaces table uses this now — the sidebar workspace
   switcher dropped its own colored initial-avatar for a plain "talk"
   wordmark instead (design review round 4, item 4a). */
.workspace-identity {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background-color: #3f3f46;
  color: #fff;
  border-radius: var(--radius);
  font-size: 10px;
  font-weight: 600;
}

.admin-member-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: calc(var(--u) * 1.5);
}

.admin-member-chip {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 1.5);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 2px 8px 2px 3px;
  font-size: 12px;
  color: var(--foreground);
}

.admin-member-chip .avatar {
  width: 16px;
  height: 16px;
  border-radius: 2px;
  font-size: 8px;
}

.admin-add-member-btn {
  background: none;
  border: none;
  padding: 2px 4px;
  font-size: 12px;
  color: var(--primary);
  cursor: pointer;
}

.admin-add-member-form {
  display: flex;
  align-items: center;
  gap: var(--u);
}

/* Shared by every dialog with a single search/filter field at the top —
   currently just workspace members (message search and find-people were
   replaced by the unified search dialog). */
.dialog-search-field {
  padding: calc(var(--u) * 3.5) calc(var(--u) * 5) calc(var(--u) * 2);
}

.dialog-search-field input {
  width: 100%;
  height: 32px;
  padding: 0 calc(var(--u) * 2.5);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  font-size: 13px;
}

/* Shared scrollable results area under a .dialog-search-field. Side
   padding matches .dialog-titlebar/.dialog-footer's own (--u*5) — it was
   --u*3 (12px vs their 20px), so list rows sat visibly further left than
   the title/footer above and below them. */
.dialog-results-list {
  padding: 0 calc(var(--u) * 5);
  max-height: 360px;
  overflow-y: auto;
}

.workspace-members-dialog-remove {
  margin-inline-start: auto;
  font-size: 12.5px;
  padding: 0;
}

.admin-add-member-form input {
  height: 24px;
  font-size: 12px;
  padding: 0 var(--u);
  border: 1px solid var(--input);
  border-radius: var(--radius);
}

/* Matches the input's own 24px height — Oat's default button padding made
   "Add" noticeably taller than the field it sits next to. */
.admin-add-member-form button {
  height: 24px;
  padding: 0 calc(var(--u) * 2);
  font-size: 12px;
}

.admin-unarchive-btn {
  justify-self: end;
  height: 24px;
  padding: 0 calc(var(--u) * 2.5);
  font-size: 12.5px;
}

.admin-table-footer {
  padding: calc(var(--u) * 3) calc(var(--u) * 6) calc(var(--u) * 4);
  font-size: 12px;
  color: var(--disabled-fg);
}

.admin-table-empty {
  padding: calc(var(--u) * 5) calc(var(--u) * 6);
  font-size: 13px;
  color: var(--faint-foreground);
}

.dialog-420 {
  width: 420px;
}

.field-inline {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2);
  padding-block-start: var(--u);
  font-size: 13px;
}

.field-hint {
  font-size: 12px;
  color: var(--faint-foreground);
}

.compound-field {
  display: flex;
  align-items: center;
  height: 32px;
  padding: 0 0 0 calc(var(--u) * 2.5);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  background-color: var(--background);
}

.compound-field input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: none;
  outline: none;
  padding: 0;
  font-size: 13px;
  background: transparent;
}

.compound-field-suffix {
  flex-shrink: 0;
  padding: 0 calc(var(--u) * 2.5);
  height: 100%;
  display: flex;
  align-items: center;
  background-color: var(--faint);
  border-inline-start: 1px solid var(--input);
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--faint-foreground);
}

.password-field {
  display: flex;
  align-items: center;
  height: 32px;
  padding: 0 calc(var(--u) * 2.5);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  background-color: var(--faint);
}

.password-field-value {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.02em;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.password-regenerate-btn {
  margin-inline-start: auto;
  color: var(--primary);
  font-size: 12px;
  padding: 0;
}

.inline-form {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--u);
}

.inline-form input {
  flex: 1;
  height: 32px;
  padding: 0 calc(var(--u) * 2.5);
  font-size: 13px;
  border: 1px solid var(--input);
  border-radius: var(--radius);
}

/* Matches the input's own 32px height — Oat's default button padding made
   "Add" noticeably taller than the field it sits next to. */
.inline-form button {
  height: 32px;
  padding: 0 calc(var(--u) * 3);
  font-size: 13px;
}

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

/* Unified search — Ctrl/Cmd+K, the sidebar trigger, and the room header's
   search button all open this same dialog (design: Vishnu's own mockup,
   ss/new/23.png). Two grouped sections: rooms/people (instant, client-side
   filter) and message content (server round-trip, debounced) — matches
   Slack's actual "search everything" pattern rather than keeping the
   rooms/people switcher and message search as two separate dialogs. */
.unified-search-dialog {
  width: 520px;
  padding: 0;
}

/* Oat's generic `dialog > :is(p,div,section)` rule (see the comment further
   down near .dialog-title) sets padding on ALL sides plus overflow-y:auto —
   overriding only padding-inline left the inherited vertical padding in
   place, which ate this row's entire fixed height and forced a spurious
   scrollbar. Reset both explicitly. */
.unified-search-input-row {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2.5);
  height: 48px;
  padding: 0 calc(var(--u) * 5);
  overflow: visible;
  border-block-end: 1px solid var(--border);
}

.unified-search-input-icon {
  flex-shrink: 0;
  font-size: 15px;
  filter: grayscale(1);
  opacity: 0.75;
}

.unified-search-input-row input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: none;
  outline: none;
  font-size: var(--text-body);
  background: transparent;
}

.unified-search-esc-hint {
  flex-shrink: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--hint);
}

.unified-search-results {
  max-height: 420px;
  overflow-y: auto;
  padding: var(--u);
}

.unified-search-section:not(:first-child) {
  margin-block-start: calc(var(--u) * 2);
}

.unified-search-section-header {
  display: flex;
  align-items: baseline;
  gap: var(--u);
  padding: calc(var(--u) * 1.5) calc(var(--u) * 2.5);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint-foreground);
}

.unified-search-section-count {
  margin-inline-start: auto;
  font-size: 11px;
  text-transform: none;
  letter-spacing: normal;
  color: var(--hint);
}

.unified-search-footer {
  display: flex;
  align-items: center;
  height: 36px;
  padding: 0 calc(var(--u) * 5);
  overflow: visible;
  border-block-start: 1px solid var(--border);
  font-size: 11.5px;
  color: var(--disabled-fg);
}

.unified-search-view-all {
  margin-inline-start: auto;
  color: var(--primary);
  text-decoration: none;
}

/* Message result row: avatar + header line (name, room · relative time) +
   snippet, same shape as a transcript message so it reads consistently. */
.unified-search-message-item {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  gap: calc(var(--u) * 2.5);
  width: 100%;
  padding: calc(var(--u) * 1.5) calc(var(--u) * 2.5);
  border-radius: var(--radius);
  text-align: start;
}

.unified-search-message-item:hover {
  background-color: var(--faint);
}

.unified-search-message-item.active {
  background-color: var(--mention-bg);
}

.unified-search-message-item .avatar {
  width: 20px;
  height: 20px;
  font-size: 9px;
  margin-block-start: 1px;
}

.unified-search-message-body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.unified-search-message-meta {
  display: flex;
  align-items: baseline;
  gap: var(--u);
  font-size: 12.5px;
}

.unified-search-message-sender {
  font-weight: 600;
  color: var(--foreground);
}

.unified-search-message-room {
  color: var(--faint-foreground);
}

.unified-search-message-snippet {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  color: var(--muted-foreground);
}

.unified-search-highlight {
  color: var(--primary);
  background-color: var(--mention-bg);
  border-radius: 2px;
}

/* Mobile (<=768px, Oat's own breakpoint): Oat's own [data-sidebar-header]
   ("[mark] talk" + hamburger-equivalent) becomes visible at this width and
   stacks directly above our .room-header — two header-shaped bars doing
   overlapping jobs on a screen with little room to spare. Full mobile nav
   (list view <-> conversation view, the way real chat apps handle this)
   is its own real design task, not done here — this just removes the
   most visibly broken part for now: search/⋯ redundant with the
   sidebar's own search (reachable via the hamburger toggle), room title
   stays so you still know where you are. */
@media (max-width: 768px) {
  .room-header-search,
  .room-header-menu-trigger {
    display: none;
  }
}

.quick-switcher-item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: calc(var(--u) * 2.5);
  width: 100%;
  height: 32px;
  padding: 0 calc(var(--u) * 2.5);
  border-radius: var(--radius);
  font-size: 13px;
  text-align: start;
}

.quick-switcher-item:hover {
  background-color: var(--faint);
}

/* Bolder than :hover on purpose — this is the "press Enter and this is what
   opens" state, and the plain --faint gray it used to share with :hover was
   too subtle to tell apart from "no selection at all". */
.quick-switcher-item.active {
  background-color: var(--mention-bg);
}

.quick-switcher-item-icon {
  flex-shrink: 0;
  width: 16px;
  text-align: center;
  color: var(--hint);
  font-size: 14px;
}

.quick-switcher-item .avatar {
  width: 20px;
  height: 20px;
  font-size: 9px;
}

.quick-switcher-empty {
  padding: calc(var(--u) * 4);
  text-align: center;
  font-size: 12.5px;
  color: var(--faint-foreground);
}

/* Now the sidebar-toggle button itself (see templates/app.html) — the
   whole "[mark] talk" combo is one tap target instead of a separate
   hamburger icon next to it. Resets Oat's default filled/bordered
   button chrome back to plain text+icon. */
[data-sidebar-header] .brand {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2);
  padding: 0;
  background: none;
  border: none;
  font-weight: 600;
  font-size: inherit;
  color: var(--foreground);
}

/* Chat-bubble mark — CSS shape, no image/SVG asset (matches the design
   spec's "no icon library" constraint). Geometry is ratio-driven off one
   height variable, not fixed per size, so the same rule works at any
   scale: body 6:5, radius = 1/4 height, tail a border-triangle sized off
   the same height. Filled only (spec: "below 20px the outline loses the
   tail — use filled"); every size we actually use is at or below that.
   Color is --foreground only, never the accent — already flips to
   #e8e8ea in dark mode for free, since that's how the token itself works. */
.talk-logo-mark {
  --logo-h: 17px;
  position: relative;
  display: inline-block;
  flex-shrink: 0;
  width: calc(var(--logo-h) * 6 / 5);
  height: var(--logo-h);
  background-color: var(--foreground);
  border-radius: calc(var(--logo-h) / 4);
}

.talk-logo-mark::after {
  content: "";
  position: absolute;
  left: 20%;
  bottom: calc(var(--logo-h) * -0.3);
  width: 0;
  height: 0;
  border-top: calc(var(--logo-h) * 0.4) solid var(--foreground);
  border-right: calc(var(--logo-h) * 0.4 - 1px) solid transparent;
}

.talk-logo-mark--lg {
  --logo-h: 24px;
  /* .login-card's flex gap (24px) is too much air between the mark and
     the wordmark right below it — pull them close, matching the mockup's
     tight stack. */
  margin-block-end: calc(var(--u) * -4.5);
}

.talk-logo-mark--sm {
  --logo-h: 14px;
}

.sidebar-branding {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--u);
  padding-block: calc(var(--u) * 1.5) calc(var(--u) * 1.5 + 2px);
  padding-inline: calc(var(--u) * 3);
  border-block-start: 1px solid var(--border);
  font-size: 11px;
  line-height: 1;
  color: var(--faint-foreground);
}

.sidebar-branding-name {
  font-weight: 600;
}

.sidebar-branding-version,
.sidebar-branding-domain {
  font-family: var(--mono);
}

.sidebar-branding-domain {
  margin-inline-start: auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A sibling of #app, not a child of it — Oat's [data-sidebar-header] (where
   this originally lived) is display:none above its 768px breakpoint,
   mobile-toggle chrome only, so it silently disappeared on desktop widths.
   Fixed-positioned so it's visible regardless of viewport size or sidebar
   collapse state. */
.connection-status {
  position: fixed;
  top: var(--u);
  inset-inline-start: 50%;
  transform: translateX(-50%);
  z-index: 10;
  padding: calc(var(--u) / 2) calc(var(--u) * 3);
  background-color: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-small);
  color: var(--presence-away);
  font-size: 12px;
  font-style: italic;
}

[data-sidebar] {
  background-color: var(--chrome);
  display: flex;
  flex-direction: column;
}

.workspace-bar {
  flex-shrink: 0;
  height: var(--header-h);
  border-block-end: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2.5);
  padding-inline: calc(var(--u) * 3);
}

.sidebar-wordmark {
  flex-shrink: 0;
  font-weight: 600;
}

.workspace-bar-divider {
  flex-shrink: 0;
  width: 1px;
  height: 16px;
  background-color: var(--border);
}

/* ot-dropdown (not display:flex itself) is the actual flex item here, so
   it's what needs flex:1 to claim the space "talk" + the divider don't —
   width:100% on the button inside then fills that expanded box. Scoped to
   this one spot; every other ot-dropdown (self-bar, room menu, etc.) stays
   its own natural size. */
.workspace-bar > ot-dropdown {
  flex: 1;
  min-width: 0;
}

.workspace-switcher {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--u);
  text-align: start;
}

#current-workspace-name {
  flex: 1;
  font-size: var(--text-chrome);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.workspace-caret {
  flex-shrink: 0;
  font-size: 14px;
  color: var(--muted-foreground);
}

/* Oat's generic [popover] min-width (12rem/192px) is tuned for menus with
   room to fill — way more than "Log out" or "Active/Away/Offline" need,
   reads as an oversized empty box. min-width still lets a genuinely long
   workspace name push wider on its own. */
#self-menu,
#self-presence-menu,
#workspace-menu {
  min-width: 140px;
}

.quick-switcher-trigger {
  flex-shrink: 0;
  width: calc(100% - 2 * var(--u) * 3);
  margin: calc(var(--u) * 2) auto;
  display: flex;
  align-items: center;
  gap: var(--u);
  text-align: start;
  height: 28px;
  padding: 0 12px;
  background-color: var(--background);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  color: var(--faint-foreground);
  font-size: 12.5px;
}

.quick-switcher-trigger-icon {
  flex-shrink: 0;
  font-size: 15px;
  filter: grayscale(1);
  opacity: 0.75;
}

.quick-switcher-trigger span:not(.quick-switcher-trigger-icon):not(.search-shortcut-hint) {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.search-shortcut-hint {
  flex-shrink: 0;
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--hint);
}

/* ---------- self bar ---------- */

.self-bar {
  flex-shrink: 0;
  margin-block-start: auto;
  border-block-start: 1px solid var(--border);
  padding: calc(var(--u) * 2) calc(var(--u) * 3);
  display: flex;
  align-items: center;
  gap: var(--u);
}

.self-bar-avatar .avatar {
  width: 20px;
  height: 20px;
  font-size: 9px;
}

.self-bar-handle {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  line-height: 1;
  font-size: 12.5px;
  color: var(--muted-foreground);
}

.self-bar-icon-btn {
  flex-shrink: 0;
  padding: calc(var(--u) / 2);
  min-width: 0;
  color: var(--disabled-fg);
  font-size: 15px;
}

/* ---------- room list ---------- */

/* Oat's own [data-sidebar] > nav rule sets overflow-y:auto but never
   overflow-x — per spec, leaving the other axis at its initial `visible`
   computes it as `auto` too, not `visible`. So any sub-pixel horizontal
   overflow (e.g. a row's bold-on-unread text rendering a hair wider than
   its regular-weight width, right at the edge of the available space)
   shows up as a real, if tiny, horizontal scrollbar. Same fix already
   applied to #message-pane for the identical Oat quirk. */
#room-list {
  overflow-x: hidden;
}

#room-list-items {
  font-size: var(--text-chrome);
  padding: calc(var(--u) * 1.5);
}

.room-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--u) calc(var(--u) * 2) calc(var(--u) / 2);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--faint-foreground);
}

.room-section-header:not(:first-child) {
  margin-block-start: calc(var(--u) * 2);
}

.room-section-add-btn {
  visibility: hidden;
  flex-shrink: 0;
  min-width: 0;
  padding: 0 calc(var(--u) / 2);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  color: var(--disabled-fg);
}

.room-section-header:hover .room-section-add-btn {
  visibility: visible;
}

.room-section-add-menu {
  min-width: 140px;
}

.room-section-empty {
  padding: calc(var(--u) / 2) calc(var(--u) * 2) calc(var(--u) * 1.5);
  font-size: 12px;
  color: var(--hint);
}

/* Four states, all grey + weight — never a second colour (design spec's
   colour rules: "unread is weight, not colour"). Fill and text-weight are
   independent channels, so selected-and-unread stays legible. */
.room-row {
  position: relative;
}

.room-row a {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2);
  height: var(--row-h);
  padding-inline: calc(var(--u) * 2);
  /* Bigger than the generic --radius (3px, tuned for buttons/dialogs) —
     on a 28px-tall row that reads as barely-rounded, not the floating
     pill hover/selected fill is going for. */
  border-radius: calc(var(--u) * 1.5);
  color: var(--muted-foreground);
  font-weight: 400;
}

.room-row a.unread {
  color: var(--foreground);
  font-weight: 600;
}

.room-row a:hover {
  background-color: var(--chrome-hover);
}

.room-row a[aria-current="page"] {
  background-color: var(--chrome-active);
  color: var(--foreground);
  font-weight: 600;
}

.room-row-overflow {
  visibility: hidden;
  position: absolute;
  right: calc(var(--u) * 2);
  top: 50%;
  transform: translateY(-50%);
  padding: 0 var(--u);
  min-width: 0;
  color: var(--disabled-fg);
  font-size: 15px;
}

.room-row:hover .room-row-overflow {
  visibility: visible;
}

.room-list-icon {
  flex-shrink: 0;
  width: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted-foreground);
  font-size: 14px;
}

.room-list-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.room-unread-count {
  flex-shrink: 0;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--faint-foreground);
}

/* Desaturated and small on purpose — privacy is the least actionable thing
   about a room, so it shouldn't be the one saturated colour in the
   sidebar. Sits after the name instead of in the leading icon slot, which
   stays "#" for every channel so rows actually line up. */
.room-list-lock {
  flex-shrink: 0;
  font-size: 10px;
  filter: grayscale(1);
  opacity: 0.5;
}

.plain-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 2);
}

/* ---------- avatars ---------- */

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 600;
  flex-shrink: 0;
}

/* ---------- message pane ---------- */

#message-pane {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow-x: hidden;
  /* Oat gives every <main> a default padding-block-start (base page-layout
     assumption) — never overridden here before because nothing else set
     `padding` on this rule for that property to compete with. Pushed the
     whole header down inside its own box, which looked exactly like a
     height mismatch against the sidebar (not a <main>, so unaffected)
     even though .room-header's own height was correct the whole time. */
  padding-block-start: 0;
}

/* Everything below the (now always-present, static) room header — a
   transparent flex pass-through so .message-list/.composer/etc. lay out
   exactly as if they were #message-pane's own direct children, which
   they were before the header moved out to the static template. */
#message-pane-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.empty-state {
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--u) * 3);
  color: var(--faint-foreground);
  text-align: center;
}

.room-header {
  flex-shrink: 0;
  padding: 0 calc(var(--u) * 4);
  height: var(--header-h);
  border-block-end: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2);
  font-size: var(--text-body);
}

.room-header-search {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--u);
  min-width: 0;
  margin-inline-start: auto;
  width: 220px;
  height: 30px;
  padding: 0 calc(var(--u) * 2.5);
  line-height: 1;
  background-color: var(--background);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  color: var(--faint-foreground);
  font-size: 12.5px;
}

.room-header-search-icon {
  flex-shrink: 0;
  font-size: 15px;
  filter: grayscale(1);
  opacity: 0.75;
}

.room-header-search span:last-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.room-header-menu-trigger {
  flex-shrink: 0;
  /* Stays in flow (not [hidden]) even with no active room — reserves its
     own width always, so the search box next to it doesn't visibly shift
     position the moment a room is opened and this becomes visible. */
  visibility: hidden;
  height: 28px;
  line-height: 1;
  font-size: 17px;
  color: var(--disabled-fg);
  padding: 0 0 0 var(--u);
}

/* Oat's .ghost still fills with --accent on hover by default — for a bare
   "⋯" glyph sitting alone in the room header, that read as a whole button
   materializing rather than just "this is clickable" (cursor already
   says that). */
.room-header-menu-trigger:hover {
  background-color: transparent;
}

.room-header h2 {
  margin: 0;
  /* Room name has no width limit of its own — without this, a long name
     forces the whole header row wider than the pane, and that overflow
     leaks out as horizontal scroll on the page (not just the header). */
  min-width: 0;
  overflow: hidden;
  font-size: inherit;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2);
}

.room-title-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.room-title-hash {
  flex-shrink: 0;
  color: var(--muted-foreground);
}

.room-header-divider {
  width: 1px;
  height: 14px;
  background: var(--border);
  flex-shrink: 0;
}

.room-header-meta {
  font-size: 12.5px;
  color: var(--faint-foreground);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.date-divider {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 3);
  margin: var(--u) 0;
  color: var(--faint-foreground);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  &::before,
  &::after {
    content: "";
    flex: 1;
    border-block-end: 1px solid var(--border);
  }
}

.unread-divider {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 3);
  margin: calc(var(--u) * 1.5) 0 calc(var(--u) * 3.5);
  color: var(--danger);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  &::before,
  &::after {
    content: "";
    flex: 1;
    border-block-end: 1px solid var(--danger-rule);
  }
}

/* Mattermost-style grouping: avatar/name/time once per consecutive run from
   the same sender; continuations are just text, aligned under the message
   column with a hover-only timestamp in place of the avatar. */
.message-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* Same fix as #room-list: overflow-y:auto with overflow-x left unset
     computes overflow-x as auto too (CSS spec quirk, not a browser bug),
     so any sub-pixel overflow shows a horizontal scrollbar. Now that
     .message stretches to the pane's full width instead of capping at
     --measure, this is worth pinning explicitly rather than relying on
     everything happening to fit exactly. */
  overflow-x: hidden;
  /* Bottom-anchored: a short conversation should rest just above the
     composer with the empty space above it (reads as "history"), not at
     the top with empty space below it (reads as "failed to load more").
     Deliberately NOT `justify-content: flex-end` — with overflow-y:auto,
     that pattern clips content spilling past the start edge in some
     browsers instead of letting you scroll up to it. margin-top:auto on
     the first child gives the same bottom-anchor for short rooms while
     keeping overflow ordinary top-down scrollable content. */
  display: flex;
  flex-direction: column;
  /* Slightly more start-padding than end — our 12-hour "05:02 PM" hover
     timestamp is wider than the mockup's own examples (bare "09:14", no
     meridiem), so right-aligned in the 28px avatar column it overflows
     further left than a narrower format would. A few extra px of gutter
     keeps it from crowding the pane edge instead of shrinking the format
     app-wide just for this one spot. */
  padding: calc(var(--u) * 2) calc(var(--u) * 5) calc(var(--u) * 2) calc(var(--u) * 6);
  font-size: var(--text-body);
}

.message-list > :first-child {
  margin-top: auto;
}

/* Run rhythm (design spec): 2px between continuation lines in the same run
   — reads as one paragraph — 12px between runs, the only cue separating
   speakers. A run-starting message directly after a divider skips its own
   top margin since the divider already carries that gap (see below). */
.message {
  position: relative; /* anchors .message-hover-actions */
  display: flex;
  gap: calc(var(--u) * 4);
  margin-block-start: calc(var(--u) * 3);
  /* Bleeds the hover fill a little past the text into the list's own
     side padding, without moving any actual content — the padding here
     exactly cancels the matching negative margin, so avatar/text/etc.
     land at the same position as before this was added. Vertical
     spacing is untouched on purpose: the run rhythm above (2px
     continuation / 12px between runs) is a tuned value, and adding
     padding-block here would eat into that 2px case. */
  margin-inline: calc(var(--u) * -2);
  padding-inline: calc(var(--u) * 2);
  border-radius: var(--radius);
}

.message:hover {
  background-color: var(--faint);
}

.message-continuation {
  margin-block-start: 2px;
}

.message-list > .message:first-child,
.date-divider + .message,
.unread-divider + .message {
  margin-block-start: 0;
}

.message-avatar-col {
  position: relative;
  flex-shrink: 0;
  width: 28px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-block-start: 0.15rem;
}

.message-avatar-col .avatar {
  width: 28px;
  height: 28px;
  font-size: 11px;
}

/* Positioned out of flow entirely — it must never affect the row's layout
   (that's what caused the wrapping/misalignment bug: at 1.5rem wide, text
   like "05:13 PM" doesn't fit on one line and pushed the message down). */
.message-hover-time {
  display: none;
  position: absolute;
  inset-inline-end: 0;
  top: 0.15rem;
  white-space: nowrap;
  font-size: 11px;
  color: var(--hint);
  font-variant-numeric: tabular-nums;
}

.message:hover .message-hover-time {
  display: inline;
}

.message-content {
  min-width: 0;
  flex: 1;
}

.message-header {
  display: flex;
  align-items: baseline;
  gap: calc(var(--u) * 2);
}

.message-sender {
  font-weight: 600;
  font-size: var(--text-chrome);
}

.message-time {
  color: var(--disabled-fg);
  font-size: 12px;
}

.message-text {
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

.message-attachment {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--u) * 1.5);
  margin-block-start: calc(var(--u) * 1.5);
  padding: calc(var(--u) * 1.5) calc(var(--u) * 2.5);
  background-color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 12.5px;
  color: var(--foreground);
  cursor: pointer;
  max-width: 100%;
}

.message-attachment:hover {
  background-color: var(--accent);
}

.message-attachment-icon {
  flex-shrink: 0;
  font-size: 14px;
  line-height: 1;
}

.message-attachment-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Hidden until hover, same reveal pattern as .room-row-overflow — except
   while its own picker is open (.message-reacting, toggled from JS on the
   popover's "toggle" event), since the popover renders in the top layer
   and moving the mouse into it drops :hover on .message itself. */
.message-hover-actions {
  visibility: hidden;
  position: absolute;
  top: -14px;
  right: 0;
  display: flex;
  background-color: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* --border alone (a 1px hairline meant for flush dividers, not a
     floating element sitting on top of the transcript) read as barely
     there against the page. A real shadow gives it the same "lifted off
     the page" definition every other popover in the app already gets
     for free from Oat's own [popover] base rule. */
  box-shadow: var(--shadow-small);
}

.message:hover .message-hover-actions,
.message-reacting {
  visibility: visible;
}

.message-react-trigger {
  padding: 3px 6px;
  font-size: 14px;
  color: var(--disabled-fg);
}

.message-reactions {
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--u) * 1.5);
  margin-block-start: calc(var(--u) * 1.5);
}

.message-reaction-pill {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 1);
  padding: 1px calc(var(--u) * 2);
  background-color: var(--muted);
  /* Oat's base `button` rule defaults to a filled --primary background
     with --primary-foreground (white) text — every other button here
     overrides both, but this one only overrode background-color, so the
     count was rendering white-on-light-grey. */
  color: var(--foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 12.5px;
  line-height: 1.6;
}

.message-reaction-pill:hover {
  background-color: var(--accent);
}

.message-reaction-pill.active {
  background-color: color-mix(in srgb, var(--primary), transparent 85%);
  border-color: var(--primary);
}

@keyframes message-highlight-fade {
  from {
    background-color: color-mix(in srgb, var(--primary), transparent 75%);
  }
  to {
    background-color: transparent;
  }
}

.message-highlight {
  animation: message-highlight-fade 2s ease-out;
}

/* ---------- dialogs (room settings, confirm) ---------- */
/* Oat's generic `dialog > :is(p,div,section)` padding/overflow rule loses
   to any of these on specificity, so no !important needed to get our own
   16/20/12-style spacing instead of Oat's uniform --space-6. */

.dialog-title {
  font-size: var(--text-body);
  font-weight: 600;
}

.dialog-titlebar {
  display: flex;
  align-items: center;
  padding: calc(var(--u) * 4) calc(var(--u) * 5) calc(var(--u) * 3);
}

.dialog-close {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-inline-start: auto;
  width: 24px;
  height: 24px;
  padding: 0;
  color: var(--faint-foreground);
  font-size: 16px;
}

.dialog-footer {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2);
  padding: calc(var(--u) * 3) calc(var(--u) * 5);
  border-block-start: 1px solid var(--border);
}

/* Cancel/Save/Done/Archive…/Leave…/Deactivate all sit in the same footer at
   various times — a consistent minimum width keeps them from visibly
   jumping around next to each other depending on label length. Excludes
   .ghost (the plain-text Archive room/Leave shortcuts, meant to read as
   links, not button-shaped controls). */
.dialog-footer button:not(.ghost) {
  height: 32px;
  min-width: 84px;
}

/* Archive…/Leave… (General tab's danger zone) sit outside .dialog-footer
   but should still read as the same size class of button as Cancel/Save
   changes below them — Oat's default padding made them visibly taller. */
.danger-row button {
  height: 32px;
  min-width: 84px;
}

.dialog-footer-end {
  margin-inline-start: auto;
}

.dialog-tabs {
  display: flex;
  gap: calc(var(--u) * 4.5);
  padding: 0 calc(var(--u) * 5);
  border-block-end: 1px solid var(--border);
}

.dialog-tabs [role="tab"] {
  background: none;
  border: none;
  padding: 0 0 calc(var(--u) * 2);
  margin-block-end: -1px;
  font-size: 13px;
  color: var(--muted-foreground);
  border-block-end: 2px solid transparent;
}

.dialog-tabs [role="tab"][aria-selected="true"] {
  font-weight: 600;
  color: var(--foreground);
  border-block-end-color: var(--foreground);
}

.room-settings-dialog {
  width: 560px;
}

.room-settings-members-head {
  display: flex;
  align-items: center;
  gap: var(--u);
  padding: calc(var(--u) * 3.5) calc(var(--u) * 5) calc(var(--u) * 1.5);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint-foreground);
}

.room-settings-hint {
  margin-inline-start: auto;
  font-size: 12px;
  text-transform: none;
  letter-spacing: normal;
  color: var(--hint);
}

.room-settings-members {
  padding: 0 calc(var(--u) * 3);
  max-height: 260px;
  overflow-y: auto;
}

.room-settings-member-row {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 2.5);
  height: 32px;
  padding: 0 var(--u);
  border-radius: var(--radius);
}

.room-settings-member-row:hover {
  background-color: var(--faint);
}

.room-settings-member-row .avatar {
  width: 20px;
  height: 20px;
  font-size: 9px;
}

.room-settings-member-name {
  font-size: 13px;
  font-weight: 500;
}

.room-settings-member-email {
  font-size: 12.5px;
  color: var(--disabled-fg);
}

.room-settings-member-role {
  margin-inline-start: auto;
  font-size: 12.5px;
  color: var(--faint-foreground);
}

/* Browse-channels rows: a leading "#" glyph and a trailing "Join" button,
   reusing the member row's 20px-avatar-slot layout. */
.dialog-row-icon {
  width: 20px;
  flex-shrink: 0;
  text-align: center;
  color: var(--muted-foreground);
  font-weight: 600;
}

.dialog-row-action {
  margin-inline-start: auto;
  height: 26px;
  padding: 0 calc(var(--u) * 2.5);
  font-size: 12.5px;
}

.room-settings-member-menu-trigger {
  visibility: hidden;
  font-size: 13px;
  padding: 0 0 0 var(--u);
}

.room-settings-member-row:hover .room-settings-member-menu-trigger {
  visibility: visible;
}

.room-settings-add-member {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 1.5);
  padding: calc(var(--u) * 3.5) calc(var(--u) * 5) calc(var(--u) * 4);
}

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.secret-field-row {
  display: flex;
  align-items: center;
  gap: var(--u);
}

.secret-field-row input {
  flex: 1;
  min-width: 0;
}

.field-label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted-foreground);
}

.room-settings-general-fields {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 3.5);
  padding: calc(var(--u) * 4) calc(var(--u) * 5);
}

.room-settings-created-line {
  font-size: 13px;
  color: var(--muted-foreground);
}

.danger-zone {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 3);
  padding: calc(var(--u) * 3.5) calc(var(--u) * 5) calc(var(--u) * 4);
  border-block-start: 1px solid var(--border);
}

.danger-zone-label {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint-foreground);
}

.danger-row {
  display: flex;
  align-items: flex-start;
  gap: calc(var(--u) * 4);
}

.danger-row-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.danger-row-title {
  font-size: 13px;
  font-weight: 500;
}

.danger-row-explain {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--faint-foreground);
}

.confirm-dialog {
  width: 380px;
}

.confirm-dialog-body {
  display: flex;
  flex-direction: column;
  gap: calc(var(--u) * 1.5);
  padding: calc(var(--u) * 4) calc(var(--u) * 4.5) calc(var(--u) * 3.5);
}

.confirm-dialog-body p {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--muted-foreground);
}

.dm-info-body {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 4);
  padding: calc(var(--u) * 2) calc(var(--u) * 5) calc(var(--u) * 5);
}

.dm-info-avatar .avatar {
  width: 44px;
  height: 44px;
  font-size: 15px;
}

.dm-info-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.dm-info-text strong {
  font-size: var(--text-body);
}

.dm-info-username {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--faint-foreground);
}

.dm-info-presence {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 1.5);
  margin-block-start: 2px;
  font-size: 12.5px;
  color: var(--muted-foreground);
}

/* Typing indicator and read receipt share one thin strip instead of being
   split between the room header and the composer — same kind of transient
   per-room status, no reason to live in two different places. */
.status-bar {
  flex-shrink: 0;
  display: flex;
  align-items: baseline;
  gap: calc(var(--u) * 3);
  padding: 0 calc(var(--u) * 4);
  min-height: 22px;
}

.typing-indicator {
  color: var(--faint-foreground);
  font-size: 12px;
  font-style: italic;
}

.message-meta {
  color: var(--disabled-fg);
  font-size: 12px;
}

.composer {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: var(--u);
  /* Much tighter than before (was --u*5/20px) — the input box below reads
     as "flush to the pane" per the design review, just with enough room
     for its own border/radius to still look like a rounded box and not a
     clipped one. */
  padding: calc(var(--u) * 2.5) calc(var(--u) * 2) calc(var(--u) * 3);
  border-block-start: 1px solid var(--border);
  background-color: var(--chrome);
}

.composer-row {
  display: flex;
  align-items: flex-start;
  gap: calc(var(--u) * 2.5);
  background-color: var(--background);
  border: 1px solid var(--input);
  border-radius: var(--radius);
  padding: calc(var(--u) * 2.25) calc(var(--u) * 3);
}

.composer textarea {
  flex: 1;
  min-width: 0;
  resize: none;
  border: none;
  outline: none;
  padding: 0;
  font-family: var(--font);
  font-size: var(--text-body);
  line-height: 1.5;
  background: transparent;
  /* One line at rest, grows with input (app.js's autoGrowComposerTextarea)
     up to 8 lines, then scrolls internally rather than growing the pane
     away — capped by line-height so it scales if --text-body ever does. */
  min-height: 34px;
  max-height: calc(var(--text-body) * 1.5 * 8);
  overflow-y: auto;
}

/* Lives inside the input box itself, top-right — not a separate control
   next to it (design spec's composer well). No send button on desktop:
   Enter sends, the hint row below says so. */
.composer-attach-btn {
  flex-shrink: 0;
  padding: 0;
  font-size: 16px;
  color: var(--disabled-fg);
}

.emoji-picker-grid {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 2px;
  width: 232px;
  padding: var(--u);
}

.emoji-picker-item {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: var(--radius);
  font-size: 16px;
  background: none;
  border: none;
}

.emoji-picker-item:hover {
  background-color: var(--faint);
}

.composer-hint-row {
  display: flex;
  align-items: center;
  gap: calc(var(--u) * 3);
  padding-inline: var(--u);
}

.composer-hint {
  font-size: 11.5px;
  color: var(--disabled-fg);
}

.composer-read-receipt {
  margin-inline-start: auto;
  font-size: 11.5px;
  color: var(--disabled-fg);
}

.attachment-chip {
  display: flex;
  align-items: center;
  gap: var(--u);
  align-self: flex-start;
  padding: calc(var(--u) / 2) var(--u);
  background-color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 11.5px;
}

.attachment-chip button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  color: var(--faint-foreground);
}

/* ---------- presence ---------- */
/* The one sanctioned use of hue for state (see dev.md's design-handoff
   notes): solid fill for online/away/busy, a hollow ring — not a fill —
   for offline, so absence reads as absence instead of a fourth color. */

.presence-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--presence-off);
  flex-shrink: 0;
}

.presence-dot[data-status="offline"] {
  background-color: transparent;
  border: 1.5px solid var(--presence-off);
  box-sizing: border-box;
}

.presence-dot[data-status="online"] {
  background-color: var(--presence-online);
}

.presence-dot[data-status="away"] {
  background-color: var(--presence-away);
}

/* ---------- connection error overlay ---------- */

.connection-error-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: color-mix(in srgb, var(--background), transparent 15%);
}

.connection-error-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--u) * 3);
  padding: calc(var(--u) * 6);
  background-color: var(--background);
  border: 1px solid var(--border);
  border-radius: var(--radius-medium);
  box-shadow: 0 4px 24px rgb(0 0 0 / 0.15);
}
