/* AIRun layout.
   Zero colours of its own: every value below is an AIGate theme token, so a
   change in the gate's palette lands here without a second edit. */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.55;
}

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

/* ----------------------------------------------------------- shell --- */

.shell { display: flex; min-height: 100vh; }

.sidebar {
  width: 260px;
  flex: 0 0 260px;
  background: var(--surface-solid);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 18px 14px;
  position: sticky;
  top: 0;
  height: 100vh;
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  margin-bottom: 18px;
  padding-left: 6px;
}
/* Переключатель темы и крестик ящика прижаты к правому краю шапки, как в
   дизайн-системе: логотип слева, управление справа, между ними воздух. */
.brand .sidebar-theme-toggle { margin-left: auto; }
.brand img { display: block; }
.brand span { color: var(--primary); }

/* Лупа лежит в поле, а не рядом: подпись «поиск» тогда не нужна. */
.nav-search { position: relative; }
.nav-search .i {
  position: absolute; left: 9px; top: 9px;
  color: var(--text-muted); pointer-events: none;
}
.nav-search input {
  width: 100%;
  height: 34px;
  padding: 0 10px 0 29px;
  margin-bottom: 18px;
  background: var(--bg2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: var(--font-sans);
}
.nav-search input:focus { outline: none; border-color: var(--primary); }

.nav { display: flex; flex-direction: column; gap: 2px; }

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-weight: 500;
}
.nav-item:hover { background: var(--row-hover); color: var(--text); }
.nav-item.active { background: var(--primary-dim); color: var(--primary); }
.nav-item .i { color: var(--text-muted); flex: none; }
.nav-item:hover .i { color: var(--text); }
.nav-item.active .i { color: var(--primary); }

/* Значок всегда выровнен по строке текста, где бы ни стоял. */
.i { vertical-align: -0.17em; flex: none; }
/* Во флекс-строках (.btn, .nav-item, .source) зазор держит gap, а в обычном
   тексте его держать нечему — значок прилипает к первой букве заголовка.
   Отступ добавляем только там, где флекса нет. */
h1 .i, h2 .i, h3 .i, p .i, label .i, td .i, th .i, summary .i, li .i,
.linkish .i { margin-right: 0.32em; }

/* Янтарная подложка предупреждения: 15 % прозрачности из темы на фоне
   #020617 не видно вовсе, и от баннера остаётся одна рамка. */
:root:not([data-theme="light"]) { --warning-glow: rgba(245, 158, 11, 0.2); }

/* Счётчик в пункте меню прижат вправо; сам бейдж приходит из theme.css. */
.nav-item .badge { margin-left: auto; }

/* The bottom strip is a different layer, not two more nav items. */
.nav-bottom { margin-top: auto; padding-top: 16px; }
.nav-item.quiet { color: var(--text-muted); font-weight: 400; }
.nav-user {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px; margin-top: 4px;
  border-top: 1px solid var(--border);
  color: var(--text-muted); font-size: 0.85rem;
}
.linkish {
  background: none; border: none; cursor: pointer;
  color: var(--text-muted); font-size: 0.8rem; padding: 0;
}
/* Наведение красит кнопку в цвет действия: красным раньше загорались и
   «проверить», и «включить» — цвет обещал необратимое там, где его нет.
   Красный остался за `danger`, то есть за «удалить» и «отозвать». */
.linkish:hover { color: var(--primary); }
.linkish.danger:hover { color: var(--danger); }

.content { flex: 1; min-width: 0; padding: 28px 32px 60px; max-width: 1180px; }

/* ------------------------------------------------------------ bits --- */

h1 { margin: 0; }
/* Заголовок блока. Набор AIGate рисует его классом `.ui-section-title`
   внутри `.ui-section-header`; в AIRun заголовки блоков стоят стопкой в потоке
   страницы, без строки действий справа, поэтому то же начертание задано
   элементу — иначе половина экранов звала бы компонент, а половина (там, где в
   заголовке `{% if %}` или число) осталась бы на своём `h2`, и блоки
   различались бы между собой сильнее, чем AIRun отличается от AIGate.
   Числа — из `.ui-section-title`, отступы — свои: в наборе их даёт `gap`. */
h2 {
  margin: 26px 0 10px;
  font-size: 1rem; font-weight: 700;
  line-height: 1.35; letter-spacing: 0.015em;
}
.muted { color: var(--text-muted); }
/* theme.css задаёт моноширинный шрифт; разрядность цифр — наше дополнение. */
.mono { font-variant-numeric: tabular-nums; }
/* Числа сравнивают по разрядам, поэтому они выровнены по правому краю —
   и заголовок колонки вместе с ними, иначе шапка «уезжает» от столбца. */
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.small { font-size: 0.82rem; }

/* Кнопка приходит из theme.css целиком; AIRun правит ровно две вещи.
   «Останов/ить» в две строки — это не кнопка: в тесной строке действий
   подпись не переносится, а сама кнопка не сжимается, а отбирает своё место.
   Подписи здесь короткие, так что перенос из дизайн-системы не нужен. */
.btn { white-space: nowrap; flex: none; }

/* Поиск по своим чатам: поле тянется, кнопки держат свою ширину. */
.chat-find-form { display: flex; gap: 8px; align-items: center; margin: 0 0 12px; }
.chat-find-form input { flex: 1 1 auto; min-width: 0; }
/* Заголовок карточки — `h2`: карточки стоят прямо под `h1` экрана, и `h3`
   там пропускал уровень. Размер прежний, менялась только разметка. */
