/* TeleSpace Admin — Liquid Glass workspace.
   Implements UI_DESIGN_SYSTEM.md v1.0: ambient canvas, floating glass sidebar,
   semantic tokens, Telegram-blue accent, violet workspace identity.
   Class names are unchanged — this file is styling only. */

:root{
  /* ---- primitives ---- */
  --tf-blue-500:#229ED9; --tf-blue-600:#168AC2;
  --tf-violet-500:#6D5DFB; --tf-violet-600:#5848E6;
  --tf-pink-400:#E98BC2; --tf-cyan-300:#8BE1F5;
  --tf-ink-950:#15161A; --tf-ink-800:#30323A; --tf-ink-600:#60636F; --tf-ink-400:#9296A3;
  --tf-cloud-50:#FBFCFF; --tf-cloud-100:#F4F6FB; --tf-cloud-200:#E9ECF4; --tf-white:#FFFFFF;

  /* ---- semantic colour ----
     Three text steps, each AA-verified against the glass-over-canvas background
     (~#F7F8FC): primary ≈16:1, secondary ≈6.4:1, tertiary ≈4.5:1. */
  --tf-color-canvas:#F4F6FB;
  --tf-color-text-primary:#15161A;
  --tf-color-text-secondary:#565A66;
  --tf-color-text-tertiary:#6E7280;
  --tf-color-text-inverse:#FFFFFF;
  --tf-color-accent:#229ED9;
  --tf-color-accent-strong:#168AC2;
  --tf-color-ai:#6D5DFB;
  --tf-color-focus:#229ED9;
  --tf-color-success:#2E9B65;
  --tf-color-warning:#BD7411;
  --tf-color-danger:#D94B59;
  --tf-color-info:#3A83E8;
  --tf-color-hairline:rgba(21,22,26,.09);

  /* ---- glass material ---- */
  --tf-glass-fill:rgba(255,255,255,.54);
  --tf-glass-fill-strong:rgba(255,255,255,.72);
  --tf-glass-fill-subtle:rgba(255,255,255,.34);
  --tf-glass-border:rgba(255,255,255,.72);
  --tf-glass-highlight:rgba(255,255,255,.55);
  --tf-glass-blur:24px;
  --tf-glass-blur-heavy:40px;

  /* ---- elevation ---- */
  --tf-shadow-float:0 16px 48px rgba(38,46,70,.14);
  --tf-shadow-overlay:0 28px 88px rgba(27,34,58,.20);
  --tf-shadow-press:0 6px 16px rgba(38,46,70,.12);

  /* ---- radius ---- */
  --tf-radius-control:12px; --tf-radius-card:20px; --tf-radius-panel:28px;
  --tf-radius-sidebar:32px; --tf-radius-pill:999px;

  /* ---- spacing ---- */
  --tf-space-4:4px; --tf-space-8:8px; --tf-space-12:12px; --tf-space-16:16px;
  --tf-space-20:20px; --tf-space-24:24px; --tf-space-32:32px; --tf-space-40:40px;
  --tf-space-48:48px; --tf-space-64:64px; --tf-space-80:80px; --tf-space-96:96px;

  /* ---- type ----
     Inter carries Latin; Noto Sans SC carries 中文 (Inter has no CJK glyphs, so
     without this the UI mixes two unrelated typefaces). System faces follow as
     offline fallbacks so the interface stays correct with no network. */
  --tf-font-sans:"Inter","Noto Sans SC",ui-sans-serif,-apple-system,BlinkMacSystemFont,
    "Segoe UI Variable Text","Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB",
    "Microsoft YaHei UI","Microsoft YaHei",sans-serif;
  --tf-font-mono:ui-monospace,SFMono-Regular,"SF Mono","Cascadia Mono",Menlo,Consolas,monospace;
  --tf-font-display:48px; --tf-font-h1:32px; --tf-font-h2:24px; --tf-font-h3:18px;
  --tf-font-body:15px; --tf-font-body-sm:13px; --tf-font-label:12px; --tf-font-metric:36px;
  --tf-leading-tight:1.08; --tf-leading-heading:1.18; --tf-leading-body:1.48; --tf-leading-caption:1.35;

  /* ---- motion ---- */
  --tf-motion-instant:100ms; --tf-motion-fast:160ms; --tf-motion-standard:240ms;
  --tf-motion-panel:360ms; --tf-motion-hero:520ms;
  --tf-ease-spring:cubic-bezier(.22,1,.36,1);
  --tf-ease-enter:cubic-bezier(.16,1,.3,1);
  --tf-ease-exit:cubic-bezier(.7,0,.84,0);

  /* ---- layout ---- */
  --tf-sidebar-width:272px; --tf-content-max:1600px; --tf-reading-max:760px;

  /* ---- legacy aliases (a few inline styles in app.js still read these) ---- */
  --bg:var(--tf-color-canvas); --surface:var(--tf-glass-fill); --surface-2:var(--tf-cloud-100);
  --border:var(--tf-color-hairline); --text:var(--tf-color-text-primary);
  --muted:var(--tf-color-text-secondary); --primary:var(--tf-color-accent);
  --primary-hover:var(--tf-color-accent-strong); --accent:var(--tf-color-ai);
  --success:var(--tf-color-success); --warning:var(--tf-color-warning);
  --danger:var(--tf-color-danger); --info:var(--tf-color-info);
  --radius:var(--tf-radius-card); --radius-sm:var(--tf-radius-control);
  --shadow:var(--tf-shadow-float);
}

*{ box-sizing:border-box; }
html,body{ margin:0; height:100%; }
body{
  background:var(--tf-color-canvas); color:var(--tf-color-text-primary);
  font-family:var(--tf-font-sans);
  font-size:var(--tf-font-body); line-height:var(--tf-leading-body); font-weight:400;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  font-optical-sizing:auto;
  /* contextual alternates + proportional lining figures for running text */
  font-variant-numeric:lining-nums proportional-nums;
}

/* ---------- Ambient canvas (static, behind everything) ---------- */
body::before,body::after{
  content:""; position:fixed; inset:0; z-index:-1; pointer-events:none;
}
body::before{
  background:
    radial-gradient(1100px 720px at 12% -8%, rgba(34,158,217,.16), transparent 62%),
    radial-gradient(900px 640px at 92% 4%, rgba(109,93,251,.14), transparent 60%);
}
body::after{
  background:
    radial-gradient(820px 560px at 78% 92%, rgba(233,139,194,.12), transparent 62%),
    radial-gradient(700px 520px at 4% 84%, rgba(139,225,245,.12), transparent 60%);
}

.muted{ color:var(--tf-color-text-secondary); }
.small{ font-size:var(--tf-font-body-sm); line-height:var(--tf-leading-caption); }
[hidden]{ display:none !important; }

/* Visible focus everywhere (accessibility requirement) */
:focus-visible{ outline:2px solid var(--tf-color-focus); outline-offset:2px; border-radius:6px; }

/* ---------- Glass material primitives ---------- */
.panel,.card,.kpi,.tbl-wrap,.click-card,.tile,.queue-details,.modal,.login-card{
  background:var(--tf-glass-fill);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur)) saturate(180%);
  backdrop-filter:blur(var(--tf-glass-blur)) saturate(180%);
  border:1px solid var(--tf-glass-border);
  box-shadow:var(--tf-shadow-float), inset 0 1px 0 var(--tf-glass-highlight);
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .panel,.card,.kpi,.tbl-wrap,.click-card,.tile,.queue-details,.modal,.login-card,.sidebar{
    background:rgba(255,255,255,.94);
  }
}

/* ---------- Login ---------- */
/* Its own small palette: the login page is the one screen that isn't the admin
   shell, and hard-coding these keeps a later token change from quietly
   restyling it. */
.login-wrap{
  --lp-primary:#356DFF;
  --lp-secondary:#6C63FF;
  --lp-ink:#172554;
  --lp-ink-soft:#5A6B93;

  position:relative; min-height:100vh; min-height:100dvh;
  display:flex; align-items:center; justify-content:center;
  padding:var(--tf-space-40) var(--tf-space-24) var(--tf-space-64);
  /* Three stops, not two: a straight two-stop ramp reads as a cheap gradient,
     the lighter band through the middle is what makes it look lit. */
  background:linear-gradient(160deg,#EEF5FF 0%,#F7F9FF 48%,#EAF1FF 100%);
  /* Only sideways. Clipping vertically would cut the card off on a short laptop
     screen instead of letting the page scroll; the decorations are clipped by
     .lp-bg, which is what overflow:hidden was actually for. */
  overflow-x:hidden;
}

/* ---- background decoration ---- */
.lp-bg{ position:absolute; inset:0; pointer-events:none; overflow:hidden; }
.lp-canvas{ position:absolute; inset:0; width:100%; height:100%; }
.lp-orbits{ position:absolute; inset:-10%; width:120%; height:120%; opacity:.5; }
.lp-orbit-spin{ transform-origin:600px 400px; animation:lp-spin 90s linear infinite; }
@keyframes lp-spin{ to{ transform:rotate(360deg) } }

/* Round white badges resting on the map. Each gets its own duration and delay —
   matched timings make them bob in lockstep, which reads as a loading spinner
   rather than atmosphere. */
.lp-badge{ position:absolute; display:grid; place-items:center;
  width:60px; height:60px; border-radius:50%;
  background:rgba(255,255,255,.72); color:var(--lp-primary);
  border:1px solid rgba(255,255,255,.9);
  box-shadow:0 10px 26px rgba(80,100,180,.14);
  -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px); }
.lp-badge svg{ width:26px; height:26px; }
.lp-b1{ top:24%; left:19%;  animation:lp-drift 19s ease-in-out infinite; }
.lp-b2{ top:44%; left:13%;  animation:lp-drift 24s ease-in-out infinite -6s; }
.lp-b3{ top:27%; right:16%; animation:lp-drift 21s ease-in-out infinite -11s; }
.lp-b4{ top:62%; right:14%; animation:lp-drift 27s ease-in-out infinite -3s; }

.lp-plane{ position:absolute; color:#fff;
  filter:drop-shadow(0 6px 14px rgba(80,110,200,.35)); }
.lp-plane svg{ width:100%; height:100%; display:block; }
.lp-p1{ width:56px; height:56px; top:52%; left:8%;  transform:rotate(-8deg);
  animation:lp-fly 26s ease-in-out infinite; }
.lp-p2{ width:64px; height:64px; top:40%; right:6%; transform:scaleX(-1) rotate(-8deg);
  animation:lp-fly 31s ease-in-out infinite -12s; }
@keyframes lp-drift{
  0%,100%{ transform:translate3d(0,0,0) }
  50%    { transform:translate3d(0,-20px,0) }
}
@keyframes lp-fly{
  0%,100%{ translate:0 0 }
  50%    { translate:16px -26px }
}
@media (max-width:1180px){ .lp-b2, .lp-b4{ display:none; } }
@media (max-width:820px){ .lp-badge, .lp-plane{ display:none; } }

/* ---- corner chrome ---- */
.lp-chip{ position:absolute; z-index:2; display:inline-flex; align-items:center;
  gap:var(--tf-space-8); padding:10px var(--tf-space-16);
  border-radius:var(--tf-radius-pill); font-family:inherit;
  font-size:var(--tf-font-body-sm); font-weight:500; color:var(--lp-ink);
  background:rgba(255,255,255,.72); border:1px solid rgba(255,255,255,.9);
  box-shadow:0 8px 22px rgba(80,100,180,.12);
  -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px); }
.lp-chip svg{ width:16px; height:16px; color:var(--lp-primary); flex:none; }
.lp-chip-tl{ top:var(--tf-space-24); left:var(--tf-space-24); }
.lp-chip-tr{ top:var(--tf-space-24); right:var(--tf-space-24); cursor:pointer;
  transition:transform 200ms ease, box-shadow 200ms ease; }
.lp-chip-tr:hover{ transform:translateY(-1px); box-shadow:0 12px 26px rgba(80,100,180,.2); }
.lp-chip-tr:focus-visible{ outline:2px solid var(--lp-primary); outline-offset:3px; }

@media (max-width:640px){
  .lp-chip-tl{ display:none; }
  .lp-chip-tr{ top:var(--tf-space-16); right:var(--tf-space-16); }
}

/* ---- the card ---- */
.login-card{
  position:relative; z-index:1;
  /* 440, not the 540 the guide asked for: at 540 the fields stretched so wide
     that a short username looked lost in them. Narrower reads as a login box. */
  width:440px; max-width:min(460px,100%);
  border-radius:28px; padding:var(--tf-space-40) var(--tf-space-32); text-align:center;
  background:rgba(255,255,255,.82);
  -webkit-backdrop-filter:blur(30px) saturate(150%);
  backdrop-filter:blur(30px) saturate(150%);
  border:1px solid rgba(255,255,255,.45);
  box-shadow:0 35px 80px rgba(80,100,180,.18);
  animation:lp-card-in 620ms cubic-bezier(.16,.84,.44,1) both;
}
@keyframes lp-card-in{
  from{ opacity:0; transform:translateY(18px) scale(.985) }
  to  { opacity:1; transform:none }
}
.login-logo{ width:150px; max-width:60%; height:auto; display:block;
  margin:0 auto var(--tf-space-12); }
.lp-tagline{ font-size:11px; font-weight:600; letter-spacing:.22em;
  color:var(--lp-ink-soft); margin-bottom:var(--tf-space-32); }

/* ---- fields ---- */
/* The icon sits inside the box, so the input carries the left padding rather
   than the wrapper — otherwise the caret starts underneath the glyph. */
.lp-field{ position:relative; margin-bottom:var(--tf-space-20); }
/* z-index, not decoration: the input is a later sibling that paints its own
   background, so without this it covers the icon completely. */
.lp-field-ic{ position:absolute; z-index:2; left:18px; top:50%; transform:translateY(-50%);
  width:19px; height:19px; color:#93A1C4; pointer-events:none;
  transition:color 200ms linear; }
