:root {
  --bg: #121110;
  --panel: rgba(32, 30, 23, 0.72);
  --panel-solid: #1c1a14;
  --panel-2: #26231b;
  --panel-3: rgba(255, 255, 255, 0.05);
  --border: rgba(255, 255, 255, 0.10);
  --text: #f4f1e8;
  --muted: #a8a396;
  --faint: #7e7a6e;
  /* Speedzing yellow. Everything that used to be the blue/blurple accent now
     reads from these. --accent-text is the version of the yellow that stays
     readable as TEXT (the raw yellow is too light on the light theme). */
  --accent: #ffc61a;
  --accent-soft: rgba(255, 198, 26, 0.16);
  --accent-text: #ffd95e;
  --yellow-tint: rgba(255, 198, 26, 0.14);
  /* Text/icon color on top of a SOLID var(--accent) fill (buttons, badges,
     avatar fallbacks). */
  --accent-fg: #14110a;
  --ink: #f4f1e8;
  --ink-fg: #14110a;
  --green: #57f287;
  --red: #ed4245;
  --yellow: #fee75c;
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
  --radius-card: 20px;
  --sidebar-w: 264px;
  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --bg-grad:
    radial-gradient(900px 600px at 0% 0%, rgba(255, 198, 26, 0.16), transparent 62%),
    radial-gradient(800px 600px at 100% 100%, rgba(255, 198, 26, 0.10), transparent 62%);
  font-family: var(--font);
  color-scheme: dark;
}

/* ===========================================================================
   Site-wide theming — a light or dark theme, chosen per browser with the
   toggle (see themeCookie.js) and applied via data-theme on <html> (every
   view sets it from res.locals.theme — see app.js). The attribute selector
   below has the same specificity as :root, so it wins by source order,
   which is why it sits directly after :root and nowhere else.

   data-theme="dark" needs no block of its own — :root above already *is*
   the dark theme, which is why it's the default (matches "yellow accent +
   dark theme" being this site's original, still-default look).
   =========================================================================== */
[data-theme="light"] {
  --bg: #f7f5f0;
  --panel: rgba(255, 255, 255, 0.74);
  --panel-solid: #ffffff;
  --panel-2: #f4f2ea;
  --panel-3: rgba(255, 255, 255, 0.6);
  --border: #e7e5de;
  --text: #111111;
  --muted: #6b6b65;
  --faint: #a09e96;
  --accent-soft: rgba(255, 198, 26, 0.24);
  --accent-text: #8a6100;
  --yellow-tint: #fff4cc;
  --ink: #111111;
  --ink-fg: #ffffff;
  --shadow: 0 6px 24px rgba(120, 95, 20, 0.08);
  --bg-grad:
    radial-gradient(900px 620px at 0% 0%, rgba(250, 226, 150, 0.7), transparent 62%),
    radial-gradient(800px 620px at 100% 100%, rgba(255, 214, 90, 0.45), transparent 62%);
  color-scheme: light;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  font-family: var(--font);
}
/* Soft yellow glow on a fixed layer behind everything. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--bg-grad);
  pointer-events: none;
}

/* Every icon rendered by icon() (src/server/icons.js) — a plain inline SVG,
   so it inherits color from whatever it's placed in and needs no extra
   wiring per instance. */
.icon { flex-shrink: 0; }

.center-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.login-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 2.5rem;
  width: 100%;
  max-width: 360px;
  box-shadow: var(--shadow);
}

.login-card h1 {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  font-size: 1.25rem;
  margin: 0 0 1.25rem;
}

input[type="password"], input[type="text"], input[type="datetime-local"], textarea, select {
  width: 100%;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-size: 0.95rem;
  margin-bottom: 0.75rem;
  font-family: inherit;
}

/* Replaces the browser's default focus ring (a stark white/blue glow in
   most browsers, especially jarring on this dark background — this is the
   "white bar" around a focused search/text field) with a subtle accent-
   colored border + glow that actually fits the theme. */
input[type="password"]:focus, input[type="text"]:focus, input[type="datetime-local"]:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* datetime-local's own calendar/clock icon — :root already sets
   color-scheme: dark/light per theme so the icon itself auto-inverts for
   readability, this just makes it obviously clickable like every other
   custom control (select arrow, number stepper) in this app. */
input[type="datetime-local"]::-webkit-calendar-picker-indicator { cursor: pointer; }

textarea { resize: vertical; min-height: 60px; }

/* ===========================================================================
   Custom <select> replacement (public/js/custom-select.js) — every dropdown
   sitewide gets wrapped in this instead of the native popup, which the
   browser renders itself and CSS can never reach into. The real <select>
   stays in the page (hidden here) so nothing that reads/writes its .value
   or listens for 'change' needs to change.
   =========================================================================== */
.cs-native-select {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  border: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
}

.cs-wrapper { position: relative; width: 100%; margin-bottom: 0.75rem; }

.cs-trigger {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--panel-2);
  color: var(--text);
  font-size: 0.95rem;
  font-weight: 400;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  margin: 0;
  transition: border-color 0.15s ease, transform 0.12s ease;
}
.cs-trigger:hover { border-color: var(--accent); }
.cs-trigger:focus-visible,
.cs-wrapper.cs-open .cs-trigger {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.cs-trigger:disabled { opacity: 0.5; cursor: not-allowed; }
/* Overrides the sitewide button:active { transform: scale(0.98) } (see
   below) — that's tuned for a normal-width button; on a full-width:100%
   trigger like this one, the same 2% shrink moves the edges several times
   as many pixels, which read as a harsh snap instead of a subtle press.
   This one rule alone wouldn't actually animate anything either, since
   .cs-trigger's own `transition` above (a single-property value, more
   specific than the sitewide multi-property one) replaces that shorthand
   entirely rather than merging with it — transform 0.12s ease had to move
   up into this element's own transition for :active to ease at all instead
   of snapping instantly. */
.cs-trigger:active { transform: scale(0.995); }

.cs-trigger-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cs-trigger-label.cs-placeholder { color: var(--muted); }
.cs-trigger .icon { flex-shrink: 0; color: var(--muted); transition: transform 0.15s ease; }
.cs-wrapper.cs-open .cs-trigger .icon { transform: rotate(180deg); }

/* position: fixed (not absolute-relative-to-.cs-wrapper) with top/left/
   width/bottom set inline by custom-select.js's positionPanel() at open
   time, computed fresh from the trigger's actual on-screen position. A
   wrapper sitting inside any ancestor with overflow:hidden (e.g. a
   collapsible .settings-section card, used all over the settings pages)
   would otherwise silently clip this panel invisible the instant it tried
   to extend past that ancestor's edge — the dropdown LOOKED broken/
   unclickable even though it was technically still opening underneath.
   Fixed positioning escapes that clipping entirely since it's relative to
   the viewport, not any scrollable/clipped ancestor. */
.cs-panel {
  position: fixed;
  z-index: 60;
  max-height: 16rem;
  overflow-y: auto;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: 0.35rem;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.cs-panel[hidden] { display: none; }
.cs-panel::-webkit-scrollbar { width: 8px; }
.cs-panel::-webkit-scrollbar-track { background: transparent; }
.cs-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }

.cs-group-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 0.4rem 0.55rem 0.25rem;
}

.cs-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.55rem;
  border-radius: 7px;
  cursor: pointer;
  font-size: 0.86rem;
  color: var(--text);
}
.cs-option.active,
.cs-option:hover { background: var(--accent-soft); }
.cs-option.selected::after {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
}
.cs-option[aria-disabled="true"] { opacity: 0.4; cursor: not-allowed; }

.cs-empty { padding: 0.6rem 0.55rem; color: var(--muted); font-size: 0.85rem; font-style: italic; }

/* ===========================================================================
   Custom number stepper (public/js/custom-number.js) — replaces the
   browser's own up/down spinner arrows on every type="number" field
   sitewide with buttons that match the theme instead of looking like a
   stray OS control dropped onto a dark page.
   =========================================================================== */
.num-stepper {
  display: flex;
  align-items: stretch;
  width: 100%;
  margin-bottom: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
  overflow: hidden;
  transition: border-color 0.15s ease;
}
.num-stepper:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.num-stepper-input.num-stepper-input {
  flex: 1;
  min-width: 0;
  width: auto;
  margin: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  text-align: center;
}
.num-stepper-input.num-stepper-input:focus { outline: none; box-shadow: none; }
.num-stepper-input::-webkit-outer-spin-button,
.num-stepper-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.num-stepper-input[type="number"] { -moz-appearance: textfield; }

.num-stepper-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  padding: 0;
  border-radius: 0;
  flex-shrink: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.num-stepper-btn.minus { border-right: 1px solid var(--border); }
.num-stepper-btn.plus { border-left: 1px solid var(--border); }
.num-stepper-btn:hover:not(:disabled) { background: var(--accent-soft); color: var(--text); }
.num-stepper-btn:active:not(:disabled) { transform: scale(0.92); }
.num-stepper-btn:disabled { opacity: 0.35; cursor: not-allowed; }

button, .btn, .btn-primary, .btn-ghost {
  cursor: pointer;
  border: none;
  border-radius: 9px;
  padding: 0.6rem 1rem;
  font-size: 0.9rem;
  font-weight: 600;
  font-family: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  text-decoration: none;
  transition: filter 0.15s ease, border-color 0.15s ease, background-color 0.15s ease, transform 0.1s ease;
}
button:active, .btn:active, .btn-primary:active, .btn-ghost:active { transform: scale(0.98); }

/* NOT full-width by default (a change from the earlier version of this
   file) — a "Save"/"Mark Active"/etc. primary button should read as a
   normal button sitting wherever its form puts it, not a long full-width
   bar. The one place a full-width primary button IS actually correct — the
   login form, where it visually matches the full-width username/password
   inputs above it — opts back in explicitly right below. */
.btn-primary { background: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { filter: brightness(1.1); }
.login-card .btn-primary { width: 100%; }
.btn-approve { background: var(--green); color: #06240f; }
.btn-reject { background: var(--red); color: #2b0505; }
.btn-danger { background: var(--red); color: #2b0505; }
.btn-danger:hover { filter: brightness(1.1); }
.btn-ghost {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--border);
  text-decoration: none;
}
.btn-ghost:hover { color: var(--text); border-color: var(--accent); }

.error-msg {
  background: rgba(237, 66, 69, 0.15);
  color: #ff9b9d;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  font-size: 0.85rem;
  margin-bottom: 1rem;
}

.flash-msg {
  background: rgba(255, 198, 26, 0.15);
  color: var(--accent-text);
  padding: 0.6rem 1rem;
  border-radius: 8px;
  font-size: 0.9rem;
  margin: 1rem 1.5rem;
}

/* --- Page header: everything on one row to save vertical space — the
   page's own title/back-link on the left, the 3-tab main nav truly
   centered in the middle (via the grid's equal-width outer columns, not
   flexbox centering, so it stays centered regardless of how wide the left
   and right content are), and settings/logout on the right. Same structure
   on every authenticated page. Wraps to a second line only on very narrow
   screens (see the media query below), rather than ever overlapping. --- */
.page-head {
  border-bottom: 1px solid var(--border);
  background: var(--panel-3);
  backdrop-filter: blur(6px);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  padding: 0.7rem 1.5rem;
  gap: 0.75rem 1rem;
}

.page-head-left { justify-self: start; min-width: 0; }
.page-head-left h1 { font-size: 1.1rem; margin: 0; display: flex; align-items: center; gap: 0.55rem; white-space: nowrap; }
.page-head-right { justify-self: end; display: flex; align-items: center; gap: 0.75rem; }
.page-head-right a {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.88rem;
  font-weight: 600;
  transition: color 0.15s ease;
}
.page-head-right a:hover { color: var(--text); }
/* .page-head-right a's own color rule above (a class + a type selector)
   outranks .btn-primary's (just a class), so a primary-styled link sitting
   in this corner — like Msg Builder's "+ New Message" — was rendering in
   the same muted gray as a plain "Log Out"/"Back" link instead of the
   dark text .btn-primary actually calls for, which read as barely legible
   against the solid yellow background. Restated here, at equal specificity
   to the hover rule above so it also holds up on hover, so any button
   that's genuinely a .btn-primary keeps its intended dark-on-yellow text
   no matter which header it's placed in. */
.page-head-right a.btn-primary,
.page-head-right a.btn-primary:hover {
  color: var(--accent-fg);
}
/* Same specificity problem as the color fix above, just for size instead
   of color: .page-head-right a (a class + a type selector, 0-1-1) outranks
   .btn-small (a single class, 0-1-0), so Msg Builder's "+ New Message"
   link rendered at .page-head-right a's own font-size/gap (0.88rem /
   0.4rem) instead of .btn-small's intended, smaller values (0.8rem /
   0.45rem) that every other small action button sitewide actually gets -
   the one visible "small" button that wasn't actually small. Restated at
   higher specificity (two classes + type) so .btn-small wins here too. */
.page-head-right a.btn-small { font-size: 0.8rem; gap: 0.45rem; }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.88rem;
  font-weight: 600;
  transition: color 0.15s ease;
}
.back-link:hover { color: var(--text); }

@media (max-width: 820px) {
  .page-head {
    grid-template-columns: 1fr auto;
    grid-template-areas: "left right" "tabs tabs";
  }
  .page-head-left { grid-area: left; }
  .page-head-right { grid-area: right; }
  .main-tabs { grid-area: tabs; justify-self: center; }
}

.tabs {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem 1.5rem 0;
}

.tab {
  padding: 0.5rem 1rem;
  border-radius: 999px;
  text-decoration: none;
  color: var(--muted);
  border: 1px solid var(--border);
  font-size: 0.85rem;
  font-weight: 600;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.tab:hover { color: var(--text); border-color: var(--accent); }

.tab.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

/* --- Top-level 3-tab nav (Applications / Tickets / Msg Builder) — sits in
   the middle column of .page-head's grid, on the same row as the page
   title and settings/logout, instead of a row of its own. Distinct from
   .tabs above, which is the Open/Closed-style status filter within a
   single tab. --- */
.main-tabs {
  display: flex;
  justify-content: center;
  gap: 0.4rem;
}
.main-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  text-decoration: none;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 600;
  border: 1px solid transparent;
  white-space: nowrap;
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}
.main-tab:hover { color: var(--text); border-color: var(--border); }
.main-tab.active { color: var(--accent-fg); background: var(--accent); border-color: var(--accent); }

.type-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  font-weight: 700;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: rgba(255, 198, 26, 0.18);
  color: var(--accent-text);
}
.type-badge.large { font-size: 0.85rem; padding: 0.3rem 0.8rem; }

.status-pill {
  display: inline-flex;
  align-items: center;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  margin-left: 0.4rem;
}
.status-Pending { background: rgba(254, 231, 92, 0.18); color: var(--yellow); }
.status-Approved { background: rgba(87, 242, 135, 0.18); color: var(--green); }
.status-Rejected { background: rgba(237, 66, 69, 0.18); color: #ff9b9d; }
.status-Open { background: rgba(87, 242, 135, 0.18); color: var(--green); }
.status-Closed { background: rgba(154, 162, 177, 0.2); color: var(--muted); }

.ticket-app-badge { background: rgba(255, 198, 26, 0.18); color: var(--accent-text); }

.empty-state { color: var(--muted); padding: 3rem 1.5rem; text-align: center; font-size: 0.92rem; }

/* --- Avatar bubbles (src/server/icons.js's avatar() helper) — a real
   Discord avatar <img> when one's stored, or an initial-letter circle
   fallback (also what public/js/avatar-fallback.js swaps a broken/stale
   image for, so both paths look identical). --- */
.avatar {
  border-radius: 50%;
  object-fit: cover;
  background: var(--panel-2);
  vertical-align: middle;
  flex-shrink: 0;
}
.avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--accent), #ffe066);
  color: var(--accent-fg);
  font-weight: 700;
}

/* --- Dashboard: compact application/ticket list --- */

.app-list {
  display: flex;
  flex-direction: column;
  padding: 1rem 1.5rem 2rem;
  gap: 0.6rem;
  max-width: 1000px;
  /* Was left-anchored on wide screens (width-capped, but no auto margin to
     go with it) — centers Applications/Tickets/Msg Builder's lists in the
     available space instead. Logs pages override max-width to "none" (see
     .logs-layout .app-list below) since they center as part of the whole
     list+leaderboard pair via .logs-page-wrap instead. */
  margin: 0 auto;
}

