/* Все значения - из tokens.css. Литералов здесь быть не должно. */

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  line-height: var(--lh);
  /* Колонкой во всю высоту окна - чтобы каркас не обрывался на середине
     экрана там, где кончилось содержимое. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* --- каркас: шапка сверху, разделы слева --------------------------------- */

.topbar {
  display: flex;
  /* Переносить, а не распирать. Без переноса шапка не помещалась в узкое окно,
     растягивала документ, и вбок уезжала ВСЯ страница - вместе с медиазапросами,
     которые сами по себе верные. Переносится, впрочем, не сама шапка, а её
     правая половина - см. .topbar-main. */
  flex-wrap: wrap;
  align-items: stretch;
  min-height: var(--topbar-height);
  border-bottom: 1px solid var(--border);

  /* Вертикальная черта под логотипом - та же полоска и на той же координате,
     что и черта между колонками у .shell ниже. Рамкой её рисовать нельзя:
     рамка ложится ВНУТРЬ ширины колонки, а полоска - сразу за ней, и две
     линии расходятся на пиксель. В логах такого не видно никогда. */
  background: linear-gradient(var(--border), var(--border))
              var(--sidebar-width) 0 / 1px 100% no-repeat,
              var(--bg-topbar);
}

/* Левый блок шапки стоит ровно над боковой колонкой и такой же ширины. */
.topbar-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: var(--sidebar-width);
  flex-shrink: 0;
  padding-left: var(--gutter-nav);
}

.logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--logo-size);
  height: var(--logo-size);
  border-radius: var(--radius-lg);
  background: var(--accent);
  flex-shrink: 0;
}

.logo svg { width: var(--icon); height: var(--icon); fill: var(--text); }

.brand { font-weight: var(--w-semibold); letter-spacing: 0.02em; color: var(--text); }
.topbar-brand:hover { text-decoration: none; }
.topbar-brand:hover .brand { color: var(--accent); }

.topbar-brand:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Всё правее черты. Переносится здесь, внутри своей половины: перенеси мы всю
   шапку, вторая строка ушла бы под логотип и пересекла вертикальную линию. */
.topbar-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--gutter);
}

.topbar form { margin: 0; }

/* Состояние конвейера прижато к правому краю шапки. */
.topbar-state {
  margin-left: auto;
  display: flex;
  /* Переносить, а не распирать - ровно по той же причине, что и у .topbar
     выше. Без переноса нагруженная шапка («очередь 6 · воркеры 2 свободны ·
     Infidelity Notes тратит в 6,0 раза больше») не помещалась в узкое окно и
     тянула за собой ВСЮ страницу: при рамке 640 документ становился 1165.
     Эту же ошибку уже чинили у .topbar, а завели заново в новом блоке. */
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Пилюли рассказывают о состоянии, кнопка - действие. Разделитель говорит,
   что это разные вещи, а не пятый значок в ряду. */
.topbar-sep {
  width: 1px;
  height: var(--space-6);
  align-self: center;
  background: var(--border);
}

.btn-new {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--h-lg);
  white-space: nowrap;
}

.btn-new:hover { text-decoration: none; }

.btn-icon {
  width: var(--icon);
  height: var(--icon);
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--stroke-icon);
  stroke-linecap: round;
}

/* flex-wrap и max-width: длинная плашка («Infidelity Notes тратит в 2,3 раза
   больше») на телефоне переносит вторую часть на новую строку, а не шире
   шапки - иначе вбок уезжала вся страница (429 при 390, замерено 05.10).
   Короткие «очередь 0» не меняются: им переносить нечего. */
.pill {
  display: inline-flex;
  flex-wrap: wrap;
  max-width: 100%;
  align-items: baseline;
  gap: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-3);
  font-size: var(--t-xs);
  color: var(--text-muted);
  white-space: nowrap;
}

a.pill:hover { border-color: var(--accent); text-decoration: none; }

a.pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.pill-value { color: var(--text); }
.pill-ok .pill-value { color: var(--ok); }
.pill-run .pill-value { color: var(--accent); }
.pill-warn .pill-value { color: var(--pending); }
.pill-fail .pill-value { color: var(--fail); }
.pill-warn { border-color: var(--pending); }
.pill-fail { border-color: var(--fail); }

.shell {
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  align-items: start;
  flex: 1;

  /* Черта между колонками принадлежит каркасу, а не меню.
   *
   * Раньше это была граница самих разделов - и она кончалась там же, где
   * кончались одиннадцать пунктов, то есть на трети экрана. Перевесить её на
   * растянутое меню нельзя: растянутое меню перестаёт быть липким, а на
   * длинных страницах (редактор пресета) липкость и есть его смысл. Поэтому
   * черту рисует каркас - полоской в один пиксель ровно по границе колонок. */
  background: linear-gradient(var(--border), var(--border))
              var(--sidebar-width) 0 / 1px 100% no-repeat;
}

.sections {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  /* Плашка пункта отодвинута от края колонки на --space-2. Сама иконка при
     этом обязана стоять на линии --gutter-nav - см. отступ у .section. */
  padding: var(--space-5) var(--space-2);
  position: sticky;
  top: 0;
}

.section-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Подпись группы - ориентир, а не пункт меню: она тише самих пунктов
   и не кликается. */
.section-group-title {
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  padding: 0 var(--space-3) var(--space-1) var(--nav-inset);
}

.section {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-muted);
  /* Слева не --gutter-nav, а он же минус отступ колонки: иконка должна
     оказаться ровно под левым краем логотипа в шапке, а плашка вокруг неё -
     чуть левее. Напиши здесь просто --space-2, и совпадение продержится
     ровно до первой правки --gutter-nav. */
  padding: var(--space-2) var(--space-3) var(--space-2) var(--nav-inset);
  border-radius: var(--radius);
  font-size: var(--t-body);
}

.section:hover { color: var(--text); background: var(--bg-panel); text-decoration: none; }

.section:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.section-icon {
  width: var(--icon);
  height: var(--icon);
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--stroke-icon);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.section.active {
  color: var(--text);
  background: var(--bg-raised);
  font-weight: var(--w-medium);
}

/* Единственное акцентное пятно в колонке - иконка открытого раздела. */
.section.active .section-icon { color: var(--accent); }

.section-count {
  margin-left: auto;
  font-size: var(--t-xs);
  color: var(--text-faint);
}

/* Тревога - единственное тёплое пятно во всём интерфейсе, и живёт оно ровно
   столько, сколько есть упавшее. Правило наведения перебивает цвет пункта,
   поэтому тёплый цвет назван и для наведения тоже. */
.section-alarm, .section-alarm:hover { color: var(--pending); }
.section-alarm .section-count { color: var(--pending); }

.content {
  max-width: var(--page-width);
  /* Слева - --gutter, та же линия, с которой начинается правая половина
     шапки. Заголовок страницы обязан стоять ровно под переключателем канала. */
  padding: var(--space-7) var(--gutter) var(--space-8);
  min-width: 0;
}

/* Узкое окно: разделы ложатся строкой над содержимым. */
@media (max-width: 800px) {
  /* Колонок больше нет - значит, нет и вертикальной черты в шапке. */
  .topbar { background: var(--bg-topbar); }
  .topbar-brand { width: auto; }
  .topbar-main { padding-inline: var(--gutter-nav); }

  .shell { grid-template-columns: 1fr; background: none; }
  .sections {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--space-4);
    border-bottom: 1px solid var(--border);
    position: static;
  }
  .section-group {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
  }
  .section-group-title { padding: 0; }
  /* Активный пункт и здесь отмечен плашкой - той же, что в колонке.
     Подчёркиванием нельзя: у пункта скруглённые углы, и линия загибается
     вверх по краям, превращаясь в улыбку. */
  .section { padding-left: var(--space-3); }
  .section-logout { padding-left: 0; }
}

/* Шкала стояла на ступень ниже макета: заголовок страницы набирался 22-м,
 * заголовок блока - 18-м. В tokens.css написано обратное: --t-display это
 * «заголовок страницы», --t-h1 - «заголовок раздела на странице», а 18-й
 * кегль в макете носит название выпуска, а не заголовок блока. Три страницы
 * уже обошли это своими классами (.run-title, .preset-title); теперь линия
 * одна на все. */
h1 {
  font-size: var(--t-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  margin-bottom: var(--space-2);
}

h2 {
  font-size: var(--t-h1);
  color: var(--text-muted);
  margin: var(--space-7) 0 var(--space-2);
}

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

/* Проза страницы - та, что стоит прямо под заголовком, а не внутри панели.
 * Внутри панели ширину держит правило ниже; здесь его нет, и абзац
 * растягивался во всю ширину экрана - на 1280 пикселях это строка в полторы
 * сотни знаков. */
.page-note { max-width: var(--read-width); }

.back { margin-bottom: var(--space-4); }

table.runs {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--space-4);
  font-variant-numeric: tabular-nums;
}

table.runs th, table.runs td {
  text-align: left;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
}

/* Шапка таблицы из словаря: заглавными, моноширинным, с разрядкой - та же
 * подпись, что у полосы чисел и у групп меню. Правило стояло только у
 * очереди, а таких таблиц в приложении восемь. */
table.runs th, .stats-table thead th {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: var(--w-medium);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}

/* Статус из словаря компонентов: форма + цвет, а не только цвет. Подложка -
 * wash своего смысла, рамка - line, текст и точка - сам цвет. Заглавными и
 * моноширинным, чтобы слово читалось как пометка, а не как продолжение
 * соседнего текста.
 *
 * Вид собран целиком здесь, разметка в шаблонах прежняя: слово статуса лежит
 * в трёх десятках мест, и менять их ради точки слева значило бы переписать
 * половину экранов. */
.status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
  white-space: nowrap;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  color: var(--text-muted);
}

/* Точка - у тех статусов, у которых есть свой цвет. «Пропущено» цвета не
   имеет, и точки у него нет: сказать нечего.

   Имя статуса начинается с самого `.status` не для красоты: те же имена
   (`status-pending`, `status-done`) носит сегмент полосы стадий, и голое
   правило доставало и его - точка вставала над линией, а подложка плашки
   превращала полосу на «Сегодня» в семь цветных плиток. Краска плашки
   красит плашку. */
.status.status-ok::before, .status.status-done::before,
.status.status-pending::before, .status.status-running::before,
.status.status-failed::before {
  content: "";
  width: var(--space-2);
  height: var(--space-2);
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.status.status-ok, .status.status-done {
  color: var(--ok);
  background: var(--ok-wash);
  border-color: var(--ok-line);
}

.status.status-pending {
  color: var(--pending);
  background: var(--pending-wash);
  border-color: var(--pending-line);
}

.status.status-running {
  color: var(--accent);
  background: var(--accent-wash);
  border-color: var(--accent-line);
}

.status.status-failed {
  color: var(--fail);
  background: var(--fail-wash);
  border-color: var(--fail-line);
}

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}

#new-run-form { display: flex; align-items: center; gap: var(--space-2); }

#new-run-count { width: var(--w-number); }

.btn {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  color: var(--text);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: var(--t-body);
}

