:root {
  /* Основа пока прежняя, телеграмовская.

     Смальта меняет её на более глубокую и холодную, но в одиночку эта правка
     НИЧЕГО не даст: applyTheme() в telegram.js перезаписывает --bg, --panel,
     --text и остальные прямо на элементе цветами темы Telegram, а inline-стиль
     сильнее таблицы. То есть внутри Telegram новая палитра молча не применится,
     а в браузере применится — и один и тот же продукт станет выглядеть
     по-разному. Поэтому смена основы идёт отдельным решением, вместе с ответом
     на вопрос, следуем ли мы дальше за темой Telegram.

     Цвета ролей ниже от палитры НЕ зависят и работают уже сейчас: по кромке
     плитки видно, где стоит бот, не читая подпись. Владелец сказал прямо:
     «имя не всегда запомнится», а три его бота названы дословно «Incognito». */
  --bg: #17212b;
  --panel: #232e3c;
  --sunken: #1b2431;
  --border: #101921;
  --divider: #2c3949;

  /* Роль плитки. Читается как свет, поэтому цвета яркие, а не приглушённые. */
  --role-channel: #4C8DFF;
  --role-group: #F5B942;
  --role-standby: #A98BFF;
  --role-none: #33455F;
  --text: #ffffff;
  --hint: #708499;
  --dim: #5a6b7d;
  --accent: #5288c1;
  /* Надпись на акцентной кнопке. Отдельно от --text: тема Telegram может
     прислать светлый акцент, и тогда наш светлый текст на нём исчезает. */
  --accent-text: #fff;
  /* Акцент как цвет ТЕКСТА на фоне страницы. Отдельно от заливки: в чужой
     теме кнопка бывает того же цвета, что и фон, и подпись на нём пропадает. */
  --accent-on-bg: #5288c1;
  --accent-soft: #2b5278;
  --link: #62bcf9;
  --green: #4dcd5e;
  --red: #e8555a;
  --orange: #e8a33d;
  --purple: #9b7ede;
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", system-ui, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  font-size: 15px;
  line-height: 1.45;
  /* Ширину, а не только высоту — иначе широкие таблицы/строки распирают экран вбок. */
  overflow-x: hidden;
}

a { color: var(--link); }

/* label тоже бывает кнопкой: выбор файла нельзя открыть из кода, поэтому
   «Загрузить» — это <label> поверх скрытого поля. Без него такая кнопка
   рисовалась голым текстом без подложки.

   Ссылка — тем более: переход на другой экран это <a>, а не кнопка, и без
   этой строки она получала синий текст без подложки. Замерено: рядом с
   настоящей кнопкой такая пара читается как съехавшая вёрстка, хотя
   геометрия у них совпадает до пикселя. */
button, label.btn-small, label.btn-primary, a.btn-small, a.btn-primary {
  font: inherit;
  cursor: pointer;
  border: none;
  border-radius: 11px;
  padding: 10px 16px;
  /* Не var(--panel): обычная кнопка почти всегда лежит на панели того же
     цвета и на нём просто исчезает — на sunken она остаётся видимой чипсой. */
  background: var(--sunken);
  color: var(--text);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: filter 0.12s ease, transform 0.08s ease;
}
button:hover:not(:disabled) { filter: brightness(1.18); }
button:active:not(:disabled) { filter: brightness(0.88); transform: scale(0.97); }
button:disabled { opacity: 0.45; cursor: default; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent-on-bg);
  outline-offset: 2px;
}

/* Селектор с тегами намеренно: базовое правило кнопок перечисляет
   a.btn-primary и label.btn-primary, а значит перебивает голый .btn-primary
   по весу. Из-за этого ссылка и label, помеченные главными, главными
   не выглядели — фон у них оставался обычным. */
button.btn-primary,
a.btn-primary,
label.btn-primary,
.btn-primary {
  background: var(--accent);
  color: var(--accent-text);
  font-weight: 600;
}
/* Кнопка удаления в покое — тихая.

   Замерено на живом экране: в редакторе команды красный ✕ повторяется до пяти
   раз на блок и оказывается самым ярким пятном — ярче текста самого сообщения.
   Глаз ведёт к уничтожению, а не к содержанию. Красный никуда не делся, он
   включается под пальцем и в окне подтверждения, где и нужен. */