.app-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.9rem 1.25rem;
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.app-row:hover { border-color: var(--accent); background: var(--panel-2); transform: translateX(2px); }

.app-row-avatar { flex-shrink: 0; }
.app-row-main { min-width: 0; flex: 1; }
.app-row-title { font-weight: 700; font-size: 1rem; display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.app-row-excerpt {
  margin-top: 0.35rem;
  font-size: 0.85rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-row-excerpt strong { color: var(--text); font-weight: 600; }

.app-row-side { display: flex; align-items: center; gap: 0.9rem; flex-shrink: 0; }
.app-row-live-indicator { display: inline-flex; align-items: center; color: var(--green); }
.app-row-date { color: var(--muted); font-size: 0.8rem; white-space: nowrap; }
.app-row-arrow { display: inline-flex; align-items: center; color: var(--accent); }

/* A plain (non-link) .app-row — the Logs tab's Staff Logs rows aren't
   individually clickable the way an application/ticket row is, so the
   hover "this is a link" affordance (border/background/translateX) is
   turned off; the row itself still reads identically otherwise. Also lets
   .app-row-excerpt wrap onto a second line instead of ellipsis-truncating,
   since a case's reason text is often the whole point of the row. */
.app-row-static { cursor: default; }
.app-row-static:hover { border-color: var(--border); background: var(--panel); transform: none; }
.app-row-static .app-row-excerpt { white-space: normal; }
.app-row-static .app-row-title a { color: var(--accent); text-decoration: none; }
.app-row-static .app-row-title a:hover { text-decoration: underline; }

/* --- Chat Log: attachments + bot embeds (see logs-chat.ejs, src/bot/
   chatLog.js) --- */
.chat-attachments { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.5rem; }
.chat-attachment-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--accent);
  text-decoration: none;
  font-size: 0.82rem;
}
.chat-attachment-link:hover { text-decoration: underline; }
.chat-attachment-thumb {
  display: block;
  max-width: 220px;
  max-height: 160px;
  border-radius: 8px;
  border: 1px solid var(--border);
  object-fit: cover;
}
/* --- Action Log: expandable details per row (see logs-action.ejs,
   src/server/actionLogFormat.js, public/js/logs-action.js /
   logs-action-archive.js). The whole row is the click target when it has
   details, not just a small text label — .action-log-row's flex-start
   alignment is what keeps the avatar pinned to the top instead of
   re-centering as the row grows taller once expanded (the default .app-row
   centers vertically, which used to make the avatar visibly jump). ---*/
.action-log-row { align-items: flex-start; }
.action-log-row-expandable { cursor: pointer; }
.action-log-row-expandable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.action-log-row-chevron {
  display: inline-flex;
  vertical-align: -1px;
  margin-left: 0.15rem;
  color: var(--muted);
  transition: transform 0.15s ease;
}
.action-log-row.is-expanded .action-log-row-chevron { transform: rotate(90deg); }
.action-log-details-body { margin-top: 0.5rem; padding: 0.6rem 0.75rem; background: var(--panel-2); border-radius: 8px; border: 1px solid var(--border); font-size: 0.82rem; }
.action-log-role-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.action-log-role-chips + .action-log-field-list { margin-top: 0.5rem; }
.action-log-chip { display: inline-flex; align-items: center; padding: 0.15rem 0.55rem; border-radius: 999px; font-size: 0.78rem; font-weight: 600; }
.action-log-chip-add { background: rgba(87, 242, 135, 0.15); color: #57f287; }
.action-log-chip-remove { background: rgba(237, 66, 69, 0.15); color: #ed4245; }
.action-log-field-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.3rem; }
.action-log-field-label { color: var(--muted); }

/* A bulk_delete entry's expandable body (see logs-action.ejs, actionLog.js's
   logBulkDelete) — each purged message rendered exactly like a normal Chat
   Log row (reuses chat-attachments.ejs/chat-embeds.ejs), just stacked and
   separated instead of full app-rows, since these are nested inside an
   already-expanded Action Log row rather than being top-level list items. */
.action-log-bulk-messages { display: flex; flex-direction: column; gap: 0.65rem; max-height: 420px; overflow-y: auto; }
.action-log-bulk-message { padding-bottom: 0.65rem; border-bottom: 1px solid var(--border); }
.action-log-bulk-message:last-child { padding-bottom: 0; border-bottom: none; }
.action-log-bulk-message-head { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.25rem; }
.action-log-bulk-message-avatar { border-radius: 50%; }
.action-log-bulk-message-head .app-row-date { margin-left: auto; }
.action-log-bulk-message-body { font-size: 0.85rem; color: var(--text); }

.chat-attachment-expired { color: var(--muted); font-style: italic; }

/* Inline audio/video preview for archive-viewer attachments (see
   logs-chat-archive.js's renderAttachmentLink) — before this, anything that
   wasn't an image just rendered as a bare text link, which for the very
   common case of a voice clip or short video read as "attachments don't
   show" even though the link itself worked fine. */
.chat-attachment-media { display: flex; flex-direction: column; gap: 0.3rem; width: 100%; max-width: 320px; }
.chat-attachment-media-name { display: flex; align-items: center; gap: 0.35rem; color: var(--muted); font-size: 0.78rem; }
.chat-attachment-media audio { width: 100%; height: 32px; }
.chat-attachment-video { display: block; max-width: 320px; max-height: 220px; border-radius: 8px; border: 1px solid var(--border); }

.chat-embeds { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.5rem; }
.chat-embed {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border);
  border-radius: 6px;
  padding: 0.65rem 0.85rem;
  font-size: 0.85rem;
  white-space: normal;
  max-width: 480px;
}
.chat-embed-author { font-weight: 600; font-size: 0.78rem; color: var(--muted); margin-bottom: 0.2rem; }
.chat-embed-title { font-weight: 700; color: var(--text); margin-bottom: 0.25rem; }
.chat-embed-title a { color: var(--accent); text-decoration: none; }
.chat-embed-title a:hover { text-decoration: underline; }
.chat-embed-desc { color: var(--muted); white-space: pre-line; }
.chat-embed-fields { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; margin-top: 0.5rem; }
.chat-embed-field { flex: 1 1 100%; }
.chat-embed-field-inline { flex: 1 1 calc(33% - 0.7rem); min-width: 120px; }
.chat-embed-field-name { font-weight: 700; font-size: 0.75rem; color: var(--text); margin-bottom: 0.1rem; }
.chat-embed-field-value { color: var(--muted); white-space: pre-line; }
.chat-embed-image { display: block; max-width: 100%; max-height: 220px; border-radius: 6px; margin-top: 0.5rem; object-fit: cover; }
/* A gifv-style embed's real animated content (Tenor/Giphy/Klipy-style GIF
   links — see chatLog.js's downloadEmbedImages) — autoplay/loop/muted in
   the markup (chat-embeds.ejs) makes it behave like a GIF despite actually
   being a short muted video file, same as Discord's own client. */
.chat-embed-video { display: block; max-width: 100%; max-height: 220px; border-radius: 6px; margin-top: 0.5rem; }
.chat-embed-footer { color: var(--muted); font-size: 0.72rem; margin-top: 0.5rem; }

/* --- Logs tab: the search bar, the list, and the leaderboard all used to
   sit flush against the left edge with the search bar stretching full
   page width above a much narrower (width-capped) list — nothing tying
   their widths together or centering the block as a whole on wide
   screens. .logs-page-wrap now wraps all of it (flash message, toolbar,
   list+leaderboard) so it centers as one unit, the same way .settings-wrap
   centers Log Settings below its own header. --- */
.logs-page-wrap {
  max-width: 1360px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

.logs-toolbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 1rem 0 0;
  flex-wrap: wrap;
}
.logs-toolbar select { width: auto; }

/* --- Logs tab: two-column layout — the list on the left, a small
   leaderboard card on the right (see logs-*.ejs + partials/
   logs-leaderboard.ejs, routes/logs.js's buildLeaderboard()). --- */
.logs-layout {
  display: flex;
  align-items: flex-start;
  gap: 1.5rem;
  padding: 0 0 2rem;
}
.logs-layout .app-list { padding: 1rem 0 0; max-width: none; margin: 0; }
.logs-layout .empty-state { flex: 1; padding-top: 1rem; }

.logs-main { flex: 1; min-width: 0; max-width: 1000px; }

/* Not position:sticky — that pinned the sidebar near the top of the
   viewport the moment you scrolled the (much longer) log list, so its top
   edge drifted away from whatever entry row happened to be at the same
   height, reading as "the leaderboard doesn't line up with the logs".
   Plain in-flow instead: both columns are top-aligned siblings (see
   .logs-layout's align-items: flex-start above) with nothing scrolling
   independently, so the sidebar's top always stays level with the first
   log entry's top, at any scroll position.
   padding-top matches .logs-layout .app-list's own 1rem top padding above
   — without it the sidebar (no top padding of its own) starts a full 1rem
   higher than the first log row, even though both are top-aligned flex
   siblings with the same starting Y position. */
.logs-sidebar { width: 280px; flex-shrink: 0; padding-top: 1rem; }

.leaderboard-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.1rem 1.25rem 1.3rem;
}
.leaderboard-card-head {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.9rem;
}
.leaderboard-card-icon { display: inline-flex; color: var(--accent); }

.leaderboard-total { font-size: 2rem; font-weight: 800; line-height: 1; }
.leaderboard-total-label {
  color: var(--muted);
  font-size: 0.8rem;
  margin: 0.3rem 0 1.1rem;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--border);
}

.leaderboard-rows { display: flex; flex-direction: column; gap: 0.75rem; }
.leaderboard-row { display: flex; align-items: flex-start; gap: 0.55rem; }
.leaderboard-rank { color: var(--muted); font-size: 0.76rem; font-weight: 700; padding-top: 0.15rem; width: 1.3rem; flex-shrink: 0; }
.leaderboard-row-main { flex: 1; min-width: 0; }
.leaderboard-row-top { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; font-size: 0.84rem; }
.leaderboard-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.leaderboard-count { color: var(--muted); font-weight: 700; flex-shrink: 0; }
.leaderboard-bar-track { height: 4px; background: var(--panel-2); border-radius: 2px; margin-top: 0.3rem; overflow: hidden; }
.leaderboard-bar-fill { height: 100%; background: var(--accent); border-radius: 2px; }

.leaderboard-empty { color: var(--muted); font-size: 0.85rem; }

@media (max-width: 980px) {
  .logs-layout { flex-direction: column; }
  .logs-sidebar { width: 100%; }
}

/* Sits right under the leaderboard card (see logs-chat.ejs) — same button
   style used everywhere else, just stretched to the sidebar's width and
   given a little breathing room above it. */
.logs-open-archive-btn { display: flex; align-items: center; justify-content: center; gap: 0.4rem; width: 100%; margin-top: 0.85rem; box-sizing: border-box; }

/* --- Chat Log archive browser (see logs-chat-archive.ejs) — a plain list
   of past archive batches, each expandable into a live-status "View"
   panel (see public/js/logs-chat-archive.js's SSE handling). ---
   .archive-page-content adds the top breathing room .logs-page-wrap itself
   doesn't have (that's normally supplied by .logs-toolbar's own top
   padding on the other Logs pages, which this page has no equivalent of) —
   without it the search bar/card list sat flush against the tabs row
   below, corners visually "clipped" against it. */
.archive-page-content { padding-top: 1.25rem; }

.archive-search-bar { margin-bottom: 1rem; }
.archive-search-input {
  width: 100%;
  max-width: 480px;
  padding: 0.55rem 0.9rem;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
  font-size: 0.88rem;
}
.archive-search-input:focus { outline: none; border-color: var(--accent); }
.archive-search-empty { margin-top: 0; }

.archive-batch-list { display: flex; flex-direction: column; gap: 0.75rem; }
.archive-batch-card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 1rem 1.2rem; }
.archive-batch-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.archive-batch-range { font-weight: 700; }
.archive-batch-meta { color: var(--muted); font-size: 0.82rem; margin-top: 0.2rem; }
.archive-batch-panel { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); }

