/* ============================================================
   Админка бота-аутрича – стили
   Vanilla CSS, без сборки и внешних зависимостей.

   Визуальный язык – общий с LMS (ваш-домен.ru, `app/web/static/lms.css`):
   кремовый бумажный холст, три ступени поверхностей (canvas → ivory → white),
   один электрический кобальт #0068f9 на все интерактивные элементы,
   hairline #efefef вместо серых рамок, почти невидимые трёхслойные тени,
   капсулы у кнопок и пунктов меню, карточки 16px, Inter с ss01.
   Имена классов и разметка не менялись: это слой оформления, не вёрстки.
   ============================================================ */

:root {
  /* Палитра – токены LMS под именами, которые уже использует эта админка */
  --canvas:       #faf9f7;   /* кремовый холст */
  --ivory:        #fbfaf7;   /* вторая ступень поверхности */
  --lavender:     #f4f0ff;
  --sky:          #d5ecff;
  --powder:       #d6e4f1;

  --bg:           var(--canvas);
  --surface:      #ffffff;
  --surface-2:    var(--ivory);
  /* Панель навигации белая, как сайдбар админки LMS: она отделена от холста
     hairline-полосой, а не цветом. */
  --surface-nav:  #ffffff;
  --border:       #efefef;
  --border-2:     #efefef;
  --faint:        #cccccc;

  --text:         #121722;   /* ink */
  --text-2:       #666b75;   /* slate – подписи и мелкий текст */
  --text-3:       #8a8a8a;   /* steel – служебные цифры, метки времени */
  --text-nav:     #121722;
  --text-nav-on:  #024bb1;

  --accent:       #0068f9;   /* cobalt */
  --accent-dark:  #024bb1;   /* cobalt-deep */
  --accent-soft:  #e8f1ff;
  --accent-line:  #cddffd;   /* рамка активного состояния на accent-soft */
  --danger:       #b42318;   /* rust */
  --danger-soft:  #fff4f3;
  --danger-line:  #f5cfcb;
  --ok:           #046645;   /* forest */
  /* Панель навигации теперь светлая, и отдельный осветлённый зелёный не нужен:
     на белом читается тот же forest, что и во всём остальном интерфейсе. */
  --ok-nav:       var(--ok);
  --violet:       #6736eb;   /* декоративная метка, как в LMS */

  /* Геометрия: капсулы у кнопок и пунктов меню, 16px у карточек */
  --nav-w:        220px;
  --list-w:       308px;
  --r-sm:         8px;
  --r-md:         12px;
  --r-lg:         16px;
  --r-button:     48px;
  --r-pill:       100px;

  /* Тени LMS: трёхслойная микротень и мягкая 20px вместо «поднятых» блоков */
  --shadow-sm:    rgba(0,0,0,.07) 0 1px 1px 0, rgba(0,0,0,.04) 0 -1px 1px 0 inset, rgba(0,0,0,.14) 0 0 0 .5px inset;
  --shadow-md:    rgba(0,0,0,.07) 0 1px 1px 0, rgba(0,0,0,.14) 0 0 0 .5px inset, rgba(0,0,0,.04) 0 20px 20px -8px;
  --shadow-lg:    rgba(0,0,0,.06) 0 24px 40px -10px, rgba(0,0,0,.14) 0 0 0 .5px inset;

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
          "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
}

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.52;
  color: var(--text);
  background: var(--bg);
  /* ss01 и cv11 – те же начертания Inter, что и в LMS: одинаковая «a» и цифры */
  font-feature-settings: "ss01", "kern", "cv11";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

[hidden] { display: none !important; }

button, input, textarea, select { font: inherit; color: inherit; }
/* Галки и радио красятся системой: без этого они остаются синими «как в ОС»
   и не попадают в кобальт остального интерфейса. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: #d8d8d6;
  border-radius: 8px;
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--faint); background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

/* ============================================================
   Базовые элементы управления
   ============================================================ */

/* Капсула 48px, как в LMS: одна форма у всех кнопок интерфейса, различается
   только заливка – кобальт у главного действия, белый ghost у остальных. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 38px;
  padding: 0 18px;
  border: 1px solid transparent;
  border-radius: var(--r-button);
  background: transparent;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.btn:disabled { opacity: .5; cursor: default; box-shadow: none; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover:not(:disabled) { background: var(--accent-dark); border-color: var(--accent-dark); }

/* Кнопка «Подключить Instagram» и подсказка под ней – отдельный блок над
   полем токена, чтобы основной путь читался первым, а ручной ввод – запасным. */
.channel-connect { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.channel-connect .btn { align-self: flex-start; }
.channel-connect .hint { margin: 0; }
.channel-connected { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.channel-connect-form { display: flex; gap: 8px; align-items: stretch; }
.channel-connect-form .input { flex: 1 1 240px; min-width: 0; }
.channel-connect-form .btn { flex: 0 0 auto; }
.channel-connect-form-col { flex-direction: column; align-items: stretch; max-width: 460px; }
.channel-connect-form-col .input { flex: 0 0 auto; width: 100%; }
.channel-connect-form-col .btn { align-self: flex-start; }
.channel-connected-foot { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.channel-connected-foot .hint { margin: 0; flex: 1 1 240px; }

.btn-ghost {
  background: var(--surface);
  border-color: var(--border);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}
.btn-ghost:hover:not(:disabled) { background: var(--ivory); border-color: var(--faint); color: var(--text); }

.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover:not(:disabled) { background: #8f1c12; border-color: #8f1c12; }

.btn-danger-ghost { background: var(--surface); border-color: var(--border); color: var(--danger); box-shadow: var(--shadow-sm); }
.btn-danger-ghost:hover:not(:disabled) { background: var(--danger-soft); border-color: var(--danger-line); }

.btn-sm { height: 32px; padding: 0 14px; font-size: 13px; }
.btn-block { width: 100%; }

/* Поля: белые, hairline, 12px, фокус – кобальтовое кольцо (то же, что в LMS) */
.input, .textarea, .select {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  padding: 9px 13px;
  color: var(--text);
  box-shadow: rgba(0,0,0,.04) 0 1px 1px 0;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
.input:hover:not(:focus), .textarea:hover:not(:focus), .select:hover:not(:focus) { border-color: var(--faint); }
.input:focus, .textarea:focus, .select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 104, 249, .16);
}
.input { height: 40px; }
/* Однострочное поле: длинная подсказка не должна ломать высоту. */
.input::placeholder {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.textarea { resize: vertical; line-height: 1.55; }
.mono { font-family: var(--font-mono); font-size: 12.8px; }

.select {
  height: 40px;
  appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23666b75' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
}

.field { display: block; }
.field-label {
  display: block;
  margin-bottom: 6px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}

.hint { margin: 8px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--text-2); }

.status { font-size: 12.5px; color: var(--text-2); }
.status.is-ok { color: var(--ok); }
.status.is-error { color: var(--danger); }

/* Сообщение об ошибке – плашка LMS: подложка, hairline в тон, 12px */
.alert {
  margin: 0;
  padding: 11px 14px;
  border-radius: var(--r-md);
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  color: var(--danger);
  font-size: 13px;
}

/* ============================================================
   Экран логина
   ============================================================ */

/* Вход повторяет `.auth-page` LMS: вертикальный градиент cream → sky → lavender
   и белая карточка 20px с мягкой тенью по центру. */
.login-screen {
  min-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: linear-gradient(180deg, var(--canvas) 0%, var(--sky) 55%, var(--lavender) 100%);
}

.login-card {
  width: 100%;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 28px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--shadow-sm), rgba(0,0,0,.04) 0 20px 20px -8px;
}

/* Марка над заголовком – та же кобальтовая капсула-«док», что в шапке LMS */
.login-card::before {
  content: "";
  display: block;
  width: 30px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--accent) linear-gradient(90deg, transparent 0 8px, #fff 8px 14px, transparent 14px);
}

.login-title {
  margin: 0;
  font-size: 30px;
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -.028em;
}
/* Подпись стоит под заголовком, а не над ним, поэтому это лид LMS (18px,
   обычное начертание), а не eyebrow капсом. */
.login-sub {
  margin: -12px 0 4px;
  font-size: 16px;
  line-height: 1.5;
  color: var(--text-2);
}

/* ============================================================
   Каркас приложения
   ============================================================ */

.app {
  height: 100%;
  display: flex;
  align-items: stretch;
}

.view {
  flex: 1;
  min-width: 0;
  display: flex;
}

/* ---------- Левая панель навигации ---------- */

/* Сайдбар как в админке LMS (`.admin-side`): белая колонка 220px, отделённая
   от холста hairline-полосой, марка сверху, пункты – капсулы с иконкой и
   подписью в строку. Тёмный рельс 88px заменён именно здесь: это то, по чему
   интерфейс опознаётся как часть одной системы. */
.nav {
  flex: 0 0 var(--nav-w);
  width: var(--nav-w);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  padding: 20px 14px;
  background: var(--surface-nav);
  border-right: 1px solid var(--border);
  color: var(--text-nav);
}

/* Марка: кобальтовая капсула-«док» и название, как `.brand` в LMS */
.nav-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
  padding-inline: 10px;
  color: var(--text);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -.012em;
}
.nav-brand::before {
  content: "";
  flex: none;
  width: 30px;
  height: 20px;
  border-radius: var(--r-pill);
  background: var(--accent) linear-gradient(90deg, transparent 0 8px, #fff 8px 14px, transparent 14px);
}
.nav-brand small { color: var(--text-2); font-size: 13px; font-weight: 500; letter-spacing: .01em; }

/* Тумблер «Отвечает / Молчит» под логотипом. Геометрия повторяет .nav-item:
   вертикальный блок с подписью мелким шрифтом, чтобы в узкой панели он читался
   как часть меню, а не как чужеродный виджет. */
/* Тумблер стоит в одной сетке с пунктами меню: та же строка «значок + подпись»,
   только на ivory-подложке с hairline, чтобы читаться органом управления,
   а не шестым разделом. */
.nav-switch {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding: 9px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-button);
  background: var(--ivory);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.nav-switch:hover { background: var(--surface); border-color: var(--faint); }
.nav-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nav-switch:disabled { cursor: default; opacity: .55; }

.nav-switch-track {
  position: relative;
  display: block;
  flex: none;
  width: 34px;
  height: 19px;
  border-radius: 999px;
  /* Выключен – нейтральный серый: «молчит» не тревога, а осознанный режим. */
  background: #c9c9c7;
  transition: background .15s ease;
}

.nav-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #fff;
  transition: transform .15s ease;
}

.nav-switch[aria-checked="true"] .nav-switch-track { background: var(--ok-nav); }
.nav-switch[aria-checked="true"] .nav-switch-knob { transform: translateX(15px); }
.nav-switch[aria-checked="true"] .nav-switch-label { color: var(--ok-nav); }

.nav-switch-label {
  font-size: 14px;
  font-weight: 500;
  transition: color .15s ease;
}

/* Пункт меню – капсула в строку: иконка, подпись. Активный не «подсвечен
   ярче», а залит бледно-голубым с кобальтовой рамкой, как `aria-current`
   в сайдбаре LMS. */
.nav-item {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-nav);
  text-align: left;
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.nav-item:hover { background: var(--canvas); color: var(--text); }
.nav-item.is-active {
  background: var(--accent-soft);
  color: var(--text-nav-on);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.nav-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.nav-icon {
  flex: none;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.nav-label { font-size: 15px; font-weight: 500; }

.nav-spacer { flex: 1; }
/* В колонке 220px «Автоматизация» помещается целиком: мягкий перенос из узкого
   рельса больше не нужен и только рвал бы слово посреди строки. */
.nav-label-wrap { white-space: nowrap; hyphens: none; }

/* «Выход» отделён чертой снизу, как `.admin-side-foot` в LMS */
.nav-item-quiet {
  margin-top: 6px;
  padding-top: 0;
  border-top: 1px solid var(--border);
  border-radius: 0 0 var(--r-md) var(--r-md);
  color: var(--text-2);
  min-height: 46px;
}
.nav-item-quiet:hover { color: var(--text); }

/* ============================================================
   Раздел «Чат»
   ============================================================ */

.view-chat { display: flex; min-height: 0; }

/* ---------- Список чатов ---------- */

.chat-list {
  flex: 0 0 var(--list-w);
  width: var(--list-w);
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
}

.pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  height: 64px;
  padding: 0 14px 0 18px;
  border-bottom: 1px solid var(--border-2);
}

.pane-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -.014em;
}

.chat-search { padding: 8px 10px 4px; }
.chat-search-input { width: 100%; height: 34px; padding: 0 10px; font-size: 13px; }
.chat-search-input::-webkit-search-cancel-button { -webkit-appearance: searchfield-cancel-button; }
/* Найденная реплика в превью: подсвечена, чтобы отличаться от «последнего сообщения». */
.chat-item-match { color: var(--accent-dark); }

.chat-list-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 6px 8px 12px;
}

.chat-item {
  width: 100%;
  /*
    min-width: 0 обязателен: элемент списка это <button>, а кнопки не
    сжимаются ниже содержимого. Без него длинное превью растягивало кнопку
    (замер: 2988px при колонке 307px), ellipsis не срабатывал, и весь список
    уезжал в горизонтальную прокрутку.
  */
  min-width: 0;
  max-width: 100%;
  /* flex, а не block: слева кружок канала, справа – тело с именем и превью.
     Тело обязано иметь свой min-width: 0, иначе ellipsis внутри него не
     сработает и список снова уедет в горизонтальную прокрутку. */
  display: flex;
  align-items: flex-start;
  gap: 10px;
  text-align: left;
  padding: 10px 12px;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  cursor: pointer;
  transition: background .12s ease;
}
.chat-item + .chat-item { margin-top: 2px; }
.chat-item:hover { background: var(--canvas); }
/* Выбранная строка: бледно-голубая заливка с кобальтовой рамкой – та же
   механика, что у активного пункта меню и `aria-current` в LMS. */
