/* chat405 — SPA shell stylesheet.
   Dark theme is the default, defined on :root. Light theme overrides live
   under :root[data-theme="light"] and are toggled by the inline script in
   index.html. No frameworks, no external requests — every rule below is
   self-contained. */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  overflow: hidden; /* the app manages its own internal scroll regions */
}
h1, p, ul, li, form { margin: 0; padding: 0; list-style: none; }
button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
}
input, textarea {
  font: inherit;
  color: inherit;
}
img, svg { display: block; }
a { color: inherit; }

/* ---------- theme: dark (default) ---------- */
:root {
  color-scheme: dark;

  --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
    sans-serif, "Apple Color Emoji", "Segoe UI Emoji";

  --bg-app: #0e1621;
  --bg-sidebar: #17212b;
  --bg-thread: #0e1621;
  --bg-panel: #17212b;
  --bg-elevated: #1f2b38;
  --bg-hover: #1c2733;
  --bg-active: #2b5278;
  --bg-input: #242f3d;

  --border-subtle: #1e2933;
  --border-strong: #2b3947;

  --text-primary: #e9edef;
  --text-secondary: #8296a8;
  --text-tertiary: #5d707f;
  --text-on-accent: #ffffff;

  --accent: #4ea4f5;
  --accent-strong: #2b96e8;

  --bubble-own-bg: #2b5278;
  --bubble-own-text: #eaf3ff;
  --bubble-other-bg: #1f2b38;
  --bubble-other-text: #e5e9ec;
  --bubble-quote-bg: rgba(255, 255, 255, 0.08);

  --unread-badge-bg: #4ea4f5;
  --unread-badge-text: #0e1621;
  --unread-badge-muted-bg: #35424e;
  --unread-badge-muted-text: #8296a8;

  --tick-sent-color: #8296a8;
  --tick-read-color: #6ecbff;

  --danger: #f2555a;
  --divider-unread-color: #4ea4f5;
  --presence-online: #43b581;

  --scrollbar-thumb: #2b3947;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;
  --radius-bubble: 14px;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;

  --shadow-panel: 0 12px 32px rgba(0, 0, 0, 0.45);
  --sidebar-width: 340px;
}

/* ---------- theme: light ---------- */
:root[data-theme="light"] {
  color-scheme: light;

  --bg-app: #eef2f5;
  --bg-sidebar: #ffffff;
  --bg-thread: #e9eef2;
  --bg-panel: #ffffff;
  --bg-elevated: #f3f5f7;
  --bg-hover: #f2f4f6;
  --bg-active: #e3f1ff;
  --bg-input: #eef1f4;

  --border-subtle: #e4e8ec;
  --border-strong: #d5dbe1;

  --text-primary: #17212b;
  --text-secondary: #6d7883;
  --text-tertiary: #98a1a9;
  --text-on-accent: #ffffff;

  --accent: #3390ec;
  --accent-strong: #2077cf;

  --bubble-own-bg: #dceeff;
  --bubble-own-text: #113355;
  --bubble-other-bg: #ffffff;
  --bubble-other-text: #1a232c;
  --bubble-quote-bg: rgba(17, 51, 85, 0.06);

  --unread-badge-bg: #3390ec;
  --unread-badge-text: #ffffff;
  --unread-badge-muted-bg: #d7dce1;
  --unread-badge-muted-text: #6d7883;

  --tick-sent-color: #9aa4ac;
  --tick-read-color: #2f8fe8;

  --danger: #e0393e;
  --divider-unread-color: #2077cf;
  --presence-online: #2fa860;

  --scrollbar-thumb: #d1d7dc;

  --shadow-panel: 0 12px 32px rgba(20, 30, 40, 0.16);
}

/* ---------- base ---------- */
body {
  background: var(--bg-app);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

.hidden { display: none !important; }

.icon {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}
.icon-lg { width: 34px; height: 34px; }
.icon-brand { width: 24px; height: 24px; color: var(--accent); }
.icon-brand-lg { width: 40px; height: 40px; color: var(--accent); }

/* scrollbars (webkit + firefox) */
* { scrollbar-width: thin; scrollbar-color: var(--scrollbar-thumb) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--scrollbar-thumb);
  border-radius: 8px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

.icon-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  flex: 0 0 auto;
}
.icon-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.icon-btn:focus-visible, .theme-toggle:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.avatar {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 15px;
  letter-spacing: 0.02em;
  color: #ffffff;
  background: linear-gradient(160deg, hsl(var(--avatar-hue, 210) 62% 52%), hsl(var(--avatar-hue, 210) 62% 38%));
}
.avatar--channel { color: #ffffff; }
.avatar--channel .icon { color: inherit; width: 22px; height: 22px; }
.avatar--sm { width: 32px; height: 32px; font-size: 12.5px; }
.avatar--lg { width: 72px; height: 72px; font-size: 24px; }

/* ---------- presence dot (Plan 3, Task 6: chat-list.js) ---------- */
/* A wrapper around .avatar rather than a child of it: chat-list.js's
   updateRowContent clears+rebuilds .avatar's own children (initials/channel
   icon) on every render, which would silently wipe out a dot appended
   directly inside it — see that module's own comment on this element. */
.avatar-frame { position: relative; flex: 0 0 auto; }
.avatar-presence-dot {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--text-tertiary);
  border: 2px solid var(--bg-sidebar);
}
.avatar-presence-dot--online { background: var(--presence-online); }
.avatar-presence-dot[hidden] { display: none; } /* same [hidden] gotcha as .chat-row[hidden] above */

/* ---------- app shell layout ---------- */
/* Same reasoning as .login-view[hidden] below: author rules always beat the
   UA default [hidden]{display:none}, so main.js toggling the `hidden`
   attribute on #app would otherwise do nothing against this display:flex —
   make the override explicit (needed starting Task 3, when main.js begins
   switching between #login-view and #app). */
.app[hidden] { display: none; }
.app {
  position: relative; /* anchors .offline-banner (Task 7) and .info-panel's .main-area ancestor */
  display: flex;
  height: 100vh;
  width: 100%;
}

/* ---------- offline banner (Task 7: ws.js reconnect state, shown via
   main.js's setConnectionStatus) ---------- */
/* No slot for this in the frozen Task 1 mockup — built at runtime like
   .jump-to-bottom/.composer-bar, overlaid so it never reflows the two
   columns underneath it. */
.offline-banner {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  text-align: center;
  padding: 6px var(--space-3);
  font-size: 12.5px;
  font-weight: 600;
  color: #fff;
  background: var(--danger);
}
.offline-banner[hidden] { display: none; } /* same [hidden] gotcha as .chat-row[hidden] above */

.sidebar {
  position: relative;              /* anchors .main-menu */
  flex: 0 0 var(--sidebar-width);
  min-width: 260px;
  display: flex;
  flex-direction: column;
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border-subtle);
  min-height: 0;
}

.main-area {
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
  display: flex;
}