.login-wrap .lp-field input{
  position:relative; z-index:1;
  width:100%; height:56px; box-sizing:border-box;
  padding:0 52px 0 50px;
  border-radius:14px; border:1px solid #E5E7EB; background:#fff;
  font-family:inherit; font-size:var(--tf-font-body); color:var(--lp-ink);
  transition:border-color 200ms linear, box-shadow 200ms linear;
}
.login-wrap .lp-field input::placeholder{ color:#9AA6C2; }
/* One ring, not two. The app-wide input:focus rule paints a teal 3px halo; this
   is the same idea in the login palette, tightened so it reads as a highlight
   rather than a second border. */
.login-wrap .lp-field input:focus{
  outline:none; border-color:var(--lp-primary);
  box-shadow:0 0 0 3px rgba(53,109,255,.16);
}
.lp-field:focus-within .lp-field-ic{ color:var(--lp-primary); }
.login-wrap .field-label{ font-size:13px; font-weight:500; color:var(--lp-ink);
  margin-bottom:var(--tf-space-8); }

.lp-eye{ position:absolute; z-index:2; right:8px; top:50%; transform:translateY(-50%);
  width:38px; height:38px; display:grid; place-items:center;
  background:transparent; border:none; border-radius:10px; cursor:pointer;
  color:#93A1C4; transition:color 200ms linear, background 200ms linear; }
.lp-eye svg{ width:19px; height:19px; }
.lp-eye:hover{ color:var(--lp-primary); background:rgba(53,109,255,.08); }
.lp-eye:focus-visible{ outline:2px solid var(--lp-primary); outline-offset:1px; }

/* ---- remember me ---- */
/* The real checkbox stays in the DOM (keyboard, forms, screen readers all rely
   on it) but is hidden behind a drawn box, since a native checkbox cannot be
   given this shape. */
.lp-remember{ display:flex; align-items:center; gap:var(--tf-space-8);
  margin:calc(var(--tf-space-4) * -1) 0 var(--tf-space-8);
  font-size:var(--tf-font-body-sm); color:var(--lp-ink-soft); cursor:pointer;
  user-select:none; text-align:left; }
.lp-remember input{ position:absolute; opacity:0; width:0; height:0; }
.lp-remember-box{ flex:none; display:grid; place-items:center;
  width:19px; height:19px; border-radius:6px;
  border:1.5px solid #D3DAEA; background:#fff; color:#fff;
  transition:background 160ms linear, border-color 160ms linear; }
.lp-remember-box svg{ width:12px; height:12px; opacity:0;
  transition:opacity 160ms linear; }
.lp-remember input:checked + .lp-remember-box{
  background:var(--lp-primary); border-color:var(--lp-primary); }
.lp-remember input:checked + .lp-remember-box svg{ opacity:1; }
.lp-remember input:focus-visible + .lp-remember-box{
  outline:2px solid var(--lp-primary); outline-offset:2px; }
.lp-remember:hover .lp-remember-box{ border-color:var(--lp-primary); }

/* ---- submit ---- */
.lp-submit{
  width:100%; height:56px; border:none; border-radius:16px; cursor:pointer;
  display:flex; align-items:center; justify-content:center; gap:var(--tf-space-8);
  font-family:inherit; font-size:15px; font-weight:600; color:#fff;
  background:linear-gradient(135deg,#4D8DFF,#2E5FFF);
  box-shadow:0 8px 20px rgba(46,95,255,.26);
  margin-top:var(--tf-space-8);
  transition:transform 250ms ease, box-shadow 250ms ease, filter 250ms ease;
}
.lp-submit svg{ width:18px; height:18px; transition:transform 250ms ease; }
.lp-submit:hover{ transform:translateY(-2px); filter:brightness(1.05);
  box-shadow:0 14px 32px rgba(46,95,255,.38); }
.lp-submit:hover svg{ transform:translateX(3px); }
.lp-submit:active{ transform:translateY(0); box-shadow:0 6px 16px rgba(46,95,255,.3); }
.lp-submit:focus-visible{ outline:2px solid var(--lp-primary); outline-offset:3px; }

.lp-newhere{ margin:var(--tf-space-20) 0 0; font-size:var(--tf-font-body-sm);
  color:var(--lp-ink-soft); }
.lp-newhere b{ color:var(--lp-primary); font-weight:600; }


@media (max-width:600px){
  .login-card{ padding:var(--tf-space-32) var(--tf-space-24); border-radius:22px; }
  .lp-tagline{ margin-bottom:var(--tf-space-24); letter-spacing:.16em; }
}
/* Atmosphere is never worth overriding this. The canvas checks the same query
   in JS and stops its loop. */
@media (prefers-reduced-motion: reduce){
  .lp-orbit-spin, .lp-badge, .lp-plane{ animation:none; }
  .login-card{ animation:none; }
}
.login-brand{ font-size:var(--tf-font-h1); font-weight:600; letter-spacing:-.02em;
  line-height:var(--tf-leading-heading); text-align:center; margin-bottom:var(--tf-space-4);
  background:linear-gradient(100deg,var(--tf-blue-500),var(--tf-violet-500));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
.login-card h1{ font-size:var(--tf-font-h3); margin:0 0 var(--tf-space-4); font-weight:600; }
.login-card p{ margin:0 0 var(--tf-space-20); font-size:var(--tf-font-body-sm); color:var(--tf-color-text-secondary); }
.field-label{ display:block; text-align:left; font-size:var(--tf-font-label);
  color:var(--tf-color-text-secondary); margin:0 0 var(--tf-space-8); font-weight:500;
  letter-spacing:.01em; }
.login-err{ color:var(--tf-color-danger); font-size:var(--tf-font-body-sm); margin-top:var(--tf-space-12); }
.auth-tabs{ display:flex; gap:var(--tf-space-4); background:var(--tf-glass-fill-subtle);
  border:1px solid var(--tf-glass-border); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-4); margin:var(--tf-space-4) 0 var(--tf-space-20); }
.auth-tab{ flex:1; background:transparent; border:none; color:var(--tf-color-text-secondary);
  font-weight:500; font-size:var(--tf-font-body-sm); padding:var(--tf-space-8) var(--tf-space-12);
  border-radius:var(--tf-space-8); cursor:pointer; font-family:inherit;
  transition:background var(--tf-motion-fast) var(--tf-ease-spring), color var(--tf-motion-fast) linear; }
.auth-tab.active{ background:var(--tf-white); color:var(--tf-color-text-primary);
  box-shadow:var(--tf-shadow-press); }
.auth-pane{ text-align:left; }
.auth-msg{ font-size:var(--tf-font-body-sm); margin-top:var(--tf-space-12); line-height:var(--tf-leading-body); }
.auth-msg.ok{ color:var(--tf-color-success); }
.auth-msg.bad{ color:var(--tf-color-danger); }
/* Something to act on, but not something broken — a restricted account still
   works, it just must be left alone for a while. */
.auth-msg.warn{ color:#b5790f; }
.auth-foot{ margin-top:var(--tf-space-16); color:var(--tf-color-text-secondary); }
.auth-foot a{ color:var(--tf-color-accent); text-decoration:none; font-weight:500; }
.role-pill{ display:inline-block; margin-top:var(--tf-space-4); font-size:var(--tf-font-label);
  font-weight:500; color:var(--tf-violet-600); background:rgba(109,93,251,.12);
  border-radius:var(--tf-radius-pill); padding:2px 10px; }
.nav-badge{ margin-left:auto; background:var(--tf-color-danger); color:var(--tf-white);
  font-size:var(--tf-font-label); font-weight:600; min-width:20px; height:20px;
  border-radius:var(--tf-radius-pill); display:inline-flex; align-items:center;
  justify-content:center; padding:0 6px; }
/* A customer reply nobody has opened yet. .nav-badge pushes itself to the end
   of its row (margin-left:auto); this one follows the name, so it must not
   inherit that push or it would fly to the far side of the cell. */
.reply-dot{ background:var(--tf-color-danger); color:var(--tf-white);
  font-size:var(--tf-font-label); font-weight:600; min-width:18px; height:18px;
  border-radius:var(--tf-radius-pill); display:inline-flex; align-items:center;
  justify-content:center; padding:0 5px; vertical-align:middle; margin-left:6px; }
tr.row-unread td{ background:rgba(220,38,38,.055); }
tr.row-unread td:first-child{ box-shadow:inset 3px 0 0 var(--tf-color-danger); }

.role-sel{ width:auto; min-width:170px; }
#whoami{ padding-bottom:var(--tf-space-12); border-bottom:1px solid var(--tf-color-hairline);
  margin-bottom:var(--tf-space-4); line-height:var(--tf-leading-caption);
  color:var(--tf-color-text-secondary); }

/* ---------- Controls ---------- */
/* `<input>` with no type attribute is still a text box, but `input[type=text]`
   does NOT match it — which silently left a third of the fields on browser
   defaults. `input:not([type])` covers them at the same specificity as the
   typed selectors, so later rules like `.filters input` still win. */
input:not([type]),
input[type=text],input[type=password],input[type=number],
input[type=datetime-local],input[type=date],input[type=time],
input[type=tel],input[type=email],
input[type=search],input[type=url],
textarea,select{
  width:100%; box-sizing:border-box;
  /* Hairline for the edge (a white glass-border would vanish on a light panel);
     the translucent fill plus the inner top highlight carry the glass look. */
  background:var(--tf-glass-fill-strong); border:1px solid var(--tf-color-hairline);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur)); backdrop-filter:blur(var(--tf-glass-blur));
  box-shadow:inset 0 1px 0 var(--tf-glass-highlight);
  color:var(--tf-color-text-primary); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-12) var(--tf-space-16); font:inherit; font-size:var(--tf-font-body);
  min-height:44px; outline:none;
  transition:border-color var(--tf-motion-fast) linear, box-shadow var(--tf-motion-fast) linear,
             background var(--tf-motion-fast) linear; }
input::placeholder,textarea::placeholder{ color:var(--tf-color-text-tertiary); }
input:focus,textarea:focus,select:focus{
  background:var(--tf-white,#fff); border-color:var(--tf-color-focus);
  box-shadow:0 0 0 3px rgba(34,158,217,.22), inset 0 1px 0 var(--tf-glass-highlight);
  outline:none; }
input:disabled,textarea:disabled,select:disabled{ opacity:.55; cursor:not-allowed; }
textarea{ resize:vertical; min-height:130px; line-height:var(--tf-leading-body); }
select{ cursor:pointer; }

/* ---------- Buttons ---------- */
.btn-primary{ background:var(--tf-color-accent); color:var(--tf-color-text-inverse); border:none;
  font-weight:500; border-radius:var(--tf-radius-control); padding:var(--tf-space-12) var(--tf-space-20);
  cursor:pointer; font-size:var(--tf-font-body); font-family:inherit; min-height:40px;
  box-shadow:var(--tf-shadow-press);
  transition:background var(--tf-motion-fast) linear, transform var(--tf-motion-fast) var(--tf-ease-spring); }
.btn-primary:hover{ background:var(--tf-color-accent-strong); transform:scale(1.01); }
.btn-primary:active{ transform:scale(.985); }
.btn-primary:disabled{ opacity:.45; cursor:not-allowed; transform:none; box-shadow:none; }
.btn-ghost{ background:var(--tf-glass-fill-subtle); color:var(--tf-color-text-primary);
  border:1px solid var(--tf-glass-border); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-8) var(--tf-space-16); cursor:pointer; font-weight:500;
  font-size:var(--tf-font-body-sm); font-family:inherit; min-height:36px;
  transition:background var(--tf-motion-fast) linear, transform var(--tf-motion-fast) var(--tf-ease-spring); }
.btn-ghost:hover{ background:var(--tf-white); transform:scale(1.01); }
.btn-ghost:active{ transform:scale(.985); }
.btn-block{ width:100%; margin-top:var(--tf-space-16); display:block; }
.btn-sm{ padding:var(--tf-space-8) var(--tf-space-12); font-size:var(--tf-font-body-sm); min-height:32px; }
.btn-danger{ color:var(--tf-color-danger); border-color:rgba(217,75,89,.32); }
.btn-danger:hover{ background:rgba(217,75,89,.10); }
.btn-danger-solid{ background:var(--tf-color-danger); color:var(--tf-white); border:none;
  font-weight:500; border-radius:var(--tf-radius-control); padding:var(--tf-space-8) var(--tf-space-16);
  cursor:pointer; font-size:var(--tf-font-body-sm); font-family:inherit; min-height:36px; }
.btn-danger-solid:hover{ background:#c33f4d; }
.btn-alt{ background:var(--tf-color-ai); color:var(--tf-white); border:none;
  border-radius:var(--tf-radius-control); cursor:pointer; font-weight:500; font-family:inherit; }
.btn-alt:hover{ background:var(--tf-violet-600); }

/* ---------- App shell: floating sidebar over ambient canvas ---------- */
.app{ display:grid; grid-template-columns:calc(var(--tf-sidebar-width) + var(--tf-space-32)) 1fr;
  min-height:100vh; }
.sidebar{
  position:sticky; top:var(--tf-space-16); height:calc(100vh - var(--tf-space-32));
  width:var(--tf-sidebar-width); margin:var(--tf-space-16);
  display:flex; flex-direction:column; padding:var(--tf-space-16);
  border-radius:var(--tf-radius-sidebar);
  background:linear-gradient(180deg, rgba(109,93,251,.10), rgba(255,255,255,.02)), var(--tf-glass-fill);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur-heavy)) saturate(180%);
  backdrop-filter:blur(var(--tf-glass-blur-heavy)) saturate(180%);
  border:1px solid var(--tf-glass-border);
  box-shadow:var(--tf-shadow-float), inset 0 1px 0 var(--tf-glass-highlight);
}
.brand{ display:flex; flex-direction:row; align-items:center; gap:var(--tf-space-12);
  padding:var(--tf-space-8) var(--tf-space-12) var(--tf-space-20); }
.brand-mark{ width:40px; height:auto; flex:none; display:block; }
.brand-text{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.brand-name{ font-size:var(--tf-font-h3); font-weight:600; letter-spacing:-.02em;
  line-height:var(--tf-leading-heading);
  background:linear-gradient(100deg,var(--tf-violet-500),var(--tf-blue-500));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; }
.brand-img{ width:160px; max-width:100%; height:auto; display:block; }
.brand-sub{ font-size:var(--tf-font-label); letter-spacing:.06em; text-transform:uppercase;
  color:var(--tf-color-text-tertiary); font-weight:500; }
.nav{ display:flex; flex-direction:column; gap:2px; margin-top:var(--tf-space-4); flex:1;
  overflow-y:auto; overflow-x:hidden; }
.nav a{ display:flex; align-items:center; gap:var(--tf-space-12);
  min-height:44px; padding:0 var(--tf-space-12); border-radius:var(--tf-radius-control);
  color:var(--tf-color-text-secondary); text-decoration:none; font-weight:500;
  font-size:var(--tf-font-body); cursor:pointer;
  transition:background var(--tf-motion-fast) linear, color var(--tf-motion-fast) linear,
             transform var(--tf-motion-fast) var(--tf-ease-spring); }
.nav a .ic{ width:18px; text-align:center; }
.nav a:hover{ background:var(--tf-glass-fill-subtle); color:var(--tf-color-text-primary); }
.nav a:active{ transform:scale(.99); }
/* selected = light liquid capsule (not a thin left bar) */
.nav a.active{ background:var(--tf-white); color:var(--tf-color-text-primary); font-weight:600;
  box-shadow:var(--tf-shadow-press), inset 0 1px 0 var(--tf-glass-highlight); }
.nav-group{ padding:var(--tf-space-16) var(--tf-space-12) var(--tf-space-4);
  font-size:var(--tf-font-label); font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--tf-color-text-tertiary); }