.archive-view-status { display: flex; flex-direction: column; gap: 0.4rem; font-size: 0.85rem; color: var(--muted); }
.archive-view-status-line { display: flex; align-items: center; gap: 0.5rem; }
.archive-view-status-line.is-current { color: var(--text); font-weight: 600; }
.archive-view-error { color: #ff9b9d; font-size: 0.85rem; }

.archive-view-fallback-notice { display: flex; align-items: center; gap: 0.5rem; color: var(--muted); font-size: 0.8rem; background: var(--panel-2, rgba(255,255,255,0.04)); border-radius: 8px; padding: 0.6rem 0.8rem; margin-top: 0.75rem; }

.archive-entry-list { display: flex; flex-direction: column; gap: 0.6rem; margin-top: 1rem; }

/* --- Single application: big, focused detail view --- */

.detail-wrap {
  max-width: 820px;
  margin: 0 auto;
  padding: 2rem 1.5rem 4rem;
}

.detail-header { display: flex; align-items: center; gap: 1.25rem; }
.detail-avatar { flex-shrink: 0; }
.detail-header h1 { font-size: 1.9rem; margin: 0 0 0.6rem; }
.detail-meta { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
.detail-submeta { color: var(--muted); font-size: 0.9rem; }
.detail-submeta code {
  background: var(--panel-2);
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  font-size: 0.85rem;
}

.qa-list { margin-top: 2rem; display: flex; flex-direction: column; gap: 1.25rem; }

.qa-block {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.5rem 1.75rem;
}

.qa-question {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 0.85rem;
  line-height: 1.4;
}

.qa-number {
  flex-shrink: 0;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  background: rgba(255, 198, 26, 0.18);
  color: var(--accent-text);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.85rem;
}

.qa-answer {
  font-size: 1.15rem;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
}

.attachment-block {
  margin-top: 1.5rem;
  padding: 1.25rem 1.75rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
}
.attachment-block label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.6rem;
}

.review-summary {
  margin-top: 1.5rem;
  padding: 1.5rem 1.75rem;
  border-radius: 14px;
  border: 1px solid var(--border);
}
.status-Approved-block { background: rgba(87, 242, 135, 0.08); border-color: rgba(87, 242, 135, 0.3); }
.status-Rejected-block { background: rgba(237, 66, 69, 0.08); border-color: rgba(237, 66, 69, 0.3); }
.review-summary-title { display: flex; align-items: center; gap: 0.5rem; font-size: 1.1rem; font-weight: 700; margin-bottom: 0.5rem; }
.review-summary-feedback { font-size: 1rem; line-height: 1.6; white-space: pre-wrap; }
.review-summary-feedback.muted { color: var(--muted); font-style: italic; }

.review-form-large {
  margin-top: 2rem;
  padding: 1.75rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
}
.review-form-large label {
  display: block;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.5rem;
}
.review-form-large textarea { min-height: 90px; font-size: 1rem; }

.actions-large { display: flex; gap: 0.85rem; margin-top: 0.5rem; }
.actions-large button { flex: 1; padding: 0.9rem 1rem; font-size: 1rem; border-radius: 10px; }
.btn-approve-large { background: var(--green); color: #06240f; }
.btn-reject-large { background: var(--red); color: #2b0505; }

.btn-ticket-large {
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.1s ease;
}
.btn-ticket-large:hover { filter: brightness(1.1); }
.btn-ticket-large:active { transform: scale(0.98); }
/* Standalone version: sits inside .review-summary (already-reviewed state)
   instead of inside .actions-large, so it needs its own sizing/spacing
   rather than inheriting flex:1 from a row of siblings. */
.btn-ticket-standalone {
  width: 100%;
  margin-top: 1.1rem;
  padding: 0.9rem 1rem;
  font-size: 1rem;
  border-radius: 10px;
}

/* --- Settings page ---
   Widened from a fixed 1440px to fill most of the viewport on real desktop
   monitors (a fixed narrow center column read as "everything's crammed into
   a small strip" on anything wider than a laptop) while still capping out
   at a sane reading width on ultrawide screens. */

.settings-wrap { max-width: min(1760px, 96vw); margin: 0 auto; padding: 2rem 1.5rem 5rem; }
.settings-wrap h1 { display: flex; align-items: center; gap: 0.6rem; font-size: 1.6rem; margin: 0 0 0.5rem; letter-spacing: -0.01em; }
.settings-intro { color: var(--muted); font-size: 0.92rem; line-height: 1.6; margin-bottom: 1.5rem; max-width: 900px; }
.settings-intro code {
  background: var(--panel-2);
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  font-size: 0.85rem;
}
.settings-error { margin-bottom: 1.5rem; white-space: pre-wrap; }

.settings-warning {
  background: rgba(250, 166, 26, 0.15);
  color: #ffc670;
  padding: 0.6rem 0.9rem;
  border-radius: 8px;
  font-size: 0.85rem;
  margin-bottom: 1.5rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* An author-stylesheet `display` rule (above) otherwise beats the browser's
   default `[hidden] { display: none }`, which would make the hidden
   attribute a no-op and show this banner even when JS never un-hides it. */
.settings-warning[hidden] { display: none; }
.settings-warning .icon { flex-shrink: 0; }

/* Small persistent note under a field, e.g. explaining a manual-ID fallback
   input when live Discord role/category data couldn't be loaded. */
.field-note { color: var(--muted); font-size: 0.78rem; margin: -0.5rem 0 0.75rem; }

/* Two-column layout: general bot messages on the left (stays put while you
   scroll), application types on the right. Stacks to one column on narrow
   screens. */
.settings-grid {
  display: grid;
  grid-template-columns: minmax(360px, 460px) 1fr;
  gap: 1.75rem;
  align-items: start;
}
@media (max-width: 980px) {
  .settings-grid { grid-template-columns: 1fr; }
  .settings-col-left { position: static !important; max-height: none !important; }
}

/* Application/ticket type cards flow into as many columns as fit — on a
   wide desktop window this puts two (sometimes three) types side by side
   instead of one long single-file column, using the space the wider
   .settings-wrap above now makes available. Each card still reads fine at
   its own natural width since .type-card-header already wraps. */
#types-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: 1.1rem;
  align-items: start;
}
#types-container > .type-card { margin-bottom: 0; }

.settings-col-left {
  position: sticky;
  top: 1rem;
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  padding-right: 0.25rem;
  /* Dark-themed scrollbar — without this, Windows/Chrome renders its default
     light scrollbar here, which shows up as a stray white line down the
     side of this column. */
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.settings-col-left::-webkit-scrollbar { width: 8px; }
.settings-col-left::-webkit-scrollbar-track { background: transparent; }
.settings-col-left::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }
.settings-col-left::-webkit-scrollbar-thumb:hover { background: var(--muted); }

.settings-col-right-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}
.settings-col-right-title { font-size: 0.95rem; font-weight: 700; color: var(--text); }
.settings-col-right-actions { display: flex; gap: 0.5rem; }
.btn-small { width: auto; padding: 0.4rem 0.8rem; font-size: 0.8rem; }

/* The page's ONE primary save/confirm action — genuinely floating over the
   page in the bottom-right corner (fixed to the viewport, not the form), so
   it's reachable without scrolling all the way down a long form. Superseded
   by .unsaved-bar right below — every page that used to have its own
   always-visible fixed Save button (Settings / Ticket Settings / Staff
   Settings / a member's "Save Roles") now registers with the one shared bar
   instead (see public/js/unsaved-bar.js), which only appears once there's
   actually something to save. Left in place in case a future one-off fixed
   action button needs the same positioning. */
.settings-actions {
  position: fixed;
  right: 1.75rem;
  bottom: 1.75rem;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 1rem;
}
.settings-actions .save-btn,
.settings-actions .btn-primary {
  width: auto;
  padding: 0.85rem 1.85rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}

/* ===========================================================================
   Shared "unsaved changes" bar (public/js/unsaved-bar.js) — the one place,
   sitewide, that a Save button now lives. Hidden until something registered
   is actually dirty, same fixed bottom-right spot .settings-actions used to
   sit in, so every page that reserved bottom padding for that already has
   room for this too.
   =========================================================================== */
.unsaved-bar {
  position: fixed;
  right: 1.75rem;
  bottom: 1.75rem;
  z-index: 200;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 0.9rem 0.75rem 1.1rem;
  border-radius: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.unsaved-bar[hidden] { display: none; }
.unsaved-bar-status { font-size: 0.85rem; color: var(--muted); white-space: nowrap; margin-right: 0.2rem; }
.unsaved-bar-save,
.unsaved-bar-revert { width: auto; padding: 0.65rem 1.25rem; }
.unsaved-bar-save:disabled,
.unsaved-bar-revert:disabled { opacity: 0.6; cursor: not-allowed; }

@keyframes unsaved-bar-enter {
  from { opacity: 0; transform: translateY(0.75rem) scale(0.96); }
  to { opacity: 1; transform: translateY(0) scale(1); }
}
.unsaved-bar-enter { animation: unsaved-bar-enter 0.28s cubic-bezier(0.2, 0.8, 0.2, 1); }

@keyframes unsaved-bar-shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); }
  40%, 60% { transform: translateX(4px); }
}
.unsaved-bar-shake { animation: unsaved-bar-shake 0.45s ease; }

/* A single card's own action button (Mark Active, Mark Retired, Mark
   Inactive, ...) — sits right at the bottom of that card, right-aligned,
   NOT fixed to the viewport (a page can have several of these at once —
   see staff-member.ejs — so only .settings-actions above, the page's one
   true primary action, actually floats). */
.card-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 1rem;
  margin-top: 1.25rem;
}
.card-actions .btn-primary {
  width: auto;
  padding: 0.75rem 1.75rem;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
}

/* ===========================================================================
   Collapsible settings sections — shared "grouped, closeable category" unit
   used on Automations, Staff Settings, Application Settings, and Ticket
   Settings (see public/js/collapsible.js). A plain <details>/<summary> pair:
   works with zero JS (native disclosure behavior), gets persistence and a
   couple of visual touches layered on top when collapsible.js runs.
   =========================================================================== */

.settings-section {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  margin-bottom: 1.1rem;
  overflow: hidden;
}
/* NOT :last-child — Staff Settings (see staff-settings.js) builds each
   section into its OWN single-purpose container (#role-mapping-container,
   #manager-roles-container, ...) instead of one shared parent, so every
   section there is trivially an only-child, which made :last-child match
   ALL of them and zero out the gap between every single card instead of
   just the actual final one. A little extra space below the true last
   section on pages where these are direct siblings is harmless — .settings-
   wrap already has generous bottom padding — so this just always applies. */

.settings-section > summary {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 1.05rem 1.4rem;
  cursor: pointer;
  list-style: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.15s ease;
}
/* Removes the browser-default disclosure triangle in both engines that draw
   one — a themed chevron (below) replaces it, in a consistent spot on every
   section regardless of browser. */
.settings-section > summary::-webkit-details-marker { display: none; }
.settings-section > summary { list-style: none; }
.settings-section > summary::marker { content: ""; }
.settings-section > summary:hover { background: var(--panel-2); }
/* Feedback the instant the pointer goes down, not on release — a plain
   `:active` covers this for a native disclosure toggle without needing any
   JS wiring. */
.settings-section > summary:active { background: var(--panel-2); }

.settings-section-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.15rem;
  height: 2.15rem;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--accent);
  flex-shrink: 0;
}
.settings-section-title-wrap {
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
.settings-section-title { font-size: 1rem; font-weight: 700; color: var(--text); letter-spacing: -0.005em; }
.settings-section-desc {
  font-size: 0.82rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-section-desc:empty { display: none; }

.settings-section-chevron {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--muted);
  transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.settings-section[open] > summary .settings-section-chevron { transform: rotate(180deg); }
.settings-section[open] > summary { border-bottom: 1px solid var(--border); }

/* Move up/down + delete, for a settings-section whose summary represents a
   reorderable/deletable item (a ticket type) rather than just a fixed
   category of fields. Sits inline in the summary, between the title and the
   chevron — each button stops the click from bubbling up to the <summary>
   itself so clicking one doesn't also toggle the section open/closed. */
.settings-section-controls {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex-shrink: 0;
}
.settings-section-controls .icon-btn:disabled { opacity: 0.4; cursor: default; }

.settings-section-body {
  padding: 1.1rem 1.4rem 1.4rem;
  animation: settings-section-reveal 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes settings-section-reveal {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .settings-section-body { animation: none; }
  .settings-section-chevron { transition: none; }
  .settings-section > summary { transition: none; }
}

/* Compact sections (a channel picker, a status button — nothing that needs
   the full page width to read comfortably) sit two-up on a wide screen
   instead of each claiming a full row of mostly-empty space. */
.settings-section-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  gap: 1.1rem;
  align-items: start;
  margin-bottom: 1.1rem;
}
.settings-section-grid > .settings-section { margin-bottom: 0; }

.settings-section-expand-controls {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  margin: -0.5rem 0 1rem;
}

/* Log Settings' DM message live preview — one small card per case type
   (ban/kick/mute/unmute/unban/warn) showing exactly what that DM would say
   right now, re-rendered on every keystroke in the template textarea (see
   public/js/logs-settings.js). */
.dm-preview-list { display: flex; flex-direction: column; gap: 0.6rem; }
.dm-preview-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.65rem 0.85rem;
  font-size: 0.85rem;
  line-height: 1.5;
}
/* The per-type on/off switch (public/js/logs-settings.js never touches
   this — see logs-settings.ejs) — nudged down a couple px so it sits level
   with the row's label text instead of its own vertical center. */
.dm-preview-row-toggle { flex-shrink: 0; margin-top: 0.15rem; }
.dm-preview-row-body { flex: 1; min-width: 0; }
.dm-preview-row-label {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.3rem;
}
.dm-preview-row-text { white-space: pre-line; }
.dm-preview-row-text strong { color: var(--text); }
/* A type whose switch is off still shows its preview text (so you can see
   what it WOULD say if you turned it back on) but visibly dims to read as
   "off" at a glance. */
.dm-preview-row:has(.dm-preview-row-toggle input:not(:checked)) { opacity: 0.55; }

/* General messages panel — now just a flex column of .settings-section
   cards (see above) rather than one continuous unbroken block of fields. */
.general-panel { display: flex; flex-direction: column; }

.type-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.5rem;
  margin-bottom: 1.5rem;
}

.type-card-header {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}
.type-card-body { margin-top: 1.25rem; }
.type-card-controls { display: flex; gap: 0.3rem; }
.type-card-toggle {
  font-size: 1rem;
  min-width: 2.1rem;
  text-align: center;
  flex-shrink: 0;
}
.type-card-header .type-emoji { width: 4.5rem; text-align: center; margin: 0; flex-shrink: 0; }
.type-card-header .type-label { flex: 1; min-width: 160px; margin: 0; font-weight: 700; }
.type-card-header .type-button-style,
.type-card-header .cs-wrapper { width: auto; margin: 0; flex-shrink: 0; }
.type-card-header .cs-wrapper .cs-trigger { width: auto; }
.type-card-header .icon-btn-danger { flex-shrink: 0; }

.icon-btn {
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.4rem 0.6rem;
  font-size: 0.85rem;
  line-height: 1;
}
.icon-btn:hover { color: var(--text); border-color: var(--accent); }
.icon-btn-danger:hover { color: #ff9b9d; border-color: var(--red); }

.field-group { margin-bottom: 1rem; }
/* Log Settings' "Last archived: ..." caption + its manual Archive now
   button (see logs-settings.ejs) — same row when there's room, wraps
   cleanly on narrow screens since the caption is display:block on its own. */
.archive-now-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
.archive-now-row .automation-card-desc { margin: 0; }
.archive-now-row form { margin: 0; }
.field-group > label {
  display: block;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.field-group .muted { text-transform: none; letter-spacing: normal; font-size: 0.78rem; }
/* Labels with a tooltip (see settings.js's fieldGroup helper) get a dotted
   underline so it's discoverable that hovering shows more info, without
   that info being visible text taking up space all the time. */
.field-group > label.has-hint {
  width: fit-content;
  cursor: help;
  border-bottom: 1px dotted var(--muted);
}
/* Flex COLUMN, not row — a checkbox-field can optionally carry a short
   description paragraph underneath it (see logs-settings.ejs's "Log chat
   messages"), and the old row layout's align-items: center vertically
   centered the checkbox against the full height of that (often multi-line)
   paragraph instead of lining it up with the first line of text, which is
   what actually looked "off". A checkbox-field with no description is a
   single child either way, so this is a no-op for every other page using
   this class (automations.ejs). */
.field-group.checkbox-field { display: flex; flex-direction: column; align-items: flex-start; }
.field-group.checkbox-field label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-transform: none;
  color: var(--text);
  font-size: 0.9rem;
  margin-top: 0.5rem;
}
/* Indented roughly under the label's own text (not the checkbox) — the
   checkbox is ~1rem wide plus the label's 0.5rem gap. Own margin instead of
   the base .automation-card-desc's (which assumes it's the whole
   .field-group's last element with a full 1.1rem to the NEXT field-group,
   double-counted here since .field-group already adds its own 1rem). */
.field-group.checkbox-field .automation-card-desc { margin: 0.35rem 0 0 1.5rem; }

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.field-row .field-group { margin-bottom: 1rem; }

.char-count { color: var(--muted); font-size: 0.72rem; margin-left: 0.4rem; text-transform: none; }

/* Shared "off by default, check the box to reveal more fields" box —
   used for the file-upload settings and the optional Requirements
   button. Same treatment either way so the interaction (check it, more
   fields appear right below) only has to be learned once. */
.toggle-group {
  background: var(--panel-2);
  border-radius: 10px;
  padding: 0.9rem 1rem;
}
.toggle-group > label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-transform: none;
  color: var(--text);
  font-size: 0.9rem;
  font-weight: 600;
}
.toggle-details { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); }

/* A generic divider with real breathing room on either side — used both
   between the general-messages panel and the type list on the Application
   Settings overview, and between each section (Basics / Message /
   Behavior / Questions) on a single type's own editor page below. */
.type-card-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 1.85rem 0;
}

/* ===========================================================================
   Application Settings — overview page (public/js/settings.js). A narrower,
   single centered column: general messages up top (still the same
   collapsible-section panel as before), then a plain scannable list of
   application types below, each one a full row linking out to its own
   editor page rather than an inline card.
   =========================================================================== */
.settings-wrap-narrow { max-width: 900px; }

.type-overview-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.1rem;
}
.type-overview-heading { font-size: 1.15rem; font-weight: 700; margin: 0; }

.type-row {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  margin-bottom: 0.8rem;
  transition: border-color 0.15s ease;
}
.type-row:hover { border-color: var(--accent); }

.type-row-main {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex: 1;
  min-width: 0;
  padding: 1.1rem 1.25rem;
  color: inherit;
  text-decoration: none;
}
.type-row-emoji { font-size: 1.5rem; flex-shrink: 0; width: 2rem; text-align: center; }

/* A custom Discord emoji (<:name:id>), rendered by window.EmojiRender
   (emoji-render.js) as an <img> instead of Discord's raw markup showing as
   literal text — sized relative to whatever font-size its container
   already uses for a plain Unicode emoji (.type-row-emoji, .mb-prev-btn-
   emoji, .mb-prev-poll-answer), so it reads as roughly the same size
   either way. */