.thread-pane {
  position: relative; /* anchors .jump-to-bottom (Task 5) */
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-thread);
  background-image: repeating-linear-gradient(
    135deg,
    rgba(255, 255, 255, 0.012) 0px,
    rgba(255, 255, 255, 0.012) 1px,
    transparent 1px,
    transparent 26px
  );
}
/* Task 2: drag-and-drop a file/photo onto the thread — toggled by
   main.js's initFileDrop. An inset outline (not a border, which would shift
   layout by its own width) plus a faint accent wash. */
.thread-pane--drag-over {
  outline: 2px dashed var(--accent);
  outline-offset: -6px;
  background-color: rgba(78, 164, 245, 0.06);
}

/* ---------- sidebar header + search ---------- */
.sidebar-top {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-2);
}
.menu-btn { position: relative; }
/* Unread-elsewhere hint, same idiom as Telegram's dot on the menu button. */
.menu-dot {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-sidebar, var(--bg-secondary));
}

.main-menu {
  position: absolute;
  top: 56px;
  left: var(--space-2);
  z-index: 40;
  min-width: 232px;
  padding: var(--space-2) 0;
  background: var(--bg-elevated, var(--bg-secondary));
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
}
.main-menu[hidden] { display: none; }
.main-menu-head {
  padding: var(--space-2) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--border-subtle);
  margin-bottom: var(--space-2);
}
.main-menu-user {
  margin-top: 2px;
  font-size: 12px;
  color: var(--text-tertiary);
}
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: 9px var(--space-4);
  background: none;
  border: none;
  text-align: left;
  font-size: 14px;
  color: var(--text-primary);
  cursor: pointer;
}
.menu-item:hover { background: var(--bg-hover); }
.menu-item .icon { color: var(--text-secondary); flex: 0 0 auto; }
.menu-item-danger, .menu-item-danger .icon { color: var(--danger, #e5534b); }
.menu-sep {
  height: 1px;
  margin: var(--space-2) 0;
  background: var(--border-subtle);
}
#theme-toggle .icon-moon,
#theme-toggle .theme-label-light { display: none; }
:root[data-theme="light"] #theme-toggle .icon-sun,
:root[data-theme="light"] #theme-toggle .theme-label-dark { display: none; }
:root[data-theme="light"] #theme-toggle .icon-moon { display: block; }
:root[data-theme="light"] #theme-toggle .theme-label-light { display: inline; }

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
}

.search-box {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-3);
  height: 38px;
  background: var(--bg-input);
  border-radius: 999px;
  color: var(--text-tertiary);
}
.search-box input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  background: none;
  border: none;
  color: var(--text-primary);
}
.search-box input::placeholder { color: var(--text-tertiary); }

/* ---------- chat list ---------- */
.chat-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--space-2);
}

/* ---------- search results (Task 5) ---------------------------------------
   Runtime-inserted by ui/search.js as a sibling of .chat-list, right after
   it in the DOM (see that module's own comment) — same flex/scroll shape as
   .chat-list itself so swapping which one is visible (search.js toggles
   [hidden] on whichever isn't current) never shifts the sidebar's layout.
   Rows deliberately reuse .chat-row/.chat-row-main/.chat-title/.chat-time/
   .chat-preview/.preview-sender wholesale — a search result and a chat-list
   row are visually the same kind of thing (avatar, title, time, one-line
   preview), so the only genuinely new classes below are the empty-state
   message and the match highlight. */
.search-results {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--space-2);
}
.search-results[hidden] { display: none; } /* same [hidden] gotcha as .chat-row[hidden] elsewhere in this file */
.search-result-row { cursor: pointer; }
.search-result-row:hover { background: var(--bg-hover); }
.search-result-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.search-result-snippet { color: var(--text-secondary); }
.search-result-highlight {
  background: rgba(78, 164, 245, 0.16); /* same accent-tinted highlight as .mention/.reaction-chip--mine */
  color: var(--accent);
  border-radius: 3px;
  padding: 0 2px;
}
.search-results-empty {
  padding: var(--space-5) var(--space-4);
  text-align: center;
  color: var(--text-tertiary);
  font-size: 13.5px;
  cursor: default;
}

.chat-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
  border-radius: var(--radius-sm);
  margin: 0 var(--space-2);
  width: calc(100% - 2 * var(--space-2));
}
/* Task 4 (chat-list.js) toggles `.hidden` on a row for the client-side
   filter box. Same gotcha as .app[hidden]/.login-view[hidden] below: an
   author rule that sets `display` always beats the UA default
   [hidden]{display:none}, so without this the row would stay visible. */
.chat-row[hidden] { display: none; }
.chat-row:hover { background: var(--bg-hover); }
.chat-row--active, .chat-row--active:hover { background: var(--bg-active); }
.chat-row--active .chat-time,
.chat-row--active .chat-preview,
.chat-row--active .preview-sender { color: var(--text-secondary); }

.chat-row-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.chat-row-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}
.chat-title {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
  font-size: 14.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.icon-pin {
  width: 13px;
  height: 13px;
  color: var(--text-tertiary);
  flex: 0 0 auto;
}
.icon-mute {
  width: 14px;
  height: 14px;
  color: var(--text-tertiary);
  flex: 0 0 auto;
}
/* Task 4: shown only when the chat is pinned/muted. chat-list.js toggles the
   `icon--hidden` CLASS (not the `hidden` attribute) on these, because they are
   SVG built by icon() and SVGElement never reflects the `hidden` IDL property
   to the `hidden` content attribute — an `[hidden]` selector could never match
   them (see memory: el-helper-cannot-create-svg.md). `.icon-pin.icon--hidden`
   (0,2,0) also outranks the `svg { display: block }` reset without !important. */
.icon-pin.icon--hidden, .icon-mute.icon--hidden { display: none; }
.chat-time {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--text-tertiary);
}
.chat-row-bottom {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  min-width: 0;
}
.chat-preview {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: 13.5px;
  color: var(--text-secondary);
}
.preview-sender { color: var(--text-tertiary); }
.bot-badge {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--text-on-accent);
  background: var(--accent);
  border-radius: 4px;
  padding: 1px 5px;
  vertical-align: middle;
}

.unread-badge {
  flex: 0 0 auto;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--unread-badge-bg);
  color: var(--unread-badge-text);
  font-size: 11.5px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.unread-badge--muted {
  background: var(--unread-badge-muted-bg);
  color: var(--unread-badge-muted-text);
}
/* Toggled with `.hidden` (chat-list row badge, jump-to-bottom badge) — needs
   the same explicit override as .chat-row[hidden] above. */
.unread-badge[hidden] { display: none; }

/* ---------- mention badge (Plan 3, Task 7: chat-list.js) ---------- */
/* Deliberately a different shape/color from .unread-badge — a distinct
   "you were mentioned" signal, not just another unread count, and shown
   even when the chat is muted (chat-list.js never applies
   .unread-badge--muted's dimming to this one). */
.mention-badge {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--danger);
  color: #ffffff;
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mention-badge[hidden] { display: none; }