.chat-item.is-active { background: var(--accent-soft); box-shadow: inset 0 0 0 1px var(--accent-line); }
.chat-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---------- Кружок канала в списке чатов ----------

   Из какого канала пришёл человек. Значок, а не буква: в списке из десятков
   диалогов это единственное, что различает одноимённых собеседников из
   Telegram и Instagram. Цвета брендовые, потому что узнаются мгновенно. */

.chat-avatar {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: #fff;
}
.chat-avatar svg { width: 17px; height: 17px; fill: currentColor; }

.chat-avatar.is-telegram { background: #2aabee; }
/* Instagram: узнаваемый градиент вместо плоского цвета. */
.chat-avatar.is-instagram {
  background: linear-gradient(45deg, #f9ce34 0%, #ee2a7b 50%, #6228d7 100%);
}
.chat-avatar.is-instagram svg { fill: none; }
.chat-avatar.is-instagram svg circle:last-of-type { fill: currentColor; }
/* Чат, созданный в админке: внешнего канала нет, значок нейтральный. */
.chat-avatar.is-local { background: var(--ivory); border: 1px solid var(--border); color: var(--text-2); }
.chat-avatar.is-local svg { fill: none; }

/* Тело элемента списка: всё, кроме кружка. */
.chat-item-body { flex: 1 1 auto; min-width: 0; }

.chat-item-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  min-width: 0;
}

/* Пометка в превью: окно ответа закрыто. Видна прямо в списке, чтобы
   оператор не открывал чат, не писал ответ и только потом не узнавал,
   что отправить нельзя. */
.chat-item-flag {
  display: inline-block;
  padding: 1px 7px;
  border: 1px solid var(--danger-line);
  border-radius: var(--r-sm);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: 11px;
  font-weight: 500;
}

.chat-item-title {
  display: block;
  min-width: 0;
  flex: 1 1 auto;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat-item.is-active .chat-item-title { color: var(--accent-dark); }

.chat-item-time {
  flex: 0 0 auto;
  font-size: 11.5px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

.chat-item-preview {
  /*
    display: block обязателен. В разметке это <span>, а у строчного элемента
    ширина, overflow и text-overflow не действуют вовсе: превью раздувалось до
    10249px внутри кнопки шириной 291px и уводило список в горизонтальную
    прокрутку. Блочное отображение даёт ellipsis точку отсчёта.
  */
  display: block;
  width: 100%;
  margin-top: 2px;
  font-size: 12.5px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---------- Переписка ---------- */

/* Лента переписки лежит на кремовом холсте, а шапка и поле ввода – белые:
   те же три ступени поверхностей, что во всём LMS. */
.chat-room {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  background: var(--canvas);
}

.chat-head {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 64px;
  padding: 0 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--border-2);
}

.chat-head-text { flex: 1; min-width: 0; }

.chat-title {
  margin: 0;
  /* Кружок канала стоит перед именем, поэтому заголовок – flex. Обрезка
     переехала на .chat-title-text: на самом flex-контейнере ellipsis не
     действует. */
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -.014em;
}
.chat-title .chat-avatar { width: 26px; height: 26px; }
.chat-title .chat-avatar svg { width: 15px; height: 15px; }

.chat-title-text {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-subtitle {
  margin: 1px 0 0;
  font-size: 12.5px;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.btn-back {
  display: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin-left: -6px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
}
.btn-back:hover { background: var(--canvas); color: var(--text); }
.btn-back svg {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ---------- Сообщения ---------- */

.messages {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 22px 24px 8px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Стороны как в мессенджерах: собеседник (user) слева нейтральным пузырём,
   наш аккаунт (assistant: бот или оператор) справа акцентным. */
.msg { display: flex; }
.msg-user { justify-content: flex-start; }
.msg-bot  { justify-content: flex-end; }

/* Пузырь – карточка LMS в миниатюре: 16px, hairline, микротень. Ответ от нас
   залит кобальтом, реплика собеседника белая. */
.bubble {
  max-width: min(640px, 74%);
  padding: 11px 15px 8px;
  border-radius: var(--r-lg);
  word-wrap: break-word;
  overflow-wrap: anywhere;
}

.msg-user .bubble {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  border-bottom-left-radius: 6px;
}
.msg-bot .bubble {
  background: var(--accent);
  color: #fff;
  border-bottom-right-radius: 6px;
}

.bubble-text {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.52;
  white-space: pre-wrap;
}

.bubble-time {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text-3);
}
.msg-bot .bubble-time { color: rgba(255, 255, 255, .78); }

/* Ошибка ответа модели – видимой строкой прямо в переписке */
.msg .bubble-error {
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  box-shadow: none;
  color: var(--danger);
}
.msg .bubble-error .bubble-time { color: var(--text-3); }

/* Индикатор «печатает» */
.typing { display: flex; align-items: center; gap: 7px; }
.typing-dots { display: inline-flex; gap: 4px; }
.typing-dots i {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .85);
  animation: typing-bounce 1.1s infinite ease-in-out;
}
.typing-dots i:nth-child(2) { animation-delay: .16s; }
.typing-dots i:nth-child(3) { animation-delay: .32s; }
.typing-label { font-size: 12.5px; color: rgba(255, 255, 255, .85); }

@keyframes typing-bounce {
  0%, 70%, 100% { transform: translateY(0);   opacity: .45; }
  35%           { transform: translateY(-4px); opacity: 1;  }
}
@media (prefers-reduced-motion: reduce) {
  .typing-dots i { animation: none; opacity: .6; }
}

/* ---------- Поле ввода ---------- */

/* Боевой чат: над полем ввода стоит переключатель роли, поэтому внешний
   контейнер – колонка, а сама строка ввода живёт в .composer-line.
   Тестовый чат остаётся одной строкой: там роль всегда одна и та же. */
.composer {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 12px 24px 18px;
  background: var(--surface);
  border-top: 1px solid var(--border-2);
}
.composer-line {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  min-width: 0;
}
/* Композер тестового чата: строка, а не столбец; отступы и фон под панель
   справа от промпта. */
.composer-test {
  flex-direction: row;
  align-items: flex-end;
  gap: 10px;
  padding: 12px 22px 10px;
  background: var(--surface);
  border-top: 0;
}

/* Строка роли: переключатель и подпись, объясняющая последствие выбора.
   Подпись переносится под переключатель, когда рядом не помещается. */
.composer-role {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  min-width: 0;
}
.composer-role-note {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-3);
  min-width: 0;
}
/* Режим «От клиента» – отладочный, и это должно быть видно без чтения: цвет
   подписи меняется вместе с выбором. */
.composer-role.is-debug .composer-role-note { color: var(--accent-dark); font-weight: 500; }

/* ---------- Окно ответа закрыто ----------

   Instagram принимает ответ только в сутки после сообщения человека. Когда
   окно закрыто, отправка невозможна ни в какой роли, и композер это
   показывает целиком: причина красным и приглушённое поле.

   Красный здесь уместен, в отличие от бейджа канала: канал жив и это не
   поломка, но действие сейчас недоступно, и оператор должен понять это до
   того, как наберёт текст. */
.composer-role-note.is-blocked { color: var(--danger); font-weight: 500; }
.composer.is-blocked .composer-input { background: var(--surface-2); }
.composer.is-blocked .composer-input::placeholder { color: var(--text-3); }

/* Компактный вариант переключателя: он служебный и не должен спорить по весу
   с полем ввода и кнопкой отправки. */
.segmented-sm { padding: 2px; }
.segmented-sm .segmented-item { padding: 5px 11px; font-size: 12.5px; }

.composer-input {
  flex: 1;
  min-height: 42px;
  max-height: 180px;
  padding: 10px 15px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: rgba(0,0,0,.04) 0 1px 1px 0;
  line-height: 1.45;
  resize: none;
  overflow-y: auto;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.composer-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(0, 104, 249, .16);
}
/*
  Подсказка не прокручивается вместе с содержимым: если она не влезает в
  строку, вторая строка просто срезается по высоте поля. Держим её в одну
  строку с многоточием, а сам ввод по-прежнему переносится и прокручивается.
*/
.composer-input::placeholder {
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.btn-send { height: 42px; flex: 0 0 auto; }

/* ---------- Пустые состояния ---------- */

/* Пунктирная рамка на ivory – блок `.empty` из LMS */
.empty {
  margin: auto;
  max-width: 360px;
  padding: 28px 20px;
  text-align: center;
  border: 1px dashed var(--border);
  border-radius: var(--r-lg);
  background: var(--ivory);
  color: var(--text-2);
}
.empty-title {
  margin: 0 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.empty-text { margin: 0; font-size: 13.5px; line-height: 1.5; }

.list-empty {
  padding: 24px 16px;
  text-align: center;
  font-size: 13.5px;
  color: var(--text-2);
}

/* ============================================================
   Раздел «Настройки»
   ============================================================ */

.view-settings { display: flex; min-height: 0; }

.settings-scroll {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
}

/* Ширина колонки как в админке LMS (`.admin-shell`): контент не растягивается
   на всю ширину монитора, по краям остаётся воздух. */
.settings-inner {
  max-width: 1360px;
  margin: 0 auto;
  padding: 28px 32px 64px;
}
/* Вкладка «Промпт» живёт в один экран: страница не прокручивается, всю
   прокрутку берут на себя блоки внутри карточек. Остальные вкладки остаются
   обычными – они длинные по смыслу. */
@media (min-width: 1101px) {
  .settings-inner:has(> #tabpanel-prompt:not([hidden])) {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    height: 100vh;
    padding-bottom: 22px;
  }
  .settings-inner:has(> #tabpanel-prompt:not([hidden])) > .page-head,
  .settings-inner:has(> #tabpanel-prompt:not([hidden])) > .segmented-tabs { flex: 0 0 auto; }
  .settings-inner:has(> #tabpanel-prompt:not([hidden])) > #tabpanel-prompt {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }
}

/* Заголовок раздела в масштабе админки LMS: плотный крупный заголовок и лид
   обычным начертанием под ним. */
.page-head { margin-bottom: 20px; }
.page-title {
  margin: 0;
  font-size: clamp(28px, 3.2vw, 34px);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -.028em;
}
.page-sub { margin: 8px 0 0; max-width: 900px; font-size: 16px; line-height: 1.5; color: var(--text-2); }

/* ---------- Сегментированный переключатель вкладок ---------- */

/* Вкладки-капсула LMS (`.tabs`): белый контейнер с hairline и тенью, активная
   вкладка залита кобальтом. Один индикатор вместо «белая плашка потолще». */
.segmented {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--r-button);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.segmented-item {
  min-height: 34px;
  padding: 0 16px;
  border: 0;
  border-radius: var(--r-button);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.segmented-item:hover { background: var(--canvas); }
.segmented-item.is-active {
  background: var(--accent);
  color: #fff;
  box-shadow: none;
}
.segmented-item.is-active:hover { background: var(--accent); }
.segmented-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Полоса вкладок раздела «Настройки». На узком экране не ломает вёрстку:
   прокручивается по горизонтали внутри себя, страница остаётся неподвижной. */
.segmented-tabs {
  display: flex;
  /* Полоса шире своих вкладок выглядит как пустая плашка: прижимаем ширину
     к содержимому и позволяем ей сжиматься на узком экране. */
  width: fit-content;
  max-width: 100%;
  margin-bottom: 20px;
  overflow-x: auto;
  scrollbar-width: none;
}
.segmented-tabs .segmented-item { flex: 0 0 auto; }
.segmented-tabs::-webkit-scrollbar { display: none; }

.tabpanel:focus { outline: none; }
/* Каналы и мониторинг – узкие списки, промпт и база знаний во всю ширину */
.tabpanel-narrow { max-width: 760px; }

/* Карточка LMS: белая, hairline, 16px, трёхслойная микротень */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.card + .card { margin-top: 20px; }
/* В сетке колонок соседство карточек – горизонтальное, зазор даёт gap.
   Вертикальный margin здесь сдвигал бы правую колонку вниз относительно левой. */
.settings-cols > .card + .card { margin-top: 0; }

/* Промпт слева, тестовый чат справа. Ниже 1100px – одна колонка.

   На вкладке «Промпт» обе карточки должны вставать в один экран целиком, без
   прокрутки страницы. Поэтому сетка берёт остаток высоты вьюпорта: от него
   вычитается всё, что стоит над ней (заголовок раздела, полоса вкладок,
   верхний отступ) и нижний отступ страницы. Прокрутка уходит внутрь карточек:
   в поле промпта, в .prompt-compose и в ленту тестового чата. */
.settings-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px;
  align-items: stretch;
}
/* Высоту сетка получает остатком: .settings-inner на этой вкладке – колонка
   во всю высоту вьюпорта, заголовок и полоса вкладок в ней фиксированы, а
   сетка забирает то, что осталось. Магической константы отступа нет: сдвинется
   заголовок или вкладки – высота пересчитается сама. */
@media (min-width: 1101px) {
  #tabpanel-prompt .settings-cols {
    flex: 1;
    min-height: 460px;
  }
}
.settings-cols + .card { margin-top: 20px; }

.card-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
/* Карточка промпта не растёт вслед за содержимым: она держит рабочую высоту
   экрана, а длинный промпт прокручивается внутри своего поля. 128px – это
   отступы страницы плюс её заголовок над карточкой. */
/* Высота карточки – по экрану, но не ниже рабочего минимума: 520px самого
   поля промпта плюс его слот, блок «Отдельные сообщения», итоги, шапка и
   подвал. Раньше минимум стоял 520px на всю карточку, и полю оставалось
   порядка двух строк.

   Ниже минимума карточка перестаёт влезать в экран и прокручивается вместе
   со страницей: место под чтение промпта важнее, чем экран без прокрутки. */
/* Высоту карточка берёт от сетки, а не от содержимого: блок промпта стал
   примерно на 20% ниже прежних 860px, а место под переменные, «Отдельные
   сообщения» и итоги освободилось. Не влезло – прокручивается .prompt-compose
   внутри карточки, страница остаётся неподвижной. */
.card-col-prompt { height: 100%; min-height: 0; }

/* Заголовок карточки промпта компактнее обычного: подпись под ним длинная,
   а высота нужна полю. */
.card-col-prompt > .card-head { padding-top: 13px; padding-bottom: 13px; }
.card-col-prompt > .card-head .card-sub { margin-bottom: 0; }
/* Тестовый чат держит ту же высоту, что и карточка промпта: обе колонки
   встают в один экран целиком – заголовок, лента и поле ввода. Лента внутри
   прокручивается сама. */
#tabpanel-prompt .settings-cols > .card-col:not(.card-col-prompt) {
  height: 100%;
  min-height: 0;
}

.card-body-grow {
  flex: 1;
  min-height: 0;
  display: flex;
}
/* Общее правило для textarea, растянутой на всю карточку. Поле промпта из
   него исключено: у .prompt-editable свой рабочий минимум, а этот селектор
   специфичнее и раньше обнулял его до нуля – из-за чего промпт и показывался
   окошком в пару строк независимо от заданной высоты. */
.card-body-grow .textarea:not(.prompt-editable) {
  flex: 1;
  min-height: 0;
  resize: none;
}

/* ---------- Тестовый чат ---------- */

/* Лента тестового чата – кремовый холст внутри белой карточки: та же ступень
   поверхностей, что у боевой переписки. */
.messages-test {
  flex: 1;
  min-height: 0;
  padding: 16px 22px 8px;
  background: var(--canvas);
  border-bottom: 1px solid var(--border-2);
}
.messages-test .bubble { max-width: 88%; }


.test-note {
  margin: 0;
  padding: 0 22px 16px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-2);
  background: var(--surface);
}

.card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  padding: 18px 24px;
  border-bottom: 1px solid var(--border-2);
}

/* ---------- Карточка промпта: правка + плашки подстановок ----------

   Одна колонка сверху вниз в том же порядке, в каком запрос уходит в модель:
   редактируемый промпт, затем переменные на своих местах подстановки.
   -------------------------------------------------------------------- */

/* Прокрутка живёт внутри поля промпта, а переменные, служебные подписи и
   итоги остаются на виду. На узком экране плашки и итоги переносятся и
   перестают влезать целиком – тогда прокручивается и сама карточка, иначе
   поле промпта схлопнулось бы до пары строк. */
.prompt-compose {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow-y: auto;
}

/* Слот = одно сообщение запроса. Системный обведён, чтобы граница «промпт и
   база знаний уходят одним сообщением» читалась без подписи. */
.prompt-slot {
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-height: 0;
}
/* Системный слот забирает всю свободную высоту карточки. Поле промпта тянется
   внутри него и прокручивается само; чип базы знаний и его подпись прижаты
   снизу и никуда не уезжают. */
.prompt-slot-system {
  /* Слот забирает всю свободную высоту и отдаёт её textarea. basis:0 нужен,
     чтобы за точку отсчёта бралась не собственная высота содержимого
     (промпт на десять тысяч символов), а свободное место карточки.
     min-height удерживает рабочий размер поля, когда места мало; на широком
     экране свободного места больше и слот растёт сам.
     560px = 520px гарантированного поля плюс заголовок слота, чип базы
     знаний и внутренние отступы. */
  flex: 1 1 0;
  /*
    min-content, а не число: слот обязан вмещать заголовок, поле промпта,
    подпись про заголовок базы знаний и сам чип {{БАЗА_ЗНАНИЙ}}. С жёсткими
    300px чип вылезал за нижнюю границу на 21px и визуально обрезался блоком
    ниже. Здесь высота считается от реального содержимого.
  */
  min-height: min-content;
  padding: 12px 14px 14px;
  border: 1px solid var(--accent-line);
  border-radius: var(--r-md);
  background: var(--accent-soft);
}
.prompt-slot-messages {
  flex: 0 0 auto;
  padding: 10px 14px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
}
/* Плашки остаются в столбик: имя переменной и счётчик рядом читаются, а в
   два столбца счётчик переносится и строка становится выше, а не ниже. */
.prompt-slot-messages .var-chip { padding-top: 6px; padding-bottom: 6px; }

/* Подпись слота – eyebrow LMS: капс, разрядка .077em, slate */
.prompt-slot-title {
  margin: 0;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .077em;
  text-transform: uppercase;
  color: var(--text-2);
}
.prompt-slot-system .prompt-slot-title { color: var(--accent); }

/* Единственное, что правится руками. Промпт на десять тысяч символов не должен
   растягивать карточку – он прокручивается внутри поля. */
.prompt-editable {
  flex: 1 1 0;
  /* Промпт – главный рабочий текст админки, и читать его надо целиком, а не
     в окошко на две строки. 520px – нижняя граница: дальше поле растёт вслед
     за высотой экрана, а длинный промпт прокручивается внутри себя. */
  min-height: 200px;
  resize: vertical;
  overflow-y: auto;
  background: var(--surface);
}

.prompt-vars {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.prompt-vars:empty { display: none; }

/* Служебная вставка: показывает точный текст, который склейка добавляет
   от себя. Только чтение – правится он не здесь, а кодом сборки промпта.
   Одна строка по умолчанию: важен факт вставки и её точный текст, а место
   в карточке принадлежит промпту. */
.slot-note {
  flex: 0 0 auto;
  margin: 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-3);
}
.slot-note-code {
  padding: 1px 6px;
  border: 1px dashed var(--faint);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-2);
  white-space: nowrap;
  user-select: text;
}
/* Длинный перечень вставок прячется под «показать»: разворачивается по
   требованию и не отъедает высоту у поля промпта. */
.slot-note-more { margin-top: 4px; }
.slot-note-more > summary {
  cursor: pointer;
  color: var(--accent);
  font-size: 11.5px;
  list-style: none;
}
.slot-note-more > summary::-webkit-details-marker { display: none; }
.slot-note-more > summary::before { content: "▸ "; }
.slot-note-more[open] > summary::before { content: "▾ "; }
.slot-note-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.slot-note-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 5px;
}
.slot-note-row { display: flex; gap: 6px; align-items: baseline; flex-wrap: wrap; }

/* ---------- Плашка-переменная ---------- */

.var-chip {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 8px 12px;
  text-align: left;
  border: 1px dashed var(--faint);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: help;
  transition: border-color .15s ease, background .15s ease;
}
.var-chip:hover,
.var-chip:focus-visible {
  border-color: var(--accent);
  border-style: solid;
  background: var(--surface);
}
.var-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Пустая подстановка не должна выглядеть как заполненная. */
.var-chip.is-empty { opacity: .72; }

.var-chip-name {
  font-family: var(--font-mono);
  font-size: 12.4px;
  font-weight: 600;
  color: var(--accent);
  white-space: nowrap;
}
.var-chip-count {
  font-size: 12px;
  color: var(--text-2);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ---------- Итоги ---------- */

/* Итоги с выбором модели стоят в потоке карточки, но прилипают к её низу:
   прокручиваются промпт, переменные и блок «Отдельные сообщения», а модель и
   объём запроса остаются на виду. Иначе на экране 900px они уезжали под
   нижний край карточки, и до них приходилось долистывать. */
.prompt-summary {
  /*
    Без sticky. Прилипание к низу карточки визуально накладывало итоги на
    переменные {{ИСТОРИЯ_ДИАЛОГА}} и {{СООБЩЕНИЕ_ПОЛЬЗОВАТЕЛЯ}}: они уходили
    под непрозрачную плашку и переставали читаться. Пусть прокручивается
    вместе с остальным содержимым карточки.
  */
  flex: 0 0 auto;
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
}
.prompt-summary:empty { display: none; }


.preview-totals-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(124px, 1fr));
  gap: 7px 16px;
}
.preview-total { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.preview-total-key {
  font-size: 12px;
  color: var(--text-2);
}
.preview-total-val {
  font-size: 13px;
  font-weight: 600;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}

/* Модель выбирается прямо в итогах: она и так первая строка сводки, и держать
   ради неё отдельную карточку внизу страницы было незачем. */
/* Модель занимает свою строку целиком: подпись, список и текущее значение
   в одну линию – так строка стоит одну высоту, а не три. */
.preview-total-model {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 30px;
}
.preview-total-model .preview-total-key { flex: 0 0 auto; }
.preview-total-model .hint {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 11.5px;
  overflow-wrap: anywhere;
}
.model-inline {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 260px;
  min-width: 0;
}
.model-inline .select {
  flex: 1 1 220px;
  min-width: 0;
  max-width: 420px;
  height: 30px;
  padding: 0 26px 0 8px;
  font-size: 12.5px;
}
.model-inline-status { font-size: 11.5px; }
.model-inline-status:empty { display: none; }
.preview-note {
  margin: 7px 0 0;
  padding-top: 7px;
  border-top: 1px solid var(--border-2);
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--text-3);
}

.preview-state {
  margin: 0;
  padding: 10px 0;
  font-size: 13px;
  color: var(--text-3);
}
.preview-state.is-error {
  padding: 12px 14px;
  border: 1px solid var(--danger-line);
  border-radius: var(--r-md);
  background: var(--danger-soft);
  color: var(--danger);
}

/* ---------- Всплывающее превью переменной ----------

   position: fixed – координаты считаются от вьюпорта в placeVarPop(), чтобы
   панель не обрезалась прокруткой карточки и не вылезала за экран.
   -------------------------------------------------------------------- */

.var-pop {
  position: fixed;
  z-index: 60;
  width: min(420px, calc(100vw - 20px));
  max-height: min(460px, 72vh);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 12px 14px 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

.var-pop-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 4px;
}
.var-pop-name {
  font-family: var(--font-mono);
  font-size: 12.4px;
  font-weight: 600;
  color: var(--accent);
}
.var-pop-count {
  font-size: 11.5px;
  color: var(--text-3);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.var-pop-hint {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-2);
}
.var-pop-empty {
  margin: 0;
  font-size: 12.5px;
  color: var(--text-3);
}

.var-pop-list {
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
}
.var-pop-item + .var-pop-item { border-top: 1px solid var(--border-2); }

/* Строка файла – кнопка целиком: раскрывает полный текст под собой. */
.var-pop-item-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12px;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.var-pop-item-head:hover { background: var(--canvas); }
.var-pop-item-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Каретка: вправо закрыто, вниз раскрыто. */
.var-pop-item-caret {
  flex: 0 0 auto;
  align-self: center;
  width: 0;
  height: 0;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  border-left: 5px solid var(--text-3);
  transition: transform .12s ease;
}
.var-pop-item.is-open .var-pop-item-caret {
  transform: rotate(90deg) translateX(1px);
}

.var-pop-item-name { min-width: 0; flex: 1 1 auto; overflow-wrap: anywhere; }
.var-pop-item-chars {
  flex: 0 0 auto;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}

/* Раскрытое содержимое: своя прокрутка, чтобы длинный файл не растягивал
   панель. overscroll-behavior не даёт докрутке уйти на страницу под панелью. */
.var-pop-file-body {
  padding: 0 8px 8px;
}
.var-pop-file-text {
  margin: 0;
  max-height: 260px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text);
}
.var-pop-file-text.is-muted { color: var(--text-3); }

.var-pop-turns { display: flex; flex-direction: column; gap: 8px; }
.var-pop-turn { display: flex; flex-direction: column; gap: 4px; align-items: flex-start; }

.var-pop-text {
  margin: 0;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text);
}