.btn:hover { border-color: var(--accent); color: var(--accent); }

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.btn:disabled, .btn[aria-busy="true"] {
  color: var(--text-faint);
  border-color: var(--border);
  cursor: progress;
}

.btn-primary { border-color: var(--accent); color: var(--accent); }

/* Залитая акцентом - главное действие ЭТОЙ страницы, и оно всегда одно. */
.btn-accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn-accent:hover { filter: brightness(1.1); color: var(--accent-ink); }

.btn-accent:disabled, .btn-accent[aria-busy="true"] {
  background: var(--bg-panel);
  border-color: var(--border);
  filter: none;
}

.btn-small {
  padding: var(--space-1) var(--space-2);
  font-size: var(--t-xs);
}

/* Кнопка из одной иконки: квадратная, чтобы не выглядеть обрезанной рядом
 * с обычной. Подпись у неё есть - в aria-label, иначе с клавиатуры она
 * безымянная. */
.btn-icon-only {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--h);
  height: var(--h);
  padding: 0;
}

/* overflow-wrap у трёх рамок, где показывается текст ошибки прогона (плашка
   здесь, .c-alert-err, .alertbar-note): в нём бывает путь на сервере без
   единого пробела, и на телефоне он распирал страницу вбок. Обычный текст
   переносится как раньше - правило трогает только слово длиннее строки. */
.banner {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  border: 1px solid transparent;
  margin: var(--space-3) 0;
  font-size: var(--t-body);
  overflow-wrap: anywhere;
}

.banner-error { color: var(--fail); border-color: var(--fail); }

/* --- шапка страницы выпуска ------------------------------------------------ */
/* Крошки, крупное название, строка состояния и подписанная группа действий.
 * До этого среза здесь стояла ссылка «← все выпуски» и заголовок «Video 41 —
 * название»: страница не говорила ни про канал, ни про то, чем прогон занят. */

.crumbs {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  font-size: var(--t-sm);
  color: var(--text-muted);
}

.crumbs a { color: var(--text-muted); }
.crumbs a:hover { color: var(--text); }
.crumbs-sep { color: var(--text-faint); }

.run-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
  flex-wrap: wrap;
  margin-bottom: var(--space-6);
}

.run-head-main { min-width: 0; }

/* Единственное место, где заголовок берёт самую крупную ступень шкалы: h1
 * общий на двадцать страниц, и трогать его ради одной нельзя. */
.run-title {
  font-size: var(--t-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  margin-bottom: var(--space-3);
}

.run-facts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

.run-actions { flex-shrink: 0; }

/* Подпись над группой - то, что отличает действия ЭТОГО выпуска от кнопки
 * «Новый выпуск» в шапке приложения. Без неё «Перезапустить» читается как
 * ещё один способ запустить что-нибудь. */
/* Подпись над группой действий объекта - одна на приложение: на выпуске она
 * прижата к правому краю (группа стоит справа от заголовка), на карточке
 * канала идёт слева. Различие только в выравнивании, вид общий. */
.actions-label, .run-actions-label {
  display: block;
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

.run-actions-label { text-align: right; }

/* Одна рамка на две кнопки: они про один объект, и разделяет их волосяная
 * линия, а не воздух. */
.btn-group {
  display: flex;
  align-items: stretch;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.btn-group form { margin: 0; display: flex; }

.btn-flat {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--h-lg);
  background: transparent;
  border: 0;
  border-radius: 0;
  white-space: nowrap;
  font-size: var(--t-sm);
}

/* Линия между соседями - у второй кнопки, а не у обеих: иначе на краю группы
 * получилась бы двойная. */
.btn-group form + form .btn-flat { border-left: 1px solid var(--border); }

.btn-flat:hover { background: var(--bg-raised); border-color: transparent; }
.btn-danger.btn-flat:hover { background: var(--fail-wash); }

/* Выключенная кнопка не притворяется занятой: ждать тут нечего, и курсор
 * «часы» обещал бы, что что-то происходит. */
.btn-flat:disabled { color: var(--text-faint); cursor: not-allowed; }
.btn-flat:disabled:hover { background: transparent; }

/* --- правая колонка выпуска ------------------------------------------------- */
/* Чем выпуск обошёлся, чем он собран и что от него осталось на диске. Ширина
 * своя, --rail-width: колонка со значениями не должна тянуться вместе с
 * содержимым, иначе пары ключ-значение расползаются на пол-экрана.
 *
 * Колонка живёт внутри вкладки «Прогон», а не рядом со всеми тремя: у
 * «Публикации» формы под описание для YouTube, и отдать им на 312 пикселей
 * меньше значило бы испортить работающий экран ради красоты этого. */

.run-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--rail-width);
  gap: var(--space-7);
  align-items: start;
}

.run-main { min-width: 0; }

.run-rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}

/* Узкое окно: колонка уезжает под содержимое. Порог тот же, что у «Сегодня»,
 * и по той же причине - слева от контента стоит колонка разделов. */
@media (max-width: 1100px) {
  .run-cols { grid-template-columns: minmax(0, 1fr); }
}

.rail-block {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4);
}

.rail-label {
  margin: 0 0 var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: var(--w-medium);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

.rail-total {
  margin: 0 0 var(--space-4);
  font-size: var(--t-display);
  line-height: var(--lh-tight);
  color: var(--text);
}

.rail-note {
  margin: var(--space-3) 0 0;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* Деньги по стадиям. Полоска мерится от самой дорогой стадии - соседи стоят
 * рядом, и глаз сравнивает их друг с другом, а не с суммой. */
.cost-split { margin: 0; }

.cost-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.cost-row dt {
  font-size: var(--t-sm);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
}

.cost-row dd {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.cost-line {
  flex: 1 1 auto;
  height: var(--stage-line);
  border-radius: var(--radius-sm);
  background: var(--stage-idle);
  overflow: hidden;
}

.cost-fill {
  display: block;
  height: 100%;
  background: var(--accent);
}

.cost-value {
  flex-shrink: 0;
  font-size: var(--t-xs);
  color: var(--text);
}

/* Настройки прогона: ключ слева, значение справа, обе половины переносятся -
 * длинные имена моделей и смесь голосов в одну строку не влезают. */
.rail-pairs { margin: 0; }

.rail-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--t-sm);
}

.rail-pair:last-child { border-bottom: 0; }
.rail-pair dt { color: var(--text-muted); }
.rail-pair dd { margin: 0; color: var(--text); overflow-wrap: anywhere; }

.run-rail .repeat-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.rail-files {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rail-file {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border-soft);
  font-size: var(--t-sm);
}

.rail-file:last-child { border-bottom: 0; }
.rail-file-size { color: var(--text-muted); }

/* Файла ещё нет - строка остаётся, но гаснет вся: половина строки обычного
 * цвета читалась бы как «файл есть, просто маленький». */
.rail-file.is-missing { color: var(--text-faint); }
.rail-file.is-missing .rail-file-size { color: var(--text-faint); }

/* --- проигрыватель, главы и лог на «Прогоне» -------------------------------- */

/* Здесь проигрыватель занимает всю колонку: --media-width сделан под превью
 * в форме, а тут видео - главное, ради чего страницу открывают. */
.run-media video { max-width: 100%; }

/* Главы: ряд отметок под проигрывателем. Прокручивается внутри себя - частей
 * бывает и двенадцать, и страница от этого вбок ехать не должна. */
.chapters {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.chapter {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  height: var(--h);
  padding: 0 var(--space-3);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--t-sm);
  cursor: pointer;
}

.chapter:hover { border-color: var(--accent); color: var(--text); }

.chapter:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.chapter-time { color: var(--text); }

.log-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.log-head h2 { margin-bottom: var(--space-2); }

.log-live {
  font-size: var(--t-sm);
  color: var(--text-faint);
}

/* --- полоса стадий на выпуске ---------------------------------------------- */
/* Тот же компонент, что на «Сегодня» (.stagebar/.seg ниже по файлу), но
 * нажимаемый: сегмент разворачивает лог своей стадии. Поэтому он не div, а
 * button - и всю кнопочную краску с него надо снять, иначе семь серых
 * прямоугольников затопчут и линии, и подписи. */

.strip-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0;
}

.strip-label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
}

.strip-said {
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* Рамка фокуса рисуется в 2px ЗА краем сегмента, а полоса лежит в контейнере
 * с горизонтальной прокруткой: `overflow-x: auto` обрезает и по вертикали, и
 * от рамки оставались две скобки по бокам. Запас сверху и снизу пускает её
 * целиком; столько же вычитается из внешних отступов, чтобы полоса не
 * сдвинулась. Класс здесь двойной: общее правило `.stagebar` лежит НИЖЕ по
 * файлу и своим `margin` перебило бы одиночный `.stagebar-tabs`. */
.stagebar.stagebar-tabs {
  padding-block: var(--space-1);
  margin-block: calc(var(--space-4) - var(--space-1))
                calc(var(--space-3) - var(--space-1));
}

.stagebar-tabs .seg {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

/* Под мышью светлеет только дорожка: рамка вокруг сегмента вернула бы ту
 * самую плитку, вместо которой полоса и заводилась. */
.stagebar-tabs .seg:hover .seg-line { background: var(--border); }
.stagebar-tabs .seg:hover .seg-name { color: var(--text); }

.stagebar-tabs .seg:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Раскрытая стадия помечена тем же акцентом, что и её лог ниже. */
.stagebar-tabs .seg.is-open .seg-name { color: var(--accent); }
.stagebar-tabs .seg.is-open .seg-line { background: var(--accent-line); }

/* --- полоса семи стадий --------------------------------------------------- */
/* Одна строка вместо семи карточек: шесть из семи всё равно говорили "ожидает",
 * а занимали весь верх экрана. Состояние стадии здесь несёт цвет и заливка,
 * слово состояния и время уехали в подсказку и в раскрытый лог стадии. */

.timeline {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(84px, 1fr);
  gap: var(--space-1);
  margin-bottom: var(--space-2);
}

.tl-stage {
  position: relative;   /* заливка прогресса лежит под именем */
  overflow: hidden;
  display: block;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-1);
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  text-align: center;
  cursor: pointer;
}

.tl-stage:hover { border-color: var(--accent); color: var(--text); }

.tl-stage:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.tl-stage.is-open { background: var(--bg-raised); border-color: var(--accent); }

.tl-name { position: relative; }   /* поверх заливки */

.tl-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--accent);
  opacity: 0.22;
}

/* Цвет сегмента берётся из его состояния: рамка, текст и заливка. */
.tl-stage.status-done { border-color: var(--ok); color: var(--text); }
.tl-stage.status-done .tl-fill { background: var(--ok); }
.tl-stage.status-running { border-color: var(--accent); color: var(--text); }
/* У упавшей стадии заливки нет - она не дошла до конца, и рисовать её полной
 * значило бы соврать. Поэтому знак здесь другой: красным написано имя. */
.tl-stage.status-failed { border-color: var(--fail); color: var(--fail); }
.tl-stage.status-failed .tl-fill { background: var(--fail); }