/* ---------- thread header ---------- */
.thread-header {
  flex: 0 0 auto;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 0 var(--space-4);
  background: var(--bg-sidebar);
  border-bottom: 1px solid var(--border-subtle);
}
.thread-header-identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  flex: 1 1 auto;
  padding: 6px;
  margin: 0 calc(var(--space-2) * -1);
  border-radius: var(--radius-md);
}
.thread-header-identity:hover { background: var(--bg-hover); }
.thread-header-identity .avatar { width: 40px; height: 40px; font-size: 13.5px; }
.thread-header-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  text-align: left;
  gap: 1px;
}
.thread-title {
  font-weight: 600;
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.thread-status {
  font-size: 12.5px;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.thread-status--typing {
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 6px;
}
.typing-dots {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.typing-dots span {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: currentColor;
  animation: typing-bounce 1.2s infinite ease-in-out;
}
.typing-dots span:nth-child(2) { animation-delay: 0.15s; }
.typing-dots span:nth-child(3) { animation-delay: 0.3s; }
@keyframes typing-bounce {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
  30% { transform: translateY(-3px); opacity: 1; }
}
.thread-header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex: 0 0 auto;
}

/* ---------- messages ---------- */
.thread-messages {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.message-list {
  padding: var(--space-4) 0 var(--space-2);
  display: flex;
  flex-direction: column;
}

.day-separator {
  display: flex;
  justify-content: center;
  margin: var(--space-2) 0 var(--space-4);
}
.day-separator span {
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-size: 12.5px;
  font-weight: 600;
  padding: 5px 14px;
  border-radius: 12px;
}

.unread-divider {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-2) var(--space-5) var(--space-3);
  color: var(--divider-unread-color);
}
.unread-divider::before,
.unread-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--divider-unread-color);
  opacity: 0.35;
}
.unread-divider span {
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.message {
  display: flex;
  gap: var(--space-2);
  padding: 2px var(--space-5);
  margin-top: 6px;
}
.message--grouped { margin-top: 2px; }
.message--own { justify-content: flex-end; }

.message-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex: 0 0 auto;
  align-self: flex-end;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11.5px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(160deg, hsl(var(--avatar-hue, 210) 62% 52%), hsl(var(--avatar-hue, 210) 62% 38%));
}
.message-avatar-spacer { width: 30px; flex: 0 0 auto; }

.message-body {
  position: relative; /* anchors .message-actions (Task 5 hover reply button) */
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 66%;
}
.message--own .message-body { align-items: flex-end; }
.message--other .message-body { align-items: flex-start; }

.message-sender {
  font-size: 12.5px;
  font-weight: 700;
  color: hsl(var(--avatar-hue, 210) 70% 62%);
  margin: 0 var(--space-1) 3px;
}

.bubble {
  min-width: 0;
  max-width: 100%;
  background: var(--bubble-other-bg);
  color: var(--bubble-other-text);
  border-radius: var(--radius-bubble);
  padding: 7px 10px 6px;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.08);
}
.message--own .bubble {
  background: var(--bubble-own-bg);
  color: var(--bubble-own-text);
}

.message-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  font-size: 14.5px;
  line-height: 1.42;
}

/* ---------- @mentions (Plan 3, Task 7) ---------- */
/* Rendered as a plain <span> around an ordinary text node (thread.js's
   renderMessageText) — styling only, never a different element type or
   markup; the underlying content is exactly the "@username" substring the
   sender typed. */
.mention-chip {
  font-weight: 600;
  color: var(--accent);
  background: rgba(78, 164, 245, 0.14);
  border-radius: 4px;
  padding: 0 2px;
}
.message--own .mention-chip {
  color: var(--bubble-own-text);
  background: rgba(255, 255, 255, 0.18);
}

.message-meta {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 5px;
  margin-top: 2px;
}
.message-time {
  font-size: 11px;
  color: var(--text-tertiary);
  white-space: nowrap;
}
.message--own .message-time { color: rgba(234, 243, 255, 0.65); }
:root[data-theme="light"] .message--own .message-time { color: rgba(17, 51, 85, 0.55); }
.message-edited {
  font-size: 11px;
  font-style: italic;
  color: var(--text-tertiary);
}
.message--own .message-edited { color: rgba(234, 243, 255, 0.6); }
:root[data-theme="light"] .message--own .message-edited { color: rgba(17, 51, 85, 0.5); }

.ticks { display: inline-flex; align-items: center; }
.ticks .icon { width: 15px; height: 10px; }
.ticks--sent { color: var(--tick-sent-color); }
.ticks--read { color: var(--tick-read-color); }
.message--own .ticks--sent { color: rgba(234, 243, 255, 0.65); }

/* ---------- optimistic send states (Task 5: thread.js renders the
   .message--pending/.message--failed classes a message carries once
   composer.js's optimistic send lands in Task 6) ---------- */
/* New states beyond the frozen Task 1 mockup — "sending"/"failed" bubbles
   didn't exist as a mockup concept, but the plan explicitly calls out that
   these need *some* visual treatment. Built from existing tokens only. */
.message--pending .bubble { opacity: 0.55; }
.message--failed .bubble { box-shadow: 0 0 0 1px var(--danger); }
.message-retry {
  font-size: 11px;
  font-weight: 700;
  color: var(--danger);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}
.message-retry:hover { text-decoration: underline; }

/* Brief highlight when a reply quote's "scroll to the quoted message"
   click lands (thread.js scrollToMessage). */
.bubble--flash { box-shadow: 0 0 0 2px var(--accent); transition: box-shadow 0.25s ease; }

/* ---------- per-message hover actions (Task 5: thread.js reply button;
   the click handler itself is wired to composer.js in Task 6) ---------- */
.message-actions {
  position: absolute;
  top: 0;
  display: flex;
  gap: 4px; /* Task 4 added React/Pin alongside the original Reply button */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.message--own .message-actions { right: 100%; margin-right: 6px; }
.message--other .message-actions { left: 100%; margin-left: 6px; }
.message:hover .message-actions, .message-actions:focus-within { opacity: 1; pointer-events: auto; }
.msg-action-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elevated);
  color: var(--text-secondary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
  flex: 0 0 auto;
}
.msg-action-btn .icon { width: 15px; height: 15px; }
.msg-action-btn:hover { background: var(--bg-hover); color: var(--text-primary); }

.mention {
  color: var(--accent);
  background: rgba(78, 164, 245, 0.14);
  border-radius: 4px;
  padding: 0 4px;
  font-weight: 600;
}

/* reply quote */
.reply-quote {
  display: flex;
  gap: var(--space-2);
  padding: 4px var(--space-2);
  margin-bottom: 5px;
  background: var(--bubble-quote-bg);
  border-radius: 8px;
  cursor: pointer;
  max-width: 100%;
}
/* Task 5: a reply quote is only clickable (scrolls to the quoted message)
   when that message is actually loaded — see thread.js renderReplyQuote.
   Without this override every quote would still show a pointer cursor even
   when clicking it does nothing. */