.nav-group:first-child{ padding-top:var(--tf-space-4); }

/* --- group trigger rows: same shape as a nav item, plus a caret --- */
.nav-group-btn{ display:flex; align-items:center; gap:var(--tf-space-12); width:100%;
  min-height:44px; padding:0 var(--tf-space-12); border:none; background:transparent;
  border-radius:var(--tf-radius-control); color:var(--tf-color-text-secondary);
  font:inherit; font-size:var(--tf-font-body); font-weight:500; text-align:left; cursor:pointer;
  transition:background var(--tf-motion-fast) linear, color var(--tf-motion-fast) linear; }
.nav-group-btn:hover{ background:var(--tf-glass-fill-subtle); color:var(--tf-color-text-primary); }
.nav-group-btn .nav-caret{ margin-left:auto; color:var(--tf-color-text-tertiary);
  transition:transform var(--tf-motion-fast) var(--tf-ease-spring); }
.nav-group-btn.open .nav-caret{ transform:rotate(90deg); }
/* Merely expanded: a quiet emphasis only — it must NOT read as "you are here". */
.nav-group-btn.open{ background:var(--tf-glass-fill-subtle); color:var(--tf-color-text-primary); }
/* Current location: the white capsule. Declared last so it wins over .open, and
   only ever one item in the sidebar carries it. */
.nav-group-btn.active{ background:var(--tf-white); color:var(--tf-color-text-primary);
  font-weight:600; box-shadow:var(--tf-shadow-press), inset 0 1px 0 var(--tf-glass-highlight); }

/* --- inline group expansion: children slide open under their group --- */
.nav-children{ display:flex; flex-direction:column; gap:2px;
  margin:2px 0 var(--tf-space-8) var(--tf-space-12);
  padding-left:var(--tf-space-8); border-left:2px solid var(--tf-color-hairline);
  /* Resting state is the OPEN state, so children stay usable even if the
     animation never runs (paused tab, no animation support). */
  animation:navChildrenIn var(--tf-motion-standard) var(--tf-ease-enter); }
@keyframes navChildrenIn{
  from{ opacity:0; transform:translateY(-4px); }
  to{ opacity:1; transform:none; }
}
.nav-child{ display:flex; align-items:center; min-height:38px;
  padding:0 var(--tf-space-12); border-radius:var(--tf-radius-control);
  color:var(--tf-color-text-secondary); text-decoration:none; cursor:pointer;
  font-size:var(--tf-font-body-sm); font-weight:500;
  transition:background var(--tf-motion-fast) linear, color var(--tf-motion-fast) linear; }
.nav-child:hover{ background:var(--tf-glass-fill-subtle); color:var(--tf-color-text-primary); }
.nav-child.active{ background:var(--tf-white); color:var(--tf-color-text-primary); font-weight:600;
  box-shadow:var(--tf-shadow-press), inset 0 1px 0 var(--tf-glass-highlight); }

/* --- Message Studio: library list + preview --- */
.ms-split{ display:grid; grid-template-columns:minmax(260px,360px) 1fr; gap:var(--tf-space-24);
  align-items:start; }
.ms-list{ display:flex; flex-direction:column; gap:var(--tf-space-4); max-height:62vh; overflow-y:auto; }
.ms-row{ display:flex; align-items:flex-start; gap:var(--tf-space-12); width:100%; text-align:left;
  padding:var(--tf-space-12); border:none; background:transparent; cursor:pointer; font:inherit;
  border-radius:var(--tf-radius-control); border-left:3px solid transparent;
  transition:background var(--tf-motion-fast) linear; }
.ms-row:hover{ background:var(--tf-glass-fill-subtle); }
.ms-row.active{ background:var(--tf-white); border-left-color:var(--tf-color-accent);
  box-shadow:var(--tf-shadow-press); }
.ms-row-ic{ flex:none; }
.ms-row-main{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.ms-row-name{ font-size:var(--tf-font-body); font-weight:500; color:var(--tf-color-text-primary); }
.ms-row-snip{ font-size:var(--tf-font-body-sm); color:var(--tf-color-text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@media (max-width:1024px){ .ms-split{ grid-template-columns:1fr; } }
.sidebar-foot{ border-top:1px solid var(--tf-color-hairline); padding-top:var(--tf-space-16);
  margin-top:var(--tf-space-8); display:flex; flex-direction:column; gap:var(--tf-space-8); }
.sidebar-foot .btn-block{ margin-top:0; }

.main{ min-width:0; }
.view{ padding:var(--tf-space-32) var(--tf-space-32) var(--tf-space-64); max-width:var(--tf-content-max); }

/* ---------- Section header ---------- */
.sec-h{ display:flex; align-items:center; gap:var(--tf-space-12); }
.sec-h .ic{ font-size:var(--tf-font-h2); }
.sec-h h2{ font-size:var(--tf-font-h1); font-weight:600; margin:0; letter-spacing:-.02em;
  line-height:var(--tf-leading-heading); }
.sec-sub{ color:var(--tf-color-text-secondary); margin:var(--tf-space-8) 0 0; font-size:var(--tf-font-body);
  max-width:var(--tf-reading-max); }
.sec-rule{ height:1px; background:var(--tf-color-hairline); border:0; margin:var(--tf-space-24) 0 var(--tf-space-32); }

/* ---------- Cards / panels ---------- */
.card{ border-radius:var(--tf-radius-card); padding:var(--tf-space-24); }
.panel{ border-radius:var(--tf-radius-panel); padding:var(--tf-space-24);
  margin-bottom:var(--tf-space-24); }
.panel-title{ font-size:var(--tf-font-h3); font-weight:600; margin:0 0 var(--tf-space-16);
  letter-spacing:-.01em; }

/* ---------- Community: group cards ---------- */
.cm-bar{ margin-bottom:var(--tf-space-16); flex-wrap:wrap; gap:var(--tf-space-12); }
.cm-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(340px,1fr));
  gap:var(--tf-space-20); align-items:start; }
/* the grid supplies the gaps — the panel's own bottom margin would double them */
.cm-grid > .panel{ margin-bottom:0; }
.cm-card{ cursor:pointer; transition:transform var(--tf-motion-fast) var(--tf-ease-spring),
  box-shadow var(--tf-motion-fast) linear; }
.cm-card:hover{ transform:translateY(-2px); box-shadow:var(--tf-shadow-overlay); }
.cm-name{ display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  font-size:var(--tf-font-body); }
.cm-meta{ display:flex; gap:var(--tf-space-20); flex-wrap:wrap;
  margin-top:var(--tf-space-16); padding-top:var(--tf-space-12);
  border-top:1px solid var(--tf-color-hairline); font-size:var(--tf-font-body-sm); }
.cm-actions{ margin-top:var(--tf-space-16); }
/* ---------- Group message filters ---------- */
.gf-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr));
  gap:var(--tf-space-4) var(--tf-space-20); }
.gf-item{ align-items:flex-start; padding:var(--tf-space-12) 0;
  border-bottom:1px solid var(--tf-color-hairline); }
.gf-item .sw{ margin-top:2px; }

/* chat-kind chips: quieter than a status badge, but a basic group is called out
   because Telegram simply can't mute in one */
.ck-row{ display:flex; gap:var(--tf-space-4); flex-wrap:wrap; margin:var(--tf-space-4) 0; }
.ck{ display:inline-block; padding:2px 8px; border-radius:var(--tf-radius-pill);
  font-size:var(--tf-font-label); font-weight:500; white-space:nowrap; }
.ck-ok{ color:var(--tf-color-text-secondary); background:var(--tf-glass-fill-subtle);
  border:1px solid var(--tf-color-hairline); }