.btn-danger {
  background: transparent;
  color: var(--dim);
  box-shadow: inset 0 0 0 1px var(--divider);
  transition: color .12s, box-shadow .12s;
}

.btn-danger:hover,
.btn-danger:focus-visible,
.btn-danger:active {
  color: var(--red);
  box-shadow: inset 0 0 0 1px #5c3538;
}

/* Исключение — «Опасная зона»: там красный не пятно, а подпись раздела.
   Раздел так и называется, и кнопка в нём одна. */
.btn-danger.btn-loud { color: var(--red); box-shadow: inset 0 0 0 1px #5c3538; }
.btn-small { padding: 7px 12px; font-size: 13px; border-radius: 9px; min-height: 36px; }

/* Подчёркивание и цвет ссылки кнопке не нужны — иначе каждый такой переход
   приходится глушить стилем прямо в разметке, и однажды об этом забывают. */
/* :not(.btn-primary) не придирка: у главной ссылки оба класса, вес правил
   одинаковый, и побеждает то, что ниже. Без исключения обычный цвет текста
   ложился поверх цвета надписи на заливке — и на светлом акценте надпись
   пропадала. */
a.btn-small:not(.btn-primary) { text-decoration: none; color: var(--text); }
a.btn-primary { text-decoration: none; }

input, select, textarea {
  font: inherit;
  width: 100%;
  padding: 11px 13px;
  min-height: 44px;
  border-radius: 11px;
  border: 1px solid var(--divider);
  background: var(--sunken);
  color: var(--text);
  outline: none;
  transition: border-color 0.12s ease;
}
textarea { min-height: 44px; line-height: 1.45; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
input::placeholder, textarea::placeholder { color: var(--dim); }
input:disabled, select:disabled, textarea:disabled { opacity: 0.5; }

.app { max-width: 720px; margin: 0 auto; padding: 0 14px calc(60px + env(safe-area-inset-bottom, 0px)); }

/* ── Шапка ───────────────────────────────────────────────────────────────── */

header.top {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 0 10px;
}
/* Знак — та же мозаика, что на аватарке бота: тёмная плашка, белые плитки
   и одна акцентная. Сплошной синий квадрат спорил с синей главной кнопкой. */
.mark {
  width: 36px; height: 36px; border-radius: 10px; background: var(--sunken);
  box-shadow: inset 0 0 0 1px var(--divider);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.who { display: flex; flex-direction: column; gap: 4px; flex-grow: 1; min-width: 0; }
.who b { font-size: 17px; font-weight: 700; letter-spacing: -0.2px; }
.who span {
  font-size: 13px; color: var(--hint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
header.top .btn-small { flex-shrink: 0; }

nav.tabs { display: flex; align-items: center; gap: 8px; padding-bottom: 13px; }
nav.tabs a {
  text-decoration: none;
  background: var(--panel);
  color: var(--hint);
  font-size: 13px;
  font-weight: 600;
  padding: 9px 16px;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  border-radius: 10px;
  transition: filter 0.12s ease;
}
nav.tabs a:active { filter: brightness(0.88); }
nav.tabs a.on { background: var(--accent); color: var(--accent-text); font-weight: 600; }

/* ── Карточки ────────────────────────────────────────────────────────────── */

.stack { display: flex; flex-direction: column; gap: 12px; }

/* Фильтр списка ботов. Кнопки те же, что у периодов в статистике:
   заводить ради этого второй вид «чипа» незачем. */
.bot-filter { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }

/* Заголовок группы ботов: каналы, группы, нигде не стоят.
   Отступ сверху больше, чем снизу, намеренно — так заголовок читается
   как начало СВОЕЙ группы, а не как подпись к предыдущей. */
.group-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin: 10px 0 -4px;
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--dim);
}
.group-head:first-child { margin-top: 0; }
.group-head span { font-variant-numeric: tabular-nums; }

/* Дублёр — под своим основным, а не рядом с ним.
   Полоска слева и отступ говорят «этот подчинён верхнему» без единого слова:
   на живых данных три бота назывались одинаково и стояли как три равных. */
.nest { padding-left: 22px; position: relative; }
.nest::before {
  content: "";
  position: absolute;
  left: 9px; top: -12px; bottom: 14px;
  border-left: 2px solid var(--divider);
  border-bottom: 2px solid var(--divider);
  width: 9px;
  border-bottom-left-radius: 8px;
}
.card.nested { background: var(--sunken); }

/* Имя бота — В ОДНУ строку.
   Замерено на снимке живого списка: «🫦You might wanna see this…» переносилось
   на вторую строку, точка состояния уезжала под имя, и карточка становилась
   выше соседних — список переставал читаться столбиком. Глобально .name
   переносить НАДО (в дереве сценария длинные названия команд читают целиком),
   поэтому правило только здесь. */
.bot-card .row { flex-wrap: nowrap; }
.bot-card .name,
.bot-card .sub {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

/* Плитка смальты.

   Не заливка, а стекло: сверху светлее, снизу темнее, по верхней грани —
   волосяной блик. Разница видна не в описании, а на списке из восьми штук:
   плоские прямоугольники сливаются в полосу, стеклянные читаются штуками. */
.card {
  background: linear-gradient(180deg, var(--panel), var(--sunken));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.055);
  border-radius: 15px;
  padding: 14px;
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: inherit;
  transition: filter 0.12s ease;
}
a.card:active, a.card:hover { filter: brightness(1.12); }
.card.bad {
  background: linear-gradient(180deg, #2E2530, #241D26);
  box-shadow: inset 0 0 0 1px #5c3538;
}
.card.off { background: var(--sunken); box-shadow: none; }

/* ── Кромка роли ───────────────────────────────────────────────────────────

   Светящаяся полоса слева у карточки бота. Цвет говорит, ГДЕ бот стоит:
   синий — каналы, золотой — группы, лиловый — дублёр, серый — нигде.

   Зачем: у владельца три бота названы дословно «Incognito», а имена
   остальных начинаются с эмодзи. Различить их по буквам нельзя, а по цвету
   и группе — можно, не читая вовсе.

   Псевдоэлемент, а не рамка: рамка съедает внутренний отступ и сдвигает
   содержимое, из-за чего строки с кромкой и без неё перестают стоять
   в одном столбце. */
.bot-card { position: relative; overflow: hidden; }

.bot-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 3px;
  border-radius: 0 3px 3px 0;
  background: var(--role-none);
}

.bot-card.role-channels::before {
  background: var(--role-channel);
  box-shadow: 0 0 10px rgba(76, 141, 255, 0.55);
}

.bot-card.role-groups::before {
  background: var(--role-group);
  box-shadow: 0 0 10px rgba(245, 185, 66, 0.5);
}

/* Стоит и там, и там — кромка показывает это буквально, двумя цветами. */
.bot-card.role-both::before {
  background: linear-gradient(180deg, var(--role-channel) 0%, var(--role-channel) 45%,
                                      var(--role-group) 55%, var(--role-group) 100%);
  box-shadow: 0 0 10px rgba(76, 141, 255, 0.4);
}

.bot-card.role-standby::before { background: var(--role-standby); }

/* Выключенный бот не светится: свет — признак жизни, и гореть у него нечему. */
.bot-card.off::before { background: var(--role-none); box-shadow: none; }

.ava {
  width: 44px; height: 44px; border-radius: 13px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 700; color: #17212b;
}

.grow { flex-grow: 1; min-width: 0; }
.row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 8px; }
.row.wrap { flex-wrap: wrap; }
/* Внутри строки текстовые/полевые элементы должны иметь право сжиматься —
   иначе длинный текст (regex-триггер, длинное имя) распирает ряд и вылезает
   за экран, даже если у ряда есть перенос. */
.row > input, .row > select, .row > textarea,
.row > span, .row > a, .row > .name, .row > .sub, .row > .mono {
  min-width: 0;
}
.name { font-size: 15px; font-weight: 600; overflow-wrap: anywhere; }
.sub { font-size: 13px; color: var(--hint); overflow: hidden; text-overflow: ellipsis; }
.dot { width: 7px; height: 7px; border-radius: 4px; flex-shrink: 0; }

/* Тумблер: показывает СОСТОЯНИЕ, а не предлагает действие. Кнопка с текстом
   «да» на его месте читалась как «нажать да», и было неясно, включено сейчас
   или включится после нажатия. */
.switch {
  width: 46px; min-height: 28px; height: 28px; padding: 3px;
  border-radius: 999px; background: var(--sunken);
  display: inline-flex; align-items: center; justify-content: flex-start;
  flex-shrink: 0; transition: background 0.15s ease;
}
.switch > span {
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--dim); transition: transform 0.15s ease, background 0.15s ease;
}
.switch.on { background: var(--accent-soft); justify-content: flex-end; }
.switch.on > span { background: var(--accent); }

/* Нижние разделы бота.

   Стоят там, куда дотягивается палец: между «Сценарием», «Рассылками» и
   «Статистикой» одного бота ходят часто, и раньше это стоило двух касаний —
   «назад» и потом плитка.

   Панель прижата к низу и не уезжает при прокрутке, поэтому содержимому
   отдаётся отступ снизу — иначе последняя кнопка экрана оказывалась
   под ней и не нажималась. */
.bottom-tabs {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  display: flex;
  background: var(--panel);
  border-top: 1px solid var(--divider);
  padding-bottom: env(safe-area-inset-bottom, 0);
  z-index: 40;
}
.bottom-tabs a {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 8px 2px 7px;
  color: var(--dim);
  text-decoration: none;
  font-size: 11px;
  /* Подпись обязана поместиться целиком: «Статистика» в две строки
     ломает высоту всей панели. */
  white-space: nowrap;
}
.bottom-tabs a.on { color: var(--accent-on-bg); }
body:has(.bottom-tabs) #app { padding-bottom: 78px; }

/* Родные кнопки Telegram лежат поверх вкладок — поднимаем вкладки над ними.

   Класс ставит telegram.js и только тогда, когда ЗАМЕРИЛ перекрытие: если
   Telegram сжал окно под свои кнопки сам, класса не будет и полоса
   останется внизу, как была.

   68 точек — высота ряда родных кнопок вместе с их полями. «Сохранить»
   и «Проверить» стоят в один ряд (position: left), поэтому ряд один. */
body.tg-buttons .bottom-tabs { bottom: 68px; }
body.tg-buttons:has(.bottom-tabs) #app { padding-bottom: 150px; }

/* ── Широкий экран: нижние вкладки становятся боковой колонкой ────────────

   Та же разметка, другая раскладка. Ни один экран не переписан: вкладки как
   были ссылками в <nav>, так и остались — меняется только то, где этот <nav>
   стоит и как выглядит.

   Зачем: на мониторе всё жило в полосе 720 точек посреди экрана, а вкладки
   оставались внизу, как на телефоне. Владелец растянул окно и не увидел
   ничего нового — справедливо.

   Порог 1000, а не 900: на 900 уже стоит предпросмотр сценария шириной 320,
   и колонка слева отобрала бы у редактора последнее место. */
@media (min-width: 1000px) {
  /* Ширина — ВСЕМ экранам, а не только тем, где есть боковая колонка.

     Иначе список ботов и «Люди» остаются полосой 720 посреди монитора, и
     переход от бота к списку выглядит как скачок: только что было широко,
     стало узко. */
  #app { max-width: 1180px; }

  .bottom-tabs {
    right: auto;
    top: 0;
    width: 236px;
    flex-direction: column;
    justify-content: flex-start;
    gap: 2px;
    padding: 16px 12px;
    background: var(--sunken);
    border-top: 0;
    border-right: 1px solid var(--divider);
  }

  .bottom-tabs a {
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    padding: 11px 13px;
    border-radius: 11px;
    font-size: 14.5px;
  }

  /* Выбранный раздел — плашкой, а не одним цветом подписи: в столбце из шести
     строк цвет текста читается хуже, чем подложка. */
  .bottom-tabs a.on {
    background: var(--panel);
    color: var(--text);
    box-shadow: inset 2px 0 0 var(--accent-on-bg);
  }

  .bottom-tabs a:hover { background: var(--panel); }

  /* Содержимое отодвигается от колонки и получает всю оставшуюся ширину.
     Не на всю: строка длиннее ~1000 точек читается хуже, а карточки
     рассчитаны на колонку. */
  /* Содержимое — колонкой разумной ширины, а не во всю доступную.

     Растянутое на 900 точек поле «Имя бота» и кнопка «Удалить», уехавшая
     к самому краю, — это и есть «слишком много пустого места»: глазу нечем
     связать левый край строки с правым. Ширину держим у содержимого,
     а не у окна. */
  body:has(.bottom-tabs) #app {
    max-width: 1216px;   /* 948 содержимого + 268 под колонку */
    margin-left: auto;
    margin-right: auto;
    padding-left: 268px;
    padding-bottom: 32px;
  }

  /* Поля ввода не растягиваются вслед за панелью: строка длиннее полусотни
     знаков не читается, а имя бота короче любой из них. */
  input, textarea, select { max-width: 560px; }

  /* Кнопки внутри строки жмутся друг к другу, а не разъезжаются по краям:
     «Повторить» и «Удалить» на разных концах девятисот точек выглядят как
     два разных экрана. */
  .panel > .row.wrap > .grow { flex-grow: 0; }

  /* Управление держится рядом с подписью, а не улетает к дальнему краю.

     Распорка .grow тянет тумблер или кнопку в конец строки. На телефоне это
     верно — там строка узкая. На мониторе строка почти в тысячу точек, и
     между «Бот следит за чатом» и его тумблером остаётся полметра пустоты:
     глазу нечем связать одно с другим, и приходится вести взглядом. */
  .panel .row > .grow { max-width: 620px; }

  /* Список ботов в ДВА столбца.

     В одну колонку на мониторе карточка растягивается на всю ширину, а внутри
     у неё аватарка да две строки слева — остальное пустое поле. Владелец
     сказал прямо: «слишком много пустого места».

     Заголовок группы и вложенный дублёр занимают строку целиком: заголовок
     иначе поделил бы список пополам, а дублёр оторвался бы от своего бота. */
  .stack:has(> .bot-card) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
    column-gap: 12px;
  }

  .stack:has(> .bot-card) > .group-head,
  .stack:has(> .bot-card) > .nest {
    grid-column: 1 / -1;
  }
}