.reply-quote:not(.reply-quote--clickable) { cursor: default; }
.reply-quote-bar {
  flex: 0 0 auto;
  width: 3px;
  border-radius: 2px;
  background: var(--accent);
}
.reply-quote-content { min-width: 0; }
.reply-quote-author {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--accent);
}
.message--own .reply-quote-author { color: var(--tick-read-color); }
.reply-quote-text {
  font-size: 12.5px;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.message--own .reply-quote-text { color: rgba(234, 243, 255, 0.75); }
/* Own bubbles are light in the light theme, so the dark-theme quote colour
   above drops to ~1.5:1 contrast there. */
:root[data-theme="light"] .message--own .reply-quote-text { color: var(--text-secondary); }
:root[data-theme="light"] .message--own .reply-quote-author { color: var(--accent); }

/* file row */
.bubble--file { padding: 8px 10px; }
.file-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 220px;
}
.file-icon {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-sm);
  background: rgba(78, 164, 245, 0.16);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
}
.file-info { min-width: 0; }
.file-name {
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.file-size {
  font-size: 12px;
  color: var(--text-tertiary);
  margin-top: 1px;
}

/* media placeholder */
.bubble--media { padding: 6px; }
.media-placeholder {
  position: relative; /* anchors .upload-progress overlay (Task 2) */
  overflow: hidden;    /* clips .media-image to the rounded corners */
  width: 260px;
  max-width: 100%;
  aspect-ratio: 4 / 3; /* fallback only — Task 2's thread.js sets the real
                          ratio inline from the photo's actual dimensions the
                          instant the bubble is built, before the <img> even
                          starts loading, so the box never resizes once the
                          bytes arrive (no layout jump). */
  border-radius: calc(var(--radius-bubble) - 6px);
  background: var(--bg-elevated);
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 6px;
}
.bubble--media .message-text { padding: 0 var(--space-1); }
.bubble--media .message-meta { padding-right: var(--space-1); }

/* Task 2: the actual photo, once thread.js has a thumbnail/local preview URL
   to point it at (media-placeholder above is the fallback shown otherwise —
   still loading with no known dimensions, or a real decode failure). */
.media-image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  cursor: default;
}
.media-image--clickable { cursor: zoom-in; }

/* Task 2: upload progress for a pending file/image send. Plain block flow
   under a file bubble's row; absolutely-positioned overlay near the bottom
   of an image bubble's frame (which is already position:relative above). */
.upload-progress {
  height: 3px;
  border-radius: 2px;
  overflow: hidden;
  background: rgba(127, 127, 127, 0.3);
  margin-top: 6px;
}
.upload-progress-bar {
  height: 100%;
  background: var(--accent);
  transition: width 0.15s ease;
}
.media-placeholder .upload-progress {
  position: absolute;
  left: var(--space-2);
  right: var(--space-2);
  bottom: var(--space-2);
  margin-top: 0;
}

/* Task 2: download action on a resolved file bubble. */
.file-download {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}
.file-download:hover { background: var(--bg-hover); color: var(--text-primary); }
.file-download .icon { width: 16px; height: 16px; }
.message--own .file-download { color: rgba(234, 243, 255, 0.75); }
.message--own .file-download:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }

/* ---------- jump-to-bottom (Task 5: thread.js) ---------- */
/* No slot for this in the frozen Task 1 mockup — built at runtime, anchored
   to .thread-pane (position:relative, added above). */
.jump-to-bottom {
  position: absolute;
  right: var(--space-5);
  bottom: 84px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--bg-elevated);
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-panel);
  z-index: 5;
}
.jump-to-bottom[hidden] { display: none; } /* same [hidden] gotcha as .chat-row[hidden] above */
.jump-to-bottom:hover { background: var(--bg-hover); color: var(--text-primary); }
.jump-to-bottom .unread-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 18px;
  height: 18px;
  font-size: 10.5px;
}

/* ---------- composer ---------- */
/* Reply/edit bar above the composer (Task 6: composer.js) — no slot for this
   in the frozen Task 1 mockup either; inserted at runtime the same way
   login.js inserts #login-error (a new element, not a restructuring of any
   approved markup). */
.composer-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--bg-sidebar);
  border-top: 1px solid var(--border-subtle);
}
.composer-bar[hidden] { display: none; }
.composer-bar-rail {
  flex: 0 0 auto;
  width: 3px;
  align-self: stretch;
  border-radius: 2px;
  background: var(--accent);
}
.composer-bar--edit .composer-bar-rail { background: var(--tick-read-color); }
.composer-bar-content { flex: 1 1 auto; min-width: 0; }
.composer-bar-label { font-size: 12.5px; font-weight: 700; color: var(--accent); }
.composer-bar--edit .composer-bar-label { color: var(--tick-read-color); }
.composer-bar-snippet {
  font-size: 12.5px;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.composer-error {
  padding: var(--space-1) var(--space-4);
  background: var(--bg-sidebar);
  color: var(--danger);
  font-size: 12.5px;
  font-weight: 600;
}

.composer {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-sidebar);
  border-top: 1px solid var(--border-subtle);
}
.composer-input-wrap {
  flex: 1 1 auto;
  min-width: 0;
}
.composer-input {
  width: 100%;
  display: block;
  resize: none;
  max-height: 160px;
  background: var(--bg-input);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: 9px 12px;
  color: var(--text-primary);
  line-height: 1.4;
  font-size: 14.5px;
}
.composer-input::placeholder { color: var(--text-tertiary); }
.composer-input:focus { outline: none; border-color: var(--accent); }
.composer-input:disabled { opacity: 0.6; cursor: not-allowed; }

.send-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #fff;
}
.send-btn:hover { background: var(--accent-strong); }
.send-btn:disabled { background: var(--bg-elevated); color: var(--text-tertiary); cursor: not-allowed; }
.send-btn .icon { width: 18px; height: 18px; }

/* ---------- info panel (hidden until Task 8 wires it up) ---------- */
.info-panel {
  position: absolute;
  inset: 0 0 0 auto;
  width: 320px;
  max-width: 85%;
  background: var(--bg-panel);
  border-left: 1px solid var(--border-subtle);
  box-shadow: var(--shadow-panel);
  z-index: 20;
  display: flex;
  flex-direction: column;
}
.info-panel[hidden] { display: none; }

.info-panel-header {
  flex: 0 0 auto;
  height: 64px;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  font-weight: 600;
  font-size: 15px;
}
.info-panel-body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--space-5) var(--space-4);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
}
.info-panel-avatar { margin-bottom: var(--space-1); }
.info-panel-title { font-weight: 700; font-size: 17px; text-align: center; }
.info-panel-sub { color: var(--text-secondary); font-size: 13px; margin-bottom: var(--space-4); }
.info-panel-section { width: 100%; }
.info-panel-section-title {
  font-size: 12.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
  margin: var(--space-3) 0 var(--space-2);
  align-self: flex-start;
}
.member-list { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; }
.member-list li {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) 0;
}
.member-name { flex: 1 1 auto; min-width: 0; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-role {
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--text-tertiary);
  background: var(--bg-elevated);
  padding: 2px 7px;
  border-radius: 6px;
}

/* Plan 4 gap-closer: owner-only promote/demote button on a member row
   (info-panel.js's rosterFor/render) — deliberately plain/quiet rather than
   .info-panel-pref-btn's pill-with-icon look: this sits inside a dense list
   row, not the panel's own toolbar, so it stays a small text affordance that
   doesn't compete with the avatar/name/role already there. `.member-name`'s
   own flex:1+ellipsis above is what keeps a long display name from pushing
   this off the 320px sidebar at the 900px single-column breakpoint. */