/* Строка под полосой: что идёт прямо сейчас. Пусто - строки нет вовсе. */
.tl-now {
  margin: 0 0 var(--space-3);
  color: var(--text-muted);
  font-size: var(--t-sm);
}

.tl-now strong {
  font-family: var(--font-mono);
  font-weight: normal;
  color: var(--accent);
}

.tl-panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}

.tl-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.tl-panel-head form { margin: 0; }

.tl-panel .block { margin: 0; }

/* Сводочный вариант полосы на "Сегодня": те же сегменты, но не нажимаются. */
.timeline-mini { grid-auto-columns: minmax(60px, 1fr); margin-bottom: 0; }

.timeline-mini .tl-stage {
  cursor: default;
  padding: var(--space-1);
}

.timeline-mini .tl-stage:hover { border-color: var(--border); }
.timeline-mini .tl-stage.status-done:hover { border-color: var(--ok); }
.timeline-mini .tl-stage.status-running:hover { border-color: var(--accent); }
.timeline-mini .tl-stage.status-failed:hover { border-color: var(--fail); }

/* Вкладки страницы выпуска. Разделов десять, и свитком они складывались
 * в три экрана; вкладка показывает один смысловой кусок за раз. Полоса
 * стадий при этом лежит ВЫШЕ вкладок и видна всегда. */
.page-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  border-bottom: 1px solid var(--border);
  /* Внутренний отступ первой вкладки вычитается из контейнера, иначе её
     первое слово стоит на 16px правее заголовка страницы, и левая линия
     контента ломается ровно там, где её виднее всего. */
  margin: 0 0 var(--space-4) calc(var(--space-4) * -1);
}

.page-tab {
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--text-muted);
  padding: var(--space-2) var(--space-4);
  margin-bottom: -1px;   /* полоска выбранной вкладки садится на линию снизу */
  font-size: var(--t-body);
  cursor: pointer;
}

.page-tab:hover { color: var(--text); }

.page-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.page-tab.is-open {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* Сколько настроек в группе. Приписка к названию вкладки, а не самостоятельное
 * число: поэтому тусклее и мельче, и никогда не крупнее подписи. */
.tab-count {
  margin-left: var(--space-2);
  font-size: var(--t-xs);
  color: var(--text-faint);
}

.page-tab.is-open .tab-count { color: var(--text-muted); }

/* Панель раскладывается сеткой, поэтому hidden нужно сказать вслух:
 * display: grid перебил бы правило браузера, и спрятанная вкладка
 * осталась бы на экране. */
.tab-panel {
  display: grid;
  gap: var(--space-2);
}

.tab-panel[hidden] { display: none; }

/* У элемента сетки min-width по умолчанию равен ширине содержимого: одна
 * длинная строка в логе распёрла бы панель, а с ней и страницу вбок. */
.tab-panel > * { min-width: 0; }

/* Первый заголовок во вкладке стоит сразу под её полоской - большой
 * верхний отступ h2 здесь только оторвал бы его от вкладки. */
.tab-panel > section:first-child > h2 { margin-top: 0; }

/* Чего в выпуске ещё нет - одной строкой, а не тремя заголовками
 * с извинениями под каждым. */
.material-missing {
  font-size: var(--t-sm);
  margin-top: var(--space-4);
}

.empty-note {
  padding: var(--space-7) 0;
  max-width: var(--read-width);
}

input[type="text"], input[type="number"], input[type="date"],
input[type="url"], input[type="search"], input[type="password"], select {
  background: var(--bg-raised);
  border: 1px solid var(--border);
  color: var(--text);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius);
  font-size: var(--t-body);
}

input[type="text"]:focus, input[type="number"]:focus,
input[type="date"]:focus, input[type="url"]:focus,
input[type="search"]:focus, input[type="password"]:focus, select:focus {
  outline: none;
  border-color: var(--accent);
}

label {
  display: block;
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-bottom: var(--space-1);
}

.field-row { margin-bottom: var(--space-3); }

.field-row input, .field-row select { width: 100%; max-width: var(--field-width); }

.field-help { margin: var(--space-1) 0 0; font-size: var(--t-xs); }

/* --- строка поля на странице пресета: три зоны ----------------------------- */
/* Слева имя с подсказкой, в середине контрол, справа режим. До этого всё
 * лежало в столбик, и на широком мониторе подсказку от её названия отделяла
 * половина строки контролов. Правила нарочно сужены до .preset-form: тот же
 * .field-row носит карточка канала, и она устроена иначе - поле там лежит
 * ВНУТРИ своей метки. */

.preset-form .field-row {
  display: grid;
  grid-template-columns: var(--field-label) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--bg-panel);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
}

/* Всё, что не имя и не режим, - середина. Правило стоит первым, чтобы две
 * строки ниже его перебили: сортировка по месту в файле здесь и есть
 * приоритет. Видимый контрол середины всегда ровно один - остальные лежат
 * под hidden и места в сетке не занимают. */
.preset-form .field-row > * { grid-column: 2; }
.preset-form .field-row > .f-name { grid-column: 1; grid-row: 1; }
.preset-form .field-row > .field-modes { grid-column: 3; grid-row: 1; }

.f-name { min-width: 0; }

.f-name label {
  display: block;
  margin-bottom: 0;
  color: var(--text);
}

.preset-form .field-help { color: var(--text-muted); }

/* Режим не «фикс» - значит значение поля решает не человек, а бросок внутри
 * рамок, и показывать рядом с рамками ещё и текущее число незачем: это два
 * ответа на один вопрос. Само поле при этом остаётся в форме и сохраняется. */
.preset-form .field-row.mode-rules > [data-group],
.preset-form .field-row.mode-rules > .multi-options,
.preset-form .field-row.mode-rules > .slider-field,
.preset-form .field-row.mode-rules > .inherited { display: none; }

/* Узкое окно: зоны встают друг под друга. Порог тот же, что у «Сегодня» и
 * у выпуска, и по той же причине - слева 216 пикселей разделов. */
@media (max-width: 1100px) {
  .preset-form .field-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .preset-form .field-row > *,
  .preset-form .field-row > .f-name,
  .preset-form .field-row > .field-modes {
    grid-column: 1;
    grid-row: auto;
  }
}

/* Группа перестала быть карточкой: по макету её заголовок стоит прямо на
 * фоне страницы, а карточки - сами строки полей. Фон блокам превью поэтому
 * пришлось вернуть своим .panel: они держались на фоне группы. */
.field-group { margin-bottom: var(--space-7); }

.group-head {
  margin: 0 0 var(--space-4);
  font-size: var(--t-h1);
  color: var(--text);
  text-transform: none;
  letter-spacing: normal;
}

/* Настройка, которая упрётся в промпт. Не украшение: это единственное место,
   где про отказ прогона говорится ДО ночи, а не из упавшего лога. */
.field-warn {
  margin: var(--space-1) 0 0;
  font-size: var(--t-xs);
  color: var(--pending);
}

/* Кнопка под полем - та же мерка отступа, что у подписи над ней. */
.field-action { margin: var(--space-2) 0 0; }

#create-form, .copy-form, .rename-form {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  min-width: 0;
}

/* Галочки «что взять у образца» под формой нового канала. */
.create-take {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  border: 0;
  max-width: var(--read-width);
}

.create-take[hidden] { display: none; }

.create-take legend { padding: 0; margin-bottom: var(--space-2); }

.create-take .check-hint:last-child { flex-basis: 100%; margin: 0; }

.preview-block {
  margin-top: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.preview-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.preview-note {
  font-size: var(--t-xs);
  max-width: var(--media-width);
}

.preview-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
  max-width: var(--media-width);
}

/* Живое обновление рисует один стиль - ему вся ширина, а не половина. */
.preview-grid:has(figure:only-child) { grid-template-columns: 1fr; }

/* Шесть кадров - это раскадровка, а не галерея: три в ряд, читается разом. */
.frames-grid { grid-template-columns: repeat(3, 1fr); }
.frames-grid:has(figure:only-child) { grid-template-columns: repeat(3, 1fr); }

/* Черновик и точный кадр стоят встык: сверить их можно только рядом. Пока
   точного кадра нет, черновик занимает всю ширину. */
.preview-pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  max-width: var(--media-width);
}

.preview-pair figure { margin: 0; }

.preview-pair canvas,
.preview-pair img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

/* Подпись на черновике тянется мышкой. Курсор «схватить» показывается только
   над самой подписью: над остальным кадром хватать нечего.
   touch-action нужен, чтобы пальцем тянуть подпись, а не прокручивать страницу. */
#subs-draft { touch-action: none; }
#subs-draft.is-grabbable { cursor: grab; }
#subs-draft.is-dragging { cursor: grabbing; }

/* Про читаемость говорится до рендера, а не после часа ожидания. */
.preview-warn {
  margin: 0;
  font-size: var(--t-xs);
  max-width: var(--media-width);
  color: var(--pending);
}

.preview-warn.is-bad { color: var(--fail); }

.preview-pair figcaption {
  text-align: center;
  font-size: var(--t-xs);
  color: var(--text-muted);
  margin-top: var(--space-1);
}

.preview-grid figure { margin: 0; }

.preview-grid img {
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  display: block;
}

.preview-grid figcaption {
  text-align: center;
  font-size: var(--t-xs);
  color: var(--text-muted);
  margin-top: var(--space-1);
}

/* Раскадровка выпуска. Четыре в ряд, а не одной прокручиваемой лентой:
   смысл в том, чтобы увидеть все двенадцать кадров разом и заметить
   провалившийся в черноту. Уехавшее вбок начало этому мешает. */
.filmstrip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
  max-width: var(--read-width);
}

@media (max-width: 700px) {
  .filmstrip { grid-template-columns: repeat(2, 1fr); }
}

/* Кадр - кнопка: по нему перематывается проигрыватель. Значит, ему нужны
   все четыре состояния, включая фокус с клавиатуры. */
.shot {
  display: block;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-panel);
  cursor: pointer;
  overflow: hidden;
}

.shot:hover { border-color: var(--ch); }
.shot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.shot:active { border-color: var(--ch); }

.shot img {
  width: 100%;
  height: auto;
  display: block;
}

.shot-time {
  display: block;
  padding: var(--space-1) var(--space-2);
  font-size: var(--t-xs);
  color: var(--text-muted);
  text-align: center;
}

.shot:hover .shot-time { color: var(--text); }

.preview-audio-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 480px;
}

.preview-audio-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.preview-audio-row span {
  width: 130px;
  flex-shrink: 0;
  font-size: var(--t-sm);
}

.preview-audio-row.is-current span { color: var(--accent); }

.preview-audio-row audio {
  flex: 1;
  max-width: none;
  margin: 0;
}

.block {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  white-space: pre-wrap;
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  color: var(--text);
  overflow-x: auto;
}

/* Лог лежит на фоне ГЛУБЖЕ панели: это дно экрана, а не ещё одна карточка
 * в ряду с остальными. */
.log { max-height: 360px; overflow-y: auto; background: var(--bg); }

video, audio {
  display: block;
  width: 100%;
  max-width: var(--media-width);
  margin: var(--space-2) 0 var(--space-4);
}