.ck-warn{ color:#8a540c; background:rgba(189,116,17,.14); }
.ck-alert{ color:#a3323f; background:rgba(217,75,89,.14); margin-left:var(--tf-space-4); }
.ck-chan{ color:var(--tf-violet-600,#6d5dfb); background:rgba(109,93,251,.12); }

/* inline "Change" next to the handling bot — a link, not a competing button */
.cm-chbot{ background:none; border:none; padding:0 0 0 var(--tf-space-4); cursor:pointer;
  font:inherit; font-size:var(--tf-font-body-sm); color:var(--tf-color-accent);
  text-decoration:underline; }

/* ---------- Bot welcome-button rows ---------- */
/* Each menu screen is its own card; a nested one is nudged in and marked with a
   rail, so the relationship reads without having to measure indentation. */
.bmn-card + .bmn-card{ margin-left:var(--tf-space-24);
  border-left:3px solid var(--tf-color-accent, #2f6fd0); }
.bmn-auto{ margin-top:var(--tf-space-8); padding:var(--tf-space-8) var(--tf-space-12);
  border:1px dashed var(--tf-color-hairline); border-radius:var(--tf-radius-control);
  color:var(--tf-color-text-secondary); font-size:var(--tf-font-body-sm); }

.bmn-row{ display:flex; align-items:center; gap:var(--tf-space-12);
  padding:var(--tf-space-12); border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-control); margin-bottom:var(--tf-space-8); }
.bmn-ord{ display:flex; flex-direction:column; gap:2px; flex:none; }
.bmn-ord button{ min-width:28px; padding:0 var(--tf-space-4); line-height:1.4; }
.bmn-main{ flex:1; min-width:0; }
.bmn-main .muted{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ---------- Vertical rhythm between stacked blocks ----------
   Several block types carry a bottom margin but no top one (.panel), or no
   margin at all (.kpis, .seg, .tbl-wrap), so two of them in a row ended up
   touching. Sibling-only rules fix that without side effects: an element that
   sets its own margin inline still wins, the first block in a view stays flush,
   and adjacent margins collapse so nothing doubles up. */
.kpis + .panel,    .kpis + .kpis,     .kpis + .tbl-wrap, .kpis + .stack,
.kpis + .filters,  .kpis + .seg,      .kpis + .bulk-bar, .kpis + .empty,
.seg + .panel,     .seg + .kpis,      .seg + .tbl-wrap,  .seg + .stack,
.seg + .filters,   .seg + .empty,
.tbl-wrap + .panel, .tbl-wrap + .kpis, .tbl-wrap + .stack, .tbl-wrap + .tbl-wrap,
.stack + .panel,   .stack + .kpis,    .stack + .tbl-wrap,
.filters + .tbl-wrap, .filters + .kpis, .filters + .stack, .filters + .panel,
.panel + .kpis,    .panel + .tbl-wrap, .panel + .stack,   .panel + .seg,
.bulk-bar + .tbl-wrap, .empty + .panel{
  margin-top:var(--tf-space-24);
}

/* ---------- Metric cards ---------- */
.kpis{ display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:var(--tf-space-16); }
.kpi{ border-radius:var(--tf-radius-card); padding:var(--tf-space-20); }
.kpi-top{ display:flex; align-items:center; gap:var(--tf-space-8); margin-bottom:var(--tf-space-12); }
.kpi-ic{ display:none; }
.kpi-ic-hidden{ display:none; }
.kpi-label{ font-size:var(--tf-font-label); text-transform:uppercase; letter-spacing:.05em;
  color:var(--tf-color-text-secondary); font-weight:500; line-height:var(--tf-leading-caption); }
.kpi-value{ font-size:var(--tf-font-metric); font-weight:600; letter-spacing:-.02em;
  line-height:var(--tf-leading-tight); font-variant-numeric:tabular-nums; }
.kpi.accent .kpi-value{ color:var(--tf-color-ai); }
.kpi-delta{ margin-top:var(--tf-space-8); font-size:var(--tf-font-body-sm); font-weight:500;
  color:var(--tf-color-success); }
.kpi-delta.flat{ color:var(--tf-color-text-tertiary); }

/* ---------- Schedule progress ---------- */
.kpis-sm{ grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); }
.kpis-sm .kpi-value{ font-size:var(--tf-font-h2); }
.progc{ display:flex; align-items:center; gap:var(--tf-space-12); }
.frac{ font-weight:600; min-width:44px; font-variant-numeric:tabular-nums; }
.frac.done{ color:var(--tf-color-success); }
.prog{ flex:1; min-width:80px; height:8px; background:var(--tf-cloud-200);
  border-radius:var(--tf-radius-pill); overflow:hidden; }
.prog-fill{ height:100%; background:linear-gradient(90deg,var(--tf-blue-500),var(--tf-violet-500));
  border-radius:var(--tf-radius-pill); transition:width var(--tf-motion-standard) var(--tf-ease-spring); }
.queue-details{ margin-top:var(--tf-space-24); border-radius:var(--tf-radius-card);
  padding:var(--tf-space-16) var(--tf-space-24); }
.queue-details summary{ cursor:pointer; font-weight:500; font-size:var(--tf-font-body);
  color:var(--tf-color-text-secondary); }
.queue-details summary:hover{ color:var(--tf-color-text-primary); }
.queue-details[open] summary{ color:var(--tf-color-text-primary); margin-bottom:var(--tf-space-8); }

/* ---------- Segmented control ---------- */
.seg{ display:inline-flex; gap:var(--tf-space-4); background:var(--tf-glass-fill-subtle);
  border:1px solid var(--tf-glass-border); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-4); flex-wrap:wrap;
  -webkit-backdrop-filter:blur(var(--tf-glass-blur)); backdrop-filter:blur(var(--tf-glass-blur)); }
.seg-btn{ background:transparent; border:none; color:var(--tf-color-text-secondary); font-weight:500;
  font-size:var(--tf-font-body-sm); padding:var(--tf-space-8) var(--tf-space-16);
  border-radius:var(--tf-space-8); cursor:pointer; font-family:inherit; min-height:36px;
  transition:background var(--tf-motion-fast) var(--tf-ease-spring), color var(--tf-motion-fast) linear; }
.seg-btn:hover{ color:var(--tf-color-text-primary); background:var(--tf-glass-fill-subtle); }
.seg-btn.active{ background:var(--tf-white); color:var(--tf-color-text-primary); font-weight:600;
  box-shadow:var(--tf-shadow-press); }
.tab.disabled{ opacity:.4; pointer-events:none; }

/* ---------- Welcome tour + guide page ---------- */
/* Wider than a form dialog: this one is read, not filled in, and at 560px the
   Chinese intro broke into three cramped lines. Doubled-up selector on purpose —
   plain `.modal-tour` ties with `.modal{width:560px}`, which is declared later
   in this file and would quietly win. */
.modal.modal-tour{ width:660px; }
/* Shared by the tour dialog and the guide page, so the base rule carries only
   rhythm. The gutter belongs to the dialog alone — the page already sits inside
   a .panel that has its own padding, and adding it here would double it up. */
.tr-list{ display:flex; flex-direction:column; gap:var(--tf-space-20); }
/* In the dialog the list is what scrolls, so it needs the side gutter the head
   has (without it the text sits flush against the glass edge while the title is
   indented — that mismatch is what read as "narrow") plus min-height:0, or a
   long step pushes the buttons off the bottom instead of scrolling. */
.modal-tour .tr-list{ padding:var(--tf-space-24) var(--tf-space-32);
  overflow-y:auto; min-height:0; }
.tr-item{ display:flex; flex-direction:column; gap:var(--tf-space-4); }
.tr-k{ font-weight:600; font-size:var(--tf-font-body); line-height:1.5;
  color:var(--tf-color-text-primary); }
.tr-v{ font-size:var(--tf-font-body-sm); color:var(--tf-color-text-secondary);
  line-height:1.7; }

.tr-foot{ display:flex; align-items:center; justify-content:space-between;
  gap:var(--tf-space-16); flex-wrap:wrap;
  padding:var(--tf-space-16) var(--tf-space-32) var(--tf-space-24);
  border-top:1px solid var(--tf-color-hairline); }
.tr-dots{ display:flex; gap:var(--tf-space-8); }
.tr-dot{ width:7px; height:7px; border-radius:50%;
  background:var(--tf-color-hairline); transition:background var(--tf-motion-fast) linear; }
.tr-dot.on{ background:var(--tf-violet-600,#6d5dfb); }
.tr-btns{ display:flex; gap:var(--tf-space-8); margin-left:auto; }

.modal-tour .modal-head{ padding:var(--tf-space-32) var(--tf-space-32) var(--tf-space-20); }
.modal-tour .modal-head h3{ margin-bottom:var(--tf-space-8); }
.modal-tour .modal-head .muted{ line-height:1.7; max-width:58ch; }
/* #tr-body wraps head/list/foot, so it has to be the flex column the modal's
   own max-height expects — otherwise the list can't scroll and a long step
   pushes the buttons off the bottom of the screen. */
.modal-tour > #tr-body{ display:flex; flex-direction:column; min-height:0; }

@media (max-width:700px){
  /* Scoped the same way as the rule above, or it would both leak onto the guide
     page and lose to `.modal-tour .tr-list` on specificity anyway. */
  .modal-tour .tr-list{ padding:var(--tf-space-20) var(--tf-space-24); }
  .tr-foot{ padding:var(--tf-space-16) var(--tf-space-24) var(--tf-space-20); }
  .modal-tour .modal-head{ padding:var(--tf-space-24) var(--tf-space-24) var(--tf-space-16); }
}
@media (max-width:520px){
  .tr-foot{ flex-direction:column; align-items:stretch; }
  .tr-btns{ margin-left:0; }
  .tr-btns .btn-primary, .tr-btns .btn-ghost{ flex:1; }
}

/* ---------- Date range picker (group Overview + Members) ---------- */
.dr{ display:flex; align-items:center; gap:var(--tf-space-16); flex-wrap:wrap;
  margin-bottom:var(--tf-space-20); }
/* The pair of native date fields here is gone; this is the shared picker, at
   the height of the preset buttons standing beside it. */
.dr .drp-trigger{ padding:var(--tf-space-8) var(--tf-space-12); min-height:36px;
  font-size:var(--tf-font-body-sm); }
@media (max-width:640px){
  .dr{ gap:var(--tf-space-12); }
  .dr .drp{ width:100%; }
  .dr .drp-trigger{ width:100%; }
}

/* ---------- Connection widgets ---------- */
.conn-card .row-between{ flex-wrap:wrap; gap:var(--tf-space-12); }
#tg-conn-line{ font-size:var(--tf-font-body); }
#tg-conn-line code{ background:var(--tf-cloud-200); padding:2px 7px;
  border-radius:6px; font-family:var(--tf-font-mono); font-size:.85em; }

/* ---------- Bars ---------- */
.bars{ display:flex; align-items:flex-end; gap:var(--tf-space-20); height:200px; padding:var(--tf-space-8) var(--tf-space-4) 0; }
.bar-col{ flex:1; display:flex; flex-direction:column; align-items:center; gap:var(--tf-space-8);
  height:100%; justify-content:flex-end; }
.bar{ width:100%; max-width:54px; background:linear-gradient(180deg,var(--tf-blue-500),var(--tf-violet-500));
  border-radius:var(--tf-space-8) var(--tf-space-8) 0 0; min-height:4px; }
.bar-val{ font-size:var(--tf-font-body-sm); color:var(--tf-color-text-primary); font-weight:600;
  font-variant-numeric:tabular-nums; }
.bar-lab{ font-size:var(--tf-font-label); color:var(--tf-color-text-secondary); text-align:center; white-space:nowrap; }

/* ---------- Click cards ---------- */
.clicks{ display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:var(--tf-space-16); }
.click-card{ border-radius:var(--tf-radius-card); padding:var(--tf-space-20); }
.click-card .spark{ margin-top:var(--tf-space-8); }

/* ---------- Tables ---------- */
.tbl-wrap{ border-radius:var(--tf-radius-card); overflow-x:auto; }
.tbl th,.tbl td{ white-space:nowrap; }
table.tbl{ width:100%; border-collapse:separate; border-spacing:0; font-size:var(--tf-font-body-sm); }
.tbl th{ text-align:left; text-transform:uppercase; letter-spacing:.05em; font-size:var(--tf-font-label);
  color:var(--tf-color-text-secondary); font-weight:600; padding:var(--tf-space-12) var(--tf-space-20);
  border-bottom:1px solid var(--tf-color-hairline); background:transparent; }
.tbl td{ padding:var(--tf-space-12) var(--tf-space-20); border-bottom:1px solid var(--tf-color-hairline);
  color:var(--tf-color-text-primary); vertical-align:middle; }
.tbl tr:last-child td{ border-bottom:none; }
.tbl tbody tr{ transition:background var(--tf-motion-fast) linear; }
.tbl tbody tr:hover td{ background:var(--tf-glass-fill-subtle); }
.tbl tbody tr:hover td:first-child{ border-radius:var(--tf-radius-control) 0 0 var(--tf-radius-control); }
.tbl tbody tr:hover td:last-child{ border-radius:0 var(--tf-radius-control) var(--tf-radius-control) 0; }
.mono{ font-family:var(--tf-font-mono); color:var(--tf-color-text-secondary);
  font-size:.92em; font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
.num{ font-variant-numeric:tabular-nums; }
/* data columns line up: figures in tables are always tabular */
.tbl td,.tbl th{ font-variant-numeric:tabular-nums; }

/* ---------- Badges (always paired with a label, never colour alone) ---------- */
.badge{ display:inline-block; padding:3px 10px; border-radius:var(--tf-radius-pill);
  font-size:var(--tf-font-label); font-weight:500; letter-spacing:.01em; }
.b-active,.b-sent{ color:#1f6b47; background:rgba(46,155,101,.14); }
.b-pending{ color:#8a540c; background:rgba(189,116,17,.14); }
.b-blocked,.b-failed,.b-error,.b-inactive{ color:#a3323f; background:rgba(217,75,89,.14); }
.b-cancelled,.b-optedout{ color:#2a5fa8; background:rgba(58,131,232,.14); }
.b-converted{ color:#4a3ec4; background:rgba(109,93,251,.14); }
.b-reply{ color:#2a5fa8; background:rgba(58,131,232,.14); }
.src-tag{ display:inline-block; padding:2px 10px; border-radius:var(--tf-radius-pill);
  font-size:var(--tf-font-label); font-weight:500; color:var(--tf-color-text-secondary);
  background:var(--tf-cloud-100); border:1px solid var(--tf-color-hairline); white-space:nowrap; }
.tags-box{ display:flex; flex-wrap:wrap; gap:var(--tf-space-8); }

/* ---------- Modal / sheet / popover ---------- */
#ct-table tbody tr{ cursor:pointer; }
.modal-overlay{ position:fixed; inset:0; background:rgba(21,22,26,.28);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  display:flex; align-items:center; justify-content:center; z-index:100; padding:var(--tf-space-24);
  animation:fadein var(--tf-motion-fast) var(--tf-ease-enter); }
@keyframes fadein{ from{ opacity:0 } to{ opacity:1 } }
.modal{ border-radius:var(--tf-radius-panel); width:560px; max-width:100%; max-height:84vh;
  display:flex; flex-direction:column; background:var(--tf-glass-fill-strong);
  /* the crisp lit edge is what makes a glass panel read as glass rather than
     as a plain grey box floating on a blur */
  border:1px solid var(--tf-glass-border);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur-heavy)) saturate(180%);
  backdrop-filter:blur(var(--tf-glass-blur-heavy)) saturate(180%);
  box-shadow:var(--tf-shadow-overlay), inset 0 1px 0 var(--tf-glass-highlight);
  animation:modalin var(--tf-motion-standard) var(--tf-ease-enter); }
@keyframes modalin{ from{ opacity:0; transform:translateY(10px) scale(.985) } to{ opacity:1; transform:none } }
.modal-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:var(--tf-space-12);
  padding:var(--tf-space-24) var(--tf-space-24) var(--tf-space-16); border-bottom:1px solid var(--tf-color-hairline); }
.modal-head h3{ margin:0 0 var(--tf-space-4); font-size:var(--tf-font-h3); font-weight:600; }
.modal-close{ background:transparent; border:none; color:var(--tf-color-text-secondary);
  font-size:var(--tf-font-body); cursor:pointer; line-height:1; padding:var(--tf-space-8);
  border-radius:var(--tf-space-8); min-width:36px; min-height:36px; }
.modal-close:hover{ background:var(--tf-cloud-100); color:var(--tf-color-text-primary); }
.modal-sub{ display:flex; align-items:center; gap:var(--tf-space-12); flex-wrap:wrap;
  padding:var(--tf-space-12) var(--tf-space-24); border-bottom:1px solid var(--tf-color-hairline); }
.tl{ padding:var(--tf-space-20) var(--tf-space-24) var(--tf-space-24); overflow-y:auto; }
/* Form rhythm inside a dialog: a label that follows a control needs air above
   it, otherwise fields run together in one grey block. */
.tl input + .field-label, .tl textarea + .field-label, .tl select + .field-label,
.tl .seg + .field-label, .tl .uploader + .field-label, .tl div + .field-label{
  margin-top:var(--tf-space-20); }
.tl .seg{ width:100%; }
.tl .seg .seg-btn{ flex:1; }
.tl-item{ border-left:2px solid var(--tf-color-hairline); padding:0 0 var(--tf-space-20) var(--tf-space-16);
  position:relative; }
.tl-item:last-child{ padding-bottom:2px; }
.tl-item::before{ content:""; position:absolute; left:-5px; top:6px; width:8px; height:8px;
  border-radius:50%; background:var(--tf-color-text-tertiary); }
.tl-item.sent{ border-left-color:rgba(46,155,101,.45); }
.tl-item.sent::before{ background:var(--tf-color-success); }
.tl-item.failed,.tl-item.blocked{ border-left-color:rgba(217,75,89,.45); }
.tl-item.failed::before,.tl-item.blocked::before{ background:var(--tf-color-danger); }
.tl-item.inbound{ border-left-color:rgba(58,131,232,.45); }
.tl-item.inbound::before{ background:var(--tf-color-info); }
/* A button they tapped. Drawn as the button they saw, not as a speech bubble —
   they pressed something, they did not say anything. */
.tl-item.tl-tap{ border-left-color:rgba(109,93,251,.45); }
.tl-item.tl-tap::before{ background:var(--tf-violet-600); }
.b-tap{ color:var(--tf-violet-600); background:rgba(109,93,251,.14); }
.tl-tap-btn{ display:inline-block; margin-top:var(--tf-space-8);
  padding:var(--tf-space-8) var(--tf-space-16); border-radius:var(--tf-radius-pill);
  background:var(--tf-white); border:1px solid rgba(109,93,251,.35);
  font-size:var(--tf-font-body-sm); font-weight:600; color:var(--tf-violet-600);
  max-width:90%; word-break:break-word; }
.tl-msg{ margin-top:var(--tf-space-8); font-size:var(--tf-font-body); line-height:var(--tf-leading-body);
  white-space:pre-wrap; word-break:break-word; }
.tl-msg.in{ background:var(--tf-white); border:1px solid var(--tf-color-hairline);
  border-radius:4px 14px 14px 14px; padding:var(--tf-space-8) var(--tf-space-12);
  display:inline-block; max-width:90%; }
.modal-foot{ display:flex; align-items:center; justify-content:space-between; gap:var(--tf-space-12);
  padding:var(--tf-space-16) var(--tf-space-24); border-top:1px solid var(--tf-color-hairline); }
.modal.modal-lg{ width:720px; }
/* Wide enough for a row of figures and a three-column table to sit side by side
   without wrapping — the delivery detail was a tall thin column otherwise. */
.modal.modal-xl{ width:900px; }
/* Same body treatment as .tl — the campaign builder was the odd one out. */
.cmp-modal-body{ padding:var(--tf-space-20) var(--tf-space-24) var(--tf-space-24); overflow-y:auto; }

/* Most dialogs keep their Save/Cancel row inside the scrolling body rather than
   in a real .modal-foot. Give that last row the same rule and breathing space
   so every dialog closes with an identical action bar. */
.tl > .tile-actions:last-child,
.cmp-modal-body > .tile-actions:last-child{
  margin-top:var(--tf-space-24); padding-top:var(--tf-space-20);
  border-top:1px solid var(--tf-color-hairline);
  /* Those rows are written [Primary][Cancel]; the real footers put the primary
     on the right. Reversing here lines every dialog up the same way — confirm
     on the right — without rewriting nine blocks of markup. */
  flex-direction:row-reverse; justify-content:flex-start; }

/* Dialog buttons are touch-sized and never squeezed by a narrow field row. */
.modal .tile-actions .btn-primary,.modal .tile-actions .btn-ghost,
.modal-foot .btn-primary,.modal-foot .btn-ghost{ min-height:42px; padding-left:var(--tf-space-20);
  padding-right:var(--tf-space-20); }

/* two-way chat composer inside the contact sheet */
.chat-compose{ display:flex; gap:var(--tf-space-8); align-items:flex-end;
  padding:var(--tf-space-16) var(--tf-space-24); border-top:1px solid var(--tf-color-hairline); }
.chat-compose textarea{ flex:1; min-height:44px; resize:vertical; }

/* ---------- Campaign sequence rows ---------- */
.cmp-seq{ display:flex; flex-direction:column; gap:var(--tf-space-8); }
.seq-row{ display:flex; align-items:center; gap:var(--tf-space-12); background:var(--tf-white);
  border:1px solid var(--tf-color-hairline); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-8) var(--tf-space-12); }
.seq-n{ width:24px; height:24px; flex:none; border-radius:50%; background:var(--tf-color-accent);
  color:var(--tf-white); font-weight:600; font-size:var(--tf-font-label); display:flex;
  align-items:center; justify-content:center; }
.seq-name{ flex:1; font-size:var(--tf-font-body-sm); font-weight:500; }
.seq-day{ display:flex; align-items:center; gap:var(--tf-space-8); font-size:var(--tf-font-body-sm);
  color:var(--tf-color-text-secondary); }
.seq-day input{ width:68px; padding:var(--tf-space-8); text-align:center; }
.seq-actions{ display:flex; gap:var(--tf-space-4); }
.seq-grip{ flex:none; display:flex; align-items:center; justify-content:center; width:20px; height:26px;
  color:var(--tf-color-text-tertiary); cursor:grab; border-radius:6px; touch-action:none; }
.seq-grip:hover{ color:var(--tf-color-text-primary); background:var(--tf-cloud-100); }
.seq-grip:active{ cursor:grabbing; }
.seq-grip .grip-ic{ pointer-events:none; }
.seq-row.lift{ box-sizing:border-box; box-shadow:var(--tf-shadow-float);
  border-color:var(--tf-color-accent); transform:scale(1.015); }
.seq-ph{ box-sizing:border-box; border:1.5px dashed var(--tf-color-accent);
  border-radius:var(--tf-radius-control); background:rgba(34,158,217,.06); }
.cmp-section-head{ font-size:var(--tf-font-label); font-weight:600; letter-spacing:.05em;
  text-transform:uppercase; color:var(--tf-color-text-secondary); margin:var(--tf-space-4) 0 var(--tf-space-12); }
.tile-draft{ border-style:dashed; }

/* ---------- Kebab menus (elevated glass popover) ---------- */
.kebab-wrap{ position:absolute; top:var(--tf-space-12); right:var(--tf-space-12); }
.kebab{ background:transparent; border:none; color:var(--tf-color-text-secondary);
  font-size:var(--tf-font-h3); line-height:1; cursor:pointer; padding:var(--tf-space-4) var(--tf-space-8);
  border-radius:var(--tf-space-8); min-width:36px; min-height:36px; }
.kebab:hover{ background:var(--tf-cloud-100); color:var(--tf-color-text-primary); }
.kebab-menu{ position:absolute; right:0; top:32px; background:var(--tf-glass-fill-strong);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur-heavy)); backdrop-filter:blur(var(--tf-glass-blur-heavy));
  border:1px solid var(--tf-glass-border); border-radius:var(--tf-radius-card);
  box-shadow:var(--tf-shadow-overlay); padding:var(--tf-space-4); display:flex; flex-direction:column;
  gap:2px; z-index:20; min-width:150px;
  animation:popin var(--tf-motion-fast) var(--tf-ease-enter); }
@keyframes popin{ from{ opacity:0; transform:translateY(-6px) scale(.98) } to{ opacity:1; transform:none } }
.kebab-menu button{ background:transparent; border:none; color:var(--tf-color-text-primary);
  text-align:left; font-size:var(--tf-font-body-sm); font-family:inherit;
  padding:var(--tf-space-8) var(--tf-space-12); border-radius:var(--tf-space-8); cursor:pointer;
  min-height:36px; }
.kebab-menu button:hover{ background:var(--tf-cloud-100); }
.kebab-menu .km-del,.kebab-menu .rk-del{ color:var(--tf-color-danger); }

/* ---------- Contact form grid ---------- */
.cf-grid{ display:grid; grid-template-columns:1fr 1fr; gap:var(--tf-space-20) var(--tf-space-20); }
.cf-field{ display:flex; flex-direction:column; }
.cf-field .field-label{ margin-bottom:var(--tf-space-8); }
.cf-field input{ width:100%; box-sizing:border-box; }
.row-actions{ text-align:right; }
.row-menu{ position:relative; display:inline-block; }
.row-menu .kebab-menu{ top:34px; }

/* ---------- Channel chooser + contact picker ---------- */
.ch-grid{ display:grid; grid-template-columns:1fr 1fr; gap:var(--tf-space-12); }
.ch-btn{ display:flex; flex-direction:column; align-items:center; gap:var(--tf-space-4);
  background:var(--tf-white); border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-card); padding:var(--tf-space-20) var(--tf-space-12); cursor:pointer;
  color:var(--tf-color-text-primary); font-family:inherit;
  transition:border-color var(--tf-motion-fast) linear, transform var(--tf-motion-fast) var(--tf-ease-spring); }
.ch-btn:hover{ border-color:var(--tf-color-accent); transform:scale(1.01); }
.ch-btn span{ font-size:var(--tf-font-body); font-weight:600; }
.ch-btn small{ font-size:var(--tf-font-body-sm); color:var(--tf-color-text-secondary); }
.pk-row{ display:flex; align-items:center; gap:var(--tf-space-12);
  padding:var(--tf-space-12) var(--tf-space-8); border-bottom:1px solid var(--tf-color-hairline);
  cursor:pointer; font-size:var(--tf-font-body-sm); border-radius:var(--tf-radius-control); }
.pk-row:hover{ background:var(--tf-glass-fill-subtle); }
.pk-row input{ width:auto; }
.pk-row:last-child{ border-bottom:none; }

/* ---------- Bulk bar ---------- */
.bulk-bar{ display:flex; align-items:center; gap:var(--tf-space-12); flex-wrap:wrap;
  background:var(--tf-glass-fill-strong); border:1px solid var(--tf-color-accent);
  border-radius:var(--tf-radius-card); padding:var(--tf-space-12) var(--tf-space-16);
  margin:var(--tf-space-16) 0 var(--tf-space-4); box-shadow:var(--tf-shadow-press);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur)); backdrop-filter:blur(var(--tf-glass-blur)); }
.bulk-n{ font-size:var(--tf-font-body-sm); }
.chk-col{ width:40px; text-align:center; }
.chk-col input{ width:auto; cursor:pointer; }
#q-table tbody tr{ cursor:pointer; }

/* ---------- Toolbar / filters ---------- */
.toolbar{ display:flex; align-items:center; gap:var(--tf-space-12); margin-bottom:var(--tf-space-16); }
.search{ flex:1; }
.filters{ display:flex; flex-wrap:wrap; gap:var(--tf-space-12); align-items:center;
  margin-bottom:var(--tf-space-8); }
.filters select,.filters input{ width:auto; min-width:150px; }
/* The filters are our own dropdowns now; they need the width the selects had,
   and their lists sized to the words rather than to the control. */
.filters .gsel{ min-width:150px; }
/* Standing in a row with the search box, so it is built the same way — the
   input's own padding is what gives that row its height. */
.filters .gsel-trigger{ padding:var(--tf-space-12) var(--tf-space-16); min-height:44px; }
.filters .gsel-pop{ min-width:200px; width:max-content; max-width:min(340px,80vw); }
.filters .search{ flex:1; min-width:230px; }
th[data-k]{ user-select:none; cursor:pointer; }
.row-between{ display:flex; align-items:center; justify-content:space-between; gap:var(--tf-space-12); }
.row{ display:flex; }

/* ---------- Tabs ---------- */
.tabs{ display:flex; gap:var(--tf-space-8); border-bottom:1px solid var(--tf-color-hairline);
  margin:var(--tf-space-8) 0 var(--tf-space-20); }
.tab{ padding:var(--tf-space-12) var(--tf-space-16); cursor:pointer; color:var(--tf-color-text-secondary);
  font-weight:500; font-size:var(--tf-font-body); border-bottom:2px solid transparent;
  transition:color var(--tf-motion-fast) linear, border-color var(--tf-motion-fast) linear; }
.tab:hover{ color:var(--tf-color-text-primary); }
.tab.active{ color:var(--tf-color-accent); border-bottom-color:var(--tf-color-accent); }

/* ---------- Uploader / previews ---------- */
.uploader{ margin-top:var(--tf-space-12); }
/* Thumbnails rather than one big preview: with up to ten of them, full-size
   images would push the send button off the screen. */
.up-grid{ display:flex; flex-wrap:wrap; gap:var(--tf-space-8);
  margin-bottom:var(--tf-space-12); }
.up-thumb{ position:relative; width:96px; height:96px; }
.up-thumb img{ width:100%; height:100%; object-fit:cover; display:block;
  border-radius:var(--tf-radius-control); border:1px solid var(--tf-color-hairline); }
/* The order they were attached is the order Telegram shows them in, so it is
   worth being able to see it. */
.up-num{ position:absolute; left:var(--tf-space-4); bottom:var(--tf-space-4);
  min-width:18px; height:18px; padding:0 5px; border-radius:9px;
  background:rgba(21,22,26,.66); color:var(--tf-white);
  font-size:var(--tf-font-label); line-height:18px; text-align:center; }
.up-remove{ position:absolute; top:var(--tf-space-4); right:var(--tf-space-4);
  width:24px; height:24px; display:flex; align-items:center; justify-content:center;
  border-radius:50%; background:rgba(21,22,26,.62); color:var(--tf-white); border:none;
  cursor:pointer; line-height:1; padding:0; }
.up-remove:hover{ background:var(--tf-color-danger); }
.up-remove svg{ width:13px; height:13px; }
.up-hint{ margin-top:var(--tf-space-8); }
.prev-banner{ width:100%; max-height:180px; object-fit:cover; border-radius:var(--tf-radius-control);
  margin-bottom:var(--tf-space-8); display:block; }
/* Telegram lays an album out as a tight mosaic, not a stack. Two side by side,
   three or more in a 3-wide grid — close enough to set expectations. */
/* Checkbox + explanation, sitting on one row. The label is what the whole
   block reacts to, so the text is clickable too. */
.kw-conv{ display:flex; gap:var(--tf-space-10); align-items:flex-start;
  margin-top:var(--tf-space-16); cursor:pointer; }
.kw-conv input{ margin-top:3px; flex:0 0 auto; }
.kw-conv-t{ display:block; }
.kw-conv .field-label{ margin:0 0 2px; display:block; }
.kw-conv .muted{ display:block; line-height:1.5; }

.prev-album{ display:grid; grid-template-columns:repeat(3, 1fr); gap:2px;
  border-radius:var(--tf-radius-control); overflow:hidden;
  margin-bottom:var(--tf-space-8); }
.prev-album.two{ grid-template-columns:repeat(2, 1fr); }
.prev-album img{ width:100%; height:76px; object-fit:cover; display:block; }
.prev-split{ font-size:11px; color:var(--tf-color-text-muted); text-align:center;
  margin:var(--tf-space-8) 0; position:relative; }
.prev-split::before, .prev-split::after{ content:""; position:absolute; top:50%;
  width:22%; height:1px; background:var(--tf-color-hairline); }
.prev-split::before{ left:0; } .prev-split::after{ right:0; }

.tpl-thumb{ width:100%; max-height:120px; object-fit:cover; border-radius:var(--tf-space-8);
  margin:var(--tf-space-4) 0 var(--tf-space-8); display:block; }
.pager{ display:flex; align-items:center; justify-content:center; gap:var(--tf-space-16);
  margin:var(--tf-space-20) 0 var(--tf-space-4); }
.camp-preview{ margin-top:var(--tf-space-12); border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-control); padding:var(--tf-space-12); background:var(--tf-cloud-100);
  max-height:340px; overflow:auto; }
.camp-prev-head{ font-weight:600; font-size:var(--tf-font-body-sm); margin-bottom:var(--tf-space-8); }
.camp-prev-step{ border:1px solid var(--tf-color-hairline); border-radius:var(--tf-space-12);
  padding:var(--tf-space-12); margin-bottom:var(--tf-space-8); background:var(--tf-white); }
.camp-prev-step:last-child{ margin-bottom:0; }
.cps-top{ display:flex; align-items:center; gap:var(--tf-space-8); margin-bottom:var(--tf-space-8); }
.cps-name{ font-weight:500; font-size:var(--tf-font-body-sm); flex:1; }
.cps-day{ flex:none; }
.cps-body{ font-size:var(--tf-font-body-sm); color:var(--tf-color-text-secondary);
  line-height:var(--tf-leading-body); white-space:pre-wrap; }
.camp-prev-step .tpl-thumb{ max-height:72px; width:auto; margin:2px 0 var(--tf-space-8); }

/* ---------- Personal Telegram: conversations | thread | customer ---------- */
.tu-grid{ display:grid; grid-template-columns:minmax(240px,300px) minmax(320px,1fr) minmax(260px,320px);
  gap:var(--tf-space-16); align-items:stretch; height:min(72vh, 780px); }
.tu-list,.tu-thread,.tu-customer{ margin-bottom:0; padding:0; display:flex; flex-direction:column;
  overflow:hidden; }
.tu-list{ padding:var(--tf-space-12); }
.tu-list .search{ flex:none; margin-bottom:var(--tf-space-8); }
.tu-dialogs{ flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:2px; }
.tu-dialog{ display:flex; align-items:center; gap:var(--tf-space-12); width:100%; text-align:left;
  padding:var(--tf-space-8) var(--tf-space-12); border:none; background:transparent; cursor:pointer;
  font:inherit; border-radius:var(--tf-radius-control);
  transition:background var(--tf-motion-fast) linear; }
.tu-dialog:hover{ background:var(--tf-glass-fill-subtle); }
.tu-dialog.active{ background:var(--tf-white); box-shadow:var(--tf-shadow-press); }
.tu-avatar{ width:36px; height:36px; flex:none; border-radius:50%; display:flex; align-items:center;
  justify-content:center; font-weight:600; color:var(--tf-white); font-size:var(--tf-font-body-sm);
  background:linear-gradient(135deg,var(--tf-blue-500),var(--tf-violet-500)); }
.tu-dialog-main{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1; }
.tu-dialog-top{ display:flex; align-items:baseline; gap:var(--tf-space-8); }
.tu-dialog-name{ font-size:var(--tf-font-body-sm); font-weight:600; color:var(--tf-color-text-primary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1; }
.tu-dialog-time{ font-size:var(--tf-font-label); color:var(--tf-color-text-tertiary); flex:none; }
.tu-dialog-prev{ font-size:var(--tf-font-label); color:var(--tf-color-text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.tu-thread-head{ flex:none; padding:var(--tf-space-16) var(--tf-space-20);
  border-bottom:1px solid var(--tf-color-hairline); }
.tu-thread-body{ flex:1; overflow-y:auto; padding:var(--tf-space-16) var(--tf-space-20);
  display:flex; flex-direction:column; gap:var(--tf-space-8); }
.tu-thread .chat-compose{ flex:none; padding:var(--tf-space-12) var(--tf-space-20); }
.tu-daysep{ text-align:center; margin:var(--tf-space-12) 0 var(--tf-space-4); }
.tu-daysep span{ font-size:var(--tf-font-label); color:var(--tf-color-text-tertiary);
  background:var(--tf-cloud-100); border-radius:var(--tf-radius-pill); padding:3px 12px; }
.tu-msg{ display:flex; flex-direction:column; align-items:flex-start; max-width:78%; }
.tu-msg.out{ align-self:flex-end; align-items:flex-end; }
.tu-bubble{ background:var(--tf-white); border:1px solid var(--tf-color-hairline);
  border-radius:14px 14px 14px 4px; padding:var(--tf-space-8) var(--tf-space-12);
  font-size:var(--tf-font-body-sm); line-height:var(--tf-leading-body);
  white-space:pre-wrap; word-break:break-word; }
.tu-msg.out .tu-bubble{ background:rgba(34,158,217,.12); border-color:rgba(34,158,217,.28);
  border-radius:14px 14px 4px 14px; }
.tu-time{ font-size:var(--tf-font-label); color:var(--tf-color-text-tertiary); margin-top:2px; }
.tu-customer{ padding:var(--tf-space-20); overflow-y:auto; }
.tu-cust-hist{ display:flex; flex-direction:column; }

@media (max-width:1280px){
  .tu-grid{ grid-template-columns:minmax(220px,280px) 1fr; height:auto; }
  .tu-customer{ grid-column:1 / -1; }
  .tu-thread{ min-height:460px; }
}
@media (max-width:820px){
  .tu-grid{ grid-template-columns:1fr; }
  .tu-list{ max-height:320px; }
}

/* ---------- Campaign builder: horizontal step timeline ---------- */
.cb-name{ font-size:var(--tf-font-h2); font-weight:600; letter-spacing:-.02em;
  border:none; background:transparent; padding:0; width:100%; }
.cb-name:focus{ outline:none; box-shadow:none; border-bottom:2px solid var(--tf-color-accent); }
.cb-timeline{ display:flex; gap:var(--tf-space-16); overflow-x:auto; padding-bottom:var(--tf-space-12);
  align-items:stretch; }
.cb-step{ flex:0 0 260px; display:flex; flex-direction:column; gap:var(--tf-space-8);
  background:var(--tf-glass-fill); border:1px solid var(--tf-glass-border);
  border-radius:var(--tf-radius-card); padding:var(--tf-space-16);
  box-shadow:var(--tf-shadow-float), inset 0 1px 0 var(--tf-glass-highlight);
  position:relative; }
/* the arrow between steps reads as "then, N days later" */
.cb-step:not(:last-child)::after{ content:"→"; position:absolute; right:calc(var(--tf-space-16) * -1 + 1px);
  top:50%; transform:translate(50%,-50%); color:var(--tf-color-text-tertiary);
  font-size:var(--tf-font-h3); pointer-events:none; }
.cb-step-head{ display:flex; align-items:center; gap:var(--tf-space-8); }
.cb-day{ width:72px; text-align:center; padding:var(--tf-space-8); }
.cb-step-name{ font-size:var(--tf-font-body); font-weight:600; }
.cb-step-prev{ flex:1; font-size:var(--tf-font-body-sm); line-height:var(--tf-leading-body);
  background:var(--tf-white); border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-control); padding:var(--tf-space-12); min-height:84px; }
.cb-step-prev .tpl-thumb{ max-height:56px; margin:0 0 var(--tf-space-8); }
.cb-step-actions{ display:flex; gap:var(--tf-space-4); flex-wrap:wrap; }
.cb-step-actions .btn-sm{ padding:var(--tf-space-4) var(--tf-space-8); min-height:30px; }

/* ---------- Settings: narrow reading width ---------- */
.settings-wrap{ max-width:var(--tf-reading-max); display:flex; flex-direction:column; }

/* ---------- Delivery Center: list + detail inspector ---------- */
.dc-rows{ display:flex; flex-direction:column; gap:var(--tf-space-8); }
.dc-rows > div{ display:flex; justify-content:space-between; gap:var(--tf-space-16);
  font-size:var(--tf-font-body-sm); padding:var(--tf-space-8) 0;
  border-bottom:1px solid var(--tf-color-hairline); }
.dc-rows > div:last-child{ border-bottom:none; }

/* Delivery detail. It used to be a sticky panel in the right margin, which cost
   the table about a third of its width and put the last columns behind a
   horizontal scrollbar — so it opens as a centred modal, like every other list
   in the app. This is the scrolling middle of that modal.

   The horizontal padding matches .modal-head, .modal-sub and .modal-foot — the
   body is the only part that sets its own, and with it at zero the headings sat
   hard against the border while the title above them was inset 24px. */
.dc-detail{ padding:var(--tf-space-20) var(--tf-space-24) var(--tf-space-24);
  overflow-y:auto; max-height:min(64vh, 660px); }
.dc-detail .tbl-wrap{ max-height:none; }

/* The figures read across the width rather than down it. Inside the modal these
   tiles sit on glass already, so they take a flat tint instead of a second
   blur — stacking backdrop-filter on backdrop-filter turns both to mud. */
.dc-stats{ margin-top:var(--tf-space-12); gap:var(--tf-space-12); }
.dc-stats .kpi{ padding:var(--tf-space-12) var(--tf-space-16);
  background:var(--tf-glass-fill-subtle);
  border:1px solid var(--tf-color-hairline);
  -webkit-backdrop-filter:none; backdrop-filter:none; box-shadow:none; }
.dc-stats .kpi-top{ margin-bottom:var(--tf-space-4); }
.dc-stats .kpi-value{ font-size:var(--tf-font-h2); }
.kpi-suffix{ font-size:var(--tf-font-body-sm); font-weight:500;
  color:var(--tf-color-text-secondary); margin-left:var(--tf-space-4); }

/* One tracked address and how many people opened it. */
.dc-link-row{ display:flex; align-items:center; justify-content:space-between;
  gap:var(--tf-space-16); margin-top:var(--tf-space-8);
  padding:var(--tf-space-8) var(--tf-space-12);
  border:1px solid var(--tf-color-hairline); border-radius:var(--tf-radius-card);
  font-size:var(--tf-font-body-sm); }
.dc-link-row .mono{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  color:var(--tf-color-text-secondary); }
.dc-link-row b{ white-space:nowrap; }

.tbl tbody tr.row-selected td{ background:rgba(34,158,217,.10); }
.tbl tbody tr.row-selected td:first-child{ box-shadow:inset 3px 0 0 var(--tf-color-accent); }

/* ---------- Home: task-first starting point ---------- */
.home-hero{ display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--tf-space-24); flex-wrap:wrap; }
.home-greet{ font-size:var(--tf-font-h1); font-weight:600; letter-spacing:-.02em;
  line-height:var(--tf-leading-heading); margin:0; }
.home-todo{ display:flex; flex-direction:column; }
.home-todo-row{ display:flex; align-items:center; gap:var(--tf-space-12);
  padding:var(--tf-space-12) 0; border-bottom:1px solid var(--tf-color-hairline); }
.home-todo-row:last-child{ border-bottom:none; }
.home-todo-txt{ flex:1; min-width:0; font-size:var(--tf-font-body); }
.home-dot{ width:8px; height:8px; flex:none; border-radius:50%; background:var(--tf-color-accent); }
.home-dot.warn{ background:var(--tf-color-warning); }
.home-cols{ display:grid; grid-template-columns:1fr 1fr; gap:var(--tf-space-24); align-items:start; }
.home-cols .panel{ margin-bottom:var(--tf-space-24); }
.home-list{ display:flex; flex-direction:column; }
.home-list-row{ display:flex; align-items:center; gap:var(--tf-space-12);
  padding:var(--tf-space-8) 0; border-bottom:1px solid var(--tf-color-hairline); }
.home-list-row:last-child{ border-bottom:none; }
/* A row that opens something has to look like it does, or nobody tries. */
.home-open{ cursor:pointer; border-radius:10px; transition:background .12s; }
.home-list-row.home-open{ padding-left:var(--tf-space-8); padding-right:var(--tf-space-8); }
.home-open:hover{ background:var(--tf-glass-fill); }
.kpi.home-open:hover{ box-shadow:var(--tf-shadow-float); }
.home-list-txt{ flex:1; min-width:0; font-size:var(--tf-font-body-sm);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@media (max-width:900px){ .home-cols{ grid-template-columns:1fr; } }

/* ---------- Create broadcast: three-pane creation workspace ---------- */
.bc-grid{ display:grid; grid-template-columns:minmax(320px,1.1fr) minmax(300px,1fr) minmax(300px,340px);
  gap:var(--tf-space-24); align-items:start; }
.bc-editor,.bc-preview,.bc-audience{ margin-bottom:0; }
.bc-body{ min-height:280px; font-size:var(--tf-font-body); }
.bc-meta{ margin-top:var(--tf-space-8); }
.bc-more{ margin-top:var(--tf-space-16); border-top:1px solid var(--tf-color-hairline);
  padding-top:var(--tf-space-12); }
.bc-more summary{ cursor:pointer; font-size:var(--tf-font-body-sm); font-weight:500;
  color:var(--tf-color-text-secondary); }
.bc-more summary:hover{ color:var(--tf-color-text-primary); }
.bc-editor-foot{ margin-top:var(--tf-space-16); border-top:1px solid var(--tf-color-hairline);
  padding-top:var(--tf-space-16); }

/* preview framed as a Telegram-like context (not a copy of the app) */
.tg-frame{ background:var(--tf-cloud-100); border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-card); padding:var(--tf-space-12); }
.tg-frame-head{ display:flex; align-items:center; gap:var(--tf-space-8);
  padding:0 var(--tf-space-4) var(--tf-space-12); }
.tg-avatar{ width:28px; height:28px; flex:none; border-radius:50%; display:flex;
  align-items:center; justify-content:center; font-size:.85rem;
  background:linear-gradient(135deg,var(--tf-blue-500),var(--tf-violet-500)); }
.tg-ident{ font-size:var(--tf-font-body-sm); font-weight:600; color:var(--tf-color-text-primary); }

/* audience pane */
.bc-modes{ display:flex; flex-wrap:wrap; width:100%; }
.bc-modes .seg-btn{ flex:1 1 auto; text-align:center; }
.bc-estimate{ display:flex; align-items:center; justify-content:space-between; gap:var(--tf-space-12);
  margin:var(--tf-space-20) 0 var(--tf-space-8); padding:var(--tf-space-16);
  border-radius:var(--tf-radius-card); background:rgba(34,158,217,.08);
  border:1px solid rgba(34,158,217,.20); }
.bc-est-label{ font-size:var(--tf-font-body-sm); color:var(--tf-color-text-secondary); }
.bc-est-n{ font-size:var(--tf-font-h2); font-weight:600; color:var(--tf-color-accent-strong);
  letter-spacing:-.02em; }
.bc-audience .btn-block{ margin-top:var(--tf-space-12); }
.bc-audience input[type=datetime-local]{ margin-top:var(--tf-space-8); }

/* send confirmation summary */
.bc-review{ display:flex; flex-direction:column; gap:var(--tf-space-8);
  padding:var(--tf-space-16); border-radius:var(--tf-radius-card);
  background:var(--tf-cloud-100); border:1px solid var(--tf-color-hairline); }
.bc-review > div{ display:flex; justify-content:space-between; gap:var(--tf-space-16);
  font-size:var(--tf-font-body-sm); }

@media (max-width:1400px){
  .bc-grid{ grid-template-columns:1fr 1fr; }
  .bc-audience{ grid-column:1 / -1; }
}
@media (max-width:900px){ .bc-grid{ grid-template-columns:1fr; } }

/* ---------- Compose: editor + live Telegram preview ---------- */
.grid-2{ display:grid; grid-template-columns:1fr 1fr; gap:var(--tf-space-24); }
.preview-msg{ background:var(--tf-white); border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-card); padding:var(--tf-space-16); line-height:var(--tf-leading-body);
  box-shadow:var(--tf-shadow-press); }
.pill-btn{ display:flex; align-items:center; justify-content:center; gap:var(--tf-space-8);
  background:rgba(34,158,217,.10); border:1px solid rgba(34,158,217,.28);
  color:var(--tf-color-accent-strong); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-12); margin-top:var(--tf-space-8); font-weight:500;
  font-size:var(--tf-font-body-sm); }

/* ---------- Tiles ---------- */
.stack{ display:flex; flex-direction:column; gap:var(--tf-space-12); }
.tile{ border-radius:var(--tf-radius-card); padding:var(--tf-space-20); position:relative; }
.tile h4{ margin:0 0 var(--tf-space-4); font-size:var(--tf-font-body); font-weight:600; }
.tile .snippet{ color:var(--tf-color-text-secondary); font-size:var(--tf-font-body-sm);
  line-height:var(--tf-leading-body); margin-bottom:var(--tf-space-12); }
.tile-actions{ display:flex; gap:var(--tf-space-8); flex-wrap:wrap; }
.checkbox{ display:flex; align-items:center; gap:var(--tf-space-12); margin:var(--tf-space-16) 0;
  font-size:var(--tf-font-body); cursor:pointer; }
.checkbox input{ width:auto; }

/* ---------- Toast (bottom-right safe area) ---------- */
.toast{ position:fixed; right:var(--tf-space-24); bottom:var(--tf-space-24);
  background:var(--tf-glass-fill-strong);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur-heavy)); backdrop-filter:blur(var(--tf-glass-blur-heavy));
  border:1px solid var(--tf-glass-border); border-left:3px solid var(--tf-color-accent);
  border-radius:var(--tf-radius-control); padding:var(--tf-space-16) var(--tf-space-20);
  box-shadow:var(--tf-shadow-overlay); font-size:var(--tf-font-body-sm); max-width:360px; z-index:200;
  animation:slidein var(--tf-motion-standard) var(--tf-ease-enter); }