.member-role-btn {
  flex: 0 0 auto;
  height: 24px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-secondary);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.member-role-btn:hover { background: var(--bg-hover); color: var(--text-primary); border-color: var(--accent); }

/* Refusal banner for a promote/demote attempt (e.g. the zero-owners guard) —
   same flat "error strip" language as .composer-error, just centered to
   match this panel's own centered body layout. */
.info-panel-error {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  color: var(--danger);
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
}

.btn {
  height: 40px;
  padding: 0 var(--space-5);
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: 14px;
  width: 100%;
}
.btn--primary { background: var(--accent); color: #fff; }
.btn--primary:hover { background: var(--accent-strong); }
.btn--danger {
  margin-top: var(--space-5);
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
}
.btn--danger:hover { background: rgba(242, 85, 90, 0.1); }

/* ---------- Task 4: mute/pin — info-panel toggle buttons + chat-list's
   right-click row context menu (both call the same PATCH .../prefs) ---------- */
.info-panel-prefs {
  display: flex;
  gap: var(--space-2);
  width: 100%;
  margin-bottom: var(--space-4);
}
.info-panel-pref-btn {
  flex: 1 1 0;
  height: 36px;
  width: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-elevated);
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 600;
}
.info-panel-pref-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.info-panel-pref-btn .icon { width: 16px; height: 16px; }
.info-panel-pref-btn--active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.info-panel-pref-btn--active:hover { background: var(--accent-strong); }

/* chat-list.js's row context menu reuses .main-menu/.menu-item's look
   (border/background/box-shadow/menu-item hover, all already theme-aware)
   but is positioned at the cursor rather than anchored under a toolbar
   button, so it needs its own `position` — `fixed`, not `.main-menu`'s
   `absolute`, so JS-set clientX/clientY coordinates map directly regardless
   of any ancestor's scroll/positioning context. Same-specificity class
   rules cascade by source order, and this rule is declared AFTER .main-menu
   above, so `position: fixed` wins for that one property without needing
   `!important`. */
.chat-row-menu {
  position: fixed;
  min-width: 200px;
}

/* ---------- modal overlay: People directory + Ctrl+K quick switcher
   (Task 8: ui/people.js) ---------- */
/* No slot for either in the frozen Task 1 mockup — both built at runtime,
   same pattern as .jump-to-bottom/.composer-bar/.offline-banner above.
   Shared shell for two otherwise-similar overlays (search box + a
   scrollable result list); ui/people.js parameterises the one difference
   (the department filter row) rather than this stylesheet needing two
   near-identical rule sets. */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  padding-top: 12vh;
}
.modal-overlay[hidden] { display: none; }
.modal {
  width: 480px;
  max-width: calc(100vw - 48px);
  max-height: 70vh;
  background: var(--bg-panel);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-panel);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.modal-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text-tertiary);
}
.modal-header .icon { color: var(--text-tertiary); }
.modal-search-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 36px;
  background: var(--bg-input);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  padding: 0 var(--space-3);
  color: var(--text-primary);
  font-size: 14px;
}
.modal-search-input:focus { outline: none; border-color: var(--accent); }
.modal-filter-row {
  flex: 0 0 auto;
  padding: 0 var(--space-4) var(--space-3);
}
.modal-dept-input {
  width: 100%;
  height: 32px;
  background: var(--bg-input);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-2);
  font-size: 12.5px;
  color: var(--text-primary);
}
.modal-dept-input:focus { outline: none; border-color: var(--accent); }
.modal-results {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-2) 0;
}
.modal-section-title {
  padding: var(--space-2) var(--space-4) 4px;
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
}
.modal-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
}
.modal-row:hover, .modal-row:focus-visible { background: var(--bg-hover); outline: none; }
.modal-row-main { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 1px; }
.modal-row-name {
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.modal-row-sub {
  font-size: 12px;
  color: var(--text-secondary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.modal-empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--text-tertiary);
  font-size: 13px;
}

/* ---------- login view ---------- */
/* Author rules always beat the UA default [hidden]{display:none}, so the
   flex display below would otherwise show the login card even while the
   `hidden` attribute is set on it — make the override explicit. */
.login-view[hidden] { display: none; }
.login-view {
  height: 100vh;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-app);
  background-image: radial-gradient(circle at 20% 20%, rgba(78, 164, 245, 0.08), transparent 45%),
    radial-gradient(circle at 80% 80%, rgba(78, 164, 245, 0.06), transparent 45%);
}
.login-card {
  width: 340px;
  max-width: calc(100vw - 48px);
  background: var(--bg-sidebar);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  box-shadow: var(--shadow-panel);
}
.login-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.login-brand h1 { font-size: 20px; font-weight: 700; }
.login-tagline {
  text-align: center;
  color: var(--text-secondary);
  font-size: 13px;
  margin-bottom: var(--space-2);
}
.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12.5px;
  color: var(--text-secondary);
  font-weight: 600;
}
.field input {
  height: 40px;
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: 0 var(--space-3);
  color: var(--text-primary);
  font-size: 14px;
}
.field input:focus { outline: none; border-color: var(--accent); }
.field input::placeholder { color: var(--text-tertiary); }
/* Plan 4, Task 5: admin forms need select/textarea/checkbox rows too — the
   login card (.field's original and only consumer through Plan 3) never
   needed anything but a plain text/password input. Same border/radius/
   token vocabulary as .field input above, no new colours. */
.field select, .field textarea {
  background: var(--bg-input);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-3);
  color: var(--text-primary);
  font-size: 14px;
  font-family: inherit;
}
.field select { height: 40px; }
.field textarea { resize: vertical; min-height: 72px; }
.field select:focus, .field textarea:focus { outline: none; border-color: var(--accent); }
.field-row-checkbox { flex-direction: row; align-items: center; gap: var(--space-2); }
.field-row-checkbox input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--accent); }
/* Added in Task 3 — inserted by public/js/ui/login.js at runtime, not present
   in the static markup. Reuses existing tokens only, no new colours. */
.login-error {
  text-align: center;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--danger);
}
.login-hint {
  text-align: center;
  font-size: 12px;
  color: var(--text-tertiary);
  margin-top: var(--space-2);
}

/* ---------- responsive: keep two columns usable down to 900px, below that
   single column with back navigation (Task 8) ---------- */
@media (max-width: 1000px) {
  :root { --sidebar-width: 280px; }
  .message-body { max-width: 78%; }
  .media-placeholder { width: 210px; }
}

/* No slot for this in the frozen Task 1 mockup (a two-column-only layout) —
   built at runtime by main.js's initResponsiveNav, inert (never shown)
   above the breakpoint below. */
.thread-header-back { display: none; }