.card h2,
.card h3 { margin: 0 0 6px; font-size: 1rem; }

.status { font-family: var(--font-mono); font-size: 0.82rem; white-space: nowrap; }
.status.succeeded { color: var(--success); }
.status.failed, .status.stopped { color: var(--danger); }
.status.running, .status.starting, .status.queued { color: var(--accent-cyan); }
.status.waiting_approval { color: var(--warning); }

.source { display: inline-flex; align-items: center; gap: 7px; white-space: nowrap; }
label { display: block; font-size: 0.82rem; color: var(--text-muted); margin: 14px 0 6px; }
input[type=text], input[type=password], input[type=number],
input[type=email], input[type=url], input[type=search], textarea, select {
  width: 100%; padding: 10px 12px;
  background: var(--bg2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-family: var(--font-sans); font-size: 0.9rem;
}
textarea.code { font-family: var(--font-mono); min-height: 320px; line-height: 1.5; }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--primary); }
/* Нативный выбор файла — это серая кнопка с английской подписью посреди
   русского экрана. Поле прячем, подписью работает <label class="file-field">,
   имя выбранного файла дописывает static/js/file-field.js. */
.file-field {
  display: inline-flex; align-items: center; gap: 10px;
  margin: 10px 0 0; color: var(--text-muted); font-size: 0.85rem;
}
.file-field input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; }
.file-field .btn { pointer-events: none; }
.file-field input[type=file]:focus-visible + .btn { border-color: var(--primary); }

.flash-stack { margin-bottom: 16px; }

/* ------------------------------------------------------- run / chat --- */

.result {
  background: var(--surface-solid); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 20px 22px;
  white-space: pre-wrap; line-height: 1.65;
}
/* Упавший запуск: текст в коробке — это причина падения, а не ответ агента. */
.result-error { border-color: var(--danger); background: var(--danger-glow); }
.steps { list-style: none; margin: 0; padding: 0; }
.steps li {
  padding: 7px 0 7px 20px; position: relative;
  color: var(--text-muted); font-size: 0.88rem;
}
.steps li::before {
  content: "›"; position: absolute; left: 4px; color: var(--primary);
}
details.trace { margin-top: 18px; }
details.trace summary { cursor: pointer; color: var(--text-muted); }

.approval {
  border: 1px solid var(--warning); background: var(--warning-glow);
  border-radius: var(--radius-lg); padding: 16px 18px; margin-bottom: 16px;
}

.approval-what { margin: 10px 0 12px; display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 14px; }
.approval-what dt { color: var(--text-muted); font-size: 0.85rem; }
.approval-what dd { margin: 0; min-width: 0; }
.approval-args { border-collapse: collapse; width: 100%; }
.approval-args th {
  text-align: left; font-weight: 400; color: var(--text-muted);
  padding: 2px 10px 2px 0; vertical-align: top; white-space: nowrap;
}
.approval-args td { padding: 2px 0; word-break: break-word; }

.chat-layout { display: grid; grid-template-columns: 252px minmax(0, 1fr); gap: 16px; }

.chat-list { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.chat-new {
  display: block; padding: 9px 12px; margin-bottom: 6px;
  border: 1px dashed var(--border-light); border-radius: var(--radius-sm);
  color: var(--text-muted); text-align: center; font-size: 0.85rem;
}
.chat-new:hover { border-color: var(--primary); color: var(--primary); }
.chat-item { padding: 9px 11px; border-radius: var(--radius-sm); color: var(--text); }
.chat-item:hover { background: var(--row-hover); }
.chat-item.active { background: var(--primary-dim); color: var(--primary); }
.chat-item-title {
  font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.chat-pane {
  /* `relative` — ради кнопки «вниз»: она висит над лентой, а не в потоке. */
  position: relative;
  display: flex; flex-direction: column; height: calc(100vh - 150px);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface-section); overflow: hidden;
}
.chat-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 18px; border-bottom: 1px solid var(--border); background: var(--surface-solid);
}
.chat-head-main { min-width: 0; }
.chat-head h1 {
  margin: 0; font-size: 1.02rem; font-weight: 600; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  border: 1px solid transparent; border-radius: var(--radius-sm); padding: 1px 5px; margin-left: -5px;
}
/* The title is editable in place. It looks like text until you hover it,
   because a rename button on every chat is a button nobody presses. */
.chat-head h1[contenteditable]:hover { border-color: var(--border-light); }
.chat-head h1[contenteditable]:focus {
  outline: none; border-color: var(--primary); white-space: normal; overflow: visible;
}
/* `wrap` обязателен: внутри группы шесть элементов — состояние пода, «i»,
   «Завершить», поиск, «Выгрузить», «Журнал», — а `.chat-pane` обрезает всё,
   что вышло за край. На экране 360px правый край уезжал за границу панели. */
.chat-head-side {
  display: flex; align-items: center; gap: 10px; flex-shrink: 0;
  flex-wrap: wrap; justify-content: flex-end;
}
.chat-head-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* The model switcher sits in the subtitle line and is sized like the text
   around it: it is a statement of which model answers, that happens to be
   changeable, not a form control demanding attention. */
.chat-model {
  background: none; border: 1px solid transparent; border-radius: 6px;
  color: var(--text-muted); font-family: var(--font-mono); font-size: 0.78rem;
  padding: 1px 4px; max-width: 320px; cursor: pointer;
}
.chat-model:hover { border-color: var(--border-light); color: var(--text); }
.chat-model:focus { outline: none; border-color: var(--primary); color: var(--text); }