details summary {
  cursor: pointer;
  color: var(--accent);
  margin-bottom: var(--space-2);
}

#upload-form {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.bg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-3);
  margin: var(--space-4) 0;
}

.bg-tile {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-1);
}

.bg-tile img {
  width: 100%;
  height: 100px;
  object-fit: cover;
  border-radius: var(--radius-sm);
  display: block;
  margin-bottom: var(--space-1);
}

.bg-tile form { margin: 0; }

.bg-tile figcaption {
  font-size: var(--t-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Пустое состояние экрана: короткая фраза + кнопка, которая это создаёт.
 * По словарю оно не карточка, а пунктирный проём - место, где содержимое
 * будет, когда появится. Поэтому фон страницы, а не панели, и рамка
 * пунктиром: сплошная читалась как ещё один блок с содержимым. */
.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-8) var(--space-6);
  margin: var(--space-4) 0;
  color: var(--text-muted);
}

/* Фраза - одна строка, и переносить её посреди экрана незачем. */
.empty p { margin: 0; }

/* --- переключатель канала в шапке ---------------------------------------- */

.channel-switch {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

.channel-dot {
  width: var(--space-3);
  height: var(--space-3);
  border-radius: 50%;
  background: var(--ch);
  flex-shrink: 0;
}

.channel-switch select { min-width: var(--select-width); }

/* --- раздел «Каналы» ------------------------------------------------------ */

.channel-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
}

.channel-card {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--ch);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-5);
}

.channel-card.is-archived { opacity: 0.6; }

.channel-card-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.channel-card-head h2 {
  margin: 0;
  color: var(--text);
  font-size: var(--t-h2);
}

.channel-slug { font-family: var(--font-mono); font-size: var(--t-xs); }

.channel-count {
  margin-left: auto;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
}

/* Группы стали вкладками, и видна из них всегда одна: столбику с зазорами
 * здесь больше нечего разделять. Отступ под полоской вкладок задаёт сама
 * .page-tabs. */
.channel-groups { margin: var(--space-4) 0; }

.channel-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-3);
  margin: var(--space-3) 0 0;
}

.channel-fields .field-row { margin-bottom: 0; }

.channel-sched-note { margin: var(--space-3) 0 0; }
.channel-fields input { width: 100%; }
.channel-fields input[type="color"] { padding: var(--space-1); height: var(--space-7); }

/* --- очередь -------------------------------------------------------------- */

.queue-table td { vertical-align: top; }

/* Задание, которое воркер возьмёт следующим - не всегда первая строка:
   строка выше может стоять на лимите или ждать своего окна. */
.queue-table tr.is-next { background: var(--bg-raised); }
.queue-table tr.is-next td:first-child { border-left: var(--space-1) solid var(--ok); }

.queue-actions {
  display: flex;
  gap: var(--space-2);
  white-space: nowrap;
}

.queue-actions form { margin: 0; }

/* Дни недели - это .seg-modes из словаря компонентов; своих правил у них
   осталось одно. Раньше здесь жили семь отдельных фишек, каждая с настоящей
   галочкой внутри: 467 px на колонку, из-за которых таблица расписания
   вылезала из своей панели на 210 px и прятала две последние колонки. */
.weekdays { align-self: start; }

.channel-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.channel-actions form { display: flex; gap: var(--space-2); margin: 0; }

.btn-danger { border-color: var(--fail); color: var(--fail); }
.btn-danger:hover { border-color: var(--fail); color: var(--fail); }

/* «Удалить насовсем» стояло в одном ряду с «Сохранить»: та же кнопка того же
 * размера, промахнуться мышью - дело одного движения. Отжимаем в правый край
 * и делаем тихим - это не действие ряда, а выход из него. Подтверждение
 * вводом имени канала при этом никуда не делось.
 *
 * Цвет и рамка возвращаются под мышью и с клавиатуры: тихая кнопка - это про
 * то, чтобы её не нажали случайно, а не про то, чтобы её не нашли. */
.channel-actions .channel-delete {
  margin-left: auto;
  background: none;
  border-color: transparent;
  color: var(--text-faint);
}

.channel-actions .channel-delete:hover,
.channel-actions .channel-delete:focus-visible {
  border-color: var(--fail);
  color: var(--fail);
}

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

.field-row.is-inherited > [data-group] { display: none; }

/* На карточке канала поле лежит ВНУТРИ своей метки, поэтому селектор здесь
   не прямого потомка. Прячется только ввод: имя поля должно остаться
   видимым, иначе серая строка повиснет без подписи. */
.field-row.is-inherited [data-key] { display: none; }

/* ...а когда серую строку нажали, уходит уже она: значение теперь в поле, и
   держать рядом вторую копию того же числа на плотной карточке незачем.
   Правило нарочно сужено до карточки канала - на странице пресета серая
   строка остаётся видна, и этот срез её поведение не меняет. */
.channel-fields .field-row:not(.is-inherited) .inherited { display: none; }

/* Поле-галочки: значение лежит в скрытом поле, а видно ряд галочек. Прятать
   и гасить надо ряд, иначе унаследованное поле показывает серую кнопку и
   галочки одновременно - два ответа на один вопрос. */
.field-row.is-inherited > .multi-options { display: none; }

.multi-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  max-width: var(--field-width);
}

.multi-option {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.multi-option input { width: auto; }

/* Ползунок: ручка, число и крестик «вернуть не задано» в одну строку.
   В отличие от списка и ряда галочек, унаследованный ползунок НЕ прячется.
   Двух ответов на один вопрос тут не возникает: серая кнопка говорит, откуда
   берётся число, ручка показывает, где оно стоит, а подпись рядом - что своего
   значения у поля пока нет. Спрятать ползунок значило бы спрятать всё, ради
   чего он тут: настройку цвета не видно, пока не нажмёшь на серое. */
.slider-field {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  max-width: var(--field-width);
}

.slider-input {
  flex: 1;
  min-width: 0;
  accent-color: var(--accent);
}

.slider-value {
  min-width: 5.5rem;
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.slider-value.is-unset { color: var(--text-faint); }

/* Две кнопки блока «Цвет» на странице выпуска. */
.regrade-buttons {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

.slider-clear {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--t-sm);
  line-height: 1;
  padding: var(--space-1) var(--space-2);
}

.slider-clear:disabled {
  color: var(--text-faint);
  cursor: default;
  opacity: 0.5;
}

/* Поле, которое при нынешнем выборе ничего не делает: шесть чисел стиля
   «свой» при любом другом стиле субтитров. Гасим, но не прячем - иначе
   человек будет искать глазами поле, которого нет. */
.field-row.is-idle > label,
.field-row.is-idle > [data-group],
.field-row.is-idle > .multi-options,
.field-row.is-idle > .slider-field,
.field-row.is-idle > .inherited,
.field-row.is-idle > .field-help { opacity: 0.45; }

.inherited {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  width: 100%;
  max-width: var(--field-width);
  text-align: left;
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-2);
  cursor: text;
  font-size: var(--t-body);
  font-family: inherit;
}

.inherited:hover { border-color: var(--accent); }

.inherited:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.inherited-value { color: var(--text-faint); }

.inherited-source {
  margin-left: auto;
  color: var(--text-faint);
  font-size: var(--t-xs);
}

/* --- главная страница «Сегодня» ------------------------------------------ */

/* Дата стоит сразу за заголовком, а не у правого края экрана: на широком
 * мониторе между ними была половина стола, и читались они как два разных
 * сообщения. */
.today-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.today-head h1 { margin-bottom: 0; }

.today-now {
  color: var(--text-muted);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
}

/* Точка цвета канала и имя канала рядом. */
.ch-dot {
  display: inline-block;
  width: var(--space-2);
  height: var(--space-2);
  border-radius: 50%;
  background: var(--ch);
  flex-shrink: 0;
}

.ch-name {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}

/* Кадр-заглушка. Настоящих превью нет, поэтому прямоугольник цвета канала
   с номером выпуска — видно, чей выпуск, и видно, что это не фотография. */
.thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--ch), var(--bg-raised));
  border-radius: var(--radius-sm);
  color: var(--bg);
  font-weight: var(--w-semibold);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

/* Числа набираются моноширинным: столбик сумм и времён должен читаться
   колонкой, а не рваным краем. tabular-nums к тому же держит одну ширину
   у всех цифр, поэтому счётчик не дёргается при каждом обновлении. */
.nums {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Быстрый запуск. */
.quick-run {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: 0;
}

.quick-run label {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--t-xs);
  color: var(--text-muted);
}

.quick-run select { min-width: var(--select-width); }

/* Широкое содержимое прокручивается внутри себя, страница вбок не едет. */
.scroll-x { overflow-x: auto; }

.today-section { margin-bottom: var(--space-6); }

/* Заголовок блока - светлый и полужирный. Общий h2 приглушён, и рядом со
 * своей же подписью («ближайшие сутки») он читался как её продолжение:
 * один цвет, один размер, разницы нет. */
.today-section > h2 {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  color: var(--text);
  font-weight: var(--w-semibold);
  margin: var(--space-7) 0 var(--space-3);
}

/* Первый заголовок на странице стоит сразу под полосой тревоги - лишние
   32 пикселя сверху ему не нужны. */
.today-section:first-of-type > h2 { margin-top: var(--space-6); }

.count-badge {
  font-size: var(--t-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Две колонки: слева то, что происходит, справа итоги. */

.c-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: var(--space-6);
  align-items: start;
}

.c-col { min-width: 0; }

.panel {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}

.panel h2 {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  margin-top: 0;
  font-size: var(--t-label);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}

/* Таблица уже внутри панели — своя рамка сверху ей не нужна. */
.panel table.runs { margin-top: var(--space-2); }

/* Поиск и фильтры над списком выпусков. Переносятся на вторую строку, а не
   вылезают за панель: контролов пять, и на ноутбуке они в строку не влезают. */
.run-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-3) 0 var(--space-2);
}

.run-filters input[type="search"] { width: var(--field-width); max-width: 100%; }

.run-filters-reset { font-size: var(--t-sm); color: var(--text-muted); }
.panel .empty { margin: 0; }

/* Сплошной текст читается колонкой, а не полосой во весь экран: на строке в
 * полтораста символов глаз не находит начало следующей. Ширина - тот же
 * --content-width, которым живут узкие страницы. Таблицы, поля и превью
 * правило не трогает: там ширину диктует содержимое, а не чтение. */
.panel > p,
.empty p { max-width: var(--read-width); }

/* Путь и команду не читают, а копируют целиком - их ширину задаёт сам текст. */
.panel > .ff-path,
.panel > .ff-command { max-width: none; }

.c-run {
  border-left: 2px solid var(--ch);
  padding-left: var(--space-3);
  margin-bottom: var(--space-4);
}

.c-run:last-child { margin-bottom: 0; }

.c-run-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
}