@keyframes slidein{ from{ transform:translateY(12px); opacity:0 } to{ transform:none; opacity:1 } }

/* ---------- Bot identity (avatar pulled from Telegram) ---------- */
.bot-cell{ display:flex; align-items:center; gap:var(--tf-space-12); }
.bot-ava{ flex:none; border-radius:50%; object-fit:cover; display:block;
  background:var(--tf-glass-fill-subtle); border:1px solid var(--tf-color-hairline); }
.bot-ava-fb{ display:flex; align-items:center; justify-content:center; font-weight:600;
  color:var(--tf-color-text-secondary); letter-spacing:0; }
.bot-found{ display:flex; align-items:center; gap:var(--tf-space-12); margin-top:var(--tf-space-12);
  padding:var(--tf-space-12); border-radius:var(--tf-radius-control);
  background:var(--tf-glass-fill-subtle); border:1px solid var(--tf-color-hairline); }
.bot-found-name{ font-weight:600; }

/* ---------- Delivery Center: personal run tiles ---------- */
.dc-msg{ padding:var(--tf-space-12) 0; border-bottom:1px solid var(--tf-color-hairline); }
.dc-msg:last-child{ border-bottom:none; }


/* What went out with a send, shown once for the whole send. Thumbnails rather
   than the full picture: this sits above a list of recipients, and a full-size
   banner would push that list off the screen. Tapping one opens the original. */