/* Метка роли – та же капсула `.tag` LMS: 8px, ivory, hairline */
.role-badge {
  padding: 2px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--ivory);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
}
.role-badge.role-system { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); }
.role-badge.role-user { background: var(--ivory); border-color: var(--border); color: var(--text-2); }
.role-badge.role-assistant { background: #edf9f1; border-color: #c6ecd3; color: var(--ok); }

.card-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  line-height: 1.38;
  letter-spacing: -.014em;
}
.card-sub { margin: 4px 0 0; font-size: 13.5px; line-height: 1.5; color: var(--text-2); }

.card-body { padding: 20px 24px; }
.card-body-flush { padding: 0; }

.card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 24px;
  background: var(--ivory);
  border-top: 1px solid var(--border-2);
}

/* ---------- Файлы базы знаний ---------- */

.kb-item { border-bottom: 1px solid var(--border-2); }
.kb-item:last-child { border-bottom: 0; }

.kb-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 24px;
}

.kb-toggle {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  user-select: none;
}
.kb-toggle input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }

/* Имя файла – кнопка раскрытия: сброс вида кнопки, строка остаётся строкой. */
.kb-main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  margin: -6px -8px;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.kb-main:hover { background: var(--canvas); }
.kb-main:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-main-text { min-width: 0; display: flex; flex-direction: column; }
.kb-caret {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-right: 1.6px solid var(--text-2);
  border-bottom: 1.6px solid var(--text-2);
  transform: rotate(-45deg);
  transition: transform .15s ease;
}
.kb-main[aria-expanded="true"] .kb-caret { transform: rotate(45deg); }

.kb-name {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kb-item.is-off .kb-name { color: var(--text-2); font-weight: 400; }

.kb-meta { margin-top: 2px; font-size: 12.5px; color: var(--text-2); }

/* Действия убраны под кнопку с тремя точками: в строке файла важны имя и
   переключатель, а «Удалить» рядом с ними слишком легко нажать мимо. */
.kb-actions { flex: 0 0 auto; display: flex; gap: 6px; position: relative; }

/* Кнопка «⋯» и всплывающее меню – механика `.rmenu` из LMS: круглая кнопка,
   меню карточкой 12px с мягкой тенью, пункты капсулами 10px. */
.kb-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  line-height: 1;
  font-size: 17px;
  transition: background .15s ease, border-color .15s ease;
}
.kb-menu-btn:hover { background: var(--canvas); color: var(--text); }
.kb-menu-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.kb-menu-btn[aria-expanded="true"] { background: var(--canvas); color: var(--text); }