.c-run-left {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Название стадии, которая идёт: единственное слово в строке, за которым
 * следят, поэтому оно не приглушено, как остальные. */
.c-run-stage {
  font-family: var(--font-mono);
  font-weight: normal;
  color: var(--accent);
}

.c-run-sub {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  color: var(--text-muted);
  font-size: var(--t-xs);
  margin: var(--space-2) 0;
  font-variant-numeric: tabular-nums;
}

.c-timeline {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.c-slot {
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: var(--space-3);
  align-items: baseline;
  font-size: var(--t-sm);
}

.c-slot-time {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  border-right: 1px solid var(--border);
  padding-right: var(--space-3);
}

.c-slot-what {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.c-mini {
  display: flex;
  gap: var(--space-3);
  align-items: center;
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--t-sm);
}

.c-mini:last-child { border-bottom: 0; }

.c-mini .thumb {
  width: var(--thumb-width);
  height: var(--thumb-height);
  font-size: var(--t-xs);
}

.c-mini-body { min-width: 0; }

/* Название выпуска - заголовок, а не ссылка синим по чёрному: в списке из
 * десяти строк десять синих строк читаются как меню, а не как выпуски.
 * Ссылкой оно быть не перестаёт - это видно по наведению. */
.c-mini-title {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text);
}

.c-mini-title:hover { color: var(--accent); }

.c-mini-meta {
  color: var(--text-muted);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
}

.c-alert {
  border-left: 2px solid var(--fail);
  padding-left: var(--space-3);
  margin-bottom: var(--space-3);
  font-size: var(--t-sm);
}

.c-alert:last-child { margin-bottom: 0; }

.c-alert-err {
  color: var(--fail);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  margin: var(--space-1) 0;
  overflow-wrap: anywhere;
}

/* --- «Сегодня» по макету: четыре компонента ----------------------------------
 *
 * Полоса чисел, полоса тревоги, карточка прогона и полоса стадий. Названия
 * новые, а не поверх .c-run: те же классы носит страница канала, разметки
 * с заголовком, часами и подписью у неё нет, и общий стиль сломал бы её.
 * Переедет она отдельным срезом.
 */

/* Полоса чисел: четыре ячейки в одной рамке, разделённые линиями. Подпись
 * заглавными, значение крупным моноширинным - число здесь главное. */
.statbar {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin: 0 0 var(--space-4);
}

.stat {
  padding: var(--space-3) var(--space-4);
  border-left: 1px solid var(--border);
  min-width: 0;
}

/* Линия только МЕЖДУ ячейками: у первой она совпала бы с рамкой полосы и
 * дала бы двойную черту в два пикселя. */
.stat:first-child { border-left: 0; }

.stat dt {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}

.stat dd {
  margin: var(--space-2) 0 0;
  font-size: var(--t-h1);
}

/* Прямо сейчас что-то происходит - это единственное число полосы, которое
 * меняется, пока на него смотрят. */
.stat .is-live { color: var(--accent); }

/* Полоса тревоги и замка. Тёплый цвет во всём интерфейсе один, и вот его
 * второе (после пилюли перерасхода) законное место. */
.alertbar {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  background: var(--pending-wash);
  border: 1px solid var(--pending-line);
  border-radius: var(--radius-lg);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}

.alert-icon {
  width: var(--icon);
  height: var(--icon);
  flex-shrink: 0;
  margin-top: var(--space-1);
  color: var(--pending);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--stroke-icon);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.alertbar-body { flex: 1 1 auto; min-width: 0; }

.alertbar-title {
  display: block;
  font-weight: var(--w-medium);
}

.alertbar-note {
  display: block;
  color: var(--text-muted);
  font-size: var(--t-sm);
  overflow-wrap: anywhere;
}

/* Кнопка тревоги не заливается: главное действие страницы одно, и это не
 * оно. Тёплая рамка - чтобы её искали внутри полосы, а не по всему экрану. */
.alertbar-do {
  flex-shrink: 0;
  border-color: var(--pending-line);
  color: var(--pending);
}

.alertbar-do:hover { border-color: var(--pending); }

/* Карточка прогона. Рельс цвета канала - фоном, а не рамкой: рамка легла бы
 * внутрь скругления и на углах превратилась бы в дугу. */
.runcard {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: var(--space-4) var(--space-5);
  padding-left: calc(var(--space-5) + var(--rail));
  margin-bottom: var(--space-3);
  overflow: hidden;
}

.runcard::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--rail);
  background: var(--ch);
}

.runcard-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.runcard-what { min-width: 0; }

.runcard-chan {
  display: block;
  color: var(--ch);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}

.runcard-title {
  display: block;
  margin-top: var(--space-1);
  color: var(--text);
  font-size: var(--t-h2);
  font-weight: var(--w-semibold);
}

.runcard-title:hover { color: var(--accent); }

.runcard-clock {
  flex-shrink: 0;
  text-align: right;
}

.runcard-time {
  display: block;
  font-size: var(--t-h1);
}

.runcard-eta {
  display: block;
  color: var(--text-muted);
  font-size: var(--t-sm);
}

.runcard-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  color: var(--text-faint);
  font-size: var(--t-xs);
}

/* Полоса стадий. Ширина сегмента - его длительность: flex-grow берёт вес,
 * посчитанный сервером (today.stage_weights), а не долю от семи. Минимум -
 * чтобы четырёхсекундная стадия не исчезла и не соврала «пропущена». */
.stagebar {
  display: flex;
  gap: var(--space-3);
  margin: var(--space-4) 0 var(--space-3);
}

.seg {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex-grow: var(--w);
  flex-shrink: 1;
  flex-basis: 0;
  min-width: var(--seg-min);
}

.seg-line {
  display: block;
  height: var(--stage-line);
  border-radius: var(--radius-sm);
  background: var(--stage-idle);
  overflow: hidden;
}

.seg-fill {
  display: block;
  height: 100%;
  background: var(--stage-done);
}

.seg.status-running .seg-line { background: var(--stage-track); }
.seg.status-running .seg-fill { background: var(--accent); }
.seg.status-failed .seg-line { background: var(--fail-line); }
.seg.status-failed .seg-fill { background: var(--fail); }

/* Подпись и значение стоят в столбик, друг под другом. В строку их ставить
 * нельзя: у соседних сегментов они слипаются в одно слово. */
.seg-name {
  font-size: var(--t-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.seg-value {
  color: var(--text-muted);
  font-size: var(--t-xs);
}

/* Пройденная стадия называется белым, а не зелёным. Класс status-done красит
 * элемент в --ok глобально (см. .status-ok / .status-done выше по файлу), и
 * без этой строки семь имён стадий зеленели бы разом: цвет в полосе несёт
 * линия, а не буквы. */
.seg.status-done .seg-name { color: var(--text); }

.seg.status-pending .seg-name { color: var(--text-faint); }
.seg.status-running .seg-name,
.seg.status-running .seg-value { color: var(--accent); }
.seg.status-failed .seg-name { color: var(--fail); }

/* Низ экрана: очередь шире, чем список вышедших - в ней четыре колонки. */
.today-cols {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--space-6);
  align-items: start;
}

/* Ссылка «все 11» уезжает к правому краю заголовка блока. */
.today-more {
  margin-left: auto;
  font-size: var(--t-sm);
  font-weight: var(--w-regular);
  text-transform: none;
  letter-spacing: normal;
}

.queue-table td { vertical-align: top; }
.ta-right { text-align: right; }

/* Пометка появляется, только когда есть что сказать: «ждёт: суточный лимит»
 * или «упало, попытка 2 из 3». Иначе строка очереди молчит. */
.queue-note {
  display: block;
  color: var(--text-faint);
  font-size: var(--t-xs);
}

.queue-note.is-waiting { color: var(--pending); }

/* Кадр выпуска в списке «вышло за сутки». Рамка стоит всегда - и под кадром,
 * и под плашкой-заглушкой, иначе строки прыгают на пиксель. */
.c-mini .shot {
  position: relative;
  width: var(--shot-width);
  height: var(--shot-height);
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg-raised);
  overflow: hidden;
}

.c-mini .shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Заглушка занимает весь кадр целиком, а не свои 64×32 из общего правила. */
.c-mini .shot .thumb {
  width: 100%;
  height: 100%;
  border-radius: 0;
  font-size: var(--t-xs);
}

.shot-time {
  position: absolute;
  right: var(--space-1);
  bottom: var(--space-1);
  padding: 0 var(--space-1);
  background: var(--bg);
  border-radius: var(--radius-sm);
  font-size: var(--t-label);
}

.c-mini-chan {
  display: block;
  color: var(--ch);
  font-size: var(--t-label);
  text-transform: uppercase;
  letter-spacing: var(--track-label);
}

/* Узкое окно: колонки становятся одна под другой, вбок ничего не едет. */
@media (max-width: 800px) {
  .c-cols { grid-template-columns: 1fr; }
}

/* У «Сегодня» порог свой и он выше: слева от содержимого стоит колонка
 * разделов в 216 пикселей, а в таблице очереди четыре колонки. При 900
 * пикселях окна таблице оставалось 400, и она наезжала на соседний блок. */
@media (max-width: 1100px) {
  .today-cols { grid-template-columns: 1fr; }
  .statbar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Третья ячейка встала в новый ряд первой - линия слева у неё снова
     совпала бы с рамкой полосы и дала бы двойную черту. */
  .stat:nth-child(3) { border-left: 0; }
}

/* То, чего в очереди ещё нет: расписание только собирается это поставить. */
.c-slot.is-planned { opacity: 0.7; }

/* Строка расписания: поля должны помещаться, но не расползаться. */
.sched-table td { vertical-align: top; }
.sched-table input[type="time"] { width: auto; }
.sched-table input[type="number"] { width: var(--w-number); }
.sched-table select { min-width: var(--select-width); }

/* Семь сегментов в строке - теснее, чем три на странице пресета: там группа
   стоит одна в поле, здесь их семь в колонке из девяти. Сужение только
   здесь, общий компонент не трогаем. */
.sched-table .seg-mode span { padding-inline: var(--space-1); }

/* --- шапка страницы пресета ----------------------------------------------- */
/* «Рандом» стоял отдельной панелью наверху, а «Сохранить» - в самом низу
 * свитка, за шестью группами полей. Выкатив значения, человек их не сохранял:
 * кнопки на экране не было. Теперь это одна группа в правом верхнем углу. */

.preset-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}

/* Вторая страница, которой заголовок нужен самой крупной ступенью шкалы.
 * Общий h1 стоит на двадцати страницах, и трогать его ради этой нельзя;
 * .run-title сюда не годится - имя врало бы о том, что это за страница. */
.preset-title {
  font-size: var(--t-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-tight);
  margin: 0;
}

/* Кнопки не сжимаются рядом с заголовком, но на узком экране переносятся
   внутри его ширины: без переноса три кнопки лаборатории уводили страницу
   на телефоне вбок на 75 px, а редактор пресета - на 25. */
.preset-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
  flex-wrap: wrap;
  max-width: 100%;
}

/* Сид - не поле формы, а приписка к «Рандому»: своей рамки у него нет,
 * рамка одна на подпись и число. */
.seed-pill {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--h);
  margin-bottom: 0;
  padding: 0 var(--space-3);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  color: var(--text-muted);
}

.seed-pill input {
  width: auto;
  height: auto;
  padding: 0;
  background: none;
  border: 0;
  color: var(--text);
}