.pod-state {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--text-muted); font-size: 0.78rem; font-family: var(--font-mono);
}
.pod-state.stale { color: var(--warning); }
.live-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--border-light); flex: none;
}
.pod-state.live .live-dot, .chat-item .live-dot {
  background: var(--primary); box-shadow: 0 0 7px var(--primary-glow);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.35; } }

.chat-log { flex: 1; overflow-y: auto; padding: 22px 20px; display: flex; flex-direction: column; gap: 14px; }
.msg { max-width: 78%; }
.msg .bubble { padding: 11px 15px; border-radius: var(--radius-lg); }
.msg .bubble > :first-child { margin-top: 0; }
.msg .bubble > :last-child { margin-bottom: 0; }
.msg .bubble p { margin: 0 0 8px; line-height: 1.55; }
/* Недописанный хвост ответа: разметка в нём та же, что и в готовой части, —
   меняется только то, что он пересобирается на каждом кадре потока. */
.msg .bubble .bubble-tail { display: block; line-height: 1.55; }
.msg .bubble .bubble-tail > :first-child { margin-top: 0; }
.msg .bubble .bubble-tail > :last-child { margin-bottom: 0; }
.msg .bubble h1, .msg .bubble h2, .msg .bubble h3,
.msg .bubble h4, .msg .bubble h5, .msg .bubble h6 {
  margin: 14px 0 6px; font-size: 0.97rem; font-weight: 600; line-height: 1.35;
}
.msg .bubble h1 { font-size: 1.1rem; }
.msg .bubble h2 { font-size: 1.03rem; }
.msg .bubble ul, .msg .bubble ol { margin: 0 0 8px; padding-left: 20px; }
.msg .bubble li { margin: 3px 0; line-height: 1.5; }
.msg .bubble li > ul, .msg .bubble li > ol { margin: 3px 0; }
.msg .bubble blockquote {
  margin: 0 0 8px; padding: 2px 0 2px 12px;
  border-left: 2px solid var(--border-light); color: var(--text-muted);
}
.msg .bubble hr { border: none; border-top: 1px solid var(--border); margin: 12px 0; }
.msg .bubble img { max-width: 100%; border-radius: var(--radius-sm); }
.msg .bubble a { text-decoration: underline; text-underline-offset: 2px; }
/* Отчёты агента приходят таблицами (хосты, IOC, кто что подтвердил), поэтому
   таблица здесь — обычный элемент ответа, а не редкость. Горизонтальная
   прокрутка живёт внутри своего контейнера: страница вбок не едет. */
.table-scroll { overflow-x: auto; margin: 0 0 10px; }
.msg .bubble table {
  border-collapse: collapse; width: 100%; font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
}
.msg .bubble th, .msg .bubble td {
  border: 1px solid var(--border); padding: 5px 9px; text-align: left; vertical-align: top;
}
.msg .bubble th { background: var(--table-header); font-weight: 600; white-space: nowrap; }
.msg .bubble code {
  font-family: var(--font-mono); font-size: 0.86em;
  background: var(--bg2); border: 1px solid var(--border); border-radius: 4px; padding: 1px 4px;
}
.msg.user { align-self: flex-end; }
.msg.user .bubble {
  background: var(--primary-dim); border: 1px solid var(--primary);
  border-bottom-right-radius: 6px;
}
.msg.agent { align-self: flex-start; }
.msg.agent .bubble {
  background: var(--surface-elevated); border: 1px solid var(--border);
  border-bottom-left-radius: 6px;
}
.msg.step {
  align-self: flex-start; color: var(--text-muted); font-size: 0.8rem;
  padding: 0 6px; border-left: 2px solid var(--border-light); max-width: 78%;
}
/* The caret only exists while the reply is still arriving: a product that
   fakes typing after the text is final is lying about latency. */
.msg.streaming .bubble > :last-child::after {
  content: "▍"; color: var(--primary); animation: pulse 1s steps(2) infinite; margin-left: 2px;
}
.msg.typing .bubble { display: flex; gap: 4px; padding: 14px 15px; }
.msg.typing .bubble span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted);
  animation: pulse 1.2s ease-in-out infinite;
}
.msg.typing .bubble span:nth-child(2) { animation-delay: 0.2s; }
.msg.typing .bubble span:nth-child(3) { animation-delay: 0.4s; }

.code { margin: 0 0 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.code-head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 4px 10px; background: var(--bg2); border-bottom: 1px solid var(--border);
}
.code pre { margin: 0; padding: 11px 12px; overflow-x: auto; background: var(--bg); }
/* Сброс обязан быть не слабее правила пузыря выше (`.msg .bubble code`),
   иначе рамка инлайнового кода рисуется вокруг каждой строки блока: строчный
   элемент на несколько строк даёт по рамке на каждый фрагмент. */
.msg .bubble .code pre code,
.code pre code { background: none; border: none; padding: 0; font-size: 0.84rem; line-height: 1.5; }

/* Подсветка highlight.js собрана из токенов темы, своих цветов тут нет: в
   светлой теме палитра переворачивается сама, без второй таблицы цветов. */
.hljs-comment, .hljs-quote { color: var(--text-muted); font-style: italic; }
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type { color: var(--primary); }
.hljs-string, .hljs-regexp, .hljs-meta-string { color: var(--accent-cyan); }
.hljs-number, .hljs-symbol, .hljs-bullet { color: var(--warning); }
.hljs-title, .hljs-section, .hljs-name, .hljs-selector-id { color: var(--primary-hover); font-weight: 600; }
.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-template-variable { color: var(--text); }
.hljs-meta, .hljs-doctag { color: var(--text-dim); }
.hljs-deletion { color: var(--danger); }
.hljs-addition { color: var(--success); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: 600; }