.custom-emoji-icon { width: 1.2em; height: 1.2em; vertical-align: -0.2em; object-fit: contain; }
.type-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }
.type-row-title-line { display: flex; align-items: center; gap: 0.6rem; }
.type-row-title { font-weight: 700; font-size: 1rem; }
.type-row-count {
  flex-shrink: 0;
  font-size: 0.72rem;
  color: var(--muted);
  background: var(--panel-2);
  border-radius: 999px;
  padding: 0.15rem 0.55rem;
}
.type-row-desc {
  color: var(--muted);
  font-size: 0.85rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.type-row-controls {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex-shrink: 0;
  padding: 0 1rem;
  border-left: 1px solid var(--border);
}

/* ===========================================================================
   Application Settings — a single type's own editor page
   (public/js/settings-type.js). Four clearly separate full-width sections
   (Basics / Message / Behavior / Questions), each with a heading and a
   short one-line explanation of what it affects, so it's never ambiguous
   which part of the page changes what the applicant sees.
   =========================================================================== */
.type-page-section { position: relative; }
.type-page-section-heading { font-size: 1.15rem; font-weight: 700; margin: 0 0 0.25rem; }
.type-page-section-sub { color: var(--muted); font-size: 0.85rem; margin: 0 0 1.25rem; }

.type-page-basics-row { grid-template-columns: 6rem 1fr 12rem; }
.type-page-delete { margin-top: 0.5rem; }

.question-row {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.1rem 1.25rem;
  margin-bottom: 0.9rem;
  position: relative;
}
.question-row-controls { display: flex; gap: 0.3rem; justify-content: flex-end; margin-bottom: 0.6rem; }

.add-question-btn { margin-top: 0.25rem; }

/* Discord-formatting toolbar + preview, attached under a textarea */
.formatted-field { margin-bottom: 0; }
.formatting-toolbar { display: flex; gap: 0.3rem; margin-bottom: 0.4rem; }
.toolbar-btn {
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.3rem 0.6rem;
  font-size: 0.78rem;
  font-weight: 700;
  min-width: 2rem;
}
.toolbar-btn:hover { color: var(--text); border-color: var(--accent); }

.md-preview {
  margin-top: 0.5rem;
  padding: 0.65rem 0.85rem;
  background: var(--bg);
  border: 1px dashed var(--border);
  border-radius: 8px;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text);
  min-height: 1.4rem;
}
.md-preview code {
  background: var(--panel-2);
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  font-size: 0.85em;
}
.md-preview code.md-block { display: block; padding: 0.5rem; white-space: pre-wrap; margin-top: 0.3rem; }
.md-preview .md-spoiler { background: #202225; color: #202225; border-radius: 3px; padding: 0 0.2rem; }
.md-preview .md-spoiler:hover { color: var(--text); }
.md-preview .md-empty { color: var(--muted); font-style: italic; }
.md-preview .md-heading { display: block; font-size: 1.1em; margin-bottom: 0.2rem; }
.md-preview .md-subtext { display: block; color: var(--muted); font-size: 0.82em; margin-top: 0.2rem; }

/* --- Heading markdown (#/##/###) outside .md-preview — the Msg Builder /
   Automations "mb-prev-*" message previews (see msg-builder.js's and
   automations.js's renderMarkdown()). Discord's Text Display components
   render these noticeably larger than plain/bold text, same as regular
   message content — without differentiated sizes here the preview
   understates how big a "# Title" actually looks once sent, which is
   exactly why a Welcome Message title can look small next to its banner
   image in the dashboard preview even though it won't in Discord. Scoped
   by level (unlike .md-preview's single flat size above) since a real "#"
   heading is meaningfully bigger than "###". --- */
.md-heading { font-weight: 700; }
.md-h1 { font-size: 1.5em; line-height: 1.25; }
.md-h2 { font-size: 1.25em; line-height: 1.3; }
.md-h3 { font-size: 1.0625em; line-height: 1.3; }

/* --- Ticket detail / transcript page --- */

.muted { color: var(--muted); }

.ticket-meta-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1rem;
  margin-top: 1.5rem;
}
.ticket-meta-item {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1rem 1.25rem;
}
.ticket-meta-item label {
  display: block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.ticket-meta-item > div { font-size: 0.95rem; word-break: break-word; }
.ticket-meta-sub { margin-top: 0.35rem; font-size: 0.78rem; color: var(--muted); }

.transcript-heading { display: flex; align-items: center; gap: 0.55rem; font-size: 1.15rem; margin: 2rem 0 1rem; }

.transcript {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding-bottom: 3rem;
}
.transcript-msg {
  display: flex;
  gap: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.9rem 1.1rem;
}
.transcript-msg-avatar { flex-shrink: 0; margin-top: 0.1rem; }
.transcript-msg-body { min-width: 0; flex: 1; }
.transcript-msg-header {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin-bottom: 0.4rem;
}
.transcript-msg-author { font-weight: 700; font-size: 0.9rem; }
.transcript-msg-time { color: var(--muted); font-size: 0.75rem; }
.transcript-msg-content { font-size: 0.92rem; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
.transcript-attachments { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.6rem; }
.transcript-attachment {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.8rem;
  color: var(--accent-text);
  background: rgba(255, 198, 26, 0.12);
  padding: 0.3rem 0.65rem;
  border-radius: 8px;
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.transcript-attachment:hover { background: rgba(255, 198, 26, 0.2); }

/* --- Msg Builder placeholder (kept in case any older link still points at
   the stub — the real overview page below has fully replaced it) --- */

.stub-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  padding: 2rem 1.5rem;
}
.stub-card {
  text-align: center;
  max-width: 420px;
}
.stub-icon { display: flex; align-items: center; justify-content: center; color: var(--muted); margin-bottom: 1rem; }
.stub-card h2 { margin: 0 0 0.6rem; font-size: 1.3rem; }
.stub-card p { color: var(--muted); font-size: 0.92rem; line-height: 1.6; margin: 0; }

/* ===========================================================================
   Msg Builder — overview list (msg-builder.ejs)
   =========================================================================== */

.status-Sent { background: rgba(87, 242, 135, 0.18); color: var(--green); }
.status-Draft { background: rgba(154, 162, 177, 0.2); color: var(--muted); }

.msg-row-channel {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-weight: 700;
  font-size: 1rem;
}
.msg-row-channel-unset { color: var(--muted); font-weight: 600; font-size: 0.92rem; }
.msg-row-delete { flex-shrink: 0; }

/* Zero-state hero — unlike Dashboard/Tickets, this page has no status tabs
   sitting above the list to give an empty result some visual weight, so a
   plain .empty-state line here just reads as a big broken void. This gives
   it the same "icon + heading + body + CTA" treatment as the old Msg
   Builder placeholder page (.stub-*, now unused) had. */
.msg-empty-wrap {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 60vh;
  padding: 2rem 1.5rem;
}
.msg-empty-card { text-align: center; max-width: 440px; }
.msg-empty-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4.5rem;
  height: 4.5rem;
  margin: 0 auto 1.25rem;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
}
.msg-empty-card h2 { margin: 0 0 0.6rem; font-size: 1.3rem; }
.msg-empty-card p { color: var(--muted); font-size: 0.92rem; line-height: 1.6; margin: 0 0 1.5rem; }
.msg-empty-card .btn { width: auto; padding: 0.75rem 1.5rem; }

/* ===========================================================================
   Msg Builder — compose/edit page (msg-builder-compose.ejs, driven almost
   entirely by public/js/msg-builder.js — the DOM it builds is what these
   classes target). Two-column layout: editor on the left, live Discord-style
   preview pinned on the right; stacks to one column on narrow screens.
   =========================================================================== */

.mb-save-status { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.mb-save-status-unsaved { color: var(--yellow); }
.mb-save-status-saving, .mb-save-status-sending { color: var(--muted); }
.mb-save-status-saved { color: var(--green); }
.mb-save-status-sent { color: var(--green); font-weight: 700; }
.mb-save-status-scheduled { color: var(--accent); font-weight: 700; }

.mb-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(360px, 460px);
  gap: 1.5rem;
  align-items: start;
  max-width: 1600px;
  margin: 0 auto;
  padding: 1.5rem 1.5rem 4rem;
}
@media (max-width: 1100px) {
  .mb-layout { grid-template-columns: 1fr; }
  .mb-preview-pane { position: static !important; max-height: none !important; }
}

.mb-editor { display: flex; flex-direction: column; gap: 1.25rem; min-width: 0; }

/* --- Channel picker --- */
.mb-channel-field {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.1rem 1.25rem;
}
.mb-channel-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.5rem;
}
.mb-channel-field select, .mb-channel-field input, .mb-channel-field .cs-wrapper { margin-bottom: 0; }
.mb-channel-field .field-note { margin: 0.5rem 0 0; }

/* --- Message tab strip (batching) --- */
.mb-message-tabs { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.mb-message-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--panel);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem 0.85rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.mb-message-tab:hover { color: var(--text); border-color: var(--accent); }
.mb-message-tab.active { color: var(--accent-fg); background: var(--accent); border-color: var(--accent); }
.mb-message-tab-remove {
  background: transparent;
  border: none;
  padding: 0;
  margin: 0 -0.15rem 0 0.1rem;
  color: inherit;
  opacity: 0.7;
  display: inline-flex;
}
.mb-message-tab-remove:hover { opacity: 1; }
.mb-message-tab-add {
  padding: 0.5rem 0.7rem;
  color: var(--accent);
  border-style: dashed;
}
.mb-message-tab-add:hover { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }

.mb-message-panel { display: flex; flex-direction: column; gap: 1.25rem; }
/* See .settings-warning[hidden] above for why this override is needed
   whenever a class sets its own `display` and JS also toggles [hidden]. */
.mb-message-panel[hidden] { display: none; }

/* --- Add-row / add-section buttons (Add Embed, Add Action Row, Add Poll,
   Add Message — all the same "full-width ghost row with a trailing
   counter" shape) --- */
.mb-add-section-btn, .mb-add-row-btn {
  width: 100%;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px dashed var(--border);
  border-radius: 10px;
  padding: 0.65rem 1rem;
  font-size: 0.85rem;
  font-weight: 600;
  justify-content: flex-start;
}
.mb-add-section-btn:hover, .mb-add-row-btn:hover { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
.mb-add-row-btn { padding: 0.5rem 0.85rem; margin-top: 0.5rem; }
.mb-flex-spacer { flex: 1; }
.mb-counter { color: var(--muted); font-size: 0.78rem; font-weight: 600; }
.mb-char-counter { color: var(--muted); font-size: 0.72rem; font-weight: 600; margin-left: auto; }

.mb-add-message-btn { border-color: var(--accent); color: var(--accent); }
.mb-add-message-btn:hover { background: var(--accent-soft); }

/* --- Format toggle (Embed / Components V2) --- */
.mb-format-toggle {
  display: inline-flex;
  gap: 0.25rem;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.25rem;
}
.mb-format-tab {
  background: transparent;
  color: var(--muted);
  border: none;
  border-radius: 9px;
  padding: 0.5rem 1rem;
  font-size: 0.85rem;
  font-weight: 700;
}
.mb-format-tab.active { background: var(--accent); color: var(--accent-fg); }
.mb-format-tab:not(.active):hover { color: var(--text); }
.mb-format-caption { color: var(--muted); font-size: 0.78rem; margin: -0.75rem 0 0; line-height: 1.5; }
.mb-format-section { display: flex; flex-direction: column; gap: 1rem; }
.mb-format-section[hidden] { display: none; }

/* --- Message content box (legacy `content`) --- */
.mb-content-box {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.1rem 1.25rem;
}
.mb-content-box-heading {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.6rem;
}
.mb-content-box textarea { margin-bottom: 0; }
.mb-content-box-footer { display: flex; justify-content: flex-end; margin-top: 0.3rem; }

.formatting-toolbar .toolbar-sep { width: 1px; background: var(--border); margin: 0.2rem 0.2rem; }

/* --- Embed cards --- */
.mb-embeds-list { display: flex; flex-direction: column; gap: 1rem; }
/* An empty flex-column list still reserves a full `gap` on both sides of
   itself among its own siblings (flex `gap` applies between every pair of
   adjacent items regardless of rendered size) — so a fresh/empty builder
   with zero embeds/buttons/etc. showed doubled whitespace around these
   list containers. Collapsing them out of flow when empty fixes it. */
.mb-embeds-list:empty { display: none; }
.mb-embed-card {
  display: flex;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.mb-embed-colorbar { width: 5px; flex-shrink: 0; background: var(--accent); }
/* Flex-column wrapper holding header above body — see makeEmbedCard's
   BUG FIX comment: header must live outside the collapsible `body` so the
   toggle button that reopens the card is never hidden along with it. */
.mb-embed-card-inner { display: flex; flex-direction: column; flex: 1; min-width: 0; }
/* margin-top lives on the body (not margin-bottom on the header) so a
   collapsed card — body hidden via `hidden` → display:none — doesn't leave
   this gap dangling below the header with nothing below it to justify it.
   A display:none element contributes nothing to layout, including its own
   margin, so this gap disappears automatically on collapse instead of
   needing a `:not([hidden])` guard. Same fix applied to the action-row/
   CV2/poll cards below. */
.mb-embed-body { padding: 0 1.25rem 1.1rem; margin-top: 1rem; }
.mb-embed-body[hidden] { display: none; }
.mb-embed-card-header { display: flex; align-items: center; gap: 0.6rem; padding: 1.1rem 1.25rem 0; }
.mb-embed-card-title { display: flex; align-items: center; gap: 0.4rem; font-weight: 700; font-size: 0.9rem; }
.mb-embed-card-header-spacer { flex: 1; }
.mb-embed-toggle { flex-shrink: 0; }

.field-group-heading {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 0.4rem;
}
.field-group-heading .mb-char-counter, .field-group-heading .mb-counter { margin-left: auto; }

.mb-inline-row { display: flex; gap: 0.75rem; margin-bottom: 1rem; }
.mb-inline-row > * { flex: 1; min-width: 0; }
.mb-inline-field { display: flex; align-items: center; gap: 0.5rem; color: var(--muted); }
.mb-inline-field input { margin-bottom: 0; }
.mb-half { flex: 1; }

/* align-items: stretch (not the more typical `center`) so the color swatch
   and the hex text field next to it come out exactly the same height as
   each other instead of two different fixed heights (swatch: 2.6rem;
   text input: whatever the sitewide input padding/font-size worked out to,
   a couple px shorter) sitting side by side looking visibly mismatched. */
.mb-color-row { display: flex; align-items: stretch; gap: 0.6rem; }
/* A plain <input type="color"> has no theming hook of its own — by default
   it renders with the browser's native white/gray padded frame around the
   swatch, which stands out against this dark UI ("the accent color thingy
   is kinda messed up and all white"). border/background here recolor that
   frame; the ::-webkit-/::-moz- pseudo-elements below reach inside it to
   remove the native padding and round the swatch itself, since a plain CSS
   border/padding on the <input> alone can't touch the swatch's own shape
   in Chromium or Firefox. No explicit height — stretch (above) matches it
   to the hex input's real rendered height instead. */
.mb-color-row input[type="color"] {
  width: 2.6rem;
  padding: 0.2rem;
  flex-shrink: 0;
  margin-bottom: 0;
  cursor: pointer;
  border: 1.5px solid var(--border);
  border-radius: 8px;
  background: var(--panel-2);
}
.mb-color-row input[type="color"]:hover { border-color: var(--accent); }
.mb-color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.mb-color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 5px; }
.mb-color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 5px; }
.mb-color-hex { margin-bottom: 0; font-family: monospace; }

.mb-fields-list { display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 0.6rem; }
.mb-field-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.6rem 0.75rem;
}
.mb-field-row input { margin-bottom: 0; flex: 1; min-width: 0; }
.mb-inline-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--muted);
  font-size: 0.82rem;
  white-space: nowrap;
  flex-shrink: 0;
}
.mb-radio-row { display: flex; gap: 1rem; margin-bottom: 0.6rem; flex-wrap: wrap; }
.mb-radio-label { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; color: var(--text); }
.mb-radio-label-block { align-items: flex-start; gap: 0.6rem; padding: 0.5rem 0; cursor: pointer; }
.mb-radio-label-block input { margin-top: 0.2rem; }
.mb-radio-label-block strong { font-size: 0.88rem; }

.mb-emoji-input { max-width: 4.5rem; text-align: center; }
.mb-emoji-field { max-width: 5.5rem; flex: 0 0 auto !important; }
.mb-flex-2 { flex: 2 !important; }

/* --- Button editor (legacy action rows + CV2 action rows/section
   accessory all share this) --- */
.mb-buttons-wrap { display: flex; flex-direction: column; gap: 0.75rem; }
.mb-buttons-wrap[hidden] { display: none; }
.mb-buttons-wrap:empty { display: none; }
/* Only the legacy action-row card (makeActionRowCard) has buttonsWrap as a
   *direct* sibling of its header — that's the only place this gap belongs.
   Inside a CV2 action_row node (makeComponentCard), buttonsWrap instead
   sits inside .mb-cv2-card-body, which already gets its own margin-top
   below; adding it here too would double that gap for CV2 action rows. */
.mb-actionrow-card > .mb-buttons-wrap { margin-top: 1rem; }
.mb-actionrow-card > .mb-buttons-wrap:empty { margin-top: 0; }
.mb-button-editor {
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.85rem 1rem;
  position: relative;
}
.mb-button-editor .field-group { margin-bottom: 0.6rem; }
.mb-button-editor .field-group:last-of-type { margin-bottom: 0; }
.mb-button-delete { position: absolute; top: 0.6rem; right: 0.6rem; }