.seed-pill input:focus-visible { outline: none; }
.seed-pill:focus-within { border-color: var(--accent); }

/* Обе строки состояния в одном месте: «выпало полей: 7» и «сохранено»
 * отвечают на одно и то же нажатие и читаются подряд. */
.preset-status {
  display: flex;
  gap: var(--space-3);
  min-height: var(--t-body);
  margin: 0 0 var(--space-4);
  font-size: var(--t-sm);
}

/* --- режимы полей и «Рандом» в редакторе пресета -------------------------- */

/* Заголовок группы и приписка к нему. Приписка стоит в строку с названием,
 * а не абзацем под ним: это одна мысль, и абзац оторвал бы её от полей. */
.group-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.group-hint {
  font-family: var(--font-ui);
  font-size: var(--t-sm);
  font-weight: var(--w-regular);
  letter-spacing: normal;
  color: var(--text-muted);
}

/* Примечание под панелью: объясняет не поле, а то, что делает вся группа. */
.panel-note {
  max-width: var(--read-width);
  margin: var(--space-3) 0 0;
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* Форма не тянется шире читаемой колонки: строка поля и так в три зоны,
 * а на широком мониторе подпись уезжала от своего контрола через полэкрана. */
.preset-form .tab-panel { max-width: var(--read-width); }

/* --- правая колонка пресета ------------------------------------------------ */
/* Видна на любой вкладке - в этом вся её работа. «Рандом» раскидывает значения
 * по шести группам разом, и без неё пять шестых выпавшего оставались за
 * кадром. Блоки те же, что на странице выпуска: .rail-block и .rail-label
 * написаны там и второй раз не переписываются. */

.preset-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--rail-width);
  gap: var(--space-7);
  align-items: start;
}

.preset-main { min-width: 0; }

.preset-rail {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
  /* Колонка едет со страницей, но не уезжает выше шапки приложения. */
  position: sticky;
  top: var(--space-4);
}

/* Кадр в колонке: 312 пикселей вместо 640, поэтому своя мерка, а не
 * --media-width. Пропорции кадра держит сам canvas. */
.rail-shot { margin: 0; }

.rail-shot canvas {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius);
  border: 1px solid var(--border);
}

.rail-shot figcaption {
  margin-top: var(--space-1);
  text-align: center;
  font-size: var(--t-xs);
  color: var(--text-muted);
}

/* «не тронуто» - не значение, а ответ на вопрос «почему не поменялось».
 * Поэтому тусклым и не моноширинным: это слова, а не число. */
.rail-pair dd.is-idle { color: var(--text-faint); }

/* Узкое окно: колонка уезжает под поля. Порог тот же, что у «Сегодня» и у
 * выпуска, и по той же причине - слева 216 пикселей разделов. */
@media (max-width: 1100px) {
  .preset-cols { grid-template-columns: minmax(0, 1fr); }

  .preset-rail {
    position: static;
    max-width: var(--read-width);
  }
}

.field-modes {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* Сегменты вместо выпадающего списка: режимов у поля два или три, и все они
 * помещаются на экран разом. Список прятал их за нажатием - прочесть, что у
 * поля вообще бывает, было нельзя, не открыв его. Под низом это радиокнопки:
 * стрелки с клавиатуры работают сами, писать для них ничего не нужно. */
.seg-modes {
  display: flex;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.seg-mode { margin: 0; cursor: pointer; }

/* Радиокнопка не прячется в display:none: так она выпадает из обхода с
 * клавиатуры вместе со своей группой. Уводим её с глаз, оставив на месте. */
.seg-mode input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.seg-mode span {
  display: block;
  padding: var(--space-1) var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}

.seg-mode + .seg-mode span { border-left: 1px solid var(--border); }

.seg-mode:hover span { color: var(--text-muted); }

.seg-mode input:checked + span {
  background: var(--bg-panel);
  color: var(--text);
}

.seg-mode input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Замок: пока он не висит - тихая иконка рядом с сегментами. Повис - янтарная
 * плашка ВМЕСТО сегментов, потому что под замком режим ничего не решает. */
.lock-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-faint);
  padding: var(--space-1) var(--space-2);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}

.lock-icon {
  width: var(--icon);
  height: var(--icon);
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--stroke-icon);
  stroke-linecap: round;
}

/* Дужка у открытого замка откинута, у закрытого - опущена. Рисунка два,
 * видно всегда один: подменять путь скриптом значило бы держать форму замка
 * в двух местах сразу. */
.lock-icon .lock-shackle { display: none; }
.lock-btn[aria-pressed="true"] .lock-icon .lock-shackle { display: inline; }
.lock-btn[aria-pressed="true"] .lock-icon .lock-shackle-open { display: none; }

.lock-word { display: none; }

.lock-btn:hover { color: var(--text-muted); }

.lock-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.lock-btn[aria-pressed="true"] {
  background: var(--pending-wash);
  border-color: var(--pending-line);
  color: var(--pending);
}

.lock-btn[aria-pressed="true"] .lock-word { display: inline; }

.field-row.is-locked .seg-modes { display: none; }

/* Тот же замок у имени поля: правая часть строки далеко, а по столбику имён
 * сразу видно, какие поля бросок не трогает вовсе. */
.lock-mark {
  display: none;
  width: var(--icon);
  height: var(--icon);
  vertical-align: -2px;
  fill: none;
  stroke: var(--pending);
  stroke-width: var(--stroke-icon);
  stroke-linecap: round;
}

.field-row.is-locked .lock-mark { display: inline-block; }

.rule-source { color: var(--text-faint); font-size: var(--t-xs); }

.field-rule {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: 1px solid var(--border);
}

/* На странице пресета блок рамок - это и есть средняя зона строки, а не
 * приписка под полем: линия слева читалась бы как «вложено во что-то». */
.preset-form .field-rule {
  margin-top: 0;
  padding-left: 0;
  border-left: 0;
}

.rule-range {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* --- диапазон двумя ручками ------------------------------------------------ */
/* Было два поля «от» и «до»: числа честные, но увидеть, широки ли рамки и где
 * они стоят внутри разумных, было нельзя. Поля никуда не делись - они и есть
 * значение, - но при ползунке уходят с глаз. */

.rule-range.has-slider .range-num { display: none; }

.range-end {
  font-size: var(--t-xs);
  color: var(--text-faint);
  white-space: nowrap;
}

.range-pair {
  position: relative;
  flex: 1;
  min-width: 0;
  height: var(--icon);
  display: flex;
  align-items: center;
}

/* Дорожка и заливка между ручками - один фон. Доли приходят разметкой в
 * --from и --to, как вес сегмента в полосе стадий: считает их JS, красит CSS,
 * и числа в правилах вида не заводится. */
.range-track {
  position: absolute;
  left: 0;
  right: 0;
  height: var(--range-track);
  border-radius: var(--radius-sm);
  background: linear-gradient(to right,
    var(--stage-idle) 0 var(--from),
    var(--accent) var(--from) var(--to),
    var(--stage-idle) var(--to) 100%);
}

/* Две дорожки поверх одной нарисованной не нужны, поэтому у самих ползунков
 * фона нет, а кликается только ручка: без pointer-events верхний из двух
 * перехватывал бы нажатия по всей ширине, и нижняя ручка не бралась бы. */
.range-pair input[type="range"] {
  position: absolute;
  left: 0;
  width: 100%;
  height: var(--icon);
  margin: 0;
  padding: 0;
  background: none;
  appearance: none;
  -webkit-appearance: none;
  pointer-events: none;
}

.range-pair input[type="range"]::-webkit-slider-runnable-track { background: none; }
.range-pair input[type="range"]::-moz-range-track { background: none; }

.range-pair input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  pointer-events: auto;
  width: var(--range-thumb);
  height: var(--range-thumb);
  border-radius: 50%;
  background: var(--accent);
  border: 0;
  cursor: pointer;
}

.range-pair input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: var(--range-thumb);
  height: var(--range-thumb);
  border-radius: 50%;
  background: var(--accent);
  border: 0;
  cursor: pointer;
}

/* Обводка фокуса садится на саму ручку: сам ползунок прозрачен во всю ширину,
 * и рамка вокруг него обвела бы пустое место. */
.range-pair input[type="range"]:focus-visible { outline: none; }

.range-pair input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--space-1) var(--accent-wash);
}

.range-pair input[type="range"]:focus-visible::-moz-range-thumb {
  box-shadow: 0 0 0 var(--space-1) var(--accent-wash);
}

.range-out {
  min-width: calc(var(--w-number) * 1.5);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: right;
}

.rule-range label,
.rule-list label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 0;
  font-size: var(--t-xs);
}

.rule-range input { width: 6rem; font-variant-numeric: tabular-nums; }
.rule-list .rule-options-text { max-width: var(--field-width); }

.rule-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Список разрешённого - чипами, а не столбиком галочек. Вариантов у поля
 * бывает девять, и столбик из девяти строк занимал больше места, чем всё
 * остальное поле вместе взятое. Отмеченный чип залит, неотмеченный обведён
 * пунктиром: разница видна, не читая. */
.rule-option {
  display: inline-flex;
  align-items: center;
  margin: 0;
  cursor: pointer;
}

/* Галочка не прячется в display:none - так она выпадает из обхода с
 * клавиатуры. Уводим с глаз, оставив на месте; то же, что у сегментов. */
.rule-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.rule-option span {
  display: block;
  padding: var(--space-1) var(--space-3);
  border: 1px dashed var(--border);
  border-radius: var(--radius-xl);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--text-faint);
  white-space: nowrap;
}

.rule-option:hover span { color: var(--text-muted); }

.rule-option input:checked + span {
  background: var(--accent-wash);
  border-style: solid;
  border-color: var(--accent-line);
  color: var(--accent);
}

.rule-option input:focus-visible + span {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.field-range { color: var(--text-faint); }

/* Подпись только для читалки с экрана. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- ворота после истории -------------------------------------------------- */
/* Плашка стоит над вкладками: это главное, что можно сделать на странице
   остановленного выпуска, и во вкладку её прятать нельзя. Рамка цветом
   ожидания - тем же, каким полоса стадий красит стадию, которая ещё не шла. */

.gate-box {
  background: var(--bg-panel);
  border: 1px solid var(--pending);
  border-radius: var(--radius);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  max-width: var(--read-width);
}

.gate-box h2 {
  margin-top: 0;
  font-size: var(--t-body);
  color: var(--pending);
}

.gate-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0 var(--space-3);
}

/* --- повтор выпуска ------------------------------------------------------- */

.repeat-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.repeat-new-story {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 0;
  color: var(--text);
}

.repeat-new-story input { width: auto; }

/* Промпт плана - это десять килобайт текста, и правят его целиком, а не по
   строчке. Поле должно быть высоким, моноширинным и не переносить строки
   молча: перенос сбил бы с толку там, где важен исходный вид абзаца. */