.kb-menu {
  /* Все меню «···» живут в fixed-слое: у .card стоит overflow: hidden ради
     скруглённых углов, и абсолютное меню резалось нижней границей карточки.
     Координаты ставит placeMenu() в app.js по кнопке вызова. */
  position: fixed;
  z-index: 40;
  min-width: 168px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.kb-menu[hidden] { display: none; }

.kb-menu-item {
  display: block;
  width: 100%;
  padding: 8px 11px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  text-align: left;
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
}
.kb-menu-item:hover { background: var(--canvas); }
.kb-menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.kb-menu-item.is-danger { color: var(--danger); }

.kb-editor {
  padding: 0 24px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.kb-editor-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
}
.kb-editor .textarea { min-height: 220px; }
.kb-editor .kb-content { min-height: min(60vh, 520px); font-size: 13px; line-height: 1.55; }
.kb-editor-hint { margin: 0 auto 0 0; font-size: 12px; }

/* ---------- Раздел «Рассылка» ---------- */

.camp-list { display: flex; flex-direction: column; gap: 8px; }

.camp-item {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  padding: 12px 14px;
}

/* Строка кампании – flex, а тело внутри с min-width: 0: иначе длинное
   название не обрезается и список уезжает в горизонтальную прокрутку.
   Тем же болел список чатов. */
.camp-item-main {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.camp-item-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }

.camp-item-top {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.camp-item-name {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.camp-item-meta {
  font-size: 12.5px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Статус кампании – метка `.tag` LMS: 8px, ivory, hairline, цвет несёт состояние */
.camp-status {
  flex: none;
  font-size: 11.5px;
  font-weight: 500;
  padding: 2px 9px;
  border-radius: var(--r-sm);
  background: var(--ivory);
  border: 1px solid var(--border);
  color: var(--text-2);
}
.camp-status.is-running { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); }
.camp-status.is-done { background: #edf9f1; border-color: #c6ecd3; color: var(--ok); }
.camp-status.is-failed { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }

.camp-progress {
  display: block;
  height: 4px;
  border-radius: 999px;
  background: var(--border-2);
  overflow: hidden;
}
.camp-progress-bar {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  transition: width .3s ease;
}

/* Цифры кампании в её строке: отправлено, пропущено, ошибки. Общая
   сводка по всем кампаниям сразу ни на один вопрос оператора не отвечала:
   спрашивают «как прошла вот эта», а не «сколько всего за всё время». */
.camp-item-stats {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 5px;
}
.camp-item-stat {
  font-size: 12px;
  color: var(--text-2);
  padding: 2px 9px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--ivory);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.camp-item-stat b { color: var(--text); font-weight: 600; }
.camp-item-stat.is-failed b { color: var(--danger); }
.camp-item-stat.is-sent b { color: var(--ok); }

/* Меню действий кампании: та же механика, что у файлов базы знаний. */
.camp-actions { position: relative; flex: none; }

/* Форма создания.

   Компактность здесь не вкусовщина: форму открывают, чтобы отметить людей и
   написать текст, а до правок половину экрана занимал воздух между полями,
   и список получателей приходилось искать прокруткой. */

.camp-form { display: flex; flex-direction: column; gap: 12px; }

/* Название и канал рядом: два коротких поля не стоят двух экранных строк. */
.camp-row { display: flex; gap: 12px; align-items: flex-start; }
.camp-row > .field { margin: 0; }
.camp-field-name { flex: 1 1 auto; min-width: 0; }
.camp-field-channel { flex: 0 0 auto; }
.camp-row-tail { flex-wrap: wrap; align-items: flex-start; }
.camp-when-field { flex: 1 1 auto; min-width: 0; }

.camp-channels { display: flex; gap: 8px; flex-wrap: wrap; }

.camp-channel {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-button);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  /* 44px по высоте: на телефоне это нижняя граница удобного касания. */
  min-height: 44px;
}
.camp-channel.is-active { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-dark); }

.camp-no-channels { margin: 6px 0 0; margin-left: 0; }

.linklike {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.input-sm { height: 34px; font-size: 13px; }

.camp-aud-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.camp-aud-label { margin: 0; }
.camp-aud-count { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.camp-aud-tools { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.camp-aud-search { max-width: 200px; }
.camp-filters-btn { flex: none; }
.camp-filters-btn[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent-dark);
}

/* Блок фильтров. Свёрнут до клика: развёрнутым он занимал больше места,
   чем список людей, ради которого форму и открывают. */
.camp-filters {
  padding: 12px 14px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
}
.camp-filters-row {
  display: flex;
  align-items: center;
  gap: 8px 14px;
  flex-wrap: wrap;
}
.camp-filter-group { display: flex; align-items: center; gap: 6px; min-width: 0; }
.camp-filter-label { font-size: 12px; color: var(--text-3); white-space: nowrap; }
.camp-filter-dash { color: var(--text-3); font-size: 12px; }
.camp-filter-group input { width: 132px; box-sizing: border-box; }
.camp-filters-reset { margin-left: auto; }

.camp-filter-window {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 12.5px;
  cursor: pointer;
  white-space: nowrap;
}
.camp-filter-window input { width: 16px; height: 16px; flex: none; }
/* Заблокированная галка в Instagram: платформа не даст отправить вне окна,
   и снятая галка обещала бы отправку, которая кончится ошибкой в отчёте. */
.camp-filter-window input:disabled { cursor: default; }
.camp-filter-window:has(input:disabled) { color: var(--text-3); cursor: default; }
.camp-filter-note { margin: 4px 0 0; font-size: 11.5px; line-height: 1.45; }

.camp-aud-note {
  margin: 0 0 6px;
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.45;
}

/* «Выбрать все» над списком, вне прокрутки: внутри неё оно уезжало бы
   из виду вместе с первыми получателями. */
.camp-aud-all {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border: 1px solid var(--border);
  border-bottom: 0;
  border-radius: var(--r-md) var(--r-md) 0 0;
  background: var(--ivory);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
}
.camp-aud-all input { width: 16px; height: 16px; flex: none; }

.camp-aud-list {
  /* Фиксированная высота с прокруткой внутри: аудитория из ста человек
     растягивала форму на пять экранов, и до текста сообщения было не
     добраться иначе как листанием. */
  max-height: 210px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
}
/* Список под «выбрать все» – одна карточка: верхний угол скругляет шапка. */
.camp-aud-all:not([hidden]) + .camp-aud-list { border-radius: 0 0 var(--r-md) var(--r-md); }

.camp-aud-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--border-2);
  cursor: pointer;
  min-height: 38px;
  box-sizing: border-box;
}
.camp-aud-item:last-child { border-bottom: 0; }
/* Недоступные не прячем, а гасим: оператору нужен масштаб «доступно N из M»,
   а молча укоротившийся список читается как потеря людей. */
.camp-aud-item.is-off { opacity: .55; cursor: default; }
.camp-aud-item input { margin-top: 3px; flex: none; width: 16px; height: 16px; }
.camp-aud-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.camp-aud-name { font-size: 13px; overflow-wrap: anywhere; }
.camp-aud-note-item { font-size: 11.5px; color: var(--text-3); overflow-wrap: anywhere; }

/* Режим текста и два столбца */

.camp-mode-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.camp-mode-head .field-label { margin: 0; }

/* В режиме «каждому свой» шаблон встаёт вторым столбцом справа: исходный
   текст и его разложенный вариант читаются только рядом, а не по очереди. */
.camp-msg-cols { display: grid; grid-template-columns: 1fr; gap: 12px; }
.camp-msg-cols.is-split { grid-template-columns: 1fr 1fr; }
.camp-msg-col { display: flex; flex-direction: column; min-width: 0; }
.camp-msg-col .camp-textarea { flex: 1 1 auto; }
/* В split обе шапки видны, и поля начинаются на одной высоте. */
.camp-msg-cols.is-split .camp-msg-head-src { display: flex; }

.camp-spin-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 6px;
  /* Одна высота у обеих шапок: справа стоит кнопка, слева только подпись,
     и без этого поля начинались на разной высоте. */
  min-height: 30px;
}
.camp-spin-title { font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.camp-spin-hint { margin: 6px 0 0; margin-left: 0; font-size: 11.5px; line-height: 1.45; }
/* Пример синтаксиса – отдельной строкой: разорванный посередине, он читается
   не как образец, а как часть предложения вокруг. */
.camp-spin-hint code {
  display: block;
  margin-top: 3px;
  font-size: 11.5px;
  padding: 3px 7px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--ivory);
  overflow-wrap: anywhere;
}
.camp-spin-status { margin: 5px 0 0; font-size: 12px; color: var(--text-2); }
.camp-spin-status.is-error { color: var(--danger); }
.camp-spin-status.is-ok { color: var(--ok); }

/* Высота задаётся здесь, а не атрибутом rows: rows считает строки шрифтом
   браузера, и одно и то же поле выходило на треть выше расчёта. */
.camp-textarea { min-height: 92px; height: 92px; }

.camp-pace-field { flex: 0 0 auto; }
.camp-pace { max-width: 120px; }

.camp-eta { margin: 0; font-size: 12.5px; color: var(--text-2); }
.camp-risk {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--danger);
}

.camp-when { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.radio {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  cursor: pointer;
  min-height: 32px;
}
.radio input { width: 16px; height: 16px; }
.camp-at { max-width: 230px; }

/* Карточка кампании */

.camp-detail-actions { display: flex; gap: 8px; flex-wrap: wrap; }

#camp-detail-card .camp-progress { margin-bottom: 12px; height: 6px; }

/* Счётчики – плитки с разделителем: подряд, через один пробел, «0 Отправлено
   12 В очереди» читается одной строкой мусора, и число не привязывается к
   своей подписи. */
.camp-counters { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.camp-counter {
  font-size: 13px;
  color: var(--text-2);
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
  white-space: nowrap;
}
.camp-counter b { font-size: 16px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }

.camp-detail-message {
  margin: 0 0 16px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Отметки времени кампании: подпись и значение парой, в одну строку. */
.camp-dates {
  display: flex;
  gap: 6px 18px;
  flex-wrap: wrap;
  margin: 0 0 14px;
  font-size: 12.5px;
}
.camp-dates > div { display: flex; gap: 6px; }
.camp-dates dt { color: var(--text-3); }
.camp-dates dd { margin: 0; color: var(--text); font-variant-numeric: tabular-nums; }

.camp-targets-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  text-align: left;
  min-height: 38px;
}
.camp-targets-toggle[aria-expanded="true"] { border-radius: var(--r-md) var(--r-md) 0 0; border-bottom: 0; }
.camp-targets-chevron {
  margin-left: auto;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--text-3);
  border-bottom: 1.5px solid var(--text-3);
  transform: rotate(-45deg);
  transition: transform .15s ease;
}
.camp-targets-toggle[aria-expanded="true"] .camp-targets-chevron { transform: rotate(45deg); }

.camp-targets {
  border: 1px solid var(--border);
  border-radius: 0 0 var(--r-md) var(--r-md);
  overflow: hidden;
  max-height: 420px;
  overflow-y: auto;
}
.camp-target {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border-2);
  font-size: 12.5px;
}
.camp-target:last-child { border-bottom: 0; }
.camp-target-name { min-width: 0; overflow-wrap: anywhere; }
.camp-target-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
/* Точный текст этому человеку: в режиме «каждому свой» он у каждого свой,
   и проверить раскладку можно только увидев её до запуска. */
.camp-target-text {
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text-3);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.camp-target-right { display: flex; align-items: center; gap: 8px; flex: none; }
.camp-target-chat { flex: none; }
.camp-target-status { flex: none; color: var(--text-3); text-align: right; }
.camp-target.is-sent .camp-target-status { color: var(--ok); }
.camp-target.is-failed .camp-target-status { color: var(--danger); }
/* Ошибка бывает длинной: на узком экране она обязана переноситься, иначе
   выдавит имя получателя за край. */
.camp-target-status { overflow-wrap: anywhere; }

.stop-words-textarea {
  min-height: 200px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px;
  line-height: 1.6;
}

.field-note {
  margin-left: 6px;
  font-weight: 400;
  color: var(--text-3);
}

/* ============================================================
   Раздел «Автоматизация»
   ============================================================ */

.auto-no-ig { margin: 0 0 10px; }
.auto-item.is-off .camp-item-name { color: var(--text-3); }
.auto-item .channel-switch { flex: none; }

/* Ширина держится здесь, чтобы расчёт placeMenu() и отрисовка не разошлись. */
.auto-item .kb-menu { width: 172px; }

/* Форма: две колонки. Слева повод, справа ответ, и порядок чтения совпадает
   с порядком мысли, а не с порядком полей в базе. */
.auto-form { display: flex; flex-direction: column; gap: 14px; }
.auto-name-field { margin: 0; max-width: 460px; }
.auto-cols { display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 18px; align-items: start; }
.auto-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* «Добавить реакцию» прижата влево под списком: по центру или справа она
   отрывалась бы от реакций, к которым относится. */
.auto-rule-add { align-self: flex-start; }
.auto-col-title {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  text-transform: uppercase;
  letter-spacing: .03em;
}

/* Поводы: галка, заголовок, пояснение. Настройки комментариев вложены
   под свой повод, поэтому у блока нет сетки, только колонка. */