.chat-suggest { display: flex; gap: 8px; flex-wrap: wrap; padding: 0 20px 4px; }
/* Без этой строки атрибут `hidden` не работает: `display: flex` выше сильнее
   браузерного правила для [hidden], и подсказки висели бы под перепиской. */
.chat-suggest[hidden] { display: none; }
.chat-input {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px 4px; border-top: 1px solid var(--border);
}
.chat-input-row { display: flex; gap: 8px; align-items: flex-end; }
.chat-input textarea {
  min-height: 44px; max-height: 220px; resize: none; overflow-y: auto; line-height: 1.5;
}
.chat-input .send { flex: none; width: 44px; height: 44px; padding: 0; font-size: 1.1rem; }
/* Скрепка — кнопка-значок набора, но ростом с поле и кнопку отправки и без
   заливки: приложить файл можно, а главное действие здесь всё-таки отправить. */
.chat-attach {
  flex: none;
  width: 44px; min-width: 44px; height: 44px;
  background: none;
}

/* Файлы: выбранные — над полем, отправленные — под пузырём. Чип один и тот
   же, разница только в том, что у выбранного есть крестик. */
.chat-picked, .msg-files { display: flex; flex-wrap: wrap; gap: 6px; }
.msg-files { margin-top: 6px; }
.file-chip {
  display: inline-flex; align-items: center; gap: 7px; max-width: 280px;
  padding: 5px 9px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg2); color: var(--text); font-size: 0.82rem;
}
a.file-chip:hover { border-color: var(--primary); color: var(--primary); }
.file-chip-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-chip .chip-x {
  background: none; border: none; color: var(--text-dim); cursor: pointer;
  font-size: 1rem; line-height: 1; padding: 0 1px;
}
.file-chip .chip-x:hover { color: var(--danger); }
/* Пока файл тащат над окном, мишень — вся переписка, а не полоска ввода. */
.chat-pane.dropping { outline: 2px dashed var(--primary); outline-offset: -6px; }
.chat-closed {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 14px 18px; border-top: 1px solid var(--border);
}

.chat-start { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.chat-start-pick { flex: 1; min-width: 240px; }

/* ------------------------------------------------------- settings --- */

/* The gate chip lives in the sidebar on every screen. An installation that
   cannot reach AIGate must not be discoverable only by a failing run. */
.gate-chip {
  display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
  padding: 8px 11px; border-radius: var(--radius-sm); font-size: 0.78rem;
  border: 1px solid var(--border); color: var(--text-muted); background: var(--bg2);
}
.gate-chip .gate-led { width: 7px; height: 7px; border-radius: 50%; background: var(--border-light); flex: none; }
.gate-ok { border-color: var(--primary); color: var(--primary); background: var(--primary-dim); }
.gate-ok .gate-led { background: var(--primary); box-shadow: 0 0 7px var(--primary-glow); }
.gate-missing, .gate-broken { border-color: var(--danger); color: var(--danger); background: var(--danger-glow); }
.gate-missing .gate-led, .gate-broken .gate-led { background: var(--danger); }
.gate-bypass { border-color: var(--warning); color: var(--warning); }
.gate-bypass .gate-led { background: var(--warning); }

.gate-summary h3 { display: flex; align-items: center; gap: 9px; }
/* Без собственной заливки: цвет точки задают .gate-ok/.gate-bypass/
   .gate-broken выше — иначе в сводке лампа всегда серая. */
.gate-summary .gate-led { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.card.gate-ok { border-color: var(--primary); }
.card.gate-missing, .card.gate-broken { border-color: var(--danger); }
.card.gate-bypass { border-color: var(--warning); }

.checklist { display: flex; flex-direction: column; gap: 6px; margin: 0 0 22px; }
.check { display: flex; gap: 10px; align-items: flex-start; font-size: 0.88rem; }
.check-mark { font-family: var(--font-mono); width: 16px; flex: none; }
.check.ok .check-mark { color: var(--success); }
.check.bad .check-mark { color: var(--danger); }

.form-wide { max-width: 680px; }
.form-wide h2 { margin-top: 26px; }
.advanced {
  margin: 16px 0; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg2);
}
.advanced summary { cursor: pointer; color: var(--text-muted); font-size: 0.88rem; }
.advanced[open] summary { margin-bottom: 10px; color: var(--text); }

/* Пара полей в строку. Это не `ui-entity-grid` из набора: там сетка
   карточек с минимумом 320px, а здесь два поля формы, которым тесно
   уже на 260px, и расходятся они раньше. */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; }
.role-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; margin-bottom: 8px; }
.ui-inline-form select, .ui-inline-form input { width: auto; min-width: 130px; margin: 0; }

/* Переключатель: сам `.ui-toggle` приходит из набора целиком, а строку
   «переключатель + подпись» AIGate собирает в `configuration-page.css` —
   файле своего экрана, которого в vendor нет. Повторяем ровно его правило. */
.toggle-group { display: flex; align-items: center; gap: 12px; margin: 14px 0; }
/* Ячейка с действиями — это по-прежнему ячейка: display:flex на <td>
   выбрасывает её из колоночной сетки, и кнопки уезжают за правый край
   таблицы. Выравниваем содержимое, не трогая режим отображения. */
