:root {
  --bg: #1a1d21;
  --bg-2: #222529;
  --bg-3: #2c2f33;
  --bg-hover: #353a40;
  --fg: #d1d2d3;
  --fg-dim: #8e9297;
  --fg-bright: #fff;
  --accent: #7c3aff;
  --accent-2: #3a4cff;
  --green: #2bac76;
  --red: #e01e5a;
  --yellow: #ecb22e;
  --border: #383b40;
  --radius: 6px;
  --sidebar-w: 260px;
  --rail-w: 360px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 14px;
  color: var(--fg);
  background: var(--bg);
}
* { box-sizing: border-box; }
body { margin: 0; height: 100vh; color: var(--fg); }

/* Global scrollbar styling — track blends into the panel background,
   thumb is the next-step-up surface color so it's just visible on
   hover without ever competing with the content. Element-scoped
   overrides (e.g. #sidebar, .seen-list) still take precedence by
   specificity. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--bg-3) transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; background: transparent; }
*::-webkit-scrollbar-track,
*::-webkit-scrollbar-corner { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--bg-3);
  border-radius: 6px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--border); background-clip: padding-box; }
/* The native up/down arrow caps on Windows look out of place against
   our flat surfaces — drop them so the scrollbar is just the thumb. */
*::-webkit-scrollbar-button { display: none; height: 0; width: 0; }
button { font-family: inherit; }
.dim { color: var(--fg-dim); }
.hidden { display: none !important; }
a { color: var(--accent-2); }

/* ---------- Login ---------- */
.login-page {
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(135deg, var(--bg) 0%, var(--bg-3) 100%);
}
.auth-card {
  width: 380px; padding: 32px; background: var(--bg-2);
  border: 1px solid var(--border); border-radius: 10px;
}
.auth-card h1 { margin: 0; color: var(--fg-bright); font-size: 28px; }
.muted { color: var(--fg-dim); margin-top: 4px; }
.tabs { display: flex; gap: 0; margin: 24px 0 16px; border-bottom: 1px solid var(--border); }
.tab {
  flex: 1; padding: 8px; background: transparent; color: var(--fg-dim);
  border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 14px;
}
.tab.active { color: var(--fg-bright); border-bottom-color: var(--accent); }
.auth-form { display: flex; flex-direction: column; gap: 10px; }
.auth-form input {
  padding: 10px 12px; background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius); font-size: 14px;
}
.auth-form button {
  padding: 10px; background: var(--accent); color: white;
  border: none; border-radius: var(--radius); cursor: pointer; font-weight: 600;
}
.auth-form button:hover { background: var(--accent-2); }
.error { color: var(--red); min-height: 1em; font-size: 12px; }

.google-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 10px 14px; background: white; color: #1f1f1f;
  border: 1px solid #dadce0; border-radius: var(--radius);
  text-decoration: none; font-weight: 600; font-size: 14px;
  width: 100%; cursor: pointer;
}
.google-btn:hover { background: #f8f9fa; }
.divider {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 0; color: var(--fg-dim); font-size: 12px;
}
.divider::before, .divider::after {
  content: ''; flex: 1; border-top: 1px solid var(--border);
}

/* ---------- App layout (Slack-like) ----------
   The rail used to be a 5th grid column, which meant opening it shrank
   the middle pane. New behavior: the rail is absolute-positioned and
   OVERLAYS the middle pane — chat width never changes when threads /
   search / admin slide in. The grid stays 3 columns (sidebar / resizer
   / main) regardless of rail state. */
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) 4px 1fr;
  grid-template-rows: 100vh;
  position: relative;       /* anchor for the absolute-positioned #rail */
  /* Animate the column tracks themselves so the sidebar shrinks/grows in
     place and the 1fr main column slides into (or out of) the freed
     space. Modern browsers interpolate explicit grid-template-columns
     widths -- no JS needed. The mobile rules below override the grid to
     a single column, so the transition is desktop-only in practice. */
  transition: grid-template-columns 220ms ease;
}

/* Pin each grid item to its column explicitly. Without this, auto-
   placement reflows the items when an earlier sibling is `display: none`
   -- specifically, hiding #sidebar would cause #main to take column 1
   (a 0-width track when collapsed) instead of column 3, squeezing the
   chat into the freed space instead of letting it expand.
   #rail and .resizer.rail are absolute-positioned (see below), so they
   are deliberately NOT given a grid-column. */
#sidebar         { grid-column: 1; }
.resizer.sidebar { grid-column: 2; }
#main            { grid-column: 3; }

/* Collapsed sidebar -- driven by body.sidebar-collapsed (set by JS,
   persisted in localStorage). The grid keeps 3 tracks so #main stays
   in column 3; the sidebar + its resizer just have 0 width. The 1fr
   column for #main expands to fill the freed space.

   Why visibility instead of display:none: display:none isn't animatable,
   so collapsing would snap. Visibility IS animatable (with a discrete
   transition), so we hold the sidebar visible for the duration of the
   shrink, then flip to hidden once the track has fully closed. On
   expand, the visibility flip is immediate (delay 0) so the contents
   are present from frame one of the grow. */
.app.sidebar-collapsed {
  grid-template-columns: 0 0 1fr;
}

/* ---- Rail (overlay) ----
   Slides in from the right edge, sits on top of the middle pane. Drop
   shadow gives the depth that says "this is on top of, not next to,
   the content beneath." The transform-based slide is GPU-friendly and
   matches the timing of the old grid-column transition. */
#rail {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: var(--rail-w);
  background: var(--bg-2);
  border-left: 1px solid var(--border);
  box-shadow: -10px 0 28px rgba(0, 0, 0, 0.30);
  transform: translateX(100%);
  transition: transform 220ms ease, width 220ms ease;
  z-index: 30;
  overflow-y: auto;
  pointer-events: none;     /* invisible-when-closed: don't grab clicks */
}
.app.rail-open #rail {
  transform: translateX(0);
  pointer-events: auto;
}

.resizer.rail {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 4px;
  right: var(--rail-w);
  z-index: 31;
  display: none;
}
.app.rail-open .resizer.rail { display: block; }

/* Workspace-admin rail mode: widen the rail to fit the admin's
   two-column layout. Same overlay behavior — chat doesn't move. */
body.rail-wide #rail { width: min(720px, 92vw); }
body.rail-wide .app.rail-open .resizer.rail { right: min(720px, 92vw); }

/* ---------- Fullscreen feature modals (PM, Timecard) ----------
   Each heavy module (Project Manager, Timecard) presents in a
   fullscreen overlay over the chat app. The module's existing
   standalone page (/pm.html, /timecard.html) is loaded into an
   iframe so its layout doesn't have to know whether it's inside
   a modal or standalone.
   Chrome (close button + thin gradient bar) auto-hides — it only
   reveals when the cursor moves near the top of the viewport,
   then fades after ~2s of no top-edge activity. The iframe posts
   mousemove events to the parent so cursor activity inside the
   iframe still triggers the reveal. */
.fullscreen-modal {
  position: fixed;
  inset: 0;
  background: var(--bg);
  z-index: 9999;
  display: none;
}
.fullscreen-modal.show { display: block; }
.fullscreen-modal iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.fullscreen-modal-chrome {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 52px;
  z-index: 10000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.45), transparent);
}
.fullscreen-modal-chrome.show { opacity: 1; }
.fullscreen-close {
  position: fixed;
  top: 12px;
  right: 14px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: rgba(20, 22, 26, 0.88);
  border: 1px solid rgba(255, 255, 255, 0.10);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10001;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease, background 0.15s, transform 0.12s;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}
.fullscreen-modal-chrome.show ~ .fullscreen-close,
.fullscreen-close.show {
  pointer-events: auto;
  opacity: 1;
}
.fullscreen-close:hover { background: rgba(40, 42, 48, 0.95); transform: scale(1.05); }
body.fullscreen-modal-open { overflow: hidden; }
/* When the admin shell is hosted in the rail, the rail's default
   overflow-y: auto from the base #rail rule does the scrolling. The
   earlier flex+overflow:hidden styling was for the iframe approach
   (which has been replaced with a native fragment mount); restoring
   the default layout fixes scrolling for the new admin shell. */
/* Allow the grid track to actually compress the sidebar to 0. Flex
   containers default to min-width:auto, which would refuse to shrink
   below their content. Clip any horizontal overflow during the shrink
   so contents don't bleed into the chat column. */
#sidebar { min-width: 0; overflow-x: hidden; }
.resizer.sidebar { transition: visibility 0s linear 0s; }
.app.sidebar-collapsed #sidebar,
.app.sidebar-collapsed .resizer.sidebar {
  visibility: hidden;
  pointer-events: none;
  transition: visibility 0s linear 220ms;
}

/* Sidebar toggle button (in #channelHeader). Icon swaps based on state:
   - sidebar visible  -> show .icon-collapse, hide .icon-expand
   - sidebar collapsed -> show .icon-expand,   hide .icon-collapse */
#sidebarToggle { display: inline-flex; align-items: center; justify-content: center; padding: 6px; }
#sidebarToggle svg { width: 18px; height: 18px; }
#sidebarToggle .icon-expand { display: none; }
body.sidebar-collapsed #sidebarToggle .icon-collapse { display: none; }
body.sidebar-collapsed #sidebarToggle .icon-expand { display: inline-block; }
.resizer {
  background: transparent;
  cursor: col-resize;
}
.resizer:hover { background: var(--accent); opacity: 0.4; }

/* ---------- Sidebar ---------- */
#sidebar {
  background: var(--bg-2); border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  height: 100vh;
  /* Whole sidebar scrolls as a single unit -- header, nav, channels,
     DMs, and the sign-out footer all flow in one column and scroll
     together when the content is taller than the viewport. */
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--bg-3) transparent;
}
#sidebar::-webkit-scrollbar { width: 8px; }
#sidebar::-webkit-scrollbar-track { background: transparent; }
#sidebar::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 4px; }
#sidebar::-webkit-scrollbar-thumb:hover { background: var(--border); }
.ws-header { padding: 14px 16px; border-bottom: 1px solid var(--border); }
/* The title row is now a clickable button that opens the workspace
   switcher (cross-tenant). Strip the default button chrome but keep
   the layout we had as a div. */
button.ws-title {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  background: transparent; border: 0; padding: 0;
  color: inherit; font: inherit; text-align: left;
  cursor: pointer; width: 100%;
  border-radius: 6px;
}
button.ws-title:hover { background: var(--bg-hover); }
.ws-name { font-weight: 700; color: var(--fg-bright); font-size: 16px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ws-switcher-caret { color: var(--fg-dim); font-size: 11px; flex: 0 0 auto; margin-left: auto; padding-right: 4px; }
/* Optional workspace logo (mig #43). Reserved 32px box; the source
   image is uploaded as 256×256 and scales down here. Border-radius
   matches our other rounded surfaces; object-fit: cover keeps any
   non-square upload from squishing. */
.ws-logo { width: 32px; height: 32px; border-radius: 6px; object-fit: cover; flex: 0 0 auto; }

/* Workspace switcher modal. Each row is a button so keyboard nav
   works for free; the current workspace is rendered disabled. */
.ws-pick { min-width: 320px; max-width: 480px; }
.ws-pick-list { display: flex; flex-direction: column; gap: 4px; }
.ws-pick-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px;
  background: var(--bg-3);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  text-align: left;
  font: inherit;
}
.ws-pick-row:hover:not([disabled]) { background: var(--bg-hover); border-color: var(--accent); }
.ws-pick-row.is-current { opacity: 0.85; cursor: default; }
.ws-pick-row.is-disabled { opacity: 0.55; }
.ws-pick-logo {
  width: 36px; height: 36px; border-radius: 6px;
  object-fit: cover; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; color: #fff;
  background: var(--accent);
}
.ws-pick-logo-fallback { font-size: 16px; }
.ws-pick-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.ws-pick-name { font-weight: 600; color: var(--fg-bright); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ws-pick-slug { font-size: 11px; }
.ws-pick-current {
  background: rgba(0,200,80,0.18); color: #2c8;
  font-size: 11px; font-weight: 600; padding: 2px 8px;
  border-radius: 100px; text-transform: uppercase; letter-spacing: 0.05em;
}
.ws-pick-disabled {
  background: rgba(220,53,69,0.18); color: #ff8a95;
  font-size: 11px; font-weight: 600; padding: 2px 8px;
  border-radius: 100px; text-transform: uppercase; letter-spacing: 0.05em;
}
.me { color: var(--fg-dim); font-size: 12px; margin-top: 2px; }
.me a { color: var(--fg); text-decoration: none; }
.nav { padding: 8px 8px 0; }
.nav-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 10px; background: transparent; color: var(--fg);
  border: none; border-radius: var(--radius); cursor: pointer;
  text-align: left; font-size: 14px;
}
.nav-item:hover { background: var(--bg-hover); }
/* Single-color SVG icon, color comes from currentColor on the parent button.
   Slightly dimmer than the label text by default so the label leads the eye. */
.nav-icon {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  color: var(--fg-dim);
  stroke: currentColor;
}
.nav-item:hover .nav-icon,
.nav-item.active .nav-icon { color: var(--fg-bright); }
/* Numeric pill at the right edge of nav items (used by Drafts). Hidden
   by default; the JS layer flips .hidden when the count > 0. */
.nav-item .nav-count {
  margin-left: auto;
  background: var(--bg-3);
  color: var(--fg-dim);
  font-size: 10px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: 9px;
  flex-shrink: 0;
}
/* Bolded label + bright count when a nav-item has unread items.
   Used by Inbox; the count badge flips to the accent color so the
   eye catches it from across the sidebar. */
.nav-item.nav-unread { font-weight: 700; color: var(--fg-bright); }
.nav-item.nav-unread .nav-icon { color: var(--fg-bright); }
.nav-item.nav-unread .nav-count {
  background: var(--red); color: white;
}
/* Pulsing red corner dot for nav items that demand attention but
   don't have a count badge (Reminders fired-but-not-cleared). The
   .nav-unread-mention class is added/removed by JS in step with
   the reminder banner. */
.nav-item.nav-unread-mention { position: relative; }
.nav-item.nav-unread-mention::before {
  content: '';
  position: absolute;
  top: 4px; right: 4px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 0 2px var(--bg);
  animation: nav-mention-pulse 1.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes nav-mention-pulse {
  0%   { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(224, 30, 90, 0.6); }
  70%  { transform: scale(1.18); box-shadow: 0 0 0 2px var(--bg), 0 0 0 6px rgba(224, 30, 90, 0); }
  100% { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(224, 30, 90, 0); }
}

.ch-section { padding: 12px 8px; flex: 0 0 auto; }
/* `.ch-scroll` is just a markup grouping for the channels + DMs
   sections. The whole sidebar scrolls as one unit (see #sidebar
   above), so this wrapper gets no inner scroll of its own. */
.ch-scroll { display: contents; }
.section-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 4px 10px; color: var(--fg-dim); text-transform: uppercase; font-size: 11px; font-weight: 700;
}
.head-actions { display: flex; gap: 2px; }
.icon-btn {
  background: transparent; color: var(--fg-dim); border: none; cursor: pointer;
  font-size: 16px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 4px; border-radius: 4px;
  line-height: 1;
}
.icon-btn:hover { color: var(--fg-bright); background: var(--bg-hover); }
/* Inline SVG inside an icon-btn -- single color via currentColor. */
.icon-btn svg { width: 14px; height: 14px; display: block; stroke: currentColor; }
/* Channel + DM lists flow naturally; the whole sidebar scrolls as a
   single unit (#sidebar { overflow-y: auto }), so the inner lists must
   not impose their own scroll containers. */
.ch-list { list-style: none; padding: 0; margin: 0; }
.ch-list li { padding: 0; }

/* New channel-row layout: button + hover-revealed actions */
.ch-row {
  display: flex; align-items: center; gap: 2px;
  border-radius: var(--radius); margin: 1px 0;
  position: relative;
}
/* Pulsing orb in the lower-right corner of any channel/DM row that
   has unread. Color encodes the urgency:
     GREEN  = general channel chatter (no @-mention)
     RED    = personal attention required (DM, @-mention, reminder)
   Both share the same pulse keyframes; the color comes from the
   --pulse-rgb custom property so each variant only has to set its
   own RGB triplet. */
.ch-row.ch-unread::after {
  content: '';
  position: absolute;
  right: 6px; bottom: 4px;
  width: 7px; height: 7px;
  border-radius: 50%;
  --pulse-rgb: 43, 172, 118;     /* green default — channel without mention */
  background: rgb(var(--pulse-rgb));
  animation: ch-unread-pulse 1.6s ease-out infinite;
  pointer-events: none;
}
.ch-row.ch-unread.ch-mention::after,
.ch-row.ch-unread.ch-dm::after {
  --pulse-rgb: 224, 30, 90;       /* red — DM or @-mention demands attention */
}
@keyframes ch-unread-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(var(--pulse-rgb), 0.55); }
  70%  { box-shadow: 0 0 0 8px rgba(var(--pulse-rgb), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--pulse-rgb), 0); }
}
/* When the row is active (currently open), the highlight color is the
   accent blue -- the orb against blue would clash, so suppress it. */
.ch-row.active.ch-unread::after { display: none; }
.ch-row:hover { background: var(--bg-hover); }
.ch-row.active { background: var(--accent); }
.ch-row.active .ch-name,
.ch-row.active .sigil { color: white; }
.ch-row .ch-btn {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; background: transparent; color: inherit;
  border: none; cursor: pointer; text-align: left; font-size: 14px;
  overflow: hidden;
}
.ch-row .ch-btn .ch-name {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1;
}
.ch-row .sigil { color: var(--fg-dim); flex-shrink: 0; }
.ch-row.ch-unread .ch-name { font-weight: 700; color: var(--fg-bright); }
.ch-row.ch-unread .sigil   { color: var(--fg-bright); }
.ch-row .badge {
  background: var(--red); color: white;
  font-size: 10px; padding: 1px 6px; border-radius: 9px; font-weight: 700;
  flex-shrink: 0;
}
.ch-row .ch-actions {
  display: none; gap: 1px; padding-right: 4px; flex-shrink: 0;
}
.ch-row:hover .ch-actions { display: flex; }
.ch-row:hover .badge { display: none; }
.ch-mini {
  background: transparent; color: var(--fg-dim); border: none;
  cursor: pointer; padding: 2px 5px; border-radius: 3px; font-size: 13px; line-height: 1;
}
.ch-mini:hover { background: rgba(255,255,255,.12); color: var(--fg-bright); }
.ch-mini.danger:hover { background: rgba(224, 30, 90, 0.18); color: var(--red); }
.ch-row.active .ch-mini { color: rgba(255,255,255,.7); }
.ch-row.active .ch-mini:hover { color: white; background: rgba(255,255,255,.18); }
.sidebar-footer { margin-top: auto; padding: 10px; border-top: 1px solid var(--border); }
.sidebar-footer button {
  width: 100%; padding: 8px; background: transparent; color: var(--fg-dim);
  border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer;
}