.mb-button-action {
  margin-top: 0.2rem;
  padding-top: 0.75rem;
  border-top: 1px dashed var(--border);
}
.mb-button-action[hidden] { display: none; }
.mb-action-visibility { color: var(--muted); line-height: 1.4; }
/* .mb-inline-toggle sets its own `display: inline-flex`, which — same
   [hidden] gotcha as .settings-warning/.mb-format-section above — beats the
   browser's default [hidden]{display:none} unless overridden explicitly.
   Only this row is ever toggled via .hidden; the other .mb-inline-toggle
   uses in this file are always visible, so this is scoped to just this
   class rather than changing .mb-inline-toggle[hidden] globally. */
.mb-action-visibility[hidden] { display: none; }

.mb-actionrow-card, .mb-poll-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.1rem 1.25rem;
}
/* No margin-bottom here — see the .mb-embed-body comment above for why
   that gap has to live on the collapsible body (poll: .mb-poll-card-body,
   below) instead of the always-visible header. */
.mb-actionrow-header { display: flex; align-items: center; gap: 0.6rem; }
.mb-locked-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--muted);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.15rem 0.5rem;
}
.mb-poll-card-body { margin-top: 1rem; }
.mb-actionrows-list, .mb-poll-wrap { display: flex; flex-direction: column; gap: 0.9rem; }
.mb-actionrows-list:empty, .mb-poll-wrap:empty { display: none; }

/* --- Poll editor --- */
.mb-poll-answers { display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 0.5rem; }
.mb-poll-answers:empty { display: none; margin-bottom: 0; }
.mb-poll-answer-row { display: flex; align-items: center; gap: 0.5rem; }
.mb-poll-answer-row input { margin-bottom: 0; }
.mb-poll-answer-row .mb-emoji-input { flex-shrink: 0; }
.mb-poll-answer-row input:not(.mb-emoji-input) { flex: 1; min-width: 0; }

/* --- Components V2 --- */
.mb-info-banner {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--accent-soft);
  color: var(--accent-text);
  padding: 0.6rem 0.9rem;
  border-radius: 10px;
  font-size: 0.82rem;
  margin-bottom: 1rem;
}
.mb-cv2-header-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
.mb-cv2-header { display: flex; align-items: center; gap: 0.4rem; font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }

.mb-cv2-tree { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 0.75rem; }
.mb-cv2-tree:empty { display: none; margin-bottom: 0; }
.mb-cv2-children { display: flex; flex-direction: column; gap: 0.75rem; margin: 0.9rem 0; padding-left: 1rem; border-left: 2px solid var(--border); }
.mb-cv2-children:empty { display: none; margin: 0; }

.mb-cv2-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1.1rem 1.25rem;
}
.mb-cv2-card-body { margin-top: 1rem; }
.mb-cv2-card-body textarea { margin-bottom: 0; }
.mb-cv2-card-container { border-color: rgba(255, 198, 26, 0.35); }

.mb-cv2-add-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
  gap: 0.6rem;
}
.mb-cv2-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px dashed var(--border);
  border-radius: 10px;
  padding: 0.8rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  text-align: center;
}
.mb-cv2-tile:hover { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
.mb-cv2-tile-icon { color: var(--accent); }

.mb-media-items, .mb-section-texts { display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 0.6rem; }
.mb-media-items:empty, .mb-section-texts:empty { display: none; margin-bottom: 0; }
.mb-media-item-row { display: flex; align-items: center; gap: 0.5rem; }
.mb-media-item-row input { margin-bottom: 0; }
.mb-media-item-row input:first-child { flex: 2; min-width: 0; }
.mb-media-item-row input:nth-child(2) { flex: 1.5; min-width: 0; }

/* --- Drag handle for the Welcome Message's reorderable image variations
   list — see automations.js's makeImageRow(). Plain character + native
   HTML5 drag-and-drop rather than a library — one drag interaction, not
   worth a dependency for. --- */
.drag-handle {
  cursor: grab;
  color: var(--muted);
  font-size: 0.95rem;
  line-height: 1;
  padding: 0 0.15rem;
  user-select: none;
  flex-shrink: 0;
}
.drag-handle:active { cursor: grabbing; }
.mb-media-item-row.dragging { opacity: 0.4; }
.mb-media-item-row.drag-over { border-top: 2px solid var(--accent); }

/* --- Welcome Message's image display mode toggle — sits below the bordered
   .mb-preview-card as its own block (see initWelcomeEditor()), not inside
   the preview card itself. Same pill shape as .tab above, scoped
   separately since it lives in the preview column rather than the
   page-level nav. --- */
.wim-toggle { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.wim-btn {
  padding: 0.35rem 0.8rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}
.wim-btn:hover { color: var(--text); border-color: var(--accent); }
.wim-btn.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.wim-note { margin-top: 0.4rem; }
.mb-section-text-row { display: flex; align-items: flex-start; gap: 0.5rem; }
.mb-section-text-row textarea { margin-bottom: 0; flex: 1; }
.mb-accessory-fields { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.6rem; }
.mb-accessory-fields input { margin-bottom: 0; }

/* --- Notification settings (batch-level, collapsible) --- */
.mb-notify-settings {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  overflow: hidden;
}
.mb-collapsible-header {
  width: 100%;
  background: transparent;
  color: var(--text);
  border: none;
  border-radius: 0;
  padding: 1rem 1.25rem;
  font-size: 0.92rem;
  font-weight: 700;
  justify-content: flex-start;
}
.mb-collapsible-header:hover { background: var(--panel-2); }
.mb-notify-body { padding: 0 1.25rem 1.1rem; display: flex; flex-direction: column; gap: 1rem; }
.mb-notify-body[hidden] { display: none; }

.mb-switch-row { display: flex; align-items: flex-start; gap: 0.75rem; cursor: pointer; padding: 0.25rem 0; }
.mb-switch-row strong { font-size: 0.88rem; }
.mb-switch-row input.mb-switch { margin-top: 0.15rem; }
/* .field-note's sitewide -0.5rem top margin is tuned to sit snug right
   under a <label> (which has its own margin-bottom to cancel it out) —
   here it instead sits directly under a bare <strong>/heading with no
   such margin to offset, so that same -0.5rem just yanked the description
   text up tight against the title above it instead of leaving a normal
   gap. Overriding it back to a small positive gap in both of Notification
   Settings' two spots that use this pattern (the switch row here, and the
   "Add a mention" heading below). */
.mb-switch-row .field-note { margin: 0.2rem 0 0; }

/* --- "Add a mention" — Notification Settings' mention picker buttons.
   Clicking one opens a modal (.mb-modal-*) to pick who gets pinged, then
   inserts the real Discord mention syntax into the open message's content
   — see insertMentionAtCursor() in msg-builder.js for why it has to work
   this way (Discord only pings from literal mention text in content). --- */
.mb-mention-section { display: flex; flex-direction: column; gap: 0.5rem; }
/* Same .field-note-after-a-heading-not-a-label fix as .mb-switch-row above
   — .mb-mention-section already puts 0.5rem between every child via its
   own `gap`, so the note just needs to stop fighting that with its own
   negative margin, not add extra space on top of it. */
.mb-mention-section .field-note { margin: 0; }
.mb-mention-btn-row { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.mb-mention-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 0.5rem 0.85rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.mb-mention-btn:hover { border-color: var(--accent); color: var(--accent); }
.mb-mention-flash {
  background: rgba(237, 66, 69, 0.15);
  color: #ff9b9d;
  padding: 0.5rem 0.75rem;
  border-radius: 8px;
  font-size: 0.82rem;
}

/* --- Resolved @mention pill / custom emoji in the live preview — see
   resolveMentionsHtml() in mention-autocomplete.js (shared by every page's
   renderMarkdown), which turns raw <@id>/<@&id>/<#id>/<:name:id>/
   @everyone/@here syntax into these friendly spans so the preview reads
   like real Discord instead of showing raw numeric ids. --- */
.mb-prev-mention {
  background: rgba(255, 198, 26, 0.3);
  color: var(--accent-text);
  border-radius: 4px;
  padding: 0 0.2rem;
  font-weight: 600;
}
.mb-prev-emoji {
  width: 1.375em;
  height: 1.375em;
  vertical-align: bottom;
  object-fit: contain;
}

/* --- Mention picker modal (Users / Roles / @everyone-@here) --- */
.mb-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 7, 10, 0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 1.5rem;
}
.mb-modal-panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--shadow);
  width: 100%;
  max-width: 420px;
  max-height: min(32rem, 85vh);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.mb-modal-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 1rem 1.1rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.mb-modal-title { font-weight: 700; font-size: 0.95rem; }
.mb-modal-note { margin: 0.75rem 1.1rem 0; }
/* The bare `.mb-modal-search` class selector loses a specificity fight
   with the sitewide `input[type="text"] { margin-bottom: 0.75rem; }` rule
   (an attribute selector + a type selector out-specifies a lone class
   selector), so its own attempt to zero out the bottom margin here was
   silently overridden — that 0.75rem clashed with `.mb-modal-list`'s own
   top padding as the list's first row's hover/selection background, with
   no divider between the two, giving the impression the row was creeping
   up into the search box. Qualifying the selector with the element type
   (`input.mb-modal-search`) matches that rule's specificity exactly, and
   this rule appears later in the file, so it wins the tie deterministically
   instead of depending on which rule happens to have one extra selector
   part. A visible border + guaranteed top margin on .mb-modal-list (below)
   makes the boundary unambiguous either way. */
input.mb-modal-search { margin: 0.9rem 1.1rem 1rem; width: calc(100% - 2.2rem); }
.mb-modal-list {
  overflow-y: auto;
  padding: 0.6rem 0.6rem;
  margin-top: 0.15rem;
  border-top: 1px solid var(--border);
  flex: 1;
  min-height: 3rem;
}
.mb-modal-empty { padding: 0.75rem 0.5rem; }
/* min-height + line-height + no-wrap keep every row the same height
   regardless of content — without this, a longer name (e.g. "Trial
   Moderator") could wrap to a second line while shorter ones stayed on
   one, and role names with unusual Unicode symbols/emoji prefixes (a
   common real-world case — custom role name decorations) can render with
   a taller line-height than plain text in some fonts. Either on its own
   made row heights inconsistent down the list, reading as "spacing is
   messed up" even though the padding itself was uniform. */
.mb-modal-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 1.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  font-size: 0.87rem;
  line-height: 1.3;
  cursor: pointer;
}
.mb-modal-row:hover { background: var(--panel-2); }
.mb-modal-row input { margin: 0; flex-shrink: 0; }
.mb-modal-row span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mb-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  padding: 0.9rem 1.1rem;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
}

/* --- Actions footer (Save as Template / Save Draft / Send Message) — same
   lean, un-boxed treatment as .settings-actions above: just the buttons
   themselves at the bottom-right, no full-width bordered bar. --- */
.mb-actions-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0;
  flex-wrap: wrap;
}
.mb-actions-footer-left, .mb-actions-footer-right {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.mb-actions-footer .btn, .mb-actions-footer .btn-ghost, .mb-send-btn {
  width: auto;
  padding: 0.7rem 1.4rem;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
}

/* Schedule-for-later — inline note showing the current scheduled time next
   to the Save as Template button, and the popover datetime picker that
   appears when "Schedule…" is clicked. See msg-builder.js's
   renderFooterScheduleInfo()/openSchedulePanel(). */
.mb-schedule-note {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.85rem;
  color: var(--muted);
}
.mb-schedule-note-text {
  display: inline-flex;
  align-items: center;
}
.mb-schedule-panel {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  order: 3;
  padding: 0.75rem;
  margin-top: 0.25rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  justify-content: flex-end;
}
.mb-schedule-panel input[type="datetime-local"] {
  /* flex: 0 0 auto with no width left this at the browser's own intrinsic
     default for a datetime-local field, which reads noticeably smaller/
     cramped than every other input in the app (all of which get width:
     100% + this same padding/font-size from the sitewide input rule) —
     min-width keeps it from ever shrinking below a size that comfortably
     fits the full date+time text, and the padding/font-size below now
     matches the sitewide input styling instead of its own smaller values. */
  flex: 0 1 auto;
  min-width: 11rem;
  padding: 0.6rem 0.75rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
}

/* ===========================================================================
   Msg Builder — live preview pane (Discord-style bubble rendering)
   =========================================================================== */

.mb-preview-pane {
  position: sticky;
  top: 1rem;
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  background: #313338;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 1rem;
  color: #dbdee1;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.mb-preview-pane::-webkit-scrollbar { width: 8px; }
.mb-preview-pane::-webkit-scrollbar-track { background: transparent; }
.mb-preview-pane::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }

.mb-preview-tab {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #949ba4;
  margin-bottom: 0.9rem;
}
.mb-preview-scroll { display: flex; flex-direction: column; gap: 1.1rem; }

.mb-prev-message { font-size: 0.92rem; line-height: 1.45; }
.mb-prev-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.4rem; }
/* Either a <div> wrapping a fallback icon (bot avatar not loaded yet) or,
   once mention-autocomplete.js's guild-data fetch resolves the bot's real
   avatar, an <img> directly carrying this class — object-fit keeps that
   image a clean cropped circle instead of stretching to fill it. */