@media (max-width: 900px) {
  /* Below 900px there isn't room for both columns at once (see the plan's
     own "must not be unusable on a laptop half-screen" acceptance) — show
     exactly one, switching on whether a chat is open. #app's
     `has-active-chat` class is kept in sync with store.activeChatId by
     main.js's initResponsiveNav; every rule here is driven off it, nothing
     new to track. Above this breakpoint both rules are simply never
     matched, so the existing two-column layout is untouched. */
  .sidebar { flex: 1 1 100%; width: 100%; }
  .main-area { display: none; }
  .app.has-active-chat .sidebar { display: none; }
  .app.has-active-chat .main-area { display: flex; flex: 1 1 100%; width: 100%; }

  .thread-header-back { display: flex; }
  .info-panel { width: 100%; max-width: 100%; }
}

/* ---------- lightbox (Task 2: public/js/ui/lightbox.js) ---------- */
/* Deliberately theme-independent (always a dark scrim) — a fullscreen image
   viewer is the one surface in this app where that's the expected, standard
   convention (native OS viewers, Telegram, Slack all do the same regardless
   of their own light/dark setting); it does not opt this app out of theme
   support anywhere else. No slot in the frozen Task 1 mockup — built at
   runtime and appended to <body>, same pattern as every other
   runtime-inserted overlay in this file (.offline-banner, the People/Ctrl-K
   modal, etc). */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 10, 14, 0.92);
}
.lightbox-overlay[hidden] { display: none; }
.lightbox-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  max-width: min(90vw, 1100px);
  max-height: 90vh;
}
.lightbox-image {
  max-width: min(90vw, 1100px);
  max-height: 78vh;
  width: auto;
  height: auto;
  border-radius: var(--radius-sm);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  object-fit: contain;
}
.lightbox-footer {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: rgba(255, 255, 255, 0.85);
  font-size: 13px;
  max-width: 100%;
}
.lightbox-caption {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.lightbox-counter {
  flex: 0 0 auto;
  color: rgba(255, 255, 255, 0.55);
}
.lightbox-close {
  position: absolute;
  top: var(--space-5);
  right: var(--space-5);
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
}
.lightbox-close:hover { background: rgba(255, 255, 255, 0.16); }
.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
}
.lightbox-nav:hover { background: rgba(255, 255, 255, 0.16); }
.lightbox-nav .icon { width: 22px; height: 22px; }
.lightbox-prev { left: var(--space-5); }
.lightbox-next { right: var(--space-5); }
.lightbox-next .icon { transform: rotate(180deg); } /* reuses #icon-back, mirrored */

@media (max-width: 640px) {
  .lightbox-nav { width: 40px; height: 40px; }
  .lightbox-prev { left: var(--space-2); }
  .lightbox-next { right: var(--space-2); }
  .lightbox-close { top: var(--space-3); right: var(--space-3); }
}

/* ---------- reaction chips (Plan 3, Task 4: thread.js renderReactionChips) ---------- */
/* Existing token vocabulary only — no new colours, so both themes fall out
   for free the same way every other runtime-inserted element in this file
   does. */
.message-reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}
.message--own .message-reactions { justify-content: flex-end; }
.reaction-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 7px;
  border-radius: 11px;
  background: var(--bg-elevated);
  border: 1px solid var(--border-subtle);
  color: var(--text-secondary);
}
.reaction-chip:hover { background: var(--bg-hover); }
.reaction-chip--mine {
  background: rgba(78, 164, 245, 0.16);
  border-color: var(--accent);
  color: var(--accent);
}
.reaction-chip-emoji { font-size: 13px; line-height: 1; }
.reaction-chip-count { font-weight: 700; font-size: 11px; line-height: 1; }

/* ---------- emoji picker (Plan 3, Task 4: public/js/ui/emoji-picker.js) ---------- */
/* A curated, first-party grid — no external emoji library, no CDN request.
   Positioned in JS (openEmojiPicker's own position()), so only sizing/look
   lives here. */
.emoji-picker {
  position: absolute;
  z-index: 60;
  width: 236px;
  padding: var(--space-2);
  background: var(--bg-panel);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-panel);
}
.emoji-picker-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 2px;
}
.emoji-picker-item {
  width: 34px;
  height: 34px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 19px;
  line-height: 1;
}
.emoji-picker-item:hover { background: var(--bg-hover); }

/* ---------- mention autocomplete (Plan 3, Task 7: public/js/ui/mention-autocomplete.js) ---------- */
/* Positioned in JS (that module's own position()), opens upward from the
   composer — same reasoning as its own header comment: there's never room
   below the textarea for a dropdown. */
.mention-autocomplete {
  position: absolute;
  z-index: 60;
  max-height: 240px;
  overflow-y: auto;
  padding: var(--space-1);
  background: var(--bg-panel);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-panel);
}
.mention-autocomplete-list {
  display: flex;
  flex-direction: column;
}
.mention-autocomplete-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: 6px var(--space-2);
  border-radius: var(--radius-sm);
  text-align: left;
}
.mention-autocomplete-item:hover,
.mention-autocomplete-item--active {
  background: var(--bg-hover);
}
.mention-autocomplete-name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mention-autocomplete-username {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 12.5px;
  color: var(--text-tertiary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-align: right;
}

/* ---------- pinned-message bar (Plan 3, Task 4: thread.js buildPinnedBar) ---------- */
/* No slot for this in the frozen Task 1 mockup — inserted at runtime as a
   normal flex child of .thread-pane, between .thread-header and
   .thread-messages (NOT absolutely positioned, unlike jump-to-bottom/toast
   below — it displaces the message list rather than overlaying it, same as
   .composer-bar does at the bottom). */
.pinned-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 48px;
  padding: 0 var(--space-4);
  background: var(--bg-sidebar);
  border-bottom: 1px solid var(--border-subtle);
  cursor: pointer;
}
.pinned-bar[hidden] { display: none; } /* same [hidden] gotcha as .chat-row[hidden] above */
.pinned-bar:hover { background: var(--bg-hover); }
.pinned-bar-icon { width: 16px; height: 16px; color: var(--accent); flex: 0 0 auto; }
.pinned-bar-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.pinned-bar-label { font-size: 11.5px; font-weight: 700; color: var(--accent); }
.pinned-bar-text {
  font-size: 13px;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pinned-bar-count { flex: 0 0 auto; font-size: 11.5px; color: var(--text-tertiary); }
.pinned-bar-count[hidden] { display: none; }
.pinned-bar-close {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
}
.pinned-bar-close:hover { background: var(--bg-active); color: var(--text-primary); }
.pinned-bar-close .icon { width: 14px; height: 14px; }

/* ---------- thread toast (Plan 3, Task 4: a friendly landing spot for e.g.
   a 403 trying to pin in a channel — "surfaced as a sensible message, not a
   crash") ---------- */
.thread-toast {
  position: absolute;
  left: 50%;
  bottom: 84px;
  transform: translateX(-50%);
  z-index: 30;
  max-width: 80%;
  padding: 8px 14px;
  border-radius: var(--radius-md);
  background: var(--danger);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
  box-shadow: var(--shadow-panel);
}
.thread-toast[hidden] { display: none; }

/* ================================================================
   Plan 4, Task 5: admin section (public/js/ui/admin/*.js)
   ================================================================
   A full-viewport overlay (higher z-index than the People/Ctrl+K modal
   overlay's 100 — Admin is reached from the main menu, never opened
   underneath another overlay, but this keeps the stacking order obvious
   from the CSS alone) with a tab rail down the left and one scrollable
   content pane on the right. Existing token vocabulary only throughout —
   no new colours beyond what .thread-toast/.danger/.accent/.presence-online
   above already established, so both themes and the 900px floor fall out
   for free the same way every other runtime-inserted surface in this file
   does. */
.admin-overlay {
  position: fixed;
  inset: 0;
  z-index: 150;
  background: var(--bg-app);
  display: flex;
}
.admin-overlay[hidden] { display: none; }
.admin-shell {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
}

.admin-tab-rail {
  flex: 0 0 220px;
  display: flex;
  flex-direction: column;
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border-subtle);
  padding: var(--space-2);
  overflow-y: auto;
}
.admin-rail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-2) var(--space-2) var(--space-4);
}
.admin-rail-title { font-weight: 700; font-size: 16px; }
.admin-tab-btn {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: 10px var(--space-3);
  border-radius: var(--radius-md);
  color: var(--text-secondary);
  font-size: 14px;
  font-weight: 600;
  text-align: left;
}
.admin-tab-btn .icon { flex: 0 0 auto; }
.admin-tab-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.admin-tab-btn--active { background: var(--bg-active); color: var(--text-primary); }