/* ---------- Main ---------- */
#main {
  display: flex; flex-direction: column; min-width: 0; height: 100vh; background: var(--bg);
  position: relative;
}
#channelHeader {
  display: flex; justify-content: space-between; align-items: center;
  padding: 10px 18px; border-bottom: 1px solid var(--border); background: var(--bg);
}
#channelHeader h2 { margin: 0; color: var(--fg-bright); font-size: 18px; }
.topic { margin: 2px 0 0; color: var(--fg-dim); font-size: 12px; }
.header-actions { display: flex; gap: 6px; }
.header-actions button {
  background: transparent; color: var(--fg); border: 1px solid var(--border);
  padding: 6px 10px; border-radius: var(--radius); cursor: pointer; font-size: 12px;
  display: inline-flex; align-items: center; gap: 6px;
}
.header-actions button:hover { background: var(--bg-hover); }
.header-actions button.active { background: var(--green); color: white; border-color: var(--green); }
.header-actions button svg { width: 14px; height: 14px; flex: 0 0 auto; stroke: currentColor; }

.messages { flex: 1; overflow-y: auto; padding: 10px 18px; position: relative; }

/* "New" divider rendered between the last-read message and the first
   unread one when a channel is opened. The label is centered; the lines
   on either side fade in/out. */
.unread-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 12px 0 8px; padding: 0 4px;
}
.unread-divider .ud-line {
  flex: 1; height: 1px; background: var(--red);
  opacity: 0.6;
}
.unread-divider .ud-label {
  font-size: 11px; font-weight: 700; color: var(--red);
  text-transform: uppercase; letter-spacing: 0.08em;
  padding: 1px 8px; border: 1px solid var(--red); border-radius: 100px;
  background: var(--bg);
}

/* "Jump to latest" floating button. Anchored to the bottom-center of
   #main, hovers above the composer. Only shown when the message list
   has scrolled more than ~1 page above the bottom (toggled by JS via
   #main.show-jump). */
#main .jump-latest {
  position: absolute;
  bottom: 96px; left: 50%; transform: translateX(-50%);
  background: var(--bg-2); color: var(--fg);
  border: 1px solid var(--border); border-radius: 999px;
  padding: 6px 14px 6px 12px; font-size: 12px;
  display: none; align-items: center; gap: 6px;
  cursor: pointer; z-index: 5;
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
#main .jump-latest:hover { background: var(--bg-3); }
#main .jump-latest svg { width: 13px; height: 13px; stroke: currentColor; }
#main.show-jump .jump-latest { display: inline-flex; }
#main .jump-latest .jl-unread {
  background: var(--red); color: white;
  font-size: 10px; font-weight: 700;
  padding: 1px 6px; border-radius: 9px;
}

/* ---- Pinned-message banner (above the messages list) ---- */
.pin-banner {
  margin: 0 18px 6px; padding: 8px 12px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 6px;
  font-size: 12px;
}
.pin-banner.hidden { display: none; }
.pin-banner-head {
  display: flex; align-items: center; gap: 6px;
  color: var(--fg-bright); font-weight: 600;
}
.pin-banner-head svg { width: 14px; height: 14px; stroke: currentColor; }
.pin-banner-head .pin-toggle {
  margin-left: auto; background: transparent; border: none; color: var(--fg-dim);
  cursor: pointer; padding: 2px 6px; border-radius: 4px; font-size: 12px;
}
.pin-banner-head .pin-toggle:hover { background: var(--bg-hover); color: var(--fg-bright); }
.pin-banner-list { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; max-height: 30vh; overflow-y: auto; }
.pin-row {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 4px;
  padding: 6px 10px;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.pin-row .pin-row-main { flex: 1; min-width: 0; cursor: pointer; }
.pin-row:hover { background: var(--bg-hover); }
.pin-row-meta { color: var(--fg-dim); font-size: 11px; }
.pin-row-meta b { color: var(--fg); }
.pin-row-body { color: var(--fg); font-size: 13px; margin-top: 2px; }
.pin-row-body p { margin: 0; }
.pin-row-unpin {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  opacity: 0;
  transition: opacity .12s, color .12s, background .12s;
}
.pin-row:hover .pin-row-unpin { opacity: 1; }
.pin-row-unpin:hover { color: #d97070; background: var(--bg-3); }

/* Inline pin badge in the message meta line */
.pin-badge {
  display: inline-flex; align-items: center;
  color: var(--accent); margin-left: 4px;
}
.pin-badge svg { width: 12px; height: 12px; stroke: currentColor; }

/* Direct-reply quote header above a message body */
.reply-header {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-3); border-left: 3px solid var(--accent);
  padding: 4px 8px; border-radius: 0 4px 4px 0;
  margin-bottom: 4px; font-size: 12px; color: var(--fg-dim);
  cursor: pointer; max-width: 100%;
}
.reply-header:hover { background: var(--bg-hover); }
.reply-header svg { width: 12px; height: 12px; stroke: currentColor; flex: 0 0 auto; }
.reply-header b { color: var(--fg); }
.reply-header .reply-snippet {
  color: var(--fg-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: 60ch;
}
.reply-header-deleted { font-style: italic; color: var(--fg-dim); cursor: default; }
.reply-header-deleted:hover { background: var(--bg-3); }

/* Composer reply chip (sits above the textarea while a reply is staged) */
.composer-reply-chip {
  display: flex; align-items: center; gap: 8px;
  margin: 0 18px 4px; padding: 6px 10px;
  background: var(--bg-3); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 4px; font-size: 12px;
}
.composer-reply-chip svg { width: 14px; height: 14px; stroke: currentColor; flex: 0 0 auto; color: var(--accent); }
.composer-reply-chip > span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.composer-reply-chip b { color: var(--fg); }
.composer-reply-chip .dim { color: var(--fg-dim); }
.composer-reply-chip button {
  background: transparent; border: none; color: var(--fg-dim);
  cursor: pointer; padding: 2px 6px; border-radius: 4px; font-size: 16px;
}
.composer-reply-chip button:hover { background: var(--bg-hover); color: var(--fg-bright); }

/* Jump-to-message flash highlight */
@keyframes msg-flash {
  0%   { background: rgba(255, 255, 100, 0.20); }
  100% { background: transparent; }
}
.msg.flash { animation: msg-flash 1.5s ease-out; }

/* ---- PR action card (rendered when a message has metadata_json.kind='pr') ---- */
.pr-card {
  margin-top: 6px; padding: 8px 12px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 6px;
  font-size: 12px;
}
.pr-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pr-card-head a { color: var(--accent); text-decoration: none; font-weight: 600; }
.pr-card-head a:hover { text-decoration: underline; }
.pr-state {
  font-size: 10px; font-weight: 700; text-transform: uppercase;
  padding: 1px 6px; border-radius: 9px; letter-spacing: 0.05em;
}
.pr-state-open    { background: rgba(43, 172, 118, 0.20); color: #4ade80; }
.pr-state-closed  { background: rgba(220, 53, 69, 0.18);  color: #ff8a95; }
.pr-state-merged  { background: rgba(139, 92, 246, 0.20); color: #c4b5fd; }
.pr-state-draft   { background: rgba(120, 120, 120, 0.20); color: #a8a8a8; }
.pr-author { color: var(--fg-dim); margin-left: auto; }

.pr-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.pr-action-btn {
  background: var(--bg-2); color: var(--fg); border: 1px solid var(--border);
  padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
}
.pr-action-btn:hover { background: var(--bg-hover); }
.pr-action-btn.pr-approve         { color: #4ade80; }
.pr-action-btn.pr-approve:hover   { background: rgba(43, 172, 118, 0.18); }
.pr-action-btn.pr-request_changes { color: #fbbf24; }
.pr-action-btn.pr-request_changes:hover { background: rgba(251, 191, 36, 0.15); }
.pr-action-btn.pr-merge           { color: #c4b5fd; }
.pr-action-btn.pr-merge:hover     { background: rgba(139, 92, 246, 0.18); }
.pr-action-btn.pr-close           { color: #ff8a95; }
.pr-action-btn.pr-close:hover     { background: rgba(220, 53, 69, 0.15); }
.pr-actions-hint {
  margin-top: 6px; font-size: 11px; color: var(--fg-dim); font-style: italic;
}
.msg {
  display: grid; grid-template-columns: 36px 1fr auto; gap: 10px; padding: 6px 8px;
  border-radius: 4px; transition: background .1s;
}
.msg:hover { background: var(--bg-hover); }
.msg.hilite { background: rgba(252, 211, 77, 0.15); transition: background 1.5s; }
.msg .avatar {
  width: 36px; height: 36px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  color: white; font-weight: 700; font-size: 14px;
}
.msg .body { min-width: 0; }
.msg .meta { display: flex; gap: 8px; align-items: baseline; }
.msg .author { font-weight: 700; color: var(--fg-bright); cursor: pointer; }
.msg .author:hover { text-decoration: underline; }
.msg .time { font-size: 11px; color: var(--fg-dim); }
/* Ghosted "(edited)" subscript next to the timestamp -- intentionally
   subtler than .time so it reads as a footnote, not a metadata label. */
.msg .edited-tag {
  font-size: 10px; font-style: italic;
  color: var(--fg-dim); opacity: 0.55;
  margin-left: 2px;
}
.msg:hover .edited-tag { opacity: 0.8; }

/* Inline message-edit form. Sits in place of the message content
   while the editor is open; clean up on save/cancel. */
.msg-edit-wrap {
  display: flex; flex-direction: column; gap: 6px;
  margin: 4px 0; max-width: 100%;
}
.msg-edit-area {
  width: 100%; box-sizing: border-box;
  padding: 8px 10px;
  background: var(--bg-3); border: 1px solid var(--accent);
  border-radius: 6px;
  color: var(--fg); font-family: inherit; font-size: 14px;
  resize: vertical; min-height: 60px;
}
.msg-edit-area:focus { outline: none; border-color: var(--accent); }
.msg-edit-actions {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px;
}
.msg-edit-actions .msg-edit-save {
  background: var(--accent); color: white;
  border: none; padding: 5px 12px; border-radius: 4px;
  cursor: pointer; font-weight: 600;
}
.msg-edit-actions .msg-edit-cancel {
  background: transparent; color: var(--fg-dim);
  border: 1px solid var(--border); padding: 5px 12px; border-radius: 4px;
  cursor: pointer;
}
.msg-edit-actions .msg-edit-cancel:hover { color: var(--fg); }
.msg-edit-actions .msg-edit-hint {
  color: var(--fg-dim); font-size: 11px;
  margin-left: auto;
}

/* "Seen X ago" stamp under the most recent own message in a DM.
   Right-aligned + ghosted so it doesn't compete with the message
   body for attention. */
.msg-seen-tag {
  font-size: 10px; color: var(--fg-dim);
  margin-top: 2px;
  text-align: right;
  font-style: italic;
  opacity: 0.7;
}

/* Read-receipts modal (channel info button on own messages). The
   list is bounded by max-height so a heavily-read message in a
   100-member channel scrolls inside the modal instead of stretching
   it past the viewport. */
.seen-modal { max-width: 460px; }
.seen-list {
  display: flex; flex-direction: column; gap: 2px;
  max-height: 50vh; overflow-y: auto;
  margin: 0 -4px; padding: 0 4px;
  scrollbar-width: thin;
  scrollbar-color: var(--bg-3) transparent;
}
.seen-list::-webkit-scrollbar { width: 8px; }
.seen-list::-webkit-scrollbar-thumb { background: var(--bg-3); border-radius: 4px; }
.seen-row {
  display: flex; gap: 10px; align-items: center;
  padding: 8px 10px; border-radius: 6px;
}
.seen-row:hover { background: var(--bg-hover); }
.seen-avatar {
  width: 32px; height: 32px; border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  color: white; font-weight: 700; font-size: 13px;
  object-fit: cover; flex-shrink: 0;
}
.seen-row-body { flex: 1; min-width: 0; }
.seen-row-name {
  font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.seen-row-time {
  font-size: 11px; color: var(--fg-dim);
  margin-top: 1px;
}
.msg .content { color: var(--fg); word-wrap: break-word; }

/* Ephemeral bot replies — slash commands the user ran. Never stored,
   never broadcast; rendered locally for the caller only. The dashed
   left edge + muted footer make the privacy contract obvious without
   needing a wall of text. */
.msg-ephemeral .content {
  background: var(--bg-2);
  border-left: 2px dashed var(--accent);
  padding: 6px 10px 4px;
  border-radius: 0 6px 6px 0;
}
.msg-ephemeral-footer {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px dashed var(--border);
  font-size: 11px;
  color: var(--fg-dim);
}
.msg-ephemeral-footer em { font-style: normal; color: var(--accent); }
.msg-ephemeral-dismiss {
  background: transparent; border: 0;
  color: var(--fg-dim); cursor: pointer;
  font-size: 12px; padding: 0 4px; line-height: 1;
}
.msg-ephemeral-dismiss:hover { color: var(--fg-bright); }
.msg .content p { margin: 0.2em 0; }
.msg .content code { background: var(--bg-3); padding: 1px 5px; border-radius: 3px; font-size: 12px; font-family: ui-monospace, "SF Mono", Menlo, monospace; }
.msg .content pre, .codeblock {
  background: var(--bg-3); padding: 10px 12px; border-radius: 6px; overflow-x: auto;
  border: 1px solid var(--border); margin: 4px 0;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px;
}
.msg .content pre code, .codeblock code { background: transparent; padding: 0; font-size: 12px; }
.msg .content blockquote {
  border-left: 3px solid var(--border); padding-left: 10px; margin: 4px 0; color: var(--fg-dim);
}
.msg .content h1 { font-size: 18px; margin: 4px 0; }
.msg .content h2 { font-size: 16px; margin: 4px 0; }
.msg .content h3 { font-size: 14px; margin: 4px 0; }
.msg .content table { border-collapse: collapse; margin: 4px 0; }
.msg .content th, .msg .content td {
  border: 1px solid var(--border); padding: 4px 8px; font-size: 12px;
}
.msg .content th { background: var(--bg-3); }
.msg .content ul, .msg .content ol { margin: 4px 0; padding-left: 22px; }
.msg .content hr { border: none; border-top: 1px solid var(--border); margin: 6px 0; }
.msg .content a {
  color: inherit;            /* match surrounding text */
  text-decoration: underline;
  text-underline-offset: 2px;
}
.msg .content a:hover { text-decoration: underline; opacity: 0.85; }
.msg .content .mention {
  background: rgba(99, 102, 241, 0.15); color: var(--accent-2);
  padding: 0 4px; border-radius: 3px; cursor: pointer; font-weight: 600;
}
.msg .content .chref {
  color: var(--accent-2); cursor: pointer; font-weight: 600;
}
.msg.bot .author { color: var(--yellow); }
.msg.bot .author::after {
  content: 'BOT'; font-size: 9px; background: var(--yellow); color: black;
  padding: 1px 4px; border-radius: 3px; margin-left: 4px; vertical-align: middle; font-weight: 700;
}
.msg-actions {
  opacity: 0; transition: opacity .1s;
  display: flex; gap: 2px; align-items: flex-start;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 4px; padding: 2px;
  height: fit-content;
}
/* Always reveal when explicitly tapped open (mobile UX, but harmless on desktop). */
.msg.actions-open .msg-actions { opacity: 1; }
/* On true hover-capable devices, also reveal on hover. Touch devices
   simulate :hover after a tap and never clear it -- gating the rule on
   `hover: hover` keeps that stickiness off mobile. */
@media (hover: hover) {
  .msg:hover .msg-actions { opacity: 1; }
}
.msg-actions button {
  background: transparent; border: none; cursor: pointer; padding: 4px 6px; color: var(--fg-dim);
  font-size: 14px; border-radius: 3px;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
.msg-actions button:hover { background: var(--bg-hover); color: var(--fg-bright); }
.msg-actions button svg { width: 14px; height: 14px; stroke: currentColor; display: block; }
/* Bookmark + pin: when the action is "set" we fill the icon's body so
   the toggle state is unambiguous at a glance. The star/pin paths are
   closed shapes, so flipping fill from `none` to currentColor is the
   whole change — no separate active-state SVG needed. */
.msg-actions button.active { color: var(--yellow); }
.msg-actions button.active svg polygon,
.msg-actions button.active svg path { fill: currentColor; }
.reactions { display: flex; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
.reaction-pill {
  background: var(--bg-3); border: 1px solid var(--border); color: var(--fg);
  padding: 1px 8px; border-radius: 12px; font-size: 12px; cursor: pointer;
}
.reaction-pill.mine { background: rgba(99, 102, 241, 0.2); border-color: var(--accent); }
.reaction-pill:hover { border-color: var(--accent); }
.thread-chip {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 6px;
  background: transparent; border: 1px solid transparent; color: var(--accent-2);
  padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer; font-weight: 600;
}
.thread-chip:hover { background: var(--bg-3); border-color: var(--border); }
.thread-time { color: var(--fg-dim); font-weight: 400; margin-left: 4px; }
.msg-files { margin-top: 6px; display: flex; flex-direction: column; gap: 4px; }
.msg-file {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 10px; background: var(--bg-3); border-radius: 6px;
  border: 1px solid var(--border); max-width: 400px;
}
.msg-file.img { padding: 0; background: transparent; border: none; max-width: none; }
.msg-file a { color: var(--accent-2); text-decoration: none; }
.msg-file img { max-width: 380px; max-height: 280px; border-radius: 6px; display: block; border: 1px solid var(--border); }
/* Image-thumb button: looks like the bare image but is a real button so
   the click opens the lightbox rather than triggering a download. */
.msg-image-btn {
  background: transparent; border: 0; padding: 0; cursor: zoom-in; display: inline-block;
}
.msg-image-btn img { display: block; }

/* ---------- Image lightbox ---------- */
.img-lightbox {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.88);
  z-index: 5000;
  display: flex; align-items: center; justify-content: center;
  cursor: zoom-out;
  overflow: hidden;
}
.img-lightbox-stage {
  position: relative;
  width: 100vw; height: 100vh;
  overflow: hidden;
}
.img-lightbox-img {
  position: absolute;
  user-select: none;
  -webkit-user-drag: none;
  border-radius: 4px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.6);
  transition: width .15s ease, height .15s ease;
}
.img-lightbox-stage:not(.zoomed) .img-lightbox-img {
  left: 50%; top: 50%; transform: translate(-50%, -50%);
}
.img-lightbox-close {
  position: fixed; top: 14px; right: 14px;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(255,255,255,0.10);
  color: #fff;
  border: 0;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  z-index: 1;
}
.img-lightbox-close:hover { background: rgba(255,255,255,0.20); }
/* Download pill at the bottom of the zoomed view. Stays fixed even
   when the image is panned; clicking it doesn't dismiss the lightbox
   because the overlay's close-on-click checks e.target === overlay
   (or stage), which an inner anchor click doesn't satisfy. */
.img-lightbox-download {
  position: fixed; bottom: 18px; left: 50%;
  transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 16px;
  background: rgba(255,255,255,0.10);
  color: #fff;
  border-radius: 100px;
  text-decoration: none;
  font-size: 13px; font-weight: 600;
  cursor: pointer;
  z-index: 1;
  backdrop-filter: blur(4px);
}
.img-lightbox-download:hover { background: rgba(255,255,255,0.22); }
.img-lightbox-download svg { width: 16px; height: 16px; flex: 0 0 auto; }
.msg-file small { color: var(--fg-dim); margin-left: auto; font-size: 11px; }

/* ---------- Unfurls ---------- */
.unfurls { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.unfurl { display: block; }
.unfurl-image img { max-width: 380px; max-height: 280px; border-radius: 6px; border: 1px solid var(--border); }
/* YouTube embed: responsive 16:9, capped to the same width as other
   unfurls so a chat row never explodes width-wise. The aspect-ratio
   property handles the height — no padding-top hack needed. */
.unfurl-youtube {
  width: 100%; max-width: 480px;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border);
}
.unfurl-youtube iframe { width: 100%; height: 100%; border: 0; display: block; }
.unfurl-card {
  display: flex; gap: 10px; max-width: 480px;
  background: var(--bg-2); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  padding: 10px; border-radius: 4px; text-decoration: none; color: var(--fg);
}
.unfurl-card:hover { background: var(--bg-3); }
.unfurl-img { width: 80px; height: 80px; flex-shrink: 0; background-size: cover; background-position: center; border-radius: 4px; }
.unfurl-text { flex: 1; min-width: 0; }
.unfurl-site { color: var(--fg-dim); font-size: 11px; }
.unfurl-title { color: var(--fg-bright); font-weight: 600; margin-top: 2px; }
.unfurl-desc { color: var(--fg-dim); font-size: 12px; margin-top: 4px; }

.typing { padding: 0 18px; height: 18px; color: var(--fg-dim); font-size: 12px; font-style: italic; }

#composer {
  padding: 10px 18px 14px; display: flex; gap: 8px; align-items: flex-end;
  border-top: 1px solid var(--border); position: relative;
}
#composer textarea {
  flex: 1; resize: none; padding: 10px 12px; max-height: 200px;
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  border-radius: var(--radius); font-family: inherit; font-size: 14px;
}
#composer button[type=submit] {
  padding: 10px 14px; background: var(--accent); color: white;
  border: none; border-radius: var(--radius); cursor: pointer; font-weight: 600;
}
.upload, #emojiBtn {
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; background: var(--bg-3); color: var(--fg-dim);
}
.upload:hover, #emojiBtn:hover { background: var(--bg-hover); color: var(--fg-bright); }
.upload svg, #emojiBtn svg { width: 18px; height: 18px; stroke: currentColor; }

/* drag-and-drop overlay */
.drop-overlay {
  position: absolute; inset: 0; pointer-events: none; opacity: 0;
  background: rgba(99, 102, 241, 0.25); border: 3px dashed var(--accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700; color: var(--fg-bright);
  transition: opacity .15s; z-index: 10;
}
#main.dropping .drop-overlay { opacity: 1; }

/* ---------- Right rail ---------- */
#rail {
  background: var(--bg-2); border-left: 1px solid var(--border);
  overflow-y: auto; padding: 0;
}
.rail-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 14px; border-bottom: 1px solid var(--border); background: var(--bg-2);
  position: sticky; top: 0; z-index: 1;
}
.rail-header h3 { margin: 0; color: var(--fg-bright); font-size: 15px; }
/* Sub-header toolbar slot (used by the Inbox "Mark all read" button).
   Sits under the rail-header; secondary actions live here. */
.rail-toolbar {
  display: flex; gap: 6px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
}
.rail-toolbar-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px;
  background: var(--bg-3); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius);
  cursor: pointer; font-size: 12px;
}
.rail-toolbar-btn:hover:not(:disabled) { background: var(--bg-hover); color: var(--fg-bright); }
.rail-toolbar-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.rail-toolbar-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
#rail h4 { margin: 0 0 8px; color: var(--fg-bright); font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; }
#rail input[type=text], #rail input:not([type]), #rail input[type=password], #rail input[type=email],
#rail textarea, #rail select {
  width: 100%; padding: 8px 10px; background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius); font-size: 14px;
  font-family: inherit; margin-bottom: 4px;
}
#rail input[type=file] { width: 100%; margin-bottom: 8px; }
#rail label { display: block; margin: 8px 0 4px; color: var(--fg-dim); font-size: 12px; }
/* Inline checkbox row (e.g. "clone now") -- override the block-label rule
   AND the full-width input rule below so the box stays its natural size. */
#rail .checkbox-row {
  display: flex; align-items: center; gap: 8px;
  margin: 10px 12px 8px;
}
#rail .checkbox-row input[type=checkbox] {
  margin: 0; width: auto; flex: 0 0 auto;
}
#rail .checkbox-row label {
  display: inline; margin: 0; cursor: pointer;
  font-size: 13px; color: var(--fg);
  white-space: nowrap;
}