.row-actions { white-space: nowrap; text-align: right; }
.row-actions > * { display: inline-flex; vertical-align: middle; margin: 0; }
.row-actions > * + * { margin-left: 12px; }
/* Раскрытый <details> нельзя оставлять инлайн-флексом: форма смены пароля
   ложится в одну строку с самой ссылкой и накрывает её. */
.row-actions > details { display: inline-block; text-align: left; }
.row-actions details .ui-inline-form { margin-top: 6px; }
.row-off { opacity: 0.45; }

/* ----------------------------------------------------------- login --- */

/* Щит, карточка и поля входа — из `auth.css` набора AIGate. Своего здесь
   только подложка под карточкой и разделитель «или»: каталогов входа у
   AIGate нет, а значит нет и этого разделителя. */
body.auth-page {
  background: radial-gradient(circle at 50% 0%, var(--bg2), var(--bg) 70%);
}
.brand-centered { justify-content: center; margin-bottom: 14px; }

/* Разделитель «или» между локальным входом и кнопками каталогов. */
.sso-split {
  display: flex; align-items: center; gap: 10px;
  margin: 20px 0 14px; color: var(--text-muted); font-size: 12px;
}
.sso-split::before, .sso-split::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}

/* ---------------------------------------------------------- mobile --- */

@media (max-width: 1024px) {
  /* Ящик навигации приезжает из theme.css: он двигает `.sidebar` трансформом,
     а трансформ работает только когда панель вынута из потока флекса. */
  /* Размеры ящика тоже перечислены здесь: базовые правила `.sidebar` из
     airun.css идут позже по файлу и той же силы, что медиазапрос в theme.css,
     поэтому молча отменяли бы и ширину по экрану, и 100dvh, и безопасную
     зону под чёлкой. */
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    width: min(320px, 88vw);
    flex: 0 0 auto;
    height: 100dvh;
    padding: env(safe-area-inset-top) 0 18px env(safe-area-inset-left);
  }
  .sidebar .brand { padding: 18px 14px 0; margin-bottom: 14px; }
  .sidebar .nav, .sidebar .nav-search, .sidebar .nav-bottom { padding: 0 14px; }
  .content {
    padding: calc(var(--mobile-topbar-height) + 16px) calc(16px + env(safe-area-inset-right))
             calc(50px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left));
  }
  .chat-layout { grid-template-columns: 1fr; }
  .chat-list { flex-direction: row; overflow-x: auto; }
  .chat-switch { flex-direction: row; gap: 6px; }
  .chat-switch > summary {
    display: list-item; flex: none; align-self: center;
    padding: 8px 12px 8px 20px; cursor: pointer;
    color: var(--text-dim); font-size: 0.85rem; white-space: nowrap;
  }
  .chat-switch:not([open]) { overflow: hidden; }
  .chat-item { min-width: 170px; }
  .chat-new { margin: 0; min-width: 120px; }
    /* 100vh на iOS считает экран без адресной строки, и поле ввода уезжает
     под неё. dvh меряет то, что видно сейчас. */
  .chat-pane { height: calc(100dvh - 210px); }
  .chat-head { flex-wrap: wrap; }
  .msg { max-width: 92%; }
}