/* Стрелки порядка в списке ботов. Приглушены намеренно: порядок меняют
   раз в жизни, и спорить за внимание с самими ботами им незачем. */
.move { display: flex; flex-direction: column; gap: 4px; flex-shrink: 0; color: var(--dim); }
.move > span {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 22px; border-radius: 6px; cursor: pointer;
}
.move > span:active { background: var(--sunken); color: var(--text); }

.panel { background: var(--panel); border-radius: 15px; padding: 16px; }
/* Отступ между соседними блоками задаётся ими самими, а не разметкой.
   Правило было только для .panel — и сетка плиток, встав следом за панелью,
   прилипала к ней вплотную: рядом две скруглённые рамки без зазора читаются
   как наезд одной на другую. */
.panel + .panel,
.panel + .tiles,
.tiles + .panel,
.tiles + .tiles,
.panel + .fold,
.fold + .panel,
.fold + .tiles,
.tiles + .fold,
.fold + .fold { margin-top: 12px; }

/* ...но внутри сетки отступ задаёт сам grid своим gap, а это правило там
   лишнее: соседи по РЯДУ получали margin-top и съезжали вниз относительно
   первой карточки. Замерено на статистике — «Подписчиков» стояла на 269,
   а «Активных за период» на 281, и ряд выглядел кривым. */