/* Rail header icon -- single color, sized to match the heading. */
.rail-header h3 { display: inline-flex; align-items: center; gap: 8px; }
.rail-header-icon { display: inline-flex; align-items: center; color: var(--fg-dim); }
.rail-header-icon svg { width: 16px; height: 16px; stroke: currentColor; }
#rail .item {
  padding: 8px 12px; margin: 0 12px 6px;
  background: var(--bg-3); border: 1px solid var(--border); border-radius: 6px;
}
#rail .item.search-item { cursor: pointer; }
#rail .item.search-item:hover { background: var(--bg-hover); }
#rail .item.unread { border-left: 3px solid var(--accent); }
#rail .item small { color: var(--fg-dim); font-size: 11px; display: block; margin-top: 2px; }
#rail .item .break { word-break: break-all; }
#rail .row-actions { margin-top: 6px; display: flex; gap: 6px; }
#rail .row-actions button, #rail button:not(.primary):not(.close-rail):not(.link-btn):not(.mem-btn) {
  background: var(--bg); color: var(--fg); border: 1px solid var(--border);
  padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
}
#rail button.primary {
  background: var(--accent); color: white; border: none;
  padding: 8px 14px; border-radius: 4px; cursor: pointer; font-weight: 600;
}
#rail button.danger { background: var(--red); color: white; border: none; }
#rail button.link-btn {
  background: transparent; border: none; color: var(--accent-2);
  cursor: pointer; padding: 0; font-size: 12px; margin-left: 8px;
}
#rail .close-rail {
  background: transparent; color: var(--fg-dim); border: none;
  cursor: pointer; font-size: 22px; line-height: 1; padding: 0 6px;
}
#rail .rail-section { padding: 14px 12px; border-top: 1px solid var(--border); }
#rail .rail-section:first-of-type { border-top: none; }

/* Indent + full-width text-like inputs in rail sections. Explicitly
   exclude checkbox / radio so they don't get stretched into empty
   full-width boxes (which made radio dots float centered above
   their labels in the in-rail admin panel). */
#rail input:not([type=checkbox]):not([type=radio]) {
  margin-left: 12px;
  width: calc(100% - 24px);
}
#rail input[type=color] { width: 60px; padding: 0; }

.filter-chips {
  display: flex; gap: 4px; flex-wrap: wrap; padding: 8px 12px;
  align-items: center;
}
.chip {
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  padding: 4px 10px; border-radius: 14px; cursor: pointer; font-size: 12px;
}
.chip.active { background: var(--accent); color: white; border-color: var(--accent); }

.member-row {
  /* Flat row, no surrounding tile/background/border. The only
     visible shape on the row is the avatar button itself — same
     visual treatment as the me-card. flex-wrap lets the perm-grid
     fall to a second line below the avatar+name+role line when an
     admin is viewing. */
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 12px;
}
.member-row:hover { background: var(--bg-hover); }
.member-row .name { color: var(--fg-bright); text-decoration: none; }
.member-row .perm-grid { flex-basis: 100%; margin: 4px 0 0 46px; }

/* Members rail avatar — defensive: explicit min/max/width/height on
   both the button and the image so no parent flex / inherited rule
   can introduce extra space. Image fills the button bounds. No
   border-radius, no extra background. */
.mem-btn {
  background: transparent;
  border: 0;
  margin: 0;
  padding: 0;
  cursor: pointer;
  display: block;
  line-height: 0;
  flex: 0 0 36px;
  width: 36px; min-width: 36px; max-width: 36px;
  height: 36px; min-height: 36px; max-height: 36px;
  overflow: hidden;
}
.mem-btn > img,
.mem-btn > .mem-initials {
  display: block;
  width: 36px; height: 36px;
  margin: 0; padding: 0;
  object-fit: cover;
}
.mem-btn > .mem-initials {
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 14px;
  text-transform: uppercase;
}
.mem-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.2; }
.mem-meta .name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mem-meta small  { font-size: 11px; }
.member-item { padding: 8px 12px; }
.dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--fg-dim); flex-shrink: 0;
}
.dot.online { background: var(--green); }
.perm-grid {
  display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap;
  font-size: 11px; color: var(--fg-dim);
}
.perm-grid label { display: flex; align-items: center; gap: 3px; margin: 0; }

/* ---------- Theme picker ---------- */
.theme-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; padding: 0 12px; }
.theme-card {
  background: var(--bg-3); border: 2px solid var(--border); border-radius: 6px;
  padding: 8px; cursor: pointer; text-align: center; color: var(--fg);
}
.theme-card.selected { border-color: var(--accent); }
.theme-card .swatch { height: 40px; border-radius: 4px; overflow: hidden; margin-bottom: 4px; }
.custom-grid {
  display: grid; grid-template-columns: 1fr auto; gap: 6px; align-items: center; margin: 8px 0;
}

/* ---------- Modals ---------- */
.modal {
  position: fixed; inset: 0; background: rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center; z-index: 100;
}
.modal-card {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 24px; min-width: 400px; max-width: 600px; max-height: 80vh; overflow-y: auto;
}
.modal-card h3 { margin: 0 0 12px; color: var(--fg-bright); }
.modal-card label { display: block; margin: 10px 0 4px; color: var(--fg-dim); font-size: 12px; }
.modal-card input, .modal-card select, .modal-card textarea {
  width: 100%; padding: 8px 10px; background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius); font-size: 14px;
  font-family: inherit;
}
.modal-card .actions { margin-top: 16px; display: flex; gap: 8px; justify-content: flex-end; }
.modal-card button {
  padding: 8px 14px; border: 1px solid var(--border); background: var(--bg-3);
  color: var(--fg); border-radius: var(--radius); cursor: pointer;
}
.modal-card button.primary { background: var(--accent); color: white; border-color: var(--accent); }

/* ---------- Contact card ---------- */
.contact-card { text-align: left; min-width: 360px; }
.contact-card .big-avatar {
  width: 80px; height: 80px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  color: white; font-weight: 700; font-size: 36px; margin-bottom: 12px;
}
.contact-card h3 { margin: 0; }
.contact-row { padding: 6px 0; border-top: 1px solid var(--border); display: flex; gap: 8px; }
.contact-row b { color: var(--fg-dim); min-width: 80px; font-weight: 500; }
.contact-bio {
  background: var(--bg-3); padding: 8px 12px; border-radius: 6px;
  margin-top: 12px; border-left: 3px solid var(--accent);
}

/* ---------- Threads (nested tree) ---------- */
.thread-tree { padding: 8px 8px 12px; }
.thread-node { padding: 2px 0; }
.thread-node .msg { background: var(--bg-3); border-radius: 6px; padding: 8px 10px; }
.thread-node .msg:hover { background: var(--bg-hover); }
.thread-msg { font-size: 13px; }
.thread-children {
  margin-left: 12px;
  padding-left: 8px;
  border-left: 2px solid var(--border);
  margin-top: 4px;
}
.thread-children .thread-node:first-child { padding-top: 4px; }

.inline-reply-form {
  margin: 6px 0 6px 12px;
  padding: 8px 10px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
}
.inline-reply-form .ir-meta {
  font-size: 11px; color: var(--fg-dim); margin-bottom: 6px;
}
.inline-reply-form textarea {
  width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 4px;
  font-family: inherit; font-size: 13px; resize: vertical;
}
.inline-reply-form .ir-actions {
  display: flex; gap: 6px; justify-content: flex-end; margin-top: 6px;
}
.inline-reply-form .ir-actions button {
  padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;
  border: 1px solid var(--border); background: var(--bg-3); color: var(--fg);
}
.inline-reply-form .ir-actions button.primary {
  background: var(--accent); color: white; border-color: var(--accent); font-weight: 600;
}

.thread-composer {
  padding: 10px 12px; display: flex; gap: 6px; flex-direction: column;
  position: sticky; bottom: 0; background: var(--bg-2); border-top: 1px solid var(--border);
}
/* The autocomplete popovers (slash/mention/emoji) anchor to their
   host with `bottom: 100%`, so the host needs an explicit positioned
   context. `position: sticky` already counts, but the inline thread
   composers below need an explicit `position: relative` for the same
   reason — without it the popover falls back to the nearest
   positioned ancestor (often #messages) and lands far from the
   composer. */
.inline-thread-composer { position: relative; }
.inline-reply-form      { position: relative; }
.thread-composer textarea {
  width: 100%; padding: 8px 10px; background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: var(--radius); font-family: inherit; resize: vertical;
}
.thread-composer button {
  align-self: flex-end; background: var(--accent); color: white;
  border: none; padding: 8px 14px; border-radius: var(--radius); cursor: pointer; font-weight: 600;
}

/* ---------- Emoji picker ---------- */
.emoji-picker {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
  width: 340px; padding: 8px; box-shadow: 0 4px 24px rgba(0,0,0,0.4);
  display: flex; flex-direction: column; gap: 6px;
}
.emoji-popover {
  position: absolute;
  bottom: 100%; left: 18px; margin-bottom: 6px; z-index: 50;
}
/* Floating variant (used by the React-on-message picker) is positioned with
   explicit inline `top`/`left`. Cancel the bottom-anchor inherited above so
   the height is determined by content, not viewport stretching. */
.emoji-popover.floating {
  bottom: auto; left: auto; margin-bottom: 0; z-index: 200;
}
.emoji-search {
  width: 100%; padding: 6px 8px; background: var(--bg); color: var(--fg);
  border: 1px solid var(--border); border-radius: 4px;
  font-family: inherit; font-size: 13px;
}
.emoji-tabs {
  display: flex; gap: 1px; border-bottom: 1px solid var(--border);
  padding-bottom: 4px;
}
.emoji-tabs.hidden { display: none; }
.emoji-tab {
  flex: 1; background: transparent; border: none; color: var(--fg);
  cursor: pointer; padding: 5px 0; font-size: 16px; line-height: 1;
  border-radius: 4px;
}
.emoji-tab:hover { background: var(--bg-hover); }
.emoji-tab.active {
  background: var(--bg-3); box-shadow: inset 0 -2px 0 var(--accent);
}
.emoji-grid {
  display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px;
  max-height: 240px; overflow-y: auto; padding: 2px;
}
.emoji-cell {
  background: transparent; border: none; cursor: pointer; padding: 4px;
  font-size: 20px; border-radius: 4px;
  display: flex; align-items: center; justify-content: center;
  height: 32px;
}
.emoji-cell:hover { background: var(--bg-hover); }

.emoji-autocomplete {
  position: absolute; bottom: 100%; left: 18px; margin-bottom: 6px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 6px;
  z-index: 50; min-width: 220px; box-shadow: 0 4px 20px rgba(0,0,0,0.4);
}
.emoji-auto-item {
  padding: 6px 10px; cursor: pointer; color: var(--fg); font-size: 13px;
}
.emoji-auto-item.active, .emoji-auto-item:hover { background: var(--bg-hover); }

/* @mention picker. Anchored above the composer like the emoji + slash
   pickers; rows show avatar + @username + display name so a partial
   match resolves to a clear identity at a glance. */