.auto-triggers { display: flex; flex-direction: column; gap: 8px; }
.auto-trigger {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
  cursor: pointer;
}
.auto-trigger:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.auto-trigger input { width: 16px; height: 16px; margin-top: 2px; flex: none; }
.auto-trigger-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.auto-trigger-title { font-size: 13px; font-weight: 600; }
.auto-trigger-sub { font-size: 11.5px; color: var(--text-3); line-height: 1.4; }

/* Отступ слева равен ширине галки с зазором: настройки читаются как
   продолжение своего повода, а не как отдельный блок. */
.auto-trigger-body-settings {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: -2px 0 2px 26px;
}

.auto-scope { align-self: flex-start; }

.auto-media-box { display: flex; flex-direction: column; gap: 6px; }
.auto-media-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }

/* Сетка обложек: ролик узнают по картинке, поэтому она крупнее подписи.
   В колонке места меньше, чем было на всю ширину, поэтому карточки уже. */
.auto-media {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 7px;
  max-height: 260px;
  overflow-y: auto;
  padding: 7px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
}
.auto-media .list-empty { grid-column: 1 / -1; }
.auto-media-item {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 5px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
  min-width: 0;
}
.auto-media-item.is-on { border-color: var(--accent); background: var(--accent-soft); }
.auto-media-item input {
  position: absolute;
  top: 10px;
  left: 10px;
  width: 17px;
  height: 17px;
  margin: 0;
  z-index: 1;
}
.auto-media-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: 5px;
  background: var(--border-2);
}
.auto-media-thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  font-size: 16px;
}
.auto-media-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.auto-media-type { font-size: 10.5px; color: var(--text-3); }
.auto-media-caption {
  font-size: 11px;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}

/* ============================================================
   Раздел «Публикации»
   ============================================================ */

/* Плитки над сеткой – те же .camp-counter; здесь только отступ от подсказки. */
.pub-counters { margin-bottom: 6px; }
.pub-quota-note, .pub-grid-note {
  margin: 0 0 14px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-3);
}
.pub-grid-note { color: var(--danger); }
.pub-grid-note .linklike { margin-left: 4px; }

/* Пустые состояния раздела: кнопки под текстом, на 375 – в столбик. */
.pub-empty-actions {
  display: flex;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.pub-no-permission { max-width: 420px; }
.pub-no-permission code { font-family: var(--font-mono); font-size: 12.5px; }
.pub-no-permission .status { margin: 10px 0 0; }
.pub-no-permission .status:empty { display: none; }

/* Сетка аккаунта: по образцу .auto-media, но плитка выше – миниатюра 1:1 и
   строка цифр снизу. Без прокрутки внутри: это главный экран раздела, а не
   вложенный список в форме. */
.pub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 10px;
}
.pub-grid .list-empty { grid-column: 1 / -1; }
.pub-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  text-align: left;
  font: inherit;
  cursor: pointer;
  min-width: 0;
  transition: border-color .15s ease, background .15s ease;
}
.pub-tile:hover { border-color: var(--faint); background: var(--ivory); }
.pub-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.pub-tile-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 7px;
  background: var(--border-2);
}
.pub-tile-thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  font-size: 12px;
}
/* Метка статуса лежит на миниатюре: у черновика и очереди она важнее подписи. */
.pub-tile-tag { position: absolute; top: 12px; left: 12px; }
.pub-tile-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: 0 2px 2px; }
.pub-tile-type { font-size: 11px; color: var(--text-3); }
.pub-tile-caption {
  font-size: 12px;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.pub-tile-stats { font-size: 11.5px; color: var(--text-2); font-variant-numeric: tabular-nums; }

/* Метка статуса – та же капсула .tag LMS, что у кампаний; цвет несёт состояние */
.pub-tag {
  display: inline-block;
  flex: none;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.4;
  padding: 2px 9px;
  border-radius: var(--r-sm);
  background: var(--ivory);
  border: 1px solid var(--border);
  color: var(--text-2);
  white-space: nowrap;
}
.pub-tag.is-live { background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-dark); }
.pub-tag.is-done { background: #edf9f1; border-color: #c6ecd3; color: var(--ok); }
.pub-tag.is-failed { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }

/* Форма: одна колонка сверху вниз в порядке сборки поста. */
.pub-form { display: flex; flex-direction: column; gap: 16px; }
.pub-form .field { display: flex; flex-direction: column; gap: 6px; }
.pub-form .field-label { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 6px; }
.pub-kind { align-self: flex-start; }
.segmented-item:disabled { opacity: .45; cursor: not-allowed; }
.segmented-item:disabled:hover { background: transparent; }

/* Ошибка под полем: красным блоком, чтобы не спутать с подсказкой. */
.field-error {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--danger);
}
.pub-detail-error {
  padding: 12px 14px;
  border: 1px solid var(--danger-line);
  border-radius: var(--r-md);
  background: var(--danger-soft);
}
.pub-detail-error-text { margin: 0; font-size: 14px; line-height: 1.5; color: var(--danger); }
.pub-detail-error-code { margin: 4px 0 0; font-size: 12px; color: var(--text-2); font-family: var(--font-mono); }
.pub-detail-error-code:empty { display: none; }
.pub-detail-error-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.pub-detail-error-actions:empty { display: none; }

/* Макет поста. Ширина 470px – столько занимает пост в вебе Instagram:
   уже макет перестаёт читаться как пост, шире растягивает кадр. */
.pub-compose { display: flex; }
.pub-post {
  width: 100%;
  max-width: 470px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  overflow: hidden;
}

/* Шапка: аватар и ник, как в посте. */
.pub-post-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
}
.pub-post-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent-dark);
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  line-height: 1;
}
.pub-post-who { min-width: 0; }
.pub-post-name {
  display: block;
  font-size: 13.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Медиа: до загрузки квадрат, после – пропорции самого файла, чтобы кадр
   выглядел так, как его увидит подписчик. Пропорции задаёт JS в style. */
.pub-post-media {
  position: relative;
  /* Заглушка ниже квадрата: пустой квадрат 470px уводил подпись и
     комментарии за сгиб, и оператор не видел, что ниже вообще есть пост.
     После загрузки область встаёт в пропорции самого файла. */
  aspect-ratio: 16 / 11;
  background: var(--surface-2);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
/* С загруженным файлом высоту задаёт сама картинка: aspect-ratio
   контейнера спорил с её max-height и оставлял пустую полосу под кадром. */
.pub-post-media.is-filled { aspect-ratio: auto; }
/* Кадр не выше 460px: вертикальное фото 4:5 в полную величину уводило
   подпись и комментарии за сгиб, и оператор правил подпись, не видя кадра.
   Пропорции сохраняются, лишнее режется по краям, как в ленте. */
.pub-post-img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 460px;
  object-fit: cover;
}

/* Заглушка – она же кнопка загрузки, поэтому занимает всю область. */
.pub-drop {
  position: absolute;
  inset: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 12px;
  border: 1.5px dashed var(--faint);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-2);
  font: inherit;
  text-align: center;
  cursor: pointer;
}
.pub-drop:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-dark); }
.pub-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.pub-drop-icon {
  width: 34px;
  height: 34px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pub-drop-title { font-size: 14px; font-weight: 500; }
.pub-drop-hint { font-size: 12.5px; color: var(--text-3); }
.pub-drop:hover .pub-drop-hint { color: inherit; }
/* Требования к файлу: мелким серым в самой заглушке. Под макетом отдельной
   строки им больше нет – там они висели и когда файл уже загружен. */
.pub-drop-req { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.pub-drop:hover .pub-drop-req { color: inherit; }

/* Перетаскивание: видимое состояние «отпустите файл» поверх области. */
.pub-drop-over {
  position: absolute;
  inset: 0;
  display: none;
  place-items: center;
  background: var(--accent-soft);
  border: 2px dashed var(--accent);
  color: var(--accent-dark);
  font-size: 14px;
  font-weight: 600;
}
.pub-post-media.is-dragover .pub-drop-over { display: grid; }

/* «Заменить» и «Убрать» поверх кадра: в макете им места нет, а рядом с
   кадром они ломали бы вид поста. */
.pub-media-actions {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 6px;
}
.pub-media-actions .btn {
  background: rgba(255,255,255,.94);
  box-shadow: var(--shadow-sm);
}

/* Строка действий: декорация, повторяющая вид Instagram. */
.pub-post-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px 2px;
  color: var(--text-3);
}
.pub-post-actions-left { display: flex; align-items: center; gap: 14px; }
/* Иконка и её число рядом, как в Instagram. Числа декоративные. */
.pub-post-action { display: inline-flex; align-items: center; gap: 5px; }
.pub-post-action-n { font-size: 13px; font-variant-numeric: tabular-nums; }
.pub-ig-icon {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pub-post-likes {
  margin: 0;
  padding: 4px 14px 0;
  font-size: 13px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
}
.pub-post-likes b { font-weight: 600; color: var(--text); }

/* Подпись: ник жирным и текст в одном потоке, как в посте.
   Вся область подписи – зона клика: `cursor: text` и обработчик в app.js
   ставят фокус в поле, даже если ткнули в ник или в пустое место справа. */
.pub-post-caption {
  padding: 8px 14px 10px;
  font-size: 13.5px;
  line-height: 1.55;
  cursor: text;
}
.pub-post-caption[hidden] { display: none; }
.pub-post-caption-name { font-weight: 600; margin-right: 5px; }
/* `inline-block` вместо `inline`: инлайновый пустой блок имел нулевую
   высоту, и каретка в нём не рисовалась вовсе. Теперь у поля есть своя
   строка с высотой, и курсор виден и мигает. `min-width` держит зону клика
   у пустого поля: иначе в него нечем попасть мышью. */
.pub-caption {
  display: inline-block;
  vertical-align: top;
  min-height: 1.55em;
  min-width: 40%;
  outline: none;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  /* Цвет каретки задаётся явно токеном: у contenteditable внутри блока с
     серым placeholder браузер иначе берёт унаследованный цвет, и на светлом
     фоне курсор терялся. */
  caret-color: var(--text);
}
/* Placeholder: у contenteditable своего нет, и текст рисуется псевдоэлементом.
   `position: absolute` выводит его из потока, чтобы каретка стояла в начале
   строки, а не за подсказкой. `pointer-events: none` – чтобы клик по самой
   подсказке доходил до поля. Подсказка уходит по фокусу и по первому символу
   (поле перестаёт быть :empty). `content` из ::before попадает в дерево
   доступности даже у скрытой карточки, поэтому она рисуется только на
   видимой форме. */
#pub-form-card:not([hidden]) .pub-caption:empty:not(:focus)::before {
  content: attr(data-placeholder);
  position: absolute;
  color: var(--text-3);
  pointer-events: none;
}
.pub-caption:empty { position: relative; }
.pub-caption:focus-visible { outline: none; }
/* Фокус подписи обозначается полосой слева, без заливки: залитая строка
   читалась как выделенный элемент списка, а не как поле ввода. */
.pub-post-caption:focus-within { box-shadow: inset 2px 0 0 var(--accent); }
/* Хэштег и упоминание – синим, как ссылка в Instagram. */
.pub-caption .pub-tagword { color: var(--accent); }

/* Первый комментарий: поле рабочее, текст уходит в черновик и публикуется
   сразу после выхода поста. Вид остаётся видом комментария Instagram –
   ник жирным, текст рядом, без рамки поля ввода. */
.pub-post-comments { border-top: 1px solid var(--border); padding: 10px 14px 12px; }
.pub-post-comments-note { margin: 0; font-size: 12.5px; color: var(--text-3); }
/* Ник обтекается текстом, как в Instagram: `float` вместо колонки flex,
   иначе вторая строка комментария начиналась под ником лестницей, а не у
   левого края (замер 15.09.2026 на 1280). */
.pub-post-comment-row {
  display: block;
  margin-top: 8px;
  cursor: text;
}
.pub-post-comment-name {
  float: left;
  margin-right: 5px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.5;
}
.pub-post-comment-field {
  display: block;
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  /* `font-family` отдельно, а не через `font: inherit`: шорткат сбрасывал бы
     размер на унаследованный от карточки. На узком экране размер всё равно
     станет 16px общим правилом ниже по файлу: iPhone приближает страницу
     при фокусе в поле со шрифтом меньше, и это правило сильнее вида. */
  font-family: inherit;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  caret-color: var(--text);
  resize: none;
  overflow: hidden;
  outline: none;
}
.pub-post-comment-field::placeholder { color: var(--text-3); }
.pub-post-comments .field-error { margin: 6px 0 0; }

/* Состояние первого комментария в карточке публикации. */
.pub-detail-comment {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.pub-detail-comment-label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--text-3);
}
.pub-detail-comment-text { margin: 0; font-size: 13px; line-height: 1.5; }
.pub-detail-comment-state { margin: 0; font-size: 12.5px; color: var(--text-2); }
.pub-detail-comment.is-error .pub-detail-comment-state { color: var(--danger); }

/* Служебное под макетом: требования, счётчик, ошибки, проба адреса. */
.pub-compose-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 470px;
}
.pub-compose-meta > p { margin: 0; }
.pub-compose-meta .status:empty { display: none; }
.pub-compose-meta .pub-items:empty { display: none; }
.pub-compose-meta .field-note:empty { display: none; }
.pub-progress {
  display: block;
  height: 6px;
  border-radius: 3px;
  background: var(--border-2);
  overflow: hidden;
}
.pub-progress-bar { display: block; height: 100%; width: 0; background: var(--accent); transition: width .15s ease; }
.pub-caption-count { font-variant-numeric: tabular-nums; }
.pub-caption-count.is-over { color: var(--danger); }

/* Файл, принятый сервером: размер, размеры и вердикт пробы. Строка идёт под
   макетом: внутри поста ей места нет, там сам кадр. */