.cards > .panel { margin-top: 0; }
.panel h3 {
  margin: 0 0 12px;
  font-size: 13px; font-weight: 700; letter-spacing: 0.5px;
  color: var(--hint); text-transform: uppercase;
}

.line {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; row-gap: 8px;
  padding: 11px 0; border-bottom: 1px solid var(--divider);
}
.line:last-child { border-bottom: none; padding-bottom: 0; }
.line:first-of-type { padding-top: 0; }

.mono { font-family: ui-monospace, Menlo, Consolas, monospace; overflow-wrap: anywhere; }
.muted { color: var(--hint); font-size: 13px; }
.tiny { font-size: 13px; color: var(--dim); }

.banner {
  background: #2b2732; box-shadow: inset 0 0 0 1px #5c3538;
  color: #e8888c; border-radius: 12px; padding: 12px 14px;
  font-size: 13px; margin-bottom: 12px; overflow-wrap: anywhere;
}

.empty { text-align: center; padding: 44px 20px; color: var(--hint); }
.empty .empty-icon {
  width: 52px; height: 52px; margin: 0 auto 16px; border-radius: 16px;
  background: var(--sunken); display: flex; align-items: center; justify-content: center;
}
.empty b { display: block; font-size: 17px; color: var(--text); margin-bottom: 8px; }
.empty p { margin: 0; font-size: 13px; line-height: 1.45; max-width: 320px; margin-inline: auto; }