.admin-content {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  padding: var(--space-6);
}
.admin-panel[hidden] { display: none; }

.admin-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}
.admin-search { flex: 1 1 260px; min-width: 200px; max-width: 360px; }
.admin-toolbar-btn { width: auto; padding: 0 var(--space-4); height: 38px; white-space: nowrap; }

.admin-empty {
  padding: var(--space-6);
  text-align: center;
  color: var(--text-tertiary);
  font-size: 13.5px;
}

/* ---- tables (People/Bots/Audit-list/Stats tables) ---- */
.admin-table-wrap { overflow-x: auto; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); }
.admin-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.admin-table th, .admin-table td {
  padding: var(--space-2) var(--space-3);
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--border-subtle);
}
.admin-table th {
  color: var(--text-tertiary);
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: var(--bg-elevated);
}
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table tbody tr:hover { background: var(--bg-hover); }
.admin-row--inactive { opacity: 0.55; }
.admin-person-cell { display: flex; align-items: center; gap: var(--space-3); }
.admin-person-name { display: flex; align-items: center; gap: var(--space-2); font-weight: 600; color: var(--text-primary); }
.admin-person-username { font-size: 12px; color: var(--text-tertiary); }
.admin-row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.admin-row-btn { width: auto; height: 28px; padding: 0 10px; font-size: 12px; font-weight: 600; border-radius: var(--radius-sm); background: var(--bg-elevated); color: var(--text-secondary); }
.admin-row-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.admin-row-btn--danger { color: var(--danger); }