.pub-items { display: flex; flex-direction: column; gap: 8px; }
.pub-items[hidden] { display: none; }
.pub-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.pub-item-thumb {
  flex: none;
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 6px;
  background: var(--border-2);
}
.pub-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.pub-item-title { font-size: 12.5px; font-weight: 500; overflow-wrap: anywhere; }
.pub-item-ok { font-size: 12px; color: var(--ok); }
.pub-item-warn { font-size: 12px; color: var(--danger); }
/* «Убрать» живёт поверх кадра: в строке файла это был бы второй такой же
   элемент управления на один и тот же файл. */
.pub-item [data-pub-item-action="remove"] { display: none; }

.pub-share-feed { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; }
.pub-share-feed input { width: 16px; height: 16px; margin: 0; }
.pub-check { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.pub-check .field-note, #pub-detail-check-result {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 12.5px;
  line-height: 1.5;
}
#pub-detail-check-result { margin-top: 8px; }

/* Подвал формы: «Отмена» слева со статусом, действия справа. */
.pub-foot-left { display: flex; align-items: center; gap: 12px; min-width: 0; }

/* Карточка публикации */
.pub-detail { display: flex; flex-direction: column; gap: 16px; }
.pub-detail-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pub-detail-step { font-size: 13.5px; line-height: 1.5; color: var(--text-2); }
.pub-detail-link-row { margin: 0; }
.pub-detail-link { font-size: 14px; }
.pub-detail-caption {
  margin: 0;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
  font-size: 13.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.pub-detail-items { display: flex; gap: 10px; flex-wrap: wrap; }
.pub-detail-items:empty { display: none; }
.pub-detail-item { display: flex; flex-direction: column; gap: 4px; max-width: 100%; }
.pub-detail-thumb {
  display: block;
  width: 160px;
  max-width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: var(--r-md);
  background: var(--border-2);
}
.pub-detail-item-meta { font-size: 11.5px; color: var(--text-3); white-space: nowrap; }
.pub-detail-insights { margin-bottom: 0; }
.pub-detail-insights:empty { display: none; }
.pub-detail .field { display: flex; flex-direction: column; gap: 6px; }
.pub-detail .field-note { margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--text-3); }

/* Меню карточки: тот же fixed-слой (.kb-menu), фиксированная ширина. */
.pub-detail-menu-wrap { position: relative; }
.pub-detail-menu { width: 172px; }

/* Реакции */

.auto-rules { display: flex; flex-direction: column; gap: 12px; }
.auto-rule {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface-2);
}
.auto-rule-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.auto-rule-title { font-size: 12.5px; font-weight: 600; color: var(--text-2); }
.auto-rule-field { margin: 0; display: flex; flex-direction: column; }
.auto-rule-field .field-label { margin-bottom: 5px; }
.auto-rule-textarea { min-height: 76px; height: 76px; }
.auto-plain-note { margin: 5px 0 0; margin-left: 0; font-size: 11.5px; line-height: 1.45; }
.auto-match-note { margin: 5px 0 0; margin-left: 0; font-size: 11.5px; }

/* Сверка стоит в шапке слов, а не отдельным полем: она описывает именно
   эти слова, и разнесённые по форме они читались бы как чужие друг другу. */
.auto-rule-words-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 5px;
}
.auto-rule-words-head .field-label { margin: 0; }

/* Карточка с кнопками */

.auto-card { gap: 0; }
.auto-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.auto-card-head .field-label { margin: 0; }
.auto-card-body {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-top: 8px;
  padding: 10px 12px;
  border: 1px dashed var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
}
.auto-card-buttons { display: flex; flex-direction: column; gap: 6px; }
/* Подпись, ссылка и крестик в строку: ссылка длиннее подписи, поэтому ей
   отдана растягивающаяся доля. */
.auto-card-button {
  display: grid;
  grid-template-columns: minmax(0, 140px) minmax(0, 1fr) 26px;
  gap: 6px;
  align-items: center;
}
.auto-card-add { align-self: flex-start; }

.auto-hits { border-radius: var(--r-md); max-height: 320px; }

/* Чипы ключевых слов: поле выглядит как input, слова внутри плашками. */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-height: 34px;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: text;
}
.chips:focus-within { border-color: var(--accent); }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 6px 2px 9px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12.5px;
}
.chip-x {
  border: 0;
  background: none;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--text-3);
  cursor: pointer;
  padding: 0 2px;
}
.chip-x:hover { color: var(--danger); }
.chips-input {
  flex: 1 1 80px;
  min-width: 80px;
  border: 0;
  outline: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  padding: 3px 0;
}

/* ============================================================
   Раздел «Каналы»
   ============================================================ */


/* Метка состояния канала – `.tag` LMS: капсула 8px на ivory с hairline */
.badge {
  flex: 0 0 auto;
  padding: 3px 10px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  background: var(--ivory);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  white-space: nowrap;
}
.badge.is-on { background: #edf9f1; border-color: #c6ecd3; color: var(--ok); }
.badge.is-off { background: var(--ivory); color: var(--text-2); }
.badge.is-error { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }


/* ---------- Компактный список каналов ----------

   Канал – строка, а не карточка: в свёрнутом виде видно название,
   состояние и имя бота, всё остальное открывается по нажатию. Так раздел
   остаётся списком, когда рядом с Telegram встанут Instagram и WhatsApp. */

.channels-card { padding: 0; }

.channel-item { border-bottom: 1px solid var(--border-2); }
.channel-item:last-child { border-bottom: 0; }

.channel-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 20px 6px 12px;
}

/* Название и состояние – одна большая кнопка: попасть по ней легко,
   в том числе пальцем на телефоне. */
.channel-toggle {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border: 0;
  border-radius: var(--r-md);
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.channel-toggle:hover { background: var(--canvas); }
.channel-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.channel-caret {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-right: 1.8px solid var(--text-2);
  border-bottom: 1.8px solid var(--text-2);
  transform: rotate(-45deg);
  transition: transform .15s ease;
}
.channel-item.is-open .channel-caret { transform: rotate(45deg); }

.channel-ident { min-width: 0; }

.channel-name {
  display: block;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -.01em;
}

.channel-sub {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.channel-row-right {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* ---------- Тумблер «ИИ отвечает» в строке канала ----------

   Живёт в самой строке, а не в раскрытой части: отвечает ли бот в канале –
   то, что нужно видеть и переключать не разворачивая канал, наравне со
   статусом подключения. Форма та же, что у глобального тумблера в левом
   меню (.nav-switch), поэтому оба читаются как один и тот же орган
   управления, только на разных уровнях: там – все каналы, здесь – один. */

.channel-switch {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  border: 0;
  border-radius: var(--r-button);
  background: none;
  font: inherit;
  /* Подпись одного веса в обоих состояниях: «ИИ отвечает» – название
     органа управления, а не индикатор. Гасить её в серый при выключении
     значило бы показывать рабочий режим «отвечаю руками» как неактивный. */
  color: var(--text-2);
  cursor: pointer;
  transition: background .13s ease, color .13s ease;
}
.channel-switch:hover { background: var(--canvas); }
.channel-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Канал не подключён: переключать нечего. Дорожка гасится до нейтральной
   независимо от aria-checked – иначе тумблер у ненастроенного канала выглядит
   включённым и обещает, что ИИ там отвечает. Само aria-checked при этом не
   врёт: флаг в базе действительно «отвечает», просто канала ещё нет. */
.channel-switch:disabled { cursor: default; opacity: .45; }

.channel-switch-label {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  transition: color .15s ease;
}

.channel-switch-track {
  position: relative;
  display: block;
  width: 32px;
  height: 18px;
  border-radius: 999px;
  /* Выключен – нейтральный серый: «отвечаю руками» рабочий режим, не тревога. */
  background: #c9c9c7;
  transition: background .15s ease;
}

.channel-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .2);
  transition: transform .15s ease;
}

/* :not(:disabled) в самом правиле, а не отдельным блоком ниже: у
   `.channel-switch:disabled .track` и `.channel-switch[aria-checked] .track`
   одинаковая специфичность, и кто победит, решал бы порядок строк – хрупко.
   У неподключённого канала тумблер обязан выглядеть нейтральным: зелёный
   обещал бы, что ИИ там отвечает, хотя канала ещё нет. */
.channel-switch[aria-checked="true"]:not(:disabled) .channel-switch-track { background: var(--ok); }
.channel-switch[aria-checked="true"]:not(:disabled) .channel-switch-knob { transform: translateX(14px); }

/* Меню канала повторяет меню файла базы знаний – те же .kb-menu-btn
   и .kb-menu внутри, отличается только позиционирующая обёртка. */
.channel-actions { flex: 0 0 auto; display: flex; position: relative; }

/* «Обновить подключение» длиннее пунктов файлового меню и в 168px переносится
   на две строки. Пункт действия должен читаться одной строкой. */
.channel-actions .kb-menu { min-width: 208px; }
.channel-actions .kb-menu-item { white-space: nowrap; }

.channel-details {
  padding: 4px 24px 20px;
  border-top: 1px solid var(--border-2);
}
.channel-details[hidden] { display: none; }
.channel-details .field { margin-top: 14px; }
.channel-details > .status:empty { display: none; }
.channel-details > .status { display: block; margin-top: 12px; }

.channel-status {
  margin-top: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--ivory);
}

.channel-status-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 3px 0;
  font-size: 13px;
}
.channel-status-key { color: var(--text-2); }
.channel-status-val {
  min-width: 0;
  text-align: right;
  color: var(--text);
  overflow-wrap: anywhere;
}
.channel-status-val.is-ok { color: var(--ok); }
.channel-status-val.is-off { color: var(--text-2); }
.channel-status-val.is-error { color: var(--danger); }
.channel-status-val a { color: var(--accent); }

.channel-error {
  margin: 10px 0 0;
  padding: 10px 13px;
  border-radius: var(--r-md);
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  font-size: 13px;
  color: var(--danger);
  overflow-wrap: anywhere;
}

.card-foot-actions { display: flex; align-items: center; gap: 8px; }

/* ============================================================
   Уведомления и модальное окно
   ============================================================ */

/* Плавающая панель сохранения. Держится над контентом внизу экрана, пока
   настройки расходятся с сохранёнными. Ниже панели тостов по z-index, чтобы
   «Сохранено · Отменить» ложилось поверх, а не под неё. */
.savebar {
  position: fixed;
  left: var(--nav-w, 84px);
  right: 0;
  bottom: 0;
  z-index: 55;
  padding: 14px 24px calc(14px + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border-top: 1px solid var(--border);
  box-shadow: rgba(0,0,0,.05) 0 -12px 24px -8px;
  animation: savebar-in .16s ease-out;
}
.savebar[hidden] { display: none; }

.savebar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  max-width: 1180px;
  margin: 0 auto;
}
.savebar-text { font-size: 14.5px; font-weight: 500; min-width: 0; }
.savebar-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }

@keyframes savebar-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.toasts {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  pointer-events: none;
}
/* Панель сохранения занимает низ экрана – тосты поднимаются над ней. */
.toasts.has-savebar { bottom: calc(20px + var(--savebar-h, 62px)); }

/* Тост как в LMS: светлая карточка с hairline в тон состояния, а не тёмная
   плашка. Успех – зелёный, ошибка – rust, нейтральный – белый. */