/* ── Плитки разделов ─────────────────────────────────────────────────────── */

/* Четыре раздела бота были четырьмя одинаковыми панелями с заголовком,
   абзацем и кнопкой — экран прокручивался, а взгляду не за что зацепиться.
   Плитки дают то же самое в четыре раза компактнее и читаются за один взгляд. */
.tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.tile {
  background: var(--panel);
  border-radius: 15px;
  padding: 14px;
  text-decoration: none;
  color: inherit;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 104px;
  transition: filter 0.12s ease, transform 0.08s ease;
}
.tile:hover { filter: brightness(1.1); }
.tile:active { transform: scale(0.985); }

/* Цвет живёт на значке, а не на всей плитке: четыре цветных прямоугольника
   рядом кричат одинаково громко и перестают что-либо значить. */
.tile .tile-icon {
  width: 32px; height: 32px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.tile b { font-size: 15px; font-weight: 600; }
.tile span { font-size: 11px; color: var(--hint); line-height: 1.45; }

/* ── Предпросмотр: кнопки под сообщением ─────────────────────────────────── */

/* В телеграме кнопки под сообщением прижаты к нему и невысокие. Обычные
   .btn-small на 44px превращали четыре кнопки в полэкрана, и было не понять,
   к какому сообщению они относятся. */
.pv-buttons {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
  max-width: 85%;
}
.pv-buttons .row-line { display: flex; gap: 4px; }

.pv-btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: 38px;
  padding: 8px 12px;
  border-radius: 7px;
  font-size: 13px;
  background: var(--sunken);
  box-shadow: inset 0 0 0 1px var(--divider);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  color: var(--link);
  text-align: center;
  overflow-wrap: anywhere;
}
button.pv-btn:hover { filter: brightness(1.15); }