.mb-prev-avatar {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--accent-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  object-fit: cover;
}
.mb-prev-header-text { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.mb-prev-username { font-weight: 600; color: #f2f3f5; }
.mb-prev-bot-badge {
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 0.62rem;
  font-weight: 700;
  padding: 0.05rem 0.3rem;
  border-radius: 4px;
  line-height: 1.3;
}
.mb-prev-timestamp { color: #949ba4; font-size: 0.72rem; }
.mb-prev-body { padding-left: 3.25rem; display: flex; flex-direction: column; gap: 0.5rem; }
.mb-prev-content { white-space: pre-wrap; word-break: break-word; color: #dbdee1; }
.mb-prev-empty { color: #6d7079; font-style: italic; font-size: 0.85rem; }

.mb-prev-embed {
  display: flex;
  max-width: 480px;
  background: #2b2d31;
  border-radius: 6px;
  border-left: 4px solid var(--border);
  overflow: hidden;
}
.mb-prev-embed-inner { flex: 1; min-width: 0; padding: 0.7rem 0.9rem; display: flex; flex-direction: column; gap: 0.5rem; }
.mb-prev-embed.has-thumb .mb-prev-embed-inner { padding-right: 0.5rem; }
.mb-prev-embed-thumb { width: 5.5rem; height: 5.5rem; object-fit: cover; margin: 0.7rem 0.9rem 0.7rem 0; border-radius: 4px; flex-shrink: 0; align-self: flex-start; }
.mb-prev-embed-author { display: flex; align-items: center; gap: 0.4rem; font-size: 0.82rem; font-weight: 600; color: #f2f3f5; }
.mb-prev-embed-author-icon { width: 1.3rem; height: 1.3rem; border-radius: 50%; }
.mb-prev-embed-title { font-weight: 700; color: #00a8fc; font-size: 0.92rem; text-decoration: none; }
a.mb-prev-embed-title:hover { text-decoration: underline; }
.mb-prev-embed-desc { font-size: 0.85rem; color: #dbdee1; line-height: 1.4; word-break: break-word; }
.mb-prev-embed-fields { display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; }
.mb-prev-embed-field { flex: 1 1 100%; min-width: 0; }
.mb-prev-embed-field.inline { flex: 1 1 calc(33% - 0.7rem); }
.mb-prev-embed-field-name { font-weight: 700; font-size: 0.8rem; color: #f2f3f5; margin-bottom: 0.15rem; }
.mb-prev-embed-field-value { font-size: 0.82rem; color: #dbdee1; word-break: break-word; }
.mb-prev-embed-image { max-width: 100%; border-radius: 4px; margin-top: 0.2rem; }
.mb-prev-embed-footer { display: flex; align-items: center; gap: 0.4rem; font-size: 0.75rem; color: #949ba4; margin-top: 0.2rem; }
.mb-prev-embed-footer-icon { width: 1.1rem; height: 1.1rem; border-radius: 50%; }

.mb-prev-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: #4e5058;
  color: #dbdee1;
  border: none;
  border-radius: 8px;
  padding: 0.45rem 0.9rem;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: default;
}
.mb-prev-btn-primary { background: var(--accent); color: var(--accent-fg); }
.mb-prev-btn-success { background: #248046; color: white; }
.mb-prev-btn-danger { background: #da373c; color: white; }
.mb-prev-btn-link { background: #4e5058; color: #dbdee1; text-decoration: underline; }
.mb-prev-btn[disabled] { opacity: 0.5; }
.mb-prev-action-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.2rem; }

.mb-prev-poll {
  background: #2b2d31;
  border-radius: 10px;
  padding: 0.9rem 1rem;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.mb-prev-poll-question { display: flex; align-items: center; gap: 0.5rem; font-weight: 700; color: #f2f3f5; }
.mb-prev-poll-answer {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: #383a40;
  border-radius: 8px;
  padding: 0.45rem 0.7rem;
  font-size: 0.85rem;
}

.mb-prev-text-display { white-space: pre-wrap; word-break: break-word; color: #dbdee1; font-size: 0.92rem; }
.mb-prev-separator { height: 1px; background: transparent; margin: 0.4rem 0; }
.mb-prev-separator:not(.no-line) { background: #3f4147; }
.mb-prev-separator-large { margin: 1.1rem 0; }
.mb-prev-media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 0.4rem; }
/* max-height bumped from 200 -> 320: Discord's real Media Gallery renders
   noticeably larger than this used to suggest (see the "Welcome Message
   preview doesn't look like the real thing" note above .md-heading) — this
   keeps the preview's image-vs-text proportions honest for both a single
   full-width banner (Welcome Message) and a multi-image gallery (Msg
   Builder), rather than always looking like a small thumbnail. */
.mb-prev-media-item { width: 100%; border-radius: 6px; object-fit: cover; max-height: 320px; }
.mb-prev-file-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: #2b2d31;
  border-radius: 8px;
  padding: 0.6rem 0.9rem;
  font-size: 0.85rem;
  color: #00a8fc;
  width: fit-content;
}
.mb-prev-action-row + .mb-prev-action-row { margin-top: 0.3rem; }
.mb-prev-section-row { display: flex; align-items: flex-start; gap: 0.75rem; }
.mb-prev-section-texts { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; }
.mb-prev-section-thumb { width: 4.5rem; height: 4.5rem; object-fit: cover; border-radius: 6px; flex-shrink: 0; }
.mb-prev-container {
  border-left: 4px solid var(--border);
  background: #2b2d31;
  border-radius: 6px;
  overflow: hidden;
}
.mb-prev-container-inner { padding: 0.75rem 1rem; display: flex; flex-direction: column; gap: 0.6rem; }

/* ===========================================================================================
   CUSTOM CHECKBOX / RADIO THEMING
   Browser-default checkboxes/radios render as plain white squares/circles that
   clash with the dark theme. `appearance: none` strips the native control and
   the ::before pseudo-element (a fixed-size inline-block, since ::before on a
   replaced/void element like <input> is still permitted in every evergreen
   browser) draws a themed box/circle in its place, using the same
   --panel/--border/--accent variables as the rest of the UI. Applies globally
   (checkboxes/radios only currently appear in the Msg Builder and Settings
   pages, both dark-themed) rather than being scoped to one page's class.
   =========================================================================================== */
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none; /* Firefox ignores plain `appearance: none` on checkboxes/radios without this — without it, Firefox falls back to its native white checkbox/circle, unthemed */
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
  margin: 0;
  background: var(--panel);
  border: 1.5px solid var(--border);
  cursor: pointer;
  position: relative;
  vertical-align: middle;
  /* accent-color is a fallback for the rare case appearance:none is
     ignored by the browser (e.g. Windows forced-colors/high-contrast mode
     overrides both) — Firefox honors it as its own checked-state color. */
  accent-color: var(--accent);
  transition: background 0.12s ease, border-color 0.12s ease;
}
input[type="checkbox"] { border-radius: 5px; }
input[type="radio"] { border-radius: 50%; }

input[type="checkbox"]:hover,
input[type="radio"]:hover { border-color: var(--accent); }

input[type="checkbox"]:checked,
input[type="radio"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}

input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 0.32rem;
  top: 0.14rem;
  width: 0.28rem;
  height: 0.55rem;
  /* var(--accent-fg), not #fff — a white check mark all but disappears
     against the bright yellow fill above (same reason button text on a
     solid accent background switched to --accent-fg). */
  border: solid var(--accent-fg);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--accent-fg);
  transform: translate(-50%, -50%);
}

input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* --- Toggle switch variant (Notification Settings' "Silent Notification")
   — a distinct visual style from the checkbox/radio theming above, so it
   needs its own fully-specified rules rather than relying on the generic
   input[type="checkbox"] ones. The extra .mb-switch class keeps these
   selectors more specific than the generic checkbox rules above, so this
   wins regardless of source order (no !important needed). --- */
input[type="checkbox"].mb-switch {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  accent-color: var(--accent);
  width: 2.5rem;
  height: 1.4rem;
  border-radius: 999px;
  background: var(--panel-2);
  border: 1.5px solid var(--border);
  position: relative;
  flex-shrink: 0;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
input[type="checkbox"].mb-switch::after {
  content: "";
  position: absolute;
  top: 0.13rem;
  left: 0.13rem;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--muted);
  border: none;
  transform: none;
  transition: transform 0.15s ease, background 0.15s ease;
}
input[type="checkbox"].mb-switch:checked {
  background: var(--accent);
  border-color: var(--accent);
}
input[type="checkbox"].mb-switch:checked::after {
  background: #fff;
  transform: translateX(1.1rem);
}
input[type="checkbox"].mb-switch:hover { border-color: var(--accent); }

/* --- Automations tab (Honeypot Channel + Banned Users) --- */
.automation-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.25rem 1.4rem;
  /* left/right auto (not just margin-bottom) — .settings-wrap is a wide
     1440px container, and without this these narrower (900px/1200px) cards
     just stuck to its left edge instead of sitting centered on the page. */
  margin: 0 auto 1.5rem;
  max-width: 900px;
}
.automation-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.automation-card-head h2 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.1rem;
  margin: 0;
}
.automation-card-desc { color: var(--muted); font-size: 0.9rem; line-height: 1.55; margin: 0.6rem 0 1.1rem; }

.honeypot-form { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1.25rem; }
.honeypot-form label { font-size: 0.85rem; color: var(--muted); }
.honeypot-form select,
.honeypot-form input[type="text"],
.honeypot-form .cs-wrapper { min-width: 260px; }
.honeypot-save-btn { width: auto; padding: 0.6rem 1.25rem; }

.honeypot-kicks h3 { font-size: 0.9rem; color: var(--muted); margin: 0 0 0.6rem; }
.honeypot-kicks-list { display: flex; flex-direction: column; gap: 0.4rem; max-height: 16rem; overflow-y: auto; }
.honeypot-kick-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.5rem;
  border-radius: 8px;
  font-size: 0.85rem;
}
.honeypot-kick-row:hover { background: var(--panel-2); }
.honeypot-kick-tag { font-weight: 600; }
.honeypot-kick-time { color: var(--muted); font-size: 0.78rem; margin-left: auto; white-space: nowrap; }

/* --- Logs > Staff Logs sidebar: Banned Users card (moved here from a
   modal popup in the Automations tab — now an inline <details> that opens
   down in place, same as every other collapsible section, see
   logs-staff.ejs/logs-staff.js) --- */
.banned-users-search { width: 100%; margin: 0 0 0.75rem; }
.banned-users-list { display: flex; flex-direction: column; gap: 0.2rem; max-height: 16rem; overflow-y: auto; }
.banned-user-row {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 0.5rem;
  border-radius: 8px;
  font-size: 0.85rem;
}
.banned-user-row:hover { background: var(--panel-2); }
.banned-user-row img.avatar { border-radius: 50%; flex-shrink: 0; margin-top: 0.1rem; }
/* Tag + reason stacked and wrapped (not truncated to one ellipsized line) —
   a 280px sidebar cut off almost every real ban reason before it said
   anything useful. */
.banned-user-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.banned-user-tag { font-weight: 600; overflow-wrap: break-word; }
.banned-user-reason { color: var(--muted); font-size: 0.78rem; line-height: 1.35; overflow-wrap: break-word; }
.banned-users-empty { padding: 0.35rem 0.1rem; color: var(--muted); font-size: 0.85rem; }

/* --- Automations tab: YouTube Notifications panel --- */
.youtube-add-channel-form { display: flex; align-items: center; gap: 0.6rem; margin-top: 0.75rem; }
.youtube-add-channel-form input[type="text"] { flex: 1; }
/* The select (or its custom-select.js .cs-wrapper replacement) has no
   flex-basis of its own here, just its native width:100% — as a flex item
   that's read as "please be as wide as the whole row", which left almost
   no room for the button next to it. The button's own text ("Add Role",
   "Add Reward", etc.) then wrapped onto two lines to fit that leftover
   sliver of space, which is what actually made it look oversized — it
   wasn't padding, it was a much taller two-line button forced by a starved
   width. Giving the picker an explicit flex-basis of 0 (so it only grows
   to fill *leftover* space, same as the text-input case above) and pinning
   the button to its natural single-line width fixes both at once. */
.youtube-add-channel-form select,
.youtube-add-channel-form .cs-wrapper { flex: 1 1 0; min-width: 0; }
.youtube-add-channel-form button { flex-shrink: 0; white-space: nowrap; }
/* Leveling's role-reward row (the only .youtube-add-channel-form with a
   number field in it, for the level) has the same problem the select did:
   custom-number.js wraps the <input> in a .num-stepper div that replaces
   it in the DOM, and THAT wrapper has its own `width: 100%` — the input's
   own inline max-width: 6rem never reached the wrapper, so the wrapper
   still claimed nearly the entire row (with a lot of dead space inside
   it), which is what was actually squeezing everything else this whole
   time, both before this file's earlier fix and, differently, after it. */
.youtube-add-channel-form .num-stepper { flex: 0 0 auto; width: 10.5rem; }

/* --- Automations tab: Counting Game panel --- */
.counting-stats-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1rem; }
.counting-stat {
  flex: 1;
  min-width: 7rem;
  background: var(--panel-2);
  border-radius: 10px;
  padding: 0.7rem 0.9rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.counting-stat-value { font-size: 1.3rem; font-weight: 700; }
.counting-stat-value-name { font-size: 1rem; }
.counting-stat-label { color: var(--muted); font-size: 0.75rem; }
.counting-reset-form { margin-bottom: 1.25rem; }
/* The counting rules form used to reuse .honeypot-form (a single-row flex
   layout meant for "one select + one button"), which left this form's
   select/checkbox-grid/toggle/number-field/button all fighting over the
   same flex row and wrapping unpredictably — the squeezed, misaligned look
   reported here. This is its own column layout instead, matching
   .honeypot-banner-form's approach right below it. */
.counting-rules-form { display: flex; flex-direction: column; }
.counting-rules-bottom-row {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}
.counting-nofail-toggle { flex: 1 1 220px; margin-bottom: 0; }
.counting-milestone-field { flex: 0 1 200px; margin-bottom: 0; }

.counting-modes-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4rem 1rem;
  margin: 1rem 0;
}
.counting-reactions-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1rem;
}
.counting-top-rank { font-weight: 700; color: var(--muted); width: 2rem; flex-shrink: 0; }
.counting-leaderboard-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  margin-top: 1rem;
  padding: 0.75rem 1rem;
  border-radius: 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.counting-leaderboard-bar:hover { border-color: var(--accent); background: var(--accent-soft); }

/* --- Automations tab: "Customize Banner" mini message-builder — form on
   the left, a live Components V2 preview on the right (reuses msg-builder's
   .mb-prev-* preview classes/markup so the honeypot banner looks exactly
   like it will in Discord, not an approximation). --- */
.automation-card-wide { max-width: 1200px; }
.honeypot-banner-editor { display: flex; gap: 1.75rem; align-items: flex-start; flex-wrap: wrap; }
.honeypot-banner-form { flex: 1 1 360px; min-width: 320px; display: flex; flex-direction: column; }
/* This class was written for the Honeypot Banner mini-editor (a column of
   text/textarea fields next to a live preview, no inline submit button of
   its own — see the comment above), where flex-direction: column's default
   align-items: stretch was never actually visible: every field in that
   original form is a text input/textarea, which are already width: 100%
   on their own regardless of stretch. Since then this same class got
   reused as a generic "settings form" wrapper across many other forms
   (Prefix Commands, Joinable Ranks, Lockdown Channels, Ban Appeals, ...)
   that DO have a plain <button type="submit">Save</button> as a direct
   child — buttons are width: auto by default, which is exactly the
   condition align-items: stretch overrides, so every one of those Save
   buttons was silently stretching into a full-width solid bar instead of
   sitting as a normal compact button like every other Save button
   sitewide. Opting direct-child buttons back out of that stretch. */
.honeypot-banner-form > button { align-self: flex-start; }
/* Flex column, no border of its own — the bordered "card" look lives on
   .mb-preview-card (below) instead, so anything appended after it (e.g.
   the Welcome Message's image-mode toggle) reads as its own block
   underneath the preview, not as part of the preview card itself, while
   still staying in this same right-hand column. */
.honeypot-banner-preview {
  flex: 1 1 320px;
  min-width: 300px;
  position: sticky;
  top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}
.mb-preview-card {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
}

/* --- Staff/Studio tab: two side-by-side columns (Staff = Moderation Team,
   Studio = Studio Team), each broken into one section per role that
   currently has at least one member, holding a small clickable "chip" per
   person. A member with roles in both columns appears once per column,
   independently, under their highest role in that column — see
   src/server/routes/staff.js's buildColumn(). --- */
.staff-studio-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
  align-items: start;
}
@media (max-width: 900px) {
  .staff-studio-columns { grid-template-columns: 1fr; }
}

.staff-studio-column-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 1.15rem;
  margin: 0 0 1rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--border);
}

.role-group { margin-bottom: 1.5rem; }
.role-group-heading {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  margin: 0 0 0.6rem;
}
.role-group-count {
  background: var(--panel-2);
  color: var(--text);
  border-radius: 999px;
  font-size: 0.7rem;
  padding: 0.05rem 0.5rem;
  font-weight: 700;
}

.member-grid { display: flex; flex-direction: column; gap: 0.5rem; }
.member-chip {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem 0.75rem;
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.member-chip:hover { border-color: var(--accent); background: var(--panel-2); transform: translateX(2px); }
.member-chip-avatar { flex-shrink: 0; }
.member-chip-tag { font-weight: 600; font-size: 0.9rem; }

.status-Retired { background: rgba(154, 162, 177, 0.2); color: var(--muted); margin-left: auto; }
.status-Inactive { background: rgba(254, 231, 92, 0.18); color: var(--yellow); margin-left: auto; }

/* --- Staff/Studio: one person's detail page --- */
.staff-member-head { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.5rem; }
.staff-member-name { font-size: 1.5rem; margin: 0 0 0.3rem; }

/* One flowing grid across BOTH teams (not two matched side-by-side
   columns) — a fixed left/right split left a big dead gap under whichever
   team has fewer roles (Moderation Team is always shorter than Studio
   Team). Letting checkboxes wrap into however many columns actually fit,
   with each team's heading spanning the full row width to break to a new
   group, keeps the same visual grouping without the wasted space. */
.staff-role-checklist { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 0.2rem 1.5rem; margin-bottom: 1rem; }
.staff-role-checklist-heading { grid-column: 1 / -1; margin: 1rem 0 0.4rem; }
.staff-role-checklist-heading:first-child { margin-top: 0; }
.staff-role-check {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0;
  font-size: 0.92rem;
  cursor: pointer;
}
.staff-role-unmapped { color: var(--muted); cursor: not-allowed; }

.staff-history-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.9rem; }
.staff-history-list li { font-size: 0.88rem; border-left: 2px solid var(--border); padding-left: 0.75rem; }

/* --- Staff Settings: the role-mapping grid + manager role list — same
   flowing-grid approach as .staff-role-checklist above, and for the same
   reason (Moderation Team has far fewer rungs than Studio Team). --- */
.staff-role-mapping-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 0 1.5rem; align-items: start; }
.staff-role-mapping-heading { grid-column: 1 / -1; margin: 1rem 0 0.4rem; }
.staff-role-mapping-heading:first-child { margin-top: 0; }
.staff-manager-role-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.4rem 1rem;
}

/* ===========================================================================
   Inline @ / # / : / "{" autocomplete popup (public/js/mention-autocomplete.js) —
   one shared instance, positioned via `fixed` under whichever field is
   currently active, so it isn't clipped by any scrolling container it
   happens to sit inside (a sticky sidebar, a scrollable card body, etc).
   =========================================================================== */
.mention-popup {
  position: fixed;
  z-index: 500;
  max-height: 15rem;
  overflow-y: auto;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: 0.35rem;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}
.mention-popup[hidden] { display: none; }
.mention-popup::-webkit-scrollbar { width: 8px; }
.mention-popup::-webkit-scrollbar-track { background: transparent; }
.mention-popup::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }

.mention-popup-section-label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 0.4rem 0.55rem 0.25rem;
}

