@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;600&display=swap");

:root {
  --ink: #1b2559;
  --ink-soft: #6b7290;
  --label-blue: #1e3a8a;
  --border: #e3e8f7;
  --field-bg: #f6f7fc;
  --card-bg: #ffffff;
  --shadow: 0 20px 45px -20px rgba(60, 50, 130, 0.28);
  --radius: 22px;
  --required: #ef4a72;
  --page-bg:
    radial-gradient(900px 500px at 90% 0%, #eaf1ff 0%, transparent 55%),
    linear-gradient(160deg, #dbe7fb 0%, #d9ceF4 45%, #cdbdf2 75%, #c4b2ee 100%);
  --panel: rgba(255, 255, 255, 0.45);
  --panel-border: rgba(255, 255, 255, 0.6);
  --wordmark: #4338ca;
  --wordmark-gradient: linear-gradient(90deg, #4338ca, #6d5ce8, #8b7ff5, #6d5ce8, #4338ca);
  /* Light-theme treatment for the animated space-photo background —
     same photo as dark theme, brightened and shaded with a light
     lavender tint (matching this theme's own --page-bg palette above)
     instead of showing the photo dark. */
  --sf-filter: brightness(1.4) saturate(0.85) contrast(0.95);
  --sf-shade: linear-gradient(180deg, rgba(234,241,255,0.55) 0%, rgba(217,206,244,0.62) 45%, rgba(196,178,238,0.72) 100%);
  --sidebar-bg: rgba(255, 255, 255, 0.28);
  --sidebar-item-bg: rgba(255, 255, 255, 0.65);
  --sidebar-item-hover: rgba(255, 255, 255, 0.85);
  --pill-bg: rgba(255, 255, 255, 0.6);
  --pill-border: rgba(255, 255, 255, 0.75);
  --brand-row-bg: rgba(255, 255, 255, 0.3);
  --accent-gold: #c8912f;
  --accent-glow: rgba(99, 102, 241, 0.35);
  --font-display: "Space Grotesk", "Inter", -apple-system, sans-serif;
  --font-mono: "JetBrains Mono", "SFMono-Regular", Consolas, monospace;
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

[data-theme="dark"] {
  --ink: #eef1fb;
  --ink-soft: #9aa3c4;
  --label-blue: #a9c1ff;
  --border: #262c50;
  --field-bg: #1a2040;
  --card-bg: #141a33;
  --shadow: 0 20px 45px -20px rgba(0, 0, 0, 0.6);
  --page-bg:
    radial-gradient(650px 320px at 18% 100%, rgba(124, 108, 240, 0.28), transparent 70%),
    radial-gradient(650px 320px at 82% 100%, rgba(56, 130, 240, 0.24), transparent 70%),
    radial-gradient(1.5px 1.5px at 8% 12%, rgba(255, 255, 255, 0.55), transparent),
    radial-gradient(1.5px 1.5px at 22% 6%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(1.5px 1.5px at 38% 18%, rgba(255, 255, 255, 0.35), transparent),
    radial-gradient(1.5px 1.5px at 58% 9%, rgba(255, 255, 255, 0.45), transparent),
    radial-gradient(1.5px 1.5px at 73% 22%, rgba(255, 255, 255, 0.3), transparent),
    radial-gradient(1.5px 1.5px at 88% 14%, rgba(255, 255, 255, 0.4), transparent),
    radial-gradient(1.5px 1.5px at 13% 35%, rgba(255, 255, 255, 0.28), transparent),
    radial-gradient(1.5px 1.5px at 52% 40%, rgba(255, 255, 255, 0.26), transparent),
    radial-gradient(1.5px 1.5px at 95% 45%, rgba(255, 255, 255, 0.22), transparent),
    linear-gradient(160deg, #090c1c 0%, #10143a 45%, #191c4a 75%, #221a45 100%);
  --panel: rgba(18, 22, 46, 0.92);
  --panel-border: rgba(255, 255, 255, 0.08);
  --wordmark: #f3c463;
  --wordmark-gradient: linear-gradient(90deg, #e0a83f, #fff2cf, #d9a53c, #f3c463, #e0a83f);
  /* Dark-theme treatment for the animated space-photo background — the
     photo as-is, no brightening, shaded dark so foreground cards stay
     readable regardless of which part of the photo sits behind them. */
  --sf-filter: none;
  --sf-shade: linear-gradient(180deg, rgba(3,4,13,0.35) 0%, rgba(3,4,13,0.55) 55%, rgba(3,4,13,0.75) 100%);
  --sidebar-bg: rgba(15, 19, 40, 0.75);
  --sidebar-item-bg: rgba(15, 19, 40, 0.88);
  --sidebar-item-hover: rgba(28, 34, 66, 0.96);
  --pill-bg: rgba(255, 255, 255, 0.04);
  --pill-border: rgba(255, 255, 255, 0.1);
  --brand-row-bg: rgba(255, 255, 255, 0.03);
  --accent-gold: #f3c463;
  --accent-glow: rgba(124, 108, 240, 0.45);
}

* { box-sizing: border-box; }

/* Global scrollbar — thin and low-key by default (barely visible against
   the background) but still clearly shows scroll position/progress once
   you look for it or start dragging it; replaces every browser's fat,
   high-contrast default scrollbar everywhere on the site (sidebars,
   thread panels, modals, tables, all of it) with one consistent style.
   Firefox uses scrollbar-color/-width; everything else (Chrome, Edge,
   Safari) uses the ::-webkit-scrollbar-* pseudo-elements below — both
   are needed to cover all browsers. */
* { scrollbar-width: thin; scrollbar-color: var(--accent-glow) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--accent-glow); border-radius: 20px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: rgba(124, 108, 240, 0.75); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* The canvas background now lives on <html> (see the rule below), not on
   <body> — this is what lets the inline placeholder <style> in each
   page's <head> (first paint, before this file has even downloaded)
   hand off to this "real" rule with zero visible change: same photo,
   same shade gradient, same values, just now driven by a CSS variable
   instead of hard-coded numbers. If <body> also carried an opaque
   background here, it would paint *above* the negative-z-index
   #starfieldRoot layer once <html> owns its own background (the
   propagation trick that lets a bare <body> background become the page
   canvas background only applies while <html> itself specifies none) —
   so <body> is deliberately left transparent and the starfield/photo
   show straight through it. */
html {
  background-color: #090c1c;
  background-image: var(--sf-shade), url('/assets/img/bg-space.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed;
}
html[data-theme="light"] {
  /* Light theme skips the photo for this base layer entirely — the
     shade over it is already 55%~72% opaque, so a plain gradient in
     the exact same tones reads as visually identical while being one
     fewer thing to keep in sync with the placeholder <style>. */
  background-color: #dbe7fb;
  background-image: var(--page-bg);
}
body {
  margin: 0;
  min-height: 100vh;
  color: var(--ink);
  transition: color 0.2s ease;
}

/* ---- Animated space-photo background (both themes) ----
   Injected by assets/starfield.js as #starfieldRoot, prepended to
   <body>. Sits behind everything (negative z-index) — real page
   content (topbar, cards, sidebars) is completely unaffected and
   keeps using its own backgrounds; this only shows through the empty
   space around/behind them, same as the plain gradient it replaces.
   Dark theme shows the photo as-is with a dark shading gradient; light
   theme brightens the same photo (--sf-filter) and shades it with a
   light lavender-tinted overlay (--sf-shade) instead, so the exact same
   photo fits either theme's own palette rather than looking like a dark
   photo just dropped onto a light page. Not present at all when
   prefers-reduced-motion is set (the .sf-static variant below — same
   photo, no zoom/parallax/stars/meteors). */
#starfieldRoot { position: fixed; inset: 0; z-index: -2; overflow: hidden; pointer-events: none; }
.sf-bgwrap { position: fixed; inset: -4%; overflow: hidden; }
.sf-bgimg {
  position: absolute; inset: -3%;
  background-image: url('/assets/img/bg-space.jpg');
  background-size: cover; background-position: center;
  filter: var(--sf-filter, none);
  /* The mouse-parallax offset (set by starfield.js as this custom
     property) and the breathing zoom below are combined into ONE
     transform via keyframes that both reference --sf-parallax — setting
     transform directly from JS would otherwise fight with the CSS
     animation each frame, since both would be writing the same
     property. */
  transform: var(--sf-parallax, translate(0, 0)) scale(1);
  animation: sf-breathe 28s ease-in-out infinite;
}
@keyframes sf-breathe {
  0%, 100% { transform: var(--sf-parallax, translate(0, 0)) scale(1.0); }
  50%      { transform: var(--sf-parallax, translate(0, 0)) scale(1.055); }
}
.sf-shade { position: fixed; inset: 0; background: var(--sf-shade); }
.sf-stars { position: fixed; inset: 0; }
.sf-star { position: absolute; border-radius: 50%; background: #fff; animation: sf-twinkle linear infinite; }
@keyframes sf-twinkle { 0%, 100% { opacity: var(--sf-min-op, 0.15); } 50% { opacity: var(--sf-max-op, 1); } }
.sf-meteors { position: fixed; inset: 0; }
.sf-meteor {
  position: absolute; top: 0; left: 0; width: 120px; height: 2px;
  background: linear-gradient(90deg, rgba(255,255,255,0.9), rgba(255,255,255,0));
  filter: drop-shadow(0 0 4px rgba(255,255,255,0.7));
  opacity: 0; animation: sf-meteor-fall linear infinite;
}
@keyframes sf-meteor-fall {
  0% { opacity: 0; transform: translate(0,0) rotate(30deg); }
  3% { opacity: 1; } 45% { opacity: 1; }
  52% { opacity: 0; transform: translate(480px, 300px) rotate(30deg); }
  100% { opacity: 0; transform: translate(480px, 300px) rotate(30deg); }
}
/* prefers-reduced-motion — starfield.js skips generating stars/meteors
   and adds this class; belt-and-suspenders in CSS too in case markup
   ever ends up present regardless. */
.sf-static .sf-bgimg { animation: none; transform: none; }
.sf-static .sf-star, .sf-static .sf-meteor { display: none; }

/* threads.html only — locks the whole page to the viewport so the only
   thing that ever scrolls is the conversation area, and the reply bar
   stays genuinely pinned regardless of browser zoom level.
   height:100dvh/100vh is used directly on <body> instead of the previous
   `html:has(body.threads-page){height:100%} + body.threads-page
   {height:100%}` pair — percentage heights need <html> itself to have a
   definite height first, which is what that :has() selector existed to
   provide, but that makes this whole fixed-viewport system depend on
   :has() actually matching. Viewport units resolve directly against the
   viewport with NO ancestor sizing required at all, so this removes that
   dependency entirely — a strictly more robust base for a mechanism the
   reply box's visibility depends on. (100vh first as a plain fallback
   for any engine that doesn't recognize dvh; a browser that doesn't
   recognize a value ignores that whole declaration and keeps the
   previous one, it doesn't fall through to auto.) */
body.threads-page {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.threads-page .topbar { flex-shrink: 0; }
body.threads-page > .brand-row { flex-shrink: 0; }
body.threads-page .threads-topline { flex-shrink: 0; }
body.threads-page .threads-shell { flex: 1; height: auto; min-height: 0; }

/* Twemoji-rendered emoji (crisp, consistent across OS/browsers, Telegram-style) */
img.emoji {
  height: 1.15em;
  width: 1.15em;
  margin: 0 0.05em 0 0.1em;
  vertical-align: -0.2em;
}

a { color: inherit; text-decoration: none; }

.page {
  max-width: 980px;
  margin: 0 auto;
  padding: 48px 24px 96px;
}

/* ---- Top bar (shared across pages) ---- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--panel-border);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
}
.topbar-left { display: flex; align-items: center; gap: 10px; }

/* ---- HubNav mobile drawer (2026-08) — button injected by hub-nav.js
   into .topbar-left; hidden above 820px (see the @media block near
   .sidebar below), where the nav is always a normal in-flow column. ---- */
.hub-nav-toggle {
  display: none;
  align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0;
  background: var(--field-bg); border: 1.5px solid var(--border);
  color: var(--ink); font-size: 15px; cursor: pointer;
}
.hub-nav-toggle:hover { border-color: var(--accent-gold); }
.hub-nav-backdrop {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.5);
  /* Below the topbar's z-index:20 (see .topbar above) so the topbar —
     logo, clock, user pill — stays fully visible above the dimmed
     backdrop and the drawer itself, matching the reference screenshots
     (drawer opens BELOW the topbar row, never covering it). */
  z-index: 14;
}
body.hubnav-open .hub-nav-backdrop { display: block; }

/* ---- index.html's OWN sidebar drawer (2026-08 — ported from the INR
   build's proven implementation, see spa-shell.js's mount()). Separate
   mechanism from .hub-nav-toggle above: index.html has always kept its
   own, independent sidebar-rendering code rather than hub-nav.js (see
   that file's header comment), and — crucially — clicking a tool card
   from the hub does NOT navigate to a new page at all, it's an in-place
   SPA mount (see spa-shell.js) that keeps THIS sidebar on screen the
   whole time. .hub-nav-toggle only ever runs on a standalone page
   load (threads.html opened directly/bookmarked/refreshed), so it was
   never reachable via the hub's own card-click flow — this is the
   fix for that. Both start hidden/inert; only turned on inside the
   @media blocks further below. */
.sidebar-toggle-btn { display: none; }
.sidebar-backdrop {
  display: none;
  position: fixed; inset: 0; z-index: 44;
  background: rgba(10, 12, 25, 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.logo-badge {
  width: 34px; height: 34px; border-radius: 10px;
  background: linear-gradient(135deg, #6366f1, #38bdf8);
  color: #fff; font-weight: 800; font-size: 13px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.logo-img { height: 32px; width: auto; flex-shrink: 0; }
.wordmark {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 0.01em;
  /* Slow gradient drift — the gold/indigo tone slowly breathes/shifts
     rather than staying a flat color (dark theme: gold via
     --wordmark-gradient; light theme: indigo, same variable, different
     value — see the two [data-theme] blocks near the top of this file).
     Font-size (17px) was measured to visually match "HEYVIP" as it's
     actually rendered inside logo.webp, not an arbitrary "bigger"
     number — see PROJECT_STATUS.md for the reasoning if this ever needs
     revisiting. Respects prefers-reduced-motion below. */
  background: var(--wordmark-gradient);
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: wordmark-drift 6s ease-in-out infinite;
}
@keyframes wordmark-drift {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}
@media (prefers-reduced-motion: reduce) {
  .wordmark { animation: none; background-position: 0% 50%; }
}

.topbar-right { display: flex; align-items: center; gap: 10px; }
.clock {
  font-family: var(--font-mono);
  font-size: 12.5px; font-weight: 600; color: var(--ink-soft);
  background: var(--pill-bg); border: 1px solid var(--pill-border);
  padding: 7px 12px; border-radius: 999px;
  white-space: nowrap;
  display: inline-flex; align-items: center; gap: 8px;
}
/* Day / date / time each get their own color; a real vertical-bar
   divider (not just a "·" character) sits between each segment. */
.clock-day { color: #39ff88; text-shadow: 0 0 6px rgba(57, 255, 136, 0.55); }
.clock-date { color: #4fa8e8; }
.clock-time { color: var(--ink); font-weight: 700; }
.clock-divider {
  display: inline-block; width: 1px; height: 12px;
  background: rgba(148, 163, 184, 0.55);
}
.theme-pill {
  display: flex; align-items: center; gap: 6px;
  background: var(--pill-bg); border: 1px solid var(--pill-border);
  color: var(--ink); font-size: 12.5px; font-weight: 700;
  padding: 7px 14px; border-radius: 999px; cursor: pointer;
  transition: transform 0.12s ease;
}
.theme-pill:hover { transform: translateY(-1px); }

/* MERGED (2026-08-21) — country switcher, rebuilt as a fully custom
   dropdown instead of a native <select>. The native version (previous
   comment block, kept as a reminder of why this changed) had two real
   problems reported by the business owner: (1) the OPEN dropdown panel
   is OS/browser-rendered chrome that CSS genuinely cannot restyle in
   every browser — Safari in particular ignores `option { background }`
   entirely and falls back to its own light panel, so "doesn't match
   the dark theme" wasn't fixable while staying native; (2) the pill's
   own WIDTH was sized by its selected option's text ("India (INR)" vs
   "Philippines (PHP)" vs "All Countries" are very different lengths),
   so switching countries visibly reflowed the whole topbar-right flex
   row — that's the "抖动"/jitter that was reported. A custom
   button+panel widget fixes both: the panel is regular DOM we fully
   control the colors of, and the trigger button gets a fixed min-width
   sized to the LONGEST option label so picking a shorter one never
   shrinks the row. */
.country-switcher-mount { display: inline-flex; position: relative; }
.country-switcher-btn {
  display: inline-flex; align-items: center; gap: 8px; justify-content: space-between;
  background: var(--pill-bg); border: 1px solid var(--pill-border);
  color: var(--ink); font-size: 12.5px; font-weight: 700;
  padding: 7px 12px; border-radius: 999px; cursor: pointer;
  font-family: inherit; min-width: 172px; white-space: nowrap;
}
.country-switcher-btn:hover { border-color: var(--accent-gold); }
.country-switcher-btn:focus-visible { outline: 2px solid var(--accent-gold); outline-offset: 1px; }
.country-switcher-btn .cs-chevron { flex: none; transition: transform 0.15s ease; }
.country-switcher-btn.is-open .cs-chevron { transform: rotate(180deg); }
.country-switcher-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 40;
  min-width: 100%; background: #1a1f3a; border: 1px solid var(--pill-border);
  border-radius: 12px; padding: 4px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  display: none;
}
.country-switcher-panel.is-open { display: block; }
.country-switcher-option {
  padding: 8px 12px; border-radius: 8px; font-size: 12.5px; font-weight: 600;
  color: #eef1fb; cursor: pointer; white-space: nowrap;
}
.country-switcher-option:hover { background: rgba(255, 255, 255, 0.08); }
.country-switcher-option.is-selected { background: rgba(200, 145, 47, 0.22); color: #f3c463; font-weight: 700; }
.country-switcher-option.cs-all-option { border-bottom: 1px solid rgba(255, 255, 255, 0.1); margin-bottom: 4px; padding-bottom: 10px; }


/* ---- Brand row (decorative, hub page only) ---- */
.brand-row {
  position: relative;
  overflow: hidden;
  padding: 14px 0;
  border-bottom: 1px solid var(--panel-border);
  background: var(--brand-row-bg);
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  mask-image: linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
}
.brand-row-track {
  display: flex;
  gap: 10px;
  width: max-content;
  animation: brandRowScroll 26s linear infinite;
  /* MERGED (2026-08-21) — hints the browser to put this element on its
     own compositor layer, so its transform animation keeps running
     smoothly on the GPU even when the rest of the page is busy (e.g.
     the 15s/30s background polls this page already had, now competing
     with more JS overall since the multi-country merge) — a real
     periodic-stutter report from the business owner prompted this;
     transform-only animations SHOULD already be compositor-only in
     modern browsers, but this makes it explicit rather than relying on
     the browser to infer it, which is the standard fix for exactly
     this symptom. Cheap for a single always-visible element like this
     one — NOT something to sprinkle on many elements at once, since
     each will-change layer costs GPU memory. */
  will-change: transform;
}
.brand-row:hover .brand-row-track { animation-play-state: paused; }
@keyframes brandRowScroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .brand-row-track { animation: none; }
}
.brand-pill {
  display: flex; align-items: center; gap: 8px;
  background: var(--pill-bg); border: 1px solid var(--pill-border);
  padding: 6px 10px 6px 6px; border-radius: 999px;
  font-size: 12.5px; font-weight: 700; color: var(--ink);
  flex-shrink: 0;
  position: relative;
}
.brand-pill .avatar {
  width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 10.5px; font-weight: 800; color: #10131f;
  flex-shrink: 0;
  object-fit: cover;
}
.brand-pill .pill-name { cursor: default; }
/* .pill-edit removed 2026-08 — the inline pencil-icon shortcut is gone;
   editing now only happens via Integration Portal → Web Link. */

/* ---- Brand logo/link edit modal ---- */
.edit-modal-backdrop {
  display: flex;
  position: fixed; inset: 0; z-index: 50;
  background: rgba(10, 12, 25, 0.55);
  /* flex-start (not center) + generous top padding: with overflow-y:auto,
     align-items:center clips content that overflows ABOVE the viewport —
     you can scroll down to the bottom but never up to the true top. See
     php-issue-hub-完整优化记录.md §5.3 for the full writeup. */
  align-items: flex-start; justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s ease;
  overflow-y: auto;
  padding: 40px 12px;
}
.edit-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }
.edit-modal {
  width: 100%; max-width: 360px;
  background: var(--card-bg); border: 1px solid var(--panel-border);
  border-radius: 18px; padding: 22px 24px 24px;
  box-shadow: var(--shadow);
  transform: scale(0.96) translateY(6px);
  transition: transform 0.18s ease;
  position: relative;
}
.edit-modal-backdrop.is-open .edit-modal { transform: scale(1) translateY(0); }
.modal-close-btn {
  position: absolute; top: 16px; right: 16px;
  width: 28px; height: 28px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--field-bg); border: 1px solid var(--border); color: var(--ink-soft);
  font-size: 13px; line-height: 1; cursor: pointer; transition: all 0.15s ease;
}
.modal-close-btn:hover { background: rgba(224, 87, 122, 0.15); border-color: #e0577a; color: #e0577a; }
.edit-modal-header-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 34px 14px 0; }
.edit-modal-title { font-family: var(--font-display); font-weight: 600; font-size: 16px; margin: 0; padding-right: 34px; }
.edit-modal-header-row .edit-modal-title { padding-right: 0; }
.acct-role-filter {
  background: var(--field-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 6px 10px; font-size: 12px; font-weight: 600; color: var(--ink); font-family: inherit; cursor: pointer;
}
.acct-profile-search {
  background: var(--field-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 6px 10px; font-size: 12px; color: var(--ink); font-family: inherit;
  width: 220px; margin-right: 8px;
}
.acct-profile-search:focus { outline: none; border-color: var(--accent-gold); }
.edit-modal-note { min-height: 16px; font-size: 12px; font-weight: 600; color: var(--ink-soft); margin: 0 0 10px; }
.edit-modal-note.ok { color: #1a9c6e; }
.edit-modal-note.err { color: #e0577a; }
.edit-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 6px; }
.btn-secondary {
  border: 1.5px solid var(--border); background: none; color: var(--ink);
  border-radius: 10px; padding: 9px 16px; font-size: 13px; font-weight: 700; cursor: pointer;
}
.edit-modal .btn-submit { width: auto; padding: 9px 18px; }
.edit-modal.wide { max-width: 940px; }

.agent-profile-table-wrap { max-height: 420px; overflow-y: auto; overflow-x: auto; }
.agent-profile-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.agent-profile-table th {
  position: sticky; top: 0; text-align: left; font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.03em; color: var(--ink-soft); font-weight: 700; padding: 8px 8px;
  background: var(--card-bg); border-bottom: 1px solid var(--border);
  border-right: 1px solid var(--panel-border);
}
.agent-profile-table th:last-child { border-right: none; }
.agent-profile-table td {
  padding: 8px; border-bottom: 1px solid var(--panel-border); vertical-align: middle; color: var(--ink);
  border-right: 1px solid var(--panel-border);
}
.agent-profile-table td:last-child { border-right: none; }
.agent-profile-table input {
  width: 100%; background: var(--field-bg); border: 1px solid var(--border); border-radius: 6px;
  padding: 4px 6px; font-size: 12px; color: var(--ink); font-family: inherit;
}
/* Inline role-edit dropdown — matches the "All roles" filter's look
   (.acct-role-filter above) instead of the browser's bare default
   <select> styling, just sized to fit inside a table cell. */
.agent-profile-table select.edit-role {
  width: 100%; background: var(--field-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 5px 8px; font-size: 12px; font-weight: 600; color: var(--ink); font-family: inherit; cursor: pointer;
}
.agent-profile-table .role-tag {
  display: inline-block; font-size: 9.5px; font-weight: 700; text-transform: uppercase;
  padding: 2px 6px; border-radius: 6px; background: var(--field-bg); color: var(--ink-soft);
}
.agent-profile-table .pw-by { color: var(--ink-soft); font-size: 10.5px; display: block; }
.agent-profile-table .office-missing { color: #e0577a; font-weight: 700; font-size: 11.5px; }
.agent-profile-table .office-none { color: var(--ink-soft); font-size: 11.5px; }
.agent-profile-table .row-btn { background: none; border: none; cursor: pointer; font-size: 13px; padding: 2px 4px; }
/* Username-as-entry-point into the Agent Personal Profile popup — reset
   <button> defaults so it still reads as a plain table cell with a
   clickable-link affordance, same underline-on-hover treatment
   .msg-attach-link already uses elsewhere for the same reason. */
.agent-profile-table .link-btn {
  border: none; background: none; padding: 0; margin: 0; font: inherit; font-size: 12.5px;
  color: var(--label-blue); text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.agent-profile-table .link-btn:hover { color: var(--accent-gold); }

/* ---- Agent Personal Profile popup (opened by clicking a username in
   the table above) — field groups inside .edit-details-fields ---- */
.agent-profile-section-label {
  font-size: 11px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 8px;
}
.agent-profile-locked-note { text-transform: none; font-weight: 600; letter-spacing: 0; color: var(--ink-soft); }
.agent-profile-status-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  border: 1.5px solid var(--border); background: var(--field-bg); border-radius: 12px;
  padding: 0 10px; height: 44px; box-sizing: border-box;
}
.agent-profile-status-row .icon-btn { flex-shrink: 0; }
.agent-profile-status-text { font-size: 14px; font-weight: 700; }
.agent-profile-status-text.is-active { color: #1a9c6e; }
.agent-profile-status-text.is-locked { color: var(--required); }
.agent-profile-module-list { display: flex; flex-direction: column; gap: 2px; }
.agent-profile-admin-section-list { display: flex; flex-direction: column; gap: 2px; }
.agent-profile-collapsible { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
.agent-profile-collapsible-header {
  width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
  padding: 0; margin: 0 0 10px; background: none; border: none; cursor: pointer; font-family: inherit; text-align: left;
}
.agent-profile-collapsible-header:hover .agent-profile-section-label-lg { color: var(--wordmark); }
.agent-profile-section-label-lg {
  font-size: 12px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink); display: block;
}
.agent-profile-section-hint {
  display: block; font-size: 11px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--ink-soft); margin-top: 2px;
}
.agent-profile-chev { color: var(--ink-soft); font-size: 13px; flex-shrink: 0; transition: transform 0.15s ease; }
.agent-profile-collapsible.is-open .agent-profile-chev { transform: rotate(90deg); }
.agent-profile-collapsible-body { padding: 0; }
.agent-profile-divider { border: none; border-top: 1px solid var(--border); margin: 10px 0; }
.agent-profile-edit-level-row {
  display: flex; align-items: center; gap: 14px; margin: 0 0 6px 26px;
  font-size: 12px; color: var(--ink-soft);
}
.agent-profile-edit-level-row label { display: flex; align-items: center; gap: 5px; cursor: pointer; }
.agent-profile-module-check {
  display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 4px 2px;
  color: var(--ink);
}
.agent-profile-module-check input[disabled] { cursor: not-allowed; }
/* MERGED (2026-08-21) — "botToken" row when the viewer isn't Owner —
   see adminSectionChecksHtml()'s own comment for why it's disabled-not-
   hidden. Slightly dimmed + a small badge, same visual language as
   "disabled" elsewhere in this app rather than a new pattern. */
.ap-force-disabled { opacity: 0.55; }
.ap-owner-only-tag {
  font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--ink-soft); background: var(--field-bg); border: 1px solid var(--border);
  padding: 1px 6px; border-radius: 999px; margin-left: 2px;
}

/* ---- TG Group / Channel (live-editable Telegram routing) ---- */
.tgroute-layout { display: flex; gap: 14px; max-height: 78vh; }
.tgroute-brands {
  flex-shrink: 0; width: 150px; display: flex; flex-direction: column; gap: 6px;
  overflow-y: auto; padding-right: 4px;
}
.tgroute-brand {
  padding: 10px 12px; border-radius: 10px; font-size: 12.5px; font-weight: 700;
  color: var(--ink-soft); background: var(--field-bg); border: 1.5px solid var(--border);
  cursor: pointer; transition: all 0.15s ease;
}
.tgroute-brand:hover { border-color: rgba(200, 145, 47, 0.5); color: var(--ink); }
.tgroute-brand.selected {
  background: linear-gradient(135deg, rgba(200, 145, 47, 0.28), rgba(200, 145, 47, 0.14));
  border-color: var(--accent-gold); color: #f3c463;
}
/* Visually separated from the brand list above it — this isn't a brand,
   it's the login-security alerts row (see resolveSecurityAlertsRoute in
   functions/api/auth/login.js). */
.tgroute-brand.tgroute-security {
  margin-top: 10px; padding-top: 12px; border-top: 1.5px dashed var(--border);
}
.tgroute-brand.tgroute-security.selected {
  border-color: var(--accent-gold);
}
/* MERGED (2026-08-21) — "All Countries" mode's country group headers in
   the brand sidebar (see groupedBrandListHtml() in index.html) — a
   plain label, not a clickable pill, sitting between groups of brand
   items so a mixed-country list is scannable instead of one flat blob. */
.tgroute-brand-group-label {
  font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ink-soft); opacity: 0.65; padding: 10px 4px 2px;
}
.tgroute-brand-group-label:first-child { padding-top: 0; }
.tgroute-modules { flex: 1; min-width: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.tgroute-row {
  border: 1px solid var(--panel-border); border-radius: 12px; padding: 10px 12px;
  background: var(--field-bg);
}
.tgroute-mod-name {
  font-size: 12.5px; font-weight: 700; color: var(--ink);
  display: flex; align-items: center; gap: 6px; margin-bottom: 8px;
}
.route-tag {
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 2px 6px; border-radius: 6px; background: var(--card-bg); color: var(--ink-soft);
  border: 1px solid var(--border);
}
.route-tag.custom { color: var(--accent-gold); border-color: var(--accent-gold); background: rgba(200, 145, 47, 0.14); }
.tgroute-fields { display: flex; align-items: center; gap: 8px; }
.tgroute-fields input {
  flex: 1; min-width: 0; background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 9px; font-size: 12px; color: var(--ink); font-family: inherit;
}
.tgroute-row-actions { display: flex; flex-shrink: 0; gap: 8px; }
.mention-backfill-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-top: 18px; }
.mention-backfill-copy { flex: 1; min-width: 0; }
.mention-backfill-title { font-size: 13px; font-weight: 700; color: var(--ink); margin-bottom: 6px; }
.mention-backfill-action { flex-shrink: 0; text-align: right; }
.mention-backfill-action .edit-modal-note { margin: 8px 0 0; white-space: nowrap; }
.tgroute-action-btn {
  font-size: 11.5px; font-weight: 700; padding: 7px 14px; border-radius: 8px;
  cursor: pointer; border: 1.5px solid var(--accent-gold); color: #0f1220;
  background: var(--accent-gold); transition: all 0.15s ease; white-space: nowrap;
}
.tgroute-action-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px -4px rgba(200, 145, 47, 0.5); }
.tgroute-action-btn.tgroute-action-secondary {
  background: none; color: var(--ink-soft); border-color: var(--border);
}
.tgroute-action-btn.tgroute-action-secondary:hover { border-color: var(--ink-soft); color: var(--ink); box-shadow: none; }
@media (max-width: 720px) {
  .tgroute-layout { flex-direction: column; max-height: 70vh; }
  .tgroute-brands { width: 100%; flex-direction: row; flex-wrap: wrap; }
  /* Force the group label onto its own full-width line in this wrapped
     row layout — without this it would just size to its text content
     and sit inline between brand pills like just another pill. */
  .tgroute-brand-group-label { flex-basis: 100%; }
  .tgroute-fields { flex-wrap: wrap; }
  .tgroute-row-actions { width: 100%; justify-content: flex-end; }
}
.tgroute-divider { border: none; border-top: 1px solid var(--panel-border); margin: 18px 0 0; }
.tgroute-footnote { margin-top: 12px; }

/* ---- IP Access panel (Account Management → IP Access) — replaces the
   old single-office Whitelist IP form. Lives inside the same .edit-modal
   (opened via openAcctModal("whitelist"), .wide sized) as tgroutes/
   settings/etc. above; see the big IP Access JS block near
   openAcctModal() in index.html. ---- */
.ipa-header-actions { display: flex; gap: 8px; flex-shrink: 0; }
.ipa-header-btn {
  background: var(--field-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 7px 14px; font-size: 12px; font-weight: 700; color: var(--ink);
  cursor: pointer; white-space: nowrap; transition: border-color 0.15s ease, color 0.15s ease;
}
.ipa-header-btn:hover { border-color: var(--accent-gold); color: var(--accent-gold); }

.ipa-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 16px; }
.ipa-stat-card {
  text-align: left; background: var(--field-bg); border: 1.5px solid var(--border); border-radius: 12px;
  padding: 12px 14px; cursor: pointer; transition: border-color 0.15s ease, transform 0.15s ease;
}
.ipa-stat-card:hover { transform: translateY(-1px); border-color: var(--accent-gold); }
.ipa-stat-card.active { border-color: var(--accent-gold); box-shadow: 0 0 0 1px var(--accent-gold) inset; }
.ipa-stat-label { display: block; font-size: 10px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 4px; }
.ipa-stat-value { display: block; font-family: var(--font-display); font-size: 24px; font-weight: 600; color: var(--ink); }
.ipa-stat-value.ipa-green { color: #34d399; }
.ipa-stat-value.ipa-gold { color: var(--accent-gold); }
.ipa-stat-value.ipa-red { color: #ef4a72; }

.ipa-section { border: 1.5px solid var(--border); border-radius: 12px; margin-bottom: 10px; overflow: hidden; }
.ipa-section-header {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  background: var(--field-bg); border: none; padding: 12px 16px;
  font-size: 13px; font-weight: 700; color: var(--ink); cursor: pointer; text-align: left;
}
.ipa-chevron { color: var(--ink-soft); transition: transform 0.15s ease; font-size: 12px; }
.ipa-chevron.open { transform: rotate(180deg); }
.ipa-section-body { padding: 14px 16px 16px; }
.ipa-form-grid {
  display: grid;
  /* minmax(0, 1fr) — NOT bare "1fr" — for the flexible middle column.
     A plain `1fr` track's implicit minimum is "auto" (the content's own
     min-content width), not 0 — if that content (the IP address input,
     which has a fairly wide placeholder) needs more room than is
     actually available, the TRACK REFUSES TO SHRINK below that and the
     row overflows instead, which is exactly what made this whole
     section render squeezed/misaligned with text cut off (reported
     with screenshots). Wrapping it in minmax(0, 1fr) removes that
     implicit floor so the track can actually shrink to fit. */
  grid-template-columns: minmax(170px, 220px) minmax(0, 1fr) auto;
  gap: 12px;
  align-items: flex-end;
}
/* Matches .ipa-form-grid's first column exactly (same minmax formula) so
   the "Block an IP" row's IP ADDRESS(ES) box lines up edge-to-edge with
   "Add IP manually"'s OFFICE box above it — the two rows previously used
   different column formulas here (equal-weight vs fixed-width first
   column), which kept their boxes from aligning vertically between the
   two stacked sections (reported with screenshots). */
.ipa-form-grid.ipa-form-2col { grid-template-columns: minmax(170px, 220px) minmax(0, 1fr) auto; }
.ipa-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ipa-field-grow { min-width: 0; }
.ipa-field label { font-size: 10px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--label-blue); }
.ipa-field select, .ipa-field input {
  width: 100%; box-sizing: border-box; height: 38px;
  background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 0 11px; font-size: 13px; color: var(--ink); font-family: inherit;
  text-overflow: ellipsis;
}
/* Native <select> chrome (internal padding + built-in arrow) is controlled
   by the browser/OS, not by our padding/border rules, so even with
   identical CSS the select's rendered box height drifts a few px from the
   text <input> next to it in the same row — the two boxes don't line up
   (reported with screenshots). appearance:none strips that native chrome
   so the explicit height above is what actually renders; we draw our own
   arrow since removing native appearance also removes the built-in one. */
.ipa-field select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%239aa3c4' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='5 8 10 13 15 8'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center; background-size: 14px;
  padding-right: 32px;
}
/* <select> has a long-standing browser quirk: unlike <input>, it refuses to
   shrink narrower than its widest <option>'s intrinsic content width, even
   with width:100% set on a narrower parent. When the OFFICE column (clamped
   170-220px) is narrower than an office name needs, the select just
   overflows past its grid column and visually sits on top of the next
   field (IP ADDRESS(ES)) instead of respecting the layout — reported with
   screenshots. min-width:0 + overflow:hidden forces it to actually honor
   the 100% width and ellipsis-truncate the option text instead. */
.ipa-field select { min-width: 0; overflow: hidden; white-space: nowrap; }
.ipa-field select:focus, .ipa-field input:focus { outline: none; border-color: var(--accent-gold); }
.ipa-btn-primary, .ipa-btn-secondary {
  border: none; border-radius: 10px; padding: 9px 20px; font-size: 13px; font-weight: 700; cursor: pointer;
  white-space: nowrap; height: 38px;
}
.ipa-btn-primary { background: linear-gradient(90deg, #f3c463, #e0a83f); color: #241c05; }
.ipa-btn-primary:hover { filter: brightness(1.05); }
.ipa-btn-secondary { background: none; border: 1.5px solid var(--border); color: var(--ink); }
.ipa-btn-secondary:hover { border-color: var(--accent-gold); color: var(--accent-gold); }
.ipa-btn-primary:disabled, .ipa-btn-secondary:disabled { opacity: 0.5; cursor: not-allowed; }
.ipa-hint { margin: 10px 0 0; font-size: 11px; color: var(--ink-soft); line-height: 1.5; }

.ipa-hint-footer { text-align: center; font-size: 12px; color: var(--ink-soft); margin: 18px 0 0; }
.ipa-tables { margin-top: 4px; }
.ipa-settled-note { font-size: 11.5px; color: var(--ink-soft); margin: 4px 0 12px; }
.ipa-table-block { margin-bottom: 18px; }
.ipa-table-block:last-child { margin-bottom: 0; }
.ipa-table-head-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.ipa-table-title {
  font-family: var(--font-display); font-weight: 600; font-size: 13.5px; color: var(--ink);
  display: flex; align-items: center; gap: 8px;
}
.ipa-table-count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px;
  padding: 0 5px; border-radius: 999px; background: var(--accent-gold); color: #241c05;
  font-size: 10.5px; font-weight: 800;
}
.ipa-page-size {
  background: var(--field-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 5px 10px; font-size: 11.5px; font-weight: 600; color: var(--ink); font-family: inherit; cursor: pointer;
}
.ipa-page-size:focus { outline: none; border-color: var(--accent-gold); }
.ipa-pagination {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  margin-top: 10px; font-size: 12px;
}
.ipa-page-link {
  background: none; border: none; color: var(--label-blue); font-weight: 700; font-size: 12px;
  cursor: pointer; padding: 2px 4px; text-decoration: underline; text-underline-offset: 2px;
}
.ipa-page-link:hover:not(:disabled) { color: var(--accent-gold); }
.ipa-page-link:disabled { color: var(--ink-soft); text-decoration: none; cursor: default; opacity: 0.55; }
.ipa-page-status { color: var(--ink-soft); font-weight: 600; }
/* The Pending/Approved/Blocked tables are already paginated (10/20/30
   rows per page via the page-size picker), so the row count on screen
   is always bounded — a fixed max-height + overflow-y:auto here was
   redundant AND harmful: it clipped the last row mid-cell and drew a
   native scrollbar inside the rounded border, which is exactly the
   stray-scrollbar-in-the-middle-of-the-table look reported with
   screenshots. Dropping the vertical scroll lets the box size itself
   to its content (matching the reference screenshot), so the top and
   bottom padding — and the divider under the last row — end up even.
   overflow-x stays so a very narrow viewport can still scroll
   sideways instead of breaking the layout. */
.ipa-table-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: 12px; }
/* The Record popover is the one place this component shows an
   UNPAGINATED, potentially long list (the full action log), so it
   still needs its own vertical scroll — unlike the tables above. */
.ipa-record-scroll { max-height: 340px; overflow-y: auto; }
/* Every cell (header AND body) gets a full grid of dividers, and every
   cell's content is centered — the two specific things asked for, on
   top of the borders .agent-profile-table already had.
   border-collapse:separate (NOT collapse) is deliberate: a sticky <th>
   (position:sticky, needed so the header stays visible while the body
   scrolls) inside a collapsed-border table is a known cross-browser
   rendering conflict — the browser has to break the "merge adjacent
   cell borders into one" model to keep the header pinned above the
   scrolling body, and different engines resolve that inconsistently,
   which is exactly what produced the table visually tearing into two
   separate boxes (reported with screenshots). border-spacing:0 keeps
   the separate model from adding any visible gap between cells, so the
   divider look (via each cell's own border-right/border-bottom below)
   ends up identical either way — just without the sticky-header bug. */
.ipa-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 12px; }
.ipa-table th {
  /* NOT position:sticky (removed) — a sticky <th> inside a container
     that scrolls on BOTH axes (.ipa-table-scroll has overflow-x AND
     overflow-y:auto) is a known source of cross-browser rendering
     corruption, and switching border-collapse to "separate" (see the
     .ipa-table rule above) reduced it but didn't fully eliminate it —
     still reported, with screenshots, as the table visually tearing
     into separate boxes with a stray scrollbar rendered mid-table.
     The scroll area here only holds up to ~260px of content anyway, so
     a header that stays pinned while scrolling isn't worth carrying
     that risk for — a plain (non-sticky) header that scrolls away with
     the rest of the table is a small UX trade for a table that
     actually renders correctly every time. */
  text-align: center; font-size: 10px; text-transform: uppercase;
  letter-spacing: 0.04em; color: var(--ink-soft); font-weight: 700; padding: 10px 10px;
  background: var(--card-bg); border-bottom: 1.5px solid var(--border); border-right: 1px solid var(--panel-border);
}
.ipa-table th:last-child { border-right: none; }
.ipa-table td {
  padding: 9px 10px; text-align: center; vertical-align: middle; color: var(--ink);
  border-bottom: 1px solid var(--panel-border); border-right: 1px solid var(--panel-border);
}
.ipa-table td:last-child { border-right: none; }
.ipa-table tr:last-child td { border-bottom: none; }
.ipa-table .ipa-ip { color: #34d399; font-family: var(--font-mono); font-weight: 600; letter-spacing: 0.01em; }
.ipa-table .ipa-empty { color: var(--ink-soft); font-style: italic; padding: 18px 10px; }
.ipa-actions { white-space: nowrap; }
.ipa-link { background: none; border: none; cursor: pointer; font-size: 11.5px; font-weight: 700; padding: 2px 6px; text-decoration: underline; text-underline-offset: 2px; }
.ipa-link.ok { color: #34d399; }
.ipa-link.err { color: #ef4a72; }
.ipa-link + .ipa-link { margin-left: 4px; }

/* "Manage offices" / "Record" — anchored INSIDE the modal, near the
   header buttons that open them (not separate centered backdrops — see
   the reference screenshots this was built from). Positioned
   absolutely against .edit-modal (position:relative already, see
   .edit-modal rule above), so they float over whatever the panel body
   is currently showing without shifting any of that content. */
/* Dark scrim behind the "Manage offices"/"Record" popovers — covers the
   whole .edit-modal (not just the area behind the popover box itself),
   so the IP Access panel underneath reads as visually backgrounded/
   inactive while a popover is open, same as how the outer
   .edit-modal-backdrop itself dims the page behind IT. Toggled by the
   same JS that shows/hides each .ipa-popover (see toggleIpaPopover() —
   it also toggles this). */
.ipa-popover-scrim {
  position: absolute; inset: 0; z-index: 4;
  background: rgba(6, 8, 18, 0.6);
  border-radius: inherit;
}
.ipa-popover {
  position: absolute;
  top: 58px; left: 24px; right: 24px;
  max-height: calc(100% - 82px);
  overflow-y: auto;
  background: var(--card-bg); border: 1.5px solid var(--accent-gold); border-radius: 14px;
  padding: 18px 20px 20px;
  box-shadow: 0 20px 45px -12px rgba(0, 0, 0, 0.45);
  z-index: 5;
}
.ipa-popover-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.ipa-popover-title { font-family: var(--font-display); font-weight: 600; font-size: 15px; margin: 0; color: var(--ink); }
.ipa-popover-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
#ipaOfficesPopover textarea {
  width: 100%; background: var(--card-bg); border: 1.5px solid var(--border); border-radius: 8px;
  padding: 9px 11px; font-size: 13px; color: var(--ink); font-family: inherit; resize: vertical;
}
#ipaOfficesPopover textarea:focus { outline: none; border-color: var(--accent-gold); }

@media (max-width: 820px) {
  .ipa-stats { grid-template-columns: repeat(2, 1fr); }
  .ipa-form-grid, .ipa-form-grid.ipa-form-2col { grid-template-columns: 1fr; align-items: stretch; }
  .ipa-btn-primary, .ipa-btn-secondary { width: 100%; }
  .ipa-popover { left: 12px; right: 12px; top: 52px; }
}

/* ---- Active Agents popup (Home → tool-card grid → #activeAgentsCard)
   Lives in its own .edit-modal-backdrop#aaModalBackdrop, separate from
   #acctModalBackdrop above so its open/close state and internal popover
   (Record) never collide with Account Management's. See the JS block
   near "window.ActiveAgentsModal" further down in index.html. ---- */
.aa-modal-header {
  display: flex; align-items: center; gap: 12px;
  margin: 0 34px 4px 0; flex-wrap: wrap;
}
.aa-modal-header-icon {
  width: 34px; height: 34px; border-radius: 10px; flex-shrink: 0;
  background: #34d39933; display: flex; align-items: center; justify-content: center;
  font-size: 15px;
}
.aa-modal-header-text { flex: 1; min-width: 160px; }
.aa-modal-subtitle { margin: 2px 0 0; font-size: 11px; color: var(--ink-soft); }
.aa-online-badge {
  display: inline-flex; align-items: center; gap: 5px;
  background: rgba(52, 211, 153, 0.12); border: 1.5px solid rgba(52, 211, 153, 0.4);
  color: #1a9c6e; border-radius: 999px; padding: 5px 12px;
  font-size: 11.5px; font-weight: 700; white-space: nowrap;
}
html[data-theme="light"] .aa-online-badge { color: #16805c; }

.aa-list { display: flex; flex-direction: column; gap: 8px; max-height: 52vh; overflow-y: auto; padding-right: 2px; }
.aa-row {
  display: flex; align-items: center; gap: 12px;
  border: 1.5px solid var(--border); border-radius: 12px;
  padding: 11px 10px 11px 14px;
}
.aa-row.is-offline { opacity: 0.75; }
.aa-row-avatar { position: relative; flex-shrink: 0; }
.aa-row-avatar-circle {
  width: 34px; height: 34px; border-radius: 9px; background: var(--field-bg);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px;
}
.aa-row-badge {
  position: absolute; bottom: -2px; right: -2px; width: 11px; height: 11px;
  border-radius: 50%; border: 2px solid var(--card-bg);
}
.aa-row-main { flex: 1; min-width: 0; }
.aa-row-name { color: var(--ink); font-weight: 700; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Icon-pill tags (role / device) — rounded-full with a small icon, closer
   to the reference design than the earlier plain rounded-rect tags. */
.aa-tags { display: flex; gap: 6px; margin-top: 5px; flex-wrap: wrap; }
.aa-tag {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--ink-soft); font-size: 10.5px; font-weight: 600;
  background: var(--field-bg); border-radius: 999px; padding: 3px 9px 3px 7px;
}
.aa-row-status { width: 100px; flex-shrink: 0; }
.aa-row-status-line { display: flex; align-items: center; gap: 6px; justify-content: flex-end; }
.aa-row-status-time { color: var(--ink-soft); font-size: 10.5px; margin-top: 2px; text-align: right; }

.aa-dot-online { background: #34d399; animation: aaBreathe 1.8s ease-in-out infinite; }
.aa-dot-gray { background: #5f5e5a; }
@keyframes aaBreathe {
  0%, 100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.55); }
  50% { box-shadow: 0 0 0 6px rgba(52, 211, 153, 0); }
}
.aa-footer {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--panel-border);
  font-size: 10.5px; color: var(--ink-soft);
}
.aa-footer-live { display: inline-flex; align-items: center; gap: 5px; }
.aa-record-pick:hover { background: var(--sidebar-item-hover); }
.aa-day-pick:hover { background: var(--field-bg); }
/* #aaRecordPopover overrides .ipa-popover's left:24px/right:24px with an
   explicit centered transform. Needed because this popover ALSO sets an
   inline max-width:820px (see ACTIVE-AGENTS-SPEC.md §2) while its parent
   .edit-modal.wide is 940px — with left+right+max-width all constrained
   at once, the CSS spec resolves the over-constraint by dropping "right"
   and anchoring to the left, which is what made it visibly hug the left
   edge with dead space on the right instead of sitting centered. Scoped
   to this ID only (not a change to the shared .ipa-popover class) so the
   IP Access modal's own popovers, which don't hit this over-constraint
   (no max-width set on them), are untouched. */
#aaRecordPopover {
  left: 50%; right: auto;
  width: calc(100% - 48px);
  transform: translateX(-50%);
}
@media (max-width: 720px) {
  .aa-modal-header { margin-right: 0; }
  #aaModalBackdrop .edit-modal.wide { max-width: 100%; }
}
@media (max-width: 480px) {
  #aaRecordPopover table, #aaRecordPopover thead, #aaRecordPopover tbody, #aaRecordPopover th, #aaRecordPopover td { white-space: normal !important; }
}

/* ---- Settings (Maintenance/Coming soon toggle) removed 2026-08 — this
   used to size the .fs-fields/.fs-dropdown rows (settings-dropdown.css,
   also removed) that lived here. See renderSettingsPanel() in
   index.html for what's left in this modal. */


/* ---- Theme toggle (legacy floating button, kept for old pages) ---- */
.theme-toggle {
  position: fixed;
  top: 20px;
  right: 20px;
  z-index: 10;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: var(--card-bg);
  color: var(--ink);
  font-size: 18px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow);
}
.theme-toggle:hover { transform: translateY(-1px); }

/* ---- Sidebar hub layout ---- */
/* ---- Fixed-viewport hub page: topbar stays put, sidebar scrolls
   independently, right column (brand ticker + banner + main content)
   is its own scroll container. Same pattern already proven on
   threads.html (body.threads-page) — see
   php-issue-hub-完整优化记录.md §6 for the writeup this was ported from.
   height:100dvh/100vh directly on <body> instead of relying on
   `html:has(body.hub-page){height:100%}` to give <html> a definite
   height first — see body.threads-page above for why. */
body.hub-page {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.hub-page .topbar { flex-shrink: 0; }

/* ---- Generic fixed-viewport 2-column shell for sub-pages that get the
   shared "Issue Submission" nav (form.html, promo.html, deposit-issue.html,
   deposit-backup.html) — mirrors body.hub-page's pattern above. Reuses
   .sidebar as-is for the nav column. ---- */
body.subpage-fixed {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
body.subpage-fixed .topbar { flex-shrink: 0; }
.subpage-shell { display: flex; flex: 1; min-height: 0; }
.subpage-right-col { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* Same wrapper pattern as index.html's .hub-content-col — keeps the
   marquee inside the content column, beside the sidebar, not spanning
   over it. */
.subpage-content-col { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.subpage-content-col > .brand-row { flex-shrink: 0; }
.subpage-right-col > #announcementBanner { flex-shrink: 0; }
.subpage-main { flex: 1; min-height: 0; overflow-y: auto; }

/* ---- SPA shell (index.html only) — #spaMount swaps in a fetched
   route's content in place of #viewHome; see spa-shell.js ---- */
#spaMount { min-width: 0; min-height: 0; }
.spa-loading {
  flex: 1; display: flex; align-items: center; justify-content: center;
  color: var(--ink-soft); font-size: 13px; letter-spacing: 0.02em;
}
.spa-loading.spa-error {
  flex-direction: column; gap: 12px; text-align: center; padding: 0 24px;
}
.spa-retry-btn {
  border: 1px solid var(--panel-border); background: var(--pill-bg);
  color: var(--ink); font-size: 13px; font-weight: 700;
  padding: 8px 18px; border-radius: 999px; cursor: pointer;
}
.spa-retry-btn:hover { background: var(--pill-border); }

.hub-layout {
  display: flex;
  flex: 1;
  min-height: 0; /* flex children default to min-height:auto and will
                    otherwise refuse to shrink below their content size,
                    silently breaking the "independent scroll" effect */
}
/* Wraps the persistent marquee + whichever of #viewHome/#spaMount is
   currently showing — sits BESIDE .sidebar (not above it, and not
   spanning the sidebar's width), so the marquee only ever occupies the
   same column the page content already lives in. Matches the INR
   build's layout exactly. */
.hub-content-col {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.hub-content-col > .brand-row { flex-shrink: 0; }
.sidebar {
  width: 290px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--panel-border);
  padding: 24px 16px 40px;
  overflow-y: auto;
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
}

/* ---- HubNav small-screen behavior (2026-08) ---- Scoped to
   .threads-shell/.subpage-shell only — i.e. every page that mounts the
   SHARED HubNav component (threads.html, deposit-issue.html,
   deposit-backup.html, promo.html, betting-resources.html, form.html,
   announcements.html; see hub-nav.js's own header comment). index.html
   has its own separate, unrelated .hub-layout > .sidebar and is
   deliberately NOT touched here — this was scoped to "after clicking
   into a tool card" specifically, not the hub page itself.
   Below 820px: hidden by default (off-canvas, translateX(-105%)) and
   toggled open as a fixed drawer via the hamburger button hub-nav.js
   injects into .topbar-left — opening it never reflows or shrinks the
   main content column next to it (.sidebar leaves the flex layout
   entirely once it's `position: fixed`, so that column just renders at
   its natural full width the whole time, whether the drawer is open or
   closed). Above 820px: no rule here applies at all, so .sidebar stays
   the normal 290px in-flow column above, exactly as before, and the
   hamburger button stays hidden (see .hub-nav-toggle above). */
@media (max-width: 820px) {
  .hub-nav-toggle { display: flex; }
  .threads-shell .sidebar,
  .subpage-shell .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100dvh;
    width: 280px;
    max-width: 82vw;
    z-index: 15;
    transform: translateX(-105%);
    transition: transform 0.22s ease;
    box-shadow: 12px 0 32px rgba(0, 0, 0, 0.35);
  }
  body.hubnav-open .threads-shell .sidebar,
  body.hubnav-open .subpage-shell .sidebar {
    transform: translateX(0);
  }
}
.hub-right-col {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.hub-right-col > #announcementBanner {
  flex-shrink: 0;
}
/* #spaMount (also .hub-right-col, see index.html) stacks its mounted
   route's content in a column — same as .hub-right-col's own default
   direction, no inline override needed. For threads/announcements (see
   spa-shell.js's ROUTES.threads/.announcements `select`), that means
   #announcementBanner sits above the WHOLE .threads-body-row as a single
   unit, mirroring .threads-content-col's own stacked layout on the
   standalone (non-SPA) page exactly — .threads-body-row already carries
   its own correct `flex:1; min-height:0` (see below), so it stretches to
   fill the remaining height and the internal scroll chain inside it
   keeps working. (An earlier version of this forced #spaMount to
   flex-direction:row and used flex-wrap to push the banner onto its own
   line — that silently broke height-stretching for the wrapped second
   line, killing internal scrolling on every SPA-mounted thread view. See
   conversation history for the reported bug + fix.) */
#spaMount > #announcementBanner { flex-shrink: 0; }
.sidebar-header {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  padding: 0 10px 16px;
}
.sidebar-header .dot { width: 6px; height: 6px; border-radius: 50%; background: #7c6cf0; }

.sidebar-item {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 12px 10px;
  border-radius: 14px;
  background: var(--sidebar-item-bg);
  border: 1px solid transparent;
  border-left: 3px solid transparent;
  margin-bottom: 8px;
  transition: background 0.15s ease, transform 0.12s ease, border-color 0.15s ease;
  animation: card-glow 2.6s ease-in-out infinite;
}
/* Corner twinkle + light-sweep — three different-length cycles (2.2s /
   2.6s / 3.4s, roughly coprime) so they never fall into visible sync and
   read as one mechanical "blink" instead of ambient sparkle. */
.sidebar-item::after {
  content: "";
  position: absolute; top: 9px; right: 11px;
  width: 5px; height: 5px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 4px 1px rgba(255, 255, 255, 0.8);
  animation: card-twinkle 2.2s ease-in-out infinite;
  pointer-events: none;
}
.sidebar-item::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(255, 255, 255, 0.09) 50%, transparent 60%);
  background-size: 250% 100%;
  animation: card-sweep 3.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes card-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(124, 108, 240, 0); }
  50%      { box-shadow: 0 0 14px 1px rgba(124, 108, 240, 0.45); }
}
@keyframes card-twinkle {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50%      { opacity: 1;   transform: scale(1.3); }
}
@keyframes card-sweep {
  0%        { background-position: 200% 0; }
  60%, 100% { background-position: -50% 0; }
}
.sidebar-item:hover {
  background: var(--sidebar-item-hover);
  border-color: var(--panel-border);
  border-left-color: var(--item-accent, var(--accent-gold));
  transform: translateX(2px);
}
.sidebar-item .icon {
  width: 38px; height: 38px;
  border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px;
  flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.sidebar-item .text { flex: 1; min-width: 0; }
.sidebar-item .name { font-family: var(--font-display); font-weight: 600; font-size: 13.5px; color: var(--ink); }
.sidebar-item .desc {
  font-size: 11.5px; color: var(--ink-soft);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar-item .arrow { color: var(--ink-soft); font-size: 14px; transition: transform 0.15s ease; }
.sidebar-item:hover .arrow { transform: translateX(3px); }
.sidebar-item.active {
  background: var(--sidebar-item-hover);
  border-color: var(--panel-border);
  border-left-color: var(--item-accent, var(--accent-gold));
  margin-bottom: 16px;
}
.sidebar-item.active .name { color: var(--item-accent, var(--ink)); }

.sidebar-item.expandable { cursor: pointer; user-select: none; }
.sidebar-item.expandable .arrow { transition: transform 0.15s ease; }
.sidebar-item.expandable.open .arrow { transform: rotate(90deg); }

/* Account Management — "one continuous card" style: the toggle header
   and its sub-items live inside one rounded, bordered group instead of
   an indented tree line, so it reads as a single self-contained panel.
   Carries its OWN copy of the ambient card-glow animation (below) —
   `.am-toggle` still has the base `.sidebar-item` class too, but that
   copy is disabled just underneath, since box-shadow bleeding outward
   from a CHILD gets clipped by this element's own `overflow: hidden`.
   An element's OWN box-shadow is NOT clipped by its OWN overflow:hidden
   (only its children/content are) — so putting the glow here instead
   lets it bleed and pulse exactly like every other sidebar card. */
.am-group {
  border-radius: 14px;
  border: 1px solid transparent;
  background: var(--sidebar-item-bg);
  overflow: hidden;
  margin-bottom: 16px;
  transition: border-color 0.15s ease;
  animation: card-glow 2.6s ease-in-out infinite;
}
.am-group:has(.am-toggle:hover),
.am-group:has(.sidebar-subitems.open) { border-color: var(--panel-border); }
.am-group .am-toggle {
  margin-bottom: 0;
  border: none;
  border-left: 3px solid transparent;
  border-radius: 0;
  background: transparent;
  /* See the .am-group comment above — this would just be an invisible
     (clipped) duplicate of the glow the group itself now carries. */
  animation: none;
}
.am-group .am-toggle:hover { background: var(--sidebar-item-hover); transform: none; }
.am-group .sidebar-subitems {
  display: none; margin: 0; padding: 6px; border-left: none;
  border-top: 1px solid var(--panel-border);
}
.am-group .sidebar-subitems.open { display: block; }
.sidebar-subitem {
  position: relative;
  display: flex; align-items: center; gap: 9px; padding: 9px 10px;
  border-radius: 10px; font-size: 12.5px; font-weight: 600;
  color: var(--ink-soft); cursor: pointer; margin-bottom: 4px;
  transition: background 0.15s ease, color 0.15s ease;
}
.sidebar-subitem:hover { background: var(--sidebar-item-hover); color: var(--ink); }
/* Hover circle behind each row's icon — color comes from that row's own
   --sub-accent inline style, so every row can have a different tint. */
.sidebar-subitem .sub-icon {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; transition: background 0.15s ease;
}
.sidebar-subitem:hover .sub-icon { background: var(--sub-accent, rgba(255, 255, 255, 0.1)); }
.brand-checks { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.brand-checks label {
  display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px;
  background: var(--field-bg); border: 1px solid var(--border); border-radius: 20px;
  padding: 4px 10px; cursor: pointer;
}

/* Gold-toggle brand picker — click the pill itself to select, no checkbox */
.brand-toggle-label {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 8px;
}
.select-all-btn {
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: none;
  color: var(--ink-soft); background: var(--field-bg); border: 1.5px solid var(--border);
  border-radius: 8px; padding: 4px 10px; cursor: pointer; transition: all 0.15s ease;
}
.select-all-btn:hover { border-color: var(--accent-gold); color: var(--ink); }
.select-all-btn.active {
  background: rgba(200, 145, 47, 0.18); border-color: var(--accent-gold); color: var(--accent-gold);
}
.brand-toggle-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 6px; }
.brand-toggle-pill {
  display: flex; align-items: center; justify-content: center;
  padding: 11px 8px; border-radius: 12px; font-size: 12px; font-weight: 700;
  letter-spacing: 0.02em; color: var(--ink-soft); background: var(--field-bg);
  border: 1.5px solid var(--border); cursor: pointer; user-select: none;
  transition: all 0.15s ease;
}
.brand-toggle-pill:hover { border-color: rgba(200, 145, 47, 0.5); color: var(--ink); }
.brand-toggle-pill.selected {
  background: linear-gradient(135deg, rgba(200, 145, 47, 0.28), rgba(200, 145, 47, 0.14));
  border-color: var(--accent-gold); color: #f3c463;
  box-shadow: 0 0 0 1px rgba(200, 145, 47, 0.3) inset;
}
/* MERGED (2026-08-20) — small country badge next to each brand pill's
   name, needed now that the same brand name (e.g. "Crickex") can appear
   twice in this grid, once per country — without this there'd be no way
   to tell the two apart when granting brand access. */
.brand-toggle-country {
  font-size: 9px; font-weight: 800; opacity: 0.6; margin-left: 4px;
  letter-spacing: 0.03em;
}



.hub-main {
  flex: 1;
  min-height: 0;
  overflow-y: auto; /* only scrolls if content actually exceeds the
                        visible height — normally sits still. Kept
                        (not removed) even though the spacing below was
                        tightened specifically so this never has to
                        trigger on a normal desktop window: a very short
                        window (small laptop, heavy browser zoom) can
                        still legitimately need it, and removing this
                        would silently clip the bottom of the page with
                        no way to reach it — the exact bug already fixed
                        once elsewhere on this site (see conversation
                        history). */
  position: relative;
  padding: 24px 40px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  text-align: center;
}
/* Was max-width: 480px — the actual root cause of a long-running "cards
   won't get wider no matter what I set .tool-cards to" bug (2026-08).
   .tool-cards' own max-width was raised to 780px, then 960px, and NEITHER
   ever took effect, because this outer wrapper — NOT .tool-cards — was
   the real bottleneck the whole time: 3 columns squeezed into 480px
   meant each card rendered at ~152px regardless of what the grid itself
   was told. Confirmed `.inner` under `.hub-main` is used by exactly two
   pages (this hub's #viewHome and betting-resources.html), so widening
   it here is safe project-wide. If a card grid ever looks stuck at a
   tiny width again, check HERE first, not the grid's own rules — that's
   the lesson this bug cost a few rounds of guessing to learn. */
.hub-main .inner { max-width: 980px; }
.status-badge-corner {
  display: inline-flex;
  margin-top: 10px;
}
.hub-main h1 { font-family: var(--font-display); font-weight: 600; font-size: 28px; margin: 0 0 10px; letter-spacing: -0.01em; }
.hub-main p { color: var(--ink-soft); margin: 0 0 24px; line-height: 1.6; }
.status-badge {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--pill-bg); border: 1px solid var(--pill-border);
  padding: 9px 18px; border-radius: 999px; font-size: 12.5px; font-weight: 700; color: var(--ink-soft);
}
.status-badge .pulse {
  width: 7px; height: 7px; border-radius: 50%; background: #34d399;
  box-shadow: 0 0 8px #34d399;
  animation: pulse 2s infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }

.hub-stats {
  display: flex;
  justify-content: center;
  gap: 22px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--panel-border);
}
.hub-stats .stat { text-align: center; }
.hub-stats .stat b {
  display: block;
  font-family: var(--font-display);
  font-size: 19px;
  color: var(--ink);
}
.hub-stats .stat span {
  font-size: 11px;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* 3-column home grid (2026-08 redesign) — order in the markup follows
   the owner's requested reading order (row 1: TG Reply Threads / Deposit
   Issue / Deposit Backup, row 2: Promo Code Search / Announcement /
   HeyVIP Betting Rules, row 3: Active Agents alone). max-width raised
   from 520px to 900px alongside the .inner fix above — with 3 columns,
   520px would have meant ~160px cards even with .inner fixed. */
.tool-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin-top: 20px;
  max-width: 900px;
}
.tool-card {
  display: block;
  position: relative;
  text-align: left;
  background: var(--card-bg);
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  padding: 18px 18px 16px;
  padding-bottom: 34px;
  min-height: 108px; /* fixed floor instead of aspect-ratio — see note
                         below on why aspect-ratio was tried and dropped */
  box-shadow: var(--shadow);
  transition: transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
.tool-card:hover {
  transform: translateY(-2px);
  border-color: var(--tool-accent, var(--accent-gold));
  box-shadow: var(--shadow), 0 0 32px -8px var(--tool-accent, transparent);
}

/* Rotating gradient ring, hover-only. NOTE: aspect-ratio was tried
   earlier in this same redesign to keep the 3-col cards from squashing
   into squares, and briefly got blamed for a "some cards render blank,
   others overlap" regression — that turned out to be a red herring (the
   .inner 480px bug above was the real cause), but aspect-ratio's
   interaction with `display:flex` + CSS Grid 1fr tracks is a genuine
   cross-browser rough edge, and this project has no real browser in its
   dev loop to verify against (only owner screenshots) — so it was left
   OUT in favor of the plain min-height above. This ring (conic-gradient
   + mask-composite) is a comparably risk-bearing feature, but the owner
   asked for it back explicitly once the real bug was found, so it's
   back in — just flagged here in case a future report of "cards look
   wrong" turns out to trace back to this instead. */
.tool-card::before {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1.5px;
  background: conic-gradient(from 0deg, transparent 0%, var(--tool-accent, var(--accent-gold)) 12%, transparent 26%, transparent 100%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
  animation: tool-card-spin 3.4s linear infinite;
  animation-play-state: paused;
}
.tool-card:hover::before {
  opacity: 1;
  animation-play-state: running;
}
@keyframes tool-card-spin {
  to { transform: rotate(360deg); }
}

/* One-shot diagonal sweep of light across the card face, hover-triggered. */
.tool-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(115deg, transparent 25%, rgba(255, 255, 255, 0.16) 42%, rgba(255, 255, 255, 0.16) 46%, transparent 62%);
  background-size: 220% 100%;
  background-position: 160% 0;
  opacity: 0;
  pointer-events: none;
}
.tool-card:hover::after {
  opacity: 1;
  animation: tool-card-sweep 0.85s ease forwards;
}
@keyframes tool-card-sweep {
  0% { background-position: 160% 0; opacity: 1; }
  100% { background-position: -60% 0; opacity: 0; }
}

.tool-card .t-arrow {
  position: absolute; right: 18px; bottom: 16px;
  color: var(--ink-soft); font-size: 15px;
  transition: transform 0.15s ease, color 0.15s ease;
}
.tool-card:hover .t-arrow { transform: translateX(3px); color: var(--tool-accent, var(--ink)); }
/* "Open →" label, hover-only — generated content so the 7 card markups
   didn't each need a new span added just for this. Reads as "Open →"
   because it's inserted immediately before the existing arrow glyph,
   only under :hover, so at rest every card still shows just the bare
   arrow it always has. */
.tool-card:hover .t-arrow::before {
  content: "Open ";
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--tool-accent, var(--ink));
}
.tool-card .t-icon {
  position: relative;
  width: 36px; height: 36px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; margin-bottom: 12px;
}
.t-badge {
  position: absolute;
  top: -6px; right: -6px;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  border-radius: 999px;
  background: #ef4a72;
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.t-badge:empty { display: none; }
.tool-card .t-name { font-family: var(--font-display); font-weight: 600; font-size: 14.5px; color: var(--ink); margin-bottom: 4px; }
.tool-card .t-desc { font-size: 11.5px; color: var(--ink-soft); line-height: 1.4; }
.t-sub-stat { font-size: 10.5px; font-weight: 700; color: var(--tool-accent, var(--ink-soft)); margin-top: 8px; }
.t-sub-stat:empty { display: none; }
.tool-card-soon { cursor: default; opacity: 0.6; }
.tool-card-soon:hover { transform: none; border-color: var(--panel-border); box-shadow: var(--shadow); }
.tool-card-soon .t-icon { opacity: 0.7; }
.t-soon-badge {
  position: absolute; right: 18px; bottom: 16px;
  font-size: 11px; font-weight: 700; color: var(--ink-soft);
  background: var(--field-bg); border: 1px solid var(--border);
  padding: 3px 9px; border-radius: 20px;
}
.t-sub-stat.has-unsolved {
  color: #ef4a72;
  font-weight: 800;
  animation: breathe 1.8s ease-in-out infinite;
}
/* Active Agents home-card — icon + "N online now" text both breathe
   together while at least one agent is online, same shared keyframe as
   the unsolved-threads badge above and the announcement icon below —
   stops breathing (goes static) the moment online count hits 0, see
   loadActiveAgentsCardStat() in index.html. */
.t-sub-stat.breathing { animation: breathe 1.8s ease-in-out infinite; }
@keyframes breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.tool-card .t-icon.breathing { animation: breathe 1.8s ease-in-out infinite; }
/* Steps down 3 -> 2 -> 1 columns as the viewport (not just .tool-cards'
   own max-width) shrinks — matches the existing 720px breakpoint below
   where the sidebar itself collapses. */
@media (max-width: 900px) {
  .tool-cards { grid-template-columns: repeat(2, 1fr); max-width: 600px; }
}
@media (max-width: 560px) {
  .tool-cards { grid-template-columns: 1fr; max-width: 360px; }
}

/* Sidebar becomes an off-canvas drawer instead of stacking full-width
   above the content (the old behavior this replaces) — matches the
   INR build's proven pattern exactly. It's out of normal flow now
   (position:fixed), so nothing about .hub-layout/.hub-main needs to
   change to accommodate it, and the main content column never gets
   squeezed or reflowed by the drawer opening/closing. */
@media (max-width: 720px) {
  .sidebar-toggle-btn {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    width: 34px; height: 34px; border: none; background: none; cursor: pointer; margin-right: 4px; padding: 0;
  }
  .sidebar-toggle-btn span { display: block; width: 19px; height: 2px; border-radius: 2px; background: var(--ink); }
  .hub-layout { min-height: auto; }
  .sidebar {
    position: fixed; top: 61px; left: 0; bottom: 0; z-index: 45;
    width: 290px; max-width: 82vw; max-height: none;
    border-right: 1px solid var(--panel-border); border-bottom: none;
    background: var(--card-bg); box-shadow: var(--shadow);
    transform: translateX(-100%);
    transition: transform 0.22s ease;
  }
  .sidebar.open { transform: translateX(0); }
  .sidebar-backdrop.open { display: block; opacity: 1; pointer-events: auto; }
}

/* Auto-hide the persistent ISSUE SUBMISSION nav sidebar while Threads is
   open, but ONLY below this width — Threads already brings its own
   340px thread-list sidebar; stacked with this one's 290px, a narrower/
   half window leaves barely any room for the actual conversation pane.
   At full window width there's plenty of room for all three columns, so
   the nav stays put there — this is a width-triggered collapse, not
   "always hide on Threads". Reuses the exact off-canvas drawer mechanics
   the ≤720px breakpoint above already has (same fixed-position +
   translateX slide + hamburger toggle) instead of a bare display:none —
   a plain display:none has no way back in; this keeps the sidebar
   reachable via the same toggle button. data-view="threads" (set in
   spa-shell.js's mount()) scopes this to Threads specifically. Ported
   from the INR build, which hit this exact "half-split window makes the
   chat unreadably narrow" complaint first. */
@media (max-width: 1300px) {
  body:has(#spaMount[data-view="threads"]) .sidebar-toggle-btn {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
    width: 34px; height: 34px; border: none; background: none; cursor: pointer; margin-right: 4px; padding: 0;
  }
  body:has(#spaMount[data-view="threads"]) .sidebar-toggle-btn span {
    display: block; width: 19px; height: 2px; border-radius: 2px; background: var(--ink);
  }
  body:has(#spaMount[data-view="threads"]) .sidebar {
    position: fixed; top: 61px; left: 0; bottom: 0; z-index: 45;
    width: 290px; max-width: 82vw; max-height: none;
    border-right: 1px solid var(--panel-border); border-bottom: none;
    background: var(--card-bg); box-shadow: var(--shadow);
    transform: translateX(-100%);
    transition: transform 0.22s ease;
  }
  body:has(#spaMount[data-view="threads"]) .sidebar.open { transform: translateX(0); }
  body:has(#spaMount[data-view="threads"]) .sidebar-backdrop.open { display: block; opacity: 1; pointer-events: auto; }
}

/* ---- betting-resources.html — the HeyVIP Betting Rules page itself ---- */
.br-inner { max-width: 820px; text-align: left; }
/* MERGED (2026-08-21) — wraps one country's pair of .br-panels when
   "All Countries" mode shows more than one — plain margin between
   stacked blocks, no border/background of its own (the country label
   above it is enough separation, and each .br-panel already has its
   own card styling below). */
.br-country-block + .br-country-block { margin-top: 28px; }
.br-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  align-items: start;
}
@media (max-width: 720px) {
  .br-panels { grid-template-columns: 1fr; }
}
.br-panel {
  background: var(--card-bg);
  border: 1px solid var(--panel-border);
  border-radius: 16px;
  padding: 18px;
  box-shadow: var(--shadow);
}
.br-panel-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
  margin-bottom: 12px;
}
.br-links-list { display: flex; flex-direction: column; gap: 10px; }
.br-link-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
  text-decoration: none;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.br-link-card:hover { border-color: var(--accent-gold); transform: translateX(2px); }
.br-link-icon { font-size: 18px; flex: none; }
.br-link-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.br-link-name { font-weight: 700; font-size: 13px; color: var(--ink); }
.br-link-url { font-size: 11px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.br-link-arrow { color: var(--ink-soft); font-size: 14px; flex: none; }

/* ---- Betting Resources Links admin panel (Account Management) ----
   Reuses the same edit-modal/field vocabulary as every other Account
   Management section rather than inventing new ones. Icon/Name/URL
   side-by-side in ONE row per the reference design (owner-provided
   mockup, 2026-08) — originally this was icon+name on one row and url
   on its own row below; the mockup put all three in a single row with
   a small card wrapper + header line explaining what the fields feed,
   so that's what this now matches. Collapses to stacked under 480px
   same as before. */
.br-category-list { display: flex; flex-direction: column; gap: 8px; }
.br-category-item {
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--field-bg);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.br-category-item:hover { color: var(--ink); }
.br-category-item.selected {
  color: var(--ink);
  border-color: var(--accent-gold);
  background: color-mix(in srgb, var(--accent-gold) 12%, var(--field-bg));
}
/* One card per link (the single "rules" link, or each "results" link) —
   a small header (icon preview + one-line description of what this
   field group feeds) sits above the Icon/Name/URL row so it's clear
   which home-card panel each form is editing without needing to leave
   the modal to check. */
.br-form-card {
  border: 1px solid var(--border);
  background: var(--field-bg);
  border-radius: 14px;
  padding: 16px;
  margin-bottom: 14px;
}
.br-form-card-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.br-form-card-icon {
  width: 30px; height: 30px; flex: none;
  border-radius: 8px;
  background: var(--card-bg);
  display: flex; align-items: center; justify-content: center;
  font-size: 15px;
}
.br-form-card-desc { font-size: 12.5px; font-weight: 700; color: var(--ink); flex: 1; }
/* Results list rows (2026-08 follow-up) — the per-link header banner
   ("Link #N — shown in the right panel") from the first mockup pass got
   dropped per a second reference screenshot: with potentially a dozen+
   results links, a 2-line header per row ate too much vertical space
   for what it said, and the owner's updated mockup showed plain rows —
   just the Icon/Name/URL fields (own labels each, same as before) plus
   a small trash-icon remove button inline at the end of the row instead
   of a separate header line. The "rules" (single link) card above still
   keeps its header — only ever one of those, so the extra context line
   isn't a density problem the way it is repeated N times in the list. */
.br-result-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.br-result-row {
  display: flex;
  gap: 10px;
  align-items: center;
  border: 1px solid var(--border);
  background: var(--field-bg);
  border-radius: 12px;
  padding: 10px 12px;
  transition: opacity 0.15s ease, border-color 0.15s ease;
}
/* Drag-to-reorder — order of these rows IS the order saved to `results`
   and the order betting-resources.html renders on the home page, so no
   separate "position"/"sortOrder" field is needed; reordering the DOM
   list and syncing back into acctBettingLinksData is the whole feature. */
.br-drag-handle {
  flex: none;
  width: 20px;
  text-align: center;
  color: var(--ink-soft);
  font-size: 15px;
  line-height: 1;
  cursor: grab;
  user-select: none;
}
.br-drag-handle:active { cursor: grabbing; }
.br-result-row.dragging { opacity: 0.4; }
.br-result-row.drag-over { border-color: var(--accent-gold); }
.br-result-row .field { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; justify-content: center; }
.br-result-row .field.br-icon-field { flex: 0 0 56px; }
/* Fixed height on every input in this row (icon/name/url alike) —
   without this, the icon input's height was following the emoji
   glyph's own font metrics (emoji line-height/ascent varies by
   codepoint across browsers), which made rows with different icons
   render at slightly different heights and threw off row-to-row
   alignment even though the markup/CSS was otherwise identical. Height
   is now decided by CSS, not by whichever emoji happens to be in the
   box. */
.br-result-row .field input {
  display: block;
  height: 40px;
  line-height: 38px;
  padding-top: 0;
  padding-bottom: 0;
  box-sizing: border-box;
}
.br-result-row .field.br-icon-field input { text-align: center; font-size: 17px; padding-left: 4px; padding-right: 4px; }
.br-result-row .field label { line-height: 1.2; }
.edit-fields-row { display: flex; gap: 10px; align-items: flex-end; }
.edit-fields-row .field { flex: 1; margin: 0; min-width: 0; }
.edit-fields-row .field.br-icon-field { flex: 0 0 64px; }
.edit-fields-row .field.br-icon-field input { text-align: center; font-size: 18px; padding-left: 4px; padding-right: 4px; }
@media (max-width: 480px) {
  .edit-fields-row { flex-direction: column; align-items: stretch; }
  .edit-fields-row .field.br-icon-field { flex: none; }
  .br-result-row { flex-wrap: wrap; }
  .br-result-row .field.br-icon-field { flex: 0 0 56px; }
  .br-result-row .field:not(.br-icon-field) { flex: 1 1 100%; }
}
.br-result-remove {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 8px;
  border: none;
  background: rgba(239, 74, 114, 0.16);
  color: #ef4a72;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s ease, color 0.15s ease;
}
.br-result-remove:hover { background: #ef4a72; color: #fff; }
.br-add-link-btn {
  width: 100%;
  padding: 10px;
  border-radius: 10px;
  border: 1px dashed var(--border);
  background: transparent;
  color: var(--ink-soft);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}
.br-add-link-btn:hover { border-color: var(--accent-gold); color: var(--ink); }

/* ---- Announcements sidebar (New button / search / status dots) ---- */
.announce-new-btn {
  width: 100%;
  padding: 12px;
  margin-bottom: 14px;
  border-radius: 12px;
  border: 1.5px dashed var(--border);
  background: transparent;
  color: var(--ink-soft);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.announce-new-btn:hover { border-color: var(--accent-gold); color: var(--ink); background: rgba(200, 145, 47, 0.06); }
.announce-item .t-title { white-space: normal; overflow: visible; text-overflow: unset; }
.status-indicator { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.status-text { font-size: 10.5px; font-weight: 700; }
.status-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.status-dot.breathing { animation: statusBreathe 1.8s ease-in-out infinite; }
@keyframes statusBreathe {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(26, 156, 110, 0.45); }
  50% { opacity: 0.4; box-shadow: 0 0 6px 2px rgba(26, 156, 110, 0.4); }
}
/* Edit-modal action buttons: Save always shows/colors the state it will
   switch TO (green = Active, gray = Inactive); Delete stays a distinct
   red outline so it doesn't compete with Save for attention. */
.announce-save-btn {
  font-size: 11.5px; font-weight: 700; padding: 7px 14px; border-radius: 8px;
  cursor: pointer; border: 1.5px solid transparent; transition: all 0.15s ease; white-space: nowrap;
}
.announce-save-btn:hover { transform: translateY(-1px); }
.announce-save-btn.state-active { background: rgba(26, 156, 110, 0.16); color: #1a9c6e; }
.announce-save-btn.state-active:hover { box-shadow: 0 4px 12px -4px rgba(26, 156, 110, 0.5); }
.announce-save-btn.state-inactive { background: rgba(136, 144, 192, 0.18); color: #8890c0; }
.announce-save-btn.state-inactive:hover { box-shadow: 0 4px 12px -4px rgba(136, 144, 192, 0.5); }
.announce-delete-btn {
  font-size: 12px; font-weight: 700; padding: 7px 12px; border-radius: 8px;
  cursor: pointer; border: 1.5px solid rgba(239, 74, 114, 0.5); background: none; color: #ef4a72;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; transition: all 0.15s ease;
}
.announce-delete-btn:hover { background: rgba(239, 74, 114, 0.1); border-color: #ef4a72; }

/* ---- TG Reply Threads ---- */
.threads-shell { display: flex; height: calc(100vh - 61px); }
/* Wraps the persistent marquee + the sidebar/main-content row — keeps
   the marquee beside #hubNavMount (not spanning over it), same pattern
   as .hub-content-col / .subpage-content-col. */
.threads-content-col { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.threads-content-col > .brand-row { flex-shrink: 0; }
/* Announcement banner now lives here (sibling of .brand-row, above the
   sidebar/main split below) so it always spans the FULL content width
   and stays edge-aligned with the marquee above it. It used to be nested
   one level deeper, inside .threads-right-col — which only spans the
   width of the right (main) column, not the .threads-sidebar (search +
   thread-list) column beside it, so the banner rendered narrower than
   the marquee and visually misaligned with it. See conversation history
   for the reported bug + before/after screenshots. */
.threads-content-col > #announcementBanner { flex-shrink: 0; }
.threads-body-row { display: flex; flex: 1; min-height: 0; }
/* Right column of the shell — "Back to Home" topline + main content,
   width-aligned to the content area instead of spanning full-page-width
   and sitting on top of the sidebar. Same fix already applied to
   index.html's .hub-right-col — see CHANGES-batch1-modal-cache-layout.md. */
.threads-right-col { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.threads-right-col > .threads-topline { flex-shrink: 0; }
.threads-sidebar {
  width: 340px;
  flex-shrink: 0;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--panel-border);
  padding: 18px 14px 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  backdrop-filter: blur(16px) saturate(160%);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
}
.threads-search {
  display: flex; align-items: center; gap: 8px;
  background: var(--field-bg); border: 1.5px solid var(--border);
  border-radius: 12px; padding: 9px 12px; margin-bottom: 16px;
}
.threads-search input {
  flex: 1; border: none; background: none; outline: none;
  font-size: 13px; color: var(--ink); font-family: inherit;
}

/* ---- Active / Solved / Recall tabs (2026-08) ----
   Inactive state: outline only, each tab's own color for border + text
   (Active = orange, Solved = green, Recall = red). Selected state:
   solid blue fill + white text for ALL THREE, regardless of that tab's
   own color — the blue is "this is what you're currently looking at",
   not a 4th category color. */
.threads-tabs { display: flex; gap: 6px; margin-bottom: 14px; }
.threads-tab {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 8px 4px; border-radius: 999px; border: 1.5px solid transparent;
  background: transparent; font-size: 12px; font-weight: 700; cursor: pointer;
  font-family: inherit; transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.threads-tab-count {
  border-radius: 999px; padding: 1px 7px; font-size: 10.5px; font-weight: 800;
  background: rgba(255, 255, 255, 0.08);
}
.threads-tab.tab-active { border-color: #e08a3c; color: #e08a3c; }
.threads-tab.tab-active .threads-tab-count { color: #e08a3c; }
.threads-tab.tab-solved { border-color: #4fbf6a; color: #4fbf6a; }
.threads-tab.tab-solved .threads-tab-count { color: #4fbf6a; }
.threads-tab.tab-recall { border-color: #e0524f; color: #e0524f; }
.threads-tab.tab-recall .threads-tab-count { color: #e0524f; }
[data-theme="light"] .threads-tab-count { background: rgba(0, 0, 0, 0.05); }
.threads-tab.is-active {
  background: #1d63e0; border-color: #1d63e0; color: #fff;
}
.threads-tab.is-active .threads-tab-count { background: #4f8fef; color: #fff; }

/* ---- "All threads" card — subtitle/count/Select toggle at the top of
   whichever tab is open, and (in Select mode) the Select all + bulk
   action row right underneath it. */
.all-threads-card {
  background: var(--field-bg); border: 1.5px solid var(--border);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 14px;
}
.all-threads-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.all-threads-title { font-size: 13.5px; font-weight: 800; color: var(--ink); }
.all-threads-sub { font-size: 11px; color: var(--ink-soft); margin-top: 2px; }
.all-threads-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.all-threads-count {
  background: var(--pill-bg); border: 1px solid var(--pill-border);
  border-radius: 999px; padding: 2px 9px; font-size: 11.5px; font-weight: 800; color: var(--ink);
}
.bulk-select-all {
  display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700;
  color: var(--ink); cursor: pointer; user-select: none; flex-shrink: 0; white-space: nowrap;
}
.threads-empty { font-size: 12px; color: var(--ink-soft); text-align: center; padding: 24px 10px; }
.thread-list {
  display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px;
  padding: 6px; border-radius: 12px;
  background: rgba(255, 255, 255, 0.025); border: 1px solid var(--panel-border);
}
[data-theme="light"] .thread-list { background: rgba(0, 0, 0, 0.02); }

/* Recall Chat History — same template as Active/Solved, red-tinted box
   border so it reads as "sensitive" without being hidden from admins
   (the count badge itself now lives on the Recall tab pill, styled by
   .threads-tab.tab-recall above). */
.threads-section.recall .thread-list { border-color: rgba(239, 68, 68, 0.16); background: rgba(239, 68, 68, 0.03); }
.recall-item .icon { background: rgba(239, 68, 68, 0.14); color: #f87171; }
.recall-item .badge {
  display: inline-block; font-size: 9px; font-weight: 800; text-transform: uppercase;
  letter-spacing: 0.03em; padding: 1px 6px; border-radius: 6px;
  background: rgba(239, 68, 68, 0.16); color: #f87171; margin-right: 6px; vertical-align: middle;
}
.recall-item .t-content { font-size: 10.5px; color: var(--ink-soft); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.thread-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-radius: 12px;
  background: var(--sidebar-item-bg);
  border-left: 3px solid transparent;
  cursor: pointer;
  transition: background 0.15s ease;
  position: relative;
}
.thread-item:hover { background: var(--sidebar-item-hover); }
.thread-item.selected { background: var(--sidebar-item-hover); border-left-color: var(--thread-accent, var(--accent-gold)); }
.thread-item .icon { width: 26px; height: 26px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 13px; flex-shrink: 0; }
.thread-item .meta { flex: 1; min-width: 0; }
.thread-item .t-title { font-size: 12.5px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* MERGED (2026-08-20) — only rendered at all for accounts that can see
   more than one country (see the JS call site) — a small fixed-width
   tag so it doesn't push the title's ellipsis truncation around. */
.thread-country-badge {
  display: inline-block; font-size: 9px; font-weight: 800; letter-spacing: 0.03em;
  padding: 1px 5px; border-radius: 5px; margin-right: 5px;
  background: var(--field-bg); border: 1px solid var(--border); color: var(--ink-soft);
  vertical-align: 1px;
}
.thread-item .t-sub { font-size: 11px; color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread-item .t-done { color: #34d399; font-size: 13px; flex-shrink: 0; }
.unread-badge {
  flex-shrink: 0;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #ef4a72;
  color: #fff;
  font-size: 10.5px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ---- Bulk action bar (Select batch mode) — a standalone row below the
   search box (2026-08 — moved out of .all-threads-card), all three
   controls (Select all + the two action buttons) on a single line. ---- */
.bulk-action-bar {
  display: flex; align-items: center; gap: 8px;
  background: var(--field-bg); border: 1.5px solid var(--border);
  border-radius: 12px; padding: 10px 12px; margin-bottom: 14px;
}
.bulk-cancel { background: none; border: none; color: var(--ink-soft); font-size: 12px; font-weight: 700; cursor: pointer; }
.bulk-cancel:hover { color: var(--ink); }
.bulk-action-btn {
  flex: 1; padding: 9px 6px; border-radius: 10px; border: 1.5px solid var(--border);
  background: var(--field-bg); color: var(--ink); font-size: 12px; font-weight: 700; cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center;
}
.bulk-action-btn:hover:not(:disabled) { border-color: var(--accent-gold); }
.bulk-action-btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* Same red/green convention as .status-chip.unsolved/.solved above — applied
   the same way to BOTH bulk buttons (not just Solved/Not solved) so the two
   rows read consistently: green = this action moves the ticket forward
   (read, solved), red = this action reverts it (unread, reopened). */
.bulk-action-btn.tone-positive { background: rgba(52, 211, 153, 0.16); border-color: transparent; color: #1a9c6e; }
.bulk-action-btn.tone-negative { background: rgba(239, 74, 114, 0.16); border-color: transparent; color: #ef4a72; }

/* ---- Per-item ⋮ menu + Select-mode checkbox ---- */
.thread-item.select-mode { cursor: pointer; }
.thread-item-check { width: 16px; height: 16px; flex-shrink: 0; cursor: pointer; }
.item-menu-btn {
  flex-shrink: 0; width: 24px; height: 24px; border-radius: 8px; border: none;
  background: none; color: var(--ink-soft); font-size: 15px; cursor: pointer; line-height: 1;
}
.item-menu-btn:hover { background: var(--sidebar-item-hover); color: var(--ink); }
.item-menu {
  position: absolute; top: 34px; right: 8px; z-index: 20; min-width: 172px;
  background: var(--card-bg); border: 1px solid var(--panel-border); border-radius: 10px;
  padding: 4px; box-shadow: 0 10px 28px -8px rgba(0, 0, 0, 0.45);
}
.item-menu-option {
  display: flex; align-items: center; gap: 8px; padding: 8px 9px; border-radius: 7px;
  font-size: 12px; font-weight: 600; color: var(--ink); cursor: pointer;
}
.item-menu-option:hover { background: var(--sidebar-item-hover); }
.item-menu-icon { width: 14px; text-align: center; flex-shrink: 0; color: var(--ink-soft); }
.item-menu-icon.ok { color: #1a9c6e; }
.item-menu-icon.danger { color: #e0577a; }

/* ---- @ Tag Username autocomplete ---- */
.mention-dropdown {
  position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); z-index: 20;
  max-height: 220px; overflow-y: auto;
  background: var(--card-bg); border: 1px solid var(--panel-border); border-radius: 10px;
  padding: 4px; box-shadow: 0 10px 28px -8px rgba(0, 0, 0, 0.45);
}
.mention-option {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: 7px;
  cursor: pointer; font-size: 12.5px;
}
.mention-option.active, .mention-option:hover { background: var(--sidebar-item-hover); }
.mention-handle { font-weight: 700; color: var(--ink); }
.mention-from { color: var(--ink-soft); font-size: 11.5px; }

.threads-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-height: 0; }

/* ---- Announcement reminder banner (public/assets/announcement-banner.js) ---- */
#announcementBanner:empty { display: none; }
.announcement-banner {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 24px; background: rgba(200, 145, 47, 0.14);
  border-top: 1px solid var(--accent-gold); border-bottom: 1px solid var(--accent-gold);
}
.announcement-banner-icon { font-size: 16px; flex-shrink: 0; line-height: 1.4; }
.announcement-banner-body { flex: 1; min-width: 0; }
.announcement-banner-label { font-size: 10.5px; font-weight: 800; letter-spacing: 0.05em; color: var(--accent-gold); }
.announcement-banner-textwrap { display: grid; margin-top: 2px; }
.announcement-banner-text { grid-column: 1; grid-row: 1; font-size: 12.5px; color: var(--ink); line-height: 1.5; }
.announcement-banner-dots { display: flex; gap: 4px; margin-top: 6px; }
.announcement-banner-dots span { width: 5px; height: 5px; border-radius: 50%; background: rgba(200, 145, 47, 0.35); }
.announcement-banner-dots span.on { background: var(--accent-gold); }
.announcement-banner-close { background: none; border: none; color: var(--accent-gold); font-size: 13px; cursor: pointer; flex-shrink: 0; padding: 2px; }
.announcement-banner-close:hover { opacity: 0.7; }
.announcement-banner-icon.breathing, .announcement-banner-label.breathing { animation: breathe 1.8s ease-in-out infinite; }
.threads-placeholder { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--ink-soft); font-size: 14px; text-align: center; }
.thread-top-fixed { flex-shrink: 0; padding: 30px 40px 0; }
.thread-scroll-area { flex: 1; overflow-y: auto; padding: 20px 40px; min-height: 0; }
.thread-reply-fixed { flex-shrink: 0; padding: 16px 40px 26px; border-top: 1px solid var(--panel-border); }
.thread-detail-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 18px; flex-wrap: wrap; }
.thread-detail-title { display: flex; align-items: center; gap: 10px; font-family: var(--font-display); font-size: 19px; font-weight: 600; }
.thread-detail-sub { font-size: 12.5px; color: var(--ink-soft); margin-top: 4px; }
.thread-detail-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
.status-chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 999px; font-size: 12px; font-weight: 700; border: none; cursor: pointer; transition: transform 0.12s ease; }
.status-chip.unsolved { background: rgba(239, 74, 114, 0.16); color: #ef4a72; }
.status-chip.solved { background: rgba(52, 211, 153, 0.16); color: #1a9c6e; }
.status-chip:hover { transform: translateY(-1px); }
.icon-btn { width: 32px; height: 32px; border-radius: 10px; border: 1px solid var(--border); background: var(--card-bg); color: var(--required); font-size: 13px; cursor: pointer; }
.icon-btn:hover { background: rgba(239, 74, 114, 0.1); }
.icon-btn.spinning { color: var(--required); animation: spin 0.8s linear infinite; }
/* Icon + text label variant (e.g. thread-detail-actions toolbar) — same
   pill family as .icon-btn but auto width, with a short caption next to
   the emoji instead of an icon-only square. Color goes back to --ink
   (neutral) since these aren't all "danger" actions like the plain
   icon-btn's red tint implies. */
.icon-btn.labeled { width: auto; height: auto; padding: 7px 12px; display: inline-flex; align-items: center; gap: 6px; color: var(--ink); font-size: 12px; font-weight: 700; white-space: nowrap; }
.icon-btn.labeled:hover { background: var(--field-bg); }
.icon-btn.labeled.spinning { color: var(--ink); }
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }

.threads-topline { display: flex; align-items: center; justify-content: flex-end; gap: 14px; padding-right: 24px; }
.agent-whoami { font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.agent-whoami .role-badge {
  display: inline-block; margin-left: 6px; padding: 2px 8px; border-radius: 20px;
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  background: rgba(124, 108, 240, 0.15); color: var(--wordmark);
}
.agent-whoami .logout-link { margin-left: 10px; color: var(--label-blue); cursor: pointer; text-decoration: underline; }
.agent-whoami .logout-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; margin-left: 8px; border-radius: 50%;
  background: #ef4444; cursor: pointer; flex-shrink: 0;
  transition: transform 0.15s ease;
}
.agent-whoami .logout-icon-btn:hover { transform: scale(1.1); }
.agent-whoami .logout-icon-btn svg { width: 12px; height: 12px; display: block; }

.login-overlay {
  display: none; align-items: flex-start; justify-content: center;
  padding: 60px 20px;
}
.login-overlay.open { display: flex; }
.login-page-wrap { display: flex; align-items: flex-start; justify-content: center; padding: 60px 20px; }

.thread-divider { border: none; border-top: 1px solid var(--panel-border); margin: 20px 0; }

.reply-input-wrap { position: relative; flex: 1; display: flex; align-items: center; }
.attach-btn {
  position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
  width: 28px; height: 28px; flex-shrink: 0; border: none; background: none;
  border-radius: 8px; font-size: 16px; cursor: pointer; opacity: 0.65;
  display: flex; align-items: center; justify-content: center;
  transition: opacity 0.12s ease;
}
.attach-btn:hover { opacity: 1; }
.reply-input-wrap::before {
  content: "";
  position: absolute;
  left: 48px; top: 22%; bottom: 22%;
  width: 1px;
  background: var(--ink-soft);
  opacity: 0.3;
  pointer-events: none;
}
.reply-input-wrap input#replyInput { padding-left: 60px; }
.reply-attach-preview:empty { display: none; }
.reply-attach-preview { margin-bottom: 8px; }
.attach-chip {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--field-bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 6px 10px; font-size: 12px; color: var(--ink);
}
.attach-chip button { border: none; background: none; color: var(--required); cursor: pointer; font-weight: 700; padding: 0; }
.msg-tick { color: #34d399; font-weight: 700; }
.msg-attach-tag { display: block; margin-top: 4px; font-size: 11px; opacity: 0.75; }

/* Auto-loading inline attachments — see loadInlineAttachments() in
   threads.html. Used both in the ticket summary card (thread-summary-
   attachments below) and inline in a reply bubble (.msg-inline-attach). */
.inline-attach-slot { margin-top: 6px; }
.inline-attach-loading {
  font-size: 12px; opacity: 0.6; font-style: italic;
}
.inline-attach-error { font-size: 12px; color: var(--required); }
.inline-attach-media {
  display: block; max-width: 100%; max-height: 320px; border-radius: 10px;
  cursor: zoom-in; border: 1px solid var(--border);
}
.inline-attach-file {
  display: inline-block; font-size: 13px; color: inherit;
  text-decoration: underline; text-underline-offset: 2px; opacity: 0.9;
}
.inline-attach-file:hover { opacity: 1; color: var(--accent-gold); }
.msg-inline-attach .inline-attach-media { max-height: 220px; }

/* Multiple attachments on one reply (sent as a Telegram album) — a
   compact thumbnail grid instead of stacking full-size images. */
.msg-multi-attach {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 6px; margin-top: 6px; max-width: 320px;
}
.msg-multi-attach .inline-attach-slot { margin-top: 0; }
.msg-multi-attach .inline-attach-media { max-height: 110px; width: 100%; object-fit: cover; }

/* Attachment preview lightbox — see viewAttachment() in threads.html.
   Fixed full-screen overlay, hidden by default (.is-open toggles it). */
.attach-lightbox {
  display: none; position: fixed; inset: 0; z-index: 500;
  background: rgba(6, 8, 20, 0.88); backdrop-filter: blur(4px);
  align-items: center; justify-content: center; padding: 40px;
}
.attach-lightbox.is-open { display: flex; }
.attach-lightbox-close {
  position: absolute; top: 20px; right: 24px; z-index: 1;
  border: 1.5px solid var(--border); background: var(--field-bg); color: var(--ink);
  width: 40px; height: 40px; border-radius: 50%; font-size: 16px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; transition: all 0.15s ease;
}
.attach-lightbox-close:hover { border-color: var(--accent-gold); color: #f3c463; }
.attach-lightbox-body {
  max-width: min(90vw, 1000px); max-height: 88vh;
  display: flex; align-items: center; justify-content: center;
}
.attach-lightbox-body img, .attach-lightbox-body video {
  max-width: 100%; max-height: 88vh; border-radius: 12px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); display: block;
}
.attach-lightbox-status {
  color: var(--ink-soft); font-size: 14px; text-align: center;
  background: var(--field-bg); border: 1.5px solid var(--border);
  border-radius: 12px; padding: 24px 32px;
}
.attach-lightbox-error { color: var(--required); }

.thread-summary-card {
  background: var(--field-bg); border: 1px solid var(--border);
  border-radius: 16px; padding: 16px 18px; margin-bottom: 24px;
}
.thread-summary-card .row { display: flex; gap: 8px; font-size: 13px; padding: 3px 0; }
.thread-summary-card .row b { color: var(--label-blue); flex-shrink: 0; }
.thread-summary-attachments {
  display: flex; flex-wrap: wrap; gap: 12px 20px; margin-top: 12px;
  padding-top: 12px; border-top: 1px dashed var(--border);
}

.conversation-title { font-size: 11px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-soft); margin-bottom: 14px; }
.conversation { display: flex; flex-direction: column; gap: 16px; }
.msg-row { display: flex; align-items: flex-start; gap: 10px; max-width: 76%; }
.msg-row.self { flex-direction: row-reverse; margin-left: auto; }
.msg-avatar {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; color: #10131f;
}
.msg-body { flex: 1; min-width: 0; }
.msg-row.self .msg-body { text-align: right; }
.msg-who { font-size: 12.5px; font-weight: 700; color: var(--ink); margin-bottom: 3px; }
.msg-row.self .msg-who { color: var(--accent-gold); }
.msg-handle { font-weight: 500; color: var(--ink-soft); }
.msg-bubble {
  position: relative;
  display: inline-block;
  max-width: 100%;
  text-align: left;
  background: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 4px 14px 14px 14px;
  padding: 9px 13px;
  font-size: 13.5px;
  line-height: 1.5;
  white-space: pre-wrap;
}
.msg-row.self .msg-bubble { background: color-mix(in srgb, var(--field-bg) 75%, var(--accent-gold) 25%); border-color: color-mix(in srgb, var(--border) 60%, var(--accent-gold) 40%); border-radius: 14px 4px 14px 14px; }
.reply-target-btn {
  position: absolute;
  top: -9px; right: -9px;
  width: 20px; height: 20px;
  border-radius: 50%;
  border: none;
  background: #38bdf8;
  color: #fff;
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.12s ease, transform 0.12s ease;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
}
.msg-row:hover .reply-target-btn { opacity: 1; transform: scale(1); }
.msg-row.self .reply-target-btn { right: auto; left: -9px; }
.reply-target-chip {
  display: flex; align-items: center; gap: 6px;
  background: var(--field-bg); border: 1px solid #38bdf8; border-left-width: 3px;
  border-radius: 8px; padding: 6px 10px; margin-bottom: 8px;
  font-size: 12px; color: var(--ink);
}
.reply-target-chip b { color: #38bdf8; }
.reply-target-chip button { margin-left: auto; border: none; background: none; color: var(--ink-soft); cursor: pointer; font-weight: 700; }
.reply-quote-preview {
  display: block;
  border-left: 3px solid #38bdf8;
  background: rgba(56, 189, 248, 0.1);
  border-radius: 6px;
  padding: 4px 8px;
  margin-bottom: 6px;
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--ink-soft);
  white-space: normal;
}
.reply-quote-preview b { display: block; color: #38bdf8; font-size: 11px; }
.msg-time { font-size: 10.5px; color: var(--ink-soft); margin-top: 4px; }
.msg-edited { font-style: italic; opacity: 0.7; }
.msg-actions { display: inline-flex; gap: 6px; margin-left: 8px; }
.msg-actions button { border: none; background: none; cursor: pointer; font-size: 10.5px; opacity: 0.55; padding: 0; }
.msg-actions button:hover { opacity: 1; }

.reply-box { display: flex; gap: 10px; }
.reply-box input { flex: 1; border: 1.5px solid var(--border); background: var(--field-bg); border-radius: 12px; padding: 11px 14px; font-size: 13.5px; color: var(--ink); font-family: inherit; }
.reply-box input:focus { outline: none; border-color: #38bdf8; }
.reply-box button { flex-shrink: 0; padding: 0 20px; border: none; border-radius: 12px; background: linear-gradient(90deg, #6366f1, #0ea5e9); color: #fff; font-weight: 700; font-size: 13px; cursor: pointer; }
.reply-box button:disabled { opacity: 0.5; cursor: not-allowed; }

@media (max-width: 820px) {
  body.threads-page { overflow: auto; height: auto; }
  .threads-shell { flex-direction: column; height: auto; }
  .threads-body-row { flex-direction: column; }
  .threads-main { overflow: visible; }
  .thread-scroll-area { overflow-y: visible; }
  .threads-sidebar { width: 100%; max-height: 320px; }
}
.back-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: linear-gradient(135deg, #38bdf8, #6366f1);
  color: #fff;
  font-weight: 700;
  font-size: 13px;
  padding: 10px 18px;
  border-radius: 999px;
  box-shadow: 0 10px 24px -10px rgba(56, 100, 240, 0.55);
  transition: transform 0.15s ease;
  margin: 16px 0 0 20px;
}
.back-pill:hover { transform: translateY(-1px); }

/* ---- Hub header (legacy, unused on new sidebar hub, kept for reference) ---- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 28px;
}
.eyebrow .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ink-soft); }

h1 {
  font-size: 28px;
  font-weight: 700;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}

.subtitle {
  color: var(--ink-soft);
  margin: 0 0 36px;
  font-size: 15px;
}

/* ---- Form page ---- */
.form-page { max-width: 560px; margin: 0 auto; padding: 24px 20px 80px; }

.form-card {
  background: var(--card-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 30px 34px 34px;
  box-shadow: var(--shadow);
}

.form-header {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  margin-bottom: 8px;
}
.form-header .icon { font-size: 20px; }

.form-hint {
  color: var(--ink-soft);
  font-size: 13px;
  margin: 0 0 16px;
}

.form-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 0 0 20px;
}

.field { margin-bottom: 18px; }

.field label {
  display: block;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--label-blue);
  margin-bottom: 8px;
}
.field .required { color: var(--required); margin-left: 2px; }

.field input,
.field select,
.field textarea {
  width: 100%;
  border: 1.5px solid var(--border);
  background: var(--field-bg);
  border-radius: 12px;
  padding: 11px 14px;
  font-size: 14px;
  color: var(--ink);
  font-family: inherit;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: #38bdf8;
}
.field textarea { resize: vertical; min-height: 90px; }

/* Highlighted "what kind of issue" box */
.field-emphasize {
  border: 1.5px solid var(--border);
  background: var(--field-bg);
  border-radius: 16px;
  padding: 14px 16px 16px;
  margin-bottom: 18px;
}
.field-emphasize label { margin-bottom: 8px; }
.field-emphasize select { border-color: #7dd3fc; }

/* Conditional fields (shown/hidden based on another field's value) */
.field[data-conditional="true"] { display: none; }
.field[data-conditional="true"].is-visible { display: block; }

/* ---- Attachments dropzone ---- */
.attach-label {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--label-blue);
  margin-bottom: 8px;
  display: block;
}
.dropzone {
  border: 1.5px dashed var(--border);
  border-radius: 16px;
  background: var(--field-bg);
  padding: 28px 16px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dropzone:hover { border-color: #38bdf8; }
.dropzone.drag-over { border-color: var(--accent-gold); background: color-mix(in srgb, var(--field-bg) 80%, var(--accent-gold) 20%); transform: scale(1.01); }
.dropzone .dz-icon { font-size: 22px; margin-bottom: 6px; }
.dropzone .dz-main { font-weight: 700; font-size: 13.5px; margin: 0 0 4px; }
.dropzone .dz-sub { color: var(--ink-soft); font-size: 12px; margin: 0; }
.dropzone .dz-paste { color: #38bdf8; font-size: 12px; font-weight: 600; margin-top: 6px; }

.file-list { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.file-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--field-bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 12px;
  font-size: 12.5px;
}
.file-chip .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-chip button {
  border: none; background: none; color: #ef4a72; cursor: pointer; font-size: 14px; font-weight: 700; padding: 0 2px;
}

.field-with-btn { display: flex; gap: 8px; align-items: stretch; }
.field-with-btn input { flex: 1; }
.btn-generate {
  flex-shrink: 0;
  width: 42px;
  border: 1.5px solid var(--border);
  background: linear-gradient(135deg, #6366f1, #0ea5e9);
  color: #fff;
  border-radius: 12px;
  font-size: 16px;
  cursor: pointer;
  transition: transform 0.12s ease;
}
.btn-generate:hover { transform: translateY(-1px); }
.btn-generate:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }

.field-note { margin: 6px 0 0; font-size: 12px; font-weight: 600; color: var(--ink-soft); }
.field-note.ok { color: #1a9c6e; }
.field-note.err { color: #e0577a; }

.field input[readonly] { background: var(--border); color: var(--ink-soft); }

/* ---- Submit ---- */
.form-actions { margin-top: 26px; }

.btn-submit {
  width: 100%;
  background: linear-gradient(90deg, #6366f1, #0ea5e9);
  color: #fff;
  border: none;
  border-radius: 12px;
  padding: 14px 22px;
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 10px 24px -10px rgba(45, 90, 220, 0.55);
  transition: transform 0.12s ease;
  display: flex; align-items: center; justify-content: center; gap: 8px;
}
.btn-submit:hover { transform: translateY(-1px); box-shadow: 0 10px 24px -10px rgba(45, 90, 220, 0.55), 0 0 0 1px var(--accent-gold); }
.btn-submit:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; }

.status-msg { display: block; margin-top: 10px; font-size: 13px; font-weight: 600; text-align: center; }
.status-msg.ok { color: #1a9c6e; }
.status-msg.err { color: #e0577a; }

/* ---- Page transition: Home <-> Form navigation ("slide" style, matches
   transition-preview.html option 2) ----
   Home (index.html) applies these to .hub-layout (sidebar + main content,
   header/brand-row stay put); Form (form.html) applies these to
   .form-page (topbar/back-pill stay put). See app.js/index.html inline
   script for the click-intercept + setTimeout(navigate) wiring — CSS
   alone can't delay a real cross-page navigation. */
.page-slide-out { animation: pageSlideOut 0.2s ease forwards; }
.page-slide-in { animation: pageSlideIn 0.28s cubic-bezier(.22, .9, .34, 1) forwards; }
@keyframes pageSlideOut { to { opacity: 0; transform: translateX(-24px) scale(0.98); } }
@keyframes pageSlideIn { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: translateX(0); } }

/* ---- "Edit fields — syncs Telegram + Sheet" modal, threads.html only.
   Field rows themselves reuse the existing .field/label/input/select/
   textarea styling from the submission form above — only the
   overlay/card chrome around them is new here. */
.edit-details-overlay {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(6, 8, 20, 0.72);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  animation: fadeIn 0.15s ease;
}
.edit-details-modal {
  position: relative;
  width: 100%; max-width: 480px; max-height: 85vh;
  overflow-y: auto;
  background: var(--card-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 30px 34px 28px;
}
.edit-details-title { font-family: var(--font-display); font-weight: 700; font-size: 18px; margin-bottom: 4px; padding-right: 24px; }
.edit-details-sub { color: var(--ink-soft); font-size: 12.5px; line-height: 1.5; margin-bottom: 20px; }
.edit-details-fields .field { margin-bottom: 16px; }
.edit-details-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 6px; }
.edit-details-status { font-size: 13px; font-weight: 600; margin-top: 4px; min-height: 18px; }
.edit-details-status.err { color: #e0577a; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* ---- "Forwarded from/to" reference cards (threads.html) — same idea as
   the quoted "Original ticket" card already used in reply threads, just
   for cross-Topic links instead of cross-message ones. Clickable, jumps
   straight to the linked ticket via openThread(). */
.forward-link-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px dashed var(--panel-border);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 12.5px;
  margin-top: 10px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.forward-link-card:hover { border-color: var(--accent-gold); background: rgba(255, 255, 255, 0.05); }
.forward-link-time { color: var(--ink-soft); font-size: 11.5px; }

/* ---- Withdraw Issue: duplicate-TID inline warning (public/assets/app.js) ---- */
.tid-warning {
  margin-left: 8px;
  font-size: 12px;
  font-weight: 700;
  color: var(--required);
}
.tid-warning.checking { color: var(--ink-soft); font-weight: 500; font-style: italic; }

/* Withdraw Issue: TID input itself, not just the label warning — see
   setTidState() in app.js, which is the single place that toggles this
   class alongside the warning text and Submit's disabled state, so all
   three always agree with each other. */
.field input.field-error {
  border-color: var(--required);
  background: color-mix(in srgb, var(--field-bg) 85%, var(--required) 15%);
}
.field input.field-error:focus { border-color: var(--required); }