/* Ссылка в предпросмотре никуда не ведёт — она бледнее и не нажимается,
   но выглядеть должна кнопкой, иначе ряд разъезжается. */
.pv-btn.dead { pointer-events: none; color: var(--hint); }

/* ── Модалка ─────────────────────────────────────────────────────────────── */

.backdrop {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.65);
  display: flex; align-items: center; justify-content: center; padding: 18px;
  z-index: 50;
  overflow-y: auto;
}
.modal {
  background: var(--panel); border-radius: 18px; padding: 20px;
  width: 100%; max-width: 420px;
  max-height: calc(100vh - 36px);
  max-height: calc(100dvh - 36px);
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 16px;
  margin: auto;
}
.modal h2 { margin: 0; font-size: 17px; overflow-wrap: anywhere; }
.modal .field { display: flex; flex-direction: column; gap: 8px; }
.modal label { font-size: 13px; color: var(--hint); }
/* Кнопки окна прибиты к низу.

   Замерено на экране 360×620: «Новая рассылка» и «Что добавить» выше окна,
   содержимое прокручивается — и «Отмена» с «Создать» оказывались НИЖЕ края.
   Человек видел форму без единой кнопки и не понимал, чем её закончить.

   Липкая полоса решает это на все окна разом: контент прокручивается под
   ней, кнопки остаются на месте. Отрицательный отступ снизу гасит внутреннее
   поле окна, чтобы полоса доставала до самого края и под ней ничего не
   просвечивало. */
.modal .actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  position: sticky;
  bottom: -20px;
  background: var(--panel);
  padding: 12px 0 20px;
  margin-bottom: -20px;
}