.mention-popup-row {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.4rem 0.55rem;
  border-radius: 7px;
  cursor: pointer;
  font-size: 0.86rem;
}
.mention-popup-row.active,
.mention-popup-row:hover { background: var(--accent-soft); }

.mention-popup-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  flex-shrink: 0;
  color: var(--muted);
}
.mention-popup-avatar {
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--panel);
}
.mention-popup-avatar-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 700;
  background: linear-gradient(135deg, var(--accent), #ffe066);
  color: var(--accent-fg);
}
.mention-popup-emoji-char { font-size: 1.1rem; width: 1.4rem; text-align: center; flex-shrink: 0; }
.mention-popup-emoji-img { width: 1.4rem; height: 1.4rem; object-fit: contain; flex-shrink: 0; }

.mention-popup-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.mention-popup-sub { flex-shrink: 0; font-size: 0.72rem; color: var(--muted); }

.mention-popup-empty { padding: 0.6rem 0.55rem; color: var(--muted); font-size: 0.85rem; font-style: italic; }

/* --- Public counting leaderboard (/leaderboard — the one page in this app
   with no login, see src/server/routes/leaderboard.js) --- */
.lb-wrap { max-width: 640px; margin: 0 auto; padding: 3rem 1.5rem 4rem; }
.lb-header { text-align: center; margin-bottom: 2rem; }
.lb-header h1 { display: flex; align-items: center; justify-content: center; gap: 0.6rem; font-size: 1.7rem; margin: 0 0 0.5rem; letter-spacing: -0.01em; }
.lb-sub { color: var(--muted); font-size: 0.95rem; margin: 0; }
/* Counting/Leveling switch at the top of the full leaderboard page (see
   routes/leaderboard.js's ?type=counting|leveling) — plain links, not a
   form, so the choice is bookmarkable/shareable and doesn't need JS. */
.lb-type-switch { display: flex; justify-content: center; gap: 0.5rem; margin-top: 1rem; }
.lb-type-switch a {
  display: inline-flex; align-items: center; gap: 0.35rem;
  padding: 0.4rem 0.9rem; border-radius: 999px;
  border: 1px solid var(--border); color: var(--muted);
  font-size: 0.85rem; font-weight: 600; text-decoration: none;
}
.lb-type-switch a:hover { color: var(--text); border-color: var(--accent); }
.lb-type-switch a.lb-type-active { color: var(--accent-contrast, #fff); background: var(--accent); border-color: var(--accent); }
.lb-error { margin-bottom: 1.5rem; }
.lb-empty { text-align: center; padding: 3rem 1rem; }
.lb-list { display: flex; flex-direction: column; gap: 0.5rem; }
.lb-row {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.7rem 1rem;
}
.lb-row-top { border-color: var(--accent); }
.lb-row-top-1 { background: linear-gradient(90deg, var(--accent-soft), transparent); }
.lb-rank { font-weight: 700; font-size: 1.05rem; width: 2.25rem; flex-shrink: 0; text-align: center; }
.lb-name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-level { font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.lb-score { font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.lb-footer { text-align: center; color: var(--muted); font-size: 0.8rem; margin-top: 2.5rem; }
.lb-footer code { background: var(--panel-2); padding: 0.1rem 0.4rem; border-radius: 4px; }

/* --- Overview tab (views/overview.ejs) — a varied dashboard grid (a large
   2x2 donut-chart widget, a couple of 2x1 segmented-bar widgets, and
   several plain single-number tiles) instead of one flat row of identical
   cards or a tall scrolling settings-style page. grid-auto-flow: dense
   lets the smaller tiles backfill whatever gaps the bigger widgets leave
   instead of forcing a strict reading order. Explicit top/bottom padding
   here (not on some outer wrapper) is what actually fixes cards rendering
   flush against .page-head — there was none before. --- */
.overview-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: minmax(120px, auto);
  grid-auto-flow: dense;
  gap: 1rem;
  max-width: 1200px;
  margin: 0 auto;
  padding: 1.5rem;
}
@media (max-width: 900px) {
  .overview-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .overview-grid { grid-template-columns: 1fr; }
  .overview-span-2x1, .overview-span-2x2, .overview-span-4x1 { grid-column: span 1 !important; }
}
.overview-span-2x1 { grid-column: span 2; }
.overview-span-2x2 { grid-column: span 2; grid-row: span 2; }
/* Full-width row — used by the Recent Activity card, a short list rather
   than a single stat, so it reads better spanning the grid's whole width
   than squeezed into a 2-column tile. */
.overview-span-4x1 { grid-column: span 4; }

/* Recent Activity card (Overview -> Logs tie-in): a compact version of the
   Staff Logs list rows — same idea as .app-row but denser, no avatar, since
   this is a "glance at the last few things" widget, not the full log. */
.activity-list { display: flex; flex-direction: column; gap: 0.5rem; margin-top: 0.7rem; }
.activity-row { display: flex; align-items: center; gap: 0.6rem; font-size: 0.82rem; }
.activity-row .type-badge { flex-shrink: 0; }
.activity-row-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); }
.activity-row-text strong { color: var(--text); font-weight: 600; }
.activity-row-time { color: var(--muted); font-size: 0.72rem; flex-shrink: 0; }

.overview-card {
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 1.1rem 1.3rem;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
a.overview-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.overview-card-static { cursor: default; }
.overview-card-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.6rem; color: var(--muted); }
.overview-card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent);
  flex-shrink: 0;
}
.overview-card-label { font-size: 0.85rem; font-weight: 600; }
.overview-card-value { font-size: 1.8rem; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; font-variant-numeric: tabular-nums; }
.overview-card-caption { color: var(--muted); font-size: 0.8rem; margin-top: 0.1rem; }

/* Applications/Tickets: a rounded horizontal track, each status as a
   proportionally-widthed colored segment — like a stacked progress bar
   with no single "done" direction, since there isn't one here. */
.stat-bar {
  display: flex;
  height: 0.55rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--panel-2);
  margin-top: 0.9rem;
}
.stat-bar span { display: block; height: 100%; }

/* Shared legend styles — a plain stacked list under the big donut widget,
   or a wrapped inline row under a bar widget. */
.chart-legend { list-style: none; margin: 0.7rem 0 0; padding: 0; font-size: 0.8rem; color: var(--muted); }
.chart-legend li { display: flex; align-items: center; gap: 0.45rem; padding: 0.2rem 0; }
.chart-legend strong { color: var(--text); font-variant-numeric: tabular-nums; margin-left: auto; }
.chart-legend-inline { display: flex; flex-wrap: wrap; gap: 0 1rem; }
.chart-legend-inline li { padding: 0; }
.chart-legend-inline strong { margin-left: 0.3rem; }
.chart-legend-compact { font-size: 0.72rem; }
.chart-legend-compact li { padding: 0.22rem 0; gap: 0.55rem; }
.legend-dot { width: 0.55rem; height: 0.55rem; border-radius: 50%; flex-shrink: 0; }

/* Staff/Studio's donut — SVG chart on the left, legend list filling the
   rest of the width on the right, side by side inside the 2x2 card. */
.donut-row { display: flex; align-items: center; gap: 1.2rem; margin-top: 0.4rem; }

/* Two mini donut+legend groups side by side (Staff/Studio's "by status"
   and "by team" charts) instead of stacking them, so the card stays the
   same one-row height as its neighbors rather than growing to fit a
   second full-size chart underneath the first. */
.donut-group-row { display: flex; gap: 2.2rem; margin-top: 0.9rem; flex-wrap: wrap; }
.donut-mini { display: flex; align-items: center; gap: 1rem; }
.donut-mini .donut-wrap { width: 4.2rem; height: 4.2rem; }
.donut-mini .donut-center-value { font-size: 0.95rem; }
.donut-mini .donut-center-label { font-size: 0.55rem; }
.overview-card-caption-tight {
  margin-top: 0.9rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--border);
  font-size: 0.72rem;
}

.donut-wrap { position: relative; width: 5.5rem; height: 5.5rem; flex-shrink: 0; }
.donut-chart { width: 100%; height: 100%; }
.donut-chart path { transition: opacity 0.15s ease; }
.donut-chart path:hover { opacity: 0.8; }
.donut-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.donut-center-value { font-size: 1.15rem; font-weight: 800; letter-spacing: -0.02em; }
.donut-center-label { font-size: 0.62rem; color: var(--muted); }

/* --- Website Settings page (views/website-settings.ejs) --- */
.theme-toggle { display: flex; gap: 0.5rem; }
.theme-toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.55rem 1.1rem;
  font-size: 0.9rem;
  font-weight: 600;
}
.theme-toggle-btn:hover { color: var(--text); border-color: var(--accent); }
.theme-toggle-btn.active { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }

/* Card Appearance panel (Automations tab) — a small live preview of
   whatever's currently pasted into the background image URL field, and the
   "Reset to defaults" button underneath its form (outside the <form> on
   purpose, since resetting is a plain field-fill, not itself a save — see
   automations.js's initCardAppearance). */
.ca-bg-preview {
  display: block;
  width: 100%;
  max-width: 320px;
  max-height: 140px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  margin-top: 0.5rem;
}
#ca-reset-btn { margin-top: 0.75rem; }

/* Moderation tab's Command Reference panel — a plain table per category
   (Moderator/Manager/...), listing every command's name/description/usage
   the same way the dashboard already tables other lists (Staff Logs,
   leaderboards). */
.mod-ref-category {
  margin: 1.25rem 0 0.5rem;
  font-size: 0.95rem;
  color: var(--text-muted);
}
.mod-ref-category:first-of-type { margin-top: 0.5rem; }
.mod-ref-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
  margin-bottom: 0.5rem;
}
.mod-ref-table th,
.mod-ref-table td {
  text-align: left;
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.mod-ref-table th {
  color: var(--text-muted);
  font-weight: 600;
}
.mod-ref-table code { white-space: nowrap; }

/* Divides the merged Staff/Studio ("Moderation" tab) page into its two
   halves — the moderation settings above, the team directory below — a
   plain heading rather than a full section header since it's not
   collapsible, just a visual break. */
.settings-section-divider {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 2rem 0 1rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
  font-size: 1.1rem;
}

/* ===========================================================================
   Events tab — calendar (events.ejs), create/edit form (event-form.ejs)
   =========================================================================== */

.status-Scheduled { background: rgba(255, 198, 26, 0.18); color: var(--accent-text); }

/* Centers the whole calendar block (toolbar + grid + sidebar) as one unit
   on wide viewports, same max-width/margin:auto treatment as .settings-wrap
   — without this, .events-layout's wider-left/narrower-right grid columns
   stretch edge to edge and the calendar itself reads as shifted left. */
.events-page-wrap {
  max-width: min(1760px, 96vw);
  margin: 0 auto;
  padding: 1.5rem 1.5rem 2rem;
}

/* Main calendar (left, wider) + Upcoming/Scheduled-Messages sidebar
   (right, narrower) — collapses to one column on small screens same as
   every other two-column layout in this app (see .staff-studio-columns). */
.events-layout {
  display: grid;
  grid-template-columns: minmax(0, 2.4fr) minmax(280px, 1fr);
  gap: 1.5rem;
}
@media (max-width: 900px) {
  .events-layout { grid-template-columns: 1fr; }
}

.events-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.events-toolbar-nav { display: flex; align-items: center; gap: 0.5rem; }
.events-toolbar-month { font-size: 1.15rem; font-weight: 700; min-width: 160px; text-align: center; }

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.calendar-weekday {
  background: var(--panel);
  padding: 0.5rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.calendar-day {
  background: var(--bg);
  min-height: 92px;
  padding: 0.4rem 0.4rem 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.calendar-day-outside { opacity: 0.4; }
.calendar-day-number {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--muted);
  align-self: flex-start;
}
.calendar-day-today .calendar-day-number {
  background: var(--accent);
  color: #14151a;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.calendar-day-events {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  overflow-y: auto;
  max-height: 74px;
}
.calendar-event-chip {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  padding: 0.12rem 0.4rem;
  border-radius: 5px;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-decoration: none;
}
.calendar-event-chip:hover { filter: brightness(1.15); }
.calendar-day-add {
  align-self: flex-end;
  opacity: 0;
  transition: opacity 0.1s ease;
  color: var(--muted);
}
.calendar-day:hover .calendar-day-add { opacity: 1; }

.events-sidebar-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem 1.1rem;
  margin-bottom: 1.25rem;
}
.events-sidebar-card h2 {
  font-size: 0.95rem;
  margin: 0 0 0.75rem;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.event-list-row {
  display: block;
  padding: 0.55rem 0;
  border-top: 1px solid var(--border);
  text-decoration: none;
  color: inherit;
}
.event-list-row:first-child { border-top: none; }
.event-list-row-title { font-weight: 700; font-size: 0.9rem; display: flex; align-items: center; gap: 0.4rem; }
.event-list-row-meta { color: var(--muted); font-size: 0.78rem; margin-top: 0.15rem; }
.event-color-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }

.event-location-fields { display: none; }
.event-location-fields.event-location-fields-active { display: block; }
.events-subscribe-row {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  margin-top: 0.5rem;
}
.events-subscribe-row input { flex: 1; font-size: 0.8rem; margin-bottom: 0; }
/* .btn-small's own padding (0.4rem 0.8rem) reads shorter than the URL
   input's (0.6rem 0.75rem, from the shared text-input rule) — matched here
   so the Copy button doesn't look like an undersized afterthought next to
   the field it belongs to. */
.events-subscribe-row .btn-small { padding: 0.6rem 0.9rem; flex-shrink: 0; }

/* Fullscreen image lightbox (see public/js/lightbox.js) — opened by
   clicking any .lightbox-trigger thumbnail (chat-attachments.ejs). One
   overlay, built and appended once, reused for every open() across Chat
   Log / Action Log's bulk-delete panel. body.lightbox-open just blocks
   background scroll while it's up. */
body.lightbox-open { overflow: hidden; }
/* display starts at `none` here and lightbox.js flips it to `flex`/`none`
   directly via overlay.style.display (an inline style) to open/close —
   NOT the `hidden` attribute or a class toggle. An inline style always
   wins over anything in this file regardless of selector/specificity, so
   there's no cascade ordering left for a future rule anywhere in this
   3000+ line file to accidentally re-win and leave the overlay stuck
   showing after close() runs (which is exactly what happened when this
   used to rely on the `hidden` attribute — this file's own unconditional
   `display: flex` was beating the attribute's UA-stylesheet default). */
.lightbox-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(6, 7, 10, 0.94);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* Dragging to pan a zoomed image used to select the page's own text
     underneath (the overlay background is deliberately translucent), which
     rendered as stray selection-highlight slivers behind it — this and
     lightbox.js's pointerdown/dragstart prevention both need to be in place
     to stop that. */
  -webkit-user-select: none;
  user-select: none;
}
.lightbox-stage {
  flex: 1;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  touch-action: none;
  position: relative;
  z-index: 1;
}
.lightbox-image {
  max-width: 90vw;
  max-height: 78vh;
  border-radius: 6px;
  cursor: zoom-in;
  transform-origin: center center;
  transition: transform 0.08s ease-out;
  user-select: none;
  -webkit-user-drag: none;
  /* Forces its own GPU compositing layer — without this, Chrome/Firefox
     occasionally show hairline seams along the image's edges while it's
     being scaled (a subpixel-rounding artifact in how the un-promoted
     layer gets clipped by .lightbox-stage's overflow:hidden mid-transform). */
  backface-visibility: hidden;
  will-change: transform;
}
.lightbox-image.is-zoomed { cursor: grab; transition: none; }
.lightbox-image.is-zoomed:active { cursor: grabbing; }
.lightbox-image-error {
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
  max-width: 320px;
}
.lightbox-btn {
  /* Resets the browser's own <button> box model (default padding/border/
     font vary by browser and are NOT 0) — left in place, that padding was
     added on top of the fixed width/height below instead of eating into
     it, which is what made the 30px zoom buttons look squished into a
     non-square shape and nudged their icons off-center inside the circle. */
  appearance: none;
  -webkit-appearance: none;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  font: inherit;
  line-height: 0;
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(23, 26, 33, 0.85);
  color: var(--text);
  cursor: pointer;
  /* Sits above .lightbox-stage explicitly — belt-and-suspenders alongside
     the stage's own lower z-index, so a button is never accidentally
     unclickable underneath it. */
  z-index: 2;
}
.lightbox-btn:hover { background: var(--panel-2); }
.lightbox-close { top: 1rem; right: 1rem; }
/* .lightbox-icon-chevron is drawn pointing right by default — rotating the
   whole PREV button 180° (rather than drawing a second, mirrored shape)
   flips it to point left, same trick the button used before, just now
   combined with a shape that's actually centered to begin with. */