.mention-autocomplete {
  position: absolute; bottom: 100%; left: 18px; margin-bottom: 6px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 6px;
  z-index: 50; min-width: 260px; max-width: 360px;
  /* Cap to ~6 rows then scroll. The user keeps typing to narrow the
     match, but for short prefixes (`@a`) the list might still be
     long; arrow-key nav scrolls the active row into view. */
  max-height: 280px; overflow-y: auto;
  box-shadow: 0 4px 20px rgba(0,0,0,0.4);
}
.mention-auto-item {
  padding: 6px 10px; cursor: pointer; color: var(--fg); font-size: 13px;
  display: flex; align-items: center; gap: 8px;
  border-bottom: 1px solid var(--border);
}
.mention-auto-item:last-child { border-bottom: 0; }
.mention-auto-item.active, .mention-auto-item:hover { background: var(--bg-hover); }
.mention-auto-avatar {
  width: 22px; height: 22px; border-radius: 50%;
  object-fit: cover; flex: 0 0 auto;
  display: inline-flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 11px;
}
.mention-auto-name { font-weight: 600; color: var(--fg-bright); }
.mention-auto-dn   { font-size: 12px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Slash-command picker. Same anchor + chrome as the emoji picker, but
   each row carries a name + signature + description so the user gets
   a hint of what each command does. */
.slash-autocomplete {
  position: absolute; bottom: 100%; left: 18px; margin-bottom: 6px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 6px;
  z-index: 50; min-width: 360px; max-width: 520px;
  /* Scrollable when the full command list (or a broad partial like
     `/g`) overflows. Arrow-key nav scrolls the active row in. */
  max-height: 320px; overflow-y: auto;
  box-shadow: 0 4px 20px rgba(0,0,0,0.4);
}
.slash-auto-item {
  padding: 7px 12px; cursor: pointer; color: var(--fg); font-size: 13px;
  display: flex; align-items: baseline; gap: 8px;
  border-bottom: 1px solid var(--border);
}
.slash-auto-item:last-child { border-bottom: 0; }
.slash-auto-item.active, .slash-auto-item:hover { background: var(--bg-hover); }
.slash-auto-name {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--fg-bright); font-weight: 600; flex: 0 0 auto;
}
.slash-auto-sig {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--fg-dim); font-size: 12px; flex: 0 0 auto;
}
.slash-auto-desc {
  color: var(--fg-dim); font-size: 12px; margin-left: auto;
  text-align: right; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- Meeting dock ---------- */
#meeting-dock {
  position: fixed; bottom: 16px; right: 16px;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 14px; max-width: 520px; box-shadow: 0 8px 28px rgba(0,0,0,0.5);
  z-index: 200;
}
#meeting-dock .dock-title {
  color: var(--fg-bright); font-weight: 700; margin-bottom: 8px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
#meeting-dock .dock-title small { color: var(--fg-dim); font-weight: 400; margin-left: auto; }
#meeting-dock .dock-expand-btn {
  background: transparent; color: var(--fg-dim); border: 1px solid var(--border);
  cursor: pointer; padding: 2px 8px; border-radius: 4px;
  font-size: 14px; line-height: 1; flex: 0 0 auto;
}
#meeting-dock .dock-expand-btn:hover { background: var(--bg-hover); color: var(--fg-bright); }
#meeting-dock.dock-fullscreen .dock-expand-btn { color: var(--accent); }
.dock-badge {
  font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 9px;
  background: var(--accent); color: #fff; text-transform: uppercase;
}
.dock-badge.free { background: var(--green); }
.dock-badge.rec  { background: #d12c2c; animation: dock-rec-pulse 1.6s ease-in-out infinite; }
@keyframes dock-rec-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }

/* ---------- Per-channel notification picker ---------- */
#notifyBtn.muted { opacity: 0.7; }
.notify-popover {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.45);
  padding: 8px; width: min(320px, 92vw);
  z-index: 300;
  font-size: 13px;
}
.notify-popover .np-head {
  padding: 4px 10px 8px;
  color: var(--fg-dim); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600;
  border-bottom: 1px solid var(--border); margin-bottom: 6px;
}
.notify-popover .np-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 8px 10px; border-radius: 6px; cursor: pointer;
}
.notify-popover .np-row:hover { background: var(--bg-hover); }
.notify-popover .np-row.active { background: var(--bg-3); }
.notify-popover .np-row input { margin-top: 3px; }
.notify-popover .np-row-title { color: var(--fg-bright); font-weight: 600; }
.notify-popover .np-row-sub   { color: var(--fg-dim); font-size: 12px; margin-top: 1px; }
.notify-popover .np-perm {
  margin-top: 8px; padding: 8px 10px; border-radius: 6px;
  font-size: 12px; line-height: 1.4;
}
.notify-popover .np-perm.ok   { background: rgba(43, 172, 118, 0.15); color: var(--green); }
.notify-popover .np-perm.warn { background: rgba(255, 200, 50, 0.10); color: var(--yellow); border: 1px solid rgba(255, 200, 50, 0.25); }
.notify-popover .np-perm a { color: var(--accent-2); }
.notify-popover .np-vol {
  margin-top: 8px; padding: 8px 10px;
  background: var(--bg-3); border-radius: 6px;
}
.notify-popover .np-vol-label {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 12px; color: var(--fg-dim); margin-bottom: 6px;
}
.notify-popover .np-vol-label span { color: var(--fg-bright); font-variant-numeric: tabular-nums; }
.notify-popover .np-vol input[type=range] {
  width: 100%; cursor: pointer; accent-color: var(--accent);
}
.notify-popover .np-test { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
.notify-popover .np-test-btn {
  width: 100%; padding: 7px 10px; cursor: pointer;
  background: var(--bg-3); color: var(--fg);
  border: 1px solid var(--border); border-radius: 6px;
  font-size: 13px; font-weight: 600;
}
.notify-popover .np-test-btn:hover { background: var(--bg-2); }
.notify-popover .np-test-hint { color: var(--fg-dim); font-size: 11px; margin-top: 4px; }

.dock-tiles {
  display: grid; gap: 6px; margin-bottom: 8px;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.dock-tile {
  position: relative; background: var(--bg-3); border: 1px solid var(--border);
  border-radius: 6px; aspect-ratio: 4/3; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.dock-tile.me { border-color: var(--green); }
.dock-tile video,
.tile-video-slot,
.tile-video-slot video {
  width: 100%; height: 100%; object-fit: cover; background: #000; display: block;
}
.tile-video-slot { position: absolute; inset: 0; }
.tile-video-slot:empty { display: none; }
.tile-stub {
  font-size: 28px; font-weight: 700; color: var(--fg-bright);
  background: var(--accent); width: 56px; height: 56px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
}
.tile-stub.overlay {
  position: absolute; bottom: 6px; left: 6px;
  width: 28px; height: 28px; font-size: 12px; z-index: 1;
  border: 2px solid var(--bg-2);
}
.dock-tile .tile-name {
  position: absolute; bottom: 4px; right: 6px;
  font-size: 10px; color: #fff; background: rgba(0,0,0,0.6);
  padding: 2px 6px; border-radius: 3px; max-width: calc(100% - 40px);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: flex; gap: 4px; align-items: center; z-index: 2;
}
.mod-pip { color: #ffd84a; }

#meeting-dock .dock-actions {
  display: flex; flex-wrap: wrap; gap: 6px;
}
#meeting-dock .dock-actions button {
  flex: 1; min-width: 80px;
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;
}
#meeting-dock .dock-actions button:disabled { opacity: 0.5; cursor: not-allowed; }
#meeting-dock .dock-actions button.danger { background: var(--red); color: white; border-color: var(--red); }
#meeting-dock .dock-actions button.active { background: var(--accent); color: white; border-color: var(--accent); }

/* ---- Screen-share viewport + draggable camera PiP ---- */
/* When a screen is being shared, the dock expands to show a large
   viewport above the small camera tile strip. The viewport is the
   primary visual; cameras become a horizontal strip you can ignore.
   In a future pass the camera strip becomes a draggable PiP overlaid
   ON the screen viewport; for now it sits below for clarity. */
#meeting-dock.has-screen { max-width: 920px; }
#meeting-dock .dock-screen { display: none; }
#meeting-dock.has-screen .dock-screen {
  display: block; position: relative;
  margin-bottom: 8px; border: 1px solid var(--border); border-radius: 8px;
  background: #000; overflow: hidden; aspect-ratio: 16/9; max-height: 60vh;
}
#meeting-dock .dock-screen-host { position: absolute; inset: 0; }

/* Three view modes for the whiteboard / screen-share viewport, set by
   meetSetScreenView() in app.js. Default (no class): docked PiP at the
   bottom-right (existing #meeting-dock styles).

   .fullscreen — covers the entire viewport. No chat visible. Best for
   solo focus or if you're driving the presentation.

   .modal — large centered window, ~92vw × 88vh, with chat still
   visible behind/around. No backdrop dim, so the user can click out
   into chat without dismissing. Best for attendees who want to follow
   the presentation while staying responsive in chat. */
/* Higher specificity than `#meeting-dock.has-screen .dock-screen`
   (which sets aspect-ratio + max-height + margin) so we can actually
   take over the viewport when fullscreen / modal are toggled. */
#meeting-dock #dockScreen.fullscreen {
  position: fixed !important;
  inset: 0 !important;
  width: 100vw !important;
  height: 100vh !important;
  max-width: none !important;
  max-height: none !important;
  aspect-ratio: auto !important;
  margin: 0 !important;
  z-index: 10000;
  background: #000;
  border-radius: 0 !important;
}
#meeting-dock #dockScreen.modal {
  position: fixed !important;
  top: 6vh !important;
  bottom: 6vh !important;
  left: 4vw !important;
  right: 4vw !important;
  width: auto !important;
  height: auto !important;
  max-width: none !important;
  max-height: none !important;
  aspect-ratio: auto !important;
  margin: 0 !important;
  z-index: 9000;
  background: #000;
  border-radius: 12px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.7);
  overflow: hidden;
}
/* Suppress page scroll only in true fullscreen — modal mode keeps
   chat scrollable so the user can follow conversation. */
body.dock-fullscreen { overflow: hidden; }
#meeting-dock .dock-screen-host video {
  width: 100%; height: 100%; object-fit: contain; background: #000; display: block;
}
#meeting-dock .dock-screen-host video.self-share-mirror {
  /* Don't render an infinite-mirror of our own screen; just dim it. */
  opacity: 0.4;
}
/* Whiteboard surface: blank white area; the annotation canvas (sibling)
   sits on top and carries the actual drawn content. */
#meeting-dock .dock-screen-host .whiteboard-surface {
  position: absolute; inset: 0;
  background:
    /* Subtle dot grid so users have a sense of the surface and scale */
    radial-gradient(circle, rgba(0,0,0,0.08) 1px, transparent 1px) 0 0/24px 24px,
    #ffffff;
}
#meeting-dock.whiteboard .dock-annot { background: transparent; }
/* Annotation canvas sits over the screen video; pointer-events on so it
   captures drawing input, the underlying video can still play. */
#meeting-dock .dock-annot {
  position: absolute; inset: 0; cursor: crosshair; touch-action: none;
}
#meeting-dock .dock-screen-toolbar {
  position: absolute; top: 8px; left: 8px;
  display: flex; gap: 4px; padding: 4px;
  background: rgba(0,0,0,0.6); border-radius: 6px;
}
#meeting-dock .dock-screen-toolbar button {
  background: transparent; border: 1px solid transparent; color: #fff;
  width: 28px; height: 28px; border-radius: 4px; cursor: pointer; padding: 0;
}
#meeting-dock .dock-screen-toolbar button.active { background: var(--accent); border-color: var(--accent); }
#meeting-dock .dock-screen-toolbar button:hover { background: rgba(255,255,255,0.1); }
#meeting-dock .dock-screen-toolbar input[type=color] {
  width: 28px; height: 28px; border: none; padding: 0; background: transparent; cursor: pointer;
}
#meeting-dock .dock-screen-toolbar input[type=range] {
  width: 80px; accent-color: var(--accent);
}
#meeting-dock .dock-screen-toolbar .tool-sep {
  width: 1px; align-self: stretch; background: rgba(255,255,255,0.2); margin: 4px 2px;
}
#meeting-dock .dock-screen-label {
  position: absolute; bottom: 8px; left: 8px;
  font-size: 11px; color: #fff; background: rgba(0,0,0,0.6);
  padding: 3px 8px; border-radius: 3px;
}

/* ---- Draggable + resizable camera PiP ---- */
/* When a presentation surface is active, .dock-pip is rendered INSIDE
   .dock-screen at an absolute position. The user grabs .pip-handle (top
   bar) to move it, and .pip-resize (bottom-right chip) to scale it.
   Tile size is driven by the CSS var --pip-tile-w which JS updates
   during resize, so cameras grow/shrink in lockstep with the container. */
#meeting-dock .dock-pip {
  --pip-tile-w: 96px;
  position: absolute; right: 12px; bottom: 12px;
  background: rgba(0,0,0,0.55); border: 1px solid rgba(255,255,255,0.15);
  border-radius: 8px; padding: 6px;
  display: flex; flex-direction: column; gap: 4px;
  z-index: 5;
  backdrop-filter: blur(8px);
  user-select: none; touch-action: none;
}
#meeting-dock .dock-pip.dragging { opacity: 0.85; cursor: grabbing; }
#meeting-dock .pip-handle {
  height: 14px; cursor: grab; border-radius: 3px;
  background: linear-gradient(transparent 4px, rgba(255,255,255,0.25) 4px,
              rgba(255,255,255,0.25) 6px, transparent 6px,
              transparent 8px, rgba(255,255,255,0.25) 8px,
              rgba(255,255,255,0.25) 10px, transparent 10px);
}
#meeting-dock .pip-handle:active { cursor: grabbing; }

/* Resize chip in the bottom-right corner. The diagonal-grip glyph is
   drawn with two stacked stripes; cursor is a se-resize so users know
   what it does on hover. */
#meeting-dock .pip-resize {
  position: absolute; right: 2px; bottom: 2px;
  width: 18px; height: 18px; cursor: se-resize;
  background:
    linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.55) 50%, rgba(255,255,255,0.55) 60%, transparent 60%),
    linear-gradient(135deg, transparent 70%, rgba(255,255,255,0.55) 70%, rgba(255,255,255,0.55) 80%, transparent 80%);
  border-bottom-right-radius: 7px;
  z-index: 6;
}

#meeting-dock .pip-tiles {
  display: flex; gap: 4px; flex-wrap: wrap;
}
#meeting-dock .pip-tiles .dock-tile {
  width: var(--pip-tile-w);
  aspect-ratio: 4/3;
  border: 1px solid rgba(255,255,255,0.2);
}
#meeting-dock .pip-tiles .dock-tile .tile-name {
  font-size: calc(var(--pip-tile-w) * 0.09); padding: 1px 4px;
}
/* Stub-avatar size scales with tile so the initials don't get tiny */
#meeting-dock .pip-tiles .dock-tile .tile-stub {
  width: calc(var(--pip-tile-w) * 0.5);
  height: calc(var(--pip-tile-w) * 0.5);
  font-size: calc(var(--pip-tile-w) * 0.28);
}
/* Hide the regular grid when in PiP mode -- tiles are inside .dock-pip */
#meeting-dock.has-screen .dock-tiles { display: none; }
#meeting-dock.has-screen .dock-pip { display: flex; }
#meeting-dock:not(.has-screen) .dock-pip { display: none; }

/* When a screen share is active, shrink the camera strip so it doesn't
   compete for visual space. Cameras become a side-strip on wide dock. */
#meeting-dock.has-screen .dock-tiles {
  grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
}
#meeting-dock.has-screen .dock-tile { aspect-ratio: 1; }

/* ---- Code share + Notes side panels ---- */
.meet-panel {
  position: fixed; right: 16px; bottom: 320px; /* sit above the dock */
  width: 460px; max-width: 90vw; max-height: 60vh;
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 10px;
  z-index: 199; display: flex; flex-direction: column;
  box-shadow: 0 8px 28px rgba(0,0,0,0.5);
}
.meet-panel-head {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; border-bottom: 1px solid var(--border);
}
.meet-panel-head h3 { margin: 0; font-size: 14px; color: var(--fg-bright); flex: 1; }
.meet-panel-head .dim { color: var(--fg-dim); font-size: 11px; }
.meet-panel-head button {
  background: transparent; border: none; color: var(--fg-dim);
  font-size: 18px; cursor: pointer; padding: 2px 6px; border-radius: 4px;
}
.meet-panel-head button:hover { background: var(--bg-hover); color: var(--fg-bright); }
.meet-panel-body { padding: 10px 12px; overflow-y: auto; flex: 1; }

.meet-code-history { display: flex; flex-direction: column; gap: 10px; max-height: 280px; overflow-y: auto; }
.meet-code-msg { background: var(--bg-3); border-radius: 6px; padding: 8px 10px; }
.meet-code-meta { font-size: 11px; color: var(--fg-dim); margin-bottom: 4px; }
.meet-code-meta b { color: var(--fg); }
.meet-code-body pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
  padding: 8px; margin: 0; overflow-x: auto; font-size: 12px;
}
.meet-code-compose {
  display: grid; gap: 6px; margin-top: 10px;
  grid-template-columns: 1fr; grid-template-rows: auto 1fr auto;
}
.meet-code-compose select, .meet-code-compose textarea {
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  border-radius: 4px; padding: 6px 8px; font-family: ui-monospace, monospace; font-size: 12px;
}
.meet-code-compose textarea { resize: vertical; min-height: 80px; }
.meet-code-compose button.primary {
  background: var(--accent); color: white; border: none; padding: 8px 12px;
  border-radius: 4px; cursor: pointer; font-weight: 600;
}

#meetNotesText {
  width: 100%; min-height: 280px;
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  border-radius: 6px; padding: 10px 12px; font-family: ui-monospace, monospace;
  font-size: 13px; resize: vertical;
}

/* Start meeting modal */
.mt-field { margin: 14px 0; }
.mt-field:first-of-type { margin-top: 0; }
.form-label {
  display: block; color: var(--fg-dim); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.5px; margin: 0 0 6px;
}
.form-label .opt { color: var(--fg-dim); font-weight: 400; text-transform: none; letter-spacing: 0; }
.form-value { color: var(--accent); font-weight: 700; text-transform: none; letter-spacing: 0; }
.mt-field .hint { color: var(--fg-dim); font-size: 11px; margin-top: 6px; }