/* ---- status/outcome pills ---- */
.admin-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  text-transform: capitalize;
  background: var(--bg-elevated);
  color: var(--text-secondary);
}
.admin-badge--active, .admin-badge--ok { background: rgba(67, 181, 129, 0.16); color: var(--presence-online); }
.admin-badge--inactive, .admin-badge--neutral { background: var(--bg-elevated); color: var(--text-tertiary); }
.admin-badge--admin { background: rgba(78, 164, 245, 0.16); color: var(--accent); }
.admin-badge--bot { background: rgba(78, 164, 245, 0.16); color: var(--accent); }
.admin-badge--warn { background: rgba(255, 176, 32, 0.16); color: #d98c00; }
:root[data-theme="light"] .admin-badge--warn { color: #a06400; }
.admin-badge--error { background: rgba(242, 85, 90, 0.14); color: var(--danger); }

/* ---- pager ---- */
.admin-pager { display: flex; align-items: center; gap: var(--space-2); justify-content: flex-end; margin-top: var(--space-3); }
.admin-pager-label { font-size: 12.5px; color: var(--text-tertiary); }
.admin-pager-next .icon { transform: rotate(180deg); } /* reuses #icon-back, mirrored — same trick as .lightbox-next */
.admin-pager button:disabled { opacity: 0.35; cursor: not-allowed; }

/* ---- toast (same pattern as .thread-toast above, own class so it can be
   positioned relative to .admin-content instead of .thread-pane) ---- */
.admin-content { position: relative; }
.admin-toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  z-index: 220;
  max-width: 80%;
  padding: 8px 14px;
  border-radius: var(--radius-md);
  background: var(--text-primary);
  color: var(--bg-app);
  font-size: 12.5px;
  font-weight: 600;
  text-align: center;
  box-shadow: var(--shadow-panel);
}
.admin-toast--error { background: var(--danger); color: #fff; }
.admin-toast[hidden] { display: none; }

/* ---- confirm dialog / secret-reveal / create-edit form modals — all reuse
   .modal-overlay/.modal wholesale (same shell as the People directory) via
   these overlay classes only for z-index (above .admin-overlay's 150) and
   inner spacing. ---- */
.admin-confirm-overlay, .admin-secret-overlay, .admin-form-overlay { z-index: 250; padding-top: 16vh; }
.admin-confirm-modal, .admin-secret-modal, .admin-form-modal { padding: var(--space-5); max-height: 80vh; overflow-y: auto; gap: var(--space-3); display: flex; flex-direction: column; width: 420px; }
.admin-confirm-title { font-size: 16px; font-weight: 700; }
.admin-confirm-message { font-size: 13.5px; color: var(--text-secondary); line-height: 1.5; }
.admin-confirm-actions { display: flex; gap: var(--space-3); justify-content: flex-end; margin-top: var(--space-2); }
.admin-confirm-btn { width: auto; padding: 0 var(--space-5); }
.admin-confirm-btn--cancel { background: var(--bg-elevated); color: var(--text-secondary); }
.admin-confirm-btn--cancel:hover { background: var(--bg-hover); }

.admin-secret-warning {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: rgba(242, 85, 90, 0.1);
  color: var(--danger);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
}
.admin-secret-warning-icon { transform: rotate(45deg); flex: 0 0 auto; width: 16px; height: 16px; } /* icon-close as a plain "x" caution mark */
.admin-secret-rows { display: flex; flex-direction: column; gap: var(--space-2); }
.admin-secret-row { display: flex; flex-direction: column; gap: 4px; }
.admin-secret-label { font-size: 12px; color: var(--text-tertiary); }
.admin-secret-value-row { display: flex; gap: var(--space-2); align-items: center; }
.admin-secret-value {
  flex: 1 1 auto;
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-input);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
  font-size: 13px;
  word-break: break-all;
}
.admin-secret-copy { width: auto; height: 32px; padding: 0 var(--space-3); flex: 0 0 auto; background: var(--bg-elevated); color: var(--text-secondary); border-radius: var(--radius-sm); font-size: 12px; font-weight: 600; }
.admin-secret-copy:hover { background: var(--bg-hover); }
.admin-secret-close { margin-top: var(--space-2); }

.admin-form { display: flex; flex-direction: column; gap: var(--space-3); }
.admin-field { gap: 6px; }
.admin-form-error { font-size: 12.5px; font-weight: 600; color: var(--danger); }
.admin-form-actions { display: flex; gap: var(--space-3); justify-content: flex-end; }
.admin-form-actions .btn { width: auto; padding: 0 var(--space-5); }

/* ---- CSV import modal ---- */
.admin-csv-modal { width: 640px; }
.admin-csv-hint { font-size: 12.5px; color: var(--text-secondary); line-height: 1.5; }
.admin-csv-textarea { width: 100%; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12.5px; }

/* ---- New group / New channel dialog (public/js/ui/new-chat.js) ----------
   Reuses .modal-overlay/.modal/.admin-form-modal/.admin-form/.admin-field/
   .admin-form-error/.admin-form-actions wholesale (same shell as the admin
   People tab's create/edit modal) — only the member-picker bits below
   (selected chips, results list sizing, the channel-only hint) are new. */
.new-chat-search-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.new-chat-field-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-secondary);
}
.new-chat-hint {
  font-size: 12.5px;
  color: var(--text-secondary);
  line-height: 1.5;
  background: var(--bg-elevated);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
.new-chat-selected {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.new-chat-selected:empty { display: none; }
.new-chat-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 4px 0 10px;
  border-radius: 13px;
  background: rgba(78, 164, 245, 0.16);
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 600;
}
.new-chat-chip-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: inherit;
}
.new-chat-chip-remove:hover { background: rgba(78, 164, 245, 0.28); }
.new-chat-chip-remove .icon { width: 12px; height: 12px; }
.new-chat-results {
  max-height: 220px;
  overflow-y: auto;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: var(--space-1) 0;
}
.new-chat-results:empty { display: none; }
.new-chat-results .modal-row { padding: var(--space-2) var(--space-3); }
.new-chat-results .modal-empty { padding: var(--space-4); }
.admin-csv-summary { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.admin-csv-preview-table { max-height: 240px; }
.admin-csv-preview-table[hidden] { display: none; }

/* ---- Audit tab ---- */
.admin-audit-hint { font-size: 12.5px; color: var(--text-secondary); margin-bottom: var(--space-3); line-height: 1.5; }
.admin-audit-view[hidden] { display: none; }
/* The unmistakable "this is not a normal chat" marker the plan requires —
   distinct colour (reuses --danger, the one colour in this app's palette
   already reserved for "pay attention"), persistent (not a toast, not
   dismissible), and rendered ABOVE the message list so it's the first thing
   visible on open, never scrolled out of view. */
.admin-audit-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--danger);
  color: #fff;
}
.admin-audit-banner-icon { transform: rotate(45deg); flex: 0 0 auto; } /* icon-close as a caution "!" substitute, matches .admin-secret-warning-icon's reuse */
.admin-audit-banner-text { flex: 1 1 auto; min-width: 0; }
.admin-audit-banner-label { font-weight: 800; letter-spacing: 0.04em; font-size: 12.5px; text-transform: uppercase; }
.admin-audit-banner-title { display: block; font-size: 12.5px; opacity: 0.9; margin-top: 2px; }
.admin-audit-back { flex: 0 0 auto; width: auto; height: 34px; padding: 0 var(--space-3); background: rgba(255, 255, 255, 0.16); color: #fff; display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; }
.admin-audit-back:hover { background: rgba(255, 255, 255, 0.26); }
.admin-audit-back .icon { width: 16px; height: 16px; }
.admin-audit-load-older { width: auto; height: 34px; padding: 0 var(--space-4); margin-bottom: var(--space-3); background: var(--bg-elevated); color: var(--text-secondary); align-self: center; font-size: 12.5px; }
.admin-audit-load-older:hover { background: var(--bg-hover); }
.admin-audit-load-older[hidden] { display: none; }
.admin-audit-messages { display: flex; flex-direction: column; gap: var(--space-3); }
.admin-audit-msg { padding: var(--space-3); border-radius: var(--radius-md); background: var(--bg-elevated); }
.admin-audit-msg-meta { display: flex; align-items: center; gap: var(--space-2); margin-bottom: 4px; font-size: 12px; }
.admin-audit-msg-sender { font-weight: 700; color: var(--text-primary); }
.admin-audit-msg-time { color: var(--text-tertiary); }
.admin-audit-msg-text { font-size: 13.5px; color: var(--text-primary); white-space: pre-wrap; overflow-wrap: anywhere; }
.admin-audit-msg-text code { background: var(--bg-input); padding: 1px 5px; border-radius: 4px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12.5px; }
.admin-audit-msg-text a { color: var(--accent); text-decoration: underline; }
.admin-audit-msg-media { color: var(--text-secondary); font-style: italic; }
.admin-audit-msg-system { color: var(--text-tertiary); font-style: italic; }

/* ---- Stats tab ---- */
.admin-stats-body { display: flex; flex-direction: column; gap: var(--space-6); }
.admin-stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-3); }
.admin-stat-tile { padding: var(--space-4); border-radius: var(--radius-md); background: var(--bg-elevated); border: 1px solid var(--border-subtle); }
.admin-stat-value { font-size: 24px; font-weight: 800; color: var(--text-primary); }
.admin-stat-label { font-size: 12px; color: var(--text-tertiary); margin-top: 2px; }
.admin-chart-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--space-4); }
.admin-chart-card { padding: var(--space-4); border-radius: var(--radius-md); background: var(--bg-elevated); border: 1px solid var(--border-subtle); }
.admin-chart-card-title { font-size: 13px; font-weight: 700; margin-bottom: var(--space-3); color: var(--text-secondary); }
.admin-chart-svg { width: 100%; height: 140px; display: block; }
.admin-chart-axis { stroke: var(--border-strong); stroke-width: 1; }
.admin-chart-bar { transition: opacity 0.1s; }
.admin-chart-bar:hover { opacity: 0.75; }
.admin-chart-axis-label { fill: var(--text-tertiary); font-size: 10px; }
.admin-chart-empty-text { fill: var(--text-tertiary); font-size: 12px; }
.admin-stats-tables { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--space-5); }
.admin-section-title { font-size: 13px; font-weight: 700; color: var(--text-secondary); margin: 0 0 var(--space-2); }

/* ---- responsive: same 900px floor as the rest of the app (Task 8's own
   breakpoint) — the rail collapses to a horizontal, scrollable strip above
   the content instead of a fixed-width left column, and tables already
   scroll horizontally via .admin-table-wrap above so no column is ever
   clipped. ---- */
@media (max-width: 900px) {
  .admin-shell { flex-direction: column; }
  .admin-tab-rail {
    flex: 0 0 auto;
    flex-direction: row;
    align-items: center;
    overflow-x: auto;
    overflow-y: visible;
    border-right: none;
    border-bottom: 1px solid var(--border-subtle);
  }
  .admin-rail-head { padding: 0 var(--space-2); flex: 0 0 auto; }
  .admin-tab-btn { width: auto; white-space: nowrap; }
  .admin-content { padding: var(--space-4); }
  .admin-confirm-modal, .admin-secret-modal, .admin-form-modal, .admin-csv-modal { width: 100%; max-width: calc(100vw - 32px); }
}