.prompt-text {
  display: block;
  width: 100%;
  min-height: var(--editor-height);
  resize: vertical;
  background: var(--bg-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  line-height: var(--lh);
}

.prompt-text.is-short { min-height: var(--editor-height-sm); }
.prompt-text.is-medium { min-height: var(--editor-height-md); }

/* Поле, которое тянется под текст: высоту ему ставит JS, поэтому своя
 * прокрутка внутри не нужна, а ручка изменения размера спорила бы с ним. */
.prompt-text.is-grow {
  overflow-y: hidden;
  resize: none;
}

.prompt-text:focus {
  outline: none;
  border-color: var(--accent);
}

.version-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.version {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-left: var(--space-1) solid var(--border);
  border-radius: var(--radius-sm);
}

/* Версия, которая сейчас лежит в prompts/ - её конвейер и читает. */
.version.is-current { border-left-color: var(--ok); }

.version-when { color: var(--text-faint); font-size: var(--t-xs); }
.version-note { color: var(--text); font-size: var(--t-sm); }

.version-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--t-xs);
}

.version-actions form { margin: 0; }

/* --- Отличия версий ------------------------------------------------------ */

.diff {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: var(--t-sm);
}

.diff-line {
  padding: 0 var(--space-2);
  white-space: pre;
  color: var(--text-muted);
}

.diff-add { color: var(--ok); }
.diff-del { color: var(--fail); }
.diff-hunk { color: var(--text-faint); background: var(--bg-raised); }

/* --- Эскизы: планы рядом, чтобы сравнить ниши ---------------------------- */

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

.lab-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--space-7);
  align-items: start;
}

.lab-main, .lab-side { min-width: 0; }

.lab-foot {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.lab-foot input { flex: 1; min-width: 0; }

.lab-note-label { margin-bottom: 0; white-space: nowrap; }

.lab-diff { white-space: nowrap; font-size: var(--t-sm); }

/* Версии открывают редко, а столбик занимал места столько же, сколько сам
 * текст промпта. Свёрнуты, но остались при редакторе: вопрос «что я менял
 * в прошлый раз» задают, глядя на текст. */
.lab-versions {
  margin-top: var(--space-6);
  border-top: 1px solid var(--border);
  padding-top: var(--space-3);
}

.lab-versions summary {
  cursor: pointer;
  color: var(--text-muted);
  font-size: var(--t-sm);
}

.lab-versions summary:hover { color: var(--text); }

/* Узкое окно: эскизы уезжают под редактор. Порог тот же, что у остальных
 * двухколонных экранов - слева 216 пикселей разделов. */
@media (max-width: 1100px) {
  .lab-cols { grid-template-columns: minmax(0, 1fr); }
}

/* Пять планов рядом. Колонка не уже 260px: план читают, а не разглядывают,
   и на узком экране лучше два ряда, чем пять полосок по три слова. */
.sketch-results {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-3);
  align-items: start;
}

.sketch {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-top: var(--space-1) solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  min-width: 0;
}

.sketch-running { border-top-color: var(--accent); }
.sketch-done { border-top-color: var(--ok); }
.sketch-failed { border-top-color: var(--fail); }

.sketch-seed {
  font-size: var(--t-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--accent);
}

/* Завязка - то единственное, что читают на карточке. Поэтому она, а не план,
 * стоит под заголовком в полный размер текста. */
.sketch-logline {
  margin: 0;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

.sketch summary {
  cursor: pointer;
  font-size: var(--t-xs);
  color: var(--text-faint);
}

.sketch summary:hover { color: var(--text-muted); }

.sketch-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.sketch-title {
  margin: 0;
  color: var(--text);
  font-size: var(--t-body);
  font-weight: var(--w-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
}

.sketch-state { color: var(--text-muted); font-size: var(--t-xs); }
.sketch-running .sketch-state { color: var(--accent); }
.sketch-done .sketch-state { color: var(--ok); }
.sketch-failed .sketch-state { color: var(--fail); }

.sketch-facts { margin: 0; font-size: var(--t-xs); }

/* Имена из плана под ценой эскиза. Тот же --pending, что и у имени, которое
   план обещал, а история не назвала: и там, и здесь это «загляни сюда». */
.sketch-names {
  margin: 0;
  font-size: var(--t-xs);
  color: var(--text-muted);
}

.sketch-names.is-absent { color: var(--pending); }

/* План длинный. Он прокручивается внутри своей карточки - страница вбок
   не едет никогда. */
.sketch-plan, .sketch-error {
  max-height: 360px;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  line-height: var(--lh);
  color: var(--text);
}

.sketch-error { color: var(--fail); }

/* Написанное по черновику (этап 4в): пачки одна под другой, у каждой -
   время сверху мелко. Под заголовком карточки - по какой версии написано;
   устаревшее тем же --pending, что и «план не назвал имён»: загляни сюда. */
.lab-results { display: flex; flex-direction: column; gap: var(--space-4); }
.lab-batch { display: flex; flex-direction: column; gap: var(--space-2); }
.lab-batch-head { margin: 0; font-size: var(--t-xs); }
.sketch-version { margin: 0; font-size: var(--t-xs); color: var(--text-muted); }
.sketch-version.is-old { color: var(--pending); }

/* Проверки (этап 5): замечания списком, кнопки шагов - рядком. */
.lab-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.lab-check { display: flex; flex-direction: column; gap: var(--space-1); }
.check-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  font-size: var(--t-xs);
  overflow-wrap: anywhere;
}
.check-list .muted { display: block; }


/* --- Сравнение двух пресетов --------------------------------------------- */

.compare-controls {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.compare-sides {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-4);
}

.compare-side {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.compare-still { margin: 0; }

.compare-still img {
  width: 100%;
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.compare-still figcaption {
  font-size: var(--t-xs);
  margin-top: var(--space-1);
}

.compare-diff td { font-variant-numeric: tabular-nums; }

/* Узкий экран: две колонки не влезают - складываем их одна под другой,
   вбок страница не едет никогда. */
@media (max-width: 900px) {
  .compare-sides { grid-template-columns: 1fr; }
}

/* Итог по выпуску: деньги и время. Плитки в ряд, переносятся на узком
   экране - вбок страница не едет никогда. */
.run-total {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-3);
}

.total-fact {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  min-width: var(--w-number);
}

.total-fact dt {
  color: var(--text-muted);
  font-size: var(--t-xs);
}

.total-fact dd {
  margin: 0;
  color: var(--text);
  font-size: var(--t-h2);
  font-variant-numeric: tabular-nums;
}

.total-note { font-size: var(--t-xs); margin: 0 0 var(--space-3); }

/* Статистика: месяц работы, строка на канал. */
.month-switch {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.btn.is-off {
  color: var(--text-faint);
  cursor: default;
}

.stats-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: var(--space-4);
  font-variant-numeric: tabular-nums;
}

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

/* Вид шапки - общий, рядом с table.runs th. Здесь остаётся только то, чем
   эта таблица отличается: её строки не переносятся. */

.stats-channel {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text);
  font-weight: var(--w-medium);
}

/* Полоска денег - доля от самого дорогого канала месяца. Простой график,
   без единой внешней библиотеки: это просто ширина блока. */
.stats-money {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: var(--w-number);
}

.stats-bar {
  display: block;
  height: var(--space-1);
  background: var(--bg-raised);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.stats-fill {
  display: block;
  height: 100%;
  background: var(--ch);
}

.stats-failed { color: var(--fail); }

/* «На что ушло» (UX.md Ст1): две таблички рядом, на узком экране - одна под
   другой. Полоска доли - нейтральная: цвет канала только у канала, а это
   стадии и модели всех каналов вместе. */
.spend-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-4);
  align-items: start;
}

.spend-fill { background: var(--text-muted); }

/* «Удачный» и заметка под названием выпуска (UX В6). */
.episode-note {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.episode-note-text { flex: 1 1 260px; min-width: 0; }

.episode-note-text summary {
  cursor: pointer;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

.episode-note-text form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.episode-note-text textarea { max-width: var(--read-width); }

/* «Сделать новый голос»: голос и доля в строку, на телефоне - друг под другом. */
.blend-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: var(--read-width);
}

.blend-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.blend-row select { flex: 1 1 220px; min-width: 0; }
.blend-row input[type="range"] { flex: 1 1 140px; }
.blend-row output { min-width: var(--w-number); }

.blend-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* «Пользователи»: имя и кнопка в строку; сообщение с паролем - на ширину текста. */
.user-new {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}

.user-fresh {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  max-width: var(--read-width);
}

.user-fresh h2, .user-fresh p { margin: 0; }

/* Начало заметки под названием в списке «Выпуски». */
.run-note {
  display: block;
  font-size: var(--t-xs);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* Тренд: двенадцать недель столбиками. Ни одной библиотеки графиков - это
   обычные блоки, у которых задана высота в процентах, ровно как полоска денег
   в таблице выше. Три графика идут друг под другом нарочно: недели у них одни
   и те же, и в столбик стоят под столбиком - иначе сравнивать нечем. */
.trend {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-7);
}

.trend-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.trend-head h2, .month-title {
  margin: 0;
  font-size: var(--t-h2);
}

.trend-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--t-sm);
  color: var(--text-muted);
}