.card-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.picker-card {
  display: flex; align-items: flex-start; gap: 10px;
  text-align: left; cursor: pointer; padding: 12px;
  background: var(--bg-3); color: var(--fg);
  border: 2px solid var(--border); border-radius: 8px;
  font-family: inherit; font-size: 13px;
  transition: border-color .12s, background .12s, transform .04s;
}
.picker-card:hover { background: var(--bg-hover); }
.picker-card:active { transform: translateY(1px); }
.picker-card[aria-pressed="true"] {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-3));
}
.pc-icon { font-size: 20px; line-height: 1; flex-shrink: 0; }
.pc-body { flex: 1; min-width: 0; }
.pc-label { font-weight: 700; color: var(--fg-bright); font-size: 14px; }
.pc-desc { font-size: 11px; color: var(--fg-dim); margin-top: 2px; }

/* Slider for max cameras */
.mt-range {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 6px; background: var(--bg-3); border-radius: 3px;
  outline: none; padding: 0 !important; border: none !important;
}
.mt-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); cursor: pointer; border: 3px solid var(--bg-2);
  box-shadow: 0 0 0 1px var(--border);
}
.mt-range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); cursor: pointer; border: 3px solid var(--bg-2);
  box-shadow: 0 0 0 1px var(--border);
}

/* Moderator panel */
.mp-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 8px; }
.mp-table th, .mp-table td {
  text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border);
}
.mp-table th { color: var(--fg-dim); font-size: 11px; text-transform: uppercase; font-weight: 700; }
.mp-table tr.hand-up { background: rgba(236, 178, 46, 0.10); }
.mp-toggle {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer; font-size: 12px;
}
.mp-table button {
  background: var(--bg-3); border: 1px solid var(--border); color: var(--fg);
  padding: 4px 10px; font-size: 11px; border-radius: 4px; cursor: pointer;
}
.mp-table button:hover { background: var(--bg-hover); }

/* ---------- Onboarding ---------- */
#onboard-modal { z-index: 500; }
#onboard-modal details summary { cursor: pointer; }

/* ---------- Formatting toolbar ---------- */
#composerWrap { border-top: 1px solid var(--border); }
.format-bar {
  display: flex; align-items: center; gap: 2px;
  padding: 4px 18px; flex-wrap: wrap;
}
.format-bar.collapsed .fb-btn:not(.fb-toggle):not(#fbReminder):not(#fbImage) { display: none; }
.format-bar.collapsed .fb-sep { display: none; }
.format-bar.collapsed .fb-toggle { transform: rotate(180deg); }
.fb-btn {
  background: transparent; color: var(--fg); border: none; cursor: pointer;
  padding: 4px 8px; border-radius: 4px; font-size: 13px; min-width: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  line-height: 1;
}
.fb-btn:hover { background: var(--bg-hover); color: var(--fg-bright); }
.fb-btn svg { width: 14px; height: 14px; stroke: currentColor; }
.fb-sep { width: 1px; background: var(--border); margin: 4px 4px; align-self: stretch; }
#composerWrap #composer { border-top: none; padding-top: 4px; }

/* ---------- Thread naming (mig #34) ---------- */
.thread-rename-btn {
  background: transparent;
  border: 1px solid transparent;
  color: var(--fg-dim);
  cursor: pointer;
  padding: 1px 6px;
  margin-left: 6px;
  border-radius: 4px;
  font-size: 12px;
}
.thread-rename-btn:hover {
  border-color: var(--border);
  color: var(--fg-bright);
  background: var(--bg-hover);
}

/* ---------- Long-message collapse (mig #33 / view toggle) ---------- */
.msg-view-btn {
  display: inline-block;
  margin: 6px 6px 0 0;
  padding: 3px 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: 4px;
  cursor: pointer;
  line-height: 1.3;
}
.msg-view-btn:hover { filter: brightness(1.1); }
.msg-view-btn.ghost {
  background: transparent;
  color: var(--fg-dim);
  border-color: var(--border);
}
.msg-view-btn.ghost:hover { color: var(--fg-bright); border-color: var(--fg-dim); }

/* Single-line peek used by the user-collapsed view. Forces all content
   to inline so a markdown paragraph + code block etc. renders flat. */
.msg-collapsed-line {
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--fg-dim);
  font-style: italic;
}
.msg-collapsed-line * {
  display: inline !important;
  margin: 0 !important;
  padding: 0 !important;
}

/* Subtle gradient fade at the bottom of a truncated long message so
   the user can tell at a glance there's more underneath. */
.msg-truncated-fade {
  height: 24px;
  margin-top: -24px;
  background: linear-gradient(to bottom, transparent, var(--bg) 90%);
  pointer-events: none;
  position: relative;
}
.msg:hover .msg-truncated-fade {
  background: linear-gradient(to bottom, transparent, var(--bg-hover) 90%);
}

/* ---------- Voice messages (mig #32) ---------- */
/* Record button is just another .upload-shaped icon button. The
   .recording state turns the mic red + adds a subtle pulse so a user
   who minimized the window can tell at a glance recording is live. */
#recordBtn.recording {
  color: var(--red, #ef4444);
  animation: voice-rec-pulse 1.4s ease-in-out infinite;
}
@keyframes voice-rec-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* Recording bar — replaces the input area while a recording is in
   progress. Inserted into #composerWrap above the form. */
#voiceBar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 18px;
  background: rgba(239, 68, 68, 0.08);
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--fg);
}
#voiceBar .vb-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--red, #ef4444);
  animation: voice-rec-pulse 1.0s ease-in-out infinite;
  flex: 0 0 auto;
}
#voiceBar .vb-text { flex: 1; min-width: 0; }
#voiceBar #voiceTimer {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--fg-bright);
  margin-left: 4px;
}
#voiceBar .vb-btn {
  border: 1px solid var(--border);
  background: transparent;
  color: var(--fg);
  padding: 4px 12px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 13px;
  flex: 0 0 auto;
}
#voiceBar .vb-btn:hover { background: var(--bg-hover); color: var(--fg-bright); }
#voiceBar .vb-send {
  background: var(--accent, #6366f1);
  border-color: var(--accent, #6366f1);
  color: #fff;
  font-weight: 600;
}
#voiceBar .vb-send:hover { filter: brightness(1.1); background: var(--accent, #6366f1); color: #fff; }
#voiceBar .vb-cancel { color: var(--fg-dim); }

/* Legacy audio styles intentionally removed — the iMessage-style
   .audio-player at the bottom of this file replaces them. Keeping
   nothing here so the new player's flex layout isn't overridden. */

/* Hidden utility — used by index.html to keep #recordBtn invisible
   until JS confirms the workspace allows voice messages. */
#recordBtn.hidden { display: none !important; }

/* ---------- Internal permalink previews ---------- */
.unfurl-internal {
  display: block; max-width: 540px; padding: 8px 12px;
  background: var(--bg-2); border: 1px solid var(--border); border-left: 3px solid var(--accent-2);
  border-radius: 4px; text-decoration: none; color: var(--fg);
}
.unfurl-internal:hover { background: var(--bg-3); }
.ui-meta { display: flex; gap: 8px; align-items: baseline; font-size: 11px; }
.ui-sigil { color: var(--accent-2); font-weight: 700; }
.ui-author { color: var(--fg-bright); font-weight: 600; }
.ui-time { color: var(--fg-dim); margin-left: auto; }
.ui-body { color: var(--fg); margin-top: 2px; font-size: 13px; word-break: break-word; }

/* ---------- Reminder modal ---------- */
.rem-presets { display: flex; gap: 6px; flex-wrap: wrap; margin: 4px 0 10px; }
.rem-presets button {
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  padding: 4px 10px; border-radius: 14px; cursor: pointer; font-size: 12px;
}
.rem-presets button:hover { border-color: var(--accent); }

/* ---------- Timeline ---------- */
.tl-day-head {
  padding: 8px 12px; color: var(--fg-dim); font-weight: 700; font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.5px; background: var(--bg);
  position: sticky; top: 40px; z-index: 1;
}
.tl-item { cursor: pointer; }
.tl-item:hover { background: var(--bg-hover); }
.tl-meta { display: flex; gap: 8px; align-items: baseline; font-size: 11px; }
.tl-ch { color: var(--accent-2); font-weight: 700; }
.tl-author { color: var(--fg-bright); font-weight: 600; }
.tl-time { color: var(--fg-dim); margin-left: auto; }
.tl-body { color: var(--fg); margin-top: 2px; font-size: 13px; }
.tl-body p { margin: 0; }
/* Deleted messages still surface in the admin timeline (audit view) but
   are visually struck-through and badged so they don't read as live. */
.tl-deleted .tl-body, .tl-deleted .tl-body p { text-decoration: line-through; color: var(--fg-dim); }
.tl-deleted-badge {
  background: rgba(220, 53, 69, 0.18); color: #ff8a95;
  font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 9px;
  margin-left: auto;
}

/* ---------- Emoji rendering ---------- */
img.emoji {
  height: 1.2em; width: 1.2em;
  vertical-align: -0.18em;
  display: inline-block;
  margin: 0 1px;
}
img.emoji-custom {
  height: 1.4em; width: auto; max-width: 1.6em;
  vertical-align: -0.25em;
  border-radius: 3px;
}
/* Picker emoji cells with images */
.emoji-cell img { width: 22px; height: 22px; display: block; margin: 0 auto; }
.emoji-cell img.emoji-custom { max-width: 22px; max-height: 22px; height: auto; width: auto; }
.emoji-section-head {
  display: flex; justify-content: space-between; align-items: center;
  font-size: 11px; text-transform: uppercase; color: var(--fg-dim);
  font-weight: 700; margin: 6px 4px 2px; letter-spacing: 0.5px;
}
.emoji-add-btn {
  background: transparent; color: var(--fg-dim); border: none; cursor: pointer;
  font-size: 16px; padding: 0 4px;
}
.emoji-add-btn:hover { color: var(--fg-bright); }
.emoji-empty {
  grid-column: 1 / -1;
  padding: 8px; color: var(--fg-dim); font-size: 12px; text-align: center;
}
.emoji-count {
  padding: 4px 6px; font-size: 11px; color: var(--fg-dim); text-align: right;
}
.ce-hint { font-size: 11px; min-height: 1em; padding: 2px 0 8px; color: var(--fg-dim); }
.ce-hint.ok  { color: var(--green); }
.ce-hint.bad { color: var(--red); }
.emoji-item {
  display: flex; align-items: center; gap: 8px;
}
.emoji-item img { width: 24px; height: 24px; object-fit: contain; }
.emoji-item code { color: var(--fg); }
.emoji-item small { color: var(--fg-dim); margin-left: auto; }

/* Big emoji in reactions and large in messages with no other content */
.reaction-pill img.emoji { height: 1em; width: 1em; vertical-align: -0.15em; }
.reaction-pill img.emoji-custom { height: 1.1em; width: auto; max-width: 1.3em; }
#toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--bg-2); color: var(--fg-bright);
  border: 1px solid var(--border); border-left: 3px solid var(--accent);
  padding: 10px 16px 10px 12px; border-radius: 8px; font-size: 13px;
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  z-index: 2000;       /* above the editor modal AND ui-dialog stack */
  cursor: pointer;
  max-width: 80vw;
  display: flex;
  align-items: center;
  gap: 10px;
  box-shadow: 0 10px 24px rgba(0,0,0,0.32);
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
#toast .toast-icon {
  width: 20px; height: 20px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  font-size: 12px; font-weight: 700;
  background: var(--accent); color: #fff;
  flex: 0 0 auto;
}
#toast .toast-msg { white-space: pre-wrap; word-break: break-word; }
#toast.toast-info    { border-left-color: var(--accent); }
#toast.toast-info    .toast-icon { background: var(--accent); }
#toast.toast-success { border-left-color: #7ed484; }
#toast.toast-success .toast-icon { background: #7ed484; color: #1a1d21; }
#toast.toast-warn    { border-left-color: #d9a85c; }
#toast.toast-warn    .toast-icon { background: #d9a85c; color: #1a1d21; }
#toast.toast-error   { border-left-color: #d97070; }
#toast.toast-error   .toast-icon { background: #d97070; }

/* ---------- Stylized dialog (uiAlert / uiConfirm / uiPrompt) ----------
   Each dialog gets its own overlay+card; can stack on top of the
   existing #modal without colliding with #modalCard's content. */
.ui-dialog-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center;
}
.ui-dialog-card {
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 20px;
  min-width: 360px;
  max-width: 520px;
  max-height: 80vh;
  overflow-y: auto;
  box-shadow: 0 8px 32px rgba(0,0,0,0.45);
}
.ui-dialog-card h3 { margin: 0 0 12px; color: var(--fg-bright); font-size: 16px; }
.ui-dialog-msg {
  margin-bottom: 12px;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--fg);
  font-size: 13px;
}
.ui-dialog-input {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 10px;
  font-size: 13px;
  font-family: inherit;
}
textarea.ui-dialog-input { resize: vertical; min-height: 80px; font-family: ui-monospace, "SF Mono", Menlo, monospace; }
.ui-dialog-actions {
  margin-top: 16px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.ui-dialog-actions button {
  padding: 6px 14px;
  font-size: 13px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.ui-dialog-actions button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.ui-dialog-actions button.danger  { background: #b14040; border-color: #b14040; color: #fff; }
.ui-dialog-actions button:hover { filter: brightness(1.1); }
.modal-card .actions button.danger {
  background: #b14040;
  border-color: #b14040;
  color: #fff;
}

/* ---------- Browse channels modal ---------- */
.bc-list {
  max-height: 50vh; overflow-y: auto; margin-top: 8px;
  border: 1px solid var(--border); border-radius: 6px;
}
.browse-row {
  display: flex; align-items: center; gap: 10px; padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}
.browse-row:last-child { border-bottom: none; }
.browse-row:hover { background: var(--bg-3); }
.browse-sigil { color: var(--fg-dim); font-weight: 700; font-size: 16px; }
.browse-info { flex: 1; min-width: 0; }
.browse-name { font-weight: 700; color: var(--fg-bright); }
.browse-topic { color: var(--fg); font-size: 12px; margin-top: 2px; }
.browse-meta { color: var(--fg-dim); font-size: 11px; margin-top: 2px; }
.bc-join {
  background: var(--accent); color: white; border: none;
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  font-size: 18px; line-height: 1; flex-shrink: 0; font-weight: 700;
}
.bc-join:hover { background: var(--accent-2); }
.bc-open {
  background: transparent; color: var(--fg-dim); border: 1px solid var(--border);
  padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; flex-shrink: 0;
}
.bc-open:hover { background: var(--bg-hover); color: var(--fg-bright); }

/* ---------- User card in sidebar ---------- */
.me-card {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: transparent; border: none; cursor: pointer;
  padding: 8px 6px; margin-top: 8px; border-radius: 6px; color: var(--fg);
  text-align: left;
}
.me-card:hover { background: var(--bg-hover); }
.me-avatar {
  width: 36px; height: 36px; flex-shrink: 0; position: relative;
  border-radius: 6px; overflow: visible;
}
.me-avatar img,
.me-avatar .me-initials {
  width: 100%; height: 100%; border-radius: 6px; object-fit: cover; display: block;
}
.me-avatar .me-initials {
  display: flex; align-items: center; justify-content: center;
  color: white; font-weight: 700; font-size: 16px;
}
.me-avatar .me-statusdot {
  position: absolute; right: -3px; bottom: -3px;
  width: 14px; height: 14px;
  border: 2px solid var(--bg-2); border-radius: 50%; box-sizing: content-box;
  background: var(--bg-2);
}
.me-avatar .me-statusdot svg { width: 100%; height: 100%; display: block; }
.me-meta { flex: 1; min-width: 0; }
.me-name { color: var(--fg-bright); font-weight: 600; font-size: 13px; display: block; }
.me-status {
  color: var(--fg-dim); font-size: 11px; display: flex; gap: 4px; align-items: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.me-status .se { display: inline-block; }
.me-caret { color: var(--fg-dim); flex-shrink: 0; }

/* ---------- Status SVG icons ---------- */
.status-dot {
  display: inline-block; vertical-align: middle; line-height: 0;
}
.status-dot svg { width: 100%; height: 100%; display: block; }

/* ---------- Avatar (in message) ---------- */
.msg .avatar { overflow: hidden; }
.msg .avatar.avatar-img { padding: 0; background: transparent; }
.msg .avatar.avatar-img img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* Built-in bot avatars: VoksioBot (Voksio logo) + gitbot (branch
   glyph). Same footprint as user avatars; no initial-letter
   fallback because we always have an icon to show. */
.msg .avatar.avatar-bot {
  padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
}
.msg .avatar.avatar-bot-voksio { background: transparent; }
.msg .avatar.avatar-bot-voksio img {
  width: 100%; height: 100%; object-fit: contain; display: block;
}
.msg .avatar.avatar-bot-git {
  background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
  color: #fff;
}
.msg .avatar.avatar-bot-git svg {
  width: 60%; height: 60%;
}

/* ---------- Contact card avatar ---------- */
.contact-avatar { margin-bottom: 12px; }
.big-avatar-img,
.contact-card .big-avatar {
  width: 80px; height: 80px; border-radius: 12px; display: block;
}
.big-avatar-img { object-fit: cover; }
.contact-status { display: flex; align-items: center; gap: 8px; }
.contact-status .status-dot { width: 14px; height: 14px; }

/* ---------- Status modal ---------- */
.status-presets {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 6px;
  margin-top: 8px;
}
.status-preset {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg-3); color: var(--fg);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 10px; cursor: pointer; font-size: 13px; text-align: left;
}
.status-preset:hover { background: var(--bg-hover); }
.status-preset.selected { border-color: var(--accent); background: rgba(99,102,241,.12); }
.status-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 16px 0 8px; color: var(--fg-dim); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.5px; font-weight: 700;
}
.status-divider::before, .status-divider::after {
  content: ''; flex: 1; border-top: 1px solid var(--border);
}
.custom-status-row { display: flex; gap: 8px; }
/* Status-emoji input + smiley picker button. The button overlays the
   right edge of the input; clicking it opens the chat emoji picker
   (same one the composer uses) and drops the picked token into the
   field. */
.cs-emoji-wrap { position: relative; flex: 0 0 130px; }
.cs-emoji-wrap input {
  width: 100%; padding-right: 32px; box-sizing: border-box;
}
.cs-emoji-btn {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 0;
  cursor: pointer; font-size: 16px; line-height: 1;
  border-radius: 4px;
  color: var(--fg-dim);
}
.cs-emoji-btn:hover { background: var(--bg-hover); color: var(--fg-bright); }
.avatar-row {
  display: flex; gap: 12px; align-items: center; margin-top: 8px;
}
.avatar-preview {
  width: 64px; height: 64px; border-radius: 12px; object-fit: cover; flex-shrink: 0;
  border: 1px solid var(--border);
}
.avatar-row button {
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;
  margin-right: 4px;
}
.avatar-row button:hover { background: var(--bg-hover); }

/* ---------- "Start a DM" picker rows ----------
   Tabular layout: [status pill] [avatar] [name]. Whole row is a
   button -- click anywhere selects the user. Name is forced to white
   without an underline since it's not a real link. */
.dm-picker { margin-top: 8px; display: flex; flex-direction: column; gap: 2px; }
.dm-pick-row {
  display: grid;
  grid-template-columns: 16px 28px 1fr;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border-radius: 6px;
  cursor: pointer;
  user-select: none;
}
.dm-pick-row:hover, .dm-pick-row:focus-visible {
  background: var(--bg-hover);
  outline: none;
}
.dm-pick-row .status-dot { width: 12px; height: 12px; }
.dm-pick-avatar {
  width: 28px; height: 28px;
  border-radius: 6px;
  display: flex; align-items: center; justify-content: center;
  color: white; font-weight: 700; font-size: 13px;
  object-fit: cover;
  flex-shrink: 0;
}
.dm-pick-name {
  color: #fff;
  text-decoration: none;
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Migrations table ---------- */
.mig-table {
  width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px;
}
.mig-table th, .mig-table td {
  text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--border);
}
.mig-table th { color: var(--fg-dim); font-weight: 700; text-transform: uppercase; font-size: 11px; }
.mig-table tr.mig-pending { background: rgba(236, 178, 46, 0.08); }

/* ---------- Inline video player ---------- */
.msg-video-wrap {
  display: inline-block;
  max-width: 480px;
  margin-top: 4px;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}
.msg-video-wrap video {
  display: block;
  width: 100%;
  max-height: 360px;
  background: #000;
  cursor: pointer;
}
.vc-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: var(--bg-2);
  border-top: 1px solid var(--border);
}
.vc-btn {
  background: transparent;
  color: var(--fg);
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 13px;
  line-height: 1;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
}
.vc-btn:hover { background: var(--bg-hover); color: var(--fg-bright); }
.vc-seek {
  flex: 1; min-width: 60px;
  height: 4px; cursor: pointer;
  -webkit-appearance: none; appearance: none;
  background: var(--bg-3); border-radius: 2px; outline: none;
}
.vc-seek::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent); cursor: pointer; border: none;
}
.vc-seek::-moz-range-thumb {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--accent); cursor: pointer; border: none;
}
.vc-vol {
  width: 70px; height: 4px; cursor: pointer;
  -webkit-appearance: none; appearance: none;
  background: var(--bg-3); border-radius: 2px; outline: none;
}
.vc-vol::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--fg); cursor: pointer; border: none;
}
.vc-vol::-moz-range-thumb {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--fg); cursor: pointer; border: none;
}
.vc-time {
  color: var(--fg-dim); font-size: 11px; font-variant-numeric: tabular-nums;
  white-space: nowrap; padding: 0 4px;
}
.vc-name {
  padding: 4px 10px;
  color: var(--fg-dim);
  font-size: 11px;
  background: var(--bg-2);
  border-top: 1px solid var(--border);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* (Legacy .msg-file.audio rules removed — see .audio-player below.) */

/* ---------- Auth + signup pages ---------- */
.auth-brand h1 { margin: 0 0 4px; }
.auth-logo {
  width: 56px; height: 56px; margin: 0 auto 16px;
  display: flex; align-items: center; justify-content: center;
}
.auth-logo svg { width: 100%; height: 100%; display: block; }
.auth-brand { text-align: center; margin-bottom: 24px; }
.auth-footer {
  margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border);
  text-align: center; font-size: 13px;
}
.slug-row {
  display: flex; align-items: center; gap: 4px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 0 10px;
}
.slug-row input { flex: 1; padding: 8px 0; background: transparent; border: none; color: var(--fg); font-size: 14px; outline: none; font-family: inherit; }
.slug-suffix { color: var(--fg-dim); font-size: 13px; white-space: nowrap; }
.form-label { display: block; color: var(--fg-dim); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px; margin: 0 0 6px; }