.lightbox-prev { top: 50%; left: 1rem; transform: translateY(-50%) rotate(180deg); }
.lightbox-next { top: 50%; right: 1rem; transform: translateY(-50%); }

/* Every lightbox button's glyph (see public/js/lightbox.js's buildOverlay)
   is a plain CSS shape rather than the shared SVG icon set — a chevron/
   plus/minus/x drawn this way centers purely off its own fixed
   width/height inside the button's flexbox, with no icon-library viewBox
   or glyph-weighting to throw that off. */
.lightbox-icon-x {
  position: relative;
  width: 14px;
  height: 14px;
}
.lightbox-icon-x::before,
.lightbox-icon-x::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 14px;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}
.lightbox-icon-x::before { transform: translate(-50%, -50%) rotate(45deg); }
.lightbox-icon-x::after { transform: translate(-50%, -50%) rotate(-45deg); }
.lightbox-icon-chevron {
  display: block;
  width: 9px;
  height: 9px;
  border-style: solid;
  border-color: currentColor;
  border-width: 0 2px 2px 0;
  border-radius: 1px;
  /* Right-pointing by default (.lightbox-next) — .lightbox-prev rotates
     the whole BUTTON 180° (see above) rather than this shape, so the
     shape itself never needs a mirrored variant. */
  transform: rotate(-45deg);
}
.lightbox-icon-plus,
.lightbox-icon-minus {
  position: relative;
  width: 13px;
  height: 13px;
}
.lightbox-icon-plus::before,
.lightbox-icon-plus::after,
.lightbox-icon-minus::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  background: currentColor;
  border-radius: 1px;
}
.lightbox-icon-plus::before,
.lightbox-icon-minus::before {
  width: 13px;
  height: 2px;
  transform: translate(-50%, -50%);
}
.lightbox-icon-plus::after {
  width: 2px;
  height: 13px;
  transform: translate(-50%, -50%);
}
.lightbox-footer {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1.25rem;
  color: var(--muted);
  font-size: 0.82rem;
  width: 100%;
  max-width: 900px;
  position: relative;
  z-index: 2;
}
.lightbox-caption { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lightbox-counter { flex-shrink: 0; }
.lightbox-zoom-controls { display: flex; gap: 0.4rem; flex-shrink: 0; }
.lightbox-zoom-controls .lightbox-btn { position: static; width: 30px; height: 30px; flex-shrink: 0; }
@media (max-width: 640px) {
  .lightbox-btn { width: 34px; height: 34px; }
  .lightbox-footer { flex-wrap: wrap; }
}

/* ── Access page + public (guest) pages + Discord login ─────────────────── */
.login-discord-btn { display: block; text-align: center; text-decoration: none; margin-top: 0.5rem; }
.login-note { font-size: 0.8rem; margin: 0.9rem 0 0; color: var(--muted); text-align: center; }
.login-password-form { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid var(--border); }
.login-password-form input[type="password"] { width: 100%; margin-bottom: 0.6rem; }
.login-password-form .btn { width: 100%; }

.public-head .public-brand { font-weight: 800; font-size: 1.15rem; letter-spacing: -0.01em; color: var(--text); text-decoration: none; }
.public-login-btn { text-decoration: none; }
.public-main { max-width: 760px; margin: 0 auto; padding: 4rem 1.25rem; }
.public-main h1 { margin: 0 0 0.5rem; font-size: 2rem; letter-spacing: -0.02em; }
.public-feature-list { display: grid; gap: 0.75rem; margin-top: 2rem; }
.public-feature {
  display: flex; flex-direction: column; gap: 0.2rem; padding: 1rem 1.2rem;
  background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
  color: var(--text); text-decoration: none;
}
.public-feature:hover { background: var(--panel-2); }
.public-feature span { color: var(--muted); font-size: 0.88rem; }

.access-h2 { font-size: 1.05rem; margin: 1.75rem 0 0.6rem; }
.access-copy, .access-chip {
  display: inline-block; padding: 0.25rem 0.55rem; margin: 0.15rem 0.35rem 0.15rem 0;
  background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
  font-size: 0.82rem; word-break: break-all; user-select: all;
}
.access-rule-head { display: flex; gap: 0.75rem; align-items: center; justify-content: space-between; margin-bottom: 0.7rem; }
.access-rule-head select { flex: 1; min-width: 0; }
.access-areas { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 0.4rem 1rem; }
.access-area { display: flex; align-items: center; gap: 0.45rem; font-size: 0.9rem; cursor: pointer; }

/* ===========================================================================
   v3.1 layout — the Speedzing dashboard design: a floating glass sidebar with
   a grouped menu (collapsible to icons), a page header with a big title and
   a grey subtitle, glass cards on a soft yellow-white background, and on
   phones a slim top bar + bottom tabs with the sidebar as a slide-in drawer.
   These rules sit on top of the older page-head styling above, which is now
   unused by the dashboard pages.
   =========================================================================== */
h1, h2, h3 { letter-spacing: -0.012em; }
.line-icon { flex-shrink: 0; }
a { color: var(--accent-text); }

/* --- Sidebar --- */
body:has(.sidebar) { padding-left: calc(var(--sidebar-w) + 32px); }
html.sb-collapsed { --sidebar-w: 76px; }
.sidebar {
  position: fixed;
  left: 16px;
  top: 16px;
  bottom: 16px;
  width: var(--sidebar-w);
  z-index: 50;
  display: flex;
  flex-direction: column;
  padding: 18px 14px 14px;
  background: var(--panel);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--border);
  border-radius: 26px;
  box-shadow: var(--shadow);
  transition: width 0.2s ease, transform 0.25s ease;
}
.sb-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 4px 16px; }
.sb-brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--text); min-width: 0; }
.sb-logo {
  width: 42px; height: 42px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 13px;
  background: var(--accent);
}
.sb-logo img { display: block; }
.sb-brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.sb-brand-text strong { font-size: 15.5px; font-weight: 700; }
.sb-brand-text small { font-size: 12px; color: var(--muted); }
.sb-collapse {
  width: 28px; height: 28px; padding: 0; flex-shrink: 0;
  border-radius: 50%;
  background: var(--panel-solid);
  border: 1px solid var(--border);
  color: var(--muted);
}
.sb-collapse:hover { color: var(--text); border-color: var(--accent); }

.sb-nav { flex: 1; overflow-y: auto; overflow-x: hidden; margin: 0 -6px; padding: 0 6px; scrollbar-width: none; }
.sb-nav::-webkit-scrollbar { display: none; }
.sb-group { margin-bottom: 4px; }
.sb-group-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--faint);
  padding: 14px 12px 6px;
}
.sb-item {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  margin-bottom: 2px;
  border-radius: 13px;
  color: var(--muted);
  text-decoration: none;
  font-size: 13.5px; font-weight: 500;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.sb-item:hover { background: var(--panel-2); color: var(--text); }
.sb-item.active { background: var(--yellow-tint); color: var(--text); font-weight: 650; }
.sb-item.active::before {
  content: ""; position: absolute; left: -6px; top: 22%; bottom: 22%;
  width: 4px; border-radius: 4px; background: #e5a800;
}

.sb-foot { position: relative; padding-top: 10px; border-top: 1px solid var(--border); }
.acct-chip {
  width: 100%; padding: 8px; gap: 10px;
  justify-content: flex-start;
  background: transparent; color: var(--text);
  border-radius: 16px; text-align: left; font-weight: 500;
}
.acct-chip:hover { background: var(--panel-2); }
.acct-avatar, .acct-chip .avatar {
  width: 34px; height: 34px; flex-shrink: 0; border-radius: 50%;
}
.acct-avatar {
  display: grid; place-items: center;
  background: var(--yellow-tint); color: var(--accent-text);
  font-size: 12px; font-weight: 700;
}
.acct-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; flex: 1; }
.acct-text strong { font-size: 13px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-text small { font-size: 11.5px; color: var(--muted); }
.acct-more { color: var(--muted); display: inline-flex; }
.acct-menu {
  position: absolute; left: 0; right: 0; bottom: calc(100% + 8px);
  min-width: 220px;
  padding: 8px;
  background: var(--panel-solid);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.18);
  z-index: 70;
}
.acct-menu[hidden] { display: none; }
.acct-menu-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--border); margin-bottom: 6px; }
.acct-menu-name { font-size: 13px; font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-menu-role { font-size: 11.5px; color: var(--muted); }
.acct-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; border-radius: 10px;
  background: transparent; color: var(--text);
  font-size: 13px; font-weight: 500; text-decoration: none; text-align: left;
  justify-content: flex-start;
}
.acct-menu-item:hover { background: var(--panel-2); }
.acct-menu-item.danger { color: var(--red); }

/* Collapsed sidebar (desktop only) */
@media (min-width: 901px) {
  html.sb-collapsed .sidebar { padding-left: 10px; padding-right: 10px; }
  html.sb-collapsed .sb-head { flex-direction: column; padding-bottom: 12px; }
  html.sb-collapsed .sb-brand-text,
  html.sb-collapsed .sb-item-text,
  html.sb-collapsed .acct-text,
  html.sb-collapsed .acct-more { display: none; }
  html.sb-collapsed .sb-collapse .icon { transform: rotate(180deg); }
  html.sb-collapsed .sb-group-label { height: 1px; padding: 0; margin: 10px 8px; background: var(--border); font-size: 0; }
  html.sb-collapsed .sb-item { justify-content: center; padding: 11px 0; }
  html.sb-collapsed .sb-item.active::before { left: -4px; }
  html.sb-collapsed .acct-chip { justify-content: center; padding: 6px 0; }
  html.sb-collapsed .acct-menu { left: 0; right: auto; width: 230px; }
}

/* --- Page header --- */
.page-head-new {
  display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
  padding: 34px 32px 8px;
}
.page-head-new-left { min-width: 0; }
.page-title { margin: 0; font-size: 28px; font-weight: 700; line-height: 1.15; letter-spacing: -0.02em; }
.page-subtitle { margin: 6px 0 0; color: var(--muted); font-size: 14px; }
.back-pill {
  display: inline-flex; align-items: center; gap: 4px;
  margin-bottom: 10px; padding: 4px 12px 4px 8px;
  border-radius: 999px; background: var(--panel-solid); border: 1px solid var(--border);
  color: var(--text); font-size: 12.5px; font-weight: 600; text-decoration: none;
}
.back-pill:hover { border-color: var(--accent); color: var(--text); }
.title-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.title-actions a:not(.btn):not(.btn-primary) {
  display: inline-flex; align-items: center; gap: 0.45rem;
  padding: 0.55rem 1rem; border-radius: 11px;
  background: var(--panel-solid); border: 1px solid var(--border);
  color: var(--text); text-decoration: none; font-size: 13px; font-weight: 600;
}
.title-actions a:not(.btn):not(.btn-primary):hover { border-color: var(--accent); }

/* --- Look of the pieces inside pages --- */
.btn-approve { background: var(--ink); color: var(--ink-fg); }
input[type="password"], input[type="text"], input[type="datetime-local"], textarea, select { border-radius: 11px; }
.login-card, .app-row, .leaderboard-card, .archive-batch-card, .qa-block, .attachment-block,
.review-form-large, .type-card, .type-row, .ticket-meta-item, .transcript-msg, .mb-channel-field,
.mb-content-box, .mb-embed-card, .mb-actionrow-card, .mb-poll-card, .mb-cv2-card, .mb-not,
.mb-modal-panel, .overview-card, .events-sidebar-card, .public-feature, .lb-row, .settings-section {
  border-radius: var(--radius-card);
  border-color: var(--border);
  box-shadow: var(--shadow);
}
.login-card, .app-row, .leaderboard-card, .archive-batch-card, .type-card, .overview-card,
.settings-section, .events-sidebar-card, .public-feature, .lb-row {
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.mb-modal-panel, .unsaved-bar, .mention-popup { background: var(--panel-solid); }
.settings-section > summary { border-radius: var(--radius-card); }
.overview-card { transition: transform 0.15s ease, border-color 0.15s ease; }
.overview-card:hover { transform: translateY(-2px); border-color: var(--accent); }
.overview-card-icon { background: var(--yellow-tint); color: var(--accent-text); border-radius: 12px; }
.settings-wrap > h1 { display: none; }
.flash-msg { border-radius: 14px; }
.tab { color: var(--text); background: var(--panel-solid); }
.tab.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

/* Content sits left-aligned under the page title. */
.app-list, .logs-page-wrap, .detail-wrap, .settings-wrap, .mb-layout, .automation-card, .overview-grid, .events-page-wrap {
  margin-left: 0;
  margin-right: auto;
  padding-left: 32px;
  padding-right: 32px;
}
.overview-grid { max-width: 1296px; padding-top: 1.1rem; }
.settings-wrap, .events-page-wrap { padding-top: 1.1rem; }

/* --- Login & public pages --- */
.login-brand { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; margin-bottom: 1.25rem; text-align: center; }
.login-brand .sb-logo { width: 52px; height: 52px; border-radius: 15px; margin-bottom: 0.6rem; }
.login-brand h1 { margin: 0; font-size: 1.5rem; font-weight: 700; justify-content: center; }
.login-brand p { margin: 0; color: var(--muted); font-size: 0.88rem; }
.login-card { max-width: 400px; padding: 2.25rem; }
.login-note { text-align: center; }
.public-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 12px 28px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--border);
}
.public-bar .sb-brand strong { font-size: 15px; }
.public-bar-right { display: flex; align-items: center; gap: 10px; }
.public-bar-right form { margin: 0; }
.public-main h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); font-weight: 700; }

/* --- Phones: slim top bar, bottom tabs, sidebar as a drawer --- */
.m-top, .m-tabs, .sb-scrim { display: none; }
@media (max-width: 900px) {
  body:has(.sidebar) { padding-left: 0; padding-bottom: 76px; }
  .sidebar { width: 288px; transform: translateX(-120%); box-shadow: 0 0 60px rgba(0, 0, 0, 0.25); background: var(--panel-solid); }
  html.sb-open .sidebar { transform: none; }
  .sb-collapse { display: none; }
  .sb-scrim { display: block; position: fixed; inset: 0; z-index: 45; background: rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transition: opacity 0.2s ease; }
  html.sb-open .sb-scrim { opacity: 1; pointer-events: auto; }
  .m-top {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    position: sticky; top: 0; z-index: 40; height: 56px; padding: 0 12px;
    background: var(--panel-solid); border-bottom: 1px solid var(--border);
  }
  .m-title { font-weight: 650; font-size: 15px; }
  .m-icon-btn {
    width: 38px; height: 38px; padding: 0; border-radius: 50%;
    background: transparent; border: none; color: var(--text);
    display: inline-flex; align-items: center; justify-content: center; text-decoration: none;
  }
  .m-ghost { visibility: hidden; }
  .m-tabs {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
    background: var(--panel-solid); border-top: 1px solid var(--border);
  }
  .m-tab {
    flex: 1; flex-direction: column; gap: 2px; padding: 4px 0;
    background: transparent; color: var(--muted); border-radius: 12px;
    font-size: 10.5px; font-weight: 600; text-decoration: none;
    display: flex; align-items: center;
  }
  .m-tab-ico { display: grid; place-items: center; width: 44px; height: 28px; border-radius: 14px; }
  .m-tab.active { color: var(--text); }
  .m-tab.active .m-tab-ico { background: var(--yellow-tint); }
  .page-head-new { padding: 18px 16px 4px; }
  .page-title { font-size: 24px; }
  .app-list, .logs-page-wrap, .detail-wrap, .settings-wrap, .mb-layout, .automation-card, .overview-grid, .events-page-wrap { padding-left: 16px; padding-right: 16px; }
  .public-bar { padding: 10px 16px; }
}