.toast {
  min-width: 210px;
  max-width: 360px;
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: 13.5px;
  box-shadow: rgba(18,23,34,.04) 0 1px 2px, rgba(18,23,34,.08) 0 8px 20px, rgba(18,23,34,.06) 0 24px 48px;
  animation: toast-in .16s ease-out;
}
.toast.is-error { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.toast.is-ok { background: #edf9f1; border-color: #c6ecd3; color: var(--ok); }

/* Тост с действием кликабелен, поэтому возвращает себе события мыши:
   контейнер их не принимает, иначе он перехватывал бы клики по странице. */
.toast-action-row {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.toast-action-text { min-width: 0; }
.toast-undo {
  flex: 0 0 auto;
  padding: 4px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-button);
  background: var(--surface);
  color: var(--text);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.toast-undo:hover { background: var(--canvas); border-color: var(--faint); }
.toast-undo:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.toast-undo[disabled] { opacity: .55; cursor: default; }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(17, 19, 26, .48);
}

.modal {
  width: 100%;
  max-width: 420px;
  padding: 24px 24px 20px;
  background: var(--surface);
  border-radius: 20px;
  box-shadow: var(--shadow-lg);
}
.modal-title { margin: 0 0 8px; font-size: 20px; font-weight: 600; letter-spacing: -.014em; }
.modal-text { margin: 0 0 20px; font-size: 14.5px; line-height: 1.5; color: var(--text-2); }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }

/* ============================================================
   Адаптив
   ============================================================ */

@media (max-width: 1100px) {
  /* Колонки складываются: промпт сверху, тестовый чат под ним */
  .settings-cols { grid-template-columns: minmax(0, 1fr); }
  .card-col { min-height: 0; }
  /* В одну колонку карточка не тянется сама: задаём ей рабочую высоту, чтобы
     поле промпта осталось высоким и прокручивалось внутри себя. Высоту берём
     от экрана, но не ниже рабочего минимума. */
  .card-col.card-col-prompt { height: auto; min-height: max(700px, calc(100vh - 118px)); }
  /* В одну колонку тестовый чат снова растёт по содержимому: экранной
     высоты на две карточки всё равно не хватит, и страница прокручивается. */
  .settings-cols > .card-col:not(.card-col-prompt) { height: auto; }
  /* В одну колонку плашки и итоги переносятся и занимают больше высоты –
     поле промпта защищаем отдельным минимумом. */
  /* В одну колонку карточка растёт вниз, места хватает: поле остаётся
     полноразмерным, а не ужимается до трёх строк. */
  .prompt-slot-system { min-height: 450px; }
  .prompt-editable { min-height: 384px; }
  .messages-test { min-height: 260px; max-height: 420px; }
}

@media (max-width: 1000px) {
  /* Сайдбар 220px и список чатов 308px вместе съедают половину планшета, и
     переписке остаётся полоса. Обе колонки ужимаются, пункты меню остаются
     подписанными: прятать их в значки на этой ширине незачем. */
  :root { --nav-w: 184px; --list-w: 264px; }
  .nav { padding: 16px 10px; }
  .nav-item { padding: 0 12px; gap: 9px; }
  .nav-label { font-size: 14px; }
  .settings-inner { padding: 26px 20px 56px; }
  .messages { padding: 18px 16px 6px; }
  .composer { padding: 10px 16px 14px; }
}

@media (max-width: 720px) {
  :root { --nav-w: 64px; }

  /* На телефоне меню уезжает вниз (блок в конце файла), здесь остаются только
     размеры, общие для узких экранов. */
  .nav { padding: 8px 6px; gap: 2px; }
  .nav-label { font-size: 10px; letter-spacing: 0; }
  .nav-brand { margin-bottom: 8px; font-size: 14px; }
  .nav-item { min-height: auto; padding: 7px 2px 6px; gap: 3px; }
  .nav-icon { width: 19px; height: 19px; }

  /* Тумблер сжимается вместе с меню: подпись остаётся – без неё непонятно,
     в каком режиме бот, а это единственное место, где режим виден. */
  .nav-switch { margin-bottom: 6px; padding: 7px 2px 6px; gap: 4px; }
  .nav-switch-label { font-size: 10px; }
  .nav-switch-track { width: 30px; height: 17px; }
  .nav-switch-knob { width: 13px; height: 13px; }
  .nav-switch[aria-checked="true"] .nav-switch-knob { transform: translateX(13px); }

  /* Список и переписка показываются по очереди */
  .chat-list {
    flex: 1;
    width: auto;
    border-right: 0;
  }
  .chat-room { display: none; }

  .view-chat.is-room-open .chat-list { display: none; }
  .view-chat.is-room-open .chat-room { display: flex; flex: 1; }

  .btn-back { display: inline-flex; }

  .bubble { max-width: 86%; }
  .btn-send { padding: 0 13px; }

  /* Панель сохранения прижимается к левому краю вслед за узкой навигацией */
  .savebar { left: var(--nav-w); padding-left: 16px; padding-right: 16px; }
}

@media (max-width: 560px) {
  /* Полоса вкладок на узком экране: чуть плотнее, прокрутка остаётся внутри */
  .segmented-tabs { margin-bottom: 16px; }
  .segmented-item { padding: 7px 12px; font-size: 13px; }
  /* Переключатель роли остаётся компактным: правило выше по файлу задано с
     той же специфичностью и иначе перебило бы его. Подпись уходит под
     переключатель – в строку они на 375px не помещаются. */
  .segmented-sm .segmented-item { padding: 5px 10px; font-size: 12.5px; }
  .composer-role { flex-direction: column; align-items: flex-start; gap: 5px; }
  .composer-role-note { font-size: 12px; }

  /* Шапка карточки: заголовок и кнопка в столбик */
  .card-head { flex-direction: column; align-items: stretch; gap: 10px; }
  .card-head .btn { align-self: flex-start; }

  /* Узкий экран: имя переменной сверху, счётчик под ним – иначе они жмутся */
  .var-chip { flex-direction: column; align-items: flex-start; gap: 3px; }
  .var-chip-count { text-align: left; }

  /* Файл базы знаний: имя сверху, переключатель и кнопки под ним */
  .kb-row {
    flex-wrap: wrap;
    align-items: flex-start;
    row-gap: 10px;
  }
  .kb-main {
    order: -1;
    flex: 1 0 100%;
  }
  .kb-name { white-space: normal; }
  .kb-toggle { flex: 1 1 auto; }
  .kb-actions { flex: 0 0 auto; }

  .card-foot { flex-direction: column; align-items: stretch; gap: 10px; }
  .card-foot .btn { width: 100%; }
  .card-foot .status:empty { display: none; }

  .card-foot-actions { flex-direction: column-reverse; align-items: stretch; }

  /* Ключ сверху, значение под ним – иначе длинный username жмётся в угол */
  .channel-status-row { flex-direction: column; gap: 1px; }
  .channel-status-val { text-align: left; }

  /* Строка канала: бейдж состояния уводим из строки – там уже есть та же
     информация словами в подписи под названием, а места на узком экране нет. */
  .channel-row { padding: 4px 12px 4px 6px; gap: 6px; }
  .channel-row .badge { display: none; }

  /* Тумблер остаётся, подпись уходит: на узком экране места нет, а состояние
     читается и по самому переключателю. Но тогда он безымянный, поэтому
     доступное имя обязано жить в aria-label кнопки, а не только в подписи. */
  .channel-switch { padding: 6px 4px; gap: 0; }
  .channel-switch-label { display: none; }

  /* Бейдж на узком экране скрыт, значит режим «отвечаете руками» читается
     только из подписи – а она в одну строку обрезается на середине имени
     бота. Разрешаем две строки: место под строкой канала есть, а потерять
     режим нельзя, иначе «бот молчит» выглядит как поломка. */
  .channel-sub {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .channel-details { padding: 4px 16px 16px; }

  /* Раздел «Рассылка» на телефоне.
     Заголовок карточки складывается в колонку, кнопки растягиваются на
     всю ширину: «Новая рассылка» и «Остановить» на 375px иначе жмутся в
     угол и мажутся мимо пальца. */
  #camp-list-card .card-head,
  #camp-detail-card .card-head { flex-direction: column; align-items: stretch; gap: 10px; }
  #camp-list-card .card-head > .btn { width: 100%; }
  .camp-detail-actions > .btn { flex: 1; }

  /* У формы «Отмена» остаётся сбоку от заголовка, а подзаголовок уходит
     совсем: в столбик они съедали треть экрана над самой формой, ради
     которой её и открыли. Это выход из формы, а не главное действие. */
  #camp-form-card .card-head { align-items: flex-start; gap: 10px; }
  #camp-form-card .card-head .card-sub { display: none; }
  #camp-form-card .card-head > .btn { flex: none; }

  /* Строка кампании: кнопка «Остановить» уходит под название, а не
     отжимает его в одну букву. */
  .camp-item { flex-wrap: wrap; }
  .camp-item > .btn { width: 100%; }

  .camp-channel { flex: 1; justify-content: center; }
  /* На телефоне два поля рядом не помещаются: каждое сжималось бы до
     нечитаемой ширины, и «в одну строку» переставало экономить место. */
  .camp-row { flex-direction: column; gap: 10px; }
  .camp-row > .field { width: 100%; }
  .camp-aud-head { align-items: stretch; gap: 6px; }
  .camp-aud-tools { margin-left: 0; }
  .camp-aud-search { max-width: none; flex: 1 1 auto; }
  /* На телефоне экран короче, а форма та же: список ужимается сильнее,
     иначе он один съедает треть доступной высоты. */
  .camp-aud-list { max-height: 156px; }
  .camp-textarea { min-height: 76px; height: 76px; }
  /* Подписи фильтров выравниваются по нижнему краю: «Последнее сообщение от»
     переносится в две строки, и её поле уезжало ниже соседнего «до». */
  /* На телефоне подпись занимает свою строку, а два поля дат делят
     следующую: иначе тире и второе поле переносились вразнобой. */
  .camp-filter-group { flex-wrap: wrap; width: 100%; }
  .camp-filter-label { width: 100%; }
  .camp-filter-group input { flex: 1 1 0; width: auto; min-width: 0; }
  .camp-filters-reset { margin-left: 0; }
  .auto-cols { grid-template-columns: 1fr; gap: 16px; }
  .auto-media { grid-template-columns: repeat(3, minmax(0, 1fr)); max-height: 220px; }
  .auto-card-button { grid-template-columns: 1fr 26px; }
  .auto-card-button [data-btn-field="url"] { grid-column: 1 / -1; }
  .auto-item { flex-wrap: wrap; }
  .auto-item .channel-switch { order: 3; }
  /* Второй столбец уходит под первый: рядом они на 375px нечитаемы. */
  .camp-msg-cols.is-split { grid-template-columns: 1fr; }
  .camp-mode-head { align-items: stretch; }
  .camp-pace, .camp-at { max-width: none; }
  .camp-pace-field { width: 100%; }
  .camp-target-right { align-self: flex-start; }
  .camp-when { gap: 12px; }
  .camp-counters { gap: 8px; }

  /* Строка получателя: статус переезжает под имя, иначе длинная причина
     («Окно 24 часа закрыто…») выдавливает имя за край. */
  .camp-target { flex-direction: column; gap: 2px; }
  .camp-target-status { text-align: left; }

  /* Панель сохранения: текст сверху, кнопки в ряд под ним. «Сохранить» справа
     остаётся под большим пальцем. */
  .savebar-inner { flex-direction: column; align-items: stretch; gap: 9px; }
  .savebar-actions { justify-content: flex-end; }
  .savebar-actions .btn { flex: 1 1 0; }

  /* Меню файла раскрывается вправо от края экрана – прижимаем к правому краю
     строки и ограничиваем ширину, иначе оно вылезает за вьюпорт. */
  .kb-menu { min-width: 150px; max-width: calc(100vw - 40px); }

  /* Селект модели в итогах занимает всю ширину строки */
  .model-inline { align-items: stretch; }
  .model-inline .select { max-width: none; flex: 1 1 100%; }

  /* Тост с кнопкой отмены не должен упираться в края */
  .toasts { left: 12px; right: 12px; align-items: stretch; }
  .toast { max-width: none; }
}

@media (max-width: 420px) {
  .pane-head, .chat-head { padding-left: 14px; padding-right: 12px; }
  .settings-inner { padding: 20px 14px 48px; }
  .card-head, .card-body, .card-foot, .kb-row { padding-left: 14px; padding-right: 14px; }
  .kb-editor { padding-left: 14px; padding-right: 14px; }
  .messages-test, .composer-test, .test-note { padding-left: 14px; padding-right: 14px; }
  .prompt-compose { padding-left: 14px; padding-right: 14px; }
}

/* ============================================================
   Телефон (≤720px): навигация уезжает вниз, контент во всю ширину.
   Блок стоит последним и намеренно перекрывает правила выше: на узком
   экране боковое меню в 64px съедало шестую часть ширины, а нижняя
   полоса – привычный для телефона паттерн и не мешает большому пальцу.
   ============================================================ */
@media (max-width: 720px) {
  :root { --bottom-nav-h: 62px; }

  .app {
    flex-direction: column;
    /* dvh учитывает адресную строку мобильного браузера; vh на iOS
       оставлял композер под панелью Safari. */
    height: 100vh;
    height: 100dvh;
  }
  .view { flex: 1; min-height: 0; }

  .nav {
    order: 2;
    flex: 0 0 auto;
    width: 100%;
    flex-direction: row;
    align-items: stretch;
    justify-content: space-around;
    gap: 2px;
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom, 0px));
    border-right: 0;
    border-top: 1px solid var(--border);
  }
  .nav-brand, .nav-spacer { display: none; }
  /* В нижней полосе значок снова встаёт над подписью: в строку семь пунктов
     на 375px не помещаются. Подложка тумблера и рамка «Выхода» тоже снимаются –
     в горизонтальном ряду они читались бы чужеродными плашками. */
  .nav-item, .nav-switch {
    flex: 1 1 0;
    min-width: 0;
    min-height: auto;
    margin: 0;
    padding: 7px 2px 5px;
    gap: 4px;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
  .nav-switch { border: 0; background: transparent; box-shadow: none; border-radius: var(--r-md); }
  .nav-item-quiet { border-top: 0; border-radius: var(--r-md); }
  /* Восемь пунктов на 375px (с «Публикациями»): кегль меньше, и подпись
     переносится по мягкому дефису внутри своей доли ширины, а не режется
     многоточием – «Расс…» и «Отвеч…» не читались. hyphens здесь обязателен:
     базовое правило выключает переносы под широкую колонку сайдбара, и без
     возврата слово вылезало на «Выход». Без обрезки: она отсекала хвост
     второй строки, и «зация» превращалась в «заци». */
  .nav-label, .nav-switch-label, .nav-label-wrap {
    font-size: 9.5px;
    letter-spacing: -.01em;
    line-height: 1.05;
    max-width: 100%;
    text-align: center;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    hyphens: manual;
  }
  .nav-icon { width: 22px; height: 22px; }
  .nav-switch-track { width: 34px; height: 19px; }
  .nav-switch-knob { width: 15px; height: 15px; }
  .nav-switch[aria-checked="true"] .nav-switch-knob { transform: translateX(15px); }

  /* Панель сохранения и тосты живут над нижним меню, а не под ним. */
  .savebar {
    left: 0;
    bottom: calc(var(--bottom-nav-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: 12px;
  }
  .toasts { bottom: calc(var(--bottom-nav-h) + 12px + env(safe-area-inset-bottom, 0px)); }
  .toasts.has-savebar { bottom: calc(var(--bottom-nav-h) + 12px + var(--savebar-h, 62px) + env(safe-area-inset-bottom, 0px)); }

  /* Поля ввода не меньше 16px: иначе iPhone приближает страницу при фокусе. */
  .input, .textarea, .composer-input, select.select { font-size: 16px; }

  /* Композер чата: меньше воздуха, подсказка короче по высоте, поле выше. */
  .composer { padding: 8px 12px 10px; gap: 6px; }
  .composer-role-note { font-size: 11.5px; line-height: 1.3; }
  .composer-input { min-height: 44px; }
  .btn-send { height: 44px; }
  .messages { padding: 14px 12px 6px; }
  .bubble { max-width: 88%; }

  /* Настройки: заголовок компактнее, вкладки в две строки вместо скрытой
     горизонтальной прокрутки – так видно все пять сразу. */
  .settings-inner { padding: 16px 14px calc(40px + env(safe-area-inset-bottom, 0px)); }
  .page-head { margin-bottom: 12px; }
  .page-title { font-size: 22px; }
  .page-sub { font-size: 13px; }
  .segmented-tabs {
    width: 100%;
    flex-wrap: wrap;
    overflow: visible;
    margin-bottom: 14px;
  }
  .segmented-tabs .segmented-item { flex: 1 1 auto; text-align: center; }

  /* Список чатов: чуть крупнее строки под палец. */
  .pane-head { padding-left: 14px; padding-right: 12px; }
  .chat-item { padding-top: 12px; padding-bottom: 12px; }
}

/* iOS Safari: документ прокручивается целиком, даже когда контент влезает в
   экран – так работают его выезжающие панели. Для чата это значит, что при
   попытке пролистать ленту уезжает весь интерфейс вместе с меню и
   композером. Стандартное решение: на телефоне документу нечего
   прокручивать – body зафиксирован, скроллят только внутренние ленты. */
@media (max-width: 720px) {
  html, body {
    height: 100%;
    overflow: hidden;
    overscroll-behavior: none;
  }
  body {
    position: fixed;
    inset: 0;
    width: 100%;
  }
  .app { height: 100%; }
  .login-screen { height: 100%; overflow-y: auto; }

  /* Ленты прокручиваются сами и не передают скролл наружу. */
  .messages, .chat-list-body, .settings-scroll {
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* Тап без серой вспышки, как в нативных приложениях. */
  * { -webkit-tap-highlight-color: transparent; }
}

/* iPhone приближает страницу при фокусе в поле со шрифтом меньше 16px.
   Правило выше задавало 16px, но у отдельных полей (содержимое базы знаний,
   поле промпта, моно-поля) свой размер стоит позже по файлу и перебивает.
   Этот блок последний, поэтому побеждает он. touch-action убирает и зум по
   двойному тапу, чтобы прокрутка не превращалась в увеличение. */
@media (max-width: 720px) {
  .input, .textarea, .textarea.mono, .kb-editor .kb-content, .prompt-editable,
  .composer-input, .camp-textarea, select.select, input, textarea, select {
    font-size: 16px !important;
  }
  html, body, .app { touch-action: manipulation; }
}

/* ============================================================
   Вкладка «Журнал»
   ============================================================ */

/* Плитки очереди над списком автоматизаций: те же .camp-counter, только
   отступ снизу чуть больше, чтобы плитки не липли к подсказке про Instagram. */
.auto-queue { margin-bottom: 16px; }

/* Фильтры в одну строку; поиск забирает остаток. На узком экране переносятся. */
.events-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 14px 24px;
  border-bottom: 1px solid var(--border-2);
}
.events-select { flex: 0 1 180px; min-width: 140px; }
.events-search { flex: 1 1 200px; min-width: 160px; }

/* Таблица шире карточки прокручивается внутри своего контейнера, а не
   растягивает страницу: у .card стоит overflow: hidden ради углов. */
.events-scroll { overflow-x: auto; }

.events-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: 13px;
}
.events-table th {
  padding: 8px 12px;
  text-align: left;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--text-3);
  border-bottom: 1px solid var(--border-2);
  white-space: nowrap;
}
.events-table th:first-child, .events-table td:first-child { padding-left: 24px; }
.events-table th:last-child, .events-table td:last-child { padding-right: 24px; }
.events-table td {
  padding: 9px 12px;
  vertical-align: top;
  border-bottom: 1px solid var(--border-2);
  line-height: 1.45;
}
.events-table tbody tr:last-child td { border-bottom: 0; }
.ev-row:hover td { background: var(--ivory); }

/* Ширины колонок: время и уровень фиксированы, сообщение забирает остаток
   и переносится по словам. Без этого длинный текст ошибки Meta раздвигал
   бы таблицу за край карточки. */
.ev-col-time { width: 84px; }
.ev-col-level { width: 92px; }
.ev-col-area { width: 130px; }

.ev-time { color: var(--text-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
.ev-area { color: var(--text-2); overflow-wrap: anywhere; }
.ev-msg { overflow-wrap: anywhere; }
.ev-msg-text { color: var(--text); }
.ev-ctx-btn { font-size: 12.5px; white-space: nowrap; }

/* Уровень: цветная точка плюс слово. Цвет один не остаётся: без слова
   различить серый и жёлтый маркер на бледном фоне трудно. */
.ev-level { white-space: nowrap; color: var(--text-2); }
.ev-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 7px;
  border-radius: 50%;
  background: var(--faint);
  vertical-align: 1px;
}
.ev-row.is-warn .ev-dot { background: #d99a1c; }
.ev-row.is-warn .ev-level { color: #8a5a00; }
.ev-row.is-error .ev-dot { background: var(--danger); }
.ev-row.is-error .ev-level { color: var(--danger); }

/* Раскрытый контекст: на всю ширину, с переносом длинных строк, чтобы
   тело ответа Meta не уезжало вправо. */
.ev-ctx td { padding: 0 24px 12px; background: var(--ivory); border-bottom: 1px solid var(--border-2); }
.ev-ctx-pre {
  margin: 0;
  padding: 10px 12px;
  max-height: 320px;
  overflow: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
}

.events-foot .status:empty { display: none; }
.events-foot:has(.status:empty) { justify-content: flex-end; }

@media (max-width: 560px) {
  /* На 375px четыре колонки не помещаются: строка складывается в карточку –
     время, уровень и область в одну служебную строку, сообщение под ней. */
  .events-table thead { display: none; }
  .events-table, .events-table tbody, .events-table tr, .events-table td { display: block; width: auto; }
  .ev-row { padding: 10px 16px; border-bottom: 1px solid var(--border-2); }
  .ev-row td { display: inline; padding: 0; border: 0; line-height: 1.5; }
  .ev-row .ev-time::after, .ev-row .ev-level::after { content: ' · '; color: var(--text-3); }
  .ev-row .ev-msg { display: block; margin-top: 2px; }
  .ev-row:hover td { background: transparent; }
  .ev-ctx td { padding: 0 16px 12px; }
  .events-filters { padding: 12px 16px; }
  .events-select { flex: 1 1 45%; min-width: 0; }
  .events-search { flex: 1 1 100%; }
  .events-table th:first-child, .events-table td:first-child,
  .events-table th:last-child, .events-table td:last-child { padding-left: 0; padding-right: 0; }
}

/* Узкий экран раздела «Публикации»: сетка в две колонки, кнопки во всю
   ширину, слайд и миниатюры карточки на всю строку. Блок в конце файла,
   чтобы не перебивать порядок узкоэкранных правил соседних разделов. */
@media (max-width: 560px) {
  .pub-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  /* Кнопки шапки списка растягиваются на всю ширину, как у рассылки. */
  #pub-list-card .card-head .btn, #pub-detail-card .card-head .btn { flex: 1 1 auto; }
  .pub-detail-actions .btn, #pub-list-card .camp-detail-actions .btn { align-self: stretch; }
  .pub-empty-actions { flex-direction: column; align-items: stretch; }
  .pub-foot-left { flex-direction: column; align-items: stretch; }
  .pub-foot-left .btn { width: 100%; }
  /* Макет поста на телефоне занимает всю ширину карточки: телефонная
     ширина и есть его родная среда, ограничение 470px тут только сузило бы. */
  .pub-post, .pub-compose-meta { max-width: none; }
  /* Комментарий переносится под ник, а не висячим отступом от него: на узком
     экране на строку рядом с ником влезает пара слов, и текст читался
     лестницей. Ник остаётся первой строкой, текст идёт во всю ширину. */
  /* На узком экране ник остаётся в потоке текста, обтекание сохраняется. */
  .pub-post-comment-name { float: left; }
  /* Строка принятого файла узкая: «Убрать» на всю ширину разорвал бы её
     на три этажа внутри поста. */
  .pub-item { flex-wrap: nowrap; }
  .pub-detail-item, .pub-detail-thumb { width: 100%; }
  .pub-detail-item-meta { white-space: normal; }
}

/* ============================================================
   Раздел «Аналитика»
   ============================================================
   Плитки, таблицы и графики. Графики нарисованы inline SVG: в проекте нет
   сборки и внешних зависимостей на фронте, и библиотека ради трёх графиков
   меняла бы это ради мелочи. */

.an-head-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Строка свежести в шапке: «Обновлено 5 мин назад · Обновить». Подпись
   тихая, кнопка обычная призрачная; точка-разделитель рисуется CSS, чтобы
   при скрытой подписи не оставалось висячего знака. */
.an-fresh { display: inline-flex; align-items: center; gap: 8px; }
.an-fresh-text { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
.an-fresh-text::after { content: ' ·'; }
.an-fresh-text.is-warn { color: var(--danger); }

/* Полоса вкладок раздела. Своя, а не общая с «Промптом»: там вкладки правят
   один документ, здесь переключают разные наборы данных. */
.an-tabs {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--border);
  margin-bottom: 18px;
}

.an-tab {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-dim, #5b6472);
  font: inherit;
  font-size: 14px;
  padding: 9px 14px;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.an-tab:hover { background: var(--canvas); color: var(--text); }

.an-tab.is-active {
  color: var(--accent-dark);
  border-bottom-color: var(--accent);
  font-weight: 500;
}

.an-h3 {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  margin: 24px 0 12px;
}

.an-h4 {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin: 0 0 10px;
}

.an-note {
  font-size: 13px;
  color: var(--text-dim, #5b6472);
  margin: 8px 0;
}

.an-freshness {
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

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

/* Плитки: число крупно, дельта под ним. */
.an-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.an-tile {
  background: var(--ivory);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 14px 16px;
}

.an-tile-title {
  font-size: 12.5px;
  color: var(--text-dim, #5b6472);
  margin-bottom: 6px;
}

.an-tile-value {
  font-size: 24px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.15;
  /* Длинные числа не должны распирать плитку на узком экране. */
  overflow-wrap: anywhere;
}

.an-tile-delta { margin-top: 6px; font-size: 12px; }

.an-delta-up { color: var(--ok); }
.an-delta-down { color: var(--danger); }
.an-delta-flat, .an-delta-none { color: var(--faint); }

/* Простые показатели без дельты. */
.an-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
}

.an-stat {
  background: var(--ivory);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.an-stat-title { font-size: 12.5px; color: var(--text-dim, #5b6472); }
.an-stat-value { font-size: 19px; font-weight: 600; margin-top: 4px; overflow-wrap: anywhere; }

/* Графики. */
.an-chart-wrap { margin-top: 20px; }

.an-chart {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 12px;
}

.an-svg { width: 100%; height: auto; display: block; }

.an-grid-line { stroke: var(--border); stroke-width: 1; }
.an-axis { fill: var(--faint); font-size: 10px; }

.an-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.an-line-0 { stroke: var(--accent); }
.an-line-1 { stroke: var(--violet); }
circle.an-line-0 { fill: var(--accent); stroke: none; }
circle.an-line-1 { fill: var(--violet); stroke: none; }

.an-legend {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 10px;
  font-size: 12.5px;
  color: var(--text-dim, #5b6472);
}

.an-legend-item { display: inline-flex; align-items: center; gap: 6px; }

.an-legend-dot {
  width: 10px;
  height: 3px;
  border-radius: 2px;
  display: inline-block;
}

.an-legend-dot.an-line-0 { background: var(--accent); }
.an-legend-dot.an-line-1 { background: var(--violet); }

/* «Требует внимания»: каждая строка ведёт на свою вкладку. */
.an-attention { margin-top: 24px; }

.an-alert {
  display: block;
  width: 100%;
  text-align: left;
  appearance: none;
  font: inherit;
  font-size: 13.5px;
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  color: var(--danger);
  border-radius: var(--r-sm);
  padding: 10px 14px;
  margin-bottom: 8px;
  cursor: pointer;
}

.an-alert:hover { background: #ffeceb; }

.an-ok {
  font-size: 13.5px;
  color: var(--ok);
  background: #f1f8f5;
  border: 1px solid #cfe7dc;
  border-radius: var(--r-sm);
  padding: 10px 14px;
}

/* Таблицы. На узком экране прокручиваются вбок сами, страница не едет. */
.an-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
}

.an-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
  min-width: 560px;
}

.an-table th,
.an-table td {
  padding: 9px 12px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.an-table th {
  background: var(--ivory);
  font-weight: 600;
  font-size: 12.5px;
  color: var(--text-dim, #5b6472);
}

.an-table tbody tr:last-child td { border-bottom: 0; }
.an-table tbody tr:hover { background: var(--canvas); }
.an-table .an-num { text-align: right; font-variant-numeric: tabular-nums; }

/* Первая колонка таблиц может быть длинной подписью: ей перенос разрешён. */
.an-table td:first-child { white-space: normal; max-width: 340px; }

/* Демография столбиками. */
.an-bars {
  background: var(--ivory);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 14px 16px;
}

.an-bar-row {
  display: grid;
  grid-template-columns: minmax(70px, 1fr) 2fr auto;
  align-items: center;
  gap: 10px;
  margin-bottom: 7px;
  font-size: 12.5px;
}

.an-bar-name {
  color: var(--text-dim, #5b6472);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.an-bar-track {
  background: var(--border);
  border-radius: var(--r-pill);
  height: 7px;
  overflow: hidden;
}

.an-bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: var(--r-pill);
}

.an-bar-value { font-variant-numeric: tabular-nums; color: var(--text); }

.an-filters { margin-bottom: 14px; }

@media (max-width: 700px) {
  /* Период и «Обновить» уезжают под заголовок, а не жмут его в угол. */
  .an-head-actions { width: 100%; }
  .an-tiles { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
  .an-tile-value { font-size: 21px; }
  .an-bar-row { grid-template-columns: minmax(60px, 1fr) 1.4fr auto; }
}