.sent-pics-title{ margin-top:var(--tf-space-16); }
.sent-pics{ display:flex; flex-wrap:wrap; gap:var(--tf-space-8);
  margin-top:var(--tf-space-8); }
.sent-pic{ display:block; width:96px; height:96px; border-radius:var(--tf-space-8);
  overflow:hidden; border:1px solid var(--tf-color-hairline);
  background:var(--tf-cloud-100); flex:0 0 auto; }
.sent-pic img{ width:100%; height:100%; object-fit:cover; display:block; }
.sent-pic:hover{ border-color:var(--tf-color-accent-strong); }
/* The send is still on record; only the file is gone. */
.sent-pic-gone{ display:none; }
.sent-pic.gone{ display:flex; align-items:center; justify-content:center;
  padding:var(--tf-space-8); border-style:dashed; }
.sent-pic.gone img{ display:none; }
.sent-pic.gone .sent-pic-gone{ display:block; text-align:center;
  font-size:var(--tf-font-label); line-height:1.35; color:var(--tf-color-text-muted); }

.dc-job{ cursor:pointer; }
.dc-bar{ height:6px; border-radius:var(--tf-radius-pill); overflow:hidden;
  background:var(--tf-color-hairline); margin-top:var(--tf-space-8); }
.dc-bar span{ display:block; height:100%; background:var(--tf-color-success,#2e9b65);
  transition:width var(--tf-motion-standard) linear; }

/* ---------- Personal follow-up composer ---------- */
.cx-modes{ margin-bottom:var(--tf-space-20); max-width:420px; }
.px-grid{ display:grid; grid-template-columns:minmax(0,1fr) 340px; gap:var(--tf-space-20);
  align-items:start; }
.px-item{ border:1px solid var(--tf-color-hairline); border-radius:var(--tf-radius-control);
  padding:var(--tf-space-12); margin-top:var(--tf-space-8); }
.px-item textarea{ width:100%; box-sizing:border-box; margin-top:var(--tf-space-8); }
.px-log{ max-height:220px; overflow-y:auto; margin-top:var(--tf-space-8);
  border-top:1px solid var(--tf-color-hairline); }
.px-log-row{ display:flex; justify-content:space-between; gap:var(--tf-space-8);
  padding:var(--tf-space-4) 0; font-size:var(--tf-font-body-sm);
  border-bottom:1px solid var(--tf-color-hairline); }
@media (max-width:1024px){ .px-grid{ grid-template-columns:1fr; } }

/* ---------- Personal Telegram: account strip ---------- */
.tu-accts{ display:flex; gap:var(--tf-space-8); flex-wrap:wrap;
  margin-bottom:var(--tf-space-16); }
/* Which project the selected account belongs to. Sits right under the strip so
   it clearly applies to the account above it, not to the page. */
.tu-proj{ display:flex; align-items:center; gap:var(--tf-space-12); flex-wrap:wrap;
  margin:calc(var(--tf-space-8) * -1) 0 var(--tf-space-16); }
/* Two settings on one line, not a form to fill in — sized like the buttons
   above them rather than like the full-height fields on a page of their own. */
.tu-proj{ gap:var(--tf-space-8); }
.tu-proj .tu-cat-note{
  width:auto; min-width:200px; min-height:32px;
  padding:var(--tf-space-8) var(--tf-space-12);
  font-size:var(--tf-font-body-sm); }
/* The two dropdowns are .gsel.sm, which carries its own size; they only need a
   width to sit at, so a short choice does not leave a stub of a button. */
.tu-proj .gsel{ min-width:170px; }
/* The label sits against its own control, with room before the next pair. */
.tu-proj .field-label{ margin-left:var(--tf-space-8); }
.tu-proj .field-label:first-child{ margin-left:0; }
.tu-acct{ display:flex; align-items:center; gap:var(--tf-space-8); cursor:pointer;
  padding:var(--tf-space-8) var(--tf-space-16); border-radius:var(--tf-radius-pill);
  border:1px solid var(--tf-color-hairline); background:transparent; font-family:inherit;
  font-size:var(--tf-font-body-sm); color:var(--tf-color-text-primary); }
.tu-acct:hover{ background:var(--tf-glass-fill-subtle); }
.tu-acct.active{ background:var(--tf-white,#fff); border-color:transparent;
  box-shadow:var(--tf-shadow-overlay); font-weight:600; }
/* Four states, and the colour is the only thing most people will read, so each
   one has to mean one thing:
     on      working
     limited Telegram is restricting it — it can connect, it must not send
     dead    login revoked, or the account banned: both need you to act
     off     holding a session nobody has tried this run
   Colour alone is not the whole signal: every chip carries a title saying which
   state it is in and why, because a red and a yellow dot are the same dot to
   somebody who cannot distinguish them. */
.tu-acct-dot{ width:8px; height:8px; border-radius:50%; flex:none; }
.tu-acct-dot.on{ background:#12b5a5; }                     /* working */
.tu-acct-dot.off{ background:var(--tf-color-hairline); }   /* not checked yet */
.tu-acct-dot.limited{ background:#e0a11a; }                /* Telegram is limiting it */
/* Telegram revoked this login — it needs signing in again, and no amount of
   waiting will fix it. Worth looking different from "not checked yet". */
.tu-acct-dot.dead{ background:var(--tf-color-danger); }
/* Banned is past saving, so it is the one dot that does not pretend to be a
   healthy shape: hollow, to read as "gone" rather than "attention needed". */
.tu-acct-dot.banned{ background:transparent; box-shadow:inset 0 0 0 2px var(--tf-color-danger); }
.tu-acct-name{ white-space:nowrap; }
.tu-acct-add{ border-style:dashed; color:var(--tf-color-text-secondary); }

/* Narrowing the two Delivery Center lists by campaign and date. */
.dc-filters{ align-items:center; margin-bottom:var(--tf-space-12); }
.dc-filters select{ width:auto; min-width:170px; }
/* The campaign filter is our own dropdown, not a <select>; it needs the width
   the select had, and its list must not stretch to the filter row's width. */
.dc-f-camp{ min-width:190px; }
.dc-f-camp .gsel-pop{ min-width:220px; right:auto; }
.dc-filters label{ display:flex; align-items:center; gap:var(--tf-space-8); }

/* ---------- Floating glass surface ----------
   The material the panels are already made of, at the weight a thing floating
   over the page needs: more fill and more blur, because what shows through is
   the content it is covering rather than the page background, and a picker you
   can read the list behind is a picker you misread. */
.glass-pop{
  /* Denser than the panels it floats over. At the panel's own weight the copy
     underneath read straight through the list — a heading behind an option is
     not a texture, it is two sentences on top of each other. Blur alone does
     not fix that: it softens edges, and text is mostly edges. */
  background:rgba(255,255,255,.93);
  -webkit-backdrop-filter:blur(var(--tf-glass-blur-heavy)) saturate(180%);
  backdrop-filter:blur(var(--tf-glass-blur-heavy)) saturate(180%);
  border:1px solid var(--tf-glass-border);
  border-radius:var(--tf-radius-card);
  box-shadow:var(--tf-shadow-overlay), inset 0 1px 0 var(--tf-glass-highlight);
}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  /* No blur to help at all, so it goes solid. */
  .glass-pop{ background:#fff; }
}

/* ---------- Account picker ----------
   A native <select> is drawn by the browser: its list cannot be styled, and near
   the foot of the page it opens upwards over the field it belongs to. This one
   is ours, so it opens downwards always and scrolls inside itself when there is
   not enough room below. */
.gsel{ position:relative; }
.gsel-trigger{ display:flex; align-items:center; gap:var(--tf-space-8); width:100%;
  padding:10px var(--tf-space-16); border-radius:var(--tf-radius-control);
  border:1px solid var(--tf-color-hairline); background:var(--tf-glass-fill-strong);
  color:var(--tf-color-text-primary); font:inherit; text-align:left; cursor:pointer;
  transition:border-color .15s, box-shadow .15s; }
.gsel-trigger:hover{ border-color:var(--tf-color-text-tertiary); }
.gsel-trigger .gsel-value{ flex:1; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; font-weight:600; }
.gsel-trigger .gsel-caret{ flex:none; color:var(--tf-color-text-tertiary);
  transition:transform .18s; }
.gsel.open .gsel-trigger{ border-color:var(--tf-color-accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--tf-color-accent) 18%, transparent); }
.gsel.open .gsel-caret{ transform:rotate(180deg); }

/* Always below the field — never over it. */
/* Sitting beside a btn-sm, at the same weight — a filter is not more important
   than the button next to it, and looked it at full size. */
.gsel.sm .gsel-trigger{ padding:var(--tf-space-8) var(--tf-space-12); min-height:32px;
  font-size:var(--tf-font-body-sm); }
.gsel.sm .gsel-value{ font-weight:500; }
/* Its list is as wide as its contents need, not as narrow as the button. */
.gsel.sm .gsel-pop{ min-width:200px; width:max-content; max-width:min(320px, 80vw); }
/* Hung from the right edge, for a control that sits at the right of its row —
   otherwise its list opens off the side of the page. */
.gsel.right .gsel-pop{ left:auto; right:0; }

.gsel-pop{ position:absolute; z-index:70; top:calc(100% + var(--tf-space-8));
  left:0; right:0; padding:var(--tf-space-8); overflow-y:auto;
  animation:gsel-in .14s ease-out; }
@keyframes gsel-in{ from{ opacity:0; transform:translateY(-4px); } }
@media (prefers-reduced-motion:reduce){ .gsel-pop{ animation:none; } }

.gsel-group{ padding:var(--tf-space-8) var(--tf-space-12) var(--tf-space-4);
  font-size:var(--tf-font-label); font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--tf-color-text-tertiary);
  /* Stays put while the names scroll under it, so it needs to be opaque —
     a see-through heading with names sliding through it is unreadable. */
  position:sticky; top:calc(var(--tf-space-8) * -1);
  background:rgba(255,255,255,.97); }
.gsel-opt{ display:flex; align-items:center; gap:var(--tf-space-8); width:100%;
  padding:9px var(--tf-space-12); border:0; border-radius:10px; background:transparent;
  color:var(--tf-color-text-primary); font:inherit; text-align:left; cursor:pointer; }
.gsel-opt:hover, .gsel-opt.cursor{ background:var(--tf-glass-fill); }
.gsel-opt.on{ background:color-mix(in srgb, var(--tf-color-accent) 14%, transparent);
  font-weight:600; }
.gsel-opt .gsel-note{ margin-left:auto; padding-left:var(--tf-space-8);
  color:var(--tf-color-text-tertiary); font-size:var(--tf-font-body-sm);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gsel-opt .gsel-tick{ flex:none; color:var(--tf-color-accent); opacity:0; }
.gsel-opt.on .gsel-tick{ opacity:1; }

/* ---------- Campaign name field ----------
   Type a new name, or take one from the list. The list used to be a <datalist>,
   which the browser drew in its own black box, in its own typeface, and offered
   no way to be rid of a name typed by mistake. */
.cmb{ position:relative; }
.cmb > input{ width:100%; padding-right:36px; }
.cmb-toggle{ position:absolute; right:6px; top:50%; transform:translateY(-50%);
  width:28px; height:28px; display:flex; align-items:center; justify-content:center;
  border:0; border-radius:8px; background:transparent;
  color:var(--tf-color-text-tertiary); cursor:pointer; }
.cmb-toggle:hover{ background:var(--tf-glass-fill); color:var(--tf-color-text-primary); }
.cmb.open .cmb-toggle svg{ transform:rotate(180deg); }
.cmb-toggle svg{ transition:transform .18s; }

/* Below the field, like every other list here. */
.cmb-pop{ position:absolute; z-index:70; top:calc(100% + var(--tf-space-8));
  left:0; right:0; padding:var(--tf-space-8); overflow-y:auto; }
.cmb-row{ display:flex; align-items:center; gap:var(--tf-space-4); }
.cmb-pick{ flex:1; display:flex; align-items:center; gap:var(--tf-space-8); min-width:0;
  padding:8px var(--tf-space-12); border:0; border-radius:10px; background:transparent;
  color:var(--tf-color-text-primary); font:inherit; text-align:left; cursor:pointer; }
.cmb-pick > span:first-child{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cmb-pick:hover{ background:var(--tf-glass-fill); }
/* How many sends carry the name — the number that makes removing it a decision
   rather than a guess. */
.cmb-n{ margin-left:auto; padding:1px 8px; border-radius:var(--tf-radius-pill);
  background:var(--tf-color-canvas); color:var(--tf-color-text-tertiary);
  font-size:var(--tf-font-label); font-variant-numeric:tabular-nums; }
.cmb-del{ flex:none; width:28px; height:28px; border:0; border-radius:8px;
  background:transparent; color:var(--tf-color-text-tertiary); font-size:18px;
  line-height:1; cursor:pointer; }
.cmb-del:hover{ background:rgba(217,75,89,.12); color:var(--tf-color-danger); }
.cmb-empty{ padding:var(--tf-space-12); color:var(--tf-color-text-tertiary);
  font-size:var(--tf-font-body-sm); }

/* ---------- Date range picker ----------
   Replaces a pair of native date inputs. Those made you type or spin two dates
   to say "last week", and showed neither what was chosen nor what was possible.
   One button naming the range, one panel to change it. */
.drp{ position:relative; }
.drp-trigger{ display:inline-flex; align-items:center; gap:var(--tf-space-8);
  padding:9px var(--tf-space-16); border-radius:var(--tf-radius-control);
  border:1px solid var(--tf-color-hairline); background:var(--tf-glass-fill-strong);
  color:var(--tf-color-text-primary); font:inherit; font-weight:600; cursor:pointer;
  transition:border-color .15s, box-shadow .15s, background .15s; }
.drp-trigger:hover{ background:var(--tf-white); }
.drp-trigger.open{ border-color:var(--tf-color-accent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--tf-color-accent) 18%, transparent); }
.drp-trigger svg{ color:var(--tf-color-text-tertiary); flex:none; }
.drp-trigger.open .drp-caret{ transform:rotate(180deg); }
.drp-caret{ transition:transform .15s; }

.drp-pop{ position:absolute; z-index:60; top:calc(100% + var(--tf-space-8)); left:0; }
/* Near the right edge there is no room to open leftwards; flip it. */
.dc-filters .drp:last-of-type .drp-pop{ left:auto; right:0; }
/* Same floating material as the account picker — see .glass-pop, which it
   carries as a second class. */
.drp-panel{ padding:var(--tf-space-16); min-width:560px; }

.drp-presets{ display:flex; flex-wrap:wrap; gap:var(--tf-space-8);
  padding-bottom:var(--tf-space-12); margin-bottom:var(--tf-space-12);
  border-bottom:1px solid var(--tf-color-hairline); }
.drp-preset{ padding:5px 12px; border-radius:var(--tf-radius-pill); border:1px solid transparent;
  background:var(--tf-color-canvas); color:var(--tf-color-text-secondary);
  font:inherit; font-size:var(--tf-font-body-sm); font-weight:500; cursor:pointer; }
.drp-preset:hover{ color:var(--tf-color-text-primary); }
.drp-preset.on{ background:color-mix(in srgb, var(--tf-color-accent) 12%, transparent);
  border-color:color-mix(in srgb, var(--tf-color-accent) 40%, transparent);
  color:var(--tf-color-accent-strong); font-weight:600; }

.drp-months{ display:flex; gap:var(--tf-space-24); }
.drp-month{ flex:1; }
.drp-mhead{ display:flex; align-items:center; justify-content:space-between;
  margin-bottom:var(--tf-space-8); }
.drp-mhead b{ font-size:var(--tf-font-body); }
.drp-nav{ width:28px; height:28px; border-radius:var(--tf-radius-control); border:0;
  background:var(--tf-color-canvas); color:var(--tf-color-text-secondary);
  font-size:18px; line-height:1; cursor:pointer; }
.drp-nav:hover{ background:var(--tf-color-hairline); color:var(--tf-color-text-primary); }
.drp-nav-gap{ width:28px; }

.drp-grid{ display:grid; grid-template-columns:repeat(7,1fr); gap:2px; }
.drp-wd{ text-align:center; font-size:var(--tf-font-label); font-weight:600;
  color:var(--tf-color-text-tertiary); padding-bottom:var(--tf-space-4);
  text-transform:uppercase; }
.drp-day{ aspect-ratio:1; border:0; background:transparent; border-radius:9px;
  color:var(--tf-color-text-primary); font:inherit; font-variant-numeric:tabular-nums;
  cursor:pointer; position:relative; }
.drp-day:hover{ background:var(--tf-color-canvas); }
.drp-day.out{ color:var(--tf-color-text-tertiary); opacity:.5; }
/* The band between the two ends. Square-cornered so consecutive days read as
   one continuous run rather than a row of separate pills. */
.drp-day.in{ background:color-mix(in srgb, var(--tf-color-accent) 14%, transparent);
  border-radius:0; }
/* The seven weekday headings are the first seven children, so a day that
   starts a row lands on 7n+1 and one that ends a row on 7n. */
.drp-day.in:nth-child(7n+1){ border-radius:9px 0 0 9px; }
.drp-day.in:nth-child(7n){ border-radius:0 9px 9px 0; }
.drp-day.edge{ background:var(--tf-color-accent); color:var(--tf-color-text-inverse);
  border-radius:9px; font-weight:600; }
.drp-day.now:not(.edge)::after{ content:""; position:absolute; left:50%; bottom:5px;
  width:4px; height:4px; margin-left:-2px; border-radius:50%;
  background:var(--tf-color-accent); }

.drp-foot{ display:flex; align-items:center; justify-content:space-between;
  gap:var(--tf-space-16); margin-top:var(--tf-space-16);
  padding-top:var(--tf-space-12); border-top:1px solid var(--tf-color-hairline); }
.drp-summary{ font-variant-numeric:tabular-nums; }
.drp-actions{ display:flex; gap:var(--tf-space-8); }

@media (max-width:720px){
  .drp-pop{ left:0; right:auto; }
  .drp-panel{ min-width:0; width:min(92vw,340px); }
  .drp-months{ flex-direction:column; gap:var(--tf-space-16); }
}

/* Filter the strip by what each account is kept for. */
.tu-filter{ margin-bottom:var(--tf-space-12); }
.tu-filter .seg-n{ margin-left:var(--tf-space-4); font-variant-numeric:tabular-nums;
  color:var(--tf-color-text-tertiary); font-weight:500; }
.tu-filter .seg-btn.active .seg-n{ color:inherit; }
.tu-none{ align-self:center; }

/* The category on an account button, and the picker on the connected card.
   Three quiet tints rather than three loud ones: this labels an account, it
   does not warn about it. */
.tu-cat-chip{ font-size:var(--tf-font-label); font-weight:600; line-height:1;
  padding:3px var(--tf-space-8); border-radius:var(--tf-radius-pill);
  max-width:14ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  background:rgba(109,93,251,.12); color:var(--tf-violet-600); }
.tu-cat-chip.cat-blasting{ background:rgba(217,119,6,.14); color:#a15c06; }
.tu-cat-chip.cat-other{ background:rgba(100,116,139,.16); color:#475569; }

/* Sits on the same row as the project selector — both say what this account
   is, so they belong together rather than stacked as two separate settings. */
#tu-cat-sel{ width:auto; min-width:150px; }
.tu-cat-note{ width:auto; min-width:200px; flex:0 1 280px; }

/* ---------- Rich message editor ---------- */
.rt-wrap{ border:1px solid var(--tf-color-hairline); border-radius:var(--tf-radius-control);
  overflow:hidden; background:var(--tf-color-surface, transparent); }
.rt-small .rt-body{ min-height:110px; }
.rt-body{ min-height:200px; max-height:420px; overflow-y:auto; padding:var(--tf-space-16);
  font-size:var(--tf-font-body); line-height:var(--tf-leading-body); outline:none;
  white-space:pre-wrap; word-break:break-word; color:var(--tf-color-text-primary); }
.rt-body:empty::before{ content:attr(data-ph); color:var(--tf-color-text-secondary); }
.rt-body code{ font-family:var(--tf-font-mono); font-size:.92em;
  background:var(--tf-glass-fill-subtle); padding:1px 4px; border-radius:4px; }
.rt-body a{ color:var(--tf-color-accent, #2f6fd0); text-decoration:underline; }
.rt-bar{ display:flex; align-items:center; gap:2px; flex-wrap:wrap;
  padding:var(--tf-space-8); border-top:1px solid var(--tf-color-hairline);
  background:var(--tf-glass-fill-subtle); }
/* ---------- Inline SVG icons ---------- */
/* Icons replaced emoji on toolbars and buttons. Emoji are text: they inherit a
   font, carry their own colour, and land on a baseline — so they sat slightly
   low and drew in full colour against a flat panel. These are artwork that
   inherits currentColor and centres properly. */
.ic{ display:block; flex:none; }
/* The buttons holding them were laid out for a text glyph. */
.rt-btn, .modal-close{ display:inline-flex; align-items:center; justify-content:center; }
.ms-row-ic{ display:flex; align-items:center; color:var(--tf-color-text-secondary); }
.rt-btn{ min-width:32px; height:30px; padding:0 var(--tf-space-8); border:none; cursor:pointer;
  background:transparent; border-radius:var(--tf-radius-control);
  color:var(--tf-color-text-primary); font-size:var(--tf-font-body-sm); line-height:1; }
.rt-btn:hover{ background:var(--tf-glass-fill-strong, rgba(0,0,0,.06)); }
.rt-sep{ width:1px; height:18px; margin:0 var(--tf-space-4);
  background:var(--tf-color-hairline); }
/* Spoiler: shown blurred in the editor and preview, like Telegram does. */
.tg-spoiler{ background:var(--tf-glass-fill-strong, rgba(0,0,0,.12)); border-radius:4px;
  padding:0 2px; }

/* ---------- Interaction buttons ---------- */
.btn-rows{ display:flex; flex-direction:column; gap:var(--tf-space-8); }
.btn-row{ display:flex; gap:var(--tf-space-8); align-items:center; }
.btn-row input{ flex:1; min-width:0; }
.btn-row .br-del{ flex:none; }
.pill-btn.reply{ border-style:dashed; }

/* ---------- Toggle switch ---------- */
.sw-row{ display:flex; align-items:center; gap:var(--tf-space-12); cursor:pointer;
  padding:var(--tf-space-8) 0; font-size:var(--tf-font-body-sm); }
.sw{ position:relative; flex:none; width:38px; height:22px; }
.sw input{ position:absolute; opacity:0; width:100%; height:100%; margin:0; cursor:pointer; }
.sw-track{ position:absolute; inset:0; border-radius:var(--tf-radius-pill);
  background:var(--tf-color-hairline); transition:background var(--tf-motion-fast) linear;
  pointer-events:none; }
.sw-track::after{ content:""; position:absolute; top:3px; left:3px; width:16px; height:16px;
  border-radius:50%; background:#fff; box-shadow:0 1px 3px rgba(0,0,0,.25);
  transition:transform var(--tf-motion-fast) var(--tf-ease-enter); }
.sw input:checked + .sw-track{ background:var(--tf-color-success, #2e9b65); }
.sw input:checked + .sw-track::after{ transform:translateX(16px); }

/* ---------- Group picker (broadcast → Groups) ---------- */
.grp-list{ max-height:260px; overflow-y:auto; border:1px solid var(--tf-color-hairline);
  border-radius:var(--tf-radius-control); padding:var(--tf-space-4); }
.grp-item{ display:flex; align-items:center; gap:var(--tf-space-8); margin:0;
  padding:var(--tf-space-8) var(--tf-space-8); border-radius:var(--tf-radius-control);
  cursor:pointer; font-size:var(--tf-font-body-sm); }
.grp-item:hover{ background:var(--tf-glass-fill-subtle); }
.grp-name{ flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.grp-type{ flex:none; }

/* ---------- Empty / loading states ---------- */
.empty{ color:var(--tf-color-text-secondary); padding:var(--tf-space-32) var(--tf-space-8);
  text-align:center; font-size:var(--tf-font-body); }

/* ---------- Responsive ---------- */
@media (max-width:1024px){
  .view{ padding:var(--tf-space-16) var(--tf-space-24) var(--tf-space-48); }
  .topbar{ padding:var(--tf-space-20) var(--tf-space-24) 0; }
  .grid-2{ grid-template-columns:1fr; }
}
@media (max-width:820px){
  .app{ grid-template-columns:1fr; }
  .sidebar{ position:static; height:auto; width:auto; margin:var(--tf-space-12);
    flex-direction:row; flex-wrap:wrap; align-items:center; gap:var(--tf-space-8);
    border-radius:var(--tf-radius-panel); }
  .brand{ padding:var(--tf-space-4) var(--tf-space-8); }
  .nav{ flex-direction:row; flex-wrap:wrap; flex:1 1 100%; overflow:visible; }
  .nav a{ min-height:40px; }
  .nav-group{ width:100%; padding:var(--tf-space-8) var(--tf-space-8) 0; }
  .sidebar-foot{ flex:1 1 100%; flex-direction:row; flex-wrap:wrap; border-top:none; padding-top:0; }
  .sidebar-foot .btn-block{ width:auto; flex:1; }
  #whoami{ flex:1 1 100%; border-bottom:none; }
  .cf-grid,.ch-grid{ grid-template-columns:1fr; }
  .view{ padding:var(--tf-space-12) var(--tf-space-16) var(--tf-space-48); }
  .topbar{ padding:var(--tf-space-16) var(--tf-space-16) 0; }
  .modal-overlay{ padding:var(--tf-space-12); }
  .btn-sm{ min-height:36px; }
}

/* ---------- Reduced motion / transparency ---------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important; }
  .btn-primary:hover,.btn-ghost:hover,.ch-btn:hover,.nav a:active,.seq-row.lift{ transform:none !important; }
}
@media (prefers-reduced-transparency:reduce){
  .panel,.card,.kpi,.tbl-wrap,.click-card,.tile,.queue-details,.modal,.login-card,.sidebar,
  .kebab-menu,.toast,.bulk-bar,.seg{
    background:var(--tf-white) !important;
    -webkit-backdrop-filter:none !important; backdrop-filter:none !important; }
  body::before,body::after{ opacity:.5; }
}