/* ---------- Platform control panel ---------- */
.platform-page {
  display: block; height: auto; min-height: 100vh;
  background: var(--bg); color: var(--fg); padding: 24px;
}
.platform-header {
  display: flex; justify-content: space-between; align-items: flex-start;
  max-width: 1100px; margin: 0 auto 24px;
}
.platform-header h1 { margin: 0; font-size: 24px; }
.platform-header p { margin: 4px 0 0; }
.btn-ghost {
  display: inline-block; padding: 6px 12px; color: var(--fg);
  text-decoration: none; border: 1px solid var(--border);
  border-radius: 4px; background: var(--bg-3);
}
.btn-ghost:hover { background: var(--bg-hover); }

.platform-main { max-width: 1100px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
.card {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 16px;
}
.card-head {
  display: flex; justify-content: space-between; align-items: center;
  margin-bottom: 12px;
}
.card-head h2 { margin: 0; font-size: 18px; color: var(--fg-bright); }
.card-row { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.card .primary {
  background: var(--accent); color: white; border: none;
  padding: 6px 12px; border-radius: 4px; cursor: pointer; font-weight: 600;
}
.ckbox {
  display: flex; gap: 8px; align-items: center; cursor: pointer; user-select: none;
}

.tenants-table {
  width: 100%; border-collapse: collapse; font-size: 13px;
}
.tenants-table th, .tenants-table td {
  text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border);
}
.tenants-table th { color: var(--fg-dim); font-size: 11px; text-transform: uppercase; font-weight: 700; }
.tenants-table tbody tr:hover { background: var(--bg-3); }
.tenants-table button {
  background: var(--bg-3); color: var(--fg); border: 1px solid var(--border);
  padding: 4px 8px; border-radius: 3px; cursor: pointer; font-size: 11px;
  margin-right: 4px;
}
.tenants-table button:hover { background: var(--bg-hover); }
.tenants-table button.danger { background: rgba(220, 53, 69, 0.15); border-color: rgba(220, 53, 69, 0.4); color: #ff8a95; }
.row-actions { white-space: nowrap; }
.pill {
  display: inline-block; padding: 2px 8px; border-radius: 9px; font-size: 11px;
  font-weight: 700; text-transform: uppercase;
}
.pill-active   { background: rgba(43, 172, 118, 0.2); color: #4ade80; }
.pill-suspended{ background: rgba(236, 178, 46, 0.2); color: #fbbf24; }
.pill-archived { background: rgba(120, 120, 120, 0.2); color: #a8a8a8; }

#overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none;
  align-items: center; justify-content: center; z-index: 1000;
}
#overlay.show { display: flex; }
.overlay-card {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 20px; max-width: 800px; width: 90%; max-height: 85vh; overflow: auto;
}

/* ============================================================
   Mobile / narrow viewport
   ------------------------------------------------------------
   Below 720px the desktop grid (sidebar | resizer | main | resizer | rail)
   stops working — a 260px sidebar + 360px rail leaves nothing for the
   message column on a phone. We collapse the layout to a single column
   and turn the sidebar + rail into overlays that slide in over main.

   The collapsed-sidebar default is set in JS (applyLayout in app.js):
   when no preference is stored AND the viewport is narrower than 720px,
   the sidebar starts hidden. Tapping outside an open sidebar closes it
   (also wired in app.js initSidebarToggle).
   ============================================================ */

@media (max-width: 720px) {
  /* Lock the page to the viewport so finger-swipes can't drag the
     entire body around (iOS Safari "rubber band" / URL-bar show-hide
     pulling the layout up under the address bar). Once html and body
     are position:fixed + overflow:hidden, only descendants with their
     own overflow:auto can scroll — which is what we want: only
     .messages scrolls, header + composer stay fixed. */
  html, body {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
  }
  /* When .messages reaches its top/bottom, don't let the bounce
     propagate to ancestors (defense in depth even with body locked). */
  .messages { overscroll-behavior: contain; }

  /* Single-column grid in every state. The sidebar and rail leave the
     grid entirely (position: fixed) so they overlay main rather than
     pushing it. (body.app == .app is the same element; same-element
     selectors are used so the rule actually matches.) */
  .app,
  .app.rail-open,
  .app.sidebar-collapsed,
  .app.sidebar-collapsed.rail-open {
    grid-template-columns: 1fr;
  }

  #sidebar {
    position: fixed; left: 0; top: 0; bottom: 0;
    width: min(82vw, 320px);
    z-index: 50;
    box-shadow: 2px 0 16px rgba(0, 0, 0, 0.5);
  }
  /* On mobile the sidebar is a fixed overlay rather than a grid track,
     so the desktop grow/shrink animation doesn't apply. Hide instantly
     when collapsed to avoid the 220ms ghost the desktop visibility
     delay would otherwise introduce. */
  body.sidebar-collapsed #sidebar { display: none; }
  /* Resizers are mouse-only — useless on touch and they steal swipes. */
  .resizer { display: none; }

  /* Channel header tightens up. The topic is nice-to-have on desktop;
     on a phone the title + buttons matter more. */
  #channelHeader { padding: 8px 10px; gap: 6px; }
  #channelHeader h2 { font-size: 15px; }
  .topic { display: none; }
  .header-actions { gap: 4px; }
  .header-actions button {
    padding: 8px; min-width: 38px; min-height: 38px;
  }
  /* Drop the text labels next to header-action icons -- icons stay,
     labels would wrap and overflow. The Meet button keeps its label
     because users genuinely don't recognize the phone-handset icon. */
  .header-actions button > span:not(#meetBtnLabel) { display: none; }

  /* Rail (thread / files / search panel) takes the whole screen as a
     sheet -- there's no room for a side-by-side layout. */
  #rail {
    position: fixed; inset: 0;
    width: 100vw; max-width: 100vw;
    z-index: 60;
  }

  /* Messages + composer go edge-to-edge with smaller paddings. */
  .messages { padding: 8px 10px; }
  #composer { padding: 8px 10px; gap: 6px; }
  #composer textarea { font-size: 16px; }  /* iOS won't auto-zoom at 16px+ */

  /* Mobile viewport height: vh includes the URL bar (which slides away
     on scroll), so #main with height:100vh ends up taller than the
     visible viewport and the composer scrolls off-screen. dvh tracks
     the actual visible height. The vh line above stays as a fallback
     for older browsers that don't support dvh (Safari < 15.4).
     overflow:hidden so only .messages can scroll — without it, the
     whole #main can become scrollable on iOS when content overflows
     and the channel header / composer drift off-screen with the body. */
  #main {
    height: 100vh;     /* fallback */
    height: 100dvh;
    overflow: hidden;
  }
  /* Pin header + composer as non-shrinkable flex items. .messages
     (flex: 1, overflow-y: auto) is the only scrollable child. */
  #channelHeader,
  #composerWrap,
  #composer { flex: 0 0 auto; }
  /* Belt-and-suspenders: even if some ancestor's overflow changes,
     stick the header to the top of #main visually. */
  #channelHeader {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--bg);
  }

  /* Pinned banner indents inherit messages padding. */
  .pin-banner { margin: 0 10px 6px; }

  /* Meeting dock floats over the bottom of the screen. Drop the right-
     edge offset and let it span almost the whole width. */
  #meeting-dock {
    right: 8px; left: 8px; bottom: 8px;
    max-width: none; width: auto;
    padding: 10px 12px;
  }
  /* Camera tiles fill the available width — single column on phones,
     two columns on tablets via auto-fit. Tiles need to actually show
     faces; the previous 96px-min was unreadable. */
  .dock-tiles {
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    max-height: 55vh;
    overflow-y: auto;
  }
  /* The 4/3 aspect ratio + wide column means tiles can grow huge on
     a tablet. Cap height so the dock doesn't push the action bar
     off-screen. */
  .dock-tile { max-height: 38vh; }

  /* Dock action buttons get more touch-friendly padding. */
  #meeting-dock .dock-actions { gap: 6px; flex-wrap: wrap; }
  #meeting-dock .dock-actions button { padding: 8px 10px; min-height: 38px; }

  /* ---------- Mobile dock placement: rides the top by default ----------
     The drag handler in app.js can flip state.dockEdge between 'top'
     and 'bottom'; we apply matching classes so the floater snaps to
     whichever edge the user dragged it to. */
  #meeting-dock.dock-edge-top {
    top: 8px;
    bottom: auto;
    left: 8px; right: 8px;
    width: auto; max-width: none;
  }
  #meeting-dock.dock-edge-bottom {
    bottom: 8px;
    top: auto;
    left: 8px; right: 8px;
    width: auto; max-width: none;
  }
  /* Title bar gets a grab cursor + small visual affordance so the
     user knows they can drag it. */
  #meeting-dock .dock-title {
    cursor: grab;
    touch-action: pan-y;
    user-select: none;
  }
  #meeting-dock .dock-title::before {
    content: '';
    display: inline-block;
    width: 32px; height: 4px;
    background: var(--fg-dim);
    opacity: 0.4;
    border-radius: 2px;
    margin-right: 6px;
    flex: 0 0 auto;
  }

  /* ---------- Collapsed state ----------
     "Meeting controls" (title bar, action buttons, non-featured peer
     tiles, screen-share viewport) collapse out of the way after 3s.
     The featured camera tile (the "highlight") STAYS visible. A small
     "Meet" pill on the right serves as the touch target to bring the
     controls back. */
  #meeting-dock.dock-collapsed {
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    padding: 0;
  }
  #meeting-dock.dock-collapsed .dock-title,
  #meeting-dock.dock-collapsed .dock-screen,
  #meeting-dock.dock-collapsed .dock-actions {
    display: none;
  }
  /* Hide all tiles in the grid except the featured one. */
  #meeting-dock.dock-collapsed .dock-tile:not(.featured) { display: none; }
  /* The featured tile carries the dock visually while collapsed. */
  #meeting-dock.dock-collapsed .dock-tiles {
    margin-bottom: 0;
    max-height: none;
    overflow: visible;
  }
  /* The Meet nub: floats on top of the featured tile (or where the
     tile would be), bottom-right. Big-enough touch target. */
  .dock-mini-expand { display: none; }   /* default: hidden */
  #meeting-dock.dock-collapsed .dock-mini-expand {
    display: inline-flex;
    align-items: center;
    position: absolute;
    right: 8px; bottom: 8px;
    background: rgba(0, 0, 0, 0.7);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: 999px;
    padding: 6px 14px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    cursor: pointer;
    z-index: 5;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
  }
  #meeting-dock.dock-collapsed .dock-mini-expand:hover {
    background: rgba(0, 0, 0, 0.85);
  }
  /* Make sure the dock stays a positioning context for the
     absolutely-positioned nub. */
  #meeting-dock { position: fixed; }

  /* ---------- Featured tile ----------
     Tap any tile to feature it. The featured tile spans every grid
     column so it dominates the dock; remaining tiles flow below as
     a normal grid row. */
  #meeting-dock .dock-tile.featured {
    grid-column: 1 / -1;
    aspect-ratio: 16 / 9;
    max-height: 60vh;
    border-color: var(--accent);
  }

  /* ---------- Modal-fullscreen toggle ---------- */
  #meeting-dock.dock-fullscreen {
    position: fixed !important;
    inset: 0;
    width: 100vw;
    height: 100dvh;
    max-width: none;
    border-radius: 0;
    padding: 12px;
    z-index: 9000;
    overflow-y: auto;
  }
  #meeting-dock.dock-fullscreen .dock-tiles {
    max-height: none;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  }
  #meeting-dock.dock-fullscreen .dock-tile { max-height: none; }
  /* Even in fullscreen, the featured tile dominates. */
  #meeting-dock.dock-fullscreen .dock-tile.featured {
    grid-column: 1 / -1;
    aspect-ratio: 16 / 9;
    max-height: 70vh;
  }

  /* Login/signup card was a fixed 380px -- on a 360px-wide phone that
     overflows. Switch to fluid width with sensible margins. */
  .auth-card { width: auto; margin: 16px; padding: 24px 20px; }

  /* Touch-friendlier sizing on nav + channel rows. */
  .nav-item, .ch-list li { padding: 10px 12px; }
  .icon-btn, #sidebarToggle { min-width: 38px; min-height: 38px; }

  /* Format toolbar in the composer scrolls horizontally on narrow screens
     instead of wrapping to multiple rows -- preserves vertical room for
     the actual message field. */
  #formatBar {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  #formatBar::-webkit-scrollbar { display: none; }
  #formatBar > * { flex: 0 0 auto; }

  /* Message rows: drop the action toolbar from the grid layout. The
     auto-sized third column was eating most of the width. Actions float
     to the top-right of the message instead, only visible on
     focus/hover/tap (handled by .msg-actions opacity rules). */
  .msg {
    grid-template-columns: 36px 1fr;
    position: relative;
  }
  .msg-actions {
    position: absolute;
    top: 4px;
    right: 6px;
    z-index: 5;
    /* Tap-revealed via .actions-open on mobile (see app.js delegated
       click handler near startReply). The hover rule is suppressed on
       touch devices via @media (hover: hover) above, so this only shows
       when the user explicitly taps a message. */
  }

  /* Reactions/threads/file chips below message content can wrap freely. */
  .reactions, .file-chips { flex-wrap: wrap; }
}