/* API keys: the one-time secret has to be readable and copyable on a phone. */
.card-accent { border-color: var(--primary); background: var(--primary-dim); }
.break { word-break: break-all; }
pre.code {
  padding: 11px 12px;
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Pods screen: числа, которые оператор смотрит до таблиц, — теми же
   плитками `.stats-grid/.stat-card`, что и везде в AIGate; свои `.stat`
   отличались от них на глаз и не перестраивались на телефоне. Здесь только
   то, чего в наборе нет: имя раннера — слово, а не число, и шрифтом в
   1.75rem оно переносится. */
/* Сообщения в AIRun — это текст, а не строка с кнопкой справа. У `.alert`
   в наборе стоит `justify-content: space-between` под кнопку закрытия,
   которой здесь нет нигде: от этого «Неверный логин или пароль» и поясняющая
   строка разъезжались по разным краям полосы. */
.alert { display: block; }
.alert > .small,
.alert > div { margin-top: 4px; }

.stat-value-sm { font-size: 1.05rem; font-family: var(--font-mono); }
.row-warn td { background: color-mix(in srgb, var(--warning) 12%, transparent); }

/* Рубильник и расход. */
.danger-panel {
  border: 1px solid var(--danger);
  background: var(--danger-glow);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 16px;
}
.danger-panel h2 { margin: 0 0 6px; font-size: 1rem; color: var(--danger); }
.limit-form { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.limit-form input[type="number"] { width: 140px; }

/* Выбор шаблона агента: карточка целиком — ссылка. Якорь по умолчанию
   строчный, поэтому padding карточки на нём не держит высоту, и три
   «карточки» складываются в три строки текста. */
.template-pick a.card { display: block; text-decoration: none; color: inherit; margin: 0; }
.template-pick a.card:hover { border-color: var(--primary); }
.template-pick a.card h2,
.template-pick a.card h3 { color: var(--text); }

/* --- схема графа агента ---------------------------------------------- */
/* Схему рисует mermaid; здесь — только то, чем она отличается от схемы из
   чужого проекта: формы узлов и цвета берутся из темы AIRun. */
.graph { overflow-x: auto; margin: 0 0 18px; text-align: center; }
.graph svg { max-width: 100%; height: auto; }
/* До отрисовки в блоке лежит сам текст схемы: читаемо, но не на весь экран. */
.graph pre.mermaid {
  margin: 0; font-family: var(--font-mono); font-size: 11px; color: var(--text-dim);
  white-space: pre-wrap; max-height: 120px; overflow: hidden;
}
.graph pre.mermaid.drawn { max-height: none; overflow: visible; color: inherit; }
.graph pre.mermaid.broken { color: var(--danger); }
/* Вторая строка подписи — тем же шрифтом, что и первая, нарочно: ширину
   узла mermaid считает одним шрифтом, а моноширинная строка оказывалась
   шире посчитанного и вылезала за рамку. */
.graph .node small { font-size: 11px; color: var(--text-dim); }
/* Ширину подписи mermaid считает своим шрифтом, а рисует нашим: лишний
   полсимвола срезался рамкой узла. Подпись шире рамки — мелочь, подпись
   обрезанная — ошибка. */
.graph .node foreignObject { overflow: visible; }
/* Тип узла цветом: выход в чужую систему и остановка на человеке — это то,
   что ищут на схеме в первую очередь. */
.graph .tool > polygon, .graph .tool .label-container path { stroke: var(--primary); }
.graph .approval .label-container path { stroke: var(--warning); }
.graph .terminal > circle { fill: var(--border-light); stroke: none; }
.graph .edgePath path, .graph .flowchart-link { stroke: var(--border-light); }
.graph marker path, .graph .marker { fill: var(--border-light); stroke: var(--border-light); }

/* --- версии агента ---------------------------------------------------- */
.pending-panel {
  border: 1px solid var(--warning);
  background: var(--warning-glow);
  border-radius: 10px;
  padding: 14px 16px;
  margin-bottom: 16px;
}
.pending-panel h2 { margin: 0 0 6px; font-size: 1rem; color: var(--warning); }
.version-row-pending td { background: color-mix(in srgb, var(--warning) 10%, transparent); }
.origin { font-family: var(--font-mono); font-size: 0.76rem; color: var(--text-dim); }

/* === Таблица агентов =====================================================
   Реестр на сотни строк: взгляд идёт по левому краю имён, а состояние,
   итоги и расход читаются справа одной горизонталью. Колонки с числами —
   табличными цифрами, иначе столбик «съезжает» на каждой девятке. */
.agents-grid td { vertical-align: middle; }
.agents-grid tbody tr:last-child td { border-bottom: none; }
.agents-grid .col-state { width: 30px; padding-right: 0; }
.agents-grid .col-ver { white-space: nowrap; }
/* Автор ужимается первым: фамилия в реестре нужна для узнавания, а не целиком. */
.agents-grid .col-owner { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.agents-grid .col-tally, .agents-grid .col-spark, .agents-grid .col-spend { width: 1%; white-space: nowrap; }
.agents-grid .col-last { width: 1%; white-space: nowrap; }
.agents-grid .col-do { width: 1%; }
.agents-grid .col-name a { font-weight: 600; }
/* Назначение агента — одной строкой: длинное описание читают на его экране. */
.clip { max-width: 20ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 0.82rem; }

.ui-status-dot.warn { background: var(--warning); box-shadow: 0 0 8px var(--warning-glow); }

/* Таблетка итога: значок говорит «что», число — «сколько». */
.tally {
  display: inline-flex; align-items: center; gap: 4px;
  margin-right: 6px; padding: 1px 6px 1px 5px;
  border-radius: 999px; font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; font-size: 0.76rem;
}
.tally:last-child { margin-right: 0; }
.tally.ok { background: var(--success-glow); color: var(--success); }
.tally.bad { background: var(--danger-glow); color: var(--danger); }
.tally.wait { background: var(--warning-glow); color: var(--warning); }
.tally.live { background: var(--primary-dim); color: var(--accent-cyan); }

.spark { display: inline-flex; align-items: flex-end; gap: 2px; height: 22px; }
.spark i { display: block; width: 5px; min-height: 2px; border-radius: 1px; background: var(--primary); opacity: 0.75; }
.spark i[style*="height:0%"] { background: var(--border-light); opacity: 1; }

.score-bar { display: block; margin-top: 4px; }

/* На экране без мыши 34 пикселя — промах: палец попадает в соседнюю строку. */
@media (hover: none) {
  .chat-down { width: 44px; height: 44px; }
}
.col-do { white-space: nowrap; }
.col-do form { display: inline-block; margin-left: 6px; }
/* ------------------------------------------------------------- чат: лента --- */

/* Подпись под репликой: время и «копировать». Появляется неярко и не мешает
   читать — но в разборе инцидента время реплики спрашивают первым. */
.msg-foot {
  display: flex; gap: 10px; align-items: center; margin-top: 4px;
  font-size: 0.72rem; color: var(--text-dim); opacity: 0; transition: opacity 0.12s;
}
.msg:hover .msg-foot, .msg:focus-within .msg-foot { opacity: 1; }
/* На тач-устройстве навести нечем: подпись, «копировать», «переспросить» и
   «править» были не видны вовсе — а нажимались вслепую, прозрачность
   кликабельность не снимает. */
@media (hover: none) {
  .msg-foot { opacity: 1; }
  .msg.tail .redo-msg, .msg.tail .edit-msg { display: inline; }
}
.msg.user .msg-foot { justify-content: flex-end; }

/* «Переспросить» и «править» живут только у хвоста разговора: в середине
   истории переспрос выбросил бы всё сказанное после него. Кнопки стоят у
   каждой реплики, чтобы хвост не приходилось перерисовывать, но видны
   только там, где они что-то значат. */
.msg .redo-msg, .msg .edit-msg { display: none; }
.msg.tail .redo-msg, .msg.tail .edit-msg { display: inline; }

/* Правка своего вопроса прямо в ленте: отдельный экран ради одного слова
   человеку не нужен. */
.msg-edit { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
.msg-edit textarea {
  width: 100%; resize: vertical; font: inherit;
  padding: 8px 10px; border-radius: var(--radius-md);
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
}
.msg-edit-actions { display: flex; gap: 8px; justify-content: flex-end; }
/* Реплику убрали из переписки, пока её правили: предупреждение внутри поля. */
.msg-gone { margin: 0; color: var(--warning); }

/* Заметка запуска: «чат начат», «модель переключена», отказ политики. Не
   реплика — поэтому без пузыря, строкой по центру ленты. */
.msg.note {
  align-self: center; max-width: 100%; padding: 2px 0;
  color: var(--text-dim); font-size: 0.78rem; text-align: center;
}
.msg.note.bad { color: var(--danger); }
/* Тело доставки в журнале: развёрнутое оно длинное, поэтому по умолчанию
   свёрнуто, а раскрытое — прокручивается внутри ячейки, а не ломает таблицу. */
.payload > summary { cursor: pointer; }
.payload > pre {
  margin: 6px 0 0; max-height: 220px; overflow: auto;
  white-space: pre-wrap; word-break: break-word;
}

/* Заметка с файлами агента: плитки под ней стоят в строку по центру. */
.msg.note.msg-made { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.msg.note.msg-made .msg-files { margin-top: 0; justify-content: center; }

/* Скриншот в ленте показывается, а не скачивается: приложили его как раз
   затем, чтобы на него посмотреть. */
.msg-shot {
  display: block; max-width: 260px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); overflow: hidden; line-height: 0;
}
.msg-shot:hover { border-color: var(--primary); }
.msg-shot img { display: block; max-width: 100%; height: auto; }

/* Карточка вызова инструмента. Свёрнута нарочно: в разговоре важен ответ, а
   аргументы и выгрузка нужны тому, кто проверяет — ему и разворачивать. */
.tool-call {
  align-self: flex-start; max-width: 80%; margin: 2px 0 8px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg2); font-size: 0.82rem;
}
.tool-call > summary {
  display: flex; align-items: center; gap: 8px; padding: 7px 11px;
  cursor: pointer; color: var(--text-muted); list-style: none;
}
.tool-call > summary::-webkit-details-marker { display: none; }
.tool-call > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.tool-call .tool-name { color: var(--text); }
.tool-call .tool-state { margin-left: auto; font-size: 0.74rem; color: var(--text-dim); }
.tool-call.running { border-color: var(--primary); }
.tool-call.running .tool-state { color: var(--primary); }
.tool-call.pending { border-color: var(--warning); }
.tool-call.pending .tool-state { color: var(--warning); }
.tool-call.denied, .tool-call.error { border-color: var(--danger); }
.tool-call.denied .tool-state, .tool-call.error .tool-state { color: var(--danger); }
.tool-body { padding: 0 11px 10px; display: flex; flex-direction: column; gap: 8px; }
/* Подпись слева, «копировать» справа, выгрузка под ними во всю ширину:
   выгрузка инструмента уезжает в тикет так же часто, как блок кода. */
.tool-part { display: grid; grid-template-columns: 1fr auto; align-items: baseline; }
.tool-part pre {
  grid-column: 1 / -1; margin: 3px 0 0; padding: 8px 10px; max-height: 260px; overflow: auto;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 0.76rem; white-space: pre-wrap; overflow-wrap: anywhere;
}

/* «Вниз» показывается, только когда человек отлистал наверх: пока он внизу,
   лента и так едет за ответом. */
.chat-down {
  position: absolute; right: 22px; bottom: 96px; z-index: 2;
  width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--text-muted);
  box-shadow: var(--shadow-md); cursor: pointer;
}
.chat-down:hover { color: var(--primary); border-color: var(--primary); }
.chat-down:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* Со стрелкой без подписи никто не догадается, что внизу дописали: пока новое
   не прочитано, кнопка называет себя словами и становится заметной. */
.chat-down.fresh {
  width: auto; padding: 0 12px; gap: 6px;
  border-color: var(--primary); color: var(--primary); background: var(--primary-dim);
}
.chat-down-new { font-size: 0.76rem; white-space: nowrap; }

/* Просмотр картинки поверх переписки: соседняя вкладка уводит из разговора,
   а скриншот в разборе смотрят, не прекращая его. */
.chat-viewer {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 28px;
  background: rgba(0, 0, 0, 0.74);
}
.chat-viewer[hidden] { display: none; }
.chat-viewer-shot {
  margin: 0; display: flex; flex-direction: column; gap: 10px;
  align-items: center; max-width: 100%; max-height: 100%;
}
.chat-viewer-shot img {
  max-width: 100%; max-height: calc(100vh - 120px);
  border-radius: var(--radius-md); background: var(--surface);
}
.chat-viewer-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center; }
.chat-viewer-name {
  color: #fff; font-size: 0.8rem; max-width: 420px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --------------------------------------------------------- утилиты ---
   Один шаг отступа вместо `style="margin-top:12px"` в шаблоне: правка
   ритма страницы делается здесь, а не поиском по тридцати файлам. */

.m0 { margin: 0; }
.mt0 { margin-top: 0; }
.mt-xs { margin-top: 6px; }
.mt-sm { margin-top: 10px; }
.mt-md { margin-top: 16px; }
.mt-lg { margin-top: 24px; }
.mb0 { margin-bottom: 0; }
.mb-xs { margin-bottom: 6px; }
.mb-sm { margin-bottom: 10px; }
.mb-md { margin-bottom: 16px; }
.my-xs { margin: 6px 0; }
.row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.self-end { align-self: flex-end; }
.pad-x { padding-left: 14px; padding-right: 14px; }
/* Подпись-заголовок поля: без приглушённого цвета и верхнего отступа `label`. */
.plain-label { margin: 0; color: inherit; font-size: inherit; }
.bad { color: var(--danger); }
.flat { border: none; padding: 0; }
/* Заголовок внутри карточки: по размеру это подзаголовок, а не заголовок страницы. */
.h-sm { font-size: 1rem; }

/* Текст только для скринридера: ярлык у поля, который на экране лишний. */
/* Знак «AITalon» нарисован двумя файлами: светло-зелёный для тёмной темы и
   тёмно-зелёный для светлой. Светлый файл лежал в репозитории без дела, и на
   светлой теме знак выцветал. */
.brand-mark-light { display: none; }
[data-theme="light"] .brand-mark-dark { display: none; }
[data-theme="light"] .brand-mark-light { display: inline-block; }

/* Карточки на страницах AIRun стоят стопкой, а в дизайн-системе — в сетке с
   `gap`, поэтому своего нижнего отступа у `.card` там нет. Добавляем его
   здесь, иначе две карточки подряд слипаются в один блок. */
.card { margin-bottom: 12px; }
.card:last-child { margin-bottom: 0; }

/* Таблица реестра агентов шире остальных: на широком экране её держит
   собственный минимум, на узком она раскладывается карточками дизайн-системы
   (`ui-table-responsive`), и минимум пришлось бы отменять ещё раз. */
.table.agents-grid:not(.ui-table-responsive) { min-width: 820px; }

@media (max-width: 768px) {
  /* В карточном режиме колонок больше нет: ширины и обрезки, которыми реестр
     держит горизонталь на мониторе, здесь резали бы значение в ноль. Правила
     перечислены поимённо, потому что селектор колонки из двух классов
     специфичнее одноклассового правила дизайн-системы. */
  .agents-grid.ui-table-responsive .col-state,
  .agents-grid.ui-table-responsive .col-ver,
  .agents-grid.ui-table-responsive .col-owner,
  .agents-grid.ui-table-responsive .col-tally,
  .agents-grid.ui-table-responsive .col-spark,
  .agents-grid.ui-table-responsive .col-spend,
  .agents-grid.ui-table-responsive .col-last,
  .agents-grid.ui-table-responsive .col-do {
    width: auto;
    max-width: none;
    overflow: visible;
    white-space: normal;
    padding: 0.8rem 0.95rem;
  }
  .agents-grid.ui-table-responsive .clip { max-width: none; white-space: normal; }
}

/* ------------------------------------------------------- ошибки --- */

/* Страница ошибки без шапки (гость или пятисотка): карточка просто стоит по
   центру экрана, а не прижимается к левому верхнему углу. */
.error-page {
  display: flex; align-items: center; justify-content: center;
  min-height: 100dvh; padding: 24px;
}
.error-card {
  display: flex; gap: 24px; align-items: center;
  max-width: 680px; margin: 0 auto; padding: 28px;
}
.error-code {
  flex: none;
  font-family: var(--font-mono); font-size: 3.4rem; line-height: 1;
  color: var(--text-dim);
}
.error-body h1 { margin: 0 0 8px; }
.error-body p { margin: 0 0 10px; }
.error-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 16px 0 12px; }

@media (max-width: 600px) {
  .error-card { flex-direction: column; align-items: flex-start; gap: 12px; }
}

/* ---------------------------------------------------------- чат --- */

/* Заголовок чата правится на месте, поэтому рядом с ним стоят карандаш и
   подсказка про Enter: без них редактируемый заголовок никто не находит. */
.chat-title-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.chat-title-row h1 { margin: 0; }
.chat-title-tip { white-space: nowrap; }

/* Остаток вложений и символов — текстом у строки ввода: на телефоне тултипа
   нет, и о потолке там узнавали только из отказа. */
.chat-left { align-self: center; white-space: nowrap; }

@media (max-width: 600px) {
  /* Прятать счётчик нельзя — на телефоне он единственный способ узнать о
     потолке до отказа. Уводим его на свою строку под поле ввода. */
  .chat-input-row { flex-wrap: wrap; }
  .chat-left { order: 9; flex: 1 0 100%; text-align: right; }
}

/* Створка списка чатов: на широком экране её заголовка не видно, а список
   внутри стоит такой же колонкой, как стоял. Сворачивается — только на узком,
   и открывает её скрипт по ширине экрана. */
.chat-switch { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.chat-switch > summary { display: none; }

/* Поиск по переписке стоит в шапке чата рядом с остальными действиями: он
   короткий, иначе шапка разъезжается на две строки на любом экране. */
.chat-find { width: 170px; min-width: 0; }
.msg.found .bubble { outline: 1px solid var(--primary); }
/* Карточка инструмента попадает в ту же выборку, что и реплики, —
   и подсвечиваться обязана так же, иначе совпадение в ней не видно. */
.tool-call.found { outline: 1px solid var(--primary); }

@media (max-width: 600px) {
  .chat-find { width: 100%; }
}