.trend-legend li {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.trend-charts {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.chart {
  margin: 0;
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
}

.chart figcaption {
  color: var(--text-muted);
  font-size: var(--t-sm);
  margin-bottom: var(--space-2);
}

.chart-body {
  display: flex;
  align-items: flex-end;
  gap: var(--space-1);
  min-width: var(--chart-width);
}

.chart-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* Столбик растёт снизу вверх, поэтому column-reverse. Пустая часть - это
   дорожка того же цвета, что и полоска денег в таблице. */
.chart-stack {
  display: flex;
  flex-direction: column-reverse;
  height: var(--chart-height);
  background: var(--bg-raised);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.chart-piece {
  display: block;
  background: var(--ch);
  flex-shrink: 0;
}

.chart-piece.is-plain { background: var(--accent); }

.chart-tick {
  text-align: center;
  font-size: var(--t-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* Неделя, которая ещё идёт: в ней два дня из семи, и низкий столбик тут не
   про то, что дело пошло хуже. Бледный - и подпись под графиком об этом. */
.chart-col.is-now .chart-piece { opacity: 0.5; }
.chart-col.is-now .chart-tick { color: var(--text-faint); }

/* Сумма недели под столбиком (UX.md Ст3): раньше она жила только во
   всплывающей подсказке, а на телефоне подсказку не вызвать. */
.chart-value {
  text-align: center;
  font-size: var(--t-xs);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  white-space: nowrap;
}

.chart-col.is-now .chart-value { color: var(--text-faint); }

/* Кнопки под текстом описания: сохранить, скопировать и слово о том, что
   произошло. Слово нужно рядом с кнопкой, а не наверху страницы: смотрят
   туда, куда только что нажали. */
.delivery-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

/* Три поля, которые вставляют в форму YouTube: заголовок, описание, теги.
   У каждого своя строка-шапка: подпись слева, счётчик букв и «Скопировать»
   справа. Кнопка стоит у своего поля, а не общим рядом внизу: копируют по
   одному, переключаясь во вкладку YouTube и обратно. */
.publish-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-6);
  max-width: var(--read-width);
}

.publish-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.publish-name { color: var(--text); font-size: var(--t-body); }

/* Счётчик прижимает себя и кнопку к правому краю поля. */
.publish-head .counter { margin-left: auto; }

.counter { font-size: var(--t-xs); }

/* Длиннее, чем примет YouTube. Предупреждение, а не запрет: чужой текст
   мы не обрезаем. */
.counter.is-over { color: var(--pending); }

.publish-line {
  width: 100%;
  background: var(--bg-raised);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3);
  font-size: var(--t-body);
}

.publish-line:focus { outline: none; border-color: var(--accent); }

/* Отступ между полями держит gap блока, а не поля поодиночке. */
.publish-block .field-help { margin: 0; }

/* Отметка «выложено»: дата, ссылка и кнопка в одну строку, на узком экране -
   в столбик. Поля разной ширины: дата короткая и фиксированная, ссылка
   длинная и тянется. */
.publish-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.publish-field { margin: 0; }
.publish-field.is-wide { flex: 1 1 var(--field-width); }
.publish-field.is-wide input { width: 100%; }

.published-line { margin: 0 0 var(--space-3); }

/* Чек-лист перед публикацией. Пояснение под каждым пунктом длинное и читается
   один раз - поэтому приглушённое и с отступом под галочку, чтобы взгляд шёл
   по самим пунктам, а не по объяснениям. */
.checklist {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: var(--read-width);
}

.check-line {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0;
  cursor: pointer;
}

.check-label { color: var(--text); font-size: var(--t-body); }

.check-line input[type="checkbox"] { accent-color: var(--ch); }

.check-help { margin-left: var(--space-6); }

.check-hint { color: var(--text-faint); }

/* Шесть вариантов обложки: сетка 3x2, на узком экране - по два в ряд.
   Плитка та же (.shot), что у раскадровки: по ней так же кликают, и вести
   себя под курсором они обязаны одинаково. */
.cover-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  max-width: var(--read-width);
  margin-top: var(--space-3);
}

@media (max-width: 700px) {
  .cover-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Список прямо в строке с кнопкой: подпись и список рядом, а не друг под
   другом, как в форме. */
.head-field {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--t-sm);
  color: var(--text-muted);
}

/* Обложка: картинка слева, настройки справа; на узком экране - друг под
   другом. Картинка задаёт ширину колонки, потому что смотрят на неё. */
.cover-layout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  margin-top: var(--space-3);
  align-items: flex-start;
}

.cover-shot { flex: 1 1 var(--media-width); max-width: var(--media-width); }

.cover-shot img {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.cover-controls { flex: 1 1 var(--field-width); }
.cover-controls .field-row select,
.cover-controls .field-row input { width: 100%; max-width: var(--field-width); }

.cover-empty {
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: var(--space-6);
  margin: 0;
  text-align: center;
}

.cover-note {
  margin-top: var(--space-2);
  max-width: var(--field-width);
  color: var(--pending);
}

/* Адрес папки в Drive. Моноширинный: его сверяют посимвольно, а не читают. */
.drive-path { margin: 0 0 var(--space-2); }

.drive-target {
  font-family: var(--font-mono);
  font-size: var(--t-sm);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
}

/* Готовый путь под полями канала: его читают целиком, поэтому во всю ширину
   карточки, а не в узкой колонке рядом с остальными полями. */
.channel-fields .field-row.is-full { grid-column: 1 / -1; }

.field-row .drive-target { display: block; width: fit-content; }


/* --- Что умеет ffmpeg (страница настроек) -------------------------------- */

.ff-path {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--text-muted);
  margin: var(--space-2) 0;
}

.ff-list {
  list-style: none;
  margin: var(--space-3) 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.ff-list li,
.ff-line {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--t-sm);
  margin: var(--space-2) 0;
}

.ff-name { font-family: var(--font-mono); font-size: var(--t-xs); }

/* Команду копируют руками, поэтому она не переносится по буквам, а едет
   вбок внутри своей рамки - страница вбок не едет. */
.ff-command {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  margin: var(--space-2) 0 0;
  overflow-x: auto;
  white-space: pre;
}

/* Голоса канала: строка на голос, поле и кнопка «убрать» в одну линию. */
.voice-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  list-style: none;
  margin: 0 0 var(--space-4);
  padding: 0;
}

/* Перенос: поле, язык голоса (этап 8б), «по умолчанию» и «Убрать» в одну
   строку на телефоне не помещаются - без него уезжала вбок вся страница. */
.voice-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.voice-row input { min-width: 200px; }

.voice-add,
.voice-default {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--t-xs);
  color: var(--text-muted);
  max-width: 420px;
}

.voice-default { margin-bottom: var(--space-4); }

/* Качество текста: замечания check_story.py на странице выпуска.

   Полоска слева цветом --pending, а не --fail: это «загляни сюда», а не
   «сломалось». Правило про смысловые цвета соблюдено - оттенок здесь и есть
   смысл, а не украшение. */

.findings {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: var(--read-width);
}

.finding {
  background: var(--bg-panel);
  border: 1px solid var(--border);
  border-left: 3px solid var(--pending);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
}

.finding-head { font-size: var(--t-body); }

.finding-where {
  color: var(--text-muted);
  margin-right: var(--space-2);
}

.finding-detail {
  font-size: var(--t-sm);
  margin-top: var(--space-1);
}

.finding-clean { color: var(--ok); }

/* Имена из плана одной строкой. Не таблицей: их четыре-шесть, и смотрят на
   них не по столбику, а разом - «все ли на месте». */

.name-counts {
  font-size: var(--t-sm);
  line-height: var(--lh);
  margin-top: var(--space-4);
  max-width: var(--read-width);
}

.name-count { white-space: nowrap; }

.name-count span { color: var(--text-muted); }

/* Имя, которое план обещал, а история не назвала ни разу. */
.name-count.is-absent,
.name-count.is-absent span { color: var(--pending); }

.parts-table { max-width: var(--read-width); }

/* Проверка на повтор. Полоска слева у находки говорит, насколько это
   тревожно; цвет тут не украшение, а сам смысл строки. */

.similar-verdict {
  max-width: var(--read-width);
  font-size: var(--t-body);
  margin-top: var(--space-2);
}

.similar-verdict.is-ok { color: var(--ok); }
.similar-verdict.is-warn { color: var(--pending); }
.similar-verdict.is-bad { color: var(--fail); }

.finding.is-ok { border-left-color: var(--ok); }
.finding.is-bad { border-left-color: var(--fail); }

/* Совпавший кусок - словами оригинала. Читают в первую очередь его: процент
   можно оспорить, двадцать слов подряд из прошлого выпуска нельзя. */
.finding-quote {
  font-size: var(--t-sm);
  color: var(--text-muted);
  margin-top: var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid var(--border);
}

/* --- обзор канала --------------------------------------------------------- */

/* Шапка страницы канала. Точка цвета стоит внутри заголовка, а не рядом:
   имя и цвет - это одно, и на узком экране они не должны разъезжаться. */
.ch-head { flex-wrap: wrap; }

.ch-head h1 {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: 0;
}

.ch-head-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--t-sm);
  /* Факты держатся имени, а не кнопок: между ними и кнопками справа
     остаётся пустое место, иначе строка читается как одна каша. */
  margin-right: auto;
}

.ch-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.ch-actions form { display: flex; align-items: center; gap: var(--space-2); margin: 0; }

/* Пары «что - чему равно». Две колонки: подпись слева узкая, ответ справа
   во всю оставшуюся ширину. Список определений, а не таблица: это и есть
   определения, и читалка экрана прочтёт их парами. */
.ch-kv {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: var(--space-2) var(--space-4);
  margin: 0 0 var(--space-4);
  font-size: var(--t-sm);
}

.ch-kv dt { color: var(--text-muted); }

.ch-kv dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Откуда приехала настройка - подпись при значении, поэтому мельче его. */
.ch-kv dd .muted { font-size: var(--t-xs); }

/* Обложка выпуска на месте заглушки-номера: та же мерка, чтобы строки
   с обложкой и без неё стояли одной высоты. */
.ep-cover {
  width: var(--thumb-width);
  height: var(--thumb-height);
  object-fit: cover;
}

/* Путь на Drive в узкой колонке. Переносится по любому месту, а не распирает
   страницу вбок: в нём нет пробелов, а колонка тут 340px. */
.ch-drive {
  display: block;
  overflow-wrap: anywhere;
}

/* --- «Готов ли к ночи» ------------------------------------------------------
 *
 * Шесть строк одного вида: слева плашка состояния, справа объяснение. Плашка
 * стоит первой и не тянется: взгляд идёт сверху вниз по одной колонке и
 * останавливается там, где цвет не зелёный. */
.ready-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.ready-when { margin-left: auto; }

.ready-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ready-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

/* Ширина плашки одна на все шесть строк - иначе объяснения встают лесенкой. */
.ready-row > .status {
  flex: 0 0 auto;
  min-width: 6.5rem;
  text-align: center;
}

.ready-body { min-width: 0; }

.ready-head { margin: 0; }

.ready-lines {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
  color: var(--text-muted);
  font-size: var(--t-sm);
}

.ready-body > p:last-child { margin-bottom: 0; }

/* Узкое окно: плашка встаёт над объяснением, а не сжимает его в столбик букв. */
@media (max-width: 800px) {
  .ready-row { flex-direction: column; }
  .ready-when { margin-left: 0; }
}

/* --- вход ----------------------------------------------------------------- */

/* Единственная страница без каркаса: карточка посреди пустого экрана. body уже
   колонка во всю высоту, поэтому центрировать нечего - хватает двух свойств. */
.login-page {
  justify-content: center;
  align-items: center;
  padding: var(--space-4);
}

.login-card {
  width: 100%;
  max-width: var(--field-width);
  margin-bottom: 0;
}

.login-card h1 { margin-top: 0; font-size: var(--t-h2); }

.login-error {
  margin: 0 0 var(--space-3);
  font-size: var(--t-sm);
  color: var(--fail);
}

/* Выход - последняя строка меню и тихая: нажимают её редко. */
.section-logout {
  margin-top: var(--space-4);
  padding: 0 0 0 var(--nav-inset);
  background: none;
  border: none;
  color: var(--text-faint);
  font-size: var(--t-sm);
  font-family: var(--font-ui);
  text-align: left;
  cursor: pointer;
}

.section-logout:hover { color: var(--accent); }

.section-logout:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.password-form { max-width: var(--field-width); }
.login-note { margin: 0 0 var(--space-3); font-size: var(--t-sm); }

/* Ночи этой недели на «Расписании» (UX Рс2): по раскрывашке на ночь. */
.night { border-top: 1px solid var(--border); padding: var(--space-2) 0; }
.night summary { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.night .scroll-x { margin-top: var(--space-2); }

/* Код модели в «Настройках» → «Модели» не рвётся по дефисам: «claude-» на одной
   строке и «opus-5-5» на другой читались как две разные вещи. Узкий экран
   прокручивает таблицу внутри .scroll-x, страница вбок не едет. */
.model-id { white-space: nowrap; }