/* ---------- Code panel (channel-bound file browser) ---------- */
.code-panel {
  display: flex;
  flex-direction: column;
  height: calc(100% - 48px);   /* leave room for the rail header */
}
.code-toolbar {
  display: flex;
  gap: 8px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
  align-items: center;
}
.code-toolbar select {
  flex: 1;
  background: var(--bg-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 8px;
}
.code-split {
  display: flex;
  flex: 1;
  min-height: 0;               /* let inner overflow:auto work */
}
.code-tree {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  font-size: 12px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  padding: 4px 0;
}
.code-viewer {
  flex: 1;
  overflow: auto;
  min-width: 0;
  padding: 8px;
}
.code-viewer-head {
  font-size: 12px;
  color: var(--fg-dim);
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  word-break: break-all;
}
.code-viewer-head small { margin-left: 6px; opacity: 0.7; }
.tree-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tree-row:hover { background: var(--bg-hover); }
.tree-row.active { background: var(--bg-3); color: var(--fg-bright); }
.tree-icon { width: 12px; display: inline-block; text-align: center; opacity: 0.7; }
.tree-row.dir .tree-name { font-weight: 500; }
.code-foot {
  padding: 6px 10px;
  border-top: 1px solid var(--border);
  background: var(--bg-2);
  font-style: italic;
}

/* ---------- Code editor (Slice 2) ---------- */
.code-edit-actions {
  float: right;
  display: inline-flex;
  gap: 6px;
  align-items: center;
}
.code-edit-actions button {
  padding: 2px 10px;
  font-size: 11px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.code-edit-actions button.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.code-edit-actions button.ghost   { background: transparent; }
.code-edit-actions button:disabled { opacity: 0.4; cursor: not-allowed; }
.code-edit-actions .small { margin-right: 4px; }

/* CodeMirror takes the rest of the viewer pane. */
.code-viewer .CodeMirror {
  height: calc(100% - 36px);   /* leave room for the head bar */
  font-size: 12px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}

/* ---------- Code annotations (Slice 3) ---------- */
.code-notes {
  max-height: 40%;
  overflow: auto;
  border-bottom: 1px solid var(--border);
  background: var(--bg-2);
}
.code-notes:empty { display: none; }
.code-note {
  padding: 6px 10px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
}
.code-note.resolved { opacity: 0.55; }
.code-note-meta {
  display: flex;
  gap: 8px;
  align-items: center;
  font-size: 11px;
  color: var(--fg-dim);
  margin-bottom: 4px;
}
.code-note-jump {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--accent);
  text-decoration: none;
}
.code-note-actions {
  margin-left: auto;
  display: inline-flex;
  gap: 8px;
}
.code-note-actions a { color: var(--fg-dim); text-decoration: none; font-size: 11px; }
.code-note-actions a:hover { color: var(--fg-bright); text-decoration: underline; }
.code-note-body p { margin: 0; }

/* ---------- Code editor modal (Slice 3 v2) ---------- */
/* Wide variant of the standard modal-card, used by the file editor.
   The editor wants real estate; the default 600px width was useless
   for source code. */
.modal-card.modal-card-wide {
  max-width: 92vw;
  width: 92vw;
  max-height: 90vh;
  height: 90vh;
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.code-modal { display: flex; flex-direction: column; height: 100%; }
.code-modal-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  background: var(--bg-3);
  flex: 0 0 auto;
}
.code-modal-title {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  color: var(--fg-bright);
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Repo segment of "RepoName:filepath" — clickable, opens the
   provider's web view in a new tab. The git URL surfaces in the
   tooltip so users can confirm what they're about to navigate to. */
.code-modal-repo {
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
}
.code-modal-repo:hover { text-decoration: underline; }
.code-modal-sep { color: var(--fg-dim); margin: 0 1px; }
.code-modal-path { color: var(--fg-bright); }
.code-modal-dirty { color: var(--accent); margin-right: 2px; }
.code-modal-actions {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  flex: 0 0 auto;
}
.code-modal-actions button {
  padding: 4px 12px;
  font-size: 11px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.code-modal-actions button.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.code-modal-actions button.ghost   { background: transparent; }
.code-modal-actions button:disabled { opacity: 0.4; cursor: not-allowed; }

/* ---------- Code-editor tab strip ---------- */
/* Horizontal scroller above the editor body. Mouse wheel over the
   strip is hijacked in app.js to scroll left/right when the strip
   overflows. The scrollbar stays visible (not auto-hidden) so users
   can drag-scroll on touch devices too. */
.code-modal-tabs {
  display: flex;
  flex: 0 0 auto;
  overflow-x: auto;
  overflow-y: hidden;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
  scrollbar-width: thin;
}
.code-modal-tabs:empty { display: none; }
.code-modal-tabs::-webkit-scrollbar { height: 6px; }
.code-modal-tabs::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 3px;
}
.code-modal-tabs::-webkit-scrollbar-thumb:hover {
  background: var(--fg-dim);
}
.code-tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 6px 12px;
  font-size: 12px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--fg-dim);
  background: transparent;
  border: 0;
  border-right: 1px solid var(--border);
  border-bottom: 2px solid transparent;
  cursor: pointer;
  white-space: nowrap;
  flex: 0 0 auto;
  max-width: 240px;
  transition: background 0.12s, color 0.12s;
}
.code-tab:hover { background: var(--bg-3); color: var(--fg); }
.code-tab.active {
  color: var(--fg-bright);
  background: var(--bg);
  border-bottom-color: var(--accent);
}
.code-tab-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 200px;
}
.code-tab-close {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  padding: 0;
  font-size: 10px;
  line-height: 1;
  color: var(--fg-dim);
  background: transparent;
  border: 0;
  border-radius: 3px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.code-tab:hover .code-tab-close,
.code-tab.active .code-tab-close { opacity: 1; }
.code-tab-close:hover {
  background: rgba(220, 53, 69, 0.18);
  color: #ff8a95;
}

.code-modal-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg);
}
/* CodeMirror inside the modal stretches to fill */
.code-modal-body .CodeMirror {
  flex: 1 1 auto;
  height: auto !important;
  font-size: 13px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}

/* CodeMirror selection — material-darker's default is too subtle and
   blends into the syntax colors. Override with an unmistakable accent. */
.code-modal-body .CodeMirror-selected,
.code-modal-body .CodeMirror-focused .CodeMirror-selected {
  background: rgba(88, 166, 255, 0.35) !important;   /* GitHub-ish blue */
}
.code-modal-body .CodeMirror-line::selection,
.code-modal-body .CodeMirror-line > span::selection,
.code-modal-body .CodeMirror-line > span > span::selection {
  background: rgba(88, 166, 255, 0.35) !important;
}
.code-modal-body .CodeMirror-line::-moz-selection,
.code-modal-body .CodeMirror-line > span::-moz-selection,
.code-modal-body .CodeMirror-line > span > span::-moz-selection {
  background: rgba(88, 166, 255, 0.35) !important;
}