/* Картинка результата внутри окна: то же сообщение, что увидит человек. */
.modal-preview {
  background: var(--sunken);
  border-radius: 12px;
  padding: 11px;
}

/* Выбор одного из нескольких.

   Строкой, а не рядом чипсов: у каждого варианта есть пояснение, и на
   телефоне читать столбик проще, чем разбирать шесть подписей вподбор. */
.pick { display: flex; flex-direction: column; gap: 8px; }

.pick-row {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px;
  width: 100%; text-align: left; padding: 11px 13px;
  border-radius: 11px; background: var(--sunken);
}

.pick-row:hover, .pick-row:focus-visible { background: var(--accent-soft); }

/* pointer-events на подписях сняты: без этого нажатие приходит на <span>,
   а data-value лежит на кнопке, и первый же тык по подписи ничего не делал. */
.pick-label, .pick-hint { pointer-events: none; }
.pick-label { font-size: 15px; }
.pick-hint { font-size: 13px; color: var(--hint); }

/* ── Предпросмотр сообщения рядом с правками ─────────────────────────────── */

.preview-pane { margin-bottom: 12px; padding: 12px 14px; }

.preview-toggle {
  width: 100%;
  background: none;
  padding: 0;
  border-radius: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--hint);
}

.preview-body { margin-top: 12px; }
.preview-pane.closed .preview-body { display: none; }

/* Широкий экран: предпросмотр стоит рядом и не прячется.

   Переключатель там не нужен — место есть, и лишняя кнопка только
   спрашивает о том, чего никто не хочет менять. */
@media (min-width: 900px) {
  .preview-pane {
    position: sticky;
    top: 12px;
    float: right;
    width: 320px;
    margin-left: 20px;
  }

  .preview-pane.closed .preview-body { display: block; }
  .preview-toggle { pointer-events: none; }
  .preview-toggle .tiny { display: none; }
}

/* ── Свёрнутая панель: общий приём всех настроек ─────────────────────────── */

/* Выглядит как обычная панель, но занимает место только когда её открыли.
   Одна разметка на бота, канал и чат — иначе настройки разъедутся. */
.fold {
  background: var(--panel);
  border-radius: 14px;
  padding: 14px 16px;
}

.fold > summary {
  cursor: pointer;
  /* Своя стрелка справа вместо треугольника слева: он тянет строку влево
     и ломает ряд с остальными заголовками экрана. */
  list-style: none;
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Заголовок и подпись переносятся вместе, а не спорят за одну строку. */
.fold-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  min-width: 0;
  flex: 1 1 auto;
}

.fold > summary::-webkit-details-marker { display: none; }

.fold-title {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--hint);
}

/* Строка, видная не открывая панель: сколько ссылок, кому выдан доступ.

   Без nowrap и без многоточия: обрезанная подпись рядом с перенесённым
   заголовком выглядела наездом одного текста на другой. Пусть лучше
   займёт свою строку — места это стоит одну строку, а читается целиком. */
.fold-note {
  font-size: 13px;
  color: var(--dim);
  min-width: 0;
  overflow-wrap: anywhere;
}

.fold > summary::after {
  content: "⌄";
  margin-left: auto;
  font-size: 15px;
  line-height: 1;
  color: var(--hint);
  transition: transform .15s;
  flex-shrink: 0;
}

.fold[open] > summary::after { transform: rotate(180deg); }
.fold > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px; }

.fold-body { margin-top: 12px; }

/* Опасное: рамка вместо заливки. Красным горит только то, что внутри. */
.fold-danger { background: none; box-shadow: inset 0 0 0 1px var(--divider); }
.fold-danger .fold-title { color: var(--red); }

/* ── Вложенная панель: линия вместо коробки ──────────────────────────────────

   Внутри карточки канала или чата панель лежит на подложке ТОГО ЖЕ цвета —
   коробки в коробке не видно, и четыре группы сливались в одно серое пятно.
   Замерено на живом экране: у вложенной и у внешней панели совпадал фон.

   Поэтому внутри карточки группы разделяются волосяной линией, как строки
   списка, а собственную заливку и скругления не получают вовсе. */
.panel > * .fold,
.panel > .fold {
  background: none;
  box-shadow: none;
  border-radius: 0;
  margin: 0;
  padding: 13px 0;
  border-top: 1px solid var(--divider);
}

/* Первая группа линии не получает: её отделяет сама шапка карточки. */
.panel > * > .fold:first-child { border-top: 0; padding-top: 4px; }

/* Опасная группа внутри карточки — тоже строкой, без рамки. */
.panel > * .fold-danger { box-shadow: none; }

/* ── Всплывашка ──────────────────────────────────────────────────────────── */

#toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(22px + env(safe-area-inset-bottom, 0px));
  background: var(--panel); box-shadow: 0 8px 28px rgba(0,0,0,0.5);
  border-radius: 12px; padding: 12px 18px; font-size: 15px;
  z-index: 100; max-width: 90vw; text-align: center;
}
#toast.err { background: #3a2529; color: #ffb4b7; }

/* Смена экрана без перехода читается как сбой отрисовки: содержимое
   мгновенно подменяется другим. Короткое проявление снизу показывает,
   что это новый экран, и не задерживает — 140 мс не ощущаются как ожидание. */
@keyframes screen-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.screen { animation: screen-in 0.14s ease-out; }

/* Кто просил не анимировать — тому не анимируем. */
@media (prefers-reduced-motion: reduce) {
  .screen { animation: none; }
  button { transition: none; }
  .tile { transition: none; }
}

.spinner {
  width: 22px; height: 22px; margin: 60px auto;
  border: 2.5px solid var(--divider); border-top-color: var(--accent-on-bg);
  border-radius: 50%; animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Мобильная ширина (телефон в первую очередь — это Mini App) ────────────── */

@media (max-width: 480px) {
  /* Палец промахивается мимо мелких кнопок — везде на телефоне держим
     нажимаемую область не меньше 44px по высоте. */
  button, .btn-small, nav.tabs a {
    min-height: 44px;
  }

  /* Ряды из нескольких select/input, рассчитанные на широкий экран,
     на телефоне превращаются в столбик — иначе поля сжимаются до нечитаемой
     ширины или наезжают друг на друга. Целимся точечно по data-атрибутам
     редактора, логику это не трогает.
     !important — у этих полей в разметке свой инлайновый flex-basis
     (например style="flex:1 1 110px"), он в любом случае перебивает
     внешний CSS по специфичности, если его явно не перебить. */
  [data-btn="text"],
  [data-btn="url"],
  [data-btn="command"],
  [data-block="variable"],
  [data-block="expect"],
  [data-block="actionVariable"],
  [data-block="actionValue"],
  [data-cond="variable"],
  [data-cond="op"],
  [data-cond="value"],
  [data-cond="goto"] {
    flex-basis: 100% !important;
  }

  /* Кнопка удаления строки прижимается вправо и остаётся компактной.
     Растянутая во всю ширину красная полоса читается как «удалить всё»,
     хотя убирает она одну строку над собой. Ширины хватает для пальца:
     min-height 44px задан выше, ширину добираем отступами. */
  [data-act="fl-btn-del"],
  [data-act="fl-branch-del"],
  [data-act="fl-poll-del"] {
    flex: 0 0 auto !important;
    margin-left: auto;
    padding-left: 20px;
    padding-right: 20px;
  }

  .modal .actions {
    flex-direction: column;
  }
  .modal .actions button {
    width: 100%;
  }
}

/* ── Имя бота как вход к нему ─────────────────────────────────────────────

   Кнопка, а не подпись: по ней уходят в самого бота проверить правку.
   Выглядит подписью намеренно — это по-прежнему адрес, а не действие
   первой руки; стрелка говорит, что нажатие куда-то ведёт.

   Отступы и подложка убраны, иначе она спорила бы за внимание с именем
   бота строкой выше, хотя нужна на порядок реже. */
.open-bot {
  background: none;
  border: 0;
  padding: 2px 0 0;
  min-height: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--accent-on-bg);
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-radius: 6px;
}

.open-bot:active { filter: brightness(1.3); }
.open-bot svg { opacity: 0.75; flex-shrink: 0; }