/* Annotation inline-edit affordance */
.code-note-edit {
  width: 100%;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 6px 8px;
  font: 12px ui-monospace, "SF Mono", Menlo, monospace;
  resize: vertical;
}
.code-note-edit-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 4px;
}
.code-note-edit-actions button {
  padding: 2px 10px;
  font-size: 11px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.code-note-edit-actions button.primary {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.code-note-edit-actions button.ghost { background: transparent; }

/* ---------- Annotation gutter pills + menu modal ---------- */
.CodeMirror-gutter.voksio-annotations {
  width: 18px;
  background: transparent;
}
/* Code-panel selection bubble (3-action floating menu).
   Anchored above the current editor selection; fixed-positioned so
   it tracks viewport coordinates rather than the scroll container. */
.code-sel-bubble {
  position: fixed;
  display: none;
  z-index: 1900;
  background: var(--bg-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
  gap: 2px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  font-size: 12px;
}
.code-sel-bubble button {
  background: transparent;
  color: var(--fg);
  border: 0;
  padding: 5px 10px;
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
}
.code-sel-bubble button:hover { background: var(--bg-3); }

.ann-pill {
  width: 12px;
  height: 12px;
  margin: 2px 3px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: 9px;
  line-height: 12px;
  text-align: center;
  font-weight: 600;
  cursor: pointer;
  display: inline-block;
}
.ann-pill.count { width: auto; min-width: 14px; padding: 0 3px; border-radius: 8px; }
.ann-pill.resolved { background: var(--fg-dim); opacity: 0.6; }
.ann-pill:hover { transform: scale(1.18); }

/* Annotation menu modal */
.ann-menu h3 { margin: 0 0 10px; }
.ann-menu-row {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 8px;
  margin-bottom: 8px;
  background: var(--bg-3);
}
.ann-menu-meta {
  display: flex;
  gap: 8px;
  font-size: 11px;
  color: var(--fg-dim);
  margin-bottom: 6px;
}
.ann-menu-body {
  font-size: 13px;
  color: var(--fg);
  margin-bottom: 8px;
}
.ann-menu-body p { margin: 0; }
.ann-menu-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.ann-menu-actions button {
  padding: 3px 10px;
  font-size: 11px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.ann-menu-actions button.danger { background: #b14040; border-color: #b14040; color: #fff; }
.ann-menu-footer {
  display: flex;
  justify-content: space-between;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.ann-menu-footer button {
  padding: 6px 14px;
  background: var(--bg-2);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
}
.ann-menu-footer button.danger { background: #b14040; border-color: #b14040; color: #fff; }

/* ---------- Inline annotation chip in chat ---------- */
.ann-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 1px 8px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 12px;
  font-size: 12px;
  color: var(--fg);
  cursor: pointer;
  vertical-align: baseline;
  text-decoration: none;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ann-chip:hover { background: var(--bg-2); border-color: var(--accent); }
.ann-chip-loc {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: inherit;
  font-weight: 500;
}
.ann-chip-sep { color: var(--fg-dim); }
.ann-chip-text { color: inherit; }
.ann-chip-missing { opacity: 0.55; cursor: not-allowed; }
.ann-chip-resolved {
  color: var(--fg-dim);
  font-style: italic;
  font-size: 11px;
  margin-left: 4px;
}

/* ---------- Inline code-deeplink chip in chat ----------
   Replaces the rendered #code=… URL with `Code: file.py:L35-L45`.
   Click navigates via location.hash so the existing handler resolves
   the channel + opens the file at the selected range. */
.code-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 1px 8px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 12px;
  font-size: 12px;
  color: var(--fg);
  cursor: pointer;
  vertical-align: baseline;
  text-decoration: none;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.code-chip:hover { background: var(--bg-2); border-color: var(--accent); }
.code-chip-label { color: var(--fg-dim); font-weight: 500; }
.code-chip-loc {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: inherit;
  font-weight: 500;
}
.ann-chip-done .ann-chip-text { text-decoration: line-through; }

/* ---------- Repos panel rewrite ---------- */
.repo-row { padding: 8px 10px; border-bottom: 1px solid var(--border); }
.repo-head { display: flex; align-items: baseline; gap: 6px; }
.repo-url { display: block; opacity: 0.7; word-break: break-all; margin: 2px 0 6px; }
.repo-status { margin-left: auto; font-size: 11px; }
.repo-row .row-actions { display: flex; gap: 4px; flex-wrap: wrap; }
.repo-row .row-actions button {
  padding: 3px 9px;
  font-size: 11px;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.repo-row .row-actions button.warning {
  background: transparent; border-color: #b8893b; color: #d9a85c;
}
.repo-row .row-actions button.danger {
  background: transparent; border-color: #b14040; color: #d97070;
}
.repo-row .row-actions button.warning:hover { background: rgba(217, 168, 92, 0.12); }
.repo-row .row-actions button.danger:hover  { background: rgba(217, 112, 112, 0.12); }
.repo-row .row-actions button.ghost {
  background: transparent;
}
.badge {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 10px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.badge.ok   { background: rgba(76, 175, 80, 0.18); color: #7ed484; }
.badge.warn { background: rgba(217, 168, 92, 0.18); color: #d9a85c; }
.badge.dim  { background: var(--bg-3); color: var(--fg-dim); }

/* ---------- Polish: focus rings + dialog button hover ---------- */
.ui-dialog-input:focus,
.modal-card input:focus,
.modal-card select:focus,
.modal-card textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent);
}
.ui-dialog-actions button { transition: filter .12s ease, transform .04s ease; }
.ui-dialog-actions button:active { transform: translateY(1px); }
.ui-dialog-actions button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ui-dialog-card { animation: ui-dialog-pop .12s ease-out; }
@keyframes ui-dialog-pop {
  from { transform: translateY(8px) scale(0.98); opacity: 0; }
  to   { transform: translateY(0)    scale(1);    opacity: 1; }
}
.ui-dialog-overlay { animation: ui-dialog-fade .12s ease-out; }
@keyframes ui-dialog-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ---------- Sidebar-toggle unread dot ----------
   Hidden by default. Visible only when the sidebar is collapsed AND
   the global unread flag is set (set by updateSidebarUnreadDot in
   app.js). Positioned bottom-right of the toggle button so it sits
   in the lower part of the hamburger glyph. */
#sidebarToggle { position: relative; }
.sidebar-unread-dot {
  position: absolute;
  right: 4px;
  bottom: 4px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ff4d4d;
  display: none;
  pointer-events: none;
  box-shadow: 0 0 0 2px var(--bg);   /* ring so it pops over any icon line */
}
.app.sidebar-collapsed.has-unread .sidebar-unread-dot,
body.sidebar-collapsed.has-unread .sidebar-unread-dot {
  display: block;
  animation: sidebar-unread-pulse 1.4s ease-in-out infinite;
}
@keyframes sidebar-unread-pulse {
  0%   { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(255, 77, 77, 0.7); }
  70%  { transform: scale(1.15); box-shadow: 0 0 0 2px var(--bg), 0 0 0 8px rgba(255, 77, 77, 0); }
  100% { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(255, 77, 77, 0); }
}

/* Channel-title inline affordances (rename DM, make group) */
.channel-title-text { margin-right: 6px; }
.channel-title-act {
  font-size: 11px;
  padding: 2px 8px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--fg-dim);
  border-radius: 12px;
  cursor: pointer;
  vertical-align: middle;
  margin-left: 4px;
}
.channel-title-act:hover { color: var(--fg-bright); border-color: var(--accent); }

/* ---------- Sidebar categories + DnD + mention pulse ---------- */
.cat-row {
  position: relative;
  list-style: none;
  display: flex;
  align-items: center;
  padding: 4px 8px;
  cursor: pointer;
  user-select: none;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
  border-top: 1px solid transparent;
  border-bottom: 1px solid transparent;
}
.cat-row:hover { color: var(--fg-bright); }
.cat-row.dragging { opacity: 0.45; }
.cat-row.dnd-over { border-top-color: var(--accent); }
.cat-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  padding: 0;
  cursor: pointer;
  position: relative;
}
.cat-arrow { width: 10px; display: inline-block; }
.cat-name  { flex: 1; }
.cat-actions { display: flex; gap: 2px; opacity: 0; transition: opacity .12s; }
.cat-row:hover .cat-actions { opacity: 1; }
.cat-actions .ch-mini {
  background: transparent; border: 0; color: var(--fg-dim);
  cursor: pointer; padding: 0 4px; font-size: 12px;
}
.cat-actions .ch-mini.danger:hover { color: #d97070; }

/* Pulsating dot at the bottom-right of a collapsed category header.
   Color matches the channel-row dot system:
     GREEN = unread channel chatter, no mentions
     RED   = at least one channel inside has @-mention or is a DM */
.cat-pulse {
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--bg);
}
.cat-pulse-unread  { background: #4caf50; animation: cat-pulse-green 1.4s ease-in-out infinite; }
.cat-pulse-mention { background: #ff4d4d; animation: cat-pulse-red   1.4s ease-in-out infinite; }
@keyframes cat-pulse-red {
  0%   { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(255, 77, 77, 0.7); }
  70%  { transform: scale(1.18); box-shadow: 0 0 0 2px var(--bg), 0 0 0 7px rgba(255, 77, 77, 0); }
  100% { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(255, 77, 77, 0); }
}
@keyframes cat-pulse-green {
  0%   { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(76, 175, 80, 0.7); }
  70%  { transform: scale(1.18); box-shadow: 0 0 0 2px var(--bg), 0 0 0 7px rgba(76, 175, 80, 0); }
  100% { transform: scale(1);    box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(76, 175, 80, 0); }
}

/* Channel row highlight when there's a direct @mention. The whole
   row's left edge picks up a green accent that pulses subtly. */
.ch-row.ch-mention .ch-btn {
  position: relative;
  box-shadow: inset 3px 0 0 #4caf50;
  animation: ch-mention-pulse 1.6s ease-in-out infinite;
}
@keyframes ch-mention-pulse {
  0%, 100% { box-shadow: inset 3px 0 0 #4caf50, 0 0 0 0   rgba(76, 175, 80, 0.0); }
  50%      { box-shadow: inset 3px 0 0 #4caf50, 0 0 0 0px rgba(76, 175, 80, 0.18); background: rgba(76, 175, 80, 0.06); }
}
.ch-row.dragging { opacity: 0.45; }
.ch-row.dnd-over .ch-btn { outline: 1px dashed var(--accent); outline-offset: -3px; }

/* ---------- Chat-bottom @ mention button ---------- */
.mention-jump {
  position: fixed;
  bottom: 80px;     /* above the composer */
  right: 24px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--bg-2);
  border: 1px solid var(--border);
  color: var(--fg-bright);
  font-weight: 700;
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 18px rgba(0,0,0,0.4);
  z-index: 800;
  transition: transform .12s ease;
}
.mention-jump:hover { transform: translateY(-2px); }
.mention-jump.hidden { display: none; }
.mention-jump .mj-at { line-height: 1; }
.mention-jump .mj-count {
  position: absolute;
  right: -4px;
  bottom: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: #4caf50;
  color: #1a1d21;
  font-size: 11px;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px var(--bg), 0 0 6px rgba(76, 175, 80, 0.55);
  animation: mj-pulse 1.6s ease-in-out infinite;
}
@keyframes mj-pulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--bg), 0 0 0 0   rgba(76, 175, 80, 0.7); }
  70%      { box-shadow: 0 0 0 2px var(--bg), 0 0 0 8px rgba(76, 175, 80, 0); }
}

/* Highlight nav items when a channel is dragged over them — the drop
   resets the channel's category (moves it to Uncategorized). */
.nav-item.dnd-over {
  background: var(--bg-3);
  outline: 1px dashed var(--accent);
  outline-offset: -3px;
}

/* DM-picker empty-state */
.dm-pick-empty {
  padding: 16px 12px;
  text-align: center;
  font-size: 13px;
}

/* Reminder banner — persistent for repeat-until-cleared reminders.
   Stays visible until user clicks Clear; rings on every fire. */
.reminder-banner {
  position: fixed;
  bottom: 80px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg-2);
  border: 1px solid #d9a85c;
  border-left: 4px solid #d9a85c;
  border-radius: 8px;
  padding: 10px 14px;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 640px;
  z-index: 1500;
  box-shadow: 0 10px 30px rgba(0,0,0,0.45);
  animation: reminder-banner-pulse 1.6s ease-in-out infinite;
}
@keyframes reminder-banner-pulse {
  0%, 100% { box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 0 0   rgba(217, 168, 92, 0); }
  50%      { box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 0 6px rgba(217, 168, 92, 0.2); }
}
.reminder-banner .rb-icon { font-size: 22px; }
.reminder-banner .rb-text { flex: 1; min-width: 0; }
.reminder-banner .rb-title { font-weight: 600; }
.reminder-banner .rb-body  { color: var(--fg-dim); font-size: 12px; word-break: break-word; }
.reminder-banner button {
  padding: 6px 12px;
  font-size: 12px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--fg);
  border-radius: 4px;
  cursor: pointer;
}
.reminder-banner .rb-clear {
  background: #d9a85c; color: #1a1d21; border-color: #d9a85c; font-weight: 600;
}

/* ---------- Compact icon nav strip ----------
   Override the per-row .nav-item layout when the parent carries the
   `.nav-icons` class. Buttons become small squares; counts move to
   corner pills; mouseover label is the title= attr. flex-wrap means
   a narrow sidebar gets a 2nd row automatically.
   The pod is also user-resizable (vertical) and supports drag-reorder
   of icons. When the container is wide enough, labels appear next to
   the icons (container query). */
.nav.nav-icons {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 6px 8px 8px;
  /* No border-bottom here — the .nav-resizer below carries the
     visual seam and is what the user grabs to resize. */
  overflow: hidden auto;
  min-height: 56px;
  max-height: 50vh;
  align-content: flex-start;
  /* Establishes a query container so labels can show/hide based on
     pod inline-size, not viewport size. */
  container-type: inline-size;
  position: relative;
}
.nav.nav-icons .nav-item {
  width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  position: relative;
  flex: 0 0 auto;
  font-size: 0;          /* kill stray text nodes / whitespace */
  cursor: pointer;
}
/* Labels sit next to the icon when the container is wide enough.
   Default: hidden so the compact icon-grid mode shows just SVGs. */
.nav.nav-icons .nav-item .nav-label {
  font-size: 13px;            /* explicit — overrides parent font-size:0 */
  font-weight: 500;
  margin-left: 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  display: none;
  flex: 1 1 auto;
  min-width: 0;
  text-align: left;
}
/* When the pod has at least ~200px of width, switch to a label
   layout: each item gets ~half the row, icon on the left, label
   to its right. Below the threshold we keep the compact icon grid. */
@container (min-width: 200px) {
  .nav.nav-icons .nav-item {
    width: auto;
    height: 32px;
    padding: 0 10px;
    flex: 1 1 calc(50% - 4px);
    justify-content: flex-start;
    gap: 6px;
  }
  .nav.nav-icons .nav-item .nav-label { display: inline-block; }
  .nav.nav-icons .nav-item .nav-count {
    /* When labels show, the count moves inline at the end of the
       label area instead of as a corner pill. */
    position: static;
    margin-left: auto;
    box-shadow: none;
  }
}
/* Drag-reorder feedback */
.nav.nav-icons .nav-item.nav-dragging {
  opacity: 0.35;
  cursor: grabbing;
}
.nav.nav-icons .nav-item.nav-drop-before {
  box-shadow: inset 3px 0 0 var(--accent);
}
.nav.nav-icons .nav-item.nav-drop-after {
  box-shadow: inset -3px 0 0 var(--accent);
}

/* ---------- Resize-handle affordances ----------
   Every resizable edge in the app gets a visible grip + a "Drag to
   resize" tooltip so the affordance isn't invisible. Applies to:
     - .resizer.sidebar / .resizer.rail (between panels, 4px tracks)
     - .nav.nav-icons resize:vertical handle (bottom of the nav pod)
   The grip dots use `currentColor` over the existing accent on
   hover so they pick up theme colors automatically. */
.resizer {
  position: relative;
  background: transparent;
  cursor: col-resize;
}
.resizer.rail, .resizer.sidebar { cursor: col-resize; }
.resizer:hover { background: rgba(124, 58, 255, 0.18); }
/* Grip = three vertical dots at the center of the bar. */
.resizer::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 2px;
  height: 32px;
  background-image: radial-gradient(circle, var(--fg-dim) 0.8px, transparent 1px);
  background-size: 2px 6px;
  background-repeat: repeat-y;
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.15s, background-image 0.15s;
}
.resizer:hover::after {
  opacity: 1;
  background-image: radial-gradient(circle, var(--accent) 0.9px, transparent 1px);
}

/* Custom drag handle that lives BELOW the nav-icons pod. JS-driven
   so it actually works (the native CSS `resize` corner-handle was
   the original approach but it's hard to discover and styling it
   has cross-browser issues). The .nav-resizer div is injected by
   initNavIconPod() and persists the dragged height into prefs. */
.nav-resizer {
  height: 8px;
  cursor: ns-resize;
  position: relative;
  background: transparent;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.nav-resizer::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 32px;
  height: 2px;
  background-image: radial-gradient(circle, var(--fg-dim) 0.9px, transparent 1px);
  background-size: 6px 2px;
  background-repeat: repeat-x;
  background-position: center;
  opacity: 0.55;
  pointer-events: none;
  transition: opacity 0.15s, background-image 0.15s, width 0.15s;
}
.nav-resizer:hover::after,
.nav-resizer.is-dragging::after {
  opacity: 1;
  width: 48px;
  background-image: radial-gradient(circle, var(--accent) 1px, transparent 1px);
}
.nav-resizer:hover { background: rgba(124, 58, 255, 0.08); }
.nav-resizer.is-dragging { background: rgba(124, 58, 255, 0.18); }
.nav.nav-icons .nav-item .nav-icon {
  width: 18px; height: 18px;
  margin: 0;
}
.nav.nav-icons .nav-item .nav-count {
  position: absolute;
  top: -2px;
  right: -2px;
  margin: 0;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  font-size: 10px;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 0 2px var(--bg);
}
.nav.nav-icons .nav-item.nav-unread {
  background: var(--bg-3);
}
.nav.nav-icons .nav-item.nav-unread .nav-count {
  background: #d97070;
}
.nav.nav-icons .nav-item.active {
  background: var(--bg-3);
}
.nav.nav-icons .nav-item.active .nav-icon {
  color: var(--accent);
}

/* ---------- Bookmarks panel ---------- */
.bm-section { padding: 6px 0 12px; }
.bm-section-head {
  padding: 4px 12px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
}
/* Three-line bookmark row: head (icon + name + date), optional notes
   accordion, and an action row (Go / Modify / Delete). The accordion
   shows the first line of notes by default; click anywhere on the
   notes block to expand to the full text. */
.bm-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 12px;
  border-left: 2px solid transparent;
}
.bm-row:hover { background: var(--bg-hover); border-left-color: var(--accent); }
.bm-line { display: flex; align-items: center; gap: 8px; }
.bm-line-actions { gap: 6px; margin-top: 2px; }
.bm-icon {
  width: 22px;
  text-align: center;
  color: var(--fg-dim);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 13px;
  flex: 0 0 auto;
}
.bm-label {
  flex: 1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 13px;
  font-weight: 600;
  color: var(--fg-bright);
}
.bm-date {
  flex: 0 0 auto;
  color: var(--fg-dim);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.bm-notes {
  margin-left: 30px;
  background: var(--bg-3);
  border-radius: 4px;
  padding: 5px 8px;
  font-size: 12px;
  color: var(--fg);
  cursor: pointer;
  user-select: text;
}
.bm-notes:hover { background: var(--bg-hover); }
.bm-notes-peek { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bm-notes-full { display: none; white-space: pre-wrap; }
.bm-notes.open .bm-notes-peek { display: none; }
.bm-notes.open .bm-notes-full { display: block; }
.bm-line-actions {
  margin-left: 30px;     /* aligns with .bm-notes left edge */
}
.bm-line-actions button {
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--fg);
  cursor: pointer;
  padding: 3px 10px;
  font-size: 11px;
  border-radius: 4px;
}
.bm-line-actions button:hover { background: var(--bg-hover); }
.bm-line-actions button.danger:hover { color: #ff8a95; border-color: rgba(220,53,69,0.4); }

/* Bookmark create/edit modal (lives inside uiDialog overlay).
   Same chrome as the other stylized dialogs — input + textarea +
   right-aligned Cancel/Save row. */
.bm-edit { min-width: 380px; max-width: 560px; }
.bm-edit-label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
  margin: 8px 0 4px;
}
.bm-edit-name, .bm-edit-notes {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  font: inherit;
  font-size: 14px;
}
.bm-edit-notes { resize: vertical; min-height: 80px; font-family: inherit; }
.bm-edit-name:focus, .bm-edit-notes:focus { outline: none; border-color: var(--accent); }
.bm-edit-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.bm-edit-actions button { padding: 7px 14px; cursor: pointer; }
.bm-edit-actions .primary {
  background: var(--accent); color: #fff; border: 0; border-radius: 6px; font-weight: 600;
}
.bm-edit-actions .primary:hover { filter: brightness(1.1); }

/* Thread messages live in the narrow rail, where the regular .msg
   grid (36px avatar | 1fr body | auto actions) leaves no room for
   the body — the actions strip alone is ~12 buttons wide. Drop the
   actions column inside threads and float the strip in the corner
   on hover. The body gets the full remaining width. */
.thread-node .msg {
  grid-template-columns: 36px 1fr;
  position: relative;
}
.thread-node .msg .msg-actions {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 2;
}

/* ---------- Inline thread expansion ---------- */
.inline-thread {
  margin: 4px 0 12px 60px;        /* indent under the parent's body column */
  padding: 6px 10px 6px 12px;
  border-left: 2px solid var(--accent);
  background: var(--bg-3);
  border-radius: 0 6px 6px 0;
}
/* Inline-thread reply rows keep the default .msg-actions chrome
   (background + border) so the icon strip stays legible against the
   inline-thread's elevated --bg-3 backdrop. The earlier
   `background: transparent` override made the icons disappear. */
.inline-thread .inline-thread-msg { padding: 4px 0 !important; }
.inline-thread-empty,
.inline-thread-loading { padding: 6px 4px; font-size: 12px; }
.inline-thread-composer {
  display: flex;
  gap: 6px;
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
}
.inline-thread-composer textarea {
  flex: 1;
  resize: none;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 8px;
  font: inherit;
  font-size: 13px;
}
.inline-thread-composer button {
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 4px;
  padding: 4px 12px;
  cursor: pointer;
  font-size: 12px;
}

/* ---------- Switches + status pills (settings panel) ---------- */
/* Slider switch built on a hidden checkbox. Use as:
   <label class="switch"><input type="checkbox"><span></span></label>
   The component is bare; pair with `.switch-row` for label + pill. */
.switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
  flex: 0 0 auto;
}
.switch input { opacity: 0; width: 0; height: 0; }
.switch span {
  position: absolute; inset: 0;
  background: var(--bg-3);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.switch span::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 14px; height: 14px;
  background: var(--fg-dim);
  border-radius: 50%;
  transition: transform .15s, background .15s;
}
.switch input:checked + span {
  background: rgba(0, 200, 80, 0.22);
  border-color: rgba(0, 200, 80, 0.55);
}
.switch input:checked + span::after {
  transform: translateX(16px);
  background: #2c8;
}
.switch input:disabled + span { opacity: 0.45; cursor: not-allowed; }

.switch-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}
.switch-row:last-child { border-bottom: 0; }
.switch-row .switch-text { flex: 1; min-width: 0; }
.switch-row .switch-text .switch-title { font-size: 13px; color: var(--fg); }
.switch-row .switch-text .switch-help  { font-size: 11px; color: var(--fg-dim); margin-top: 2px; }
.switch-row.disabled .switch-title,
.switch-row.disabled .switch-help { opacity: 0.55; }

/* Status pill — same scale as admin.html .pill.on / .pill.off so the
   visual language matches the workspace admin surface. */
.set-pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 100px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  flex: 0 0 auto;
}
.set-pill.on  { background: rgba(0, 200, 80, 0.18); color: #2c8; }
.set-pill.off { background: rgba(255, 255, 255, 0.06); color: var(--fg-dim); }

/* Settings section index — sticky top strip of jump-anchors, one per
   rail-section. Mirrors the admin sidebar nav, scaled for a narrow
   right-rail context. */
.set-index {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 8px 10px;
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}
.set-index a {
  font-size: 11px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--bg-3);
  color: var(--fg-dim);
  text-decoration: none;
  border: 1px solid transparent;
  white-space: nowrap;
}
.set-index a:hover { background: var(--bg-hover); color: var(--fg-bright); border-color: var(--border); }
.rail-section[id^="setsec-"] { scroll-margin-top: 56px; }
.rail-section[id^="setsec-"] h4 { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.rail-section[id^="setsec-"] h4 small { font-weight: 400; font-size: 11px; color: var(--fg-dim); text-transform: none; letter-spacing: 0; }

/* Inline-thread composer: reply-target chip + composer row layout */
.inline-thread-composer { display: flex; flex-direction: column; gap: 4px; }
.inline-thread-composer-row { display: flex; gap: 6px; align-items: flex-end; }
.inline-thread-composer-row textarea {
  flex: 1;
  resize: none;
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 4px 8px;
  font: inherit;
  font-size: 13px;
}
.inline-thread-composer-row button {
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 4px;
  padding: 4px 12px;
  cursor: pointer;
  font-size: 12px;
}
.inline-thread-reply-target {
  display: flex;
  gap: 8px;
  align-items: baseline;
  padding: 4px 8px;
  background: var(--bg-2);
  border-left: 2px solid var(--accent);
  border-radius: 0 4px 4px 0;
  font-size: 11px;
  color: var(--fg);
}
.inline-thread-reply-target.hidden { display: none; }
.inline-thread-reply-target small {
  flex: 1;
  color: var(--fg-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.inline-thread-clear-target {
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  cursor: pointer;
  font-size: 14px;
  padding: 0 4px;
  line-height: 1;
}
.inline-thread-clear-target:hover { color: #d97070; }

/* Hide scrollbars on every chat composer textarea (main, rail thread,
   inline thread, message-edit). The textarea still scrolls — only the
   visible track/thumb is suppressed, so a long draft scrolls cleanly
   without the chrome that competes with the editor border. */
#composer textarea,
.msg-edit-area,
.thread-composer textarea,
.inline-thread-composer textarea,
.inline-thread-composer-row textarea {
  scrollbar-width: none;            /* Firefox */
  -ms-overflow-style: none;         /* legacy Edge */
}
#composer textarea::-webkit-scrollbar,
.msg-edit-area::-webkit-scrollbar,
.thread-composer textarea::-webkit-scrollbar,
.inline-thread-composer textarea::-webkit-scrollbar,
.inline-thread-composer-row textarea::-webkit-scrollbar,
#composer textarea::-webkit-scrollbar-button,
.msg-edit-area::-webkit-scrollbar-button,
.thread-composer textarea::-webkit-scrollbar-button,
.inline-thread-composer textarea::-webkit-scrollbar-button,
.inline-thread-composer-row textarea::-webkit-scrollbar-button {
  width: 0;
  height: 0;
  display: none;                    /* Chrome / Safari + the up/down arrow buttons */
}

/* ---------- Custom audio player (iMessage-style pill) ----------
   Solid accent pill, white play icon ringed in white, white waveform.
   Filename hidden — voice clips don't need their auto-generated name
   showing. Time readout sits at the right edge inside the pill. */
.audio-player {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 6px 14px 6px 6px;
  background: var(--accent);
  border: 0;
  border-radius: 999px;
  color: #fff;
  /* Cap to ~1/3 of the message row (≈ chat panel width) on desktop,
     half on mobile (see media query below). */
  max-width: 33.333%;
  min-width: 0;
}
@media (max-width: 720px) {
  .audio-player { max-width: 50%; }
}
.audio-play {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent;
  color: #fff;
  border: 1.5px solid rgba(255, 255, 255, 0.85);
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
  transition: background .12s ease, transform .04s ease;
}
.audio-play:hover  { background: rgba(255, 255, 255, 0.18); }
.audio-play:active { transform: scale(0.94); }
.audio-play svg { width: 14px; height: 14px; display: block; }
/* Optical correction — a triangle's visual center sits left of its
   bounding-box center, so nudge it a hair right to look balanced
   inside the round button. */
.audio-play .audio-icon-play { transform: translateX(1px); }

.audio-waveform {
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
  height: 26px;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.65);
}
.audio-waveform svg {
  width: 100%; height: 100%;
  display: block;
  transition: color .12s;
}
.audio-player.playing .audio-waveform svg,
.audio-waveform:hover svg { color: #fff; }
.audio-progress {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 0%;
  background: rgba(255, 255, 255, 0.18);
  pointer-events: none;
  transition: width .1s linear;
  border-radius: 999px;
}
.audio-meta {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  color: rgba(255, 255, 255, 0.85);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
}
.audio-meta .audio-time { color: #fff; }
/* Filename / size aren't part of the iMessage-style bubble — hide
   them. The size span is rendered as `.dim` inside .audio-meta. */
.audio-meta .dim { display: none; }
