/* ================================================================
   Базовые стили админки «Аура».

   Дизайн — спокойный, плотный, без лишних украшений. Мы делаем
   рабочий инструмент для оператора/менеджера, не лендинг.

   Цвета:
   - Серая палитра + один акцент (синий) для интерактивных элементов.
   - Бэйджи статусов — пастельные.

   Шрифты — системные стек, без подгрузки.
   ================================================================ */

:root {
    /* #391: чуть темнее, чтобы базовый фон интерфейса не сливался со
       светлым canvas'ом графа (#fbfcfe) — рамка/полотно читаемо отделены. */
    --color-bg:        #e8eaf0;
    --color-surface:   #ffffff;
    /* Единый цвет ВСЕХ панелей интерфейса (фильтр-бары, легенды, тулбары,
       карточки-панели, трейс, сайдбары). Раньше токен был НЕ определён →
       панели разъезжались по цветам (белый / #f1f5f9 / прозрачный). Один
       токен = один цвет для всех панелей (data-driven). */
    --color-card:      #f8fafc;
    --color-border:    #e3e6eb;

    /* Единое оформление списочных контролов (select / multiselect /
       dropdown / setting-input). Один набор токенов = один вид у всех
       контролов: белый фон, лавандовый бордюр в покое, фиолетовый фокус,
       лавандовый hover в раскрытом списке. */
    --control-bg:          #ffffff;
    --control-border:      #c4b5fd;          /* violet-300 — бордюр в покое */
    --control-focus:       #7c3aed;          /* violet-600 — фокус/раскрытие */
    --control-focus-ring:  rgba(124, 58, 237, 0.18);
    --dropdown-bg:         #ffffff;
    --dropdown-hover-bg:   #ede9fe;          /* violet-100 */
    --dropdown-hover-text: #4c1d95;          /* violet-900 */
    --color-text:      #1f2937;
    --color-text-muted:#6b7280;
    --color-accent:    #2563eb;
    --color-accent-bg: #eff4ff;

    /* Семантические цвета состояний (унификация: раньше var(--color-danger) / var(--color-success-text) /
       var(--color-busy-text) / var(--color-warn-text) и var(--color-danger,#c33) были хардкодами в ~100
       местах; один токен = один цвет для всех ok/error/busy/pending). */
    --color-danger:       #dc2626;
    --color-success-text: #15803d;
    --color-busy-text:    #6d28d9;
    --color-warn-text:    #b45309;

    --color-status-new:        #fffbe6;
    --color-status-new-text:   #8a6d00;
    --color-status-active:     #e6f7ed;
    --color-status-active-text:#15683a;
    --color-status-operator:   #e8eefc;
    --color-status-operator-text:#1e40af;
    --color-status-escalated:  #fef0e6;
    --color-status-escalated-text:#9a4500;
    --color-status-closed:     #f4f5f7;
    --color-status-closed-text:#6b7280;
    /* Нейтральный «приглушённый» токен (off/muted-пилюли скриптов). */
    --color-status-muted:      #f1f1f5;
    --color-status-muted-text: #4b5563;

    --space-xs: 4px;
    --space-sm: 8px;
    --space-md: 16px;
    --space-lg: 24px;

    --radius-sm: 4px;
    --radius-md: 8px;

    /* Единая высота интерактивных контролов шапки (bot-switch / баланс /
       селектор тенанта / logout). Не выше пункта меню (.app-nav__tab ≈26px) и
       выровнены по тем же направляющим — берём 24px (внутри высоты вкладки). */
    --control-h: 24px;

    --font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI",
                  Roboto, "Helvetica Neue", Arial, sans-serif;
}

* {
    box-sizing: border-box;
}

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

body {
    font-family: var(--font-stack);
    font-size: 10px;
    line-height: 1.5;
    color: var(--color-text);
    background: var(--color-bg);
    /* Колонка «шапка (auto) + main (остаток)»: main получает точную высоту
       без магической константы, поэтому уменьшение высоты nav (.app-header/
       .app-nav) автоматически отдаёт высоту контенту. */
    display: flex;
    flex-direction: column;
}

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

.app-header {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
    /* P192: компактнее по вертикали — 4px вместо 8px. */
    padding: 4px 5px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}

.app-header__brand {
    display: flex;
    align-items: baseline;
    gap: var(--space-sm);
    /* P198 (#445): бренд не сжимается — подзаголовок строго в 1 строку,
       лишнюю ширину отдаём навигации (она скроллится), а не переносим текст. */
    flex-shrink: 0;
}

.app-header__logo {
    font-weight: 700;
    font-size: 13px;
    color: var(--color-accent);
    /* Кликабельный логотип → на главную (без подчёркивания на hover). */
    text-decoration: none;
    cursor: pointer;
}

.app-header__subtitle {
    font-size: 10px;
    color: var(--color-text-muted);
    /* P198 (#445): строго 1 строка — не переносить и не обрезать
       (брат .app-header__brand с flex-shrink:0 гарантирует ширину). */
    white-space: nowrap;
}

/* Per-client баланс-виджет клиента (внутри .app-identity, после емейла).
   Раскладку/зазор даёт flex-gap .app-identity — без margin-left:auto. */
.api-balance-widget {
    display: inline-flex;
    align-items: center;
    /* Текст баланса — строго в 1 строку, по центру (верт. и гориз.). */
    justify-content: center;
    white-space: nowrap;
    gap: 6px;
    height: var(--control-h);
    padding: 0 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    text-decoration: none;
    font-size: 10px;
    font-weight: 600;
    transition: background 0.15s, border-color 0.15s;
}
.api-balance-widget:hover {
    background: var(--color-card);
}
.api-balance-widget__icon {
    font-size: 10px;
    line-height: 1;
}
.api-balance-widget__value {
    font-variant-numeric: tabular-nums;
}
/* Per-client баланс — 4 порога (единый JS-хелпер balanceTier по остатку ₽).
   ok=салатовый(зелёный) · mid=горчичный(жёлтый) · low=розовый(бледно-красный)
   · critical=ярко-красный. Те же tier'ы у .balance-badge ниже. */
.api-balance-widget--ok {
    background: #ecfdf5;
    border-color: #6ee7b7;
    color: #065f46;
}
.api-balance-widget--mid {
    background: #fef3c7;
    border-color: #fcd34d;
    color: #78350f;
}
.api-balance-widget--low {
    background: #fce7f3;
    border-color: #f9a8d4;
    color: #9d174d;
}
.api-balance-widget--critical {
    background: #ef4444;
    border-color: #b91c1c;
    color: #fff;
    animation: api-balance-pulse 2s ease-in-out infinite;
}
@keyframes api-balance-pulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.6); }
    50%      { box-shadow: 0 0 0 4px rgba(220, 38, 38, 0); }
}

/* ADR-0020 P2 (#439): identity / tenant-селектор / logout справа в шапке.
   Прямой flex-child .app-header → попадает в правый кластер после баланса
   (gap шапки даёт зазор). Суперадмин: email + <select> тенанта; обычный:
   email-label; рядом — power-кнопка logout. */
.app-identity {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.app-identity__super,
.app-identity__label {
    font-size: 10px;
    max-width: 220px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-identity__super {
    font-weight: 600;
    color: var(--color-accent);
}
.app-identity__label {
    color: var(--color-text-muted);
}
.app-logout-form {
    margin: 0;
}

/* Per-client баланс-бейдж: компактная пилюля «N ₽» в строках селектора и в
   закрытом триггере. Высота встроена в строку (line-height), цвет — tier'ы
   (единый JS-хелпер balanceTier). */
.balance-badge {
    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    padding: 0 4px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    font-size: 9px;
    font-weight: 600;
    line-height: 1.5;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}
.balance-badge--ok       { background: #ecfdf5; border-color: #6ee7b7; color: #065f46; }
.balance-badge--mid      { background: #fef3c7; border-color: #fcd34d; color: #78350f; }
.balance-badge--low      { background: #fce7f3; border-color: #f9a8d4; color: #9d174d; }
.balance-badge--critical { background: #ef4444; border-color: #b91c1c; color: #fff; }

/* Кастомный селектор тенантов (суперадмин): триггер + выпадающее меню строк
   (емейл + баланс-бейдж). Заменяет нативный <select> (в option нельзя
   встроить цветной бейдж). Клик по строке = POST /admin/tenant/select. */
.tenant-picker {
    position: relative;
    display: inline-flex;
}
.tenant-picker__trigger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--control-h);
    padding: 0 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    font-family: inherit;
    font-size: 10px;
    cursor: pointer;
    max-width: 280px;
    white-space: nowrap;
    transition: border-color 0.15s;
}
.tenant-picker__trigger:hover {
    border-color: var(--color-accent);
}
.tenant-picker__current {
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 150px;
}
.tenant-picker__chevron {
    font-size: 9px;
    color: var(--color-text-muted);
}
.tenant-picker__menu {
    position: absolute;
    top: calc(100% + 2px);
    right: 0;
    z-index: 1000;
    min-width: 240px;
    max-height: 60vh;
    overflow-y: auto;
    padding: 2px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
}
.tenant-picker__menu[hidden] {
    display: none;
}
.tenant-picker__row-form {
    margin: 0;
}
.tenant-picker__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 4px 5px;
    border: none;
    border-radius: 3px;
    background: transparent;
    color: var(--color-text);
    font-family: inherit;
    font-size: 10px;
    text-align: left;
    cursor: pointer;
}
.tenant-picker__row:hover {
    background: var(--color-card);
}
.tenant-picker__row--current {
    background: var(--color-accent-bg);
    color: var(--color-accent);
    font-weight: 600;
}
.tenant-picker__email {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-logout {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Квадрат logout ужат и по высоте, и по ширине — компактнее остальных
       контролов шапки. */
    width: 20px;
    height: 20px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text-muted);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.app-logout svg {
    width: 13px;
    height: 13px;
}
.app-logout:hover {
    background: #fee2e2;
    border-color: #f87171;
    color: #b91c1c;
}
.app-identity__login {
    font-size: 10px;
    color: var(--color-accent);
    text-decoration: none;
}
.app-identity__login:hover {
    text-decoration: underline;
}

/* P407 Kill Switch: глобальный рычаг вкл/выкл бота в шапке, слева от
   баланса. margin-left:auto на рычаге группирует рычаг+баланс у правого
   края; сиблинг-правило ниже переопределяет auto баланса на фикс-зазор. */
.bot-switch {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: var(--control-h);
    padding: 0 5px;
    /* Метка «бот вкл/выкл» — строго в 1 строку: без nowrap нав сжимает
       пилюлю по ширине, текст переносится на 2 строки и пилюля становится
       выше остальных контролов (ломает направляющие шапки). */
    white-space: nowrap;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-bg);
    color: var(--color-text);
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, opacity 0.15s;
}
.bot-switch:hover {
    filter: brightness(0.97);
}
.bot-switch__light {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #9ca3af;  /* unknown/loading — серый */
    transition: background 0.15s, box-shadow 0.15s;
}
.bot-switch--on {
    background: #ecfdf5;
    border-color: #6ee7b7;
    color: #065f46;
}
.bot-switch--on .bot-switch__light {
    background: #10b981;  /* зелёный — бот включён */
    box-shadow: 0 0 6px 1px rgba(16, 185, 129, 0.7);
}
.bot-switch--off {
    background: #fee2e2;
    border-color: #f87171;
    color: #7f1d1d;
}
.bot-switch--off .bot-switch__light {
    background: var(--color-danger);  /* красный — бот выключен (Kill Switch) */
    box-shadow: 0 0 6px 1px rgba(220, 38, 38, 0.75);
}
.bot-switch--busy {
    opacity: 0.6;
    pointer-events: none;
}

.app-nav {
    display: flex;
    gap: var(--space-xs);
    /* 8 вкладок — на узких экранах не помещаются в шапку. Разрешаем
       горизонтальный скролл. */
    overflow-x: auto;
    flex-wrap: nowrap;
    /* Вкладки центрируем по вертикали — в одну ось с брендом и правыми
       контролами шапки (иначе при наличии полосы прокрутки они прижаты
       к верху). */
    align-items: center;
    /* P198 (#445): нав забирает всю свободную ширину и сжимается сам
       (min-width:0 разрешает шринк ниже контента → внутренний скролл),
       чтобы бренд/подзаголовок не сдавливались и текст не переносился. */
    flex: 1 1 auto;
    min-width: 0;
    /* Полосу прокрутки прячем ПОЛНОСТЬЮ: на Windows `scrollbar-width: thin`
       всё равно резервирует ~14px высоты → инфлейтит шапку и ломает
       выравнивание вкладок по центральной оси. Вкладки скроллятся колесом;
       ключевые разделы продублированы в дропдаунах. */
    scrollbar-width: none;
}

.app-nav::-webkit-scrollbar {
    display: none;
}

.app-nav__tab {
    /* P192: компактнее по вертикали (3px вместо 8px). */
    padding: 3px 5px;
    border-radius: 3px;
    text-decoration: none;
    /* #391: чёрный текст неактивных вкладок (был бледно-серый muted). */
    color: #000;
    /* Пункты меню — исключение из базовых 10px: 13px. */
    font-size: 13px;
    font-weight: 500;
    /* Чтобы при прокрутке шапки текст вкладок не схлопывался. */
    white-space: nowrap;
    flex-shrink: 0;
}

.app-nav__tab:hover {
    background: var(--color-bg);
    color: var(--color-text);
}

.app-nav__tab--active {
    background: var(--color-accent-bg);
    color: var(--color-accent);
}

/* PR #514: dropdown в навигации — Каталог (Направления / Товары) и
   Администрирование (Обзор / Инструменты). На hover родителя
   разворачивается выпадашка. */
.app-nav__group {
    position: relative;
    display: inline-flex;
    align-items: center;
}
.app-nav__tab--group {
    border: none;
    background: transparent;
    /* font-family (НЕ шорткат font:) — иначе font-size схлопнется до базовых
       10px и группы-кнопки станут мельче ссылок-вкладок. Размер берётся из
       .app-nav__tab (13px, исключение для меню). */
    font-family: inherit;
    cursor: pointer;
    /* #391: чёрный текст неактивных вкладок-групп (был muted). */
    color: #000;
    /* P192: выровнено с .app-nav__tab (3px вместо 8px). */
    padding: 3px 5px;
    border-radius: 3px;
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
}
.app-nav__tab--group:hover {
    background: var(--color-bg);
    color: var(--color-text);
}
.app-nav__group--active .app-nav__tab--group {
    background: var(--color-accent-bg);
    color: var(--color-accent);
}
.app-nav__chevron {
    font-size: 9px;
    line-height: 1;
    opacity: 0.7;
}
.app-nav__dropdown {
    /* PR #516: fixed вместо absolute — родитель .app-nav имеет
       overflow-x: auto (и неявно overflow-y: auto), что обрезало
       dropdown. С fixed позиционирование считается JS'ом от viewport'а,
       и dropdown свободно выходит за nav. top/left устанавливаются
       inline-скриптом в _initNavDropdowns (base.html).
       На случай отключённого JS — fallback на top 100% / left 0. */
    position: fixed;
    top: 100%;
    left: 0;
    z-index: 200;
    min-width: 200px;
    padding: 4px;
    background: #ffffff;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    box-shadow: 0 6px 18px rgba(15, 23, 42, 0.12);
    display: none;
    margin-top: 2px;
}
.app-nav__group:hover .app-nav__dropdown,
.app-nav__group:focus-within .app-nav__dropdown,
.app-nav__group--open .app-nav__dropdown,
.app-nav__dropdown:hover {
    display: block;
}
.app-nav__subtab {
    display: block;
    padding: 5px 5px;
    border-radius: 3px;
    text-decoration: none;
    color: var(--color-text-muted);
    font-size: 10px;
    white-space: nowrap;
}
.app-nav__subtab:hover {
    background: var(--color-bg);
    color: var(--color-text);
}
.app-nav__subtab--active {
    background: var(--color-accent-bg);
    color: var(--color-accent);
}

/* === Страница-заглушка для не-реализованных вкладок ============== */

.placeholder-page {
    padding: 5px var(--space-xl);
    max-width: 720px;
    margin: var(--space-xl) auto;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
}

.placeholder-page__title {
    margin: 0 0 var(--space-sm) 0;
    font-size: 13px;
    color: var(--color-text);
}

.placeholder-page__phase {
    margin: 0 0 var(--space-md) 0;
    font-size: 10px;
    color: var(--color-text-muted);
}

.placeholder-page__phase strong {
    color: var(--color-accent);
}

.placeholder-page__desc {
    margin: 0;
    color: var(--color-text);
    line-height: 1.55;
}

/* === Основной контейнер ========================================= */

.app-main {
    /* P192: остаток высоты после шапки (body — flex-column). Без магической
       константы 49px — высота пересчитывается при любом изменении nav. */
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

/* === Страница диалогов ========================================== */

.dialogs-page {
    height: 100%;
}

.dialogs-page__layout {
    display: grid;
    /* 3 колонки: таблица | resizer | preview. CSS-переменная
       --preview-width задаётся inline-стилем из JS (initResizableSplit
       в base.html), сохраняется в localStorage. По умолчанию 480px. */
    grid-template-columns: 1fr 4px var(--preview-width, 480px);
    height: 100%;
    background: var(--color-border);
}

/* Узкая «рукоятка» между колонками. Сама занимает 4px, но через
   ::before расширена до 8px для удобного попадания мышью. */
.dialogs-page__resizer {
    position: relative;
    background: var(--color-border);
    cursor: col-resize;
    user-select: none;
    z-index: 5;
}

.dialogs-page__resizer::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: -2px;
    right: -2px;
}

.dialogs-page__resizer:hover,
.dialogs-page__resizer.is-dragging {
    background: var(--color-accent);
}

/* Когда тянем — отключаем выделение текста и переключаем cursor
   на всё body (чтобы при выходе курсора за рукоятку цикл drag не
   терялся). */
body.is-dragging-split {
    cursor: col-resize !important;
    user-select: none !important;
}

.dialogs-list {
    background: var(--color-surface);
    overflow: hidden;
    /* Внешний «воздух» убран вообще — фильтры и таблица во всю ширину/высоту
       колонки (у самой панели фильтров и ячеек таблицы свои внутренние
       отступы). */
    padding: 0;
    /* Колонка под таблицу: шапка с фильтрами на естественной высоте,
       шелл с таблицей+футером — занимает остаток. flex-1 нужен, чтобы
       внутренний скролл шёл только по самой таблице, а sticky-футер
       был видимым всегда. */
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* #dialogs-table — контейнер для HTMX swap'а партиала. Внутри: форма
   фильтров + .dt-shell. Чтобы шелл получил ограничение по высоте и
   .dt-table-wrap скроллился, цепочка flex'а должна быть непрерывной:
   .dialogs-list (flex:column) → #dialogs-table (flex:1, column) →
   .dt-shell (flex:1, column) → .dt-table-wrap (flex:1, scroll). */
#dialogs-table {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* Форма фильтров — фиксированная высота, не растёт за счёт скролла. */
.dialogs-filters {
    flex-shrink: 0;
}

.dialogs-list__hint {
    font-size: 10px;
    color: var(--color-text-muted);
}

.dialogs-list__placeholder {
    margin-top: var(--space-lg);
    padding: 5px;
    border: 1px dashed var(--color-border);
    border-radius: 3px;
    color: var(--color-text-muted);
}

.dialogs-list__placeholder p {
    margin: 0 0 var(--space-sm) 0;
}

.dialogs-list__placeholder p:last-child {
    margin-bottom: 0;
}

.dialogs-preview {
    background: var(--color-surface);
    overflow: auto;
    /* Внешний воздух убран; внутренние отступы дают сами сообщения превью. */
    padding: 0;
}

.dialogs-preview__empty {
    color: var(--color-text-muted);
    font-size: 10px;
    text-align: center;
    margin-top: 30vh;
}

/* === Панель фильтров (компактная) =============================== */

.dt-filters {
    display: grid;
    /* 3 колонки: основные фильтры тянутся, две правые — auto под содержимое */
    grid-template-columns: 1fr auto auto;
    gap: 10px;
    /* position:relative — якорь для абсолютной кнопки «обновить» (.df-refresh)
       в правом нижнем углу панели. Правый паддинг под кнопку, чтобы она не
       налезала на крайний инпут колонки «Время». */
    position: relative;
    padding: 5px 30px 5px 5px;
    /* Единый цвет панелей (раньше был --color-bg → серый, выбивался). */
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    /* Белой щели к шапке таблицы нет — вместо неё тёмно-серая линия-
       разделитель: убираем нижний отступ, низ панели = тёмная кромка. */
    margin-bottom: 0;
    border-bottom: 2px solid #8b909b;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    font-size: 10px;
}

.dt-filters__main {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

/* Каждая строка фильтров — горизонтальный flex с label слева. */
.df-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    min-height: 22px;
}

.df-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    flex: 0 0 auto;
    line-height: 18px;
    min-width: 50px;
}

/* Все буквы внутри фильтр-панели — 10px. Лейблы (.df-label) — капсом,
   одинаковые с заголовками столбцов таблицы. Form-контролы (select/input/
   button) не наследуют font-size от .dt-filters, поэтому задаём явно. */
.dt-filters label,
.dt-filters span,
.dt-filters select,
.dt-filters input,
.dt-filters button {
    font-size: 10px;
}

.df-label--inline {
    margin-left: 2px;
    min-width: 0;
}

.df-label--block {
    display: block;
    margin-bottom: 4px;
}

.df-label--spaced { margin-top: 8px; }

.df-sep {
    color: var(--color-border);
    margin: 0 3px;
}

/* === Поля ввода — все одной высоты для выравнивания по baseline ====== */
.df-select,
.df-date,
.df-num,
.df-input {
    height: 18px;
    box-sizing: border-box;
    /* Плотные внутренние отступы: 2px сверху/снизу, 3px по бокам. */
    padding: 2px 3px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 10px;
    font-family: inherit;
    line-height: 1.15;
}

.df-select { min-width: 100px; max-width: 175px; }
.df-select--narrow { min-width: 0; width: 100px; }
/* Конкретно msg_direction — короче, под самое длинное «оператора». */
.df-select[name="msg_direction"] { width: 80px; min-width: 0; }

/* === Пара «метка + контрол» как атомарный блок строки фильтров ======
   .df-group — inline-flex с white-space:nowrap: при переносе строки
   фильтров (.df-row flex-wrap) метка и её селектор переносятся ВМЕСТЕ,
   метка никогда не отрывается от своего контрола. Единый паттерн для
   всех пар бара (Канал/Линия/Роль оператора/Оператор). */
.df-group {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    white-space: nowrap;
}

/* Чип-мультиселект ЛИНИЯ/ОПЕРАТОР (общий .rules-filter__multi +
   window.TagMultiSelect) в компактном баре диалогов. База .rules-filter__multi
   рассчитана на крупный rules-фильтр — ужимаем под 10px-бар. */
.dt-filters .df-multi {
    min-width: 120px;
    max-width: 190px;
    min-height: 18px;
    padding: 1px 3px;
    gap: 3px;
    align-self: center;
}
.dt-filters .df-multi .rules-filter__multi-input {
    font-size: 10px;
    min-width: 48px;
    padding: 0 2px;
}
.dt-filters .df-multi .rules-filter__chip { font-size: 10px; }

/* Кнопка «обновить» — иконка в правом нижнем углу фильтр-панели. */
.df-refresh {
    position: absolute;
    right: 5px;
    bottom: 5px;
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: 4px;
    background: var(--color-surface);
    color: var(--color-text-muted);
    cursor: pointer;
    z-index: 3;
    transition: background 120ms, color 120ms, border-color 120ms, transform 80ms;
}
.df-refresh:hover {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
    color: var(--color-accent);
}
.df-refresh:active { transform: scale(0.92); }
.df-date   { width: 122px; }
.df-num    { width: 42px; text-align: center; font-variant-numeric: tabular-nums; }
.df-input  { width: 112px; }

/* Линия с проблемами коннектора в дропдауне «Линия» — серый цвет
   и курсив. Выбрать всё ещё можно. */
.df-select option.opt-unhealthy {
    color: var(--color-text-muted);
    font-style: italic;
}

.df-select:focus,
.df-date:focus,
.df-num:focus,
.df-input:focus {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 2px var(--color-accent-bg);
}

/* === Диапазон дат + пресеты ======================================== */
.df-range {
    display: flex;
    align-items: center;
    gap: 3px;
}

.df-range-sep {
    color: var(--color-text-muted);
    font-size: 10px;
}

.df-presets {
    display: flex;
    gap: 3px;
}

.df-preset {
    /* Высота в одну линейку с инпутами фильтров (18px). */
    height: 18px;
    box-sizing: border-box;
    padding: 2px 5px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text);
    font-size: 10px;
    cursor: pointer;
    line-height: 14px;
}

.df-preset:hover {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.df-preset--clear {
    color: var(--color-text-muted);
    padding: 3px 5px;
}

/* === Чекбоксы статусов ============================================= */
.df-checks {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
}

.df-check {
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    opacity: 0.55;
    transition: opacity 0.1s ease;
    height: 22px;
    box-sizing: border-box;
    padding: 0 5px;
    font-size: 10px;
}

.df-check input[type="checkbox"] {
    margin: 0;
    width: 12px;
    height: 12px;
    accent-color: currentColor;
}

.df-check:hover { opacity: 0.8; }

.df-check:has(input:checked) {
    opacity: 1;
    box-shadow: inset 0 0 0 1px currentColor;
}

/* Статус-фильтры — label'ы с ОБОИМИ классами .df-check и .status-badge.
   .status-badge объявлен ниже в файле и той же специфичностью перетирал
   `display: inline-flex` у .df-check своим `display: inline-block` → галка
   раскладывалась как inline-элемент по baseline и «выбивалась» над текстом
   метки. Комбо-селектор (специфичность 0,2,0) возвращает flex-центрирование:
   чекбокс встаёт на один уровень со своей меткой. */
.df-check.status-badge {
    display: inline-flex;
    align-items: center;
}

/* === Радио-кнопки ================================================== */
.df-radios {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

.df-radio {
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 10px;
}

.df-radio input[type="radio"] {
    margin: 0;
    width: 12px;
    height: 12px;
}

/* === Сброс ========================================================= */
.df-reset {
    height: 22px;
    box-sizing: border-box;
    padding: 0 5px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text-muted);
    font-size: 10px;
    cursor: pointer;
}

.df-reset:hover {
    background: #fff5f5;
    border-color: #ef4444;
    color: #b91c1c;
}

/* === Правая колонка: счётчики ====================================== */
.dt-filters__counters {
    display: flex;
    flex-direction: column;
    padding-left: 5px;
    border-left: 1px solid var(--color-border);
}

.df-counter-row {
    display: grid;
    /* Ширина первой колонки задаётся модификаторами родителя ниже —
       у каждого блока своё (под самое длинное название). */
    grid-template-columns: var(--counter-name-w, 60px) auto auto auto;
    align-items: center;
    gap: 4px;
    margin-bottom: 3px;
    font-size: 10px;
}

.dt-filters__counters--msgs  { --counter-name-w: 54px; }  /* «оператор» */
.dt-filters__counters--times { --counter-name-w: 88px; }  /* «среднее ответа» */

.df-counter-name {
    color: var(--color-text);
    font-weight: 500;
}

.df-counter-name--client   { color: var(--color-status-active-text); }
.df-counter-name--operator { color: #1e3a8a; }
.df-counter-name--bot      { color: var(--color-busy-text); }

/* Заблокированный фильтр (визуально отмечен — функция «доступна,
   но фильтрация пока не реализована»). */
.df-counter-row--disabled .df-counter-name { color: var(--color-border); }
.df-counter-row--disabled .df-num          { background: var(--color-bg); cursor: not-allowed; }

/* Toggle-радио (ПЕРВОЕ/ПОСЛЕДНЕЕ, СОЗДАН/ЗАКРЫТ) — выглядят как
   кнопки-переключатели, чтобы их выбор был визуально яснее обычных
   радио-кружков. */
.df-radios--toggle .df-radio {
    padding: 3px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    transition: background 0.1s, border-color 0.1s, color 0.1s;
}
.df-radios--toggle .df-radio input { display: none; }
.df-radios--toggle .df-radio:has(input:checked) {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
    color: var(--color-accent);
    font-weight: 600;
}

/* === Таблица диалогов =========================================== */

.dt-loading {
    padding: 5px;
    text-align: center;
    color: var(--color-text-muted);
}

.dt-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-bottom: var(--space-sm);
}

.dt-toolbar__counter {
    font-size: 10px;
    color: var(--color-text-muted);
}

.dt-toolbar__pagesize {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    color: var(--color-text-muted);
    margin-left: auto;     /* прижимаем все правые элементы тулбара вправо */
}

.dt-toolbar__pagesize select { width: 60px; }

.dt-toolbar__nav {
    display: inline-flex;
    align-items: center;
    gap: 2px;
}

.dt-toolbar__navbtn {
    height: 22px;
    min-width: 24px;
    padding: 0 5px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text);
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
}

.dt-toolbar__navbtn:hover:not(:disabled) {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.dt-toolbar__navbtn:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

.dt-toolbar__pagenum {
    font-size: 10px;
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
    padding: 0 5px;
}

/* === Переключатель столбцов (A2-5) =============================== */
.dt-cols {
    display: inline-block;
}

.dt-cols__summary {
    cursor: pointer;
    user-select: none;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 4px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    list-style: none;
}

.dt-cols__summary::-webkit-details-marker { display: none; }
.dt-cols__summary:hover { color: var(--color-accent); border-color: var(--color-accent); }

.dt-cols__list {
    position: absolute;
    z-index: 10;
    margin-top: 4px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 4px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}

.dt-cols__check {
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
}

.dt-cols__check input { margin: 0; width: 13px; height: 13px; }

/* Шелл «таблица + sticky-футер» — занимает остаток вертикали внутри
   .dialogs-list, делит высоту на скроллируемую таблицу и зафиксированный
   снизу footer. Один общий border вокруг шелла. */
.dt-shell {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    overflow: hidden;
}

/* Тело таблицы — скроллируется в обе стороны. Sticky thead держится
   сверху (см. ниже), sticky первая колонка с шестерёнкой — слева. */
.dt-table-wrap {
    flex: 1;
    min-height: 0;
    overflow: auto;
    background: var(--color-surface);
}

.dt-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--color-surface);
    /* Компактность по образцу таблицы расходов (.ovw__table): мелкий шрифт,
       плотные строки, видимые границы столбцов/строк. */
    font-size: 10px;
}

.dt-table thead th {
    position: sticky;
    top: 0;
    background: var(--color-bg);
    z-index: 2;
}

/* Первая колонка-шапка с шестерёнкой — sticky слева И сверху одновременно.
   z-index выше остальных, чтобы при горизонтальной прокрутке оставаться
   поверх обычных th. Делаем колонку максимально узкой — только под
   квадрат кнопки 22×22 без лишних отступов. */
.dt-th--gear {
    position: sticky !important;
    left: 0;
    top: 0;
    z-index: 5 !important;
    width: 28px;
    min-width: 28px;
    padding: 2px !important;
    text-align: center !important;
    box-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.08);
    cursor: default;
}

/* Заглушка-ячейка под шестерёнкой в каждом ряду tbody — sticky слева,
   фон под цвет строки. На hover/selected подсвечивается синхронно с
   остальными ячейками строки благодаря inherit. */
.dt-cell--gear {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--color-surface);
    width: 28px;
    min-width: 28px;
    padding: 0 !important;
    box-shadow: 2px 0 4px -2px rgba(0, 0, 0, 0.06);
}

.dt-row:hover .dt-cell--gear {
    background: var(--color-accent-bg);
}

.dt-row--selected .dt-cell--gear {
    background: var(--color-accent-bg);
}

/* Попап шестерёнки: маленький `<details>`, абсолютный panel снизу. */
.dt-cols-popup {
    display: inline-block;
    /* relative якорь для absolute-panel; иначе попап «уезжает»
       относительно viewport в sticky-th. */
    position: relative;
}

.dt-cols-popup__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    color: var(--color-text);
    /* Браузеры по умолчанию ставят cursor: text/default на <summary> —
       явно делаем «руку», как у любой кнопки. */
    cursor: pointer !important;
    font-size: 10px;
    line-height: 1;
    list-style: none;
    user-select: none;
}

.dt-cols-popup__btn::-webkit-details-marker { display: none; }
.dt-cols-popup__btn::marker { content: ""; }

.dt-cols-popup__btn:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.dt-cols-popup[open] .dt-cols-popup__btn {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.dt-cols-popup__panel {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 10;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px;
    min-width: 220px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
    display: flex;
    flex-direction: column;
    gap: 4px;
    text-align: left;
    text-transform: none;
    letter-spacing: 0;
    color: var(--color-text);
    font-weight: normal;
}

.dt-cols-popup__title {
    font-size: 13px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin: 0 0 4px 0;
    text-align: left;
}

.dt-cols-popup__title small {
    text-transform: none;
    letter-spacing: 0;
}

.dt-cols-popup__check {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 3px 4px;
    cursor: pointer;
    font-size: 10px;
    text-transform: none;
    letter-spacing: 0;
    color: var(--color-text);
    border-radius: 3px;
}

.dt-cols-popup__check:hover {
    background: var(--color-bg);
}

.dt-cols-popup__check input {
    margin: 0;
    width: 13px;
    height: 13px;
}

.dt-cols-popup__reset {
    margin-top: 6px;
    padding: 4px 5px;
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text-muted);
    font-size: 10px;
    cursor: pointer;
    text-align: left;
}

.dt-cols-popup__reset:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

/* Бейдж 🔗 рядом с ID — компактный, не задвигает число. */
.dt-cell-sib {
    margin-left: 4px;
    font-size: 10px;
    cursor: help;
    opacity: 0.85;
}

/* Sticky-футер таблицы: размещается под скролл-телом, остаётся видимым.
   Все элементы — pagesize/counter/nav — в одну строку, без переноса. */
.dt-footer {
    flex-shrink: 0;
    border-top: 1px solid var(--color-border);
    background: var(--color-bg);
    padding: 5px 5px;
    display: flex;
    align-items: center;
    gap: var(--space-md);
    font-size: 10px;
    flex-wrap: nowrap;
    white-space: nowrap;
}

/* Левая группа — «Показывать по N · N–M из K» — рядом с селектором.
   Внутренний flex-row для select+counter; во внешнем .dt-footer
   занимает свою долю слева. */
.dt-footer__pagebox {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 0 1 auto;
    min-width: 0;
}

/* Правая группа — навигация. margin-left: auto толкает её до правого
   края даже при разной ширине pagebox'а. */
.dt-footer__nav {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}

.dt-footer__navbtn {
    width: 26px;
    height: 26px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text);
    cursor: pointer;
    font-size: 10px;
    line-height: 1;
}

.dt-footer__navbtn:hover:not(:disabled) {
    color: var(--color-accent);
    border-color: var(--color-accent);
}

.dt-footer__navbtn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.dt-footer__pagenum {
    margin: 0 6px;
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}

.dt-footer__pagesize {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-muted);
}

.dt-footer__pagesize select {
    width: 64px;
    font-size: 10px;
}

.dt-footer__counter {
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}

.dt-th {
    text-align: left;
    /* Плотная шапка по образцу .ovw__table (было 8px/16px — лишний воздух). */
    padding: 3px 5px;
    font-weight: 600;
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    border-bottom: 1px solid #c7ccd6;
    /* Видимые границы столбцов. */
    border-right: 1px solid var(--color-border);
    white-space: nowrap;
}
/* Последняя колонка — без правой границы (как в образце). */
.dt-th:last-child,
.dt-cell:last-child {
    border-right: none;
}

.dt-th--id        { width: 64px; }
.dt-th--wide      { width: auto; }
.dt-th--counters  { width: 110px; text-align: right; }

/* Сортируемые заголовки — кликабельны, активный подсвечен. */
.dt-th--sortable {
    cursor: pointer;
    user-select: none;
}
.dt-th--sortable:hover { color: var(--color-accent); }
.dt-th--active-sort    { color: var(--color-accent); }
.dt-th__arrow {
    margin-left: 4px;
    font-size: 10px;
}

.dt-row {
    /* Заметная граница между строками — как в Каталоге/списке диалогов
       (slate-300). */
    border-bottom: 1px solid #cbd5e1;
}

.dt-row:hover {
    /* Подсветка при наведении — лавандовый, в тему (как hover в списках). */
    background: var(--dropdown-hover-bg);
    cursor: pointer;
}

.dt-row--selected {
    /* Выбранная строка — лавандовая, с фиолетовым баром (в тему). */
    background: #ddd6fe !important;
    box-shadow: inset 3px 0 0 var(--control-focus);
}

.dt-cell {
    /* Плотные строки + видимые границы столбцов по образцу .ovw__table. */
    padding: 3px 5px;
    line-height: 1.3;
    vertical-align: top;
    border-right: 1px solid #edeff3;
}

.dt-cell--id {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 10px;
    color: var(--color-text-muted);
}

.dt-cell--mono {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 10px;
}

.dt-cell--wide {
    max-width: 0;            /* трюк, чтобы text-overflow:ellipsis сработал в td */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dt-cell--time {
    color: var(--color-text-muted);
    white-space: nowrap;
}

.dt-cell--counters {
    text-align: right;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.dt-cell--crm {
    white-space: nowrap;
    font-size: 10px;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

.crm-tag {
    display: inline-block;
    padding: 1px 5px;
    margin-right: 3px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text-muted);
    text-decoration: none;
}

.crm-tag:hover {
    color: var(--color-accent);
    border-color: var(--color-accent);
    background: var(--color-accent-bg);
}

/* Ссылки CRM в preview-метаданных. Цвет совпадает с акцентным
   синим, чтобы было сразу понятно что это интерактивный элемент. */
.crm-link {
    color: var(--color-accent);
    text-decoration: none;
    border-bottom: 1px dashed var(--color-accent);
}

.crm-link:hover {
    color: #1e40af;
    border-bottom-style: solid;
}

.dt-msg-text {
    color: var(--color-text);
}

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

.dt-empty {
    padding: 5px;
    text-align: center;
    color: var(--color-text-muted);
    font-size: 10px;
}

.channel-pill {
    display: inline-block;
    padding: 2px 5px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-size: 10px;
    color: var(--color-text);
}

.dir-arrow {
    display: inline-block;
    width: 14px;
    font-weight: 700;
    text-align: center;
    margin-right: 4px;
}

.dir-arrow--in  { color: var(--color-status-active-text); }   /* зелёный — клиент пишет нам */
.dir-arrow--out { color: #2563eb; }   /* синий — мы отвечаем */

/* Пиктограммы отправителя в колонке «Последнее сообщение».
   Цвета согласованы с другими частями UI:
   - оператор — синий (как dir-arrow--out, dp-msg--operator)
   - бот     — фиолетовый (как dp-msg--bot, бэйдж AI-бот)
   - клиент  — зелёный (как dir-arrow--in, dp-msg--client) */
.ic-wrap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    margin-right: 2px;
    vertical-align: middle;
}

.ic-wrap--operator { color: #2563eb; }
.ic-wrap--bot      { color: var(--color-busy-text); }
.ic-wrap--client   { color: var(--color-status-active-text); margin-left: 2px; margin-right: 4px; }

.ic-sender {
    display: block;
}

.counter {
    font-weight: 500;
}

.counter--total    { color: var(--color-text); }
.counter--client   { color: var(--color-status-active-text); }
.counter--operator { color: #1e3a8a; }
.counter--bot      { color: var(--color-busy-text); }

.counter__sep {
    color: var(--color-border);
    margin: 0 2px;
}

/* === Пагинация ================================================== */

.dt-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    margin-top: var(--space-lg);
    padding: 5px 0;
}

.dt-pager__btn {
    padding: 5px 5px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text);
    font-size: 10px;
    cursor: pointer;
}

.dt-pager__btn:hover:not(:disabled) {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
    color: var(--color-accent);
}

.dt-pager__btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.dt-pager__info {
    font-size: 10px;
    color: var(--color-text-muted);
}

/* === Правая панель preview ====================================== */

.dp {
    display: flex;
    flex-direction: column;
    height: 100%;
    /* Раньше тут был отрицательный margin (-space-lg) для компенсации padding'а
       родителя. Но у .dialogs-preview padding убран (=0) → отрицательный margin
       вылезал за панель и контент «уезжал за пределы». margin:0 + min-width:0. */
    margin: 0;
    min-width: 0;
    overflow-x: hidden;
}

.dp__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-md);
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg);
}

.dp__title {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
}

.dp__id {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-weight: 600;
    color: var(--color-accent);
}

.dp__line {
    font-size: 10px;
    color: var(--color-text-muted);
}

/* Оператор в шапке preview — рядом с линией. */
.dp__operator {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: 8px;
    padding-left: 5px;
    border-left: 1px solid var(--color-border);
    font-size: 10px;
    color: var(--color-text);
}

/* CRM-плашки в шапке preview — рядом с линией.
   align-items: baseline — выравниваем все плашки (Лид/Сделка/Контакт)
   по их первой текстовой строке. Стэк «Сделка + отв.» отращивает высоту
   ВНИЗ от своего бейслайна, не сдвигая визуальный ряд ссылок. */
.dp__crm {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    margin-left: 8px;
    padding-left: 5px;
    border-left: 1px solid var(--color-border);
    font-size: 10px;
}

/* Стэк «ссылка сделки + отв.» — вертикальный, чтобы подпись «отв. <имя>»
   шла строкой ниже под ссылкой и визуально к ней приклеивалась.
   inline-block (а не flex-column) — нужно, чтобы align-items: baseline
   у родителя нашёл тут реальный текстовый бейслайн ссылки. */
.crm-deal-stack {
    display: inline-block;
    line-height: 1.15;
}

/* Подпись ответственного — отдельной строкой под ссылкой сделки.
   block + max-width: 100% делает её не шире своего родителя; родитель
   же inline-block ужимается по содержимому → ширина равна ширине самой
   длинной строки в стэке, обычно — ссылке сделки. Длинное имя обрезаем
   многоточием, чтобы визуальная «привязка к сделке» сохранялась. */
.crm-link__resp {
    display: block;
    max-width: 100%;
    font-size: 10.5px;
    line-height: 1.1;
    color: var(--color-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Если CRM не привязан (нет URL) — показать как блёклый текст,
   а не ссылку. */
.crm-link--off {
    color: var(--color-text-muted);
    border-bottom: 1px dashed var(--color-text-muted);
    cursor: help;
}

/* Бейдж «связанные диалоги» — для случая, когда у клиента несколько
   chat_id на одной линии (типичный случай Avito-коннектора). */
.dp__siblings {
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg);
    font-size: 10px;
    color: var(--color-text-muted);
    flex-shrink: 0;
}

.dp__sibling-link {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 1px 5px;
    margin: 0 2px;
    font-size: 10px;
    color: var(--color-accent);
    font-family: var(--font-mono, monospace);
    cursor: pointer;
}

.dp__sibling-link:hover {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
}

.dp__meta {
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    flex-shrink: 0;
}
/* Когда блок свёрнут — снимаем нижний padding и margin у summary,
   чтобы не зиял пустой пояс над лентой. */
.dp__meta:not([open]) {
    padding-bottom: 0;
}
.dp__meta:not([open]) .dp__meta-summary {
    margin-bottom: var(--space-sm);
}

/* Когда блок метаданных раскрыт — ограничиваем высоту через
   CSS-переменную --meta-max (управляется JS-перетаскиванием) и
   делаем внутреннюю прокрутку. По умолчанию ~40% высоты preview. */
.dp__meta[open] {
    max-height: var(--meta-max, 40%);
    overflow-y: auto;
}

/* Унифицированный стиль заголовков секций preview (Метаданные, Системные).
   sticky top:0 — заголовок всегда виден поверх контента секции при скролле
   её внутрянки (в обычном <details>-потоке summary уезжает вместе с
   содержимым). Фон + z-index — чтобы перекрыть строки под ним. */
.dp__meta-summary,
.dp__service-summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 10px;
    color: var(--color-text-muted);
    user-select: none;
    margin-bottom: var(--space-sm);
    position: sticky;
    top: 0;
    background: var(--color-surface);
    z-index: 1;
    padding: 4px 0;
}

.dp__meta-summary:hover,
.dp__service-summary:hover {
    color: var(--color-text);
}

.dp__meta-grid {
    display: grid;
    grid-template-columns: 180px 1fr;
    gap: 4px var(--space-md);
    margin: 0;
    font-size: 10px;
}

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

.dp__meta-grid dd {
    margin: 0;
    color: var(--color-text);
}

/* «Статус бота» — первый пункт панели, человеко-читаемое лицо resting-state.
   Чуть выделяем (вес + основной цвет метки), чтобы оператор сразу видел, что
   с ботом происходит, не вчитываясь в остальные метаданные. */
.dp__meta-grid dt.dp__bot-status {
    color: var(--color-text);
    font-weight: 600;
}
.dp__meta-grid dd.dp__bot-status {
    font-weight: 600;
}

.dp__counters {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.counter-pill {
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    padding: 3px 5px;
    border-radius: 999px;
    font-size: 10px;
    line-height: 1.4;
    border: 1px solid transparent;
}

.counter-pill strong {
    font-size: 10px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.counter-pill--total {
    background: #f1f3f5;
    color: var(--color-text);
    border-color: var(--color-border);
}

.counter-pill--client {
    background: #e6f7ed;
    color: var(--color-status-active-text);
    border-color: #c5e9d4;
}

.counter-pill--operator {
    background: #dbeafe;
    color: #1e3a8a;
    border-color: #bfdbfe;
}

.counter-pill--bot {
    background: #ede9fe;
    color: var(--color-busy-text);
    border-color: #ddd6fe;
}

.dp__meta-grid .mono {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 10px;
    word-break: break-all;
}

/* Drag-handle между блоком метаданных и лентой сообщений.
   4px полоса с col-resize-подобным поведением (но row-resize). Скрыт,
   когда <details> свёрнут — нечего регулировать. */
.dp__resizer {
    height: 4px;
    flex-shrink: 0;
    background: var(--color-border);
    cursor: row-resize;
    user-select: none;
    position: relative;
    z-index: 5;
}

.dp__resizer::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: -2px;
    bottom: -2px;
}

.dp__resizer:hover,
.dp__resizer.is-dragging {
    background: var(--color-accent);
}

/* Если details свёрнут — handle скрываем (через комбинатор соседнего
   элемента, после .dp__meta / .dp__service идёт именно .dp__resizer). */
.dp__meta:not([open]) + .dp__resizer,
.dp__service:not([open]) + .dp__resizer {
    display: none;
}

body.is-dragging-row {
    cursor: row-resize !important;
    user-select: none !important;
}

/* Блок системных /service-сообщений — отдельная sibling-секция под
   метаданными (раньше был nested внутри .dp__meta). Свёрнут по умолчанию.
   max-height вычисляется JS-резайзером (см. dpInitMetaResize в base.html)
   через атрибут data-resize-target=".dp__service" у соседнего .dp__resizer. */
.dp__service {
    flex-shrink: 0;
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-surface);
    overflow: auto;
    max-height: 200px;     /* стартовый дефолт; JS перетирает из localStorage */
}

/* .dp__service-summary стилизуется вместе с .dp__meta-summary (см. выше)
   — единый стиль, sticky top:0. */

.dp__service-list {
    list-style: none;
    padding: 5px 0 0 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.dp__service-item {
    background: var(--color-bg);
    padding: 5px 5px;
    border-radius: 3px;
    font-size: 10px;
    color: var(--color-text-muted);
}

/* Сообщения, текст которых матчится в SYSTEM_STATUS_MARKERS — это сигналы
   смены dialog.status. Делаем их жирными и чуть контрастнее, чтобы в
   timeline'е служебных событий взгляд цеплялся именно за переключения
   статуса (открытие сессии / завершение / автозакрытие). См. шаблон
   _dialog_preview.html — класс dp__service-item--status. */
.dp__service-item--status {
    background: var(--color-surface);
    border-left: 2px solid var(--color-accent);
}

.dp__service-item--status .dp__service-text {
    font-weight: 700;
    color: var(--color-text);
}

.dp__service-time {
    display: block;
    font-size: 10px;
    color: var(--color-text-muted);
    margin-bottom: 3px;
}

.dp__service-text {
    margin: 0;
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 10px;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-all;
    color: var(--color-text);
}

.dp__messages {
    flex: 1 1 auto;
    min-height: 100px;
    overflow-y: auto;
    padding: 5px 5px 5px;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

/* Сообщение — общий стиль. Реплики на всю ширину (без боковых отступов/
   пустот): max-width:100% + align-self:stretch перекрывает flex-end/start
   вариантов. Цвет всё ещё различает client/operator/bot. */
.dp-msg {
    max-width: 100%;
    padding: 5px 5px;
    border-radius: 3px;
    font-size: 10px;
    word-wrap: break-word;
    overflow-wrap: anywhere;
}
.dp-msg--client,
.dp-msg--operator,
.dp-msg--bot {
    align-self: stretch;
}

.dp-msg__header {
    display: flex;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-bottom: 4px;
    font-size: 10px;
    color: var(--color-text-muted);
}

.dp-msg__sender {
    font-weight: 600;
}

.dp-msg__sender-name {
    font-weight: 400;
    color: var(--color-text-muted);
}

.dp-msg__bot-badge {
    display: inline-block;
    padding: 1px 5px;
    background: #ede9fe;
    color: var(--color-busy-text);
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.dp-msg__time {
    color: var(--color-text-muted);
    font-size: 10px;
    white-space: nowrap;
}

.dp-msg__text {
    white-space: pre-wrap;     /* сохраняем переносы строк из исходного текста */
    line-height: 1.4;
}

/* Встроенная цитата (quote-reply) в ленте — см. app/services/quote_reply.py.
   Свёрнута (<details>), приглушена + бордюр слева; ответ — обычным текстом. */
.dp-msg__quote {
    white-space: normal;       /* структура: гасим pre-wrap родителя */
    margin: 0 0 5px;
    border-left: 2px solid var(--color-border);
    padding-left: 7px;
}
.dp-msg__quote-summary {
    cursor: pointer;
    color: var(--color-text-muted);
    font-size: 11px;
    list-style: none;
    user-select: none;
}
.dp-msg__quote-summary::-webkit-details-marker { display: none; }
.dp-msg__quote-badge {
    color: var(--color-text-muted);
    background: var(--color-accent-bg);
    padding: 0 5px;
    border-radius: 3px;
    font-weight: 600;
}
.dp-msg__quote-author { font-weight: 600; }
.dp-msg__quote-at { font-variant-numeric: tabular-nums; }
.dp-msg__quote-text {
    white-space: pre-wrap;     /* возвращаем перенос строк телу цитаты */
    word-wrap: break-word;
    margin: 4px 0 0;
    color: var(--color-text-muted);
    font-size: 12px;
    line-height: 1.35;
}
.dp-msg__quote-reply {
    white-space: pre-wrap;
    word-wrap: break-word;
}

.dp-msg__attachments {
    margin-top: 6px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-xs);
    font-size: 10px;
}

.dp-msg__attachment {
    background: rgba(0, 0, 0, 0.06);
    padding: 2px 5px;
    border-radius: 3px;
}

.dp-msg__attachment .muted {
    color: var(--color-text-muted);
    margin-left: 4px;
}

/* === Картинки-вложения в сообщениях (Диалоги + Песочница) ===========
   Локальные per-компонент классы (без правки глобальных токенов). Контракт
   вложений — media_uploads (список {url,name,kind}). */
.dp-msg__image-link,
.tmsg__image-link {
    display: inline-block;
    line-height: 0;
}

.dp-msg__image,
.tmsg__image {
    /* Единая фиксированная высота плейсхолдера → все фото ряда выровнены по
       высоте (без «разнобоя»), а ширина считается по пропорции (width:auto) →
       картинки плотно заполняют ширину контейнера реплики и переносятся лишь
       когда ряд заполнен. max-width не даёт вылезти за бабл; для крайне широких
       кадров object-fit:cover кропит по ширине, сохраняя единую высоту ряда. */
    height: 132px;
    width: auto;
    max-width: 100%;
    border-radius: 8px;
    border: 1px solid rgba(0, 0, 0, 0.10);
    object-fit: cover;
    cursor: zoom-in;
    vertical-align: top;
}

.tmsg__attachments {
    margin-top: 6px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-xs);
}

/* Группировка фото по модели: заголовок-название + ряд фото этой модели
   (send_product_photos шлёт фото отдельной репликой «Модель: фото фото…»). */
.tmsg__attach-model,
.dp-msg__attach-model {
    font-weight: 700;
    font-size: 11px;
    color: var(--color-text-muted, #475569);
}
/* Чёткое разделение между группами моделей (как двойной перенос строки):
   первый заголовок — без отступа, последующие — с воздухом сверху, чтобы
   «Модель1: фото…» и «Модель2: фото…» не сливались. */
.tmsg__attach-model:not(:first-child),
.dp-msg__attach-model:not(:first-child) {
    margin-top: 12px;
}
.tmsg__attach-row,
.dp-msg__attach-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
}

/* === Композер: кнопка-скрепка 📎 + превью прикреплённой картинки ===== */
.dp-composer__attach,
.tchat__attach {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity 0.12s, background 0.12s;
}

.dp-composer__attach:hover:not(:disabled),
.tchat__attach:hover { opacity: 1; background: rgba(0, 0, 0, 0.06); }

.dp-composer__attach:disabled {
    opacity: 0.35;
    cursor: not-allowed;
}

/* Ряд действий формы песочницы: 📎 слева, «Отправить» справа. */
.tchat__form-actions {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
}

.tchat__form-actions .tchat__send { margin-left: auto; }

/* Превью staged-картинки до отправки (chip с миниатюрой + крестик). */
.dp-composer__attach-preview,
.tchat__attach-preview {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    margin: var(--space-xs) 0;
}

.dp-composer__chip,
.tchat__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 6px 4px 4px;
    background: rgba(0, 0, 0, 0.05);
    border: 1px solid rgba(0, 0, 0, 0.10);
    border-radius: 8px;
    max-width: 220px;
}

.dp-composer__chip-img,
.tchat__chip-img {
    width: 32px;
    height: 32px;
    border-radius: 5px;
    object-fit: cover;
    flex: 0 0 auto;
}

.dp-composer__chip-name,
.tchat__chip-name {
    font-size: 11px;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dp-composer__chip-x,
.tchat__chip-x {
    flex: 0 0 auto;
    border: none;
    background: transparent;
    color: var(--color-text-muted);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    padding: 0 2px;
}

.dp-composer__chip-x:hover,
.tchat__chip-x:hover { color: var(--color-danger); }

/* === Варианты сообщений по sender_kind === */

/* Клиент — справа, зелёный фон. Цвет совпадает с легендой счётчиков
   (counter-pill--client) и стрелкой направления (.dir-arrow--in) —
   глаз сразу связывает «зелёный = клиент» во всех частях UI.
   Раскладка как у оператора в Bitrix24: «я (оператор) пишу слева,
   собеседник (клиент) — справа». */
.dp-msg--client {
    align-self: flex-end;
    background: #e6f7ed;
    color: var(--color-status-active-text);
    border-bottom-right-radius: 2px;
}

/* Оператор — слева, голубой фон. */
.dp-msg--operator {
    align-self: flex-start;
    background: #dbeafe;
    color: #1e3a8a;
    border-bottom-left-radius: 2px;
}

/* AI-бот — слева, фиолетовый фон (отличаем от живого оператора). */
.dp-msg--bot {
    align-self: flex-start;
    background: #ede9fe;
    color: #4c1d95;
    border-bottom-left-radius: 2px;
}

/* Системные — по центру, мелкие, без фона */
.dp-msg--system {
    align-self: center;
    background: transparent;
    color: var(--color-text-muted);
    font-size: 10px;
    padding: 2px 5px;
    max-width: 90%;
    text-align: center;
}

.dp-msg--empty {
    align-self: center;
    color: var(--color-text-muted);
    font-size: 10px;
    padding: 5px;
}

/* === P5/P6 (эпик 568): timeline-лог вех работы бота ================
   Общий компонент `_bot_timeline_banner.html` — и в панели диалога (над
   лентой сообщений), и в trace-скелете. Закреплённый chip живого ожидания
   сверху + хронология вех; вид вехи декорируется иконкой/цветом по `kind`. */
.btl {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 6px 0 10px;
    padding: 8px 10px;
    border: 1px solid var(--color-border);
    border-radius: 6px;
    background: var(--color-surface);
}
/* Закреплённый chip живого ожидания — пастельно-янтарный, выделен сверху. */
.btl__livewait {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 8px;
    border-radius: 5px;
    background: #fef6e0;
    border: 1px solid #f3d98a;
    font-size: 12px;
    font-weight: 500;
}
.btl__livewait-text { flex: 1; }
.btl__livewait-time {
    font-size: 10.5px;
    color: var(--color-text-muted);
    white-space: nowrap;
}
.btl__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.btl__event {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 2px 2px 2px 4px;
    font-size: 12px;
    line-height: 1.35;
    border-left: 2px solid transparent;
}
.btl__event-icon { flex: 0 0 auto; font-size: 12px; }
.btl__event-time {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
    white-space: nowrap;
}
.btl__event-text { flex: 1; }
.btl__event-dedup {
    flex: 0 0 auto;
    font-size: 10.5px;
    font-weight: 600;
    color: #7c5d12;
    background: #fef6e0;
    border-radius: 3px;
    padding: 0 4px;
}
/* Цветовая декорация по виду вехи (левая кромка). */
.btl__event--bot-entry          { border-left-color: #15683a; }
.btl__event--bot-skip           { border-left-color: #9ca3af; }
.btl__event--status-change      { border-left-color: #0284c7; }
.btl__event--inactivity-reengage{ border-left-color: #b45309; }
.btl__event--wait-parked        { border-left-color: #f3d98a; }
.btl__event--unknown            { border-left-color: var(--color-border); }

/* === Бэйджи статусов (используются в A2-2) ====================== */

.status-badge {
    display: inline-block;
    padding: 2px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 500;
}

.status-badge--new        { background: var(--color-status-new);       color: var(--color-status-new-text); }
.status-badge--active     { background: var(--color-status-active);    color: var(--color-status-active-text); }
.status-badge--operator   { background: var(--color-status-operator);  color: var(--color-status-operator-text); }
.status-badge--escalated  { background: var(--color-status-escalated); color: var(--color-status-escalated-text); }
.status-badge--closed     { background: var(--color-status-closed);    color: var(--color-status-closed-text); }

/* P462 (ADR-0031 §D6): derive-бейдж «бронь оформлена» — НЕ статус, чистый
   производный признак «пройден узел главной задачи make_booking без
   относящейся к нему ошибки инструмента». Ортогонален status-badge: рисуется
   рядом с ним в dialog-панели (preview/таблица) и в шапке trace. Teal-палитра
   — единая с тегом «бронь» в testing-сайдбаре (.tsidebar__tag--booking). */
.booking-badge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    background: #ccfbf1;        /* teal-100 */
    color: #115e59;             /* teal-800 */
    border: 1px solid #5eead4;  /* teal-300 */
    white-space: nowrap;
    vertical-align: middle;
}

/* P449-finish: CRM-история клиента — бейджи панели диалога рядом с
   booking_badge (есть сделка / N сделок / последнее взаимодействие).
   Чистая проекция facts (см. _crm_history_badges.html). */
.crm-hist-badge {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
    vertical-align: middle;
}
.crm-hist-badge--deals {
    background: #e0e7ff;        /* indigo-100 */
    color: #3730a3;             /* indigo-800 */
    border: 1px solid #a5b4fc;  /* indigo-300 */
}
.crm-hist-badge--seen {
    background: #f1f5f9;        /* slate-100 */
    color: #334155;             /* slate-700 */
    border: 1px solid #cbd5e1;  /* slate-300 */
}

/* === Композер ответа (нижний бар preview) ========================
   Messenger-style «pill»: единая капсула с emoji-кнопкой слева,
   textarea в центре и paper-plane Send справа. Сверху — drag-handle
   для регулировки высоты композера (значит и ленты сообщений сверху).
   Высота сохраняется в localStorage (см. dpInitComposerResize в base).
   .dp__messages flex:1 — забирает всё свободное место, композер
   сидит «прибитым» к низу панели. */
.dp-composer {
    flex: 0 0 auto;
    position: relative;
    border-top: 1px solid var(--color-border);
    background: #fff;
    padding: var(--space-xs) 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    /* Регулируется через --dp-composer-height в JS. min/max — чтобы
       handle не позволил сжать в ноль или слишком разрастись. */
    min-height: 92px;
}

/* Drag-handle на верхней кромке композера. */
.dp-composer__resizer {
    position: absolute;
    top: -3px;
    left: 0;
    right: 0;
    height: 6px;
    cursor: row-resize;
    z-index: 5;
}
.dp-composer__resizer::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 36px;
    height: 3px;
    border-radius: 2px;
    background: transparent;
    transition: background-color .15s;
}
.dp-composer__resizer:hover::after,
.dp-composer__resizer.is-dragging::after {
    background: var(--color-accent);
}

/* Верхняя строка: Join / mark / hint — все на одном уровне. */
.dp-composer__row {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex-wrap: wrap;
}

.dp-composer__hint {
    font-size: 10px;
    color: var(--color-text-muted);
    line-height: 1.3;
    flex: 1 1 200px;
}

.dp-composer__user-label {
    font-weight: 500;
    color: var(--color-text);
}

.dp-composer__joined-mark {
    font-size: 10px;
    color: #16a34a;
    font-weight: 500;
}

.dp-composer__join {
    border: 1px solid var(--color-accent);
    background: var(--color-accent-bg);
    color: var(--color-accent);
    padding: 5px 5px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 500;
    cursor: pointer;
    transition: background-color .15s;
    flex: 0 0 auto;
}
.dp-composer__join:hover { background: #dbe5ff; }
.dp-composer__join:disabled {
    opacity: .5;
    cursor: not-allowed;
}
/* После успешного join скрываем кнопку и показываем галку. */
.dp-composer.is-joined .dp-composer__join { display: none; }
.dp-composer.is-joined .dp-composer__joined-mark { display: inline; }

/* Messenger-style капсула: одна общая обёртка с округлыми углами.
   Внутри — emoji-палет (left) + textarea (center, flex:1) + send (right). */
.dp-composer__pill {
    flex: 1 1 auto;
    display: flex;
    align-items: stretch;
    gap: 4px;
    background: #f3f4f6;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 4px 4px 4px 5px;
    transition: border-color .15s, background-color .15s;
    min-height: 44px;
    /* overflow:hidden специально не ставим: emoji-палет (position:absolute,
       bottom:100%) должен спокойно вылезать вверх за границы пилюли. */
}
.dp-composer__pill:focus-within {
    background: #fff;
    border-color: var(--color-accent);
}
/* Заблокированный (не joined) — приглушаем. */
.dp-composer:not(.is-joined) .dp-composer__pill {
    opacity: .65;
}

.dp-composer__text {
    flex: 1 1 auto;
    box-sizing: border-box;
    padding: 5px 5px;
    border: 0;
    background: transparent;
    font-family: inherit;
    font-size: 10px;
    line-height: 1.4;
    resize: none;
    /* Высота определяется родителем (pill растянут до низа композера). */
    height: 100%;
    min-height: 32px;
    color: var(--color-text);
    outline: none;
    overflow-y: auto;
}
.dp-composer__text:disabled {
    cursor: not-allowed;
    color: var(--color-text-muted);
}
.dp-composer__text::placeholder {
    color: var(--color-text-muted);
}

/* Эмоджи-палет на <details>. summary — триггер, .grid — выпадайка. */
.dp-composer__emoji {
    position: relative;
    flex: 0 0 auto;
    align-self: flex-end;
    /* Прибиваем к низу пилюли, чтобы при растянутом композере иконка
       оставалась на одном уровне с Send (а не уезжала в верх). */
    margin-bottom: 0;
}
.dp-composer__emoji-trigger {
    list-style: none;
    cursor: pointer;
    font-size: 10px;
    line-height: 1;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    user-select: none;
    transition: background-color .15s;
}
.dp-composer__emoji-trigger::-webkit-details-marker { display: none; }
.dp-composer__emoji-trigger:hover { background: rgba(0,0,0,0.05); }
.dp-composer__emoji[open] .dp-composer__emoji-trigger {
    background: var(--color-accent-bg);
}

.dp-composer__emoji-grid {
    position: absolute;
    bottom: 100%;
    left: 0;
    margin-bottom: 6px;
    background: #fff;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.08);
    padding: 5px;
    display: grid;
    grid-template-columns: repeat(8, 28px);
    gap: 2px;
    z-index: 20;
}
.dp-composer__emoji-cell {
    border: 0;
    background: transparent;
    cursor: pointer;
    padding: 4px;
    font-size: 10px;
    line-height: 1;
    border-radius: 3px;
}
.dp-composer__emoji-cell:hover { background: var(--color-accent-bg); }
/* Когда композер заблокирован (не присоединён) — палет тоже. */
.dp-composer:not(.is-joined) .dp-composer__emoji-trigger {
    opacity: .4;
    pointer-events: none;
}

.dp-composer__send {
    flex: 0 0 auto;
    align-self: flex-end;
    border: 0;
    background: var(--color-accent);
    color: #fff;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color .15s, transform .1s;
    padding: 0;
}
.dp-composer__send:hover:not(:disabled) { background: #1d4ed8; }
.dp-composer__send:active:not(:disabled) { transform: scale(0.94); }
.dp-composer__send:disabled {
    opacity: .35;
    cursor: not-allowed;
}
.dp-composer__send-icon {
    transform: translateX(1px); /* визуально центрируем самолётик */
}

.dp-composer__status {
    font-size: 10px;
    color: var(--color-text-muted);
    min-height: 0;
}
/* Резервируем 16px только когда есть контент — иначе под пилюлей
   зияет пустая полоса. */
.dp-composer__status:not(:empty) {
    min-height: 16px;
}
.dp-composer__status.is-error { color: var(--color-danger); }
.dp-composer__status.is-ok    { color: #16a34a; }

/* Pending message — оптимистичный рендер сразу после клика Send,
   до того как backend подтвердил отправку. После refresh'а preview
   реальная строка (с via_composer=true) приходит из БД и рендерится
   обычным стилем. */
.dp-msg--pending .dp-msg__text,
.dp-msg--pending {
    border-style: dashed !important;
    border-width: 1px !important;
    border-color: var(--color-accent) !important;
    opacity: .85;
}
.dp-msg--pending .dp-msg__time::after {
    content: " · отправляется…";
    color: var(--color-text-muted);
    font-style: italic;
}

/* ========================================================================
 *  Вкладка «Тестирование» — Phase 1.8
 * ====================================================================== */

.testing-page {
    height: 100%;
    display: grid;
    /* PR #413: Сетка теперь:
       sidebar (var) | resizer | chat (1fr) | resizer | trace (var).
       Обе боковые колонки растягиваемые. */
    grid-template-columns:
        var(--testing-sidebar-w, 280px)
        6px
        1fr
        6px
        var(--testing-trace-w, 420px);
    overflow: hidden;
    background: var(--color-bg);
}

/* Вертикальный drag-handle между чатом и trace-панелью. Чтобы курсор
   менялся даже на тонком 6px-стрипе, добавляем небольшой hit-target
   через ::before. */
.testing-resizer {
    cursor: col-resize;
    background: var(--color-border);
    position: relative;
}
.testing-resizer::before {
    content: '';
    position: absolute;
    inset: 0 -3px;        /* расширяем hit-зону на ±3px по бокам */
}
.testing-resizer:hover,
.testing-resizer.is-dragging {
    background: var(--color-accent);
}
body.is-dragging-col {
    cursor: col-resize !important;
    user-select: none !important;
}

/* === Sidebar === */

.testing-sidebar {
    background: var(--color-card);
    border-right: 1px solid var(--color-border);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.tsidebar__header {
    padding: 5px;
    font-weight: 600;
    border-bottom: 1px solid var(--color-border);
}

.tsidebar__new {
    padding: 5px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
}

/* PR #413: компактная двухколоночная раскладка форма создания диалога. */
.tsidebar__new--compact {
    padding: 5px 5px;
    gap: 4px;
}
.tsidebar__form-row {
    display: flex;
    gap: 4px;
    align-items: stretch;
}
.tsidebar__form-row--actions {
    margin-top: 4px;
}
.tsidebar__label--inline {
    flex: 1;
    min-width: 0;
    margin: 0;
}
.tsidebar__label-text {
    font-size: 10.5px;
    color: var(--color-text-muted);
    margin-bottom: 1px;
    display: block;
}
.tsidebar__input--inline {
    flex: 1;
    min-width: 0;
}
.tsidebar__btn--inline {
    margin: 0;
    padding: 5px 5px;
    font-size: 12.5px;
    flex-shrink: 0;
}

.tsidebar__label {
    font-size: 10px;
    color: var(--color-text-muted);
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.tsidebar__select,
.tsidebar__input {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    font-size: 10px;
    width: 100%;
}

.tsidebar__btn {
    margin-top: var(--space-xs);
    padding: 5px 5px;
    background: var(--color-accent);
    color: #fff;
    border: 0;
    border-radius: 3px;
    font-weight: 500;
    cursor: pointer;
}

.tsidebar__btn:hover {
    filter: brightness(1.05);
}

/* Кнопка «Очистить тестовые брони» — danger-вариант: серый фон, на
   hover темнеет. Не red, чтобы не нагнетать панику — действие
   обратимое (только удаляет sandbox-сделки в CRM, не клиентские). */
.tsidebar__btn--danger {
    background: #475569;
    margin-top: var(--space-sm);
    width: 100%;
}

.tsidebar__btn--danger:hover {
    background: #1f2937;
    filter: none;
}

.tsidebar__cleanup {
    padding: var(--space-xs) 5px;
}

.tsidebar__list {
    list-style: none;
    margin: 0;
    padding: var(--space-xs);
    overflow-y: auto;
    flex: 1;
}

.tsidebar__empty {
    padding: 5px;
    color: var(--color-text-muted);
    font-size: 10px;
    text-align: center;
}

/* Instant-shell: спиннер пока список sandbox-диалогов догружается
   async через /admin/testing/sidebar.fragment (см. testing.html refresh). */
.tsidebar__loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 14px 5px;
    color: var(--color-text-muted);
    font-size: 11px;
}
.tsidebar__spinner {
    width: 14px;
    height: 14px;
    border: 2px solid var(--color-border, #d0d0d0);
    border-top-color: var(--color-accent, #4a90d9);
    border-radius: 50%;
    animation: tsidebar-spin 0.7s linear infinite;
}
@keyframes tsidebar-spin {
    to { transform: rotate(360deg); }
}

.tsidebar__item {
    position: relative;
    display: block;
    /* Разделители между диалогами — как строки в Каталоге (slate-300). */
    border-bottom: 1px solid #cbd5e1;
    margin-bottom: 0;
}

.tsidebar__item:last-child {
    border-bottom: 0;
}

/* Подсветка строки при наведении — лавандовый, в тему (как hover в списках/
   dropdown'ах). На активной строке не перекрываем её акцент. */
.tsidebar__item:not(.tsidebar__item--active):hover {
    background: var(--dropdown-hover-bg);
}

.tsidebar__item--active {
    /* PR #413: усилен контраст активной плашки. В лавандовой теме —
       насыщенный фиолетовый фон + 3px-bar акцента сбоку. */
    background: #ddd6fe;          /* violet-200 */
    box-shadow: inset 3px 0 0 var(--control-focus);  /* violet left bar */
}
.tsidebar__item--active .tsidebar__dlg-label {
    color: #4c1d95;     /* violet-900 */
    font-weight: 700;
}
.tsidebar__item--active .tsidebar__dlg-id,
.tsidebar__item--active .tsidebar__dlg-meta,
.tsidebar__item--active .tsidebar__dlg-time {
    color: #5b21b6;     /* violet-800 */
}

.tsidebar__link {
    text-decoration: none;
    color: var(--color-text);
    padding: 5px 5px 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
    overflow: visible;
    cursor: pointer;
}

.tsidebar__dlg-id {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: var(--color-text-muted);
}

.tsidebar__dlg-label {
    font-size: 10px;
    font-weight: 500;
    line-height: 1.25;
    min-width: 0;
    overflow: hidden;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.tsidebar__dlg-meta {
    font-size: 10px;
    color: var(--color-text-muted);
    flex-shrink: 0;
    white-space: nowrap;
}

.tsidebar__del {
    position: absolute;
    top: 18px;
    right: 5px;
    z-index: 2;
    padding: 0;
}

.tsidebar__del-btn {
    background: transparent;
    border: 0;
    color: var(--color-text-muted);
    font-size: 10px;
    cursor: pointer;
    padding: 3px 5px;
    line-height: 1;
}

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

/* PR #409: tsidebar dialog tags row.
   Раскладка плашки sandbox-диалога:
     [#id] ─────────── [N сообщ.]
     <Эльвира / rental-mvp v1>
     [Status] [L123] [D456] [C789] [бронь]
   Tags носят классы .status-badge / .crm-tag / .tsidebar__tag--booking
   для разных фонов. .tsidebar__tag — общий wrapping shrink + размер. */
.tsidebar__dlg-row {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-start;
    align-items: baseline;
    gap: 2px 8px;
    min-width: 0;
}
.tsidebar__dlg-row--top {
    padding-right: 2px;
}
.tsidebar__dlg-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 3px 4px;
    margin-top: 3px;
    /* PR #516: явный max-width = ширина родителя минус padding,
       чтобы flex-wrap сработал даже когда суммарная ширина chip'ов
       больше item-width. Раньше последний chip (API err) вылезал
       за карточку. */
    min-width: 0;
    width: 100%;
    max-width: 100%;
    overflow: visible;
}
.tsidebar__tag {
    /* PR #465: enforce inline-flex + max-height. Жёстко фиксируем.
       PR #516: уменьшили font/padding и разрешили shrink + ellipsis
       для длинных значений (D-deal-id'ы могут быть 6-8 цифр). */
    display: inline-flex !important;
    align-items: center !important;
    box-sizing: border-box !important;
    min-height: 17px !important;
    font-size: 10px;
    line-height: 1.1;
    padding: 2px 5px !important;
    border-radius: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 0 0 auto;
    width: max-content;
    min-width: 0;
    max-width: none;
}
.tsidebar__dlg-tags .crm-tag {
    margin-right: 0;
}
/* Голубо-бирюзовый фон для тега «бронь». */
.tsidebar__tag--booking {
    background: #ccfbf1;       /* teal-100 */
    color: #115e59;            /* teal-800 */
    border: 1px solid #5eead4; /* teal-300 */
    font-weight: 600;
}
/* PR #413: тег текущего узла графа на плашке диалога. */
.tsidebar__tag--node {
    background: #ede9fe;       /* violet-100 */
    color: #4c1d95;            /* violet-900 */
    border: 1px solid #c4b5fd; /* violet-300 */
    font-family: var(--font-mono, monospace);
    font-weight: 500;
    max-width: 100%;
    flex: 0 1 auto;
    width: auto;
}
/* PR #412 + #413: тег API-ошибки в плашке. */
.tsidebar__tag--api-err {
    background: #fee2e2;       /* red-100 */
    color: #7f1d1d;            /* red-900 */
    border: 1px solid #fca5a5; /* red-300 */
    font-weight: 700;
}
/* PR #413: время создания на плашке. */
.tsidebar__dlg-time {
    font-size: 10.5px;
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
    flex-shrink: 0;
    white-space: nowrap;
}

/* === Чат === */

.testing-chat {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--color-bg);
}

.tchat__empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-text-muted);
}

.tchat__header {
    padding: 5px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-card);
}

.tchat__title {
    font-weight: 600;
    font-size: 13px;
}

.tchat__sub {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-top: 2px;
}

.tchat__sub code {
    background: var(--color-bg);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
}

.tchat__escalated {
    color: var(--color-danger);
    font-weight: 500;
}

.tchat__thread {
    flex: 1;
    overflow-y: auto;
    padding: 5px;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.tchat__hint {
    color: var(--color-text-muted);
    font-size: 10px;
    font-style: italic;
    text-align: center;
    padding: 5px;
}

.tmsg {
    max-width: 80%;
    padding: 5px 5px;
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.25;
    /* PR-376q: white-space: pre-wrap БОЛЬШЕ НЕ ЗДЕСЬ.
       Раньше стоял на структурном контейнере → Jinja-индентация
       между {% endif %} и `·` (т.е. между «Бот» и временем)
       рендерилась как РЕАЛЬНЫЕ переносы строк. Bubble получался:
           Бот
           ·
           10:38:21
                          ← гигантская пустая зона
           текст реплики
       Сейчас pre-wrap только на .tmsg__text (где он реально нужен —
       сохранять переносы внутри клиентского/бот текста). Структурный
       .tmsg — white-space: normal (default). Точно тот же урок,
       что в trace UI: pre-wrap на структурный контейнер = catastrophe.
       См. concepts/trace-ui-css.md. */
    word-wrap: break-word;
}

.tmsg__meta {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-bottom: 1px;
    /* На случай если родитель когда-нибудь снова получит pre-wrap —
       meta остаётся однострочной. */
    white-space: nowrap;
}

/* PR P19.6: badge «от узла X» в meta-строке bot-сообщения sandbox чата.
   Visual соответствует .tflow__msg-from в трассе (тот же blue pill),
   чтобы юзер на ревью dialog 6 видел источник реплики в обоих местах. */
.tmsg__from {
    display: inline-block;
    font-size: 10.5px;
    padding: 0 5px;
    margin: 0 2px;
    background: #e0e7ff;
    color: #3730a3;
    border-radius: 3px;
    border: 1px solid #c7d2fe;
    font-weight: 500;
    vertical-align: baseline;
}
.tmsg__from code {
    background: transparent;
    color: inherit;
    font-weight: 600;
    font-size: 10.5px;
}

/* Текст реплики — сохраняет переносы строк (бот пишет «строка 1\nстрока 2»). */
.tmsg__text {
    white-space: pre-wrap;
    word-wrap: break-word;
}

.tmsg--client {
    /* Симметрично trace UI .tflow__msg--client — салатово-блёклый
       зелёный, чтобы реплики клиента визуально отличались от бот-
       пузырей (которые сиренево-голубые). */
    background: #e9f7e0;
    border: 1px solid #c5e6ab;
    align-self: flex-start;
}

.tmsg--bot {
    /* PR-376p: симметрично с .tflow__msg--bot в трассе — голубо-сиреневый
       вместо почти-белого --color-accent-bg. Визуально отличается
       от клиентского салатового. */
    background: #ede9fe;
    border: 1px solid #c4b5fd;
    align-self: flex-end;
}

.tmsg--system {
    background: var(--color-bg);
    border: 1px dashed var(--color-border);
    color: var(--color-text-muted);
    font-size: 10px;
    align-self: center;
}

/* PR-376pp: плашка-индикатор эскалации внутри чата. Рендерится
   когда dialog.status='escalated' — даже если bot reply нет
   (security_jump short-circuit'ом). Раньше клиент видел только
   свою реплику и пустоту. */
.tmsg--escalation {
    background: #fef2f2;
    border: 1px solid #fecaca;
    color: #991b1b;
    font-size: 12.5px;
    align-self: stretch;
    border-left: 4px solid var(--color-danger);
}
.tmsg--escalation .tmsg__meta {
    color: #991b1b;
    font-weight: 600;
}
/* #413: УСПЕШНАЯ эскалация (CRM-handoff) — салатовая плашка в чате вместо
   красной, зеркало верхнего trace-баннера (тот же сигнал
   trace.context.escalation_success). Палитра = in-node green-блок. */
.tmsg--escalation-success {
    background: #f0fdf4;
    border: 1px solid #bbf7d0;
    color: #166534;
    font-size: 12.5px;
    align-self: stretch;
    border-left: 4px solid #16a34a;
}
.tmsg--escalation-success .tmsg__meta {
    color: #166534;
    font-weight: 600;
}

/* P406: плашка аварийного завершения flow в чате — зеркало trace-баннера.
   Сильнее escalation (толстая левая кромка), тот же источник данных. */
.tmsg--failure {
    background: #fff1f2;
    border: 1px solid #fca5a5;
    color: #7f1d1d;
    font-size: 12.5px;
    align-self: stretch;
    border-left: 5px solid var(--color-danger);
}
.tmsg--failure .tmsg__meta {
    color: #991b1b;
    font-weight: 700;
}
.tmsg__failure-route {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin-top: 4px;
}
.tmsg__failure-trace {
    margin-top: 4px;
    font-size: 10px;
}
.tmsg__failure-trace > summary {
    cursor: pointer;
    color: #7f1d1d;
    font-weight: 500;
}
.tmsg__failure-trace pre {
    margin: 6px 0 0;
    padding: 5px 5px;
    background: #1f2937;
    color: #f3f4f6;
    border-radius: 3px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 320px;
    overflow: auto;
}

.tchat__form {
    border-top: 1px solid var(--color-border);
    padding: 5px;
    display: flex;
    gap: var(--space-sm);
    background: var(--color-card);
}

/* Двухполевая форма (system + client + кнопка) — колонкой; кнопка
   стоит справа от клиентской textarea, system-textarea — над всем
   через раскрывашку. */
.tchat__form--dual {
    flex-direction: column;
    align-items: stretch;
}
.tchat__form--dual .tchat__input,
.tchat__form--dual .tchat__send { width: 100%; }

.tchat__system-toggle {
    border: 1px dashed var(--color-border);
    border-radius: 3px;
    padding: 4px 5px;
    background: var(--color-bg);
}
.tchat__system-toggle > summary {
    cursor: pointer;
    font-size: 10px;
    color: var(--color-text-muted);
    user-select: none;
    padding: 2px 0;
}
.tchat__system-toggle[open] > summary { color: var(--color-accent); }
.tchat__input--system {
    margin-top: 6px;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    line-height: 1.4;
    background: var(--color-card);
}

.tchat__input {
    flex: 1;
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    resize: vertical;
    font-family: inherit;
    font-size: 10px;
}

.tchat__send {
    padding: 5px 5px;
    background: var(--color-accent);
    color: #fff;
    border: 0;
    border-radius: 3px;
    font-weight: 500;
    cursor: pointer;
}

.tchat__send:disabled {
    opacity: 0.5;
    cursor: progress;
}

/* Оптимистичные пузыри (отправлены клиентом, ещё не подтверждены сервером).
   Лёгкая прозрачность + плавное появление. */
.tmsg--pending {
    opacity: 0.65;
    animation: tmsgFadeIn 0.18s ease-out;
}
@keyframes tmsgFadeIn {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 0.65; transform: translateY(0); }
}

/* Сетевая или серверная ошибка — пузырь с красной рамкой, чтобы
   пользователь видел что отправка не дошла. */
.tmsg--failed {
    border: 1px solid var(--color-danger);
    background: #fff3f3;
}

/* Бот «думает» — анимированное троеточие. Появляется в треде на месте
   бот-реплики до прихода ответа от сервера. */
.tmsg--thinking {
    background: var(--color-accent-bg);
    align-self: flex-end;
    opacity: 0.85;
    animation: tmsgFadeIn 0.18s ease-out;
}
.tmsg__dots {
    display: inline-flex;
    gap: 4px;
    align-items: center;
    height: 14px;
}
.tmsg__dots > span {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--color-accent);
    animation: tmsgDot 1.2s ease-in-out infinite both;
}
.tmsg__dots > span:nth-child(1) { animation-delay: 0s; }
.tmsg__dots > span:nth-child(2) { animation-delay: 0.18s; }
.tmsg__dots > span:nth-child(3) { animation-delay: 0.36s; }
@keyframes tmsgDot {
    0%, 80%, 100% { transform: scale(0.55); opacity: 0.45; }
    40%           { transform: scale(1);    opacity: 1; }
}

/* PR-376hh: fade-in анимация для новых turn'ов/сообщений в poll-обновлении.
   JS вешает .tflow__turn--just-appeared / .tmsg--just-appeared при
   append'е нового элемента; CSS keyframes плавно проявляет.
   После animation-end класс снимается JS'ом. */
@keyframes ttrace-fade-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0);   }
}
.tflow__turn--just-appeared,
.tmsg--just-appeared {
    animation: ttrace-fade-in 0.35s ease-out;
}

/* === Trace === */

.testing-trace {
    background: var(--color-card);
    border-left: 1px solid var(--color-border);
    overflow-y: auto;
    /* PR-376kk: padding-top=0 чтоб sticky-шапка прибивалась к самому
       верху scroll-контейнера без «щели» (раньше при scroll'е
       сверху виднелась полоска padding'а контейнера до того как
       sticky подхватит). Внутри шапки сами компенсируем
       (padding-top: --space-md). */
    /* Левый gutter 16px (локально, = прежний --space-md) — даёт место синим
       кружкам-номерам визитов (.tflow__node-number, left:-19px) и закрепу
       шапки, чтобы они не уезжали за левую кромку панели. Кламп нормализации
       до 5px схлопывал этот gutter → шапка и кружки клипались. Правый/нижний 5px. */
    padding: 0 5px 5px 16px;
    font-size: 10px;
}

.ttrace__empty {
    padding: 5px;
    color: var(--color-text-muted);
}

.ttrace__title {
    font-weight: 600;
    font-size: 13px;
    margin: 0 0 var(--space-xs) 0;
}

/* Bot-status баннер сверху trace (parity c dp__bot-status в Метаданных,
   требование владельца 2026-06-22). Единая точка истины derive_bot_status_text
   — два view (trace + dialog-panel). */
.ttrace__bot-status {
    display: flex;
    gap: 6px;
    align-items: baseline;
    padding: 6px 8px;
    margin: 4px 0 var(--space-xs) 0;
    background: var(--color-bg-soft, #f7fafc);
    border-left: 3px solid var(--color-accent, #4a90e2);
    border-radius: 3px;
    font-size: 12px;
    line-height: 1.4;
}
.ttrace__bot-status-label {
    font-weight: 600;
    color: var(--color-text-muted);
    white-space: nowrap;
}
.ttrace__bot-status-text {
    color: var(--color-text);
}

.ttrace__hint {
    color: var(--color-text-muted);
    font-size: 10px;
    margin: 0;
    line-height: 1.5;
}

.ttrace__header {
    margin-bottom: var(--space-md);
    padding-bottom: 5px;
    border-bottom: 1px solid var(--color-border);
    /* PR-376ff: pin в закреп. .testing-trace — scroll-контейнер
       (overflow-y:auto), sticky прибивает шапку к его верху.
       Отрицательные margin'ы + соответствующие padding'и
       расширяют шапку до краёв панели, чтобы при scroll'е под
       ней ничего не «протекало» сбоку (там padding контейнера).
       PR-376hh: усиление поверх стейджей — z-index:50 (любые
       inline-styled с position:relative проиграют), isolation:
       isolate создаёт изолированный stacking context (не
       пересекается с transformami в дочерних), box-shadow
       визуально отделяет шапку от контента под ней даже когда
       background:var(--color-card) совпадает с .tflow__stage. */
    position: sticky;
    top: 0;
    z-index: 50;
    isolation: isolate;
    /* Непрозрачный фон закреп-шапки = единый цвет панелей (--color-card),
       чтобы шапка совпадала с фоном .testing-trace и перекрывала
       прокручиваемый контент без «протекания» сбоку. */
    background: var(--color-card);
    /* Закреп-шапка делает full-bleed до кромок панели: отрицательный
       margin ДОЛЖЕН совпадать с padding'ом .testing-trace (лево 12px,
       право 5px), иначе шапка вылазит за границу и клипается. После
       нормализации padding'и шапки клампнулись до 5px, а margin остался
       -space-md(16px) → перелёт ~11px влево/вправо. Ребалансируем локально. */
    margin-left: -16px;
    margin-right: -5px;
    padding-left: 16px;
    padding-right: 5px;
    /* PR-376kk: padding-top компенсирует убранный padding-top
       у .testing-trace — шапка занимает место «щели» сверху
       и при scroll'е прижимается к самому верху без зазора. */
    padding-top: 5px;
    box-shadow: 0 4px 8px -4px rgba(0, 0, 0, 0.12);
}

.ttrace__total {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-top: 4px;
}

.ttrace__total strong {
    color: var(--color-text);
}

.ttrace__section {
    margin-bottom: var(--space-md);
}

.ttrace__section > summary {
    cursor: pointer;
    padding: var(--space-xs) 0;
    font-weight: 500;
}

.ttrace__llm,
.ttrace__tool {
    margin: var(--space-xs) 0;
    padding: 5px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
}

.ttrace__tool--err {
    border-color: var(--color-danger);
}

.ttrace__llm-head,
.ttrace__tool-head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    align-items: center;
    margin-bottom: 4px;
}

.ttrace__llm-head code,
.ttrace__tool-head code {
    background: var(--color-card);
    padding: 2px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-family: var(--font-mono, monospace);
}

.ttrace__pill {
    background: var(--color-accent-bg);
    color: var(--color-accent);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
    text-transform: uppercase;
}

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

.ttrace__lat {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-left: auto;
}

.ttrace__err-badge {
    background: var(--color-danger);
    color: #fff;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
}

.ttrace__llm-meta {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-bottom: 6px;
}

.ttrace__llm-meta strong {
    color: var(--color-text);
}

.ttrace__sub {
    margin-top: 6px;
    margin-bottom: 2px;
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.ttrace__pre {
    margin: 0;
    /* PR-376n: tighter padding 6px → 3px — основная причина
       раздутости pre-блоков в трейсе (12px → 6px overhead на pre). */
    padding: 3px 5px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-size: 10px;
    font-family: var(--font-mono, monospace);
    white-space: pre-wrap;
    word-wrap: break-word;
    max-height: 320px;
    overflow-y: auto;
    line-height: 1.3;
}

.ttrace__pre--err {
    color: var(--color-danger);
}

.ttrace__nested {
    margin-top: 6px;
}

.ttrace__nested > summary {
    cursor: pointer;
    font-size: 10px;
    color: var(--color-accent);
    padding: 2px 0;
}

.ttrace__nested > summary:hover { color: var(--color-text); }

.ttrace__sub--err {
    color: var(--color-danger);
    font-weight: 600;
}

.ttrace__sub--muted summary {
    color: var(--color-text-muted) !important;
}

/* === Фаза LLM-вызова — карточка с акцентной полосой слева ============ */
.ttrace__phase {
    margin: var(--space-sm) 0 var(--space-md);
    padding: 5px 5px 5px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-left: 3px solid var(--color-accent);
    border-radius: var(--radius-md, 3px);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}

.ttrace__phase-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    font-size: 10px;
    margin-bottom: 4px;
}

.ttrace__phase-num {
    display: inline-block;
    min-width: 24px;
    padding: 1px 5px;
    background: var(--color-accent);
    color: #fff;
    border-radius: 3px;
    text-align: center;
    font-size: 10px;
    font-weight: 700;
}

.ttrace__phase-title {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.ttrace__phase-model {
    background: var(--color-bg);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-family: var(--font-mono, monospace);
    font-weight: 500;
}

.ttrace__phase-meta {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-bottom: 6px;
}

.ttrace__phase-meta strong { color: var(--color-text); }

/* === Список инжектированных правил =================================== */
.ttrace__rules {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.ttrace__rule {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-left: 2px solid var(--color-warning, #f0b429);
    border-radius: 3px;
    padding: 5px 5px;
}

.ttrace__rule-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    margin-bottom: 4px;
}

.ttrace__rule-name {
    font-weight: 600;
    color: var(--color-text);
}

.ttrace__rule-id {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-left: auto;
    font-family: var(--font-mono, monospace);
}

.ttrace__pre--rule {
    background: var(--color-card);
    font-size: 10px;
    max-height: 220px;
}

.ttrace__pre--prompt {
    background: #fafbfc;
    max-height: 380px;
}

.ttrace__pre--reply {
    background: var(--color-accent-bg);
    color: var(--color-text);
    max-height: 380px;
}

/* === Список переданных сообщений ===================================== */
.ttrace__msgs {
    list-style: none;
    counter-reset: msg-counter;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ttrace__msg {
    counter-increment: msg-counter;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    /* PR-376n: tighter — было 6px вертикали (12px overhead),
       стало 3px (6px overhead). Trace UI вообще должен быть
       плотным, у нас тут не основной контент. */
    padding: 3px 5px;
    /* Flex-column чтобы счётчик «1. » не отрывался от заголовка
       роли, и контент шёл сразу под ним без block-margin'ов. */
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.ttrace__msg--user    { border-left: 2px solid #4a90e2; }
.ttrace__msg--assistant { border-left: 2px solid #7c5fb8; }
.ttrace__msg--system  { border-left: 2px solid #888; }

.ttrace__msg-role {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    /* margin-bottom УБРАН — gap parent-flex'а заменяет. */
    margin-bottom: 0;
    line-height: 1.2;
}
.ttrace__msg-role::before { content: counter(msg-counter) ". "; }

.ttrace__pre--msg {
    background: var(--color-card);
    font-size: 10px;
    max-height: 220px;
}

/* ========================================================================
 *  Trace flowchart — интерактивная блок-схема turn'а на правой панели
 *  вкладки «Тестирование». Сверху вниз: client → state → правила+промт
 *  → LLM → AI-ответ. Вертикальная «магистраль» — ::before у .ttrace__flow.
 * ====================================================================== */

.ttrace__flow {
    position: relative;
    padding: 5px 0;
    isolation: isolate; /* свой stacking context, чтобы ::before-линия гарантированно была под блоками */
}
/* Магистраль — тонкая вертикальная линия по центру всего потока.
   z-index:-1 ставит её ПОД всеми дочерними блоками (у них z-index:auto
   создаёт уровень >= 0 благодаря position:relative). isolation:isolate
   у родителя ограничивает -1 этим контейнером — линия не уезжает
   под body. */
.ttrace__flow::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--color-accent);
    opacity: 0.35;
    transform: translateX(-50%);
    z-index: -1;
}

/* Карточка-узел сообщения. Solid background перекрывает магистраль.
   Стилизуется по kind: client / bot / operator / system. */
.tflow__node {
    position: relative;
    background: var(--color-card);
    border: 2px solid var(--color-accent);
    border-radius: var(--radius-md, 3px);
    padding: 5px 5px;
    /* PR-376ll: убрана max-width:92% и auto-центровка — карточка
       занимает ту же ширину что и .tflow__stage / .tflow__msg
       (полная ширина scroll-контейнера за вычетом padding'а
       парента). Раньше выглядела зауженной по сравнению со
       стейджами, нарушала визуальный ритм трассы. */
    margin: 0 0 var(--space-md);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.tflow__node--client {
    background: var(--color-card);
    border-color: #f59e0b;       /* совпадает с цветом короны */
}
.tflow__node--bot {
    background: var(--color-accent-bg);
    border-color: var(--color-accent);
}
.tflow__node--operator {
    background: var(--color-card);
    border-color: #6b8acc;
}
.tflow__node--system {
    background: var(--color-bg);
    border: 1px dashed var(--color-text-muted);
    opacity: 0.85;
    max-width: 80%;
}
.tflow__node--target {
    box-shadow: 0 0 0 2px var(--color-accent), 0 1px 6px rgba(0, 0, 0, 0.1);
}

.tflow__node-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--color-card);
    border: 2px solid currentColor;
    color: var(--color-text);
}
.tflow__node-icon svg { width: 18px; height: 18px; }
.tflow__node-icon--client { color: #f59e0b; }
.tflow__node-icon--bot { color: var(--color-accent); }
.tflow__node-icon--operator { color: #6b8acc; }
.tflow__node-icon--system {
    border: none;
    width: auto;
    height: auto;
    background: transparent;
    font-size: 10px;
}

.tflow__node-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
    font-size: 10px;
    color: var(--color-text-muted);
}

.tflow__icon {
    font-size: 10px;
    line-height: 1;
}

.tflow__node-title {
    font-weight: 600;
    color: var(--color-text);
    font-size: 13px;
}

.tflow__time {
    margin-left: auto;
    font-size: 10px;
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
}

.tflow__node-body {
    font-size: 10px;
    color: var(--color-text);
    line-height: 1.45;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Узел-«хаб» на магистрали: круглая иконка с белой обводкой, поверх линии. */
.tflow__hub {
    position: relative;
    z-index: 2;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    margin: 0 auto var(--space-sm);
}

.tflow__hub--state > .tflow__hub-btn {
    list-style: none;
    cursor: pointer;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 2px solid var(--color-accent);
    background: var(--color-card);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 700;
    color: var(--color-accent);
    user-select: none;
}
.tflow__hub--state > .tflow__hub-btn::-webkit-details-marker { display: none; }
.tflow__hub--state[open] > .tflow__hub-btn {
    background: var(--color-accent);
    color: #fff;
}
.tflow__hub-popover {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md, 3px);
    padding: 5px;
    margin-top: 4px;
    width: 90%;
    max-width: 100%;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.tflow__hub-popover-title {
    font-size: 13px;
    color: var(--color-text-muted);
    margin-bottom: 4px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.tflow__hub--llm {
    flex-direction: column;
    margin-top: var(--space-xs);
    margin-bottom: var(--space-md);
}
.tflow__hub-glyph {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-card);
    border: 2px solid var(--color-accent);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
.tflow__hub-label {
    font-size: 10px;
    color: var(--color-text-muted);
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 2px 5px;
}
.tflow__hub-label strong { color: var(--color-text); }

/* Стрелка-указатель от карточек правил/промта к LLM-хабу. На магистрали,
   visually связывает «что подаём» → «куда подаём». */
.tflow__arrow {
    position: relative;
    text-align: center;
    color: var(--color-accent);
    font-size: 10px;
    line-height: 1;
    margin: 4px 0 -2px;
    user-select: none;
}

/* Фаза LLM-вызова: пара карточек (правила | промт) + LLM-хаб под ними. */
.tflow__phase {
    position: relative;
    z-index: 1;
    margin-bottom: var(--space-md);
}

.tflow__phase-cards {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-sm);
    margin-bottom: var(--space-xs);
}
@media (max-width: 720px) {
    .tflow__phase-cards { grid-template-columns: 1fr; }
}

.tflow__card {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md, 3px);
    overflow: hidden;
}
.tflow__card--rules { border-left: 3px solid var(--color-warning, #f0b429); }
.tflow__card--prompt { border-left: 3px solid var(--color-accent); }

.tflow__card-summary {
    cursor: pointer;
    padding: 5px 5px;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text);
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    gap: 6px;
}
.tflow__card[open] > .tflow__card-summary { border-bottom: 1px solid var(--color-border); }
.tflow__card:not([open]) > .tflow__card-summary { border-bottom: 0; }

.tflow__card-toggles {
    margin-left: auto;
    display: inline-flex;
    gap: 4px;
}
.tflow__toggle {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 1px 5px;
    font-size: 10px;
    color: var(--color-text-muted);
    cursor: pointer;
}
.tflow__toggle:hover { color: var(--color-text); border-color: var(--color-accent); }

.tflow__card-body {
    padding: 5px 5px;
}

/* P159b: legacy `.tflow__rule*` widget удалён — заменён на
   `.trules-widget`/`.trules-card` (см. ниже). Все правила теперь
   рендерятся через `_macros_trace.html::render_rules_widget`. */

/* Phase 4: фазовые баннеры разных типов. Базовый — серый; варианты —
   фиолетовый llm_switch, бирюзовый rules_check, оранжевый rules_jump. */
.tflow__phase-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 5px;
    margin-bottom: 6px;
    font-size: 10px;
    color: var(--color-text-muted);
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
}
.tflow__phase-banner .tflow__phase-num { font-weight: 600; }
.tflow__phase-banner .tflow__phase-finish { margin-left: auto; }
.tflow__phase-banner--intermediate { border-left: 3px solid var(--color-warning, #f0b429); }
.tflow__phase-banner--llm-switch {
    background: #f3eaff;
    border-color: #b08bff;
    color: #5a32b8;
}
.tflow__phase-banner--rules-check {
    background: #e6f7f4;
    border-color: #6cb7ab;
    color: #1d6d5d;
}
.tflow__phase-banner--rules-jump {
    background: #fff1e0;
    border-color: #d99a59;
    color: #8b4d10;
}

/* P159b: legacy `.tflow__rules-group*` / `.tflow__rule--not-taken` /
   `.tflow__rule-row--reason` / `.tflow__rule-action-kind` удалены —
   правила теперь рендерятся через `.trules-widget` / `.trules-card`
   / `.trules-skip` (см. ниже). */

/* ====================================================================
   P159: structured rules widget внутри render_llm_call
   (rules_check / rules_jump / security_check).

   Заменяет raw `<pre>{{ response_text }}</pre>` на:
   - Header с заголовком и counters (✅ N · ⊘ M · ⚠ K).
   - Секцию «Применённые правила» — раскрываемые плашки с
     ЕСЛИ (condition_prompt) / ТО (action_prompt | jump target) /
     Reason от LLM.
   - Секцию «В конфликте» — applied-style cards с обводкой.
   - Секцию «Не применено» — компактный список (id name action_kind reason).
   - Свёрнутый raw JSON (через pretty-JSON renderer).
   ==================================================================== */
.trules-widget {
    margin: 8px 0;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 3px;
    background: #fafafa;
    overflow: hidden;
}
.trules-widget--security {
    border-color: #fca5a5;
    background: #fef7f7;
}
.trules-widget--inject {
    border-color: #c7d2fe;
    background: #f5f7ff;
}
.trules-widget--jump {
    border-color: #fcd34d;
    background: #fffaf0;
}
/* ADR-0018: блёклая плашка gated/пустого rules-прохода (состояние Action'а
   skipped/empty) — «пропущено (facts diff=0)» / «без сработавших правил».
   Не полноценный виджет, а тонкая метка ПО СОСТОЯНИЮ, единообразно. */
.trules-widget--skipped {
    border-color: var(--color-border, #e5e7eb);
    background: #fafafa;
    opacity: 0.65;
}
.trules-widget__skip-label {
    display: block;
    padding: 5px 5px;
    font-size: 10px;
    font-style: italic;
}
/* ADR-0018: 🛡-бейдж security-природы (jump top-priority, rule #0) ВНУТРИ
   jump-виджета — security НЕ отдельный тип виджета, а данные правила. */
.trules-widget__sec-badge {
    margin-left: 4px;
    font-size: 10px;
    cursor: help;
}

.trules-widget__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 5px 5px;
    background: rgba(0, 0, 0, 0.03);
    border-bottom: 1px solid var(--color-border, #e5e7eb);
}
.trules-widget__title {
    font-weight: 600;
    color: var(--color-text, #1f2937);
    font-size: 13px;
}
.trules-widget__counters {
    display: inline-flex;
    gap: 8px;
    font-size: 10px;
}
.trules-widget__counter {
    font-family: var(--font-mono, monospace);
    padding: 1px 5px;
    border-radius: 3px;
    background: #fff;
    border: 1px solid var(--color-border, #e5e7eb);
}
.trules-widget__counter--applied { color: #047857; border-color: #a7f3d0; background: #ecfdf5; }
.trules-widget__counter--skipped { color: #6b7280; }
.trules-widget__counter--conflict { color: #b87a07; border-color: #fde68a; background: #fffbeb; }

.trules-widget__section {
    padding: 5px 5px;
    border-bottom: 1px solid #f3f4f6;
}
.trules-widget__section:last-child { border-bottom: none; }
.trules-widget__section-title {
    font-weight: 600;
    color: #374151;
    font-size: 13px;
    margin-bottom: 4px;
    cursor: pointer;
    list-style: none;
}
.trules-widget__section-title::-webkit-details-marker { display: none; }
.trules-widget__section-title::before {
    content: '▸';
    display: inline-block;
    margin-right: 4px;
    transition: transform 0.1s;
}
details.trules-widget__section[open] > .trules-widget__section-title::before {
    transform: rotate(90deg);
}
/* Для applied (всегда open, без details) — без disclosure-стрелки. */
.trules-widget__section--applied > .trules-widget__section-title {
    cursor: default;
}
.trules-widget__section--applied > .trules-widget__section-title::before {
    display: none;
}

.trules-widget__cards {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 4px;
}

/* --- Подгруппы применённых inject-правил: «LLM-выбор» (condition_kind=prompt)
   и «Детерминированный scope» (condition_kind=always). Визуально слабее
   главного «Применённые правила (N)» — это подзаголовки. --- */
.trules-widget__subgroup {
    margin-top: 6px;
}
.trules-widget__subgroup + .trules-widget__subgroup {
    margin-top: 8px;
    padding-top: 5px;
    border-top: 1px dashed #e5e7eb;
}
.trules-widget__subgroup-title {
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: #6b7280;
    margin-bottom: 2px;
}
.trules-widget__subgroup--llm > .trules-widget__subgroup-title { color: var(--color-busy-text); }
.trules-widget__subgroup--scope > .trules-widget__subgroup-title { color: #0369a1; }

/* --- Одна плашка применённого правила (открывается details'ом). --- */
.trules-card {
    border: 1px solid #d1d5db;
    border-left: 3px solid #047857;
    border-radius: 3px;
    background: #fff;
    padding: 0;
}
.trules-card[data-conflict="1"] {
    border-left-color: #b87a07;
    background: #fffbeb;
}
.trules-card__head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 5px 5px;
    list-style: none;
    cursor: pointer;
    font-size: 10px;
}
.trules-card__head::-webkit-details-marker { display: none; }
.trules-card__check {
    color: #047857;
    font-weight: 700;
}
.trules-card[data-conflict="1"] .trules-card__check {
    color: #b87a07;
}
.trules-card__name {
    font-weight: 600;
    color: var(--color-text, #1f2937);
    flex-grow: 1;
    min-width: 0;
}
.trules-card__id {
    font-family: var(--font-mono, monospace);
    color: #6b7280;
    font-size: 10px;
    background: #f3f4f6;
    padding: 0 5px;
    border-radius: 3px;
}
.trules-card__scope,
.trules-card__prio {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6b7280;
    padding: 0 5px;
    background: #f3f4f6;
    border-radius: 3px;
}
.trules-card__action-kind {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border: 1px solid var(--color-border, #e5e7eb);
    border-radius: 3px;
    padding: 0 5px;
    background: #fff;
    color: var(--color-text, #1f2937);
}
.trules-card__action-kind--jump_to_node {
    color: #b91c1c;
    background: #fef2f2;
    border-color: #fecaca;
}
.trules-card__action-kind--inject {
    color: #1e3a8a;
    background: #eff6ff;
    border-color: #bfdbfe;
}
.trules-card__conflict-flag {
    font-size: 10px;
    color: #b87a07;
    background: #fffbeb;
    border: 1px solid #fde68a;
    border-radius: 3px;
    padding: 0 5px;
}
.trules-card__body {
    padding: 5px 5px 5px;
    border-top: 1px dashed #e5e7eb;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.trules-card__section {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.trules-card__label {
    font-weight: 600;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #6b7280;
}
.trules-card__section--cond .trules-card__label { color: #b87a07; }
.trules-card__section--act .trules-card__label { color: var(--color-accent, #2563eb); }
.trules-card__section--reason .trules-card__label { color: #047857; }
.trules-card__text {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    line-height: 1.4;
    background: #fafafa;
    border: 1px solid #f1f5f9;
    border-radius: 3px;
    padding: 5px 5px;
    margin: 0;
    white-space: pre-wrap;
    word-wrap: break-word;
    max-height: 200px;
    overflow: auto;
    color: var(--color-text, #1f2937);
}
.trules-card__reason-text {
    /* Reason — обычный текст от LLM, не код, font иначе. */
    font-family: inherit;
    background: #ecfdf5;
    border-color: #a7f3d0;
}
.trules-card[data-conflict="1"] .trules-card__reason-text {
    background: #fffbeb;
    border-color: #fde68a;
}
.trules-card__jump-target {
    font-size: 10px;
    padding: 4px 0;
}
.trules-card__jump-target code {
    font-family: var(--font-mono, monospace);
    background: #fef2f2;
    border: 1px solid #fecaca;
    border-radius: 3px;
    padding: 1px 5px;
    color: #b91c1c;
    font-weight: 600;
}

/* --- Список НЕ применённых — компактные строки. --- */
.trules-widget__skipped-list {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.trules-skip {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    padding: 3px 5px;
    background: #fff;
    border: 1px solid #f1f5f9;
    border-radius: 3px;
    flex-wrap: wrap;
}
.trules-skip__icon { color: #94a3b8; }
.trules-skip__id {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: #6b7280;
    background: #f3f4f6;
    padding: 0 5px;
    border-radius: 3px;
}
.trules-skip__name {
    color: #374151;
    flex-shrink: 0;
}
.trules-skip__ak {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: #6b7280;
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 3px;
    padding: 0 5px;
}
.trules-skip__reason {
    color: #6b7280;
    font-size: 10px;
    min-width: 0;
    flex-grow: 1;
}

/* --- Свёрнутый блок «Сырой ответ LLM» под виджетом. --- */
.trules-widget__raw {
    margin-top: 6px;
    border: 1px dashed #e5e7eb;
    border-radius: 3px;
    background: #fafafa;
}
.trules-widget__raw > summary {
    padding: 4px 5px;
    cursor: pointer;
    font-size: 10px;
    list-style: none;
}
.trules-widget__raw > summary::-webkit-details-marker { display: none; }
.trules-widget__raw > summary::before {
    content: '▸';
    display: inline-block;
    margin-right: 4px;
    transition: transform 0.1s;
}
.trules-widget__raw[open] > summary::before { transform: rotate(90deg); }
.trules-widget__raw[open] > pre,
.trules-widget__raw[open] > div,
.trules-widget__raw[open] > .ttrace__pre--pretty-json {
    margin: 0 8px 6px;
}

/* --- Pretty JSON pre (используется render_pretty_json + render_pretty_json_string).
   JS на странице ниже подсветит "key": value пары через existing
   .fact-set / .fact-null классы. Этот класс — лишь триггер для JS,
   плюс лёгкие visual tweaks. --- */
.ttrace__pre--pretty-json {
    background: #1f2937;
    color: #e5e7eb;
    border-radius: 3px;
    padding: 5px 5px;
    font-size: 11.5px;
    line-height: 1.45;
    white-space: pre-wrap;
    word-wrap: break-word;
    max-height: 320px;
    overflow: auto;
}
.ttrace__pre--pretty-json .fact-set {
    /* В тёмном pre обычный синий слишком тёмный — берём светлее. */
    color: #93c5fd;
    font-weight: 600;
}
.ttrace__pre--pretty-json .fact-null {
    color: #9ca3af;
    font-style: italic;
}

/* ====================================================================
   Phase 4 / трассировка по узлам графа
   ==================================================================== */

/* Глобальный reset для всех <details>/<summary>/<pre>/<ul>/<ol> внутри
   trace — обнуляем browser-defaults (margin, padding) которые иначе
   накапливают вертикальный воздух между раскрывашками. Конкретные
   правила добавляют только нужные паддинги. */
.tctx [class^="tflow__"] details,
.tctx [class^="tflow__"] summary,
.tctx [class^="tflow__"] ul,
.tctx [class^="tflow__"] ol,
.tctx [class^="tflow__"] li,
.tctx [class^="tflow__"] p,
.tctx [class^="tflow__"] pre,
[class^="tflow__"] details,
[class^="tflow__"] summary,
[class^="tflow__"] ul,
[class^="tflow__"] ol,
[class^="tflow__"] li,
[class^="tflow__"] p,
[class^="tflow__"] pre {
    margin: 0;
    padding: 0;
}
/* details/summary должны быть «компактными по высоте»: убираем
   browser default outline + selection stroke, чтобы не добавляли
   2px к высоте. */
[class^="tflow__"] summary {
    outline: none;
}
/* Pre с кодом промта: сохраняем preformatted, добавляем минимальный
   паддинг внутри — для читаемости. */
[class^="tflow__"] pre.ttrace__pre {
    margin: 2px 0;
}

/* Whitespace-фикс для <details>-обёрток узлов и секций.

   Браузеры рендерят whitespace text-nodes между block-children внутри
   <details> как inline-line высотой line-height родителя — т.е. между
   <summary> и следующим <pre> в шаблоне Jinja остаётся ~57px пустого
   места из-за переносов строк/отступов в исходнике.

   ──────────────────────────────────────────────────────────────────
   КОНЦЕПТУАЛЬНОЕ РЕШЕНИЕ (PR-195+):
   Применяем `display: flex; flex-direction: column; gap: Npx` к ВСЕМ
   trace-контейнерам. Flex-модель ИГНОРИРУЕТ whitespace-only text-nodes
   между siblings'ами (они становятся 0-height anonymous flex items).
   Это убирает накопление вертикали из шаблонных пробелов БЕЗ хаков
   line-height:0/font-size:0, которые ломали наследование font-size'а
   глубже в дереве (например, эмодзи-иконки в .tflow__prompt-section
   ехали на свою строку из-за наследования line-height:0).

   Браузерная поддержка: <details> с `display: flex` корректно работает
   в Chrome/Firefox/Safari (CSS3). <summary> остаётся list-item — его
   мы flex-уем индивидуально в местах где нужна горизонтальная компоновка.

   Внутри <details>:
   - первый child <summary> ведёт себя как flex-item (заголовок)
   - последующие дети — содержимое, скрыты браузером пока не [open]
   - whitespace между ними — 0-height (главное достижение этого решения)
   ──────────────────────────────────────────────────────────────────
*/
.tflow__node,
.tflow__node-section,
.tflow__node-body,
.tflow__node-section-body,
.tflow__llm-call,
.tflow__llm-call-body,
.tflow__tool-call,
.tflow__nested,
.tflow__switch-body,
/* PR-376j: расширение flex-column модели на ещё несколько контейнеров.
   Юзер показал «воздух» вокруг details-блоков (Вопрос гейта /
   Возможные ветки / Вызов LLM / system_prompt). Эти все рендерятся
   через .tflow__stage-section (внутри stage-body). Без flex-column
   между <summary> и контентом есть whitespace text-nodes с
   line-height — отсюда вертикальные gap'ы. С flex-column whitespace
   становится 0-height, а реальный отступ задаётся gap: 2px. */
.tflow__stage-section,
.tflow__stage-body,
.tflow__stage,
.tflow__stage-llms {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* Structural wrappers must collapse template whitespace. Content blocks
       that need preserved line breaks set white-space explicitly below. */
    white-space: normal;
    /* PR-376n: line-height 1.4 → 1.25 (12px font × 1.25 = 15px line).
       Раньше 1.4 давал 16.8px lines — на 4-5 строчном <pre>-блоке
       это +7-8px пустого «воздуха» по сравнению с компактным 1.25.
       Trace UI — служебная инфа, не основной контент: плотный режим
       лучше. */
    line-height: 1.25;
    font-size: 10px;
}

/* PR-4: extract stage детализация — детерминированные шаги, cache-badge. */
.tflow__stage-cache-badge {
    display: inline-block;
    padding: 2px 5px;
    margin: 2px 0 4px;
    border-radius: 3px;
    background: #fef3c7;
    border: 1px solid #fde68a;
    color: #92400e;
    font-size: 10px;
    font-weight: 600;
}
.tflow__det-steps { display: flex; flex-direction: column; gap: 2px; }
.tflow__det-step > summary {
    cursor: pointer;
    padding: 2px 4px;
    font-size: 10px;
    border-radius: 3px;
}
.tflow__det-step > summary:hover { background: #fffbeb; }
.tflow__det-step > summary code {
    background: #fef3c7;
    color: #92400e;
    padding: 0 4px;
    border-radius: 2px;
    font-weight: 600;
}

/* PR-3: реплика клиенту — выделенный блок в reply-stage'е.
   PR-376p: цвет был зелёный (#f0fdf4 / #16a34a) — это «клиентская»
   палитра, путало (это РЕПЛИКА БОТА в чат, не клиента).
   Сейчас голубо-сиреневый — симметрично .tflow__msg--bot и
   .tmsg--bot. */
.tflow__reply-text {
    display: flex;
    gap: 6px;
    padding: 5px 5px;
    margin: 4px 0;
    border-left: 3px solid #8b5cf6;
    background: #ede9fe;
    color: #4c1d95;
    font-size: 12.5px;
    line-height: 1.3;
    border-radius: 3px;
    white-space: pre-wrap;
    word-break: break-word;
}
.tflow__reply-icon { flex: 0 0 auto; }
.tflow__reply-body { flex: 1 1 auto; }

/* PR-2: микро-этапы (stages) trace UI — компактные раскрывашки.
   Flex-column концепт (см. большой комментарий выше): whitespace
   между jinja-блоками не накапливает вертикаль. */
.tflow__stage,
.tflow__stage-body,
.tflow__stage-section,
.tflow__stage-rules,
.tflow__stage-llms,
.tflow__det-steps,
.tflow__det-step,
.tflow__det-step-out {
    display: flex;
    flex-direction: column;
    gap: 2px;
    line-height: 1.4;
    font-size: 10px;
}
/* .tflow__stage-head и .tflow__stage-tools-pills — горизонтальные
   контейнеры (иконка + title + stats / список pills). Их flex-row
   определён ниже у каждого индивидуально. */
.tflow__stage {
    margin: 2px 0;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-card);
    overflow: hidden;
}
.tflow__stage-head {
    /* PR-376ll: head — block-контейнер. Внутри две row'и:
       .tflow__stage-head-main (flex-row с icon/title/badges/stats)
       и .tflow__stage-tool-pills (block, отдельная строка).
       Раньше head сам был flex-row с flex-wrap:wrap и order:99 на
       pills — это уводило stats не туда при пустом title. Структурно
       теперь чище: каждая row знает свою роль. */
    display: block;
    padding: 1px 5px;
    cursor: pointer;
    user-select: none;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.3;
    background: linear-gradient(0deg, rgba(0,0,0,.015), transparent);
    white-space: nowrap;
}
.tflow__stage-head-main {
    display: flex;
    align-items: center;
    gap: 6px;
}
.tflow__stage-head:hover { background: rgba(37, 99, 235, 0.05); }
.tflow__stage-icon { font-size: 13px; flex: 0 0 auto; }
.tflow__stage-title {
    flex: 1 1 auto;
    /* P177: явный min-width:0 + white-space:nowrap — спец-корректная форма
       flex-ellipsis идиомы. Длинный заголовок (security-стадия в узкой
       вложенной .tflow__lifecycle-phase-stages) должен обрезаться много-
       точием, не распирая ряд. Chrome и так резолвит min-width:auto→0 при
       overflow:hidden, поэтому здесь это защита от движков/старых WebKit,
       где auto НЕ резолвится в 0 (тогда flex-ребёнок не сжимается ниже
       min-content и ряд распирает). nowrap дублирует наследуемый от
       .tflow__stage-head — делает правило самодостаточным. Полный текст —
       в title= span'а (tooltip при hover), чтобы инфо не терялась. */
    min-width: 0;
    white-space: nowrap;
    color: var(--color-text);
    overflow: hidden;
    text-overflow: ellipsis;
}
.tflow__stage-jump {
    /* P178: flex:0 1 auto (вместо 0 0 auto) + min-width:0 + overflow:hidden —
       бейдж может сжиматься при нехватке ширины. Сжимается ТОЛЬКО имя узла
       (.tflow__stage-jump code ниже), сам лейбл RULES_JUMP и стрелка держат
       натуральную ширину. В широком контексте shrink не активируется (есть
       свободное место), поэтому вид не меняется. */
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    padding: 0 5px;
    border-radius: 3px;
    background: #fef2f2;
    color: #991b1b;
    font-size: 10.5px;
    font-weight: 700;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.tflow__stage-jump code {
    background: transparent;
    color: inherit;
    /* Имя узла — единственный сжимаемый элемент бейджа: ellipsis при
       нехватке ширины, полное имя — в title= (tooltip). Имя важно, поэтому
       обрезаем его только в последнюю очередь и не теряем через tooltip. */
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* P178: в узком вложенном контексте (.tflow__lifecycle-phase-stages, ~340px)
   ряд head-main не вмещает rules-summary + jump-бейдж + stats одновременно.
   Имя узла в jump'е критично — обрезать его до 2-3 символов недопустимо.
   Решение (по образцу .tflow__stage-security-rules-row): jump уходит на
   отдельную 2-ю строку через order:1 (после stats) + flex-basis 100%. Полоса
   прозрачная (цвет несёт сам RULES_JUMP-бейдж), отступ слева выравнивает к
   началу текста title. Имя узла получает почти всю ширину ряда; ellipsis+
   tooltip (правило выше) — лишь страховка для очень длинных имён.

   :has(.tflow__stage-jump) — flex-wrap включаем ТОЛЬКО на head-main со
   jump'ом, чтобы не плодить лишний перенос stats у стадий без jump'а
   (security/extract и т.п. остаются однострочными как раньше). flex-basis:0
   на title внутри такого head-main гарантирует, что stats остаётся в row 1
   (title не «раздувает» строку своим content-size и не выталкивает stats
   на отдельную строку) — итог ровно 2 строки: [icon title rules stats] / [jump].
   Scoped к узкому контейнеру: в широком контексте jump остаётся inline в row 1. */
.tflow__lifecycle-phase-stages .tflow__stage-head-main:has(.tflow__stage-jump) {
    flex-wrap: wrap;
}
.tflow__lifecycle-phase-stages .tflow__stage-head-main:has(.tflow__stage-jump) .tflow__stage-title {
    flex-basis: 0;
}
.tflow__lifecycle-phase-stages .tflow__stage-jump {
    order: 1;                 /* после stats (order:0) → перенос на 2-ю строку */
    flex: 0 0 100%;           /* занимает всю ширину новой строки */
    background: transparent;  /* не красим full-width полосу — цвет на бейдже */
    padding: 2px 0 0 5px;    /* выравнивание к началу текста title (6+13+6) */
}

/* PR P16.6: badge «RULES_JUMP #id» — лейбл рядом со стрелкой,
   чтоб юзер видел: «прыжок не engine-default, а сработавшее правило». */
.tflow__stage-jump-badge {
    flex: 0 0 auto;          /* P178: лейбл RULES_JUMP #id не сжимается — сжимается имя узла */
    background: #991b1b;
    color: #fff;
    font-size: 9.5px;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 3px;
    letter-spacing: 0.3px;
    text-transform: none;
}
.tflow__stage-stats {
    flex: 0 0 auto;
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 500;
    white-space: nowrap;
    /* PR-376kk: margin-left:auto толкает stats в правый край row 1
       вне зависимости от того сколько других flex-children впереди
       и какой у них flex-grow. Раньше при пустом title (или с
       flex-grow:1 отдавшим всё пиллам через order:99) stats липло
       к иконке слева — теперь всегда справа. */
    margin-left: auto;
}

/* Inline-счётчики правил в head'е rules-stage'а: ✅ N · ⚠️ M · ⊘ K.
   Юзер видит итог проверки правил БЕЗ раскрытия плашки. */
.tflow__stage-rules-summary {
    flex: 0 0 auto;
    padding: 0 5px;
    border-radius: 3px;
    background: #ecfeff;
    color: #0e7490;
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
    margin-right: 6px;
}

/* Backlog #398: компактная метка «diff: N» на плашках extract/re_extract —
   сколько фактов в диффе относительно предыдущего извлечения. Неброско,
   серым, в одну строку рядом с названием стадии. */
.tflow__stage-diff-badge {
    flex: 0 0 auto;
    padding: 0 5px;
    border-radius: 3px;
    background: #f1f5f9;
    color: #64748b;
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
    margin-right: 6px;
}

/* PR-376x: бейдж результата security check'а в head'е плашки.
   V (зелёный круг) — нет триггера, диалог продолжается.
   X (красный круг) — триггер сработал, диалог уходит в эскалацию.

   P175: бейдж — inline-flex в конце row 1. title (flex:1) толкает его
   вправо к stats, поэтому вердикт стоит справа (как у других стадий-
   статусов) и НЕ наезжает на заголовок «Применение политики
   безопасности». rules-summary для security_check живёт в row 2
   (.tflow__stage-security-rules-row ниже). */
.tflow__stage-head { position: relative; }
.tflow__stage-security-badge {
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-size: 10px;
    font-weight: 700;
    color: #fff;
    pointer-events: none;
    flex: 0 0 auto;
    margin-right: 4px;
}

/* PR-376hhh: вторая строка security_check stage'а — rules-summary
   на отдельной row (security badge — inline в конце row 1).
   PR-376lll: юзер просил выровнять rules-summary к левой границе
   текста title'а («Применение политики безопасности») и подтянуть
   ближе к head'у. Используем normal flow с padding-left'ом, который
   соответствует .tflow__stage-head padding-left (6px) + icon-width
   (~13px) + flex gap (6px) = 25px. Без absolute, без transform. */
.tflow__stage-security-rules-row {
    display: block;
    width: 100%;
    padding-left: 5px;     /* выравнивание к левой границе title text */
    margin: -2px 0 2px;     /* отрицательный top — чуть приподнят к head */
    line-height: 1;
}
.tflow__stage-security-rules-row .tflow__stage-rules-summary {
    margin: 0;              /* без legacy margin-right'а из inline-варианта */
    display: inline-block;
}
.tflow__stage-security-badge--ok { background: #16a34a; }
.tflow__stage-security-badge--triggered { background: var(--color-danger); }

/* PR-376h: теги успешно вызванных tools на dispatch-стейдже.
   Показываются в head'е плашки рядом со счётчиками — юзер видит
   ЧТО реально дёрнул бот, не разворачивая плашку.

   PR-376ee: переносим контейнер на свою строку и каждый pill —
   на отдельной строке. flex-basis:100% выбрасывает контейнер
   из row 1 (там остаются icon/title/счётчики/stats),
   flex-direction:column раскладывает pills вертикально. Иначе
   при 3+ tool'ах ширина row 1 расползалась и счётчики справа
   («4 LLM · 3 tools · 10 ₽») обрезались overflow'ом. */
.tflow__stage-tool-pills {
    /* PR-376ll: tool-pills теперь sibling head-main (block), не
       вложенный flex-child. Нет order/flex-basis трюков: нативный
       block переносит на свою строку, flex-direction:column
       раскладывает pills вертикально. Margin-left=22px подгоняет
       под иконку stage'а (~13px + 6px gap + 3px ровно под title).
       PR-376mm: max-width:55% не даёт длинным pills'ам тянуть
       контейнер слишком широко (всё-равно column, но визуально
       аккуратнее). */
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    margin: 3px 0 0 22px;
    max-width: 55%;
}
.tflow__stage-tool-pill {
    padding: 1px 5px;
    border-radius: 3px;
    background: #ecfdf5;
    border: 1px solid #a7f3d0;
    color: #065f46;
    font-size: 10px;
    font-weight: 500;
    white-space: nowrap;
}
.tflow__stage-tool-pill code {
    background: transparent;
    color: inherit;
    padding: 0;
    font-size: 10px;
    font-weight: 600;
}
/* backlog #429: head-плашка тула на cache-replay dispatch-стадии — блёкло-
   зелёный тон + ♻ cache-чип (.tflow__round-cache), единообразно с раундом. */
.tflow__stage-tool-pill--cache {
    background: #f0fdf4;
    border-color: #bbf7d0;
    color: var(--color-success-text);
}
.tflow__stage-body {
    padding: 2px 5px 3px;
    background: var(--color-surface);
}
.tflow__stage-body > * + * { margin-top: 2px; }
.tflow__stage-section { margin: 1px 0; }
.tflow__stage-section > summary {
    cursor: pointer;
    user-select: none;
    padding: 0 3px;
    font-size: 10px;
    font-weight: 600;
    color: #1e3a8a;
    border-radius: 3px;
}
.tflow__stage-section > summary:hover { background: #eff6ff; }
.tflow__stage-empty { font-size: 10px; margin: 1px 0 0; }

/* Stage-варианты: цветовая индикация по kind. */
.tflow__stage--extract,
.tflow__stage--re_extract { border-left: 3px solid #f59e0b; }
/* ADR-0018: rules-стадии (jump|inject × pre|post) — единая rose-индикация;
   final_rules — легаси mixed (group_visit_into_stages). */
.tflow__stage--jump_rules,
.tflow__stage--inject_rules,
.tflow__stage--final_jump_rules,
.tflow__stage--final_inject_rules,
.tflow__stage--final_rules { border-left: 3px solid #be123c; }
.tflow__stage--llm_switch { border-left: 3px solid #2563eb; }
.tflow__stage--dispatch { border-left: 3px solid #0f766e; }
.tflow__stage--reply { border-left: 3px solid #16a34a; }
.tflow__stage--jump .tflow__stage-head { background: #fef2f2; }
/* P185: краш обработки turn'а — красная плашка «обработка прервалась». */
.tflow__stage--processing_failure { border-left: 3px solid var(--color-danger); }
.tflow__stage--processing_failure .tflow__stage-head { background: #fee2e2; }

/* ADR-0031 §D7: смена derive-статуса диалога рисуется НЕ per-step stage'ем, а
   вехой timeline-лога (`.btl__event--status-change` в `_bot_timeline_banner`),
   общей для панели и trace — поэтому собственных `.tflow__status-change*`
   классов больше нет (событие проецируется в РОВНО один дом — timeline). */

/* Tool-pills внутри dispatch-stage — зелёные использованные / серые неиспользованные. */
.tflow__stage-tools-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin: 4px 0;
}
.tflow__tool-pill {
    display: inline-flex;
    align-items: center;
    padding: 1px 5px;
    border-radius: 999px;
    border: 1px solid #cbd5e1;
    font-size: 10.5px;
    font-weight: 600;
}
.tflow__tool-pill--used {
    border-color: #86efac;
    background: #dcfce7;
    color: #166534;
}
.tflow__tool-pill--idle {
    border-color: #e2e8f0;
    background: #f1f5f9;
    color: #94a3b8;
}
/* PR #406: type-aware tinted border для LLM (фиолетовый, мозг) и
   deterministic (серо-голубой, код). Дополняется к used/idle. */
.tflow__tool-pill--llm {
    border-left: 3px solid #c084fc;  /* purple-400 */
}
.tflow__tool-pill--code {
    border-left: 3px solid #64748b;  /* slate-500 */
}
/* PR #406: tflow__tool-icon — ведущая иконка в render_tool_inv */
.tflow__tool-icon {
    display: inline-block;
    width: 1.25em;
    text-align: center;
    margin-right: 4px;
    font-size: 0.95em;
    user-select: none;
}
.tflow__tool-icon--llm { color: #9333ea; }   /* purple-600 */
.tflow__tool-icon--code {
    color: #475569;     /* slate-600 */
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 0.85em;
    /* P174: </> — 3 глифа, ширины 1.25em мало + солидус `/` даёт точку
       переноса (UAX#14) → плашка ломала иконку на 2 строки. nowrap +
       авто-ширина держат глиф в одну строку. */
    white-space: nowrap;
    width: auto;
}
/* Border-tint на главной плашке tool-call'а */
.tflow__tool-call--llm {
    border-left: 3px solid #c084fc;
    padding-left: 5px;
}
.tflow__tool-call--code {
    border-left: 3px solid #64748b;
    padding-left: 5px;
}

/* === P175: dispatch-box — единый список плашек-РАУНДОВ (вариант B) ===
   Раунд = dispatch-LLMCall + его ToolCall'ы. Заменяет два параллельных
   списка («Tools · N» + «Вызовы LLM · N»). Стиль наследует tflow__llm-call
   (рамка/радиус), стат справа — margin-left:auto (как stage/tool), стоимость
   зелёная (ключевое требование «вывести потраченные деньги»). */
.tflow__dispatch-summary {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
.tflow__dispatch-meta {
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 500;
}
.tflow__dispatch-stats {
    margin-left: auto;
    flex: 0 0 auto;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    font-size: 10px;
    color: var(--color-text-muted);
}
.tflow__dispatch-cost { color: #16a34a; font-weight: 600; }

/* Список раундов — вертикальный ритм ТОЛЬКО через gap (margin детей
   складывается с gap в flex-column — см. trace-UI lessons). */
.tflow__rounds {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.tflow__round-item { margin: 0; }
.tflow__round {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
}
.tflow__round--done {
    background: #f0fdf4;
    border-color: #bbf7d0;
}
.tflow__round-summary {
    cursor: pointer;
    user-select: none;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 5px;
    font-size: 10px;
    line-height: 1.3;
}
/* Круглый бейдж номера раунда. */
.tflow__round-idx {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #e2e8f0;
    color: #475569;
    font-size: 10px;
    font-weight: 600;
}
.tflow__round--done .tflow__round-idx { background: #16a34a; color: #fff; }
/* backlog #429: cache-replay раунд (план диспетчера переигран из кэша).
   Фон как у done-раунда, но кружок номера — БЛЁКЛЫЙ зелёный (ТОТ ЖЕ зелёный
   #16a34a, пониженная насыщенность: green-200 фон + green-700 цифра), отличая
   replay от свежего вызова. Свежий tool-раунд оставляет серый кружок. */
.tflow__round--cache {
    background: #f0fdf4;
    border-color: #bbf7d0;
}
.tflow__round--cache .tflow__round-idx { background: #bbf7d0; color: var(--color-success-text); }
/* Маркер «cache» на плашке раунда (после ярлыка/«Готово») + на плашке
   диспетчера (♻ план из кэша). Единый блёкло-зелёный тон. */
.tflow__round-cache {
    flex: 0 0 auto;
    font-size: 9px;
    font-weight: 600;
    color: var(--color-success-text);
    background: #dcfce7;
    border-radius: 3px;
    padding: 0 4px;
    margin-left: 4px;
}
.tflow__dispatch-cache {
    color: var(--color-success-text);
    font-weight: 600;
}
/* Ярлык раунда (имя tool'а / «готово») — НИКОГДА не переносится: nowrap +
   ellipsis, иначе длинное имя раздувает высоту summary (trace-UI lesson). */
.tflow__round-label {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
    color: var(--color-text);
    font-family: var(--font-mono);
}
.tflow__round-more { color: var(--color-text-muted); font-weight: 500; }
.tflow__round-stats {
    margin-left: auto;
    flex: 0 0 auto;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
}
.tflow__round-cost { color: #16a34a; font-weight: 600; }
.tflow__round-body {
    padding: 2px 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
/* Отступ детей раунда — только gap; обнуляем их собственные margin
   (tflow__tool-call несёт margin:4px 0, иначе сложится с gap). */
.tflow__round-body > * { margin: 0; }

.tflow__node-stats {
    /* PR P17.3: позиционирование (grid-area, justify-self) задано в
       более позднем правиле в этом же файле — оно перекроет
       margin-left:auto от flex-layout'а. Здесь оставлены только
       font-стили. */
    font-size: 10px;
    font-weight: 500;
    white-space: nowrap;
}

/* Древовидное раскрытие system_prompt — каждая раскрывашка это один
   слагаемый prompt_components (icon + title + длина). */
.tflow__prompt-tree {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin-top: 2px;
    padding-left: 5px;
    border-left: 2px solid #cbd5e1;
    line-height: 1.4;
    font-size: 10px;
}
.tflow__prompt-section,
.tflow__prompt-full {
    margin: 1px 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
    line-height: 1.25;
    white-space: normal;
}
.tflow__prompt-section > *,
.tflow__prompt-full > * {
    margin-top: 0;
    margin-bottom: 0;
}
.tflow__prompt-section > summary,
.tflow__prompt-full > summary {
    cursor: pointer;
    user-select: none;
    /* Flex-row: иконка + title + длина в ОДНУ строку, без переносов.
       Раньше было inline и emoji 🌐 ломал inline-flow — span'ы
       раздвигались на 3 строки с воздухом. */
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 1px 4px;
    font-size: 10px;
    font-weight: 600;
    color: #1e3a8a;
    border-radius: 3px;
    line-height: 1.4;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tflow__prompt-section > summary:hover,
.tflow__prompt-full > summary:hover {
    background: #eff6ff;
}
.tflow__prompt-marker {
    color: #94a3b8;
    font-family: ui-monospace, monospace;
    flex: 0 0 auto;
}
.tflow__prompt-section-len {
    color: #94a3b8;
    font-size: 10.5px;
    font-weight: 500;
    flex: 0 0 auto;
    margin-left: auto;
}

/* Сообщения пользователя/бота на оси (вне node-контейнеров). */
.tflow__msg {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md, 3px);
    /* PR-376o: tighter — было 8px 12px / margin 6px,
       стало 4px 10px / margin 2px (минус ~10px вертикали на bubble). */
    padding: 4px 5px;
    margin: 2px 0;
    position: relative;
}
/* Реплики клиента — салатово-блёклый зелёный (симметрично бот-реплике
   с её сиренево-блёклым голубым). Не яркий — глаз не отвлекать от
   служебных стейджей трассировки. */
.tflow__msg--client {
    background: #e9f7e0;
    border-left: 3px solid #65a30d;
}
/* PR-376o: бот-реплики — голубо-сиреневый, чтобы визуально отличать
   от клиентского салатового. Раньше был --color-accent-bg (#eaf6ff)
   — почти белый, не контрастно на странице. Сейчас явно сиреневый
   тон с purple-undertoned border. */
.tflow__msg--bot {
    background: #ede9fe;
    border-left: 3px solid #8b5cf6;
}
.tflow__msg--system {
    background: var(--color-bg);
    border: 1px dashed var(--color-border);
    color: var(--color-text-muted);
    font-size: 10px;
}
/* #572: реплики оператора — голубой тон, зеркало `dp-msg--operator` в
   _dialog_preview.html. Отличаем от ботовского сиреневого, от клиентского
   салатового и от пунктирного «системного» — чтобы operator-takeover
   диалога был визуально очевиден в трассе. */
.tflow__msg--operator {
    background: #dbeafe;
    border-left: 3px solid #2563eb;
    color: #1e3a8a;
}
.tflow__msg-operator-name {
    font-weight: 400;
    color: var(--color-text-muted);
}
.tflow__msg--target {
    box-shadow: 0 0 0 2px var(--color-accent);
}
.tflow__msg-head {
    display: flex;
    align-items: center;
    gap: 6px;
    /* PR-376o: tighter — было 4px, стало 1px. */
    margin-bottom: 1px;
    font-size: 10px;
    color: var(--color-text-muted);
}
.tflow__msg-icon { display: inline-flex; align-items: center; }
.tflow__msg-title { font-weight: 600; color: var(--color-text); }
/* PR P16.5: badge «от узла X» рядом с «Бот» — атрибуция реплики
   к узлу-генератору. Компактный pill с background. */
.tflow__msg-from {
    font-size: 10.5px;
    padding: 1px 5px;
    background: #e0e7ff;
    color: #3730a3;
    border-radius: 3px;
    border: 1px solid #c7d2fe;
    font-weight: 500;
}
.tflow__msg-from code {
    background: transparent;
    color: inherit;
    font-weight: 600;
    font-size: 10.5px;
}
.tflow__msg-time { margin-left: auto; font-family: var(--font-mono, monospace); }
/* PR #1286 sanitize-pipeline: badge показывает что client bubble отображает
   ORIGINAL text, а LLM stages видели sanitized. Помещён рядом с timestamp
   в header реплики клиента; tooltip перечисляет sanitize-kinds. */
.tflow__sanitize-badge {
    color: var(--color-warn-text);
    background: rgba(245, 158, 11, 0.12);
    padding: 1px 5px;
    border-radius: 3px;
    border: 1px solid rgba(245, 158, 11, 0.35);
    font-size: 10.5px;
    font-weight: 500;
    cursor: help;
    /* Не отбираем «авто-маржин» у timestamp'а — badge остаётся слева от
       времени; если времени нет — badge всё равно жмётся к началу. */
}
.tflow__msg-text {
    white-space: pre-wrap;
    word-wrap: break-word;
    /* PR-376o: tighter — line-height 1.45 → 1.3, font-size 13 → 12. */
    font-size: 10px;
    line-height: 1.3;
}

/* Встроенная цитата (quote-reply) — кросс-канальный парсер, см.
   app/services/quote_reply.py. Цитата свёрнута (<details>), приглушена и с
   бордюром слева — визуально отличима от нового текста, чтобы не путаться с
   дублем предыдущей реплики (прецедент 8337). Ответ (.tflow__quote-reply) —
   обычный текст основным цветом. */
/* Селектор специфичнее общего details-reset (`[class^="tflow__"] details`
   выше зануляет margin+padding); иначе тело цитаты прилипает к бордюру
   без отступа. Тот же уровень специфичности, но правило идёт позже. */
[class^="tflow__"] details.tflow__quote {
    white-space: normal;          /* структура: гасим pre-wrap родителя */
    margin: 0 0 4px;
    border-left: 2px solid var(--color-border);
    padding-left: 6px;
}
.tflow__quote-summary {
    cursor: pointer;
    color: var(--color-text-muted);
    font-size: 9.5px;
    list-style: none;             /* убираем дефолтный disclosure-маркер */
    user-select: none;
}
.tflow__quote-summary::-webkit-details-marker { display: none; }
.tflow__quote-badge {
    color: var(--color-text-muted);
    background: var(--color-accent-bg);
    padding: 0 4px;
    border-radius: 3px;
    font-weight: 600;
}
.tflow__quote-author { font-weight: 600; }
.tflow__quote-at { font-variant-numeric: tabular-nums; }
.tflow__quote-text {
    white-space: pre-wrap;        /* возвращаем перенос строк телу цитаты */
    word-wrap: break-word;
    margin: 3px 0 0;
    color: var(--color-text-muted);
    font-size: 9.5px;
    line-height: 1.3;
}
.tflow__quote-reply {
    white-space: pre-wrap;
    word-wrap: break-word;
}

/* P114-8 (ADR-0008): batch view client-bubble. Когда debouncer схлопнул
   N inbound-реплик в один engine.step (batch_size > 1), trace UI рисует
   один client-блок с header'ом «Клиент написал N сообщений за T сек:»
   и нумерованным списком bubble'ов. Визуально на той же салатовой
   подложке что и одиночный inbound (.tflow__msg--client) — юзеру
   очевидно что это пачка от одного клиента, а не разные turn'ы.
   batch_size == 1 (legacy / sync handler) рендерится старым путём. */
.tflow__batch-head {
    flex-wrap: wrap;
    gap: 6px 8px;
}
.tflow__batch-span {
    font-size: 10.5px;
    padding: 1px 5px;
    background: #ecfdf5;
    color: #065f46;
    border: 1px solid #a7f3d0;
    border-radius: 3px;
    font-weight: 500;
}
.tflow__batch-id {
    font-size: 10.5px;
    margin-left: auto;
    font-family: var(--font-mono, monospace);
}
.tflow__batch-id code {
    background: transparent;
    font-size: 10.5px;
}
.tflow__batch-list {
    /* Сбрасываем list-style и счётчик инициализируем заново — каждый
       batch начинает с 1 (юзер видит «1) ..., 2) ..., 3) ...»). */
    list-style: none;
    counter-reset: batch-item;
    padding: 0;
    margin: 4px 0 0 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.tflow__batch-item {
    counter-increment: batch-item;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 8px;
    padding: 4px 5px;
    background: rgba(255, 255, 255, 0.55);
    border-left: 2px solid #65a30d;
    border-radius: 2px;
    font-size: 10px;
    line-height: 1.3;
}
.tflow__batch-item::before {
    content: counter(batch-item) ".";
    grid-column: 1;
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    align-self: start;
    padding-top: 1px;
}
.tflow__batch-item-time {
    grid-column: 2;
    font-size: 10.5px;
    font-family: var(--font-mono, monospace);
}
.tflow__batch-item-text {
    grid-column: 2;
    white-space: pre-wrap;
    word-wrap: break-word;
}

/* Центральная осевая стрелка между блоками. */
.tflow__axis-arrow {
    text-align: center;
    color: #64748b;
    line-height: 1;
    margin: 4px 0;
    user-select: none;
    /* Внутри — inline SVG arrow_down макроса. SVG наследует
       currentColor через fill="none" stroke="currentColor". */
}
.tflow__arrow-down-svg {
    display: inline-block;
    vertical-align: middle;
}
/* P152-J Fix C: текстовая метка справа от arrow-glyph («→ <node_id>»)
   для sequential inter-visit transitions. Sequential arrows раньше были
   безымянными, юзер не понимал куда идёт переход. */
.tflow__axis-arrow--with-label {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
.tflow__axis-arrow-label {
    font-size: 10px;
    line-height: 1.2;
}
.tflow__axis-arrow-label code {
    font-family: ui-monospace, "SF Mono", Consolas, monospace;
    font-size: 10px;
}
/* P152-K (2026-05-25): transition_kind-aware стилизация axis-arrow.
   JUMP — красная стрелка + ⚡ молния + label «jump #N → slug» справа.
   SUPPRESSED default (auto перебит) — пунктир + ❌ + «сразу же» слева.
   SUPPRESSED branch (named branch перебит) — бордовый пунктир + ❌ +
   branch name слева. */
.tflow__axis-arrow--jump {
    color: #b91c1c;
}
.tflow__axis-arrow--jump .tflow__axis-arrow-glyph svg,
.tflow__axis-arrow--jump .tflow__arrow-down-svg {
    stroke: #b91c1c;
}
.tflow__axis-arrow-jump-icon {
    color: #b91c1c;
    font-size: 10px;
    line-height: 1;
}
.tflow__axis-arrow-label--right {
    margin-left: 4px;
}
.tflow__axis-arrow-label--left {
    margin-right: 4px;
}
.tflow__axis-arrow-label--jump {
    color: #b91c1c;
    font-weight: 600;
}
.tflow__axis-arrow-label--jump code {
    background: #fee2e2;
    border: 1px solid #fecaca;
    border-radius: 3px;
    padding: 0 4px;
    color: #7f1d1d;
}
.tflow__axis-arrow-label--suppressed {
    color: #64748b;
    text-decoration: line-through;
    text-decoration-color: #94a3b8;
    text-decoration-thickness: 1.5px;
}
.tflow__axis-arrow-label--suppressed-branch {
    color: #7a1f1f;
    text-decoration-color: #7a1f1f;
}
.tflow__axis-arrow-label--suppressed-branch code {
    background: #fef2f2;
    border: 1px solid #fecaca;
    border-radius: 3px;
    padding: 0 3px;
    color: #7a1f1f;
}
.tflow__suppressed-x {
    color: #ef4444;
    font-size: 10px;
    margin-right: 2px;
}

/* #P451: маркер «старт диалога» над СТАРТОВЫМ wait_start (visit_number==1).
   Зелёный круг-пилюля с датой+временем создания диалога (DD.MM HH:MM:SS, МСК)
   ВМЕСТО «сразу же»-коннектора — у первого узла графа нет входящего перехода. */
.tflow__dialog-start {
    display: flex;
    justify-content: center;
    margin: 2px 0 6px;
    user-select: none;
}
.tflow__dialog-start-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 12px;
    border-radius: 999px;
    background: #dcfce7;            /* светло-зелёный фон */
    border: 1.5px solid #16a34a;   /* зелёная обводка круга */
    color: #14532d;                /* тёмно-зелёный текст */
    font-family: var(--font-mono, monospace);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.1;
    white-space: nowrap;
}
/* Зелёная точка-кружок слева — буквальный «круг» рядом с датой+временем. */
.tflow__dialog-start-badge::before {
    content: "";
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: #16a34a;
    flex: 0 0 auto;
}

/* P192: контекст входа в escalation-family узел на внешнем контейнере
   карточки (сразу под summary). Источник — `visit.escalation_context`
   (ДАННЫЕ visit из ребра предшественника), оба типа узла. Подложка по роли
   узла: розовая для escalate, светло-зелёная для success_escalation. */
.tflow__escalation-ctx {
    margin: 6px 12px;
    padding: 5px 5px;
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.4;
}
.tflow__escalation-ctx--escalate {
    background: #fef2f2;          /* розовая — обычная (неуспешная) эскалация */
    border: 1px solid #fecaca;
    border-left: 4px solid var(--color-danger);
}
.tflow__escalation-ctx--success {
    background: #f0fdf4;          /* светло-зелёная — успешная эскалация */
    border: 1px solid #bbf7d0;
    border-left: 4px solid #16a34a;
}
.tflow__escalation-ctx-head {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-weight: 500;
    margin-bottom: 3px;
}
.tflow__escalation-ctx--escalate .tflow__escalation-ctx-head { color: #991b1b; }
.tflow__escalation-ctx--success .tflow__escalation-ctx-head { color: #166534; }
.tflow__escalation-ctx-icon { font-size: 13px; }
.tflow__escalation-ctx-trigger { font-weight: 400; color: #1f2937; }

/* PR P17.1/P17.2: единый стиль выделения jump_context.reason —
   используется в трёх местах:
   1) rules_jump LLM-call (.tflow__jc-card)
   2) escalation-banner на узле escalate (.tflow__escalation-banner-reason
      получает добавочный класс .tflow__jc-reason)
   3) global status-banner (.tflow__status-reason + .tflow__jc-reason).
   Визуально: бежевый фон, толстая красная левая рамка, метка «reason:».
   Это самое важное поле jump_context'а — чтобы юзер сразу читал
   ПОЧЕМУ engine прыгнул, а не блуждал по мета. */
.tflow__jc-card {
    margin-top: 6px;
    padding: 5px 5px;
    background: #fef3c7;  /* мягкий beige */
    border: 1px solid #fcd34d;
    border-left: 4px solid #d97706;
    border-radius: 3px;
    font-size: 10px;
}
.tflow__jc-card-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
    color: #78350f;
}
.tflow__jc-card-icon { font-size: 13px; }
.tflow__jc-source {
    padding: 1px 5px;
    background: #d97706;
    color: #fff;
    border-radius: 3px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.3px;
    font-weight: 700;
}
.tflow__jc-reason {
    /* Выделение причины — фон + левая красная рамка. */
    padding: 5px 5px;
    background: #fff7ed;
    border-left: 4px solid #ea580c;
    border-radius: 3px;
    margin: 4px 0;
    color: #1f2937;
    font-weight: 500;
    line-height: 1.4;
}
.tflow__jc-reason-label {
    display: inline-block;
    color: #c2410c;
    font-weight: 700;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    margin-right: 6px;
}
.tflow__jc-meta {
    font-size: 10px;
    margin-top: 4px;
}

/* PR-376nn: разделитель-граница между turn'ами — рендерится после
   waiting-блока, визуально отделяет «бот ждёт» от следующей
   реплики клиента (которая откроет новый turn). Тот же
   text-align:center что у axis-arrow — на одной оси с ↓.
   P175: нейтральный ⋯-разделитель в muted-сером — ⚡ зарезервирована
   за side-effect tool'ами (.tflow__tool-sidefx). */
.tflow__turn-boundary {
    text-align: center;
    line-height: 1;
    margin: 4px 0;
    user-select: none;
    color: var(--color-text-muted, #9ca3af);
    font-size: 10px;
}

/* Стрелка с меткой условия перехода. */
.tflow__transition {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 8px 0;
    gap: 2px;
}
.tflow__transition-glyph {
    /* PR #519: переходы по ветвям gate-узла рисуем бордовым (#630633),
       как и .prompt-branch в подсветке промтов. Backward / auto
       сохраняют свои оверрайды ниже. */
    color: #630633;
    line-height: 1;
    display: inline-flex;
    align-items: center;
}
.tflow__transition-label {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: #630633;
    background: var(--color-bg);
    border: 1px solid #630633;
    border-radius: 3px;
    padding: 1px 5px;
    text-transform: lowercase;
    letter-spacing: 0.04em;
    font-weight: 600;
}

/* P183-fix: строка перехода (suppressed-side + main). position:relative —
   absolute suppressed-side якорится по ЭТОЙ строке (= центр main), а не по
   всей высоте .tflow__transition (reason-бокс ниже row'а её увеличивал →
   «переход подавлен» наезжал на «почему:»). */
.tflow__transition-row {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

.tflow__transition-main {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

/* P183: «Почему:» бокс перехода — reasoning рядом с label (jump-правило /
   branch llm_switch). БЕЗ собственной подложки: для jump фон даёт родитель
   `.tflow__transition--jump`, для branch подложки нет (#1513). Длинный
   reasoning clamp'ится в 2 строки; полный текст — в `title=`. */
.tflow__transition-reason {
    width: 100%;
    max-width: 520px;
    margin-top: 2px;
    display: flex;
    align-items: baseline;
    gap: 5px;
    font-size: 10px;
    line-height: 1.35;
    text-align: left;
}
.tflow__transition-reason-label {
    flex: 0 0 auto;
    font-weight: 600;
    color: #9ca3af;
    text-transform: lowercase;
    letter-spacing: 0.02em;
}
.tflow__transition-reason-text {
    flex: 1 1 auto;
    min-width: 0;
    color: #4b5563;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: help;
}

/* Контейнер node-визита.
   PR-376o: горчично-жёлтый фон чтобы визуально выделить КАЖДЫЙ узел
   графа в трассировке от служебных stage'ей внутри. Цвет тёплый,
   мягкий — глаз не утомляет, узлы графа становятся scannable
   landmarks при чтении длинной трассы.

   position: relative нужен для абсолютного позиционирования
   `.tflow__node-number` (порядковый номер-кружок).

   margin-left отступ оставлен 0 — кружок выходит за границу карточки
   слева через top:-8px / left:-8px. */
.tflow__node {
    position: relative;
    background: #fff8e7;
    border: 1px solid #fcd9a7;
    border-radius: var(--radius-md, 3px);
    margin: 4px 0 4px 12px;
    overflow: visible;
    /* visible нужен чтобы порядковый номер-кружок (см. ниже) мог
       выходить за границу карточки слева. */
}
.tflow__node--ai_reply,
.tflow__node--escalation {
    border-left: 4px solid var(--color-accent);
}

/* PR-376o: порядковый номер визита (синий кружок в левом верхнем
   углу карточки узла). Position absolute со сдвигом наружу — overflow
   у `.tflow__node` теперь visible. */
.tflow__node-number {
    position: absolute;
    /* PR-376ll: смещаем кружок дальше за угол — раньше top/left:-8px
       давало ~64% перекрытия с border'ом узла, цифра наезжала на
       заголовок/иконку. Теперь -19px: внутрь карточки заходит
       только ~3px (≈14%), цифра свободно читается за пределами. */
    top: -19px;
    left: -19px;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: #2563eb;
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    border-radius: 50%;
    border: 2px solid #fff;
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.2);
    z-index: 3;
    /* Не перекрывает hit-area summary'я (cursor) — flex-row парент
       summary имеет padding-left достаточный чтобы кружок стоял
       снаружи. */
    pointer-events: none;
}
/* PR-376p: убрал background-overrides со спец. типов узлов. Раньше
   llm_switch / prestep / leftover задавали свой фон, который перекрывал
   общий горчично-жёлтый из .tflow__node. Теперь все визиты единого
   цвета (фон карточки графа), различаются только border-left'ом и
   синим кружком-номером. Leftover остаётся розоватым — это аномалия
   и должна выделяться. */
.tflow__node--llm_switch {
    border-left: 4px solid #b08bff;
}
.tflow__node--static_reply {
    border-left: 4px solid #64748b;
}
.tflow__node--switch {
    border-left: 4px solid var(--color-warning, #f0b429);
}
.tflow__node--terminal {
    border-left: 4px solid var(--color-text-muted);
    opacity: 0.85;
}
.tflow__node--prestep {
    border-left: 4px solid #6cb7ab;
}
.tflow__node--leftover {
    border-left: 4px solid var(--color-danger);
    background: #fff3f3;  /* sentinel: leftover специально розоватый */
}
/* P165: success_escalation card — зелёный border (успешный CRM-handoff),
   симметрично зелёной семантике этого типа в редакторе графа
   (.scripts-node--special-success_escalation). */
.tflow__node--success_escalation {
    border-left: 4px solid var(--color-success-text);
}

.tflow__node-head {
    cursor: pointer;
    padding: 5px 5px;
    /* PR P17.3 + PR P44-T2 + PR P45: grid с двумя рядами.
       row 1: [number] [badge] [sub] [flex-gap] [stats] [entry]
       row 2: [name span 6]
       1fr-spacer ВТОРОЙ слева — толкает stats + entry в правую часть.
       Между stats и entry — фиксированный column-gap (см. ниже): stats
       не упирается в правый край, entry в самом правом положении. */
    display: grid;
    grid-template-columns: auto auto auto 1fr auto auto;
    grid-template-areas:
        "number badge  sub   .     stats entry"
        "name   name   name  name  name  name";
    column-gap: 6px;
    row-gap: 2px;
    align-items: center;
    font-size: 12.5px;
    line-height: 1.3;
    user-select: none;
}
.tflow__node-head:hover { background: var(--color-bg); }
.tflow__node[open] > .tflow__node-head { border-bottom: 1px solid var(--color-border); }
.tflow__node-badge {
    font-size: 10.5px;
    font-weight: 600;
    padding: 1px 5px;
    border-radius: 3px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
    white-space: nowrap;
}

/* PR P24 (P21 trace visibility): badge 🌀 heuristic в node-head
   трассы — синхронно с .scripts-node__badge--heuristic в графе
   /admin/scripts. Юзер: «должно быть видно и в trace и в редакторе
   графа на узлах где кружки Ожидание ответа клиента». */
.tflow__node-heuristic {
    font-size: 10px;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 3px;
    background: #ede9fe;
    color: #5b21b6;
    border: 1px solid #c4b5fd;
    white-space: nowrap;
}

.tflow__heuristic-banner {
    margin: 12px 0;
    padding: 5px 5px;
    background: #fefce8;
    border: 1px solid #fde047;
    border-left: 4px solid #f59e0b;
    border-radius: 3px;
    color: #713f12;
}
.tflow__heuristic-banner-title {
    font-weight: 700;
    margin-bottom: 6px;
    color: #92400e;
}
.tflow__heuristic-banner-text {
    margin: 6px 0 8px;
    font-size: 10px;
    line-height: 1.45;
}
.tflow__heuristic-banner-text code {
    background: #fef9c3;
    padding: 1px 4px;
    border-radius: 3px;
}
.tflow__heuristic-banner-preview {
    background: #fefce8;
    border: 1px dashed #facc15;
    color: #713f12;
    font-size: 10px;
}
.tflow__node-badge--ai_reply,
.tflow__node-badge--escalation {
    border-color: var(--color-accent);
    color: var(--color-accent);
}
.tflow__node-badge--llm_switch {
    border-color: #b08bff;
    color: #5a32b8;
}
.tflow__node-badge--prestep {
    border-color: #6cb7ab;
    color: #1d6d5d;
}
.tflow__node-badge--terminal {
    color: var(--color-text-muted);
}
/* PR P98 Phase 3 trace composite: «start» бейдж — зелёный (entry). */
.tflow__node-badge--start {
    border-color: var(--color-success-text);
    color: var(--color-success-text);
}
/* P165: success_escalation бейдж — зелёный (успешный CRM-handoff). */
.tflow__node-badge--success_escalation {
    border-color: var(--color-success-text);
    color: var(--color-success-text);
}

/* PR P41: WAIT-NODE card — отдельная плашка-узел «ОЖИДАНИЕ ОТВЕТА
   КЛИЕНТА» в timeline между AIReply визитом и следующим узлом.
   Рисуется БЕЗ slug (только тип-бейдж + иконка ⌛ + название). Справа
   в stats — реальное elapsed-время ИЛИ значок эвристики (если
   автопродолжение). При heuristic-варианте — плашка полупрозрачная,
   значок крайне заметный без прозрачности. */
.tflow__node--wait {
    border-left: 4px solid #94a3b8;
    background: #f8fafc;
}
.tflow__node--wait-heuristic {
    /* Полупрозрачный фон + diagonal hatch — визуально показывает что
       узел «не отработал реально», engine пошёл дальше через эвристику.
       НЕ используем CSS-opacity (она affects children — heuristic-mark
       должен оставаться ярким). Вместо этого — alpha-channel в bg
       и приглушённый text-color. */
    background:
        repeating-linear-gradient(
            135deg,
            rgba(241, 245, 249, 0.55),
            rgba(241, 245, 249, 0.55) 6px,
            rgba(254, 243, 199, 0.55) 6px,
            rgba(254, 243, 199, 0.55) 12px
        );
    color: rgba(71, 85, 105, 0.7);
}
/* Текст внутри полупрозрачной wait-heuristic плашки тоже приглушаем
   — но heuristic-mark и его ключевые элементы остаются яркими. */
.tflow__node--wait-heuristic .tflow__node-name,
.tflow__node--wait-heuristic .tflow__wait-body,
.tflow__node--wait-heuristic .tflow__node-badge--wait {
    color: rgba(71, 85, 105, 0.75);
}
.tflow__node--wait-heuristic .tflow__node-badge--wait {
    border-color: rgba(148, 163, 184, 0.55);
    background: rgba(255, 255, 255, 0.6);
}
.tflow__node-badge--wait {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 5px;
    border: 1px solid #94a3b8;
    border-radius: 3px;
    background: #fff;
    color: #475569;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.tflow__node-badge--wait .tflow__node-badge-icon {
    font-size: 10px;
    line-height: 1;
}
.tflow__wait-heuristic-mark {
    /* Крайне заметный значок поверх полупрозрачной плашки. Использует
       полностью opaque background + bold — visual emphasis отделяет
       его от приглушённого фона wait-card. */
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 5px;
    border: 2px solid #f59e0b;
    border-radius: 3px;
    background: #fef3c7;
    color: #92400e;
    font-size: 11.5px;
    font-weight: 800;
    box-shadow: 0 2px 6px rgba(245, 158, 11, 0.35);
}

.tflow__wait-elapsed {
    color: #475569;
    font-size: 11.5px;
    font-weight: 600;
}
.tflow__wait-open {
    color: #92400e;
    font-style: italic;
    font-size: 11.5px;
}
/* P152-D3 .tflow__wait-arrow + .tflow__wait-node-id удалены в P152-K —
   source slug убран из wait-card (user feedback: «вывод только this
   node name slug, без предыдущего узла»). */
.tflow__wait-body {
    padding: 5px 5px;
    color: #475569;
    font-size: 10px;
}
.tflow__wait-body p {
    margin: 0;
    line-height: 1.45;
}
/* PR P45: runtime-only facts (без ИИ-гипотез) в теле wait-card —
   key/value-таблица из ai_calls_log и графа. */
.tflow__wait-runtime-facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 8px;
    row-gap: 4px;
    margin: 0;
    font-size: 11.5px;
    line-height: 1.45;
}
.tflow__wait-runtime-facts > dt {
    font-weight: 700;
    color: #475569;
    white-space: nowrap;
}
.tflow__wait-runtime-facts > dd {
    margin: 0;
    color: #1f2937;
}

/* ============= P162: wait-lifecycle card ===========================
   Слепляет три фазы wait-узла (engine_paused / wait_client /
   resume_pre) в один collapsible-component вместо трёх визуально-
   разных карточек (synthetic visit + inter-turn wait + pre-step
   следующего turn). Каждая фаза — bordered под-карточка внутри.
   ================================================================ */
.tflow__lifecycle-card {
    margin: 8px 0 12px;
    border: 1.5px solid #94a3b8;
    border-radius: 3px;
    background: #f8fafc;
    overflow: hidden;
}
.tflow__lifecycle-card[open] {
    box-shadow: 0 1px 4px rgba(15, 23, 42, 0.06);
}
.tflow__lifecycle-card > summary {
    list-style: none;
    cursor: pointer;
    padding: 5px 5px;
    background: linear-gradient(to right, #e2e8f0, #f1f5f9);
    border-bottom: 1px solid #cbd5e1;
    /* P167-0 hotfix: flex вместо grid — позволяет добавлять
       visit_number badge + entry_time без переписывания
       grid-template-columns. Flex-wrap по содержимому. */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}
.tflow__lifecycle-card > summary::-webkit-details-marker {
    display: none;
}
.tflow__lifecycle-card[open] > summary {
    background: linear-gradient(to right, #cbd5e1, #e2e8f0);
}
.tflow__lifecycle-card > summary::after {
    content: '▾';
    color: #64748b;
    font-size: 10px;
    margin-left: 4px;
}
.tflow__lifecycle-card[open] > summary::after {
    content: '▴';
}
.tflow__lifecycle-icon {
    font-size: 10px;
    line-height: 1;
}
.tflow__lifecycle-badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 5px;
    border: 1px solid #475569;
    border-radius: 3px;
    background: #fff;
    color: #1e293b;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.tflow__lifecycle-badge--open {
    border-color: #d97706;
    color: #92400e;
    background: #fef3c7;
}
.tflow__lifecycle-title {
    font-weight: 600;
    color: #1e293b;
    font-size: 13px;
}
.tflow__lifecycle-sub {
    font-size: 10px;
}
.tflow__lifecycle-stats {
    font-size: 11.5px;
    white-space: nowrap;
    /* P167-0 hotfix: stats отъезжают вправо, оставляя место под
       entry_time на крайнем правом. */
    margin-left: auto;
}

/* P167-0 hotfix: visit_number badge для wait-lifecycle карточек.
   Визуально совпадает с `tflow__node-number` из render_visit_list
   (тот же 22×22 кружок с цифрой) но не зависит от grid-area
   (lifecycle summary использует flex). */
.tflow__lifecycle-visit-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 5px;
    border: 1.5px solid #3b82f6;
    border-radius: 999px;
    background: #eff6ff;
    color: #1d4ed8;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}

/* P167-0 hotfix: entry_time wait-визита. Monospace, mut-color,
   разделитель dotted-border слева — parity с `.tflow__node-entry-time`. */
.tflow__lifecycle-entry-time {
    white-space: nowrap;
    font-size: 11.5px;
    font-family: var(--font-mono, monospace);
    color: var(--color-text-muted);
    padding-left: 5px;
    border-left: 1px dotted rgba(148, 163, 184, 0.35);
}

.tflow__lifecycle-phases {
    list-style: none;
    margin: 0;
    padding: 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.tflow__lifecycle-phase {
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #fff;
    overflow: hidden;
}
.tflow__lifecycle-phase--engine_paused {
    border-left: 3px solid #64748b;
}
.tflow__lifecycle-phase--wait_client {
    border-left: 3px solid #d97706;
}
.tflow__lifecycle-phase--resume_pre {
    border-left: 3px solid #059669;
}
.tflow__lifecycle-phase > details > summary {
    list-style: none;
    cursor: pointer;
    padding: 5px 5px;
    display: grid;
    grid-template-columns: 18px 18px 1fr auto auto;
    align-items: center;
    gap: 8px;
    font-size: 10px;
}
.tflow__lifecycle-phase > details > summary::-webkit-details-marker {
    display: none;
}
.tflow__lifecycle-phase > details > summary::after {
    content: '▾';
    color: #94a3b8;
    font-size: 10px;
}
.tflow__lifecycle-phase > details[open] > summary::after {
    content: '▴';
}
.tflow__lifecycle-phase > details[open] > summary {
    background: #f8fafc;
    border-bottom: 1px dashed #e2e8f0;
}
.tflow__lifecycle-phase-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #475569;
    color: #fff;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
}
.tflow__lifecycle-phase--engine_paused .tflow__lifecycle-phase-num {
    background: #64748b;
}
.tflow__lifecycle-phase--wait_client .tflow__lifecycle-phase-num {
    background: #d97706;
}
.tflow__lifecycle-phase--resume_pre .tflow__lifecycle-phase-num {
    background: #059669;
}
.tflow__lifecycle-phase-icon {
    font-size: 10px;
    line-height: 1;
}
.tflow__lifecycle-phase-title {
    color: #1e293b;
    font-weight: 600;
}
.tflow__lifecycle-phase-stats {
    font-size: 10px;
    white-space: nowrap;
}
.tflow__lifecycle-phase-body {
    padding: 5px 5px 5px;
    color: #475569;
    font-size: 10px;
    line-height: 1.5;
}
.tflow__lifecycle-phase-text {
    margin: 0 0 6px;
}
.tflow__lifecycle-phase-text:last-child {
    margin-bottom: 0;
}
.tflow__lifecycle-phase-stages {
    margin-top: 8px;
}
.tflow__lifecycle-client-reply {
    margin: 6px 0 0;
}
/* #P452: подпись над batch'ем реплик внутри wait-card — N сообщений подряд,
   обработанных одним engine.step (один цикл ожидания). */
.tflow__lifecycle-batch-hint {
    margin: 6px 0 2px;
    font-size: 10px;
    font-style: italic;
}

.tflow__node-name {
    font-weight: 600;
    color: var(--color-text);
    /* PR P17.3: name теперь живёт во второй строке grid'а
       (.tflow__node-head). Wrap длинных названий без обрезки. */
    grid-area: name;
    min-width: 0;
    overflow-wrap: anywhere;
    white-space: normal;
}
.tflow__node-number { grid-area: number; }
.tflow__node-badge { grid-area: badge; }
.tflow__node-sub { grid-area: sub; }
.tflow__node-stats {
    /* PR P17.3 + P44-T2 + P45: stats в правой части header'а grid'а,
       но НЕ в крайнем правом — между stats и entry-time идёт зазор
       (8-12px) для visual breathing. */
    grid-area: stats;
    white-space: nowrap;
    margin-right: 4px;
}
/* PR P44-T2 + P45: entry-time — крайний правый слот grid'а.
   monospace HH:MM:SS, mut-color. БЕЗ ⏎-иконки (юзер на ревью:
   «нафига иконку Enter добавил? убери»). */
.tflow__node-entry-time {
    grid-area: entry;
    justify-self: end;
    white-space: nowrap;
    font-size: 11.5px;
    font-family: var(--font-mono, monospace);
    color: var(--color-text-muted);
    padding-left: 5px;
    border-left: 1px dotted rgba(148, 163, 184, 0.35);
}
.tflow__node-sub {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}
.tflow__node-decision {
    margin-left: auto;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: var(--color-accent);
}
.tflow__node-decision-default {
    font-style: italic;
    color: var(--color-text-muted);
    font-size: 10px;
    margin-left: 2px;
}

.tflow__node-body { padding: 5px 5px; }

/* Секция внутри node — instruction / tools / rules / switch / llm / tools. */
.tflow__node-section {
    margin: 6px 0;
    border-top: 1px solid var(--color-border);
    padding-top: 5px;
}
.tflow__node-section:first-child { border-top: 0; padding-top: 0; }
.tflow__node-section-title {
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text);
    padding: 2px 0;
    user-select: none;
}
.tflow__node-section-title:hover { color: var(--color-accent); }
.tflow__node-section-body { padding-top: 4px; }

/* Список tools узла (tools_available — тезисно). */
.tflow__node-section--tools-list {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
}
.tflow__tool-name {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 1px 5px;
    color: var(--color-text);
}

/* LLM-Switch decision body. */
.tflow__switch-body {
    background: #faf6ff;
    border-radius: 3px;
    padding: 5px;
    border: 1px dashed #d0bdff;
}
.tflow__switch-decision {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    margin: 6px 0 4px;
}
.tflow__switch-label { color: var(--color-text-muted); font-size: 10px; text-transform: uppercase; }
.tflow__switch-meaning {
    margin: 2px 0;
    font-size: 10px;
    color: var(--color-text-muted);
}
.tflow__switch-target {
    margin-top: 6px;
    font-size: 10px;
    color: var(--color-text);
}

/* Инструкция шага. */
.tflow__node-instruction-pre {
    margin: 0;
    font-size: 10px;
    line-height: 1.4;
}

/* Один LLM-вызов (collapsible). */
.tflow__llm-call {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    /* PR-376i: убираю «воздух» вокруг карточки. Раньше margin:2px
       суммировался с gap:2px родительского flex-column — итого 4px
       до соседей. Сейчас 0 + parent gap:2px = 2px. */
    margin: 0;
}
.tflow__llm-call-summary {
    cursor: pointer;
    padding: 2px 5px;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 10px;
    /* Суммари — одна строка. Без flex-wrap, чтобы muted-блок с
       model/in/out/cost не разрывался на много строк (это создавало
       визуальный «воздух» между llm_switch-баджем и моделью).
       При недостатке ширины — горизонтальный overflow с эллипсисом. */
    flex-wrap: nowrap;
    min-width: 0;
    line-height: 1.3;
}
.tflow__llm-call > .tflow__llm-call-summary {
    padding: 2px 5px;
    white-space: normal;
}
.tflow__llm-call > .tflow__llm-call-summary > * {
    white-space: nowrap;
}
.tflow__llm-call-summary > .muted {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.tflow__llm-purpose {
    font-weight: 600;
    color: var(--color-text);
}
.tflow__llm-call-body { padding: 3px 5px; border-top: 1px solid var(--color-border); }

/* Один tool_invocation (всегда раскрыт). */
.tflow__tool-call {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px 5px;
    margin: 4px 0;
}
.tflow__tool-call--err {
    border-color: var(--color-danger);
    background: #fff3f3;
}
.tflow__tool-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
    font-size: 10px;
}
/* P174: ⚡-индикатор tool'а с side-effect (мутация внешнего состояния).
   Рисуется только при ti.row.side_effects; тултип поясняет семантику.
   Read-only tools (prefilter_catalog / pick_by_specs) — без него. */
.tflow__tool-sidefx {
    color: #d97706;     /* amber-600 — как signal-icon */
    font-weight: 600;
    cursor: help;
    white-space: nowrap;
}
/* P174: статистика tool-плашки — правый край header'а (как stage/visit).
   Tools без cost → только время (fmt_secs), рядом — статус-бейдж.
   margin-left:auto толкает группу вправо вне зависимости от ширины имени. */
.tflow__tool-stats {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    white-space: nowrap;
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
}
/* P174: статус вызова. ✓ — успех (зелёная галка, без кружка); ✗ — runtime-
   ошибка: белый крестик в красном кружке (не просто цветной текст). */
.tflow__tool-status--ok {
    color: #2a8a3e;
    font-weight: 700;
}
.tflow__tool-status--err {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-danger, var(--color-danger));
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}
/* Фаза 4: частичная ошибка доставки (тул вернул result.errors без
   исключения) — «!»-статус + бейдж + баннер, красно-розовая тема. */
.tflow__tool-status--warn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #e11d48;
    color: #fff;
    font-size: 11px;
    font-weight: 800;
    line-height: 1;
}
.tflow__tool-warn {
    font-size: 10px;
    font-weight: 700;
    color: #be123c;
    background: #ffe4e6;
    border: 1px solid #fecdd3;
    border-radius: 4px;
    padding: 1px 5px;
    margin-left: 4px;
    cursor: help;
}
.tflow__tool-warn-banner {
    margin-top: 6px;
    padding: 6px 8px;
    background: #fff1f2;
    border: 1px solid #fecdd3;
    border-left: 3px solid #e11d48;
    border-radius: 4px;
    font-size: 11px;
    color: #9f1239;
}
.tflow__tool-warn-banner .ttrace__pre--err { margin-top: 4px; }
/* P174: раскрываемая детализация ошибки tool'а (summary — красный). */
.tflow__tool-error > summary { color: var(--color-danger); }

/* Доставленные фото в trace-плашке tool'а (send_product_photos) — превью
   сгруппированы по модели: заголовок-название + ряд миниатюр. */
.tflow__tool-photos {
    margin-top: 6px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
}
.tflow__tool-photos-model {
    font-weight: 700;
    font-size: 10px;
    color: #475569;
}
.tflow__tool-photos-model:not(:first-child) {
    margin-top: 10px;
}
.tflow__tool-photos-row {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}
.tflow__tool-photo-link { display: inline-block; line-height: 0; }
.tflow__tool-photo {
    width: 56px;
    height: 56px;
    object-fit: cover;
    border-radius: 4px;
    border: 1px solid rgba(0, 0, 0, 0.12);
    cursor: zoom-in;
}

/* Однострочный preview аргументов tool-вызова — видно с какими
   входами вызвался tool без раскрытия details. */
.tflow__tool-args-preview {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: var(--color-text-muted);
    background: var(--color-card);
    border: 1px dashed var(--color-border);
    border-radius: 3px;
    padding: 2px 5px;
    margin: 2px 0 4px;
    overflow-x: auto;
    white-space: nowrap;
    max-width: 100%;
}

/* Warning когда rules_check / llm_switch Haiku вернул не-JSON. */
.tflow__warn {
    background: #fff8e6;
    border: 1px solid #f0b429;
    border-radius: 3px;
    padding: 5px 5px;
    margin: 4px 0;
    font-size: 10px;
    color: #7a4f00;
}

/* === Context-вьювер (popover у 🅢) =========================================
   Структурированный обзор того, что доступно rule engine'у при анализе
   условий: история, tool-вызовы (in/out), метаданные, dialog.state. */
.tctx { font-size: 10px; }

.tctx__hint {
    font-size: 10px;
    color: var(--color-text-muted);
    margin: 0 0 8px;
    line-height: 1.45;
}

.tctx__sec {
    border-top: 1px solid var(--color-border);
    padding: 5px 0;
}
.tctx__sec:first-of-type { border-top: 0; padding-top: 0; }
.tctx__sec > summary {
    cursor: pointer;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text);
    padding: 2px 0;
    user-select: none;
}
.tctx__sec > summary:hover { color: var(--color-accent); }

.tctx__empty {
    margin: 4px 0 0;
    font-size: 10px;
    font-style: italic;
}

/* История — список реплик с компактной строкой [icon · time · text]. */
.tctx__history {
    list-style: none;
    counter-reset: tctx-h;
    margin: 6px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.tctx__hist {
    counter-increment: tctx-h;
    display: grid;
    grid-template-columns: 18px 60px 1fr;
    gap: 6px;
    align-items: start;
    padding: 4px 5px;
    background: var(--color-bg);
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.4;
}
.tctx__hist--client    { border-left: 2px solid #f59e0b; }
.tctx__hist--bot       { border-left: 2px solid var(--color-accent); }
.tctx__hist--operator  { border-left: 2px solid #6b8acc; }
.tctx__hist--system    { border-left: 2px solid var(--color-text-muted); opacity: 0.85; }
.tctx__hist-icon { font-size: 12px; line-height: 1.4; }
.tctx__hist-time {
    font-family: var(--font-mono, monospace);
    color: var(--color-text-muted);
    font-size: 10px;
}
.tctx__hist-text {
    word-break: break-word;
    white-space: pre-wrap;
}

/* Tool-вызов в контекст-вьювере — компактный блок с input/output. */
.tctx__tool {
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px 5px;
    margin-top: 6px;
}
.tctx__tool--err { border-color: var(--color-danger); }
.tctx__tool-head {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    margin-bottom: 4px;
}
.tctx__tool-head code {
    background: var(--color-card);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
}
.tctx__tool-ok { color: #2a8a3e; font-weight: 600; }
.tctx__tool-err { color: var(--color-danger); font-weight: 600; }
.tctx__io { margin-top: 4px; }
.tctx__io > summary {
    cursor: pointer;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 1px 0;
}

/* Метаданные — компактный 2-колоночный grid. */
.tctx__meta {
    display: grid;
    grid-template-columns: 140px 1fr;
    gap: 3px 8px;
    margin: 6px 0 0;
    font-size: 10px;
}
.tctx__meta dt {
    color: var(--color-text-muted);
    font-weight: normal;
}
.tctx__meta dd {
    margin: 0;
    color: var(--color-text);
    word-break: break-word;
}
.tctx__meta dd.mono { font-family: var(--font-mono, monospace); }

.tctx__sub {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-top: 4px;
    margin-bottom: 2px;
}

/* === tool_use / tool_result блоки внутри messages в карточке промта ===
   Anthropic API передаёт их внутри content как list объектов
   {type: 'tool_use'|'tool_result', ...}. Рендерим их визуально отдельно
   от обычного текста — чтобы было видно, что именно LLM «видит» как
   инструмент и его результат. */
.ttrace__blk {
    border-radius: 3px;
    padding: 5px 5px;
    margin-top: 4px;
    font-size: 10px;
}
.ttrace__blk--tool-use {
    background: #fff8e8;
    border: 1px solid #f0c75a;
    border-left: 3px solid #d97706;
}
.ttrace__blk--tool-result {
    background: #eef7ee;
    border: 1px solid #a3d3a8;
    border-left: 3px solid #2a8a3e;
}
.ttrace__blk-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 4px;
    font-size: 10px;
}
.ttrace__blk-head code {
    background: var(--color-card);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-family: var(--font-mono, monospace);
}

/* Подсветка JSON-фактов в extract_dialog_facts. Трёхуровневая:
   1. .fact-set — факт существовал в prev и осталось то же значение
      (unchanged) — жирный синий.
   2. .fact-null — пустое значение (null/false/0/"") — серый plain.
   3. .fact-new — новый ключ или изменённое значение (added/changed) —
      жирный красный, перебивает .fact-set.
*/
.fact-set {
    color: #1e40af;
    font-weight: 700;
}
.fact-null {
    color: #94a3b8;
    font-style: italic;
}
.fact-new {
    color: #c0392b;
    font-weight: 700;
    background: #fff4f0;
    border-radius: 2px;
    padding: 0 2px;
}
.ttrace__pre--diff-facts {
    /* Лёгкая зелёная рамка справа от JSON где есть новые факты,
       чтобы оператор сразу заметил delta-блок в скролле trace. */
    border-left: 3px solid #27ae60;
}

/* Trace UI C: всегда видимая плашка с результатом extract'а
   (внутри wait-узлов после pre-step + внутри AI-reply узлов после
   tool use re-extract'а). Не схлопывается в <details>, потому что
   юзеру важно видеть «какие факты увидел экстрактор» без клика. */
.tflow__facts-diff {
    background: #f9fafb;
    border: 1px solid #e5e7eb;
    border-radius: 3px;
    padding: 5px 5px;
    margin: 6px 0;
}
.tflow__facts-diff-header {
    font-weight: 500;
    color: #4b5563;
    margin-bottom: 4px;
    font-size: 0.9em;
    display: flex;
    align-items: center;
    gap: 6px;
}
.tflow__facts-diff-count {
    font-size: 0.85em;
    color: #6b7280;
}
.tflow__facts-diff .ttrace__pre--facts-json {
    /* Снимаем фон с pre — он уже на родителе .tflow__facts-diff. */
    margin: 0;
}

/* Глобальный системный промт — textarea на странице Настройки. */
.settings-system-prompt {
    margin-top: 32px;
    padding: 5px;
    background: #f9fafb;
    border-radius: 3px;
    border: 1px solid #e5e7eb;
}
.settings-system-prompt__title {
    margin: 0 0 8px;
    font-size: 13px;
}
.settings-system-prompt__hint {
    margin: 0 0 12px;
    font-size: 10px;
    color: #555;
}
.settings-system-prompt__textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 5px;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    background: #fff;
    resize: vertical;
}
.settings-system-prompt__actions {
    margin-top: 8px;
    display: flex;
    justify-content: flex-end;
}

/* «Сигнал-вход / сигнал-переход / сигнал-флаг» — единый visual-язык
   для пометки событий в trace. Значок выделяется янтарным, бадж лёгкий.
   ⚡ здесь НЕ используется — зарезервирована за side-effect tool'ами
   (.tflow__tool-sidefx); переходы — 🎯/🔀/→, вход-сигнал — ↳. */
.tflow__signal-icon {
    color: #d97706;
    font-weight: 600;
}
.tflow__signal-in {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 5px;
    background: #fff8eb;
    border: 1px solid #fcd9a7;
    border-radius: 3px;
    font-size: 10px;
    color: #92400e;
    font-style: italic;
    /* PR-376m: бейдж — однострочный. Раньше «facts + branch question
       узла» внутри inline-block с white-space: normal переносился на
       2 строки → height summary'я раздувался до 71.5px (хотя должен
       быть ~22px), отсюда юзер видел «воздух» в карточке LLM-вызова.
       Если строка не помещается — обрезаем эллипсисом. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 280px;
    flex: 0 1 auto;
}
.tflow__signal-in--done {
    background: #fff4d4;
    border-color: #f59e0b;
    color: var(--color-warn-text);
    font-weight: 600;
    font-style: normal;
}
.tflow__signal-footer {
    margin-top: 6px;
    padding: 5px 5px;
    background: #fff8eb;
    border-left: 3px solid #d97706;
    border-radius: 3px;
    font-size: 10px;
    color: #92400e;
}
.tflow__signal-footer--escalation {
    background: #fef2f2;
    border-left-color: var(--color-danger);
    color: #991b1b;
}
.tflow__signal-footer-label code {
    background: rgba(0, 0, 0, 0.05);
    padding: 0 4px;
    border-radius: 3px;
}

/* «Обработка инструкции узла» (purpose=node_instruction) — основной
   рабочий цикл узла, где LLM выполняет system_addon через tool_use.
   Визуально выделяется тёмно-синей рамкой и фоном, чтобы оператор в
   trace сразу видел: «здесь модель обрабатывала инструкцию этапа». */
.tflow__llm-call-summary--node_instruction {
    background: #eef4ff;
    border-left: 3px solid #4a6cf7;
}
.tflow__llm-purpose--node_instruction {
    color: #2a4ad9;
    font-weight: 600;
}

.tflow__nested { margin-top: 0; }
.tflow__nested > summary {
    cursor: pointer;
    font-size: 10px;
    color: var(--color-accent);
    padding: 0;
    line-height: 1.3;
}

/* ====================================================================
   PR-376k: Sweeping margin-reset для всех children внутри trace-
   контейнеров. Юзер несколько раз показывал «воздух» (vertical gap'ы)
   между стандартными блоками: switch-decision, switch-target,
   tctx__sub, ttrace__pre, p-tags etc.

   Корень: каждый из этих элементов имеет свой margin top/bottom,
   и они АДДИТИВНО накладываются на flex-gap родителя.
   Стратегия: вертикальный отступ контролируется ИСКЛЮЧИТЕЛЬНО
   gap'ом родителя (2px). Все margin-top/bottom у direct children =0.
   Если нужен бо́льший gap для конкретного блока — используем
   `gap: 4px` на родителе.
   ==================================================================== */
.tflow__stage-body > *,
.tflow__stage-section > *,
.tflow__stage-llms > *,
.tflow__llm-call-body > *,
.tflow__nested > *,
.tflow__node-body > * {
    margin-top: 0;
    margin-bottom: 0;
}
/* Conkrete элементы — оставляем нулевой margin: они flex-children
   parent'ов выше, parent сам разрулит gap. */
.tflow__switch-decision { margin: 0; }
.tflow__switch-target { margin: 0; }
.tflow__switch-meaning { margin: 0; }
.tflow__stage-empty { margin: 0; }
/* tctx__sub — мини-метка над pre (например, "RESPONSE_TEXT"). Жмёмся
   к pre без вертикального отступа. */
.tctx__sub { margin: 0; }

/* Бейдж с именами tools прямо в summary collapsible'а LLM-call'а —
   чтобы в свёрнутом состоянии видно было, что итерация дёрнула. */
.tflow__llm-call-tools-preview {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 5px;
    background: #f3f0ff;
    border: 1px solid #d6cdf2;
    border-radius: 3px;
    font-size: 10px;
    color: #4a3a8c;
    font-family: var(--font-mono, monospace);
    /* PR-376m: однострочный, иначе раздувает summary'я. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 240px;
    flex: 0 1 auto;
}

/* PR-376m: blanket guard для всех summary-flex-row контейнеров в
   трейсе. Любой ребёнок флекс-row summary'я с inline-block badge'ем
   может содержать длинный текст; если он перенесётся внутрь себя,
   summary раздуется в несколько строк (юзер на скриншоте увидел
   summary высотой 71.5px вместо ~22px).

   Гарантируем что ВСЕ summary-flex-row остаются ровно одной строкой.
   Если содержимое не помещается — overflow:hidden обрежет (детали
   видны раскрытием collapsible'а).
*/
.tflow__stage-head,
.tflow__llm-call-summary {
    flex-wrap: nowrap;
    min-height: 0;
    overflow: hidden;
}
/* PR P18 fix: .tflow__node-head УБРАН из правила выше — он переведён
   на CSS grid (P17.3) с двумя рядами, где row 2 содержит полное
   название узла. overflow:hidden обрезал row 2 → юзер видел
   «Обработка узла «Эс...» вместо «Обработка узла «Эскалация менеджеру»».
   Юзер на ревью второй раз указал «ничего не поменялось» — корневая
   причина была здесь, + cache-buster в base.html не bump'нулся (см.
   PR P18 — теперь ?v=20260519p18). */
.tflow__node-head {
    flex-wrap: nowrap;
    min-height: 0;
    /* overflow: visible (default) — grid row 2 видна полностью. */
}

/* Tools, инициированные конкретным ai_reply LLM-call'ом — список
   внутри collapsible-summary этого call'а. */
.tflow__llm-tools-list {
    list-style: none;
    margin: 4px 0 0;
    padding: 0 0 0 5px;
    border-left: 2px solid var(--color-border, #e1e1e1);
}
.tflow__llm-tool {
    margin: 2px 0;
    padding: 0;
}
.tflow__llm-tool--err > details > summary { color: var(--color-danger); }
.tflow__llm-tool > details > summary {
    font-size: 10px;
    padding: 2px 4px;
}
.tflow__llm-tool > details > summary code {
    font-weight: 600;
}

.tflow__error {
    margin-top: 4px;
    background: #fff4f3;
    color: var(--color-danger);
    border: 1px solid var(--color-danger);
    border-radius: 3px;
    padding: 5px 5px;
    font-size: 10px;
}
/* PR #412: ярко-красный inline-блок для API-ошибок per-call. */
.tflow__error--api {
    background: #fee2e2;
    border: 2px solid var(--color-danger);
    color: #7f1d1d;
    font-weight: 500;
}
.tflow__error--balance {
    background: #fff1e6;
    border-color: #ea580c;
    color: #7c2d12;
}
/* P186: turn-level standalone-плашка pre-node краша («обработка прервалась
   до входа в узел»). Тот же красный, что .tflow__stage--processing_failure,
   но не внутри карточки узла — узел реально не обрабатывался. */
.tflow__error--prenode {
    border-left-width: 4px;
}
.tflow__error-prenode-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 4px;
}
.tflow__error-prenode-time {
    margin-left: auto;
    font-size: 10px;
    font-weight: 400;
}
.tflow__error-icon {
    font-size: 10px;
    margin-right: 6px;
    vertical-align: middle;
}

/* PR #412: top-level api-error banner на trace панели. Очень
   заметный — красная полоса с иконкой, при необходимости разворачивается. */
.tflow__api-error-banner {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    margin: 8px 0 14px;
    padding: 5px 5px;
    background: #fef2f2;
    border: 2px solid var(--color-danger);
    border-left-width: 6px;
    border-radius: 3px;
    color: #7f1d1d;
    box-shadow: 0 2px 6px rgba(220, 38, 38, 0.15);
}
.tflow__api-error-icon {
    font-size: 10px;
    line-height: 1;
}
.tflow__api-error-body {
    flex: 1;
    min-width: 0;
}
.tflow__api-error-title {
    font-size: 13px;
    font-weight: 700;
    margin-bottom: 4px;
}
.tflow__api-error-details summary {
    font-size: 11.5px;
    color: #991b1b;
    cursor: pointer;
    padding: 2px 0;
    user-select: none;
}
.tflow__api-error-list {
    list-style: none;
    padding: 0;
    margin: 6px 0 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.tflow__api-error-list li {
    background: #fff;
    border: 1px solid #fca5a5;
    border-radius: 3px;
    padding: 5px 5px;
    font-size: 11.5px;
}
.tflow__api-error-kind {
    display: inline-block;
    padding: 1px 5px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    margin-right: 4px;
}
.tflow__api-error-kind--balance {
    background: #ea580c;
    color: white;
}
.tflow__api-error-kind--rate_limit {
    background: #d97706;
    color: white;
}
.tflow__api-error-kind--auth {
    background: #7c2d12;
    color: white;
}
.tflow__api-error-kind--other {
    background: #6b7280;
    color: white;
}
.tflow__api-error-hint {
    margin-top: 6px;
    font-size: 10px;
}
/* Pill в шапке trace */
.ttrace__api-err-pill {
    display: inline-block;
    background: #fee2e2;
    color: #7f1d1d;
    border: 1px solid #fca5a5;
    border-radius: 3px;
    padding: 1px 5px;
    font-weight: 700;
    font-size: 10px;
}


/* ========================================================================
 *  Вкладка «Настройки» — Phase 1.8b (mapping линий + on/off бота)
 * ====================================================================== */

.settings-page {
    height: 100%;
    overflow-y: auto;
    padding: 5px var(--space-xl);
    max-width: 1200px;
    margin: 0 auto;
}

.settings-page__header {
    margin-bottom: var(--space-lg);
}

.settings-page__title {
    margin: 0 0 var(--space-xs) 0;
    font-size: 13px;
    color: var(--color-text);
}

.settings-page__hint {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.55;
    max-width: 800px;
}

.settings-page__alert {
    margin-top: var(--space-md);
    padding: 5px 5px;
    background: #fff8e1;
    border: 1px solid #f0c14b;
    border-radius: 3px;
    color: #6b4f00;
    font-size: 10px;
}

.settings-page__alert code {
    background: #fff;
    padding: 1px 5px;
    border-radius: 3px;
}

.settings-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    overflow: hidden;
}

.settings-table thead {
    background: var(--color-bg);
}

.settings-table th {
    text-align: left;
    padding: 5px 5px;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border-bottom: 1px solid var(--color-border);
}

.settings-table__col-toggle {
    width: 80px;
    text-align: center !important;
}

.settings-table__col-actions {
    width: 220px;
    text-align: right !important;
}

.settings-row td {
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    vertical-align: middle;
}

.settings-row:last-child td {
    border-bottom: 0;
}

.settings-row--orphan {
    background: rgba(0, 0, 0, 0.02);
}

.settings-row--orphan .settings-cell-line__name {
    color: var(--color-text-muted);
    font-style: italic;
}

.settings-row--on .settings-cell-line__name {
    font-weight: 600;
}

/* OPEN LINE — одна строка: «line_id: N   <имя>» (id первым, затем имя). */
.settings-cell-line__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
}

.settings-cell-line__name {
    font-size: 10px;
}

.settings-cell-line__id {
    font-size: 10px;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.settings-cell-line__id code {
    background: var(--color-bg);
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
}

.settings-cell-line__off {
    color: var(--color-danger);
}

.settings-select {
    width: 100%;
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-card);
    color: var(--color-text);
    font-size: 10px;
}

.settings-toggle {
    position: relative;
    display: inline-block;
    width: 44px;
    height: 24px;
    cursor: pointer;
}

.settings-toggle input {
    opacity: 0;
    width: 0;
    height: 0;
}

.settings-toggle__slider {
    position: absolute;
    inset: 0;
    /* OFF-стейт: затемнённый трек + рамка — иначе серый-на-белом тумблер
       сливается с фоном ячейки и не виден (белое на белом). */
    background: #cbd5e1;
    border: 1px solid #94a3b8;
    border-radius: 3px;
    transition: background 0.15s, border-color 0.15s;
}

.settings-toggle__slider::before {
    content: "";
    position: absolute;
    width: 16px;
    height: 16px;
    left: 3px;
    top: 3px;
    /* Бортик у бегунка — контраст с треком в обоих стейтах. */
    background: #fff;
    border: 1px solid #64748b;
    border-radius: 50%;
    transition: transform 0.15s;
}

.settings-toggle input:checked + .settings-toggle__slider {
    background: var(--color-accent);
}

.settings-toggle input:checked + .settings-toggle__slider::before {
    transform: translateX(20px);
}

.settings-btn {
    padding: 5px 5px;
    border: 0;
    border-radius: 3px;
    font-size: 10px;
    cursor: pointer;
    margin-left: 4px;
}

.settings-btn--save {
    background: var(--color-accent);
    color: #fff;
}

.settings-btn--save:hover {
    filter: brightness(1.05);
}

.settings-btn--delete {
    background: transparent;
    color: var(--color-danger);
    border: 1px solid var(--color-border);
}

.settings-btn--delete:hover {
    background: rgba(204, 51, 51, 0.08);
    border-color: var(--color-danger);
}

/* Тумблер без сохранённого mapping — недоступен (нечего переключать до Save). */
.settings-toggle--disabled {
    cursor: not-allowed;
}
.settings-toggle input:disabled + .settings-toggle__slider {
    opacity: 0.45;
    cursor: not-allowed;
}

/* Inline-статус строки: ⚠ ошибка guard'а Save / тумблера, ✓ успех переключения. */
.settings-row__status {
    font-size: 10px;
    margin-right: 6px;
    color: var(--color-text-muted);
    white-space: nowrap;
}
.settings-row__status.is-ok {
    color: var(--color-accent);
}
.settings-row__status.is-error {
    color: var(--color-danger);
}

.settings-empty {
    text-align: center;
    color: var(--color-text-muted);
    padding: 5px;
    font-size: 10px;
}

.settings-empty code {
    background: var(--color-bg);
    padding: 1px 5px;
    border-radius: 3px;
}

/* ========================================================================
 *  Вкладка «Правила» — Phase 1.8c (CRUD)
 * ====================================================================== */

.rules-page {
    /* PR #424: full-width вкладка.
       PR #457: убрали overflow-y:auto и padding с .rules-page —
       scrollbar переехал на .rules-list. Filter теперь сидит между
       header'ом и списком и реально доходит до viewport-края (нет
       внутреннего scrollbar'а, режущего 19px). flex column держит
       header/filter сверху, list занимает остаток высоты. */
    height: 100%;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}
.rules-page__header {
    flex-shrink: 0;
    padding: 5px 5px 0;
}
.rules-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 5px 5px 5px;
}
.rules-empty {
    margin: 0 var(--space-md);
}

.rules-page__header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-md);
    margin-bottom: var(--space-lg);
}

.rules-page__title {
    margin: 0 0 var(--space-xs) 0;
    font-size: 13px;
    color: var(--color-text);
}

.rules-page__hint {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.55;
    max-width: 700px;
}

.rules-page__new {
    flex-shrink: 0;
    padding: 5px 5px;
    background: var(--color-accent);
    color: #fff;
    border-radius: 3px;
    text-decoration: none;
    font-weight: 500;
    font-size: 10px;
}

.rules-page__new:hover {
    filter: brightness(1.05);
}

.rules-empty {
    padding: var(--space-xl);
    text-align: center;
    color: var(--color-text-muted);
    background: var(--color-card);
    border: 1px dashed var(--color-border);
    border-radius: 3px;
}

.rules-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
}

.rule-card {
    /* PR #452: position:relative для абсолютно-позиционированного
       .rule-card__invalid-badge в правом верхнем углу.
       PR #522: явная граница и shadow — раньше едва различимая
       (1px var(--color-border) на var(--color-card)). Теперь border
       толще, drop-shadow выделяет карточку из фона. */
    position: relative;
    background: var(--color-card);
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
    padding: 5px;
}

.rule-card--off {
    opacity: 0.6;
}

/* PR #452: красная обводка карточки правила если в condition_prompt /
   action_prompt есть невалидные ссылки. Зеркалит .scripts-node--has-invalid-refs
   на узлах графа (PR #443). Outline (а не border) — не съедает место. */
.rule-card--has-invalid-refs {
    outline: 2px solid var(--color-danger);
    outline-offset: 1px;
}

/* PR #452: красный кружок «!» в правом верхнем углу rule-card'а.
   Стилизация 1-в-1 как .scripts-node__invalid-badge. */
.rule-card__invalid-badge {
    position: absolute;
    top: -12px;
    right: -12px;
    z-index: 5;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--color-danger);
    color: #ffffff;
    font-size: 10px;
    font-weight: 800;
    line-height: 20px;
    text-align: center;
    cursor: help;
    box-shadow: 0 2px 6px rgba(220, 38, 38, 0.45);
    border: 2px solid #ffffff;
    transition: transform 0.15s;
    user-select: none;
}
.rule-card__invalid-badge:hover {
    transform: scale(1.18);
}

.rule-card__head {
    /* PR #521: 3-колоночный header — title | meta (tags + priority)
       | actions (icon-кнопки). Заголовок sticky-широкий (минимальный
       320px), meta-блок занимает оставшееся, actions auto-ширины
       в правом краю. На узких экранах wrap'аются. */
    display: grid;
    grid-template-columns: minmax(260px, max-content) 1fr auto;
    align-items: center;
    gap: var(--space-md);
    margin-bottom: var(--space-sm);
}

.rule-card__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text);
}

/* PR #521: icon-кнопки в верхнем правом углу карточки правила. */
.rule-card__head-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    justify-self: end;
}
.rule-card__head-action-form {
    margin: 0;
    display: inline-flex;
}
.rule-card__icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition: background 120ms, border-color 120ms, color 120ms;
}
.rule-card__icon-btn:hover {
    background: #ede9fe;
    border-color: #c4b5fd;
    color: #4c1d95;
}
.rule-card__icon-btn--delete:hover {
    background: #fee2e2;
    border-color: #fca5a5;
    color: #991b1b;
}
.rule-card__icon-btn--edit { font-size: 15px; }
.rule-card__icon-btn--delete { font-size: 17px; font-weight: 700; }

/* PR #419: ID правила перед именем — серый mono-шрифт. */
.rule-card__id {
    display: inline-block;
    margin-right: 6px;
    padding: 1px 5px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text-muted);
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
    font-size: 10px;
    font-weight: 500;
    vertical-align: middle;
}

.rule-card__off-badge {
    margin-left: var(--space-xs);
    padding: 2px 5px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
    border-radius: 3px;
    font-size: 10px;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.rule-card__meta {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.rule-card__pill {
    padding: 2px 5px;
    background: var(--color-bg);
    color: var(--color-text-muted);
    border-radius: 3px;
    font-size: 10px;
    font-family: var(--font-mono, monospace);
}

.rule-card__pill--scope {
    background: var(--color-accent-bg);
    color: var(--color-accent);
}

.rule-card__pill--kind {
    background: #fef3c7;
    color: #92400e;
}

.rule-card__pill--timing {
    background: #dbeafe;
    color: #1e40af;
}

.rule-card__desc {
    margin: 0 0 var(--space-sm) 0;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.55;
}

.rule-card__section {
    margin-top: var(--space-sm);
}

.rule-card__sub {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    margin-bottom: 4px;
}

.rule-card__pre {
    margin: 0;
    padding: 5px 5px;
    background: #ffffff;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-size: 10px;
    font-family: var(--font-mono, monospace);
    white-space: pre-wrap;
    word-wrap: break-word;
    line-height: 1.5;
    /* PR #451: contenteditable выглядит как обычный <pre>, при наведении
       курсор-text как намёк что можно редактировать. */
    cursor: text;
    outline: 0 solid transparent;
    transition: background-color 120ms ease-out, outline-color 120ms ease-out;
}
/* PR #451: фокус-режим — горчично-жёлтый фон + amber outline. Зеркалит
   .scripts-node__editor:focus на узлах графа. На blur автоматическое
   сохранение через PATCH /admin/rules/{id}/field. */
.rule-card__pre.is-editing,
.rule-card__pre:focus {
    background-color: #fef3c7 !important;  /* amber-100 */
    outline: 2px solid #f59e0b !important;  /* amber-500 */
    outline-offset: -1px;
}
/* PR #451: кнопка «↶ Отменить правки» — справа-вверху wrapper'а (section).
   mousedown (раньше blur'а) откатывает текст и снимает фокус. */
.rule-card__revert-btn {
    position: absolute;
    top: 3px;
    right: 6px;
    z-index: 10;
    background: #f59e0b;
    color: #ffffff;
    border: 0;
    border-radius: 3px;
    padding: 1px 5px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
    transition: background 100ms ease-out;
    user-select: none;
    white-space: nowrap;
}
.rule-card__revert-btn:hover {
    background: #d97706;
}
/* PR #451: toast «✓ сохранено» / «⚠ ошибка» — 2 секунды fade-out. */
.rule-card__save-toast {
    position: absolute;
    top: 3px;
    right: 6px;
    z-index: 10;
    background: #10b981;
    color: #ffffff;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
    pointer-events: none;
    animation: rule-toast-fade 2s ease-in forwards;
    user-select: none;
    white-space: nowrap;
}
.rule-card__save-toast.is-error {
    background: var(--color-danger);
}
@keyframes rule-toast-fade {
    0%, 70% { opacity: 1; transform: translateY(0); }
    100%    { opacity: 0; transform: translateY(-4px); }
}

/* PR #422: 2-column IF/THEN layout + sticky filter + scope-tags. */
.rule-card__body {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-top: 6px;
}
.rule-card__body--single {
    grid-template-columns: 1fr;
}
.rule-card__col {
    min-width: 0;
}
.rule-card__col--if .rule-card__pre {
    border-left: 3px solid #fbbf24;  /* amber-400 */
}
.rule-card__col--then .rule-card__pre {
    border-left: 3px solid #34d399;  /* emerald-400 */
}

/* PR #453: scope-теги структурно по kind:
   - everywhere / always — ярко-зелёный (bright green)
   - all_directions      — тёмно-фиолетовый
   - all_nodes           — тёмно-рыжий
   - direction (single)  — светло-фиолетовый (existing)
   - node (single)       — amber (existing, monospace)
   Legacy (--global/--direction/--script/--script_node) оставлены для
   совместимости с другими местами (rule_form и т.п.). */
.rule-card__scope-tag {
    display: inline-block;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
}
/* «везде» и «всегда» — ярко-зелёные. */
.rule-card__scope-tag--everywhere,
.rule-card__scope-tag--always {
    background: #d1fae5;         /* emerald-100 */
    color: #047857;              /* emerald-700 */
    border-color: #34d399;       /* emerald-400 */
}
/* «все направления» — тёмно-фиолетовый. */
.rule-card__scope-tag--all_directions {
    background: #6b21a8;         /* purple-800 */
    color: #ffffff;
    border-color: #6b21a8;
}
/* «все узлы» — тёмно-рыжий. */
.rule-card__scope-tag--all_nodes {
    background: var(--color-warn-text);         /* amber-700 */
    color: #ffffff;
    border-color: var(--color-warn-text);
}
/* Одиночное направление — светло-фиолетовый. */
.rule-card__scope-tag--direction {
    background: #ede9fe;         /* violet-100 */
    color: #4c1d95;
    border-color: #c4b5fd;
}
/* Одиночный узел — amber, monospace для технических id'шников. */
.rule-card__scope-tag--node {
    background: #fef3c7;         /* amber-100 */
    color: #78350f;
    border-color: #fcd34d;
    font-family: var(--font-mono, monospace);
}
/* Legacy aliases для других мест где могут использоваться */
.rule-card__scope-tag--global {
    background: #f1f5f9;
    color: #1e293b;
}
.rule-card__scope-tag--script {
    background: #ccfbf1;
    color: #115e59;
    border-color: #5eead4;
}
.rule-card__scope-tag--script_node {
    background: #fef3c7;
    color: #78350f;
    border-color: #fcd34d;
    font-family: var(--font-mono, monospace);
}

/* PR #453: неактивный ЕСЛИ-блок для правил condition_kind=always.
   Внутри: «[всегда] для: <теги>». Серый фон + dashed-border намекают
   на «вы тут не редактируете». */
.rule-card__col--always .rule-card__always-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 5px 5px;
    background: #f8fafc;
    border: 1px dashed var(--color-border);
    border-left: 3px solid #34d399;  /* зелёный = «всегда» */
    border-radius: 3px;
    font-size: 10px;
    color: var(--color-text-muted);
}
.rule-card__always-for {
    font-weight: 500;
    color: #475569;
}

/* PR #424: вкладка «Инструменты» — справочник tools. */
.tools-page {
    height: 100%;
    overflow-y: auto;
    padding: 5px 5px;
}
.tools-page__title {
    margin: 0 0 6px;
    font-size: 13px;
}
.tools-page__hint {
    margin: 0 0 8px;
    color: var(--color-text-muted);
    font-size: 10px;
}
.tools-page__legend {
    margin: 0 0 16px;
    padding: 5px 5px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-size: 12.5px;
}
.tools-page__legend > summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--color-text);
    padding: 4px 0;
    user-select: none;
}
.tools-page__legend > summary::marker {
    color: var(--color-text-muted);
}
.tools-page__legend ul {
    margin: 8px 0 4px;
    /* Локально 18px слева — нужно под маркеры списка (list-style: outside);
       кламп нормализации до 5px вжимал «•» в левую границу легенды. */
    padding: 0 0 0 18px;
    color: var(--color-text);
    line-height: 1.5;
}
.tools-page__legend li {
    margin-bottom: 6px;
}
.tools-page__legend code {
    background: rgba(99, 102, 241, 0.08);
    padding: 1px 4px;
    border-radius: 3px;
    font-size: 11.5px;
}
.tools-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.tool-card {
    /* PR #526: явная граница и shadow — зеркало .rule-card (PR #522).
       Раньше был бледный 1px var(--color-border); теперь slate-300
       #cbd5e1 + drop-shadow для выделения из фона. */
    background: var(--color-card);
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04);
    padding: 5px 5px;
}
.tool-card__head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}
.tool-card__icon {
    font-size: 10px;
    color: #9333ea;
}
.tool-card__icon--code {
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: 10px;
    color: #475569;
}
/* PR #454: иконка «двигатель» для системных tools. Тёмно-янтарная
   (#B86E05 — тот же что .prompt-node-id), визуально отличается от
   🧠 (purple) и </> (gray). */
.tool-card__icon--system {
    font-size: 10px;
    color: #B86E05;
}
/* PR #454: usage-note для системных tools — карточка под description'ом.
   Левый янтарный border намекает «это системная сноска», не редактируется. */
.tool-card--system .tool-card__system-usage {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 5px;
    margin-bottom: 10px;
    background: #fffbeb;          /* amber-50 */
    border: 1px solid #fde68a;    /* amber-200 */
    border-left: 4px solid #B86E05;
    border-radius: 3px;
    color: #78350f;               /* amber-900 */
    font-size: 12.5px;
    line-height: 1.45;
}
.tool-card__system-usage-icon {
    font-size: 10px;
    color: #B86E05;
    flex-shrink: 0;
}
.tool-card__name {
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: 10px;
    font-weight: 700;
    background: #f1f5f9;
    padding: 3px 5px;
    border-radius: 3px;
}
/* backlog #437: per-client TTL inline-edit на карточках кэшируемых тулов +
   карточке кэша плана диспетчера. placeholder «TTL» справа в шапке. */
.tool-card__ttl {
    margin-left: auto;            /* прижать вправо во flex-шапке */
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
}
.tool-card__ttl-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #64748b;               /* slate-500 — placeholder-стиль */
    text-transform: uppercase;
}
.tool-card__ttl-input {
    width: 62px;
    padding: 3px 5px;
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: 10px;
    text-align: right;
    border: 1px solid #cbd5e1;    /* slate-300 — как граница карточки */
    border-radius: 3px;
    background: #fff;
    transition: border-color 120ms, background 120ms, box-shadow 120ms;
}
.tool-card__ttl-input:focus {
    outline: none;
    border-color: #6366f1;        /* indigo-500 */
    box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.18);
}
/* override-маркер — эффективное значение отличается от код-дефолта
   (фиолетовый акцент, как у .tool-card__desc--override). */
.tool-card__ttl-input--override {
    border-color: #7c3aed;        /* violet-600 */
    background: #f5f3ff;          /* violet-50 */
}
.tool-card__ttl-unit {
    font-size: 10px;
    color: #64748b;
}
.tool-card__desc {
    color: var(--color-text);
    margin-bottom: 10px;
    /* Локально 10px — выровнено с базовым размером после нормализации
       UI-метрик (описание тула не должно быть крупнее тела карточки). */
    font-size: 10px;
    line-height: 1.5;
    /* PR #523: contenteditable + focus-lost-save. */
    padding: 5px 5px;
    border: 1px solid transparent;
    border-radius: 3px;
    transition: background 120ms, border-color 120ms;
    cursor: text;
    /* PR #527: фон cream для визуального hint «здесь можно кликать»;
       white-space:pre-wrap чтобы мягкие переносы (\n\n между абзацами)
       не схлопывались в один большой текст. */
    background: #fffdf2;            /* очень светлый cream — sticky-note */
    border-color: #f5edd0;
    white-space: pre-wrap;
}
.tool-card__desc:hover {
    border-color: #e8d68a;          /* hover чуть темнее, всё ещё нейтрально */
    background: #fffaea;
}
.tool-card__desc.is-editing {
    background: #fef9c3;          /* yellow-100 — горчично-жёлтый */
    border-color: #fde047;
    outline: none;
}
/* PR #523: override-маркер — карточка описания с override'нутым
   description'ом получает левую фиолетовую полоску, чтобы админ
   видел «отличается от code-defined». */
.tool-card__desc--override {
    border-left: 3px solid #7c3aed;
    background: #faf5ff;
}
.tool-card__cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}
/* PR #438: 3-колоночная раскладка (IN | OUT | usage refs). */
.tool-card__cols--triple {
    grid-template-columns: 1fr 1fr 1fr;
}
@media (max-width: 1100px) {
    .tool-card__cols--triple {
        grid-template-columns: 1fr;
    }
}
.tool-card__col {
    min-width: 0;
}
.tool-card__hint {
    margin: 6px 0 0;
    font-size: 11.5px;
    color: var(--color-text-muted);
    font-style: italic;
    line-height: 1.4;
}
.tool-card__sub {
    text-transform: uppercase;
    font-size: 10.5px;
    letter-spacing: 0.5px;
    color: var(--color-text-muted);
    margin-bottom: 4px;
    font-weight: 600;
}
.tool-card__schema {
    width: 100%;
    border-collapse: collapse;
    font-size: 10px;
}
.tool-card__schema th,
.tool-card__schema td {
    text-align: left;
    padding: 4px 5px;
    border-bottom: 1px solid var(--color-border);
}
/* Вложенные поля элементов массива (field[].sub) — отступ + приглушённый
   фон, чтобы было видно, что это под-поля элемента (напр. models[].avito_photos). */
.tool-card__schema-nested > td {
    background: rgba(99, 102, 241, 0.045);
}
.tool-card__schema-nested > td:first-child {
    padding-left: 16px;
    border-left: 2px solid #c7d2fe;
}
.tool-card__schema-nested code.prompt-tool-param { opacity: 0.85; }
/* PR #527: редактируемые ячейки описаний параметров IN/OUT-схемы. Фон
   cream + 1px светлая рамка визуально подсказывают «здесь можно править».
   white-space:pre-wrap сохраняет переносы строк (мягкие переносы)
   между абзацами, чтобы description не схлопывался в одну простыню. */
.tool-card__schema-desc[contenteditable] {
    background: #fffdf2;
    border: 1px solid #f5edd0;
    border-radius: 3px;
    padding: 3px 5px;
    cursor: text;
    white-space: pre-wrap;
    transition: background 120ms, border-color 120ms;
}
.tool-card__schema-desc[contenteditable]:hover {
    background: #fffaea;
    border-color: #e8d68a;
}
.tool-card__schema-desc[contenteditable].is-editing {
    background: #fef9c3;
    border-color: #fde047;
    outline: none;
}
/* Override-маркер для param-description (зеркало .tool-card__desc--override). */
.tool-card__schema-desc--override {
    border-left: 3px solid #7c3aed;
    background: #faf5ff;
}
.tool-card__schema code {
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: 11.5px;
    /* PR #475: убран hardcoded color #1e3a8a (blue-900) — перебивал
       .prompt-tool-param (teal #0d9488) по specificity (class+tag >
       class), из-за чего ИМЕНА полей в IN/OUT schema показывались
       синими, а должны быть teal как везде. Цвет теперь даётся
       prompt-X классом. */
}
.tool-card__refs {
    margin: 0;
    padding-left: 5px;
    font-size: 12.5px;
}
.tool-card__refs li {
    margin-bottom: 4px;
    line-height: 1.45;
}
/* PR #438: структурированный node-ref «script · node_id».
   script — обычным цветом, node_id — тёмно-синий жирный (как
   .prompt-good в валидаторе). */
.tool-card__ref-script {
    color: var(--color-text-muted);
    font-size: 11.5px;
}
.tool-card__ref-sep {
    color: #cbd5e1;
    margin: 0 4px;
}
.tool-card__ref-node {
    /* PR #527: рыжий цвет node-id'ов в карточках tools — выравнивает
       со .prompt-node-id (#B86E05) в подсветке промтов, чтобы узлы
       графа были одного цвета везде в UI. Раньше #1e3a8a blue-900. */
    color: #c2410c;          /* orange-700 — рыжий жирный */
    font-weight: 700;
    font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: 11.5px;
}
.tool-card__ref-warn {
    margin-left: 6px;
    color: var(--color-danger);
    font-size: 10px;
    font-weight: 600;
}
.tool-card__empty {
    color: var(--color-text-muted);
    font-size: 10px;
    margin: 4px 0;
    font-style: italic;
}

/* PR #450/467/470: легенда подсветки промптов.
   - .prompt-facts — ИМЕНА ПОЛЕЙ фактологии и dotted-paths
     (state.facts.intents[*].chosen_model_quote, qty_total) — DARK BLUE bold.
   - .prompt-facts-literal — ЛИТЕРАЛЫ из типизации (значения enum-полей):
     'accepted', 'pickup', "high", bare null, целые числа 0/1/2 — PURPLE bold.
   - .prompt-tool — registered tool name (prefilter_catalog, …) — DARK GREEN bold.
   - .prompt-tool-param — IN/OUT-параметры tool schemas — TEAL #0d9488 (морская волна).
   - .prompt-node-id — id узлов графа (router, propose, qualify_gate) — AMBER #B86E05.
   - .prompt-branch — имена branches gate-узлов (qualified, phone_given, …) — BURGUNDY #630633.
   - .prompt-engine — engine-конструкции: tool_result, rule_id=N, ai_reply
     (ТИП узла, не id), JSON — BLACK bold.
   - .prompt-bad — невалидная ссылка (RED bold с ⚠).
   - .prompt-tool-unregistered — tool в registry, но не на этом узле (RED bold).
   .prompt-good остался как legacy alias = engine для backward compat. */
.prompt-facts {
    color: #1e40af;          /* blue-800 — dark blue bold (имена полей) */
    font-weight: 700;
}
.prompt-facts-literal {
    color: #7c3aed;          /* violet-600 — purple bold (литералы) */
    font-weight: 700;
}
.prompt-tool {
    color: var(--color-success-text);          /* green-700 — dark green bold */
    font-weight: 700;
}
/* PR #467: параметры input_schema / output_schema любого
   зарегистрированного tool'а — морская волна (teal-600).
   Юзер: «все параметры INPUT / OUTPUT из tool schema подсвечиваем
   цветом морской волны». Динамически собирается из registry в
   ObjectModelIndex.tool_io_params. Цвет отделён и от tool-имён
   (зелёный), и от engine-meta (чёрный) — сразу видно «это контракт». */
.prompt-tool-param {
    color: #0d9488;          /* teal-600 — морская волна */
    font-weight: 700;
}
.prompt-node-id {
    color: #B86E05;          /* PR #450: dark amber — node id (router/propose/...) */
    font-weight: 700;
}
/* PR #470: имена branches gate-узлов — бордовый (burgundy).
   Юзер: «все ветви branches и их названия — цветом #630633».
   Собирается из ObjectModelIndex.branches_by_script. */
.prompt-branch {
    color: #630633;          /* burgundy — branch name */
    font-weight: 700;
}
.prompt-engine {
    color: #0f172a;          /* slate-900 — black bold */
    font-weight: 700;
}
/* PR #527: системные типы данных в описаниях параметров tools и всех
   промтах. bool / str / int / float / list / dict (и JSON Schema
   эквиваленты) → bold gray. Юзер: «системные типы данных bold подсветка». */
.prompt-type {
    color: #475569;          /* slate-600 — нейтральный жирный */
    font-weight: 700;
}
/* PR #527: status JSON-поле — синим. Юзер: «status — если это поле
   JSON то синим». Совпадает по оттенку с .prompt-facts (синие поля), но
   собственный namespace чтобы tooltip говорил «JSON status», а не
   «facts field». */
.prompt-status {
    color: #1d4ed8;          /* blue-700 — bold blue */
    font-weight: 700;
}
.prompt-good {
    /* Legacy alias — теперь = engine. */
    color: #0f172a;
    font-weight: 700;
}
.prompt-bad {
    color: var(--color-danger);          /* red-600 */
    font-weight: 600;
    background: #fef2f2;     /* red-50 */
    border-bottom: 1px dashed var(--color-danger);
    padding: 0 1px;
}
.prompt-bad::after {
    content: "⚠";
    font-size: 9px;
    margin-left: 2px;
    vertical-align: super;
}

/* PR #440: tool существует в registry, но НЕ зарегистрирован на этом
   узле в node.tools[] — LLM попробует вызвать → ToolError. Красный
   жирный с warning-tag.
   PR #466: подпись после ⚠ теперь визуально похожа на саму кнопку
   «⚙ tools» под узлом — оливково-зелёная плашка с тем же иконом-текстом.
   Юзер сразу понимает: «нажми эту кнопку и проставь галочку». */
.prompt-tool-unregistered {
    color: var(--color-danger);
    font-weight: 700;
    background: #fee2e2;
    padding: 0 2px;
    border-radius: 2px;
    border-bottom: 1px solid var(--color-danger);
}
.prompt-tool-unregistered::after {
    content: "⚠ нажмите ⚙ tools";
    margin-left: 4px;
    padding: 1px 5px;
    font-size: 9.5px;
    font-weight: 600;
    color: #4d5c1f;            /* olive-700 — оливково-зелёный текст */
    background: #f3f7e6;       /* olive-50 — мягкий зелёный фон */
    border: 1px solid #a3b86a; /* olive-400 — рамка */
    border-radius: 3px;
    vertical-align: 1px;
}

/* Filter bar.
   PR #424: убрана прозрачность фона — заголовок над scrolling листом
   должен полностью перекрывать карточки правил под ним.
   PR #457: filter теперь sibling между .rules-page__header и
   .rules-list (flex-column в .rules-page). overflow:auto переехал
   на .rules-list — у filter'а БОЛЬШЕ НЕТ конкурирующего scrollbar'а
   справа, фон реально доходит до viewport-краёв. Sticky тоже больше
   не нужен — filter и так в фиксированной зоне layout'а. */
.rules-filter {
    flex-shrink: 0;
    z-index: 5;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    gap: 6px;
    padding: 5px 5px;
    /* Единый цвет панелей (--color-card). Раньше тут был жёсткий #ffffff
       override — фильтр-бар выбивался из общей палитры панелей. */
    background: var(--color-card);
    border-bottom: 2px solid var(--color-border);
    box-shadow: 0 4px 8px rgba(0,0,0,0.08);
}
.rules-filter__controls {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-width: 0;
}
.rules-filter__search {
    flex: 1 1 280px;
    min-width: 200px;
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-size: 10px;
}
.rules-filter__select {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-size: 10px;
    background: var(--color-bg);
}
.rules-filter__select:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.rules-filter__count {
    margin-left: auto;
    padding: 4px 5px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    color: var(--color-text-muted);
}

/* PR #516: compact filter-bar — header «Правила» / «+ Новое правило»
   убран, кнопка перенесена в правый край фильтр-бара. Цель — сжать
   фильтры чтобы не переносились на 2 строки. */
.rules-filter--compact {
    padding: 5px 5px;
    gap: 4px;
}
.rules-filter--compact .rules-filter__controls {
    gap: 4px;
}
.rules-filter--compact .rules-filter__search {
    flex: 1 1 200px;
    min-width: 160px;
    padding: 5px 5px;
    font-size: 10px;
}
.rules-filter--compact .rules-filter__select {
    padding: 4px 5px;
    font-size: 11.5px;
    max-width: 150px;
}
.rules-filter--compact .rules-filter__multi {
    min-width: 130px;
    max-width: 220px;
    padding: 2px 5px;
}
.rules-filter--compact .rules-filter__multi-input {
    font-size: 11.5px;
}
.rules-filter--compact .rules-filter__count {
    padding: 3px 5px;
    font-size: 10px;
}

.rules-filter__new {
    align-self: start;
    padding: 5px 5px;
    border: 1px solid #7c3aed;
    border-radius: 3px;
    background: #7c3aed;
    color: #ffffff;
    font-size: 10px;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}
.rules-filter__new:hover {
    background: var(--color-busy-text);
    border-color: var(--color-busy-text);
}

/* PR #518: profiles / directions — toolbar только со счётчиком и
   «+ Новый» справа (нет фильтров, нет search). */
.rules-filter--single-action {
    padding: 5px 5px;
}

/* PR #507: multi-tag-input для фильтров направление / узел / приоритет.
   Поведение: chips слева, текстовый input для type-ahead справа,
   dropdown с подсказками absolute под контейнером. */
.rules-filter__multi {
    position: relative;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    min-width: 160px;
    max-width: 320px;
    padding: 3px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
}
.rules-filter__multi:focus-within {
    border-color: #7c3aed;
    box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);
}
.rules-filter__multi--disabled {
    opacity: 0.5;
    cursor: not-allowed;
    background: #f5f5f5;
}
.rules-filter__multi--disabled .rules-filter__multi-input {
    cursor: not-allowed;
}
.rules-filter__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
}
.rules-filter__chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 2px 5px;
    background: #ede9fe;
    border: 1px solid #c4b5fd;
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.3;
    color: #4c1d95;
    max-width: 140px;
}
.rules-filter__chip-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rules-filter__chip-x {
    border: none;
    background: transparent;
    color: var(--color-busy-text);
    cursor: pointer;
    padding: 0 2px;
    font-size: 10px;
    line-height: 1;
}
.rules-filter__chip-x:hover { color: #be123c; }
.rules-filter__multi-input {
    flex: 1 1 60px;
    min-width: 60px;
    border: none;
    outline: none;
    background: transparent;
    font-size: 10px;
    padding: 3px 2px;
}
.rules-filter__dropdown {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 50;
    margin: 0;
    padding: 4px 0;
    list-style: none;
    max-height: 240px;
    overflow-y: auto;
    background: #ffffff;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
    min-width: 100%;
    width: max-content;
    max-width: 320px;
}
.rules-filter__dropdown li {
    padding: 5px 5px;
    font-size: 10px;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.rules-filter__dropdown li:hover {
    background: #ede9fe;
    color: #4c1d95;
}

/* JUMP-правила: вместо action_prompt показываем target_node чётко
   с большой стрелкой и кодом узла. Action_prompt опционально в
   collapsed details. */
.rule-card__jump-target {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 5px;
    border: 1.5px dashed #be123c;
    border-radius: 3px;
    background: #fff5f5;
}
.rule-card__jump-arrow {
    color: #be123c;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
}
.rule-card__jump-node {
    padding: 4px 5px;
    border-radius: 999px;
    background: #fff;
    border: 1px solid #be123c;
    color: #be123c;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    font-weight: 700;
}
.rule-card__jump-meta {
    color: var(--color-text-muted);
    font-size: 10px;
}
.rule-card__jump-empty {
    color: var(--color-text-muted);
    font-style: italic;
}

/* PR #494: reason-плашка рядом с jump-target. Юзер: «в правилах
   надо рядом с jump выводить reason. сделай красиво». */
.rule-card__jump-reason {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 6px;
    padding: 5px 5px;
    border-left: 3px solid #be123c;
    background: #fff5f5;
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.45;
}
.rule-card__jump-reason-lbl {
    font-weight: 700;
    color: #9f1239;
    flex-shrink: 0;
}
.rule-card__jump-reason-val {
    color: #1f2937;
    white-space: pre-wrap;
    word-break: break-word;
}
.rule-card__jump-reason-auto {
    color: #6b7280;
    font-style: italic;
}
.rule-card__jump-extra {
    margin-top: 6px;
}
.rule-card__jump-extra > summary {
    cursor: pointer;
    color: var(--color-text-muted);
    font-size: 11.5px;
}

.rule-card__actions {
    margin-top: var(--space-md);
    padding-top: 5px;
    border-top: 1px solid var(--color-border);
    display: flex;
    gap: var(--space-xs);
    flex-wrap: wrap;
}

.rules-btn {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-card);
    color: var(--color-text);
    font-size: 10px;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
}

.rules-btn:hover {
    background: var(--color-bg);
}

.rules-btn--save {
    background: var(--color-accent);
    color: #fff;
    border-color: var(--color-accent);
}

.rules-btn--save:hover {
    filter: brightness(1.05);
    background: var(--color-accent);
}

.rules-btn--toggle {
    background: var(--color-card);
}

.rules-btn--edit {
    background: var(--color-card);
}

.rules-btn--clone {
    background: var(--color-card);
    color: #4a3a8c;
    border-color: #d6cdf2;
}
.rules-btn--clone:hover {
    background: #f3f0ff;
    border-color: #b08bff;
}

.rules-btn--delete {
    color: var(--color-danger);
    margin-left: auto;
}

.rules-btn--delete:hover {
    background: rgba(204, 51, 51, 0.08);
    border-color: var(--color-danger);
}

/* === Форма создания/редактирования правила === */

.rule-form-page {
    /* PR #426: убран max-width:900px — форма теперь занимает всю
       ширину контейнера, как и /admin/rules список. Это убирает
       пустые «поля» по бокам на широких экранах. Длинные textarea
       и pre-промты получают больше места для редактирования. */
    height: 100%;
    overflow-y: auto;
    padding: 5px var(--space-xl);
}

.rule-form-page__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-lg);
}

.rule-form-page__title {
    margin: 0;
    font-size: 13px;
}

.rule-form-page__back {
    color: var(--color-text-muted);
    text-decoration: none;
    font-size: 10px;
}

.rule-form-page__back:hover {
    color: var(--color-text);
}

.rule-form {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px;
}

.rule-form__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-md);
}

/* PR #438 (rule-form compact): новый layout с rows вместо 2-col grid. */
.rule-form--compact {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.rule-form__row {
    display: flex;
    gap: 10px;
    align-items: flex-end;
    flex-wrap: wrap;
}
.rule-form__row[hidden] { display: none !important; }
/* Top-row: имя (фиксированная ширина flex) + tight-поля справа. */
.rule-form__row--top {
    align-items: flex-end;
}
.rule-form__field--name {
    flex: 1 1 280px;
    min-width: 200px;
}
.rule-form__field--tight {
    flex: 0 0 auto;
    min-width: 130px;
}
.rule-form__field--tight.rule-form__field--checkbox {
    min-width: 0;
    padding-bottom: 5px;
}
/* Multi-selects row — все selects flex-1, минимум 200px. */
.rule-form__row--multiselects {
    align-items: flex-start;
}
.rule-form__row--multiselects > label {
    flex: 1 1 220px;
    min-width: 180px;
}

/* Warning «область везде» — compact банер вместо большой плашки. */
.rule-form__warning {
    padding: 5px 5px;
    background: #fef3c7;
    border-left: 3px solid #d97706;
    border-radius: 3px;
    font-size: 12.5px;
    color: #78350f;
}

/* PR #438: contenteditable редакторы condition_prompt и action_prompt
   с live-подсветкой (на blur fetch-render). Тот же UX-pattern что
   у inline node editors. PR #441: фон БЕЛЫЙ когда нет фокуса,
   горчично-жёлтый только под фокусом (focus → edit → save). */
.rule-form__editor {
    box-sizing: border-box;
    width: 100%;
    min-height: 70px;
    max-height: 320px;
    overflow-y: auto;
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: #ffffff;
    color: var(--color-text);
    font-family: var(--font-mono, ui-monospace, monospace);
    font-size: 10px;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
    cursor: text;
    outline: 0 solid transparent;
    transition: background-color 120ms, outline-color 120ms;
}
.rule-form__editor--tall {
    min-height: 200px;
    max-height: 60vh;
}
.rule-form__editor.is-editing,
.rule-form__editor:focus {
    background-color: #fef3c7 !important;
    outline: 2px solid #f59e0b;
    outline-offset: -1px;
}

/* PR #441: ЕСЛИ + ТО side-by-side 50/50. Flex даёт автоматическое
   distribution: когда одна колонка hidden, другая занимает 100%. */
.rule-form__split {
    display: flex;
    gap: 12px;
    align-items: stretch;
}
.rule-form__split-col {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.rule-form__split-col[hidden] { display: none; }
.rule-form__split-col--then .rule-form__action-kind {
    margin: 0;
}
.rule-form__then-content {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.rule-form__then-content[hidden] { display: none; }
@media (max-width: 900px) {
    .rule-form__split {
        flex-direction: column;
    }
}

/* PR #439: focus state для contenteditable в gate-модале — тот же
   горчично-жёлтый паттерн что у inline node editors / rule-form. */
.scripts-gate-editor.is-editing,
.scripts-gate-editor:focus {
    background-color: #fef3c7 !important;
    outline: 2px solid #f59e0b !important;
    outline-offset: -1px;
}

.rule-form__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.rule-form__field--full {
    grid-column: 1 / -1;
}

.rule-form__field--checkbox {
    flex-direction: row;
    align-items: center;
    gap: var(--space-xs);
    margin: var(--space-xs) 0;
}

.rule-form__label {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.rule-form__hint {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: none;
    letter-spacing: 0;
    font-weight: normal;
}

.rule-form__required {
    color: var(--color-danger);
}

/* Phase 4: радиогруппа Действие (inject / jump_to_node). */
.rule-form__action-kind {
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px 5px;
    background: var(--color-bg);
}
.rule-form__action-kind > legend {
    padding: 0 5px;
}
.rule-form__radio {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-right: 16px;
    font-size: 10px;
    cursor: pointer;
}
.rule-form__radio input[type="radio"] {
    margin: 0;
}

.rule-form__input,
.rule-form__textarea {
    padding: 5px 5px;
    /* В тему: белый фон + лавандовый бордюр, как у всех контролов. */
    border: 1px solid var(--control-border);
    border-radius: 3px;
    background: var(--control-bg);
    color: var(--color-text);
    font-size: 10px;
    font-family: inherit;
}

.rule-form__input:focus,
.rule-form__textarea:focus {
    outline: none;
    border-color: var(--control-focus);
    box-shadow: 0 0 0 2px var(--control-focus-ring);
}

/* Выбранные пункты в native multi-select листбоксах — в лавандовый,
   вместо системного серо-синего хайлайта. */
.rule-form__input--multi option:checked {
    background: var(--dropdown-hover-bg);
    color: var(--dropdown-hover-text);
}

.rule-form__textarea {
    resize: vertical;
    line-height: 1.5;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}

/* PR #531: универсальная кнопка «× очистить» для всех native <select multiple>.
   Вставляется JS'ом (multiselect_clear.js) ПОСЛЕ селекта. Не привязана к
   rule-form — работает на любой странице где есть `<select multiple>`. */
.multiselect-clear-btn {
    display: inline-block;
    margin-top: 4px;
    padding: 2px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface, #ffffff);
    color: var(--color-text-muted, #6b7280);
    font-family: inherit;
    font-size: 10px;
    line-height: 1.4;
    cursor: pointer;
    transition: background 120ms, color 120ms, border-color 120ms;
}
.multiselect-clear-btn:hover:not(.is-disabled):not(:disabled) {
    background: #fef2f2;
    border-color: #fecaca;
    color: #b91c1c;
}
.multiselect-clear-btn.is-disabled,
.multiselect-clear-btn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.rule-form__actions {
    margin-top: var(--space-lg);
    padding-top: 5px;
    border-top: 1px solid var(--color-border);
    display: flex;
    justify-content: flex-end;
    gap: var(--space-xs);
}

/* PR #461: tag-picker с автокомплитом (мульти-выбор из universe).
   Используется для key_specs и для каждой ячейки upsell-таблицы. */
.tag-picker {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
}
.tag-picker--inline {
    padding: 5px 5px;
}
.tag-picker__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    min-height: 0;
}
.tag-picker__chips:empty {
    display: none;
}
.tag-picker__chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 4px 2px 5px;
    background: #ede9fe;
    color: #4c1d95;
    border: 1px solid #c4b5fd;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 500;
    line-height: 1.4;
}
.tag-picker__chip-label {
    white-space: nowrap;
}
.tag-picker__chip-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    background: rgba(76, 29, 149, 0.12);
    color: #4c1d95;
    border: 0;
    border-radius: 50%;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.tag-picker__chip-remove:hover {
    background: #4c1d95;
    color: #fff;
}
.tag-picker__input-wrap {
    position: relative;
}
.tag-picker__input {
    width: 100%;
    padding: 4px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: #fff;
    font-size: 12.5px;
    line-height: 1.5;
}
.tag-picker__input:focus {
    outline: 2px solid #a78bfa;
    outline-offset: -1px;
    border-color: #a78bfa;
}
.tag-picker__suggestions {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 2px 0 0;
    padding: 4px 0;
    list-style: none;
    background: #fff;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);
    max-height: 240px;
    overflow-y: auto;
    z-index: 50;
}
.tag-picker__suggestion {
    padding: 4px 5px;
    cursor: pointer;
    font-size: 12.5px;
    color: var(--color-text);
}
.tag-picker__suggestion:hover {
    background: #ede9fe;
    color: #4c1d95;
}

/* PR #460: таблица апселл-мэтча на /admin/directions/{id}/edit. */
.direction-upsell-table {
    width: 100%;
    margin-top: 8px;
    border-collapse: collapse;
    font-size: 12.5px;
}
.direction-upsell-table thead th {
    text-align: left;
    padding: 5px 5px;
    background: #f1f5f9;
    color: var(--color-text-muted);
    font-weight: 600;
    border-bottom: 1px solid var(--color-border);
}
.direction-upsell-table tbody td {
    padding: 5px 5px;
    border-bottom: 1px solid #f1f5f9;
    vertical-align: middle;
}
.direction-upsell-table tbody td:first-child {
    width: 25%;
    min-width: 140px;
}
.direction-upsell-table__good {
    display: inline-block;
    padding: 2px 5px;
    background: #fef3c7;
    color: #78350f;
    border: 1px solid #fcd34d;
    border-radius: 3px;
    font-family: var(--font-mono, monospace);
    font-weight: 600;
}

/* ========================================================================
 *  Вкладка «Каталог» — Phase 1.8d (CRUD)
 * ====================================================================== */

/* Toolbar каталога — локальные правки (scope: #catalog-filters).
   База .rules-filter — grid в 2 колонки (controls | auto), но у каталога
   Прямые дети: controls + «Все в Avito» + статус + «+ Новая» (шестерёнка
   переехала в первую sticky-колонку шапки таблицы). Колонок ровно по числу
   детей — перенос на вторую строку запрещён, всё в один ряд. */
#catalog-filters {
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    align-items: center;
}
#catalog-filters .rules-filter__search {
    /* В 2 раза короче (было flex: 1 1 200px) — не растягивается на весь ряд. */
    flex: 0 1 140px;
    min-width: 120px;
    max-width: 220px;
}
#catalog-filters .rules-filter__new {
    flex-shrink: 0;
    white-space: nowrap;
    align-self: center;
}

.catalog-page,
.catalog-form-page {
    height: 100%;
    overflow-y: auto;
    padding: 5px var(--space-xl);
    max-width: 1200px;
    margin: 0 auto;
}

.catalog-page__header,
.catalog-form-page__header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.catalog-page__title,
.catalog-form-page__title {
    margin: 0 0 var(--space-xs) 0;
    font-size: 13px;
}

.catalog-page__hint {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.5;
    max-width: 700px;
}

.catalog-page__hint code {
    background: var(--color-bg);
    padding: 1px 5px;
    border-radius: 3px;
}

.catalog-page__count {
    margin-top: var(--space-sm);
    font-size: 10px;
    color: var(--color-text-muted);
}

.catalog-form-page__back {
    color: var(--color-text-muted);
    text-decoration: none;
    font-size: 10px;
    align-self: flex-end;
}

/* === Фильтры === */
.catalog-filters {
    display: flex;
    gap: var(--space-sm);
    align-items: flex-end;
    flex-wrap: wrap;
    padding: 5px;
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    margin-bottom: var(--space-md);
}

.catalog-filters__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 150px;
}

.catalog-filters__field--checkbox {
    flex-direction: row;
    align-items: center;
    min-width: auto;
    gap: var(--space-xs);
}

.catalog-filters__field > span {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.catalog-filters__input {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    font-size: 10px;
}

.catalog-filters__actions {
    display: flex;
    gap: var(--space-xs);
}

/* PR #518: compact-checkbox в filter-bar'е (catalog/etc). */
.catalog-filters__checkbox {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    color: var(--color-text-muted);
    white-space: nowrap;
    padding: 0 5px;
}
.catalog-filters__checkbox input { margin: 0; }

/* === Таблица моделей === */

/* Обёртка нужна для горизонтального скролла. Колонка действий
   зафиксирована справа через position:sticky — даже когда таблица
   уехала вбок, кнопки edit/delete всегда под рукой. */
.catalog-table-wrap {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    overflow-x: auto;
    overflow-y: hidden;
}

.catalog-table {
    width: 100%;
    min-width: 1100px;
    border-collapse: collapse;
}

/* Компактная версия — пониже строки и шрифт. */
.catalog-table--compact th,
.catalog-table--compact td {
    padding: 5px 5px;
    font-size: 10px;
}

.catalog-table thead {
    background: var(--color-bg);
}

.catalog-table th {
    text-align: left;
    padding: var(--space-xs) 5px;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border-bottom: 1px solid var(--color-border);
    white-space: nowrap;
}

.catalog-table__col-name {
    min-width: 240px;
}

.catalog-table__col-specs {
    min-width: 280px;
    max-width: 320px;
}

.catalog-table__num {
    text-align: right !important;
    width: 88px;
    white-space: nowrap;
}

.catalog-table__center {
    text-align: center !important;
    width: 60px;
}

.catalog-table__col-actions {
    width: 72px;
    text-align: right !important;
    white-space: nowrap;
    /* Sticky правая колонка — остаётся видимой при горизонтальной прокрутке. */
    position: sticky;
    right: 0;
    background: var(--color-card);
    box-shadow: -6px 0 8px -6px rgba(0, 0, 0, 0.08);
}

.catalog-table thead .catalog-table__col-actions {
    background: var(--color-bg);
}

.catalog-row td {
    padding: 5px 5px;
    /* PR #545: более явная граница между строками — slate-300 вместо
       бледного var(--color-border). Унифицировано со стилем
       .rule-card/.tool-card (PR #522 / #526). */
    border-bottom: 1px solid #cbd5e1;
    vertical-align: top;
    font-size: 10px;
}

.catalog-row:last-child td {
    border-bottom: 0;
}

.catalog-row--off {
    opacity: 0.55;
}

.catalog-cell-name__link {
    color: var(--color-text);
    font-weight: 500;
    text-decoration: none;
}

.catalog-cell-name__link:hover {
    color: var(--color-accent);
}

/* === Фото каталога: миниатюра в списке + менеджер фото в форме =========
   Локальные per-компонент классы (без правки глобальных токенов). */
.catalog-cell-thumb {
    float: left;
    width: 34px;
    height: 34px;
    margin: 1px 8px 1px 0;
    border-radius: 5px;
    /* contain — фото целиком в превью, без обрезки */
    object-fit: contain;
    border: 1px solid var(--color-border);
    background: var(--color-card);
}

/* --- Фото-менеджер в форме модели --- */
.catalog-photos__bar {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin: var(--space-xs) 0;
    flex-wrap: wrap;
}

.catalog-photos__btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 11px;
    border-radius: 7px;
    border: 1px solid var(--color-border);
    background: var(--control-bg);
    font-size: 12px;
    color: var(--color-text);
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
}

.catalog-photos__btn:hover { background: var(--dropdown-hover-bg); border-color: var(--control-border); }
.catalog-photos__btn--enrich { color: var(--dropdown-hover-text); border-color: var(--control-border); background: var(--dropdown-hover-bg); }
.catalog-photos__btn--enrich:hover { background: var(--control-border); color: #fff; }
.catalog-photos__btn:disabled { opacity: 0.5; cursor: default; }

.catalog-photos__status {
    font-size: 11px;
    color: var(--color-text-muted);
}
.catalog-photos__status.is-ok { color: var(--color-success-text); }
.catalog-photos__status.is-error { color: var(--color-danger); }
.catalog-photos__status.is-busy { color: var(--color-busy-text); }

/* Карточная сетка фото (вместо разреженной таблицы): компактные плитки
   surface-фона с бордером темы; ужимается/растягивается по ширине формы. */
.catalog-photos__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 8px;
    margin-top: var(--space-xs);
}

.catalog-photos__item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 26px 6px 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    min-width: 0; /* даёт ID-колонке ужиматься с ellipsis */
}

.catalog-photos__thumb {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: 6px;
    /* contain — фото вмещается в превью целиком, без обрезки */
    object-fit: contain;
    border: 1px solid var(--color-border);
    background: var(--color-card);
    cursor: zoom-in;
}

.catalog-photos__meta {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Лайтбокс: клик по миниатюре фото модели — увеличение на весь экран. */
.catalog-lightbox {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.78);
    cursor: zoom-out;
}
.catalog-lightbox__img {
    max-width: 92vw;
    max-height: 92vh;
    border-radius: 8px;
    box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
    background: #fff;
}

.catalog-photos__noimg {
    flex: 0 0 auto;
    display: inline-block;
    width: 56px;
    height: 56px;
    line-height: 56px;
    text-align: center;
    font-size: 9px;
    color: var(--color-text-muted);
    background: var(--color-card);
    border: 1px dashed var(--color-border);
    border-radius: 6px;
}

/* ID — моноширинный, в одну строку с ellipsis (полный — в title). */
.catalog-photos__id {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11px;
    color: var(--color-text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Бейдж «не загружено в Avito» — warn-цвет темы на мягком фоне. */
.catalog-photos__pending {
    align-self: flex-start;
    font-size: 10px;
    line-height: 1;
    padding: 3px 7px;
    border-radius: 999px;
    color: var(--color-warn-text);
    background: var(--color-status-new);
}

.catalog-photos__del {
    position: absolute;
    top: 2px;
    right: 4px;
    border: none;
    background: transparent;
    color: var(--color-text-muted);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
    padding: 2px 4px;
    border-radius: 5px;
}
.catalog-photos__del:hover { color: var(--color-danger); background: var(--color-accent-bg); }

.catalog-photos__empty {
    font-size: 12px;
    color: var(--color-text-muted);
    margin: var(--space-xs) 0 0;
}

.catalog-enrich-all__status {
    font-size: 11px;
    color: var(--color-text-muted);
    margin-left: 4px;
}
.catalog-enrich-all__status.is-ok { color: var(--color-success-text); }
.catalog-enrich-all__status.is-error { color: var(--color-danger); }
.catalog-enrich-all__status.is-busy { color: var(--color-busy-text); }

.catalog-cell-name__meta {
    margin-top: 2px;
    font-size: 10px;
    color: var(--color-text-muted);
}

.catalog-cell-name__meta code {
    background: var(--color-bg);
    padding: 1px 4px;
    border-radius: 3px;
    font-size: 10px;
}

.catalog-cell-specs__pre {
    margin: 0;
    padding: 0;
    background: transparent;
    border: 0;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    line-height: 1.4;
    color: var(--color-text);
    white-space: pre-wrap;
    word-wrap: break-word;
}

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

/* PR #544: inline-edit ячейки на /admin/catalog (name / slug / specs_text).
   Cream-фон + светлая рамка — визуальный hint «можно править», зеркало
   .tool-card__desc / .profile-card__inline-text. */
.catalog-inline-edit {
    cursor: text;
    border: 1px solid #f5edd0;
    border-radius: 3px;
    padding: 1px 5px;
    background: #fffdf2;
    transition: background 120ms, border-color 120ms;
}
.catalog-inline-edit:hover {
    background: #fffaea;
    border-color: #e8d68a;
}
.catalog-inline-edit.is-editing {
    background: #fef9c3;
    border-color: #fde047;
    outline: none;
}
.catalog-inline-edit--slug {
    /* slug — мелкий monospace; padding меньше. */
    padding: 0 4px;
}
.catalog-inline-edit--pre {
    /* specs_text — multi-line, сохраняем переносы. */
    white-space: pre-wrap;
    min-height: 1.4em;
}
/* PR #545: inline-edit ячейки цены (число) + отдельный ₽-маркер
   (не редактируется). */
.catalog-inline-edit--price {
    display: inline-block;
    min-width: 40px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.catalog-cell-price {
    white-space: nowrap;
}
.catalog-cell-price__unit {
    margin-left: 2px;
    color: var(--color-text-muted);
}
.catalog-inline-edit--pre:empty::before {
    content: attr(data-placeholder);
    color: var(--color-text-muted);
    font-style: italic;
}

.catalog-cell-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    max-width: 240px;
}

/* PR #544: tag-picker в ячейке tags. Использует .rules-filter__multi
   структуру, но компактнее (без рамки, chips inline с input). */
/* PR #546: убраны рамка и фон picker'а — раньше border создавал «пустой
   прямоугольник» с воздухом между chips и input ниже. display:contents
   на .rules-filter__chips делает chips прямыми flex-детьми picker'а →
   chips + «+ тег…» input flow одной линией и переносятся ВМЕСТЕ. */
.catalog-tags-picker {
    width: 100%;
    min-width: 0;
    padding: 0;
    border: 0;
    background: transparent;
    min-height: auto;
    /* Чёткий gap между chips, как в исходном .rules-filter__multi. */
    gap: 3px;
}
.catalog-tags-picker .rules-filter__chips {
    display: contents;
}
.catalog-tags-picker .rules-filter__multi-input {
    font-size: 10px;
    min-width: 50px;
    flex: 0 1 auto;
    width: 60px;  /* compact «+ тег…» placeholder */
    padding: 1px 2px;
    color: var(--color-text-muted);
}
.catalog-tags-picker .rules-filter__multi-input:focus {
    color: var(--color-text);
    width: 100px;
}
.catalog-tags-picker.catalog-tags-picker--saving {
    opacity: 0.7;
    pointer-events: none;
}

/* Locked chip (good_class auto) — серый фон, без × кнопки. */
.rules-filter__chip--locked {
    background: #e5e7eb;
    color: #4b5563;
    cursor: not-allowed;
}

/* PR #544: ЭКЗ. колонка — total сверху, Москва/СПб ВСЕГДА показаны. */
.catalog-loc-total {
    font-weight: 700;
}

.catalog-tag {
    background: var(--color-bg);
    color: var(--color-text-muted);
    padding: 0 5px;
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.4;
}

/* PR #509: класс товара в колонке таблицы. auto — фиолетовый pill
   (как chip-фильтра), custom — янтарный (отличить override). */
.catalog-cell-good-class .catalog-tag {
    background: #ede9fe;
    color: #4c1d95;
    border: 1px solid #c4b5fd;
    padding: 1px 5px;
    font-size: 10px;
    font-weight: 500;
}
.catalog-cell-good-class .catalog-tag--custom {
    background: #fef3c7;
    color: #92400e;
    border-color: #fcd34d;
}

/* PR #509: блок выбора режима класса товара в форме редактирования. */
.catalog-form__good-class {
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px 5px;
    background: var(--color-bg);
}
.catalog-form__good-class legend {
    padding: 0 5px;
    font-size: 10px;
    color: var(--color-text-muted);
}
.catalog-form__inline-choice {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 4px 0;
    font-size: 10px;
}
.catalog-form__inline-choice .catalog-form__hint {
    margin-left: 2px;
}
.catalog-form__input--muted {
    opacity: 0.55;
    background: #f8fafc;
}

/* === Иконки edit/delete в sticky колонке === */
.catalog-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-card);
    color: var(--color-text);
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    margin-left: 2px;
}

.catalog-icon-btn:first-child {
    margin-left: 0;
}

.catalog-icon-btn:hover {
    background: var(--color-bg);
}

.catalog-icon-btn--delete {
    color: var(--color-danger);
}

.catalog-icon-btn--delete:hover {
    background: rgba(204, 51, 51, 0.1);
    border-color: var(--color-danger);
}

.catalog-empty {
    padding: var(--space-xl);
    text-align: center;
    color: var(--color-text-muted);
    background: var(--color-card);
    border: 1px dashed var(--color-border);
    border-radius: 3px;
}

/* PR-376vvv: разбивка кол-ва инстансов по локациям. Показывается
   когда у модели инстансы в разных городах (МСК + СПб). */
.catalog-loc-breakdown {
    margin-top: 4px;
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    justify-content: flex-end;
}
.catalog-loc-pill {
    padding: 1px 5px;
    border-radius: 3px;
    background: #eef2ff;
    color: #3730a3;
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
}

/* === PR-376ttt: footer пагинации каталога. Generic pattern из
   .dt-footer (диалогов открытых линий). Структурно одинаковый,
   только namespace другой чтоб не было непреднамеренного коллизирования. */
.catalog-footer {
    margin-top: var(--space-md);
    padding: 5px 5px;
    border-top: 1px solid var(--color-border);
    background: var(--color-bg);
    display: flex;
    align-items: center;
    gap: var(--space-md);
    font-size: 10px;
    flex-wrap: nowrap;
    white-space: nowrap;
}
.catalog-footer__pagebox {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    flex: 0 1 auto;
    min-width: 0;
}
.catalog-footer__nav {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}
.catalog-footer__navbtn {
    width: 26px;
    height: 26px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    color: var(--color-text);
    cursor: pointer;
    font-size: 10px;
    line-height: 1;
    text-align: center;
    text-decoration: none;
    /* a-tag и button должны выглядеть одинаково. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.catalog-footer__navbtn:hover:not(:disabled) {
    color: var(--color-accent);
    border-color: var(--color-accent);
}
.catalog-footer__navbtn:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.catalog-footer__pagenum {
    margin: 0 6px;
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}
.catalog-footer__pagesize {
    display: flex;
    align-items: center;
    gap: 6px;
    color: var(--color-text-muted);
}
.catalog-footer__counter {
    color: var(--color-text-muted);
    font-variant-numeric: tabular-nums;
}
.catalog-filters__input--narrow {
    width: 64px;
}

/* === Кнопки (общие для catalog) === */
.catalog-btn {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-card);
    color: var(--color-text);
    font-size: 10px;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
    margin-left: 4px;
}

.catalog-btn:first-child {
    margin-left: 0;
}

.catalog-btn:hover {
    background: var(--color-bg);
}

.catalog-btn--save {
    background: var(--color-accent);
    color: #fff;
    border-color: var(--color-accent);
}

.catalog-btn--save:hover {
    filter: brightness(1.05);
    background: var(--color-accent);
}

.catalog-btn--toggle {
    width: 36px;
    text-align: center;
}

.catalog-btn--delete {
    color: var(--color-danger);
}

.catalog-btn--delete:hover {
    background: rgba(204, 51, 51, 0.08);
    border-color: var(--color-danger);
}

/* === Форма create/edit (компактная) === */
.catalog-form {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px;
    margin-bottom: var(--space-md);
}

.catalog-form__grid {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr 1fr;
    gap: var(--space-xs) var(--space-sm);
}

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

.catalog-form__field--full {
    grid-column: 1 / -1;
}

.catalog-form__field--checkbox {
    flex-direction: row;
    align-items: center;
    gap: var(--space-xs);
}

.catalog-form__label {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.catalog-form__hint {
    font-size: 10px;
    color: var(--color-text-muted);
    text-transform: none;
    letter-spacing: 0;
    font-weight: normal;
}

.catalog-form__required {
    color: var(--color-danger);
}

.catalog-form__input,
.catalog-form__textarea {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    font-size: 10px;
    font-family: inherit;
}

.catalog-form__textarea {
    resize: vertical;
    line-height: 1.45;
}

.catalog-form__textarea--specs {
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}

.catalog-form__actions {
    margin-top: var(--space-md);
    padding-top: 5px;
    border-top: 1px solid var(--color-border);
    display: flex;
    justify-content: flex-end;
    gap: var(--space-xs);
}

/* === Панель экземпляров (компактная) === */
.catalog-instances {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px;
}

.catalog-instances__header {
    margin-bottom: var(--space-sm);
}

.catalog-instances__title {
    margin: 0 0 2px 0;
    font-size: 13px;
}

.catalog-instances__hint {
    margin: 0;
    font-size: 10px;
    color: var(--color-text-muted);
}

.catalog-instances__hint code {
    background: var(--color-bg);
    padding: 1px 4px;
    border-radius: 3px;
}

.catalog-instances__table {
    width: 100%;
    border-collapse: collapse;
    /* PR #510: fixed table-layout — без него width на th не работает
       стабильно (браузер пересчитывает по content). */
    table-layout: fixed;
}

.catalog-instances__table th {
    text-align: left;
    padding: 4px 5px;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
    border-bottom: 1px solid var(--color-border);
    /* relative для absolute-позиционирования drag-handle справа. */
    position: relative;
}

.catalog-instances__center {
    text-align: center !important;
    width: 60px;
}

/* PR #510: дефолтные ширины колонок инстансов. Меняется resizer'ом
   и сохраняется в localStorage. */
.catalog-instances__table th[data-key="bx_id"]       { width: 110px; }
.catalog-instances__table th[data-key="internal_id"] { width: 110px; }
.catalog-instances__table th[data-key="label"]       { width: auto; min-width: 240px; }
.catalog-instances__table th[data-key="location"]    { width: 130px; }
.catalog-instances__table th[data-key="active"]      { width: 70px; }
.catalog-instances__table th[data-key="actions"]     { width: 110px; }
.catalog-instances__table td input.catalog-form__input {
    width: 100%;
    box-sizing: border-box;
}

/* PR #510: универсальный column-resizer handle. */
.col-resizable-table th {
    position: relative;
    user-select: none;
}
.col-resize-handle {
    position: absolute;
    top: 0;
    right: 0;
    width: 6px;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    z-index: 2;
}
.col-resize-handle::after {
    content: '';
    position: absolute;
    top: 25%;
    bottom: 25%;
    right: 2px;
    width: 2px;
    background: transparent;
    transition: background 0.12s;
}
.col-resize-handle:hover::after,
body.col-resizing .col-resize-handle::after {
    background: #7c3aed;
}
body.col-resizing {
    cursor: col-resize !important;
}
body.col-resizing * {
    user-select: none;
}

/* === TableView — переиспользуемый компонент таблиц (P198, #445) =========
   Декларативно навешивается на <table data-table-view>. Возможности:
   resize / hide / sort / reorder колонок + видимые границы столбцов.
   См. app/static/js/table_view.js + docs/design/tableview.md. */

/* Toolbar над таблицей с шестерёнкой (если не используется существующая
   панель через data-tv-gear). Переиспользует стили .dt-cols-popup. */
.tableview-toolbar {
    display: flex;
    justify-content: flex-end;
    padding: 4px 0;
}

/* Видимые вертикальные границы столбцов — без них соседние ячейки
   сливаются, не видно где конец колонки (skill analytics-tables-ui). */
.tableview th,
.tableview td {
    border-right: 1px solid var(--color-border);
}
.tableview tr > th:last-child,
.tableview tr > td:last-child {
    border-right: none;
}

/* Управляемый заголовок — relative-якорь для абсолютной resize-ручки,
   место под ручку ⋮⋮ слева и стрелку сортировки справа. */
.tableview__th {
    position: relative;
}
.tableview__th--sortable {
    cursor: pointer;
    user-select: none;
}

/* Ручка переноса колонки. Видимая (не opacity:0.35 — skill: невидимые
   affordances плохи), на hover подсвечивается акцентом. */
.tableview__grip {
    cursor: grab;
    /* Контрастнее — drag-зона должна быть явно видна (запрос юзера:
       «dragndrop capture zone почти не виден»). */
    color: #6b7280;
    font-weight: 700;
    letter-spacing: -2px;
    margin-right: 6px;
    user-select: none;
    vertical-align: middle;
}
.tableview__grip:hover {
    color: var(--color-accent);
}

/* Стрелка сортировки у правого края (после места под resize-ручку). */
.tableview__arrow {
    margin-left: 4px;
    font-size: 10px;
    color: #8a90a0;
}
.tableview__th[data-tv-sorted] .tableview__arrow {
    color: var(--color-accent);
    font-weight: 700;
}

/* Resize-ручка — полоса 6px у правой границы, курсор col-resize. */
.tableview__resize {
    position: absolute;
    top: 0;
    right: 0;
    width: 6px;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    z-index: 3;
}
.tableview__resize::after {
    content: '';
    position: absolute;
    top: 20%;
    bottom: 20%;
    right: 2px;
    width: 2px;
    background: transparent;
    transition: background 0.12s;
}
.tableview__resize:hover::after,
body.tableview-resizing .tableview__resize::after {
    background: var(--color-accent);
}
body.tableview-resizing {
    cursor: col-resize !important;
}
body.tableview-resizing * {
    user-select: none !important;
}

/* Перетаскиваемый заголовок + индикатор цели drop'а. */
.tableview__th--drag {
    opacity: 0.55;
}
.tableview__th--drop {
    box-shadow: inset 3px 0 0 0 var(--color-accent);
}

.catalog-instances__table td {
    padding: 3px 5px;
    border-bottom: 1px solid var(--color-border);
}

.catalog-instances__table td input.catalog-form__input {
    padding: 3px 5px;
    font-size: 10px;
}

.catalog-instance-row--new {
    background: rgba(0, 0, 0, 0.02);
}

.catalog-instance-row--new td {
    border-bottom: 0;
}

/* === Вкладка scripts: read-only граф scripts.graph =================== */

.scripts-page {
    height: 100%;
    overflow: auto;
    /* #391: padding-top СТРОГО 0 — это scroll-контейнер с закреплённой
       (position:sticky; top:0) панелью .scripts-graph-hint--sticky. Любой
       padding-top тут = щель: sticky-элемент липнет к верху content-box'а,
       т.е. на padding-top НИЖЕ верхней кромки скроллпорта, и в этот зазор
       видно прокручиваемый контент. Боковые/нижний отступы безопасны. */
    padding: 0 3px 5px;
    background: var(--color-bg);
}

.scripts-page__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    min-height: 30px;
    margin-bottom: 8px;
}

.scripts-page__title {
    margin: 0;
    font-size: 13px;
    line-height: 1.2;
}

.scripts-page__hint {
    max-width: 820px;
    margin: 0;
    color: var(--color-text-muted);
}

.scripts-page__hint code,
.scripts-toolbar__note code,
.scripts-empty code,
.scripts-error code,
.scripts-panel__empty code {
    padding: 1px 4px;
    border-radius: 3px;
    background: var(--color-surface);
}

.scripts-page__status {
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    flex-shrink: 0;
    color: var(--color-text-muted);
    font-size: 10px;
}

.scripts-status,
.scripts-pill {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 20px;
    padding: 2px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
}

.scripts-status--on,
.scripts-pill--ok {
    color: var(--color-status-active-text);
    background: var(--color-status-active);
}

.scripts-status--off,
.scripts-pill--muted {
    color: var(--color-status-muted-text);
    background: var(--color-status-muted);
}

.scripts-toolbar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: 5px 5px;
    margin-bottom: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
}

.scripts-toolbar--graph {
    flex: 1 1 auto;
    min-width: 280px;
    max-width: 620px;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
}

.scripts-toolbar--graph .scripts-toolbar__field {
    min-width: 0;
    width: 100%;
}

.scripts-toolbar__field {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: min(480px, 100%);
}

.scripts-toolbar--graph .scripts-toolbar__field {
    min-width: 0;
    width: 100%;
}

.scripts-toolbar__field span {
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.scripts-toolbar__select {
    min-height: 28px;
    padding: 4px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-bg);
    color: var(--color-text);
    font: inherit;
}

.scripts-toolbar__note {
    color: var(--color-text-muted);
    font-size: 10px;
    text-align: right;
}

.scripts-toolbar--graph {
    flex: 1 1 320px;
    min-width: 260px;
    padding: 0;
    margin: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
}

.scripts-toolbar--graph .scripts-toolbar__field {
    min-width: 0;
    gap: 2px;
}

.scripts-toolbar--graph .scripts-toolbar__field span {
    font-size: 9.5px;
}

.scripts-toolbar--graph .scripts-toolbar__select {
    min-height: 28px;
    padding: 3px 5px;
    font-size: 10px;
}

.scripts-empty,
.scripts-error {
    padding: 5px;
    border: 1px dashed var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    color: var(--color-text-muted);
}

.scripts-error {
    border-color: #f59e0b;
    background: #fffbeb;
    color: #7c2d12;
}

.scripts-error h2 {
    margin: 0 0 var(--space-xs) 0;
    font-size: 13px;
}

.scripts-error p {
    margin: 0 0 var(--space-sm) 0;
}

.scripts-error pre {
    max-height: 320px;
    overflow: auto;
    margin: 0;
    padding: 5px;
    border: 1px solid rgba(245, 158, 11, 0.35);
    border-radius: 3px;
    background: #fff7ed;
    color: #431407;
    font-family: Consolas, "SFMono-Regular", monospace;
    font-size: 10px;
    white-space: pre-wrap;
}

.scripts-summary {
    display: grid;
    grid-template-columns: repeat(4, minmax(120px, 1fr)) minmax(220px, 1.2fr);
    gap: var(--space-sm);
    margin-bottom: var(--space-md);
}

.scripts-metric {
    min-width: 0;
    padding: 5px 5px;
    /* В тему: лавандовый бордюр + белая подложка, карточка читается на
       лавандовой плашке-секции. */
    border: 1px solid var(--control-border);
    border-radius: 3px;
    background: var(--color-surface);
}

.scripts-metric--warn {
    border-color: #f59e0b;
    background: #fffbeb;
}

.scripts-metric__value,
.scripts-metric__label {
    display: block;
    min-width: 0;
}

.scripts-metric__value {
    overflow: hidden;
    color: var(--color-text);
    font-size: 10px;
    font-weight: 700;
    line-height: 1.25;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scripts-metric__label {
    color: var(--color-text-muted);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}

.scripts-legend {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-sm) var(--space-md);
    margin-bottom: var(--space-sm);
    color: var(--color-text-muted);
    font-size: 10px;
}

/* PR #486: badge валидации графа + popover (не модальный).
   Юзер: «зеленый свет если ок, красный если ошибки. подробнее
   открывает попап В ЭТОМ МЕСТЕ с именами узлов и количеством
   ошибок в каждом. попап занимает немного места, и его можно
   закрыть. Он не блокирует интерфейс». */
/* PR #494: яркий tag + светодиод. Юзер: «должен быть весь в
   ярко-зелёном обрамлении (примерно как тег "везде" в правилах)
   и ярко-зелёный светодиод. не пройдена — соответственно красным». */
.scripts-validation-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 700;
    border: 2px solid transparent;
    background: transparent;
    cursor: default;
    /* Светодиод-точка через ::before; цвет переопределяется в --ok/--fail. */
}
.scripts-validation-badge::before {
    content: "";
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: currentColor;
    box-shadow: 0 0 6px currentColor;
}
.scripts-validation-badge--ok,
.scripts-graph-stats .scripts-validation-badge--ok {
    color: var(--color-success-text);          /* green-700 текст */
    background: #bbf7d0;     /* green-200 ярче чем 100 */
    border-color: var(--color-success-text);   /* green-700 контур */
}
.scripts-validation-badge--ok::before {
    background: #22c55e;     /* green-500 — яркий светодиод */
    color: #22c55e;
    box-shadow: 0 0 8px #22c55e;
}
.scripts-validation-badge--fail,
.scripts-graph-stats .scripts-validation-badge--fail {
    color: #b91c1c;          /* red-700 */
    background: #fecaca;     /* red-200 */
    border-color: #b91c1c;
    cursor: pointer;
}
.scripts-validation-badge--fail::before {
    background: #ef4444;     /* red-500 — яркий светодиод */
    color: #ef4444;
    box-shadow: 0 0 8px #ef4444;
}
.scripts-validation-badge--fail:hover {
    background: #fca5a5;
}
.scripts-validation-pop {
    position: relative;
    display: inline-block;
}
.scripts-validation-popover {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 30;
    width: 380px;
    max-height: 360px;
    overflow-y: auto;
    padding: 5px 5px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    background: #ffffff;
    box-shadow: 0 10px 30px rgba(15, 23, 42, 0.18);
    font-size: 10px;
    color: #1f2937;
    text-align: left;
}
.scripts-validation-popover__head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: 700;
    font-size: 12.5px;
    margin-bottom: 8px;
    color: #991b1b;
}
.scripts-validation-popover__close {
    border: 0;
    background: transparent;
    color: #6b7280;
    cursor: pointer;
    font-size: 10px;
    line-height: 1;
    padding: 0 4px;
}
.scripts-validation-popover__close:hover { color: #1f2937; }
.scripts-validation-popover__list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.scripts-validation-popover__row {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    padding: 4px 5px;
    border-radius: 3px;
    background: #fef2f2;
}
.scripts-validation-popover__nid {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    color: #B86E05;
    font-weight: 700;
    font-size: 11.5px;
}
.scripts-validation-popover__label {
    color: #374151;
    font-size: 11.5px;
    flex: 1 1 100%;
}
.scripts-validation-popover__count {
    color: var(--color-danger);
    font-weight: 700;
    font-size: 10px;
    padding: 1px 5px;
    background: #ffffff;
    border-radius: 3px;
}
.scripts-validation-popover__struct {
    color: #92400e;
    font-weight: 700;
    font-size: 10px;
    padding: 1px 5px;
    background: #fef3c7;
    border-radius: 3px;
}
.scripts-validation-popover__other {
    margin-top: 10px;
    padding-top: 5px;
    border-top: 1px solid #e5e7eb;
}
.scripts-validation-popover__other-head {
    font-weight: 700;
    color: #6b7280;
    margin-bottom: 4px;
    font-size: 11.5px;
}
.scripts-validation-popover__other-item {
    color: #374151;
    font-size: 11.5px;
    padding: 2px 0;
}

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

.scripts-legend__line {
    display: inline-block;
    width: 28px;
    border-top: 2px solid #64748b;
}

.scripts-legend__line--switch { border-color: #0f766e; }
/* PR #476: легенда AI-фактчекинг — бордовый, синхрон с edge stroke. */
.scripts-legend__line--llm { border-color: #630633; }
.scripts-legend__line--default { border-color: #d97706; }
.scripts-legend__line--rule {
    border-color: #be123c;
    border-top-style: dashed;
}

.scripts-legend__handle {
    display: inline-block;
    width: 11px;
    height: 11px;
    border: 2px solid #2563eb;
    border-radius: 2px;
    background: #eff6ff;
}

.scripts-graph-shell {
    overflow: hidden;
    margin-bottom: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    display: flex;
    flex-direction: column;
    height: 85vh;
}

.scripts-graph-commandbar {
    display: flex;
    align-items: center;
    /* PR #514: одна строка — Сценарий + select + Сохранить + Загрузить
       + zoom-слайдер. flex-wrap оставлен на случай мелкого экрана. */
    flex-wrap: wrap;
    gap: 6px 10px;
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    background: #f8fafc;
}

/* PR #514: label «Сценарий» слева от select'а в commandbar'е (раньше
   был сверху как column). */
.scripts-graph-commandbar .scripts-toolbar__field {
    flex-direction: row;
    align-items: center;
    gap: 6px;
    flex: 0 1 auto;
    min-width: 0;
}
.scripts-graph-commandbar .scripts-toolbar__field > span {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--color-text-muted);
    white-space: nowrap;
}
.scripts-graph-commandbar .scripts-toolbar--graph {
    flex: 0 1 auto;
}
.scripts-graph-commandbar .scripts-toolbar__select {
    width: auto;
    min-width: 220px;
    max-width: 360px;
}

/* HEAD-версия .scripts-legend--bar: более детальная, поддерживает <b>
   и .is-warn (используется в scripts-graph-compact-legend HTML). */
.scripts-legend--bar {
    flex: 1 1 390px;
    margin: 0;
    gap: 5px 10px;
    font-size: 10px;
    line-height: 1.25;
}

.scripts-legend--bar .scripts-legend__line {
    width: 22px;
}

.scripts-graph-stats {
    display: flex;
    flex: 0 1 auto;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    color: var(--color-text-muted);
    font-size: 10px;
}

.scripts-graph-stats span {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-height: 22px;
    padding: 2px 5px;
    border: 1px solid #dbe3ef;
    border-radius: 999px;
    background: #fff;
    white-space: nowrap;
}

.scripts-graph-stats b {
    max-width: 130px;
    overflow: hidden;
    color: var(--color-text);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scripts-graph-stats .is-warn {
    border-color: #f59e0b;
    background: #fffbeb;
    color: #92400e;
}


/* PR-1 интерактивный редактор: подсказка + viewport-host для pan/drag.
   Старый .scripts-graph-scroll оставлен ниже как fallback (если шаблон
   ещё не обновлён в каком-то контексте). */
/* Однострочная панель над полотном: селектор + кнопки + zoom + бейдж
   валидности в один ряд (flex, nowrap, выровнено по вертикали). */
.scripts-graph-hint {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 6px;
    flex: 0 0 auto;
    padding: 4px 5px;
    border-bottom: 1px solid var(--color-border);
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.5;
    background: #f8fafc;
}

.scripts-graph-hint > span { flex: 1 1 auto; }

/* #391: панель-виджеты закреплена над графом и не уезжает при вертикальном
   скролле страницы. Вынесена из .scripts-graph-shell (overflow:hidden ломает
   sticky) и оформлена самостоятельной карточкой-баром. */
.scripts-graph-hint--sticky {
    position: sticky;
    top: 0;
    z-index: 30;
    margin-bottom: var(--space-sm);
    border: 1px solid var(--color-border);
    border-radius: 3px;
}

/* #391: селектор сценария сужен ~вдвое — не растягивается на всю строку;
   active-бейдж и бейдж валидности — по контенту, всё в одной строке. */
.scripts-graph-hint .scripts-toolbar--graph { flex: 0 1 auto; min-width: 0; }
.scripts-graph-hint .scripts-toolbar--graph .scripts-toolbar__select {
    width: 300px;
    max-width: 100%;
}
.scripts-graph-hint .scripts-page__status--inline { flex: 0 0 auto; }
.scripts-graph-hint .scripts-validity-list { flex: 0 0 auto; margin: 0; }

.scripts-graph-actions {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 3px;
}

.scripts-graph-btn,
.scripts-graph-reset {
    flex: 0 0 auto;
    min-height: 28px;
    padding: 2px 5px;
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #fff;
    color: #475569;
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
    transition: background 120ms, border-color 120ms;
}

.scripts-graph-btn:hover {
    background: #eff6ff;
    border-color: #bfdbfe;
    color: #1d4ed8;
}
.scripts-graph-btn:disabled,
.scripts-graph-btn[disabled] {
    opacity: 0.45;
    cursor: not-allowed;
}
.scripts-graph-btn--primary {
    background: #7c3aed;
    border-color: #7c3aed;
    color: #fff;
}
.scripts-graph-btn--primary:hover {
    background: var(--color-busy-text);
    border-color: var(--color-busy-text);
    color: #fff;
}

/* PR #536: модалки для save-as-version + import collision. Backdrop + центр.
   Скрыты по hidden+display:none; .is-open включает display + анимирует opacity. */
.scripts-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity 150ms;
}
.scripts-modal[hidden] {
    display: none !important;
}
.scripts-modal.is-open {
    opacity: 1;
}
.scripts-modal__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
}
.scripts-modal__panel {
    position: relative;
    z-index: 1;
    width: min(520px, calc(100vw - 32px));
    max-height: calc(100vh - 64px);
    overflow: auto;
    padding: 5px 5px;
    border-radius: 3px;
    background: #ffffff;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.2);
}
.scripts-modal__title {
    margin: 0 0 8px;
    font-size: 13px;
    font-weight: 700;
    color: #0f172a;
}
.scripts-modal__desc {
    margin: 0 0 14px;
    color: #475569;
    font-size: 10px;
    line-height: 1.45;
}
.scripts-modal__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 10px;
    font-size: 10px;
    color: #475569;
}
.scripts-modal__field span {
    font-weight: 600;
}
.scripts-modal__input {
    padding: 5px 5px;
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #fff;
    color: #0f172a;
    font-size: 10px;
    font-family: inherit;
}
.scripts-modal__input[readonly] {
    background: #f1f5f9;
    color: #475569;
}
.scripts-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 14px;
}
.scripts-modal__actions--column {
    flex-direction: column;
}
.scripts-modal__actions--column .scripts-graph-btn {
    width: 100%;
    padding: 5px;
    font-size: 10px;
}

/* Backlog #390: graph-level loop-guard лимит (graph.max_hops). Компактное
   int-поле в .scripts-graph-actions, в одну строку (метка + число), без
   wrap. Стилистика согласована с соседними toolbar-контролами. */
.scripts-graph-maxhops {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    white-space: nowrap;
    padding: 1px 5px;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    background: #f8fafc;
}
.scripts-graph-maxhops__label {
    font-size: 10px;
    font-weight: 600;
    color: #475569;
}
.scripts-graph-maxhops__input {
    width: 56px;
    min-height: 24px;
    padding: 1px 4px;
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #fff;
    color: #1e293b;
    font-size: 10px;
    text-align: center;
}
.scripts-graph-maxhops__input:focus {
    outline: none;
    border-color: #bfdbfe;
    box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.18);
}
.scripts-graph-maxhops__input.is-ok {
    border-color: #86efac;
    background: #f0fdf4;
}
.scripts-graph-maxhops__input.is-error {
    border-color: #fca5a5;
    background: #fef2f2;
}

/* PR #512: zoom-контрол canvas графа. Расположен в .scripts-graph-actions,
   справа от кнопок «Сохранить» / «Загрузить». Управляет CSS-zoom только
   на #scripts-graph-canvas — остальной UI страницы не затрагивается. */
.scripts-graph-zoom {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-left: 3px;
    padding: 1px 3px;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    background: #f8fafc;
}
.scripts-graph-zoom__btn,
.scripts-graph-zoom__reset {
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #fff;
    color: #475569;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.scripts-graph-zoom__btn:hover,
.scripts-graph-zoom__reset:hover {
    background: #ede9fe;
    color: #4c1d95;
    border-color: #c4b5fd;
}
.scripts-graph-zoom__slider {
    width: 130px;
    margin: 0 2px;
    accent-color: #7c3aed;
    cursor: pointer;
}
.scripts-graph-zoom__value {
    min-width: 36px;
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    text-align: right;
    color: #475569;
    font-weight: 600;
}

.scripts-graph-reset:hover {
    background: #fef2f2;
    border-color: #fecaca;
    color: #991b1b;
}

/* Toast-уведомление save/load/edit — всплывающий бейдж в правом верхнем
   углу. position:fixed → вне потока, НЕ сдвигает полотно (0 layout-shift).
   Авто-исчезает по таймеру в JS (toggle [hidden]). info — синий,
   ошибка — красный (--err от scripts_graph.js, .is-error от scripts_graph_editor.js). */
.scripts-graph-toast {
    position: fixed;
    top: 12px;
    right: 12px;
    z-index: 1100;
    max-width: min(420px, calc(100vw - 24px));
    margin: 0;
    padding: 5px 5px;
    border: 1px solid #bfdbfe;
    border-radius: 3px;
    background: #eff6ff;
    color: #1e3a8a;
    font-size: 10px;
    line-height: 1.4;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
    pointer-events: none;
}
.scripts-graph-toast[hidden] {
    display: none !important;
}
.scripts-graph-toast--err,
.scripts-graph-toast.is-error {
    border-color: #fecaca;
    background: #fef2f2;
    color: #991b1b;
}
.scripts-graph-toast--warn {
    border-color: #fde68a;
    background: #fffbeb;
    color: #92400e;
}

.scripts-graph-host {
    /* PR #515: canvas-viewport подстраивается под высоту экрана.
       Раньше было height: 85vh (на 1080 — 918px, обрезалось родителем),
       стало calc(100vh - 220px): 100vh минус суммарная высота
       header'а (49) + nav-shell'а (≈40) + page-header (≈40) +
       commandbar (≈42) + margins/padding (≈50). На viewport 1080 даст
       ~860px полезной высоты canvas'а. min-height — fallback на
       узких экранах. JS resizer (scripts_graph_resizer.js) при drag'е
       поверх ставит inline height — это override'нет calc. */
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    height: auto;
    overflow: hidden;
    cursor: grab;
    /* Полотно — лавандово-серое, заметно темнее белой верхней панели/тулбара
       (чтобы канвас не сливался с ними), сетка — приглушённо-фиолетовая. */
    background:
        linear-gradient(#dfdaf0 1px, transparent 1px) 0 0 / 36px 36px,
        linear-gradient(90deg, #dfdaf0 1px, transparent 1px) 0 0 / 36px 36px,
        #eceaf6;
    user-select: none;
}

/* PR #437: перетаскиваемый разделитель между .scripts-graph-host и
   .scripts-service-panels. cursor: row-resize намекает на drag.
   На hover/active появляется acent-полоска. */
.scripts-graph-resizer {
    position: relative;
    height: 10px;
    margin: 4px 0;
    cursor: row-resize;
    user-select: none;
    /* #391: pointer-drag (setPointerCapture) — гасим браузерные
       тач/scroll-жесты на самой планке, иначе pointermove «съедается». */
    touch-action: none;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    transition: background 120ms;
}
.scripts-graph-resizer::before {
    /* Горизонтальная hairline по центру — визуальный «шов». */
    content: "";
    position: absolute;
    left: 0; right: 0;
    top: 50%;
    height: 1px;
    background: #e2e8f0;
    transform: translateY(-50%);
    transition: background 120ms, height 120ms;
}
.scripts-graph-resizer__grip {
    /* Маленький «грипп» в центре — 32×4 закруглённый прямоугольник.
       Помогает визуально понять что элемент draggable. */
    position: relative;
    width: 32px;
    height: 4px;
    border-radius: 2px;
    background: #cbd5e1;
    transition: background 120ms, width 120ms, height 120ms;
}
.scripts-graph-resizer:hover::before {
    background: #94a3b8;
    height: 2px;
}
.scripts-graph-resizer:hover .scripts-graph-resizer__grip {
    background: #64748b;
    width: 48px;
}
.scripts-graph-resizer.is-dragging {
    background: rgba(99, 102, 241, 0.06);
}
.scripts-graph-resizer.is-dragging::before {
    background: #6366f1;
    height: 2px;
}
.scripts-graph-resizer.is-dragging .scripts-graph-resizer__grip {
    background: #6366f1;
    width: 56px;
}
/* Во время drag вешаем cursor на весь body чтобы курсор не мигал
   при выходе мыши за пределы splitter'а. JS добавляет .is-resizing
   на <body>. */
body.is-resizing-graph,
body.is-resizing-graph * {
    cursor: row-resize !important;
    user-select: none !important;
}

.scripts-graph-host.is-panning { cursor: grabbing; }

/* ВАЖНО: НЕ используем `body * { cursor: grabbing !important }` для pan'а.
   Раньше так делали чтобы курсор не «исчезал» при выходе за viewport,
   но это вызывало другой баг — после endDrag cursor оставался
   ПОЛУПРОЗРАЧНЫМ. Причина: body * затрагивает ВСЕ потомки body
   (тысячи элементов), и при снятии класса Edge compositor залипает в
   странном cursor-state.
   Cursor-state теперь только на host (cursor:grabbing локально) и
   очищается state в JS через ev.buttons===0 detection и safety hooks. */

.scripts-graph__viewport {
    position: absolute;
    top: 0;
    left: 0;
    transform-origin: 0 0;
    will-change: transform;
}

.scripts-graph-scroll {
    max-height: min(64vh, 760px);
    overflow: auto;
}

.scripts-graph {
    position: relative;
}

.scripts-graph__edges {
    position: absolute;
    inset: 0;
    z-index: 1;
    overflow: visible;
    pointer-events: none;
}

.scripts-graph__edges marker path {
    fill: #64748b;
}

.scripts-graph__paths path,
.scripts-graph__labels {
    pointer-events: visiblePainted;
}

.scripts-edge {
    fill: none;
    stroke: #64748b;
    stroke-width: 2.5;
    vector-effect: non-scaling-stroke;
    transition: stroke-width 120ms;
}

.scripts-edge:hover { stroke-width: 4; }

/* Выбранная дуга — сиреневая. !important чтоб перебить kind-specific
   stroke (.scripts-edge--switch, --llm и т.д.). Толще + прозрачный
   halo через filter (мягкое свечение). */
.scripts-edge.is-selected {
    stroke: #a855f7 !important;
    stroke-width: 4 !important;
    filter: drop-shadow(0 0 4px rgba(168, 85, 247, 0.5));
}
/* Выбранные pivot/anchor handles тоже сиреневые. */
.scripts-edge-handle.is-selected {
    fill: #f3e8ff !important;
    stroke: #a855f7 !important;
    stroke-width: 3 !important;
}

/* P166: drag-rebind snap-эффект. Когда юзер тащит dotDst к новому узлу
   и cursor попал в SNAP_THRESHOLD_PX (60px) — дуга становится насыщенно
   сиреневой с halo (сигнал «прилипло, отпусти чтобы перенацелить»). На
   release fire'ится PATCH /admin/scripts/.../edge-target. При оттаскивании
   дальше snap-зоны класс снимается, дуга в phantom-mode (dst follow
   cursor) с обычным цветом. */
.scripts-edge.is-snapping {
    stroke: #a855f7 !important;
    stroke-width: 4 !important;
    filter: drop-shadow(0 0 6px rgba(168, 85, 247, 0.65));
    transition: stroke 80ms, stroke-width 80ms, filter 80ms;
}
.scripts-edge-handle--dst.is-snapping {
    fill: #a855f7 !important;
    stroke: #6b21a8 !important;
    stroke-width: 3 !important;
    transition: fill 80ms, stroke 80ms;
}
/* P166: ошибка PATCH retarget — красная вспышка на дуге (1.2s,
   non-blocking; альтернатива alert()'у который замораживал renderer
   во время drag). */
.scripts-edge.is-snap-fail {
    stroke: var(--color-danger) !important;
    stroke-width: 4 !important;
    filter: drop-shadow(0 0 4px rgba(220, 38, 38, 0.55));
}

.scripts-edge--switch { stroke: #0f766e; }
/* PR #476: ветви LLMSwitch — бордовый (#630633), согласован с
   .prompt-branch (PR #470). Юзер: «синий цвет ветвей поменяй на
   бордовый». Раньше — синий (#2563eb), визуально путалось с blue
   facts/AI-reply узлами. */
.scripts-edge--llm { stroke: #630633; }
.scripts-edge--default { stroke: #d97706; }
.scripts-edge--signal { stroke-dasharray: 0; stroke-width: 3; }
.scripts-edge--rule {
    stroke: #be123c;
    stroke-dasharray: 7 5;
}

/* auto-continue ребро: чёрный жирный — engine идёт «без ожидания
   клиента» сразу к next (auto_continue=True на AIReply). */
.scripts-edge--auto {
    stroke: #0f172a;
    stroke-width: 2.4;
}
.scripts-edge-label--auto {
    fill: #0f172a;
    font-weight: 600;
}

.scripts-edge-label-g { pointer-events: none; }

/* === Bezier pivot-handles (PR-2) === */
.scripts-graph__handles {
    pointer-events: visiblePainted;
}

/* PR-3: handles SVG отдельным слоем над узлами (article z-index:2).
   Раньше был внутри scripts-graph__edges и anchor'ы на границе узла
   физически перекрывались article'ом — клик не доходил до handle. */
.scripts-graph__handles-layer {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 5;
    pointer-events: none;  /* клики проходят сквозь, кроме самих handles */
    overflow: visible;
}
.scripts-graph__handles-layer .scripts-edge-handle {
    pointer-events: visiblePainted;
}

/* PR-4: pivot'ы НА самой кривой (B(1/3), B(2/3)). Никаких выносных
   пунктирных линий — кривая и есть «ножка». Стиль pivot'ов остался
   квадратиком, цветовая дифференциация a/b сохранена. */
.scripts-edge-handle {
    fill: #fff;
    stroke: #2563eb;
    stroke-width: 2;
    cursor: grab;
    transition: fill 120ms, stroke 120ms, stroke-width 120ms;
}

/* PR-3: единая сиреневая hover-подсветка для ВСЕХ handles (pivot+anchor) —
   маркер «можно тащить». Согласован с .scripts-node[data-drag-handle]:hover.
   Перебивает variant-specific цвета через single :hover селектор. */
.scripts-edge-handle:hover {
    fill: #f3e8ff !important;
    stroke: #a855f7 !important;
    stroke-width: 3 !important;
}

.scripts-graph__handles.is-dragging .scripts-edge-handle {
    cursor: grabbing;
}

.scripts-edge-handle--a { stroke: #2563eb; fill: #eff6ff; }
.scripts-edge-handle--b { stroke: #0f766e; fill: #f0fdfa; }

/* Anchor-handles — точки входа/выхода ребра, прилипшие к границе блока.
   Нейтральный серый, чуть меньше pivot'ов. */
.scripts-edge-handle--src,
.scripts-edge-handle--dst {
    stroke: #475569;
    fill: #fff;
    stroke-width: 2;
}

.scripts-edge-label__bg {
    fill: rgba(255, 255, 255, 0.92);
    stroke: rgba(15, 23, 42, 0.08);
    stroke-width: 1;
}

.scripts-edge-label {
    fill: #475569;
    font-size: 10px;
    font-weight: 700;
    text-anchor: start;
    dominant-baseline: middle;
}

.scripts-edge-label--switch { fill: #0f766e; }
/* PR #476: label ветви LLMSwitch — бордовый, согласован с stroke
   и с .prompt-branch (PR #470). */
.scripts-edge-label--llm { fill: #630633; }
.scripts-edge-label--default { fill: var(--color-warn-text); }
.scripts-edge-label--rule { fill: #be123c; }
.scripts-edge-label--signal { font-size: 12.5px; }
.scripts-edge-label__bolt { fill: #eab308; font-size: 10px; }

.scripts-node {
    position: absolute;
    z-index: 2;
    display: flex;
    flex-direction: column;
    width: 366px;
    /* CARD_H в backend и CARD.h в JS должны совпадать с этим.
       Высота 384 (480 -20% по запросу юзера). Обвес ≈156px (header
       24 + title 24 + id 16 + 2 summaries 44 + footer 28 + padding
       20). Бюджет на editor'ы = 228px → влезает по 100px каждый
       при both open, плюс buffer на скролл-индикатор. */
    height: 384px;
    /* PR #449: убрали overflow:hidden — клиппирование контента
       (editor'ы) делает <details> внутри (у него свой overflow:hidden).
       На уровне article overflow:visible нужен чтобы бейдж «!» при
       top:-12 right:-12 не обрезался сверху-справа когда узел стоит
       у верхней границы canvas'а. Видимая часть бейджа = красное
       полукружье в скриншотах юзера — именно из-за clip'а. */
    overflow: visible;
    padding: 5px 5px 5px;
    /* #391: единый внешний контур всех узлов — 1px чёрный, радиус 2px
       (был 1px #d8dee8 + 5px цветная левая полоса + radius-md). Цветовая
       семантика типа узла остаётся в иконке/заголовке. */
    border: 1px solid #000;
    border-radius: 2px;
    background: var(--color-surface);
    box-shadow: 0 10px 26px rgba(15, 23, 42, 0.10);
    transition: box-shadow 120ms;
}

.scripts-node.is-dragging {
    z-index: 50;
    box-shadow: 0 16px 36px rgba(15, 23, 42, 0.22);
}

/* PR #447: static_reply — узлы дословной реплики, имеют только 1 поле
   (static_text) без dispatch_addon/system_addon. Уменьшили высоту
   с 384 до 220px чтобы не занимали место на канвасе. */
.scripts-node--static_reply {
    height: 220px;
}

/* PR #448: узлы с зарегистрированными tools показывают 2 редактора
   (dispatch_addon + system_addon) — им нужно +60% высоты чтобы оба
   editor'а имели разумный slot. 384 * 1.6 ≈ 614px. */
.scripts-node--with-tools {
    height: 614px;
}

/* PR-Multi-select: выбранный узел подсвечивается сиреневым.
   Перебивает hover и default border. */
.scripts-node.is-selected {
    box-shadow: 0 0 0 3px #a855f7, 0 10px 26px rgba(168, 85, 247, 0.20) !important;
    border-color: #a855f7 !important;
}

/* Bounding rect охватывает все выбранные узлы. Оверлей внутри
   .scripts-graph (внутри viewport чтобы pan/transform работали). */
.scripts-multi-select-rect {
    position: absolute;
    pointer-events: none;
    border: 2px dashed #a855f7;
    border-radius: 3px;
    background: rgba(168, 85, 247, 0.06);
    z-index: 1;  /* выше grid background, ниже узлов (z-index:2) */
    transition: opacity 80ms;
}

/* Marquee selection (shift+drag) — рисуем рамку поверх canvas
   во время выделения. Прозрачная сиреневая заливка + сплошной
   bordeer (не dashed как persisted selection rect). */
.scripts-marquee {
    position: absolute;
    pointer-events: none;
    border: 1.5px solid #a855f7;
    background: rgba(168, 85, 247, 0.12);
    z-index: 100;
}

/* Shift-mode: курсор-перекрестие на host'е (как в RTS-играх для
   marquee selection). Класс ставится на host через keydown/keyup
   listeners. */
.scripts-graph-host.is-marquee-mode,
.scripts-graph-host.is-marquee-mode * {
    cursor: crosshair !important;
}

/* === Rule-target badges + popover ===
   Маленький жёлтый значок СЛЕВА от каждого узла на который ссылаются
   JUMP-правила. Клик → popup со списком всех правил входящих сюда. */
/* Badge ПРИЛИПАЕТ к верхне-левому углу узла (overlap), чтобы юзер
   видел чёткую визуальную связь badge ↔ узел. position absolute
   относительно canvas, координаты в template (node.x-14, node.y-14). */
.scripts-rule-target-badge {
    position: absolute;
    z-index: 4;  /* выше узлов (z:2), но ниже handles (z:5) */
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 3px 5px;
    border: 1.5px solid #be123c;
    border-radius: 999px;
    background: #fff;
    color: #be123c;
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 6px rgba(190, 18, 60, 0.25);
    transition: background 100ms, color 100ms, transform 100ms,
                box-shadow 100ms;
}

/* На hover badge подсвечиваем PARENT-узел горчично-жёлтой
   рамкой — визуальная связь badge → node. Класс ставит JS. */
.scripts-node.is-rule-target-hover {
    box-shadow: 0 0 0 3px #d97706, 0 10px 26px rgba(217, 119, 6, 0.25) !important;
    border-color: #d97706 !important;
}
.scripts-rule-target-badge__icon {
    font-size: 10px;
}
.scripts-rule-target-badge__count {
    font-variant-numeric: tabular-nums;
}

/* Универсальный hover для всех элементов с .has-popover (где скрыта
   интерактивность). Горчично-жёлтая подсветка + указатель. */
.has-popover {
    cursor: pointer;
}
.has-popover:hover {
    background: #fef3c7 !important;
    border-color: #d97706 !important;
    color: #92400e !important;
    transform: scale(1.05);
}

/* Popover входящих JUMP-правил. Overlay — прозрачный full-screen
   click-catcher (закрытие по клику вне панели / Esc); сама панель
   позиционируется JS-ом справа от бейджа (positionRulePopover) через
   inline left/top, поэтому overlay БЕЗ flex-центрирования и без дима. */
.scripts-popover-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: transparent;
}
.scripts-popover-overlay[hidden] { display: none; }

.scripts-popover {
    position: absolute;
    left: 0;
    top: 0;
    width: min(440px, 92vw);
    max-height: min(70vh, 540px);
    display: flex;
    flex-direction: column;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    background: #fff;
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.28);
    overflow: hidden;
}
.scripts-popover__head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 5px 5px;
    border-bottom: 1px solid #e2e8f0;
    background: #f8fafc;
}
.scripts-popover__title {
    flex: 1 1 auto;
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    color: #1e293b;
}
.scripts-popover__close {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #64748b;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
}
.scripts-popover__close:hover {
    background: #e2e8f0;
    color: #1e293b;
}
.scripts-popover__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 5px 5px;
}

.scripts-popover-rule {
    margin-bottom: 14px;
    padding: 5px;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    background: #fff;
}
.scripts-popover-rule:last-child { margin-bottom: 0; }

.scripts-popover-rule__head {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
}
.scripts-popover-rule__head strong {
    color: #be123c;
    font-size: 11.5px;
}
.scripts-popover-rule__name {
    color: #1e293b;
    font-size: 11px;
    font-weight: 600;
}
.scripts-popover-rule__meta {
    color: #64748b;
    font-size: 11.5px;
}
.scripts-popover-rule__meta code {
    padding: 1px 4px;
    border-radius: 3px;
    background: #f1f5f9;
    font-size: 10px;
}
.scripts-popover-rule__field {
    margin-bottom: 8px;
}
.scripts-popover-rule__field:last-child { margin-bottom: 0; }
.scripts-popover-rule__field label {
    display: block;
    margin-bottom: 4px;
    color: #475569;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.scripts-popover-rule__field textarea {
    width: 100%;
    padding: 5px 5px;
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #f8fafc;
    color: #1f2937;
    font-family: inherit;
    font-size: 12.5px;
    line-height: 1.45;
    resize: vertical;
}

/* PR-2 drag UX: cursor:grab на всём узле (data-drag-handle перенесён
   на article в template). Контентный textarea переопределяет на text. */
.scripts-node[data-drag-handle] { cursor: grab; }
.scripts-node[data-drag-handle].is-dragging { cursor: grabbing; }
.scripts-node [contenteditable="true"],
.scripts-node input,
.scripts-node textarea,
.scripts-node button,
.scripts-node a {
    cursor: text;
}
.scripts-node button,
.scripts-node a {
    cursor: pointer;
}

/* Сиреневая hover-подсветка — маркер «можно тащить». Только когда
   юзер наводит на узел без активного drag/pan'а. Перебивается
   .is-dragging (там более яркий shadow для visual feedback drag'а). */
.scripts-node[data-drag-handle]:hover:not(.is-dragging) {
    box-shadow: 0 0 0 2px #a855f7, 0 10px 26px rgba(15, 23, 42, 0.10);
    border-color: #a855f7;
}

/* #391: контур всех узлов унифицирован (1px чёрный в .scripts-node);
   цветные левые полосы убраны. Фон панели задаём только там, где просил
   владелец — внутренние Text Edits / контейнеры НЕ трогаем. */
/* Панель ai_reply — такая же, как у terminal (#f4f5f7). */
.scripts-node--ai_reply { background: #f4f5f7; }
/* Панель wait — светло-салатовый. */
.scripts-node--wait { background: #eef8d8; }
/* static_reply — пассивный узел без LLM, голубо-серый фон. */
.scripts-node--static_reply { background: #f8fafc; }
.scripts-node--static_reply .scripts-node__editor--static {
    background: #fff;
    border: 1px dashed #cbd5e1;
    font-style: italic;
    color: #334155;
}

/* === LLMSwitch как ромб (PR-5) ============================================
   Узел остаётся прямоугольником в DOM (для контента и удобной anchor-
   математики), но фон-граница рисуется ромбом через SVG-overlay. Это
   визуально достаточно: пользователь воспринимает узел как ромб, но
   anchor-точки/drag/handles работают на bounding box узла. */
.scripts-node--llm_switch {
    /* Компактный — нет 2 textarea секций. Высота 320→160px чтобы
       ромб не растягивался на всю карту с пустым пространством.
       backend (script_graph_view.CARD_H_COMPACT) совпадает. */
    height: 160px;
    background: transparent;
    border: 0;
    box-shadow: none;
    /* PR #465 / PR #484 / PR #486: подобран баланс — не слишком высоко
       (текст не упирался в верхний край ромба), не слишком низко
       (юзер: «ты поднял чересчур. чуть приспусти»). */
    /* #391: контент липнул к верхнему узкому углу ромба — центрируем по
       вертикали (justify-content) и слегка смещаем вниз асимметричным
       паддингом (top>bottom), чтобы шапка ушла из острого верха в широкую
       часть. */
    padding: 5px 5px 5px;
    justify-content: center;
    text-align: center;
    /* PR #449: overflow:visible (не hidden) — иначе бейдж «!» при
       top:-12 обрезается. Длинные названия клиппит сам .scripts-node__title
       через line-clamp:2 — отдельный overflow:hidden на ромбе не нужен. */
    overflow: visible;
}
.scripts-node--llm_switch .scripts-node__title {
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-clamp: 2;
}
.scripts-node--llm_switch .scripts-node__shape {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 0;
}
.scripts-node--llm_switch .scripts-node__shape polygon {
    fill: #fffaf0;
    /* #391: контур ромба — 1px чёрный, как у всех узлов (был #d97706 2.5px). */
    stroke: #000;
    stroke-width: 1;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}
.scripts-node--llm_switch.scripts-node--start .scripts-node__shape polygon {
    stroke: #000;
}
.scripts-node--llm_switch > * {
    position: relative;
    z-index: 1;
}
.scripts-node--llm_switch .scripts-node__head {
    justify-content: center;
}
.scripts-node--llm_switch .scripts-node__type {
    color: var(--color-warn-text);
    font-size: 10px;
    letter-spacing: 0.5px;
}
.scripts-node--llm_switch .scripts-node__title {
    margin: 4px auto 0;
    max-width: 200px;
    -webkit-line-clamp: 3;
}
.scripts-node--llm_switch .scripts-node__id {
    margin: 6px auto 0;
}

/* PR #490: интерактивные селекторы на switch_script-узле — выбор
   target_script + target_start_node, auto-save через PATCH. */
/* PR #492: empty-state для draft-скриптов с пустым графом. Floats
   over canvas centred, не блокирует drag/scroll. */
.scripts-graph-empty {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    z-index: 4;
    max-width: 420px;
    padding: 5px 5px;
    background: #f9fafb;
    border: 1px dashed #d1d5db;
    border-radius: 3px;
    text-align: center;
    color: #6b7280;
    pointer-events: none;
}
.scripts-graph-empty h3 {
    margin: 0 0 8px;
    font-size: 13px;
    color: #374151;
}
.scripts-graph-empty p {
    margin: 0;
    font-size: 10px;
    line-height: 1.5;
}

.scripts-node__switch-script-form {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 4px 0;
    position: relative;
}
.scripts-node__sw-lbl {
    font-size: 10.5px;
    color: #6b7280;
    font-weight: 600;
    margin-top: 4px;
}
.scripts-node__sw-select {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10px;
    padding: 4px 5px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    background: #ffffff;
    color: #1f2937;
}
.scripts-node__sw-toast {
    position: absolute;
    right: 0;
    bottom: -18px;
    font-size: 10px;
    font-weight: 600;
}

/* PR #482. LLMSwitch — echo вопроса гейта в title (2 строки макс,
   не редактируется), id поднят НАД «ПРОВЕРКА ФАКТОВ». Юзер: «должен
   выводиться многострочный эхо вопроса гейта, а не сокращенная
   метка ... что не влезло, то не влезло. system gate name можно
   поднять над "ПРОВЕРКА ФАКТОВ"». */
.scripts-node__id--top {
    display: block;
    margin: 0 auto 4px;
    font-size: 10px;
    color: var(--color-text-muted);
    text-align: center;
    /* Поверх ромба, но не блокирует drag — pointer-events: none. */
    position: relative;
    z-index: 1;
}
.scripts-node--llm_switch .scripts-node__title--gate-echo {
    margin: 4px auto 0;
    max-width: 210px;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.25;
    color: #1f2937;
    -webkit-line-clamp: 2;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Чтоб title явно отделялся от header'a и центрировался в верхней
       части ромба над «?»-кнопкой. */
}

.scripts-node--start {
    outline: 2px solid rgba(37, 99, 235, 0.22);
    outline-offset: 2px;
}

.scripts-node--unreachable {
    opacity: 0.72;
    background: #f8fafc;
}

.scripts-node__head,
.scripts-node__badges,
.scripts-node__foot {
    display: flex;
    align-items: center;
}

.scripts-node__head {
    justify-content: space-between;
    gap: calc(var(--space-xs) / 2);
    margin-bottom: 2px;
}

.scripts-node__type {
    overflow: hidden;
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.3px;
    text-overflow: ellipsis;
    text-transform: uppercase;
    white-space: nowrap;
}

/* PR #471: иконка типа узла (🗿 / 🧠) — слева от type_label.
   Юзер: «надпись сдвинь вправо чтобы перед добавить иконку. для
   статичной — серая голова стоунхендж. для ИИ реплики — голова с
   сияющим мозгом». */
.scripts-node__type-icon {
    display: inline-block;
    margin-right: 6px;
    font-size: 10px;
    line-height: 1;
    vertical-align: -1px;
}
/* 🗿 (Moai) — серый монохром: убираем emoji-цвет через filter. */
.scripts-node__type-icon--static {
    filter: grayscale(1) brightness(0.85);
}
/* 🧠 — слабый «сияющий» glow от ai-blue (соответствует prompt-engine
   для AI-вызовов в графе). */
.scripts-node__type-icon--ai {
    text-shadow: 0 0 4px rgba(37, 99, 235, 0.55);
}
/* PR P98 Phase 3 UI: ▶ — старт диалога (композит wait+greeting+router).
   Зелёный glow — намёк на «entry / play». */
.scripts-node__type-icon--start {
    color: var(--color-success-text);
    text-shadow: 0 0 4px rgba(21, 128, 61, 0.4);
}
/* PR P98 Phase 3 UI: ⏳ — wait-узел. Серый монохром (нейтральная пауза). */
.scripts-node__type-icon--wait {
    filter: grayscale(1) brightness(0.95);
}

/* P181: ⏳ по центру квадрата wait-узла — крупный watermark «здесь
   бот ждёт ответа клиента» (заменил отдельный pseudo-кружок «ОТВЕТ
   КЛИЕНТА»). Под текстом debounce/next (pointer-events:none),
   приглушён чтобы не перебивать поля. */
.scripts-node__wait-center {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    line-height: 1;
    opacity: 0.5;
    filter: grayscale(1);
    pointer-events: none;
    z-index: 0;
}
/* Текст debounce/next держим НАД watermark'ом. */
.scripts-node__wait-meta {
    position: relative;
    z-index: 1;
}

.scripts-node__badges {
    gap: 3px;
    flex-shrink: 0;
}

.scripts-node__badge {
    padding: 1px 5px;
    border-radius: 3px;
    background: var(--color-accent-bg);
    color: var(--color-accent);
    font-size: 10px;
    font-weight: 700;
    line-height: 1.4;
}

.scripts-node__badge--warn {
    background: #fffbeb;
    color: var(--color-warn-text);
}

/* PR P52: CSS-правила heuristic_auto_continue toggle/badge удалены.
   Эвристика _reply_seems_decisive была удалена в P46 как
   self-validation anti-pattern. UI toggle и legacy-badge удалены
   из templates в P52. Schema field остался в JSONB для backward
   compat (engine игнорирует). */

.scripts-node__title {
    display: -webkit-box;
    min-height: 17px;
    max-height: 34px;
    overflow: hidden;
    margin: 0;
    color: var(--color-text);
    font-size: 13px;
    font-weight: 700;
    line-height: 1.3;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.scripts-node__id {
    display: block;
    overflow: hidden;
    margin-top: 2px;
    color: var(--color-text-muted);
    font-size: 10px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scripts-node__preview {
    display: -webkit-box;
    max-height: 30px;
    overflow: hidden;
    margin: 5px 0 0 0;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.35;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

.scripts-node__foot {
    position: absolute;
    right: 9px;
    bottom: 7px;
    left: 11px;
    justify-content: space-between;
    gap: var(--space-xs);
    color: var(--color-text-muted);
    font-size: 10px;
}

.scripts-node__foot span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ==== Editor + tools-pills внутри узла (PR-1 интерактивный редактор) ==== */

/* PR-3: две раскрывашки вместо одного combined editor. Каждая
   секция — <details> с summary-кнопкой "+" и contenteditable внутри. */
.scripts-node__edit-section {
    margin-top: 4px;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    background: #fff;
    overflow: hidden;
    /* PR #445: flex column контейнер чтобы editor мог растягиваться.
       Закрытая секция: flex 0 0 auto (только summary). Открытая:
       flex 1 1 0 (делит остаток поровну с другими открытыми).
       PR #449: position:relative для absolute-positioned editor'а. */
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    position: relative;
}
.scripts-node__edit-section[open] {
    flex: 1 1 0;
    min-height: 80px;
}

.scripts-node__edit-summary {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 5px;
    cursor: pointer;
    user-select: none;
    list-style: none;
    font-size: 11.5px;
    font-weight: 600;
    color: #475569;
    background: #f8fafc;
    transition: background 100ms;
}
.scripts-node__edit-summary::-webkit-details-marker { display: none; }
.scripts-node__edit-summary:hover { background: #f1f5f9; color: #1e293b; }

/* PR #431: hint справа от названия секции — серый, light, italic.
   PR #446: legacy, не используется в новом UI. */
.scripts-node__edit-hint {
    margin-left: auto;
    font-size: 10.5px;
    font-weight: 400;
    color: #94a3b8;
    font-style: italic;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 60%;
}

/* PR #446: маленькая иконка «?» вместо текстового hint'а — раньше hint
   занимал место в summary plate, теперь только tooltip при hover.
   Освобождённое место под revert-btn / save-toast которые
   позиционируются на ту же линию. */
.scripts-node__edit-help {
    margin-left: auto;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #e2e8f0;
    color: #475569;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    cursor: help;
    user-select: none;
    transition: background 100ms, color 100ms;
}
.scripts-node__edit-help:hover {
    background: #94a3b8;
    color: #ffffff;
}

/* Шеврон ▸ (closed) → ▾ (open) перед заголовком. Заменили rotation
   "+" → "−" потому что повёрнутый "+" выглядел как красный X
   («закрыть»), путал юзера. Шевроны — однозначный disclosure-маркер. */
.scripts-node__edit-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 14px;
    color: #94a3b8;
    font-size: 10px;
    line-height: 1;
    transition: transform 120ms, color 100ms;
}
.scripts-node__edit-section[open] > .scripts-node__edit-summary > .scripts-node__edit-toggle {
    /* ▸ → ▾ через rotate 90° (CSS-only, не меняем символ в DOM). */
    transform: rotate(90deg);
    color: #2563eb;
}

/* PR-376nnn: бейдж 🎲 «шаблон» в шапке секции редактора static-реплики.
   Виден только когда узел использует {a|b|c} синтаксис — даёт юзеру
   мгновенный сигнал что текст рендерится случайной комбинацией. */
.scripts-node__static-template-badge {
    margin-left: 8px;
    padding: 1px 5px;
    border-radius: 3px;
    background: #fef3c7;          /* мягко-жёлтый */
    color: #92400e;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    vertical-align: middle;
}
.scripts-node__static-hint {
    margin: 4px 8px 6px 14px;
    font-size: 10px;
    line-height: 1.4;
    color: #64748b;
}
.scripts-node__static-hint code {
    padding: 0 3px;
    background: #f1f5f9;
    border-radius: 2px;
    font-size: 10px;
    font-family: ui-monospace, monospace;
}

.scripts-node__editor {
    /* PR #445: max-height убран — теперь editor flex:1 в section'е и
       растягивается на весь доступный вертикальный slot. min-height: 0
       обязателен чтобы flex item мог быть меньше своего content height. */
    padding: 5px 5px 5px 5px;
    overflow-y: auto;
    border-top: 1px solid #e2e8f0;
    background: #fff;
    color: #1f2937;
    font-family: inherit;
    font-size: 11.5px;
    line-height: 1.45;
    white-space: pre-wrap;
    word-break: break-word;
    cursor: text;
    /* overflow-anchor:none предотвращает auto-scroll-jump браузером
       когда контент меняется — даёт стабильный scroll behavior. */
    overflow-anchor: none;
    /* PR #433: плавный переход фона при focus/blur. */
    transition: background-color 120ms ease-out, outline-color 120ms ease-out;
    outline: 0 solid transparent;
}

/* PR #433: editor под фокусом — горчично-жёлтый фон + amber outline.
   Сигнализирует «ты редактируешь, blur автоматически сохранит». */
.scripts-node__editor.is-editing,
.scripts-node__editor:focus {
    background-color: #fef3c7 !important;  /* amber-100 */
    outline: 2px solid #f59e0b;            /* amber-500 */
    outline-offset: -1px;
}

/* PR #433: кнопка «Отменить правки» — появляется в правом нижнем углу
   контейнера editor'а при фокусе. mousedown (раньше blur'а) откатывает
   текст и снимает фокус. */
/* PR #446: revert-btn внутри summary-плашки (заголовка <details>).
   Высота summary ~22-24px, кнопка тоже ~18px — умещается. Перекрывает
   правую часть плашки (где раньше был hint). z-index выше summary
   чтобы клики ловились. */
.scripts-node__revert-btn {
    position: absolute;
    top: 3px;
    right: 6px;
    z-index: 10;
    background: #f59e0b;
    color: #ffffff;
    border: 0;
    border-radius: 3px;
    padding: 1px 5px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
    transition: background 100ms ease-out;
    user-select: none;
    white-space: nowrap;
}
.scripts-node__revert-btn:hover {
    background: #d97706;
}

/* PR #446: save-toast тоже внутри summary plate. */
.scripts-node__save-toast {
    position: absolute;
    top: 3px;
    right: 6px;
    z-index: 10;
    background: #10b981;
    color: #ffffff;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
    pointer-events: none;
    animation: scripts-toast-fade 2s ease-in forwards;
    user-select: none;
    white-space: nowrap;
}
.scripts-node__save-toast.is-error {
    background: var(--color-danger);        /* red-600 */
}
@keyframes scripts-toast-fade {
    0%, 70% { opacity: 1; transform: translateY(0); }
    100%    { opacity: 0; transform: translateY(-4px); }
}

/* PR-3 v3: разделение пространства между двумя секциями.
   Логика:
   - Если обе секции [open] (= обе инструкции непустые) — делим
     пространство ПОРОВНУ (120px+120px). Узел выглядит балансировано,
     обе плашки одинакового размера.
   - Если только одна [open] (другая пустая и закрыта) — открытая
     получает больше (180px), закрытая занимает только summary (~28px).

   Используем :has() — поддерживается в современных Chrome/Edge/Safari/FF.
   :nth-of-type — порядок секций фиксирован шаблоном:
   1ая = system_addon, 2ая = dispatch_addon.
*/

/* PR #449: editor внутри открытой секции — absolute positioning ниже
   summary до низа section'а. Раньше пытались flex:1 1 auto + min-height:0
   на editor'е, но Chrome НЕ применяет flex-shrink к contenteditable
   внутри <details>: editor рос до своей content height (637/1188px),
   вылезая за section и пряча скроллбары. Absolute даёт box строго
   равный (section_h - summary_h), и overflow-y:auto показывает
   скроллбары когда текст не помещается. */
.scripts-node__edit-section[open] > .scripts-node__editor {
    position: absolute;
    top: 25px;      /* высота summary */
    left: 0;
    right: 0;
    bottom: 0;
}

.scripts-node__editor:focus {
    outline: 2px solid rgba(37, 99, 235, 0.35);
    outline-offset: -2px;
    background: #fffef0;
}

/* Placeholder для пустого contenteditable. Стандартный input
   placeholder тут не работает (это <div>), используем :empty
   + ::before с текстом из data-атрибута. */
.scripts-node__editor:empty::before {
    content: attr(data-placeholder);
    color: #94a3b8;
    font-style: italic;
    pointer-events: none;
}

.scripts-node__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin-top: 6px;
}

/* PR #440: компактная кнопка управления tools узла.
   PR #466: оливково-зелёная гамма (юзер) — визуально совпадает с
   подписью под красным prompt-tool-unregistered («нажмите ⚙ tools»),
   чтобы взгляд сразу искал ту самую кнопку под узлом. */
.scripts-tools-btn {
    flex: 0 0 auto;
    padding: 2px 5px;
    background: #f3f7e6;       /* olive-50 — мягкий зелёный фон */
    border: 1px solid #a3b86a; /* olive-400 — рамка */
    border-radius: 3px;
    color: #4d5c1f;            /* olive-700 — текст */
    font-size: 10.5px;
    font-weight: 600;
    cursor: pointer;
    line-height: 1.3;
    transition: background 100ms, border-color 100ms;
}
.scripts-tools-btn:hover {
    background: #e3edc6;       /* olive-100 */
    border-color: #7d943a;     /* olive-500 */
    color: #3a4615;            /* olive-800 */
}

/* PR #440: контейнер для тегов справа от кнопки. */
.scripts-tools-tags {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
}

/* PR #440: tag — дословное имя tool'а в moneyспейс шрифте. */
.scripts-tool-tag {
    display: inline-flex;
    align-items: center;
    padding: 1px 5px;
    border: 1px solid #c7d2fe;
    border-radius: 3px;
    background: #eef2ff;
    color: #3730a3;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
    font-weight: 600;
    line-height: 1.4;
    white-space: nowrap;
}

/* PR #440: модал управления tools узла. */
.scripts-node-tools-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}
.scripts-node-tools-overlay[hidden] { display: none; }
.scripts-node-tools-modal {
    width: min(720px, 92vw);
    max-height: 86vh;
    display: flex;
    flex-direction: column;
    background: #ffffff;
    border-radius: 3px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.3);
    overflow: hidden;
}
.scripts-node-tools-modal__head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 5px 5px;
    border-bottom: 1px solid #e2e8f0;
    background: #f8fafc;
}
.scripts-node-tools-modal__head h3 {
    flex: 1 1 auto;
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    color: #1e293b;
}
.scripts-node-tools-modal__close {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #64748b;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.scripts-node-tools-modal__close:hover {
    background: #e2e8f0;
    color: #1e293b;
}
.scripts-node-tools-modal__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.scripts-node-tools-modal__foot {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 5px 5px;
    border-top: 1px solid #e2e8f0;
    background: #fafafa;
}

/* Строка чекбокс-листа в модале. */
.scripts-node-tools-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 5px 5px;
    border-radius: 3px;
    cursor: pointer;
    transition: background 80ms;
}
.scripts-node-tools-row:hover {
    background: #f8fafc;
}
.scripts-node-tools-row__cb {
    flex: 0 0 auto;
    margin: 0;
}
.scripts-node-tools-row__name {
    flex: 0 0 auto;
    min-width: 180px;
    color: #1e3a8a;
    font-weight: 700;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
}
.scripts-node-tools-row__desc {
    flex: 1 1 auto;
    color: #475569;
    font-size: 10px;
    line-height: 1.45;
}
/* «Гарантированный вызов» — чекбокс детерминированной эмиссии движком
   (node.guaranteed_tools). Активен только у зарегистрированного tool'а. */
.scripts-node-tools-row__g {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: #6d28d9;
    font-size: 9px;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
}
.scripts-node-tools-row__g-cb {
    margin: 0;
}
.scripts-node-tools-row__g:has(.scripts-node-tools-row__g-cb:disabled) {
    opacity: 0.35;
    cursor: not-allowed;
}

/* Две секции попапа tools узла: верхняя «Гарантированный вызов» — drag-
   сортируемая очередь детерминированных вызовов движка (порядок =
   node.guaranteed_tools); нижняя «Остальные tools» — алфавитный каталог. */
.scripts-node-tools-section {
    margin-bottom: 8px;
}
.scripts-node-tools-section--g {
    border: 1px solid #ddd6fe;
    border-radius: 4px;
    background: #faf5ff;
    padding: 4px;
}
.scripts-node-tools-section__head {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 3px 5px;
    font-size: 10px;
    font-weight: 700;
    color: #64748b;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.scripts-node-tools-section--g .scripts-node-tools-section__head {
    color: #6d28d9;
}
.scripts-node-tools-section:not(.scripts-node-tools-section--g)
    .scripts-node-tools-section__head {
    border-top: 1px solid #e2e8f0;
    margin-top: 6px;
}
.scripts-node-tools-section__hint {
    font-weight: 600;
    color: #8b5cf6;
    text-transform: none;
    letter-spacing: 0;
}
.scripts-node-tools-empty {
    display: block;
    padding: 4px 6px;
    color: #94a3b8;
    font-size: 10px;
}
/* Grip drag-захвата — только у guaranteed-рядов. По образцу .tableview__grip. */
.scripts-node-tools-row__grip {
    flex: 0 0 auto;
    cursor: grab;
    color: #6b7280;
    font-weight: 700;
    letter-spacing: -2px;
    user-select: none;
    font-size: 12px;
    line-height: 1;
    align-self: center;
}
.scripts-node-tools-row__grip:hover {
    color: var(--color-accent);
}
.scripts-node-tools-row__grip:active {
    cursor: grabbing;
}
/* Guaranteed-ряд: белая карточка внутри фиолетовой секции. */
.scripts-node-tools-row--g {
    background: #fff;
    border: 1px solid #ede9fe;
    margin-bottom: 3px;
}
.scripts-node-tools-row--g:hover {
    background: #fff;
}
/* Drag-визуалы (паттерн .tableview__th--drag / --drop, но вертикально). */
.scripts-node-tools-row--drag {
    opacity: 0.55;
}
.scripts-node-tools-row--drop {
    box-shadow: inset 0 3px 0 0 var(--color-accent);
}

.scripts-tool-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 5px 2px 5px;
    border: 1px solid #cbd5e1;
    border-radius: 999px;
    background: #fff;
    color: #1f2937;
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
}

.scripts-tool-pill__icon {
    font-size: 10px;
    line-height: 1;
}

/* Цветовая дифференциация tool-pills по семантике */
.scripts-tool-pill--prefilter-catalog,
.scripts-tool-pill--pick-by-specs,
.scripts-tool-pill--check-availability,
.scripts-tool-pill--search-catalog {
    border-color: #bae6fd;
    background: #f0f9ff;
    color: #075985;
}
.scripts-tool-pill--create-booking-draft {
    border-color: #bbf7d0;
    background: #f0fdf4;
    color: #166534;
}
.scripts-tool-pill--writePhoneToContact,
.scripts-tool-pill--send-crm-form {
    border-color: #ddd6fe;
    background: #f5f3ff;
    color: #5b21b6;
}
.scripts-tool-pill--escalate-to-manager {
    border-color: #fecaca;
    background: #fef2f2;
    color: #991b1b;
}
.scripts-tool-pill--extract-dialog-facts,
.scripts-tool-pill--parse-avito-service {
    border-color: #fde68a;
    background: #fffbeb;
    color: #92400e;
}

.scripts-service-panels,
.scripts-details {
    display: grid;
    grid-template-columns: 1fr;
    gap: 8px;
}

.scripts-panel {
    min-width: 0;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    /* Единый цвет панелей (как все панели интерфейса). */
    background: var(--color-card);
    overflow: hidden;
}

.scripts-panel__title {
    margin: 0 0 var(--space-sm);
    font-size: 13px;
}

.scripts-panel__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: 5px 5px;
    /* Лёгкая лавандовая шапка плашки — в тему, отделяет заголовок от тела. */
    background: #f1eefb;
    color: var(--color-text);
    cursor: pointer;
    font-size: 10px;
    font-weight: 700;
    list-style: none;
    user-select: none;
}

.scripts-panel__summary::-webkit-details-marker {
    display: none;
}

.scripts-panel__summary::before {
    content: '';
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--control-focus);
    border-bottom: 2px solid var(--control-focus);
    transform: rotate(-45deg);
    transition: transform 140ms ease;
}

.scripts-panel[open] .scripts-panel__summary::before {
    transform: rotate(45deg);
}

.scripts-panel__summary span:first-child {
    flex: 1 1 auto;
}

.scripts-panel__summary span:last-child {
    flex: 0 1 auto;
    overflow: hidden;
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 600;
    text-align: right;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scripts-panel__body {
    padding: 0 5px 5px;
    border-top: 1px solid var(--color-border);
}

.scripts-panel__body--table {
    max-height: 360px;
    overflow: auto;
    padding: 0;
}

.scripts-panel__body--table .scripts-table th {
    position: sticky;
    top: 0;
    background: #f1eefb;
    z-index: 1;
}

.scripts-panel .scripts-summary,
.scripts-panel .scripts-legend {
    margin: var(--space-md) 0 0;
}

.scripts-panel__text {
    margin: var(--space-sm) 0 0;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.55;
}

.scripts-panel__text code {
    padding: 1px 4px;
    border-radius: 3px;
    background: var(--color-bg);
}

/* Мини-легенда внутри плашки «Jump-правила»: объясняет что такое
   jump-rule и что значат статусы «на графе» / «только в списке». */
.scripts-rules-legend {
    margin-bottom: 8px;
    padding: 5px 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: #f8fafc;
}
.scripts-rules-legend .scripts-panel__text {
    margin: 0 0 6px;
}
.scripts-rules-legend__statuses {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 4px;
    font-size: 10px;
    color: var(--color-text-muted);
    line-height: 1.45;
}
.scripts-rules-legend__statuses li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
}
.scripts-rules-legend__statuses .scripts-legend__line--rule {
    display: inline-block;
    width: 22px;
    vertical-align: middle;
}

.scripts-compact-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0;
}

.scripts-compact-stats div {
    min-width: 96px;
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: #fff;
}

.scripts-compact-stats dt {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.3px;
    text-transform: uppercase;
}

.scripts-compact-stats dd {
    overflow: hidden;
    margin: 1px 0 0;
    color: var(--color-text);
    font-size: 10px;
    font-weight: 700;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.scripts-compact-stats__warn {
    border-color: #f59e0b !important;
    background: #fffbeb !important;
}

.scripts-panel__empty {
    margin: 0;
    color: var(--color-text-muted);
}

.scripts-legend-detail {
    display: grid;
    gap: 8px;
}

.scripts-legend-detail__row {
    display: grid;
    grid-template-columns: 34px minmax(105px, 0.25fr) minmax(0, 1fr);
    align-items: center;
    gap: 8px;
    color: var(--color-text-muted);
    font-size: 10px;
    line-height: 1.35;
}

.scripts-legend-detail__row strong {
    color: var(--color-text);
}

.scripts-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 10px;
}

.scripts-table th {
    padding: 5px 5px;
    /* Лавандовая шапка таблицы — в тему (Переходы / Jump-правила). */
    background: #f1eefb;
    border-bottom: 1px solid var(--control-border);
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.4px;
    text-align: left;
    text-transform: uppercase;
}

.scripts-table td {
    padding: 5px;
    border-bottom: 1px solid var(--color-border);
    vertical-align: top;
}

.scripts-table tr:last-child td {
    border-bottom: 0;
}

.scripts-table code {
    padding: 1px 4px;
    border-radius: 3px;
    background: var(--color-bg);
    font-size: 10px;
}

.scripts-table__muted {
    display: block;
    color: var(--color-text-muted);
    font-size: 10px;
}

@media (max-width: 1100px) {
    .scripts-summary {
        grid-template-columns: repeat(2, minmax(140px, 1fr));
    }

    .scripts-metric--wide {
        grid-column: 1 / -1;
    }

    .scripts-service-panels,
    .scripts-details {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 720px) {
    .scripts-page {
        padding: 5px;
    }

    .scripts-page__header,
    .scripts-toolbar,
    .scripts-graph-hint {
        flex-direction: column;
        align-items: stretch;
    }

    .scripts-page__status,
    .scripts-graph-actions {
        flex-wrap: wrap;
    }

    .scripts-toolbar__note {
        text-align: left;
    }

    .scripts-summary {
        grid-template-columns: 1fr;
    }

    .scripts-graph-commandbar {
        align-items: stretch;
    }

    .scripts-graph-actions,
    .scripts-legend--bar,
    .scripts-graph-stats {
        flex: 1 1 100%;
    }

    .scripts-graph-host {
        min-height: 420px;
    }

    .scripts-panel__summary {
        align-items: flex-start;
    }

    .scripts-panel__summary span:last-child {
        display: none;
    }

    .scripts-legend-detail__row {
        grid-template-columns: 30px minmax(86px, 0.35fr) minmax(0, 1fr);
    }
}


/* ====================================================================
   M2M tag-picker + linked-chips (профили ↔ направления, миграция 065).
   Используется в /admin/profiles и /admin/directions: на listings —
   chip-сводка слинкованных сущностей; на edit-формах — теговый picker
   и non-editable preamble «Твоя зона ответственности — направления: …».
   ==================================================================== */

/* Chip-сводка в карточке списка (.rule-card__section). */
.link-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}

.link-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 5px;
    height: 22px;
    border-radius: 3px;
    background: #eef2ff;
    color: #3730a3;
    font-size: 10px;
    font-weight: 500;
    text-decoration: none;
    border: 1px solid #c7d2fe;
    transition: background 0.12s, border-color 0.12s;
}

.link-chip:hover {
    background: #e0e7ff;
    border-color: #a5b4fc;
}

.link-chip--off {
    opacity: 0.55;
    background: #f3f4f6;
    color: #6b7280;
    border-color: #e5e7eb;
}

/* Авто-приписка в карточке профиля (system_prompt). */
.rule-card__pre--auto {
    margin-top: 6px;
    background: #fdfce6;
    border-color: #fde68a;
    color: #78350f;
}

/* PR #528: /admin/profiles — compact inline editing card.
   Header: profile identity + typeahead directions + icon actions.
   Body: left prompt editor, right lightweight parameters area. */
.profile-card__head {
    grid-template-columns: minmax(180px, max-content) minmax(300px, 1fr) auto;
    align-items: start;
}

.profile-card__identity {
    min-width: 0;
}

.profile-card__slug {
    display: block;
    margin-top: 3px;
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}

.profile-card__dir-picker {
    max-width: none;
    min-height: 28px;
    align-self: start;
}

.profile-card__dir-picker--saving {
    opacity: 0.75;
}

.profile-card__dir-picker .rules-filter__chips {
    min-width: 0;
}

.profile-card__dir-picker .rules-filter__chip {
    max-width: 180px;
}

.profile-card__dir-picker .rules-filter__dropdown {
    right: auto;
    min-width: 0;
    width: 420px;
    max-width: calc(100vw - 40px);
}

.profile-card__body {
    display: grid;
    grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.75fr);
    gap: 16px;
    align-items: stretch;
}

.profile-card__prompt {
    min-width: 0;
    margin-top: 0;
}

.profile-card__prompt-editor {
    min-height: 200px;
}

.profile-card__params {
    min-width: 0;
    padding-left: 5px;
    border-left: 1px solid var(--color-border);
}

.profile-card__params-title {
    margin-bottom: 6px;
    color: var(--color-text-muted);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.5px;
    text-transform: uppercase;
}

.profile-card__param-grid {
    display: grid;
    grid-template-columns: minmax(84px, max-content) minmax(0, 1fr);
    gap: 5px 10px;
    margin: 0;
    font-size: 10px;
}

.profile-card__param-grid dt {
    color: var(--color-text-muted);
}

.profile-card__param-grid dd {
    min-width: 0;
    margin: 0;
    overflow-wrap: anywhere;
    color: var(--color-text);
    font-family: var(--font-mono, monospace);
}

/* PR #533: inline-controls в правой колонке карточки профиля. Соответствуют
   полям form'ы редактирования (model / description / temperature / max_tokens).
   Cream-фон + светлая рамка как hint «здесь можно править» (стиль зеркалит
   .tool-card__desc и .tool-card__schema-desc из PR #527). */
.profile-card__inline-select,
.profile-card__inline-number,
.direction-card__inline-select,
.direction-card__inline-number {
    width: 100%;
    padding: 3px 5px;
    border: 1px solid #f5edd0;
    border-radius: 3px;
    background: #fffdf2;
    color: var(--color-text);
    font-family: var(--font-mono, monospace);
    font-size: 10px;
    line-height: 1.4;
    transition: background 120ms, border-color 120ms;
}
.profile-card__inline-select:hover,
.profile-card__inline-number:hover,
.direction-card__inline-select:hover,
.direction-card__inline-number:hover {
    background: #fffaea;
    border-color: #e8d68a;
}
.profile-card__inline-select:focus,
.profile-card__inline-number:focus,
.direction-card__inline-select:focus,
.direction-card__inline-number:focus {
    background: #fef9c3;
    border-color: #fde047;
    outline: none;
}
.profile-card__inline-text {
    display: inline-block;
    min-width: 60px;
    width: 100%;
    padding: 3px 5px;
    border: 1px solid #f5edd0;
    border-radius: 3px;
    background: #fffdf2;
    cursor: text;
    white-space: pre-wrap;
    transition: background 120ms, border-color 120ms;
}
.profile-card__inline-text:hover {
    background: #fffaea;
    border-color: #e8d68a;
}
.profile-card__inline-text.is-editing {
    background: #fef9c3;
    border-color: #fde047;
    outline: none;
}
.profile-card__inline-text:empty::before {
    content: attr(data-placeholder);
    color: var(--color-text-muted);
    font-style: italic;
}

/* PR #535: direction-card layout — зеркало profile-card. head 3-col,
   body 2-col (description слева, params справа). Profile-picker
   использует те же .rules-filter__multi-стили что и dir-picker
   в profile cards. */
.direction-card__head {
    display: grid;
    grid-template-columns: minmax(220px, max-content) minmax(0, 1fr) auto;
    gap: 12px;
    align-items: start;
}
.direction-card__identity {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 0;
}
.direction-card__slug {
    color: var(--color-text-muted);
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}
.direction-card__profile-picker {
    align-self: center;
}
.direction-card__profile-picker.direction-card__profile-picker--saving {
    opacity: 0.6;
    pointer-events: none;
}
.direction-card__body {
    display: grid;
    grid-template-columns: 1fr minmax(220px, 280px);
    gap: 14px;
    margin-top: 8px;
}
.direction-card__desc-section {
    min-width: 0;
}
.direction-card__params {
    /* Стили совпадают с .profile-card__params — дублируем класс в шаблоне. */
}

/* Inline-editable name/slug в header'е карточки профиля. */
.profile-card__name-edit,
.profile-card__slug-edit,
.direction-card__name-edit,
.direction-card__slug-edit {
    cursor: text;
    border: 1px solid transparent;
    border-radius: 3px;
    padding: 1px 4px;
    transition: background 120ms, border-color 120ms;
}
.profile-card__name-edit:hover,
.profile-card__slug-edit:hover,
.direction-card__name-edit:hover,
.direction-card__slug-edit:hover {
    background: #fffaea;
    border-color: #e8d68a;
}
.profile-card__name-edit.is-editing,
.profile-card__slug-edit.is-editing,
.direction-card__name-edit.is-editing,
.direction-card__slug-edit.is-editing {
    background: #fef9c3;
    border-color: #fde047;
    outline: none;
}

/* Read-only «Скрипт» — chips, derived из направлений. */
.profile-card__scripts {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    align-items: center;
}
.profile-card__script-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 5px;
    border: 1px solid #cbd5e1;
    border-radius: 3px;
    background: #f1f5f9;
    color: #334155;
    font-family: var(--font-mono, monospace);
    font-size: 10px;
}
.profile-card__empty {
    color: var(--color-text-muted);
    font-style: italic;
}
.profile-card__scripts-hint {
    flex-basis: 100%;
    margin-top: 2px;
    color: var(--color-text-muted);
    font-size: 10px;
    font-style: italic;
}

.rule-card__icon-btn:disabled,
.rule-card__icon-btn--disabled,
.rule-card__icon-btn--disabled:hover {
    opacity: 0.45;
    cursor: not-allowed;
    background: var(--color-bg);
    border-color: var(--color-border);
    color: var(--color-text-muted);
}

@media (max-width: 980px) {
    .profile-card__head {
        grid-template-columns: 1fr auto;
    }

    .profile-card__dir-picker {
        grid-column: 1 / -1;
    }

    .profile-card__body {
        grid-template-columns: 1fr;
    }

    .profile-card__params {
        padding-left: 0;
        padding-top: 5px;
        border-left: 0;
        border-top: 1px solid var(--color-border);
    }
}

/* Picker'ный fieldset на форме. */
.m2m-picker {
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid #e5e7eb;
    border-radius: 3px;
    padding: 5px 5px;
    background: #fafafa;
}

.m2m-picker > legend {
    padding: 0 5px;
    font-weight: 600;
}

.m2m-picker__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-height: 26px;
}

.m2m-picker__chips:empty::before {
    content: "Пусто — выберите ниже";
    color: #9ca3af;
    font-size: 10px;
    font-style: italic;
    align-self: center;
}

.m2m-picker__select {
    max-width: 360px;
}

/* Сами chip'ы внутри picker'а — крупнее, чтобы тыкать кнопкой ×. */
.m2m-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 4px 3px 5px;
    height: 26px;
    border-radius: 3px;
    background: #ede9fe;
    color: #4c1d95;
    border: 1px solid #c4b5fd;
    font-size: 10px;
    font-weight: 500;
}

.m2m-chip__label {
    line-height: 1;
}

/* PR #436 hotfix: убрано duplicate broken `.m2m-chip__remove` rule
   с обрывом `border-radius: 50` без `%;}` — браузер не закрывал rule,
   merging subsequent rules в gigantic broken nested rule, что ломало
   парсинг и применение .factology-grid стилей ниже по файлу.
   Полная корректная версия `.m2m-chip__remove` — строка ~8466. */

/* ====================================================================
   M2M tag-picker + linked-chips (профили ↔ направления, миграция 065).
   Используется в /admin/profiles и /admin/directions: на listings —
   chip-сводка слинкованных сущностей; на edit-формах — теговый picker
   и non-editable preamble «Твоя зона ответственности — направления: …».
   ==================================================================== */

/* Chip-сводка в карточке списка (.rule-card__section). */
.link-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
}

.link-chip {
    display: inline-flex;
    align-items: center;
    padding: 2px 5px;
    height: 22px;
    border-radius: 3px;
    background: #eef2ff;
    color: #3730a3;
    font-size: 10px;
    font-weight: 500;
    text-decoration: none;
    border: 1px solid #c7d2fe;
    transition: background 0.12s, border-color 0.12s;
}

.link-chip:hover {
    background: #e0e7ff;
    border-color: #a5b4fc;
}

.link-chip--off {
    opacity: 0.55;
    background: #f3f4f6;
    color: #6b7280;
    border-color: #e5e7eb;
}

/* Авто-приписка в карточке профиля (system_prompt). */
.rule-card__pre--auto {
    margin-top: 6px;
    background: #fdfce6;
    border-color: #fde68a;
    color: #78350f;
}

/* Picker-ный fieldset на форме. */
.m2m-picker {
    display: flex;
    flex-direction: column;
    gap: 8px;
    border: 1px solid #e5e7eb;
    border-radius: 3px;
    padding: 5px 5px;
    background: #fafafa;
}

.m2m-picker > legend {
    padding: 0 5px;
    font-weight: 600;
}

.m2m-picker__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    min-height: 26px;
}

.m2m-picker__chips:empty::before {
    content: "Пусто — выберите ниже";
    color: #9ca3af;
    font-size: 10px;
    font-style: italic;
    align-self: center;
}

.m2m-picker__select {
    max-width: 360px;
}

/* Сами chip-ы внутри picker-а — крупнее, чтобы тыкать кнопкой ×. */
.m2m-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 4px 3px 5px;
    height: 26px;
    border-radius: 3px;
    background: #ede9fe;
    color: #4c1d95;
    border: 1px solid #c4b5fd;
    font-size: 10px;
    font-weight: 500;
}

.m2m-chip__label {
    line-height: 1;
}

.m2m-chip__remove {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--color-busy-text);
    cursor: pointer;
    font-size: 10px;
    line-height: 1;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.m2m-chip__remove:hover {
    background: #c4b5fd;
    color: #ffffff;
}

/* Zone-preamble под textarea — non-editable «приписка к prompt-у». */
.zone-preamble {
    margin-top: 8px;
    padding: 5px 5px;
    border: 1px dashed #fde68a;
    border-radius: 3px;
    background: #fffbeb;
}

.zone-preamble__label {
    display: block;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #92400e;
    margin-bottom: 4px;
}

.zone-preamble__text {
    margin: 0;
    font-family: inherit;
    white-space: pre-wrap;
    color: #78350f;
    font-size: 10px;
    line-height: 1.4;
}

.zone-preamble__text--empty {
    color: #9ca3af;
    font-style: italic;
}


/* ====================================================================
   Раздел «Фактология» в /admin/scripts — редактор шаблона инструкций
   экстрактора фактов. См. app/services/system_settings.py
   load/save_extract_facts_template + app_settings.extract_facts_instructions.
   ==================================================================== */

/* Фактология — CRUD для секций (top_level / intent / item / proposal).
   Равные колонки по числу секций (data-driven из FACTOLOGY_SECTIONS),
   каждое поле в карточке {name + type + description} с ×-удалением и
   +-добавлением. */
.factology-panel__head-actions {
    margin-left: auto;
}
.factology-preview-btn {
    font-size: 10px;
    padding: 3px 5px;
}
.factology-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-top: 8px;
    width: 100%;
}
.factology-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
    max-height: 60vh;
    background: #ffffff;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    overflow: hidden;
}
.factology-col__head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 5px 5px;
    background: #f8fafc;
    border-bottom: 1px solid #e2e8f0;
    flex: 0 0 auto;
}
.factology-col__label {
    font-size: 10px;
    font-weight: 700;
    color: #1e293b;
}
.factology-col__key {
    font-size: 10.5px;
    color: #94a3b8;
    margin-left: auto;
    background: rgba(99, 102, 241, 0.08);
    padding: 1px 5px;
    border-radius: 3px;
}
.factology-col__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 5px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: #f8fafc;
}
.factology-col__add {
    flex: 0 0 auto;
    margin: 6px 8px;
    padding: 5px 5px;
    border: 1px dashed #94a3b8;
    border-radius: 3px;
    background: transparent;
    color: #64748b;
    font-size: 11.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background 100ms, color 100ms, border-color 100ms;
}
.factology-col__add:hover {
    background: #f1f5f9;
    color: #1e293b;
    border-color: #64748b;
}

/* Карточка поля: ×-удаление в правом верхнем, name+type на одной
   строке, description ниже. */
.factology-field-card {
    position: relative;
    background: #ffffff;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    padding: 5px 5px 5px 5px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
    line-height: 1.45;
}
.factology-field-card__delete {
    position: absolute;
    top: 2px;
    right: 4px;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #94a3b8;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    transition: background 100ms, color 100ms;
}
.factology-field-card__delete:hover {
    background: #fee2e2;
    color: var(--color-danger);
}
/* === Системные readonly-поля (движок пишет в коде, не экстрактор) ========
   Отдельный блок под редактируемыми: dashed-разделитель, приглушённый фон,
   серый текст и cursor:default — визуально «смотреть, но не трогать». Нет
   delete-кнопки и contenteditable (разметка их не рендерит). */
.factology-col__system {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed #cbd5e1;
    display: flex;
    flex-direction: column;
    gap: 5px;
}
.factology-col__system-label {
    font-size: 10.5px;
    font-weight: 600;
    color: #64748b;
    letter-spacing: 0.02em;
    cursor: help;
}
.factology-field-card--system {
    background: #f8fafc;
    border-style: dashed;
    cursor: default;
}
.factology-field-card--system .factology-field-card__name,
.factology-field-card--system .factology-field-card__type,
.factology-field-card--system .factology-field-card__desc {
    cursor: default;
    color: #64748b;
}
.factology-field-card--system .factology-field-card__name {
    color: #475569;            /* serln-600 — темнее, но НЕ синий редактируемого */
    font-weight: 600;
}
.factology-field-card__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
    margin-bottom: 4px;
}
/* PR #435: имя поля — тёмно-зелёный жирный (требование пользователя). */
.factology-field-card__name {
    /* #391: имена полей фактологии — синие (конвенция, совпадает с
       .prompt-facts в промтах). Раньше были зелёные. */
    color: #1e40af;            /* blue-800 — как .prompt-facts */
    font-weight: 700;
    min-width: 60px;
    outline: 0 solid transparent;
    border-radius: 3px;
    padding: 0 3px;
    transition: background-color 120ms;
}
/* PR #435: тип — чёрный жирный. */
.factology-field-card__type {
    color: #1e293b;
    font-weight: 700;
    flex: 1 1 auto;
    min-width: 100px;
    outline: 0 solid transparent;
    border-radius: 3px;
    padding: 0 3px;
    transition: background-color 120ms;
}
/* PR #435: описание/семантика — серый обычный. */
.factology-field-card__desc {
    color: #64748b;
    font-weight: 400;
    outline: 0 solid transparent;
    border-radius: 3px;
    padding: 0 3px;
    min-height: 14px;
    transition: background-color 120ms;
}
/* Focus → горчично-жёлтый (общая UX-механика). */
.factology-field-card__name:focus,
.factology-field-card__type:focus,
.factology-field-card__desc:focus,
.factology-field-card__name.is-editing,
.factology-field-card__type.is-editing,
.factology-field-card__desc.is-editing {
    background-color: #fef3c7 !important;
    outline: 2px solid #f59e0b;
    outline-offset: -1px;
}
/* Empty-state placeholder через CSS (показывается в пустом
   contenteditable с data-placeholder). */
.factology-field-card__name[data-placeholder]:empty::before,
.factology-field-card__type[data-placeholder]:empty::before,
.factology-field-card__desc[data-placeholder]:empty::before {
    content: attr(data-placeholder);
    color: #cbd5e1;
    font-style: italic;
    pointer-events: none;
}

/* Toast/revert для card-полей (общие с node editors). */
.factology-field-card .scripts-node__save-toast,
.factology-field-card .scripts-node__revert-btn {
    bottom: -2px;
    right: 24px;          /* не перекрывает ×-кнопку */
    font-size: 10px;
    padding: 2px 5px;
}

/* PR #435: Превью JSON modal. */
.factology-preview-overlay {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}
.factology-preview-overlay[hidden] { display: none; }
.factology-preview-modal {
    width: min(900px, 92vw);
    max-height: 86vh;
    display: flex;
    flex-direction: column;
    background: #ffffff;
    border-radius: 3px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.3);
    overflow: hidden;
}
.factology-preview-modal__head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 5px 5px;
    border-bottom: 1px solid #e2e8f0;
    background: #f8fafc;
}
.factology-preview-modal__head h3 {
    flex: 1 1 auto;
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    color: #1e293b;
}
.factology-preview-modal__close {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #64748b;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}
.factology-preview-modal__close:hover {
    background: #e2e8f0;
    color: #1e293b;
}
.factology-preview-modal__body {
    flex: 1 1 auto;
    overflow: auto;
    padding: 5px 5px;
    margin: 0;
    background: #ffffff;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    line-height: 1.5;
    white-space: pre;
    color: #1e293b;
}
/* PR #435: подсветка в превью. */
.factology-preview-modal__body .preview-name {
    /* #391: имена полей фактологии — тёмно-синие (конвенция, как
       .prompt-facts / .factology-field-card__name). Были зелёные. */
    color: #1e40af;            /* blue-800 — тёмно-синий жирный */
    font-weight: 700;
}
.factology-preview-modal__body .preview-type {
    color: #1e293b;            /* чёрный жирный */
    font-weight: 700;
}
.factology-preview-modal__body .preview-desc {
    color: #94a3b8;            /* серый обычный */
    font-weight: 400;
}

/* Средний экран — 4 колонки тесны, раскладываем 2×2. */
@media (max-width: 1400px) and (min-width: 1101px) {
    .factology-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* Узкий экран — стэк вертикально. */
@media (max-width: 1100px) {
    .factology-grid {
        grid-template-columns: 1fr;
    }
    .factology-col {
        max-height: 40vh;
    }
}

/* ADR-0014: блок «Промт экстрактора фактов» — inline-textarea + «Сброс».
   Компактные паддинги (вдвое, #1582-style). Position:relative — якорь для
   toast (.rule-card__save-toast) и кнопки revert. */
.extract-prompt-block {
    position: relative;
    margin-top: 12px;
    padding-top: 5px;
    border-top: 1px solid #e2e8f0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.extract-prompt-block__head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.extract-prompt-block__title {
    font-size: 13px;
    font-weight: 700;
    color: #1e293b;
}
.extract-prompt-block__badge {
    font-size: 10.5px;
    padding: 1px 5px;
    border-radius: 3px;
    background: #f1f5f9;
    color: #64748b;
    white-space: nowrap;
}
.extract-prompt-block__badge.is-custom {
    background: #fef3c7;
    color: var(--color-warn-text);
}
.extract-prompt-block__reset {
    margin-left: auto;
    font-size: 10px;
    padding: 3px 5px;
}
/* Якорь для toast/revert (top-right textarea, не перекрывая «Сброс»). */
.extract-prompt-block__editor-wrap {
    position: relative;
    display: flex;
}
.extract-prompt-block__editor {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    min-height: 220px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
    line-height: 1.45;
    padding: 5px 5px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    background: #ffffff;
    color: #1f2937;
    resize: vertical;
    tab-size: 2;
    /* #391: contenteditable — переносим длинные строки и показываем
       text-каретку; подсветка-спаны (.prompt-facts/.prompt-bad/…) рендерятся
       инлайн. */
    white-space: pre-wrap;
    word-break: break-word;
    cursor: text;
    overflow: auto;
    transition: background-color 120ms, border-color 120ms;
}
/* Focus → горчично-жёлтый (общая UX-механика inline-edit фактологии). */
.extract-prompt-block__editor:focus,
.extract-prompt-block__editor.is-editing {
    background-color: #fffbeb;
    outline: 2px solid #f59e0b;
    outline-offset: -1px;
    border-color: #f59e0b;
}
.extract-prompt-block__hint {
    margin: 0;
    font-size: 10px;
    color: #94a3b8;
}

/* === Узел главной задачи: селектор НАД сеткой полей =======================
   Блок-«мишень» (красная палитра — тон серверной иконки 🎯 на канвасе).
   Селектор пишет в единый write-path; Observer перевешивает мишень. */
.main-task-block {
    margin: 4px 0 12px;
    padding: 8px 10px;
    border: 1px solid #fecaca;
    border-radius: 4px;
    background: #fef2f2;
    display: flex;
    flex-direction: column;
    gap: 6px;
}
.main-task-block__head {
    display: flex;
    align-items: center;
    gap: 8px;
}
.main-task-block__title {
    font-size: 13px;
    font-weight: 700;
    color: #991b1b;
}
.main-task-block__status {
    margin-left: auto;
    font-size: 11.5px;
    font-weight: 600;
    color: #047857;
}
.main-task-block__status.is-error {
    color: var(--color-danger);
}
.main-task-block__hint {
    margin: 0;
    font-size: 10.5px;
    line-height: 1.45;
    color: #6b7280;
}
.main-task-block__select-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    font-weight: 600;
    color: #374151;
}
.main-task-block__select {
    flex: 1;
    min-width: 0;
    font-size: 12px;
    padding: 4px 6px;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    background: #ffffff;
    color: #1f2937;
}
.main-task-block__select:focus {
    outline: 2px solid #dc2626;
    outline-offset: -1px;
    border-color: #dc2626;
}

/* Две per-field галки карточки поля («в гл. задачу» + «значимо для jump»),
   стопкой под описанием. Подсветка карточки — :has (современный Chrome). */
.factology-field-card__flags {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: 4px;
}
.factology-field-card__reqfact,
.factology-field-card__jumpsig {
    display: flex;
    align-items: center;
    gap: 4px;
    font-size: 10px;
    color: #64748b;
    cursor: pointer;
    user-select: none;
}
.factology-field-card__reqfact input,
.factology-field-card__jumpsig input {
    margin: 0;
    cursor: pointer;
}
.factology-field-card__reqfact input {
    accent-color: #dc2626;    /* красный — тема узла главной задачи (🎯) */
}
.factology-field-card__jumpsig input {
    accent-color: #f59e0b;    /* янтарный — jump-значимость в facts-diff */
}
.factology-field-card__reqfact input:disabled {
    cursor: not-allowed;
}
/* disabled (нет главной задачи) гасит ТОЛЬКО required-галку, не jump-галку. */
.factology-field-card__reqfact:has(input:disabled) {
    opacity: 0.4;
    cursor: not-allowed;
}
.factology-field-card:has([data-required-fact]:checked) {
    border-color: #fca5a5;
    background: #fef2f2;
}
/* jump_significant снят (поле игнорируется в diff) — тусклое имя-метка галки. */
.factology-field-card__jumpsig:not(:has(input:checked)) {
    color: #94a3b8;
}

.scripts-factology-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 8px;
}

.scripts-factology-form__textarea {
    /* PR #425: 100% ширины контейнера — был 50% но при wide-layout
       выглядел как «узкая колонка». Длинные строки JSON-примеров
       пусть скроллятся горизонтально (white-space: pre + overflow auto). */
    width: 100%;
    min-width: 0;
    min-height: 480px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    line-height: 1.45;
    padding: 5px 5px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    background: #ffffff;
    color: #1f2937;
    /* resize: vertical — горизонталь не нужна, ширина уже 100%. */
    resize: vertical;
    tab-size: 2;
    white-space: pre;
    overflow: auto;
}

.scripts-factology-form__textarea:focus {
    outline: 2px solid #6366f1;
    outline-offset: -2px;
    border-color: #6366f1;
}

.scripts-factology-form__actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
}


/* ====================================================================
   LLMSwitch «?»-кнопка и popup-редактор гейта (PR-070).
   Кнопка живёт на ромбе, по клику открывает overlay с двумя textarea
   для редактирования question + descriptions всех веток.
   ==================================================================== */

/* «?»-кнопка на ромбе LLMSwitch. Ромб широк только в y=50%; в верхнем
   углу bbox он сужается в точку и кнопка визуально вылезает за грани
   полигона. Ставим в правом углу по вертикальному центру (там полигон
   занимает всю ширину). */
.scripts-node__gate-btn {
    position: absolute;
    top: 50%;
    right: 10px;
    transform: translateY(-50%);
    z-index: 3;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 2px solid #475569;
    background: #ffffff;
    color: #1f2937;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}

.scripts-node__gate-btn:hover {
    background: #475569;
    color: #ffffff;
}

.scripts-node__gate-btn:focus-visible {
    outline: 2px solid #6366f1;
    outline-offset: 2px;
}

/* Popup-форма редактора гейта (изолированный namespace gate-modal).
   Не наследуется от .scripts-popover, чтобы не воевать с его
   width/display и не получать сюрпризы от нативных <label>/<form>
   defaults. Все ключевые свойства явно прописаны. */
.gate-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}

.gate-modal[hidden] { display: none; }

.gate-modal__panel {
    box-sizing: border-box;
    width: min(820px, 94vw);
    max-height: 90vh;
    display: flex;
    flex-direction: column;
    background: #ffffff;
    border-radius: 3px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.30);
    overflow: hidden;
    margin: 0;
    padding: 0;
}

.gate-modal__head {
    box-sizing: border-box;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 5px 5px;
    border-bottom: 1px solid #e2e8f0;
    background: #f8fafc;
}

.gate-modal__title {
    flex: 1 1 auto;
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    color: #1e293b;
}

.gate-modal__close {
    flex: 0 0 auto;
    width: 28px;
    height: 28px;
    border: 0;
    border-radius: 3px;
    background: transparent;
    color: #64748b;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
}

.gate-modal__close:hover {
    background: #e2e8f0;
    color: #1e293b;
}

.gate-modal__body {
    box-sizing: border-box;
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 5px 5px;
    display: flex;
    flex-direction: column;
    gap: 18px;
}

.gate-modal__field {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
}

.gate-modal__label {
    display: block;
    font-size: 10px;
    font-weight: 600;
    color: #1f2937;
    margin: 0;
}

.gate-modal__hint {
    margin: 0;
    color: #6b7280;
    font-size: 10px;
    line-height: 1.4;
}

.gate-modal__textarea {
    box-sizing: border-box;
    display: block;
    width: 100%;
    min-height: 180px;
    padding: 5px 5px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    background: #ffffff;
    color: #1f2937;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    line-height: 1.5;
    resize: vertical;
    tab-size: 2;
}

.gate-modal__textarea:focus {
    outline: 2px solid #6366f1;
    outline-offset: -2px;
    border-color: #6366f1;
}

.gate-modal__actions {
    box-sizing: border-box;
    flex: 0 0 auto;
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 5px 5px;
    border-top: 1px solid #e5e7eb;
    background: #fafafa;
}


/* ====================================================================
   Trace UI: индикатор «бот ждёт клиента» в конце turn'а.
   Показывает где engine остановился (waiting at node X) или что
   диалог достиг terminal (ended).
   ==================================================================== */

.tflow__waiting {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 6px 0 12px;
    padding: 5px 5px;
    border: 1px dashed #94a3b8;
    border-radius: 3px;
    background: #f8fafc;
    color: #475569;
    font-size: 10px;
}

.tflow__waiting--ended {
    border-color: #e5e7eb;
    background: #f1f5f9;
    color: #64748b;
}
/* PR #416: engine не дошёл до узла (API-сбой) — warning-стиль */
.tflow__waiting--failed {
    border-color: #fcd34d;
    background: #fef3c7;
    color: #78350f;
    font-weight: 500;
}

.tflow__waiting-dot {
    flex: 0 0 auto;
    font-size: 10px;
}

.tflow__waiting-text {
    flex: 1 1 auto;
}

.tflow__waiting-text code {
    margin-left: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
}


/* Escalation banner в trace UI — красная плашка наверху ленты, видно
   что диалог технически перевёлся в escalated и бот не отвечает. */
.tflow__status-banner {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 6px 0 12px;
    padding: 5px 5px;
    border-radius: 3px;
    font-size: 10px;
}

.tflow__status-banner--escalated {
    background: #fef2f2;
    border: 1px solid #fecaca;
    color: #991b1b;
}

/* #413: УСПЕШНАЯ эскалация (CRM-handoff, узел типа success_escalation) —
   салатовая плашка вместо красной. Палитра сверена с in-node green-блоком
   .tflow__escalation-ctx--success (тот же data-driven сигнал — ТИП узла).
   Красные акценты вложенных элементов (reason / source-pill / reply-
   separator / reply-label) переопределяются на зелёные, чтобы плашка была
   однотонно-салатовой. */
.tflow__status-banner--escalated-success {
    background: #f0fdf4;
    border: 1px solid #bbf7d0;
    color: #166534;
}
.tflow__status-banner--escalated-success .tflow__status-reason {
    color: #166534;
}
.tflow__status-banner--escalated-success .tflow__status-source,
.tflow__status-banner--escalated-success .tflow__status-rule {
    background: rgba(22, 163, 74, 0.12);
    color: #166534;
}
.tflow__status-banner--escalated-success .tflow__status-reply {
    border-top-color: rgba(22, 163, 74, 0.28);
}
.tflow__status-banner--escalated-success .tflow__status-reply-label {
    color: #166534;
}

.tflow__status-icon {
    flex: 0 0 auto;
    font-size: 10px;
    line-height: 1;
    margin-top: 1px;
}

.tflow__status-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tflow__status-reason {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
    color: #7f1d1d;
    word-break: break-word;
}

.tflow__status-hint {
    font-size: 10px;
}


/* P406: баннер аварийного завершения flow (api_error/loop/max_hops/
   exception). Визуально СИЛЬНЕЕ escalation-плашки (авария ≠ бизнес-
   эскалация): насыщенный фон + толстая левая кромка-акцент. */
.tflow__status-banner--failure {
    background: #fff1f2;
    border: 1px solid #fca5a5;
    border-left: 5px solid var(--color-danger);
    color: #7f1d1d;
}
.tflow__failure-kind {
    align-self: flex-start;
    background: var(--color-danger);
    color: #fff;
    font-size: 10px;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 3px;
    letter-spacing: 0.2px;
}
.tflow__failure-route {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin-top: 2px;
    font-size: 10px;
}
.tflow__failure-route-label {
    color: #7f1d1d;
    font-weight: 600;
    margin-right: 2px;
}
/* Подсветка slug'ов маршрута/цикла аварии. Базовый чип — нейтрально-
   красный; цикл (loop) — ярче (заливка), чтобы выделить зациклившийся
   сегмент. */
.flow-route-slug {
    display: inline-block;
    background: #fee2e2;
    border: 1px solid #fca5a5;
    color: #991b1b;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
    padding: 0 5px;
    border-radius: 3px;
}
.flow-route-slug--cycle {
    background: var(--color-danger);
    border-color: #b91c1c;
    color: #fff;
    font-weight: 700;
}
.flow-route-arrow {
    color: #b91c1c;
    font-size: 10px;
}
.tflow__failure-trace {
    margin-top: 4px;
    font-size: 10px;
}
.tflow__failure-trace > summary {
    cursor: pointer;
    color: #7f1d1d;
    font-weight: 500;
}
.tflow__failure-trace-pre {
    margin: 6px 0 0;
    padding: 5px 5px;
    background: #1f2937;
    color: #f3f4f6;
    border-radius: 3px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 10px;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 320px;
    overflow: auto;
}


/* PR P20: hint под inbound клиентом если turn пустой (visits=0,
   no bot reply) И диалог уже escalated/closed. Юзер на ревью:
   «реплика клиента как будто уехала из-за временного рассинхрона».
   На самом деле реплика пришла ПОСЛЕ handoff'а, engine не
   обрабатывает её (передано оператору). Hint объясняет это
   визуально — серый info-блок под inbound. */
.tflow__orphan-inbound-hint {
    margin: 2px 0 8px 24px;
    padding: 4px 5px;
    background: #f3f4f6;
    border-left: 2px dashed #9ca3af;
    border-radius: 3px;
    font-size: 10px;
    color: #4b5563;
    font-style: italic;
}

/* PR P19: переход TO следующий visit вызван rules_jump (не auto_continue).
   Юзер на ревью dialog 6 указал: рисовать badge [RULES_JUMP #id] вместо
   «авто-переход: сразу же». jump_rules подавил auto_continue, поэтому
   реальная семантика перехода — RULES_JUMP. Визуально: красный
   фон (как у эскалации), badge с rule_id. */
.tflow__transition--jump {
    background: #fef2f2;
    border-left: 3px solid #b91c1c;
    /* PR P19.5: padding симметричный (8px с обеих сторон), раньше было
       только padding-left:5px — сдвигало icon вправо от общей оси.
       Юзер на ревью: «почему сдвинуто вправо? на общую ось подвинь
       влево». Теперь icon выравнен с центром остальных transition'ов. */
    padding: 4px 5px;
    border-radius: 3px;
}
.tflow__transition--jump .tflow__transition-glyph,
.tflow__transition--jump .tflow__transition-label,
.tflow__transition--jump .tflow__signal-icon {
    color: #7f1d1d;
    font-weight: 600;
}
.tflow__transition-jump-badge {
    display: inline-block;
    background: #b91c1c;
    color: #fff;
    font-size: 10.5px;
    font-weight: 700;
    padding: 1px 5px;
    border-radius: 3px;
    letter-spacing: 0.3px;
    margin-right: 6px;
}

/* PR P19.5: полный JSON jump_context под transition badge — collapsible
   details. Юзер на ревью: «мне нужна полная информация с JSON
   jump_context при любом jump-rule переходе». В свёрнутом состоянии —
   мини-summary с пунктирной рамкой; при клике показывает форматированный
   JSON в beige блоке с моноширинным шрифтом. */
.tflow__transition-jc {
    margin-top: 4px;
    width: 100%;
    max-width: 520px;
    font-size: 10px;
}
.tflow__transition-jc > summary {
    cursor: pointer;
    color: #7f1d1d;
    font-weight: 500;
    padding: 2px 5px;
    background: #fff5f5;
    border: 1px dashed #fca5a5;
    border-radius: 3px;
    list-style: none;
    user-select: none;
    display: inline-block;
}
.tflow__transition-jc > summary::-webkit-details-marker { display: none; }
.tflow__transition-jc[open] > summary {
    background: #fee2e2;
    border-style: solid;
}
.tflow__transition-jc > pre {
    margin: 4px 0 0;
    padding: 5px 5px;
    background: #fffbeb;
    border: 1px solid #fcd34d;
    border-radius: 3px;
    font-size: 10px;
    line-height: 1.4;
    white-space: pre-wrap;
    word-break: break-word;
    font-family: var(--font-mono, monospace);
    color: #1f2937;
}

/* Sub-task B (юзерский запрос 2026-05-25): дифференциация стрелок
   transitions по 3 типам через transition_kind:
   - auto      → серая ↓ + 'сразу же'      (--auto уже определён выше)
   - branch    → бордовая ↓ + 'ветвь: <n>' (LLMSwitch/Switch result)
   - jump_rule → бордовая ↓ + 'jump #..→.' (apply_jump_rules trigger,
                                            визуально как --jump)
   Базовый .tflow__transition-glyph + -label уже бордовые (#630633)
   по умолчанию, --branch — это явный semantic marker (для будущих
   override'ов и a11y). */

/* Branch-переход БЕЗ фоновой подложки — она зарезервирована за
   сработавшим jump (.tflow__transition--jump). Branch выделяется только
   цветом glyph/label (см. ниже), как обычный переход без панели. */
.tflow__transition--branch .tflow__transition-glyph,
.tflow__transition--branch .tflow__transition-label,
.tflow__transition--branch .tflow__signal-icon {
    color: #630633;
    font-weight: 600;
}

/* Sub-task B: branch-label с inline-кодом названия ветви — бордовый
   pill с code-родом, чтобы slug ветви читался как identifier. */
.tflow__transition-label--branch code {
    background: rgba(99, 6, 51, 0.08);
    padding: 1px 4px;
    border-radius: 3px;
    color: #630633;
    font-weight: 600;
}

/* Sub-task B: jump_rule визуально наследует базовый бордовый стиль
   и существующий .tflow__transition--jump (см. выше, #b91c1c —
   более насыщенный красный для эскалационных jump'ов). Никаких
   override'ов здесь не нужно — kind-class --jump_rule добавлен
   как semantic marker. */
.tflow__transition--jump_rule {
    /* Marker class — стили приходят из --jump (см. выше). */
}

/* ====================================================================
   P152-H: Информативные стрелки переходов (Data > Code).

   Кейс: engine «намеревался» сделать естественный переход (default next
   или LLMSwitch-ветка), но jump-rule перебил. Юзеру важно видеть и
   «что должно было быть» и «что фактически произошло» — фактическая
   jump-стрелка остаётся на центральной оси, подавленная рисуется рядом
   слева как пунктирная зачёркнутая стрелка вниз.

   Эти модификаторы добавлены вместо if-else в шаблоне: kind enum
   → CSS-modifier, без условной логики в template'е (P98+P99-style).
   ==================================================================== */

/* Side-by-side layout: suppressed arrow sits slightly left of the central
   jump arrow, not as a separate row in the trace. */
.tflow__transition--with-suppressed {
    position: relative;
}

.tflow__transition-suppressed-side {
    position: absolute;
    top: 50%;
    right: calc(50% + 10px);
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    width: max-content;
    max-width: min(360px, calc(100vw - 48px));
    transform: translateY(-50%);
}

.tflow__transition-suppressed-label {
    font-family: var(--font-mono, monospace);
    font-size: 10.5px;
    line-height: 1.25;
    font-weight: 700;
    text-transform: lowercase;
    letter-spacing: 0.02em;
    color: #64748b;
    background: #f8fafc;
    border: 1px dashed #94a3b8;
    border-radius: 3px;
    padding: 1px 5px;
    white-space: nowrap;
    overflow: visible;
    text-overflow: clip;
    max-width: 320px;
}
.tflow__transition-suppressed-label code {
    background: transparent;
    color: inherit;
    padding: 0;
}

.tflow__transition-suppressed-side--suppressed_branch
.tflow__transition-suppressed-label {
    color: #7a1f1f;
    background: #fff5f5;
    border-color: #8b3a3a;
}

.tflow__transition-suppressed-glyph {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    color: #64748b;
    opacity: 0.8;
}
.tflow__transition-suppressed-side--suppressed_branch
.tflow__transition-suppressed-glyph {
    color: #8b3a3a;
}
.tflow__transition-suppressed-glyph .tflow__arrow-down-svg path {
    stroke-dasharray: 3 3;
}
.tflow__transition-suppressed-glyph::after {
    content: "";
    position: absolute;
    left: -1px;
    right: -1px;
    top: 50%;
    border-top: 2px solid currentColor;
    transform: rotate(-32deg);
    transform-origin: center;
}

@media (max-width: 720px) {
    .tflow__transition-suppressed-side {
        right: calc(50% + 6px);
        gap: 4px;
        max-width: calc(50vw - 16px);
    }

    .tflow__transition-suppressed-label {
        max-width: calc(50vw - 40px);
        white-space: normal;
        overflow-wrap: anywhere;
        text-align: right;
    }
}

.tflow__transition-label--right {
    /* Дефолт template'а — для нормальных переходов (column-flex,
       порядок: signal-icon → glyph → label сверху-вниз). */
}


/* ====================================================================
   «Узел главной задачи» (PR-Goal): красная мишень рядом с узлом.
   На графе UI обозначает goal-узел, к которому стремится LLMSwitch
   роутер. Один на граф (валидируется в Graph._validate_references).
   ==================================================================== */

.scripts-node--main-task {
    /* Лёгкий glow вокруг карточки чтобы быстро находить взглядом. */
    box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.15),
                0 6px 16px rgba(15, 23, 42, 0.08);
}

/* PR #444: пиктограммы типа узла (target / escalate / terminal / entry)
   теперь ВНУТРИ карточки в правом верхнем углу. Иконка ~28px,
   позиционируется чуть ниже header'а чтобы не перекрывать тип-лейбл. */
.scripts-main-task-target {
    position: absolute;
    top: 6px;
    right: 6px;
    z-index: 4;
    cursor: help;
    transition: transform 0.15s;
}

/* PR #443 → PR #465: бейдж «!» теперь СНИЗУ-справа узла (раньше был
   сверху-справа). Юзер: «бейдж индикации ошибок перенеси в правый
   нижний угол». z-index ниже типо-пиктограмм внутри узла (z:4), но
   выше canvas-сетки. */
.scripts-node__invalid-badge {
    position: absolute;
    bottom: -12px;
    right: -12px;
    z-index: 5;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--color-danger);
    color: #ffffff;
    font-size: 10px;
    font-weight: 800;
    line-height: 20px;
    text-align: center;
    cursor: help;
    box-shadow: 0 2px 6px rgba(220, 38, 38, 0.45);
    border: 2px solid #ffffff;
    transition: transform 0.15s;
    user-select: none;
}
.scripts-node__invalid-badge:hover {
    transform: scale(1.18);
}

/* PR #448: revert per-type override — теперь бейдж для ВСЕХ узлов
   (включая LLMSwitch-ромбы) сидит при top:-12 right:-12 у правого
   верхнего угла article rect. Outline (.scripts-node--has-invalid-refs)
   тоже рисует прямоугольник вокруг полного rect'а — бейдж в его
   правом верхнем углу логичен. Для clipping-edge cases юзер драгает
   узел вниз чтобы вершина rect'а попала в видимую canvas-область. */

/* PR #443: красная обводка вокруг карточки узла — дополнительный
   визуальный сигнал что в промтах есть невалидные ссылки.
   Outline (а не border) — не съедает место в layout. */
.scripts-node--has-invalid-refs {
    outline: 2px solid var(--color-danger) !important;
    outline-offset: 1px;
}

.scripts-main-task-target:hover {
    transform: scale(1.15);
}

.scripts-main-task-target svg {
    display: block;
    filter: drop-shadow(0 2px 4px rgba(220, 38, 38, 0.35));
}

/* PR #444: пиктограммы особых узлов (escalate / terminal / entry).
   ВНУТРИ карточки в правом верхнем углу (рядом с main-task-target).
   Раньше были снаружи (top:-14 left:-14) — клипались canvas-овским
   overflow:hidden, иконки не были видны. */
.scripts-special-marker {
    position: absolute;
    top: 6px;
    right: 6px;
    z-index: 4;
    cursor: help;
    transition: transform 0.15s;
}
.scripts-special-marker:hover { transform: scale(1.15); }
.scripts-special-marker svg { display: block; }
.scripts-special-marker--escalate svg {
    filter: drop-shadow(0 2px 4px rgba(220, 38, 38, 0.35));
}
.scripts-special-marker--terminal svg {
    filter: drop-shadow(0 2px 4px rgba(71, 85, 105, 0.35));
}
.scripts-special-marker--entry svg {
    filter: drop-shadow(0 2px 4px rgba(13, 148, 136, 0.35));
}
/* P165: success-escalation marker — зелёная галочка для отличения
   успешного CRM-handoff от обычного escalate (off-topic / жалоба). */
.scripts-special-marker--success-escalation svg {
    filter: drop-shadow(0 2px 4px rgba(22, 163, 74, 0.35));
}

/* Цветовое выделение карточки по special_kind. Glow аналогичен
   .scripts-node--main-task, чтобы единообразно «подсвечивать»
   важные узлы. */
.scripts-node--special-escalate {
    box-shadow: 0 0 0 2px rgba(220, 38, 38, 0.18),
                0 6px 16px rgba(15, 23, 42, 0.08);
    background: #fef2f2;
}
.scripts-node--special-terminal {
    box-shadow: 0 0 0 2px rgba(71, 85, 105, 0.20),
                0 6px 16px rgba(15, 23, 42, 0.08);
    background: #f4f5f7;
    opacity: 0.92;
}
.scripts-node--special-entry {
    box-shadow: 0 0 0 2px rgba(13, 148, 136, 0.18),
                0 6px 16px rgba(15, 23, 42, 0.08);
    background: #f0fdfa;
}
/* P165: success_escalation card — зелёный glow vs красный (escalate). */
.scripts-node--special-success_escalation {
    box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.18),
                0 6px 16px rgba(15, 23, 42, 0.08);
    background: #f0fdf4;
}
/* P165: type-icon в header'е (✅ из NODE_TYPE_UI mapping для
   success_escalation). Цвет под зелёную семантику.  */
.scripts-node__type-icon--success-escalation {
    color: #16a34a;
}

/* Hint-параграф для terminal-узла (вместо editor'ов). */
.scripts-node__terminal-hint {
    margin: 8px 0 0;
    padding: 5px 5px;
    font-size: 11.5px;
    color: var(--color-text-muted);
    background: rgba(71, 85, 105, 0.06);
    border-left: 3px solid #475569;
    border-radius: 3px;
    line-height: 1.4;
}

/* Viewer 10 сценариев эскалации. Read-only список — компактный,
   ol с короткими описаниями + note.
   PR-376ppp: внутренний скролл (max-height + overflow-y:auto) —
   .scripts-node имеет height:384px + overflow:hidden, без этого
   список обрезался после 3-го пункта. Теперь все 10 видны через
   собственный scrollbar внутри блока. */
.scripts-node__scenarios-list {
    margin: 4px 0 6px 18px;
    padding: 0 5px 0 0;     /* буфер справа под scrollbar */
    list-style: decimal;
    font-size: 10px;
    line-height: 1.35;
    color: var(--color-text);
    /* PR-376rrr: max-height снижен до 80px — синхронизировано с
       editor'ом 1-й секции через :has-override (см. правило ниже).
       Бюджет .scripts-node 228px = 28+80 + 28+80 = 216 ≤ 228. Скролл
       внутри ol даёт доступ к всем 10 пунктам нативным wheel'ом. */
    max-height: 80px;
    overflow-y: auto;
    overflow-x: hidden;
    /* Тонкая полоска-индикатор скролла справа: ясно что есть продолжение. */
    scrollbar-width: thin;
    /* overscroll-behavior:contain — wheel в этом ol НЕ всплывает на
       parent .scripts-node (graph pan). Без этого прокрутка списка
       одновременно сдвигала бы весь граф. */
    overscroll-behavior: contain;
}
.scripts-node__scenarios-list::-webkit-scrollbar {
    width: 6px;
}
.scripts-node__scenarios-list::-webkit-scrollbar-thumb {
    background: rgba(148, 163, 184, 0.55);
    border-radius: 3px;
}
.scripts-node__scenarios-list li {
    margin-bottom: 3px;
}
.scripts-node__scenarios-list strong {
    color: #991b1b;
}
.scripts-node__scenarios-note {
    margin: 6px 0 0;
    padding: 5px 5px;
    font-size: 10px;
    color: var(--color-text-muted);
    background: rgba(220, 38, 38, 0.05);
    border-radius: 3px;
    line-height: 1.4;
}

/* PR-376ppp / PR #487: status-banner детали (source/rule) у эскалации
   отображаются inline-flex pills под reason'ом. Селектор
   .tflow__status-scenario убран — поле scenario удалено из
   state.jump_context (PR #487). */
.tflow__status-details {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 4px;
}
.tflow__status-source,
.tflow__status-rule {
    padding: 2px 5px;
    border-radius: 3px;
    background: rgba(220, 38, 38, 0.10);
    color: #991b1b;
    font-size: 10px;
    font-weight: 600;
    white-space: nowrap;
}
/* PR audit-fix (P4.2): badge «legacy» для устаревших source значений
   в state.jump_context (direction_guard / off_catalog / security_jump).
   Не пишутся в новых диалогах — встречается только в старых записях БД. */
.tflow__status-legacy {
    font-size: 10px;
    font-weight: 500;
    color: #92400e;
    background: rgba(251, 191, 36, 0.15);
    padding: 1px 5px;
    border-radius: 3px;
    margin-left: 4px;
    opacity: 0.85;
}

/* PR-376qqq: блок-цитата — реплика которую бот сказал клиенту в
   момент эскалации. Отдельный disclaimer чтоб юзер видел что
   услышал клиент (без скролла к нужному turn'у). */
.tflow__status-reply {
    margin-top: 8px;
    padding-top: 5px;
    border-top: 1px dashed rgba(220, 38, 38, 0.25);
}
.tflow__status-reply-label {
    display: block;
    font-size: 10px;
    font-weight: 600;
    color: #991b1b;
    margin-bottom: 2px;
}
.tflow__status-reply-text {
    margin: 0;
    /* #413: убран левый inset (border-left-бар 3px + left-padding 10px) и
       card-фон — текст реплики начинается слева, вровень с остальным
       контентом баннера (заголовок / reason), без «пустой зоны» перед ним.
       Различимость цитаты держат label «💬 Реплика…» + верхний separator
       .tflow__status-reply + курсив. */
    padding: 4px 0 0;
    font-size: 10px;
    line-height: 1.4;
    color: #0f172a;
    font-style: italic;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ========================================================================
 *  Вкладка «Администрирование» — расход Anthropic API в USD/RUB
 * ====================================================================== */

.admin-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    margin-top: var(--space-md);
    flex-wrap: wrap;
}

.admin-btn {
    padding: 5px 5px;
    border: 1px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-text);
    border-radius: 3px;
    font-size: 10px;
    cursor: pointer;
    transition: background 0.15s;
}
.admin-btn:hover {
    background: var(--color-bg);
}

.admin-cbr-info {
    font-size: 10px;
    color: var(--color-text-muted);
}
.admin-cbr-info strong {
    color: var(--color-text);
}
.admin-cbr-info__date {
    font-size: 10px;
    margin-left: 4px;
}
.admin-cbr-info--err {
    color: var(--color-warn-text);
}

.admin-kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: var(--space-md);
    margin: var(--space-lg) 0;
}

.admin-kpi-card {
    background: var(--color-card);
    border: 1px solid var(--color-border);
    border-radius: 3px;
    padding: 5px 5px;
}

.admin-kpi-card__label {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.admin-kpi-card__period {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-top: 2px;
}

.admin-kpi-card__value-usd {
    font-size: 10px;
    font-weight: 700;
    color: var(--color-text);
    margin-top: var(--space-sm);
    font-variant-numeric: tabular-nums;
}

.admin-kpi-card__value-rub {
    font-size: 10px;
    color: var(--color-text-muted);
    margin-top: 2px;
    font-variant-numeric: tabular-nums;
}

.admin-section-title {
    margin: var(--space-lg) 0 var(--space-sm) 0;
    font-size: 13px;
    color: var(--color-text);
}

.admin-models-table td {
    padding: 5px 5px;
    border-bottom: 1px solid var(--color-border);
    font-size: 10px;
    vertical-align: middle;
}
.admin-models-table tbody tr:last-child td {
    border-bottom: 0;
}
.admin-models-table__num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    width: 130px;
    white-space: nowrap;
}

.admin-hint {
    color: var(--color-text-muted);
    font-size: 10px;
    font-style: italic;
    margin: var(--space-md) 0;
}

/* ===========================================================================
   PR #465: full-width admin pages — убрать боковые поля во всех разделах.
   Юзер: «везде во всех разделах мы отказываемся от полей. ширина — flex».
   Любые page-wrappers становятся 100% по горизонтали. Inner-padding
   карточек / header'ов сохраняется в своих собственных правилах.
   =========================================================================== */
.settings-page,
.tools-page,
.rule-form-page,
.catalog-page,
.catalog-form-page,
.directions-page,
.rule-form-page {
    max-width: none !important;
    margin-left: 0 !important;
    margin-right: 0 !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
}

/* Внутри full-width page содержимое получает свой горизонтальный отступ
   через __header / __body / соответствующие inner-блоки. Если у страницы
   нет inner-обёрток с padding, контент будет упираться в края — там
   нужны точечные доработки template. */
.tools-page__header,
.tools-page > .tools-list {
    padding-left: 5px;
    padding-right: 5px;
}
.settings-page > * {
    padding-left: 5px;
    padding-right: 5px;
}
.directions-page > * {
    padding-left: 5px;
    padding-right: 5px;
}
.catalog-page > *,
.catalog-form-page > * {
    padding-left: 5px;
    padding-right: 5px;
}
.rule-form-page > * {
    padding-left: 5px;
    padding-right: 5px;
}

/* ============================================================
   P110-3: Graph editor v2 (MVC) — CSS scaffolding.
   Активируется когда main.js навешивает класс
   `graph-editor-v2-active` на <body> (когда URL содержит
   ?editor_v=2). Прячет старый canvas, показывает новый mount.
   ============================================================ */

/* По умолчанию v2-mount скрыт — v1 default. */
.graph-editor-v2-mount {
    display: none;
}

/* В v2-режиме прячем старый canvas и показываем v2 mount. */
body.graph-editor-v2-active .scripts-graph-host {
    display: none !important;
}

body.graph-editor-v2-active .graph-editor-v2-mount {
    display: block;
    position: relative;
    min-height: 600px;
    background: #f8fafc;
    border: 1px dashed #cbd5e1;
    border-radius: 3px;
    overflow: auto;
    padding: 5px;
}

/* Banner вверху страницы в v2-режиме. */
.graph-editor-v2-banner {
    position: fixed;
    top: 8px;
    right: 8px;
    z-index: 9999;
    padding: 5px 5px;
    background: #fef3c7;
    color: #78350f;
    border: 1px solid #fde68a;
    border-radius: 3px;
    font-size: 10px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
.graph-editor-v2-banner a {
    color: #78350f;
    margin-left: 8px;
    text-decoration: underline;
}

/* Карточка узла в v2. */
.graph-node {
    box-sizing: border-box;
    width: 340px;
    padding: 5px 5px;
    background: #ffffff;
    border: 1.5px solid #475569;
    border-radius: 3px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.06);
    font-size: 12.5px;
    line-height: 1.4;
    cursor: grab;
    user-select: none;
}
/* P115-4: .graph-node--start удалён — composite StartNode больше нет
   (P115-3). Стартовый узел графа — обычный WaitNode без входящих рёбер
   (P125-1 derived-start); подсветка через .graph-node--start-marker. */
.graph-node--wait {
    border-color: #d97706;
    background: #fff7ed;
}
.graph-node--llm_switch {
    border-color: #7c3aed;
    background: #faf5ff;
}
.graph-node--terminal {
    border-color: #6b7280;
    background: #f3f4f6;
}
.graph-node--start-marker {
    box-shadow: 0 0 0 3px #14b8a6, 0 2px 4px rgba(0,0,0,0.06);
}
.graph-node__head {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 700;
    color: #334155;
    text-transform: uppercase;
    font-size: 10px;
    letter-spacing: 0.5px;
    border-bottom: 1px dashed #e2e8f0;
    padding-bottom: 4px;
    margin-bottom: 6px;
}
.graph-node__icon { font-size: 14px; }
.graph-node__id {
    display: inline-block;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10px;
    color: #6b7280;
    background: #f1f5f9;
    padding: 1px 4px;
    border-radius: 3px;
}
.graph-node__title {
    margin: 4px 0 6px;
    font-size: 13px;
    color: #1f2937;
    font-weight: 600;
}
.graph-node__body {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
/* P115-4: .graph-node__section / .graph-node__section-summary /
   .graph-node__branches удалены — это были стили collapsible-секций
   композитной карточки StartNode (greeting/debounce/router) и списка
   branches внутри router-секции. После P115-3 узлы рендерятся как
   обычные карточки через field_editor; никакой композитной разметки
   внутри карточки больше нет. Branches LLMSwitch'а редактируются через
   `_renderBranchesCrud` (fallback v1 UI до P110-3b). */

/* Field editor controls. */
.graph-field {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 4px 0;
}
.graph-field--inline {
    flex-direction: row;
    align-items: center;
    gap: 6px;
}
.graph-field__label {
    font-size: 10px;
    font-weight: 600;
    color: #6b7280;
}
.graph-field__help {
    color: #94a3b8;
    cursor: help;
}
.graph-field__input {
    box-sizing: border-box;
    padding: 4px 5px;
    border: 1px solid #d1d5db;
    border-radius: 3px;
    font-family: inherit;
    font-size: 10px;
    background: #ffffff;
    color: #1f2937;
    cursor: text;
}
.graph-field__input--contenteditable,
.graph-field__input--textarea {
    min-height: 24px;
    white-space: pre-wrap;
    word-break: break-word;
}
.graph-field__input--textarea {
    min-height: 60px;
}
.graph-field__input--number {
    width: 120px;
}
.graph-field__input--checkbox {
    width: auto;
    margin: 0;
}
.graph-field__input:focus {
    outline: 2px solid #fbbf24;
    outline-offset: -1px;
    background: #fffbeb;
}
.graph-field__fallback {
    font-size: 10px;
    color: #94a3b8;
}
.graph-field__fallback-ui {
    color: #d97706;
    font-weight: 600;
}

/* === P129: Trace UI Model↔View bijection fixes ============================ */

/* P129-fix-1: phase badge на render_llm_call summary. Per-purpose окраска
   через neutral colors — phase != purpose, цвет нюанс чтоб не путать. */
.tflow__llm-phase {
    display: inline-block;
    font-size: 10.5px;
    font-weight: 600;
    padding: 1px 5px;
    margin-left: 4px;
    border-radius: 3px;
    background: #f1f5f9;
    color: #475569;
    border: 1px solid #cbd5e1;
    text-transform: lowercase;
    letter-spacing: 0.02em;
}
.tflow__llm-phase--pre,
.tflow__llm-phase--pre_reply,
.tflow__llm-phase--security_check {
    background: #fef3c7;
    border-color: #fcd34d;
    color: #78350f;
}
.tflow__llm-phase--dispatch,
.tflow__llm-phase--router {
    background: #dbeafe;
    border-color: #93c5fd;
    color: #1e3a8a;
}
.tflow__llm-phase--reply {
    background: #d1fae5;
    border-color: #6ee7b7;
    color: #064e3b;
}
.tflow__llm-phase--post_dispatch_jump,
.tflow__llm-phase--post_dispatch_inject {
    background: #ede9fe;
    border-color: #c4b5fd;
    color: #4c1d95;
}

/* P129-fix-3: open turn с security_check который сработал — финальная
   эскалация, не «in-progress». Красный banner перед pre_security_step
   плашкой. */
.tflow__escalation-pending-banner {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    margin: 8px 0 14px;
    padding: 5px 5px;
    background: #fef2f2;
    border: 2px solid #b91c1c;
    border-left-width: 6px;
    border-radius: 3px;
    color: #7f1d1d;
}
.tflow__escalation-pending-icon { font-size: 22px; line-height: 1; }
.tflow__escalation-pending-body { flex: 1; min-width: 0; }
.tflow__escalation-pending-body strong {
    font-size: 10px;
    font-weight: 700;
}

/* P129-fix-6: legacy fallback badge для system_prompt без prompt_components. */
.tflow__prompt-legacy-badge {
    display: inline-block;
    font-size: 10px;
    font-weight: 600;
    color: #92400e;
    background: #fef3c7;
    border: 1px solid #fcd34d;
    border-radius: 3px;
    padding: 1px 5px;
    margin-left: 6px;
}

/* P129-fix-8: tool-registry-failed banner. Менее агрессивный чем
   api-error (это display issue, не crash). */
.tflow__registry-warning {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    margin: 6px 0 12px;
    padding: 5px 5px;
    background: #fef3c7;
    border: 1px solid #f59e0b;
    border-left-width: 5px;
    border-radius: 3px;
    color: #78350f;
}
.tflow__registry-warning-icon { font-size: 18px; line-height: 1; }
.tflow__registry-warning-body { flex: 1; min-width: 0; }
.tflow__registry-warning-body strong { font-weight: 700; }

/* P129-fix-9: rules_applied=NULL legacy badge. */
.tflow__llm-rules-legacy {
    display: inline-block;
    font-size: 10px;
    font-weight: 600;
    color: #92400e;
    background: #fef3c7;
    border: 1px solid #fcd34d;
    border-radius: 3px;
    padding: 1px 5px;
    margin-left: 4px;
    cursor: help;
}

/* P129-fix-10: synthetic entry_time marker. Визуально dim'ed —
   таймстамп фейковый, юзеру нужно знать но не отвлекать. */
.tflow__node-entry-time--synthetic {
    color: #64748b;
    font-style: italic;
}
.tflow__entry-time-synthetic-mark {
    font-size: 9.5px;
    margin-left: 2px;
    color: #94a3b8;
    font-weight: 600;
}

/* Подсветка узла в канвасе при клике на node-link в панели.
   Pulsing outline 2 sec, потом fade. */
.scripts-node--validation-highlight,
.graph-node--validation-highlight {
    box-shadow: 0 0 0 4px #fbbf24, 0 0 12px 6px rgba(251, 191, 36, 0.4);
    animation: graph-validation-pulse 1.2s ease-in-out 2;
}
@keyframes graph-validation-pulse {
    0%, 100% { box-shadow: 0 0 0 4px #fbbf24; }
    50% { box-shadow: 0 0 0 6px #fbbf24, 0 0 16px 8px rgba(251, 191, 36, 0.5); }
}

/* === Scripts list — колонка валидности =========================== */

.scripts-validity-list {
    margin: 8px 0 0;
    padding: 0;
    border: 1px solid var(--color-border, #cbd5e1);
    border-radius: 3px;
    background: #fff;
}
.scripts-validity-list__summary {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 5px;
    cursor: pointer;
    font-weight: 500;
    list-style: revert; /* keep native disclosure triangle */
    user-select: none;
}
.scripts-validity-list[open] .scripts-validity-list__summary {
    border-bottom: 1px solid #e2e8f0;
}
.scripts-validity-list__count {
    font-size: 10px;
    padding: 2px 5px;
    border-radius: 999px;
    font-weight: 500;
}
.scripts-validity-list__count--ok {
    background: #dcfce7;
    color: #166534;
}
.scripts-validity-list__count--bad {
    background: #fee2e2;
    color: #b91c1c;
}

.scripts-validity-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 10px;
}
.scripts-validity-table th,
.scripts-validity-table td {
    padding: 5px 5px;
    text-align: left;
    border-bottom: 1px solid #f1f5f9;
}
.scripts-validity-table th {
    font-weight: 600;
    color: #475569;
    background: #f8fafc;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.scripts-validity-row--bad td {
    background: #fffbeb;
}
.scripts-validity-row__empty {
    color: #94a3b8;
}

.scripts-validity-badge {
    display: inline-block;
    font-size: 10px;
    line-height: 1;
}
.scripts-validity-badge--ok { color: #16a34a; }
.scripts-validity-badge--err { color: var(--color-danger); }
.scripts-validity-badge--unknown { color: #94a3b8; }

.scripts-validity-pop {
    display: inline-block;
    position: relative;
}
.scripts-validity-pop__summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.scripts-validity-pop__summary::-webkit-details-marker { display: none; }
.scripts-validity-pop__count {
    font-size: 10px;
    color: #b91c1c;
    font-weight: 500;
}
.scripts-validity-pop__list {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 50;
    min-width: 320px;
    max-width: 480px;
    margin: 4px 0 0;
    padding: 5px 5px;
    list-style: none;
    background: #fff;
    border: 1px solid #fecaca;
    border-radius: 3px;
    box-shadow: 0 6px 16px rgba(15, 23, 42, 0.18);
    font-size: 10px;
    line-height: 1.4;
}
.scripts-validity-pop__list li {
    margin: 0 0 6px;
    padding-bottom: 5px;
    border-bottom: 1px solid #f1f5f9;
}
.scripts-validity-pop__list li:last-child {
    margin-bottom: 0;
    padding-bottom: 0;
    border-bottom: none;
}
.scripts-validity-pop__nodes {
    display: block;
    margin-top: 4px;
    color: #475569;
}
.scripts-validity-pop__nodes code {
    background: #f1f5f9;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 10px;
}

/* === P187/P192: вкладка «Обзор / расходы» (admin_overview.js) =========
   P217: страница скроллится вертикально. .ovw — flex-column на всю высоту
   .app-main; при разрастании контента (формы под таблицами: TTL, системный
   промт, Avito, секреты) .ovw сам прокручивается (overflow-y:auto). Таблицы
   остаются ограниченными внутренними фреймами со своим скроллом (аналитика
   max-height:50vh, пополнения max-height:25vh) — не схлопываются и не тянут
   страницу бесконечно. */
.ovw {
  display: flex; flex-direction: column; height: 100%;
  box-sizing: border-box; overflow-y: auto; overflow-x: hidden;
  padding: 5px 5px; gap: 5px;
}
/* Прямые секции страницы не сжимаются flex-колонкой — при нехватке высоты
   уходят в общий вертикальный скролл .ovw, а не схлопываются. */
.ovw > * { flex-shrink: 0; }
.ovw__filters {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  margin: 0; flex-shrink: 0;
}
.ovw__field { display: inline-flex; align-items: center; gap: 3px; font-size: 10px; }
/* Компактные datepicker'ы — выровнены с .df-date панели фильтров /admin/dialogs. */
.ovw__field input[type="date"] {
  height: 22px; box-sizing: border-box; padding: 0 5px;
  border: 1px solid var(--color-border); border-radius: 3px;
  background: var(--color-surface); color: var(--color-text);
  font-size: 10px; font-family: inherit; line-height: 1.15; width: 128px;
}
.ovw__toggle { display: inline-flex; align-items: center; gap: 5px; font-size: 10px; cursor: pointer; }
.ovw__spend { font-size: 10px; }
.ovw__spend strong { color: #147a7a; }
.ovw__balance { font-size: 10px; color: #6b7280; }
.ovw__status { margin-left: auto; color: #9aa1ad; font-size: 10px; }

/* Фрейм аналитики — ограниченная высота (≤50vh, design-doc P187), скролл
   внутренний по обеим осям; не схлопывается (flex-shrink:0) и не растягивает
   страницу — остаток секций уходит в общий скролл .ovw. */
.ovw__scroll {
  flex: 0 0 auto; max-height: 50vh; overflow: auto;
  border: 1px solid #d9dee8; border-radius: 3px;
}
.ovw__table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 10px; }
.ovw__table th, .ovw__table td {
  border-right: 1px solid #e3e6ec; border-bottom: 1px solid #eef0f4;
  padding: 2px 5px; white-space: nowrap; text-align: left; line-height: 1.3;
}
.ovw__table th:last-child, .ovw__table td:last-child { border-right: none; }
.ovw__table tbody td.ovw__num, .ovw__table tfoot th.ovw__num { text-align: right; font-variant-numeric: tabular-nums; }
/* sticky шапка */
.ovw__table thead th {
  position: sticky; top: 0; z-index: 3;
  background: #eef2f7; border-bottom: 1px solid #c7ccd6;
  cursor: pointer; user-select: none;
}
.ovw__table thead th .ovw__th-inner { display: inline-flex; align-items: center; gap: 4px; }
.ovw__drag { cursor: grab; color: #98a0ad; font-weight: 700; letter-spacing: -2px; }
.ovw__drag:hover { color: #2d6cdf; }
.ovw__sort-arrow { font-size: 10px; color: #b0b5be; }
.ovw__table thead th[data-sort="asc"] .ovw__sort-arrow,
.ovw__table thead th[data-sort="desc"] .ovw__sort-arrow { color: #111; font-weight: 900; }
.ovw__resize {
  position: absolute; top: 0; right: 0; width: 5px; height: 100%;
  cursor: col-resize; border-right: 2px solid transparent;
}
.ovw__resize:hover { border-right-color: #2d6cdf; }
/* sticky ИТОГО */
.ovw__table tfoot th {
  position: sticky; bottom: 0; z-index: 2;
  background: #e4ecf6; font-weight: 700; border-top: 1px solid #c7ccd6;
}
.ovw__table tbody tr:hover td { background: #f7fafd; }
.ovw__table tbody tr:nth-child(even) td { background: #fbfcfe; }

/* Шестерёнка ⚙ в углу шапки — sticky слева+сверху (паттерн /admin/dialogs).
   Колонка без data-col в реестре, поэтому reorder/visibility/sort её не
   трогают; заглушки-ячейки в tbody/tfoot держат выравнивание. */
.ovw__th-gear {
  position: sticky; left: 0; top: 0; z-index: 6;
  width: 30px; min-width: 30px; padding: 2px !important;
  text-align: center; cursor: default;
  background: #eef2f7; box-shadow: 2px 0 4px -2px rgba(0, 0, 0, .08);
}
.ovw__td-gear {
  position: sticky; left: 0; z-index: 1;
  width: 30px; min-width: 30px; padding: 0 !important;
  background: #fff; box-shadow: 2px 0 4px -2px rgba(0, 0, 0, .06);
}
.ovw__table tbody tr:hover .ovw__td-gear { background: #f7fafd; }
.ovw__table tfoot th.ovw__th-gear {
  top: auto; bottom: 0; z-index: 4; background: #e4ecf6;
}

.ovw__subtitle { margin: 0; font-size: 10px; flex-shrink: 0; }
/* per-client пополнения: подсказка + пустая строка таблицы (остальное —
   переиспользует .ovw__topups* стили ниже). */
.ovw__topups-hint { margin: 0; font-size: 10px; color: var(--color-text-muted); flex-shrink: 0; }
.ovw__empty { color: var(--color-text-muted); padding: 4px 5px; }
/* Нижний фрейм: [таблица пополнений ~25vh, внутр.скролл] [зазор ~1см] [форма]. */
.ovw__topups {
  display: flex; flex-direction: row; align-items: flex-start;
  gap: 38px; flex-shrink: 0; min-height: 0;
}
.ovw__topups-scroll {
  max-height: 25vh; overflow: auto;
  border: 1px solid #e3e6ec; border-radius: 3px;
}
.ovw__topups-table { border-collapse: separate; border-spacing: 0; font-size: 10px; min-width: 420px; }
.ovw__topups-table th, .ovw__topups-table td { border-bottom: 1px solid #eef0f4; padding: 2px 5px; }
.ovw__topups-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: #eef2f7; border-bottom: 1px solid #c7ccd6;
}
.ovw__topups-table .ovw__num { text-align: right; font-variant-numeric: tabular-nums; }
.ovw__topup-form { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0; }
.ovw__topup-form input {
  height: 24px; box-sizing: border-box; padding: 0 5px; font-size: 10px;
  border: 1px solid var(--color-border); border-radius: 3px; font-family: inherit;
}
.ovw__topup-form input[type="number"] { width: 110px; }
.ovw__topup-del { color: #b91c1c; cursor: pointer; background: none; border: none; font-size: 10px; }
.ovw__topups-table td.ovw__editable { cursor: pointer; }
.ovw__topups-table td.ovw__editable:hover { background: #f1f5fd; }
.ovw__cell-input { width: 100%; box-sizing: border-box; font-size: 10px; padding: 1px 3px;
    border: 1px solid #2d6cdf; border-radius: 2px; font-family: inherit; }

/* backlog #438: строка int inline-edit «TTL кэша плана диспетчера» в самом
   низу страницы. flex-shrink:0 — не схлопывается под аналитической таблицей
   (.ovw__scroll flex:1 отдаёт ей место), остаётся видимой внизу. */
.ovw__engine-setting {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  flex-shrink: 0; font-size: 10px;
}
.ovw__engine-label { color: var(--color-text); }
.ovw__engine-setting input[type="number"] {
  height: 24px; width: 90px; box-sizing: border-box; padding: 0 5px; font-size: 10px;
  border: 1px solid var(--color-border); border-radius: 3px; font-family: inherit;
}
.ovw__engine-status { font-size: 10px; color: #9aa1ad; }
.ovw__engine-status--ok { color: #1a7f37; }
.ovw__engine-status--err { color: #b91c1c; }
.ovw__engine-status.is-ok { color: #1a7f37; }
.ovw__engine-status.is-error { color: #b91c1c; }

/* Per-client реквизиты Avito (2 поля + опция отправки фото). */
.ovw__avito-setting {
  display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
  flex-shrink: 0; font-size: 11px; margin-bottom: 12px;
}
.ovw__avito-setting input[type="text"],
.ovw__avito-setting input[type="password"] {
  height: 26px; width: 220px; box-sizing: border-box; padding: 0 7px; font-size: 11px;
  border: 1px solid var(--color-border); border-radius: 4px; font-family: inherit;
}
.ovw__avito-toggle {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  flex-basis: 100%;
}
.ovw__avito-toggle input { margin: 0; }

/* =====================================================================
   ADR-0012 Фаза 6 — фронт-фичи редактора графа v1 (F1-F4).
   Модуль scripts_graph_editor.js. Изолированный блок: палитра типов
   (F2), компактная панель identity узла (F4: uid+slug+name), кнопки
   +/x перехода (F1), корзина+подтверждение (F3).
   ===================================================================== */

/* --- F2: палитра типов (закреплена слева поверх canvas) --- */
.scripts-palette {
    position: absolute;
    /* #391: панель добавления узлов прижата к левому-верхнему углу полотна
       без зазоров. */
    top: 0;
    left: 0;
    z-index: 6;                 /* выше viewport/узлов (z:2), ниже handles (z:5)? */
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 132px;
    max-height: 100%;
    overflow-y: auto;
    padding: 4px;
    border: 1px solid #d8dee8;
    border-radius: 3px;
    background: rgba(255, 255, 255, 0.96);
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.14);
    cursor: default;
    user-select: none;
}
.scripts-palette__title {
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: #64748b;
    padding: 1px 2px 2px;
}
.scripts-palette__btn {
    display: flex;
    align-items: center;
    gap: 3px;
    width: 100%;
    padding: 3px 4px;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    background: #fff;
    color: #1f2937;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.2;
    text-align: left;
    cursor: pointer;
    transition: background 100ms, border-color 100ms, transform 80ms;
}
.scripts-palette__btn:hover {
    background: #eef2ff;
    border-color: #a5b4fc;
}
.scripts-palette__btn:active { transform: scale(0.97); }
.scripts-palette__btn { cursor: grab; }
.scripts-palette__icon { font-size: 14px; flex: 0 0 auto; }
.scripts-palette__label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- F2-drag: полупрозрачный ghost фишки, следующий за курсором --- */
/* Кастомный (не native DnD) floating-элемент: position:fixed по client-
   координатам, центрирован на курсоре, pointer-events:none чтобы не
   перехватывать mouseup/drop под собой. Во время drag — grabbing-курсор. */
body.scripts-palette-dragging,
body.scripts-palette-dragging .scripts-graph-host { cursor: grabbing; }
.scripts-palette-ghost {
    position: fixed;
    left: 0;
    top: 0;
    transform: translate(-50%, -50%);
    z-index: 10000;
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 5px;
    border: 1px solid #a5b4fc;
    border-radius: 3px;
    background: #eef2ff;
    color: #1f2937;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    box-shadow: 0 4px 14px rgba(79, 70, 229, 0.25);
    opacity: 0.62;
    pointer-events: none;
}
.scripts-palette-ghost__icon { font-size: 14px; }

/* --- F4/F1/F3: компактная панель identity узла --- */
.scripts-node__editbar {
    display: flex;
    align-items: center;
    gap: 2px;
    margin: 0 0 2px;
    min-height: 22px;
}
.scripts-node__uid {
    flex: 0 0 auto;
    padding: 1px 5px;
    border-radius: 3px;
    background: #eef2f7;
    color: #64748b;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1.6;
    cursor: help;
}
.scripts-node__idfield {
    box-sizing: border-box;
    height: 22px;
    min-width: 0;
    padding: 1px 5px;
    border: 1px solid transparent;
    border-radius: 3px;
    background: #f8fafc;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10px;
    line-height: 1.4;
    transition: border-color 100ms, background 100ms;
}
.scripts-node__idfield--slug {
    flex: 1 1 42%;
    color: #630633;       /* slug — как имя ветки/идентификатор в попапах */
    font-weight: 700;
}
.scripts-node__idfield--name {
    flex: 1 1 58%;
    color: #1f2937;
}
.scripts-node__idfield:hover { background: #eef2f7; }
.scripts-node__idfield:focus {
    outline: none;
    border-color: #6366f1;
    background: #fffdf3;          /* горчично-светлый «editing» фон */
}
.scripts-node__idfield.is-ok {
    border-color: #16a34a;
    background: #f0fdf4;
}
.scripts-node__idfield.is-error {
    border-color: var(--color-danger);
    background: #fef2f2;
    color: #b91c1c;
}

/* --- CPA узла (стоимость за действие, ₽) — inline-edit под editbar'ом.
   Редактируемый только суперадмином (input disabled остальным ролям —
   роль enforce'ится и на бэке). --- */
.scripts-node__cpa {
    display: flex;
    align-items: center;
    gap: 4px;
    margin: 0 0 2px;
    min-height: 20px;
}
.scripts-node__cpa-label {
    flex: 0 0 auto;
    font-size: 10px;
    font-weight: 700;
    color: #64748b;
    cursor: help;
}
.scripts-node__cpa-input {
    box-sizing: border-box;
    flex: 0 0 auto;
    width: 72px;
    height: 20px;
    padding: 1px 5px;
    border: 1px solid transparent;
    border-radius: 3px;
    background: #f8fafc;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10px;
    line-height: 1.4;
    color: #1f2937;
    transition: border-color 100ms, background 100ms;
}
.scripts-node__cpa-input:hover:not(:disabled) { background: #eef2f7; }
.scripts-node__cpa-input:focus {
    outline: none;
    border-color: #6366f1;
    background: #fffdf3;
}
.scripts-node__cpa-input:disabled {
    background: #f1f5f9;
    color: #94a3b8;
    cursor: not-allowed;
}
.scripts-node__cpa-input.is-ok {
    border-color: #16a34a;
    background: #f0fdf4;
}
.scripts-node__cpa-input.is-error {
    border-color: #dc2626;
    background: #fef2f2;
    color: #b91c1c;
}

/* F1 +/x кнопки + F3 корзина — компактные иконки в одну строку. */
.scripts-node__edgebtn,
.scripts-node__delbtn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid #e2e8f0;
    border-radius: 3px;
    background: #fff;
    font-size: 10px;
    line-height: 1;
    cursor: pointer;
    transition: background 100ms, border-color 100ms, color 100ms;
}
.scripts-node__edgebtn--plus { color: #0d9488; }
.scripts-node__edgebtn--plus:hover { background: #ecfdf5; border-color: #5eead4; }
.scripts-node__edgebtn--x { color: var(--color-warn-text); }
.scripts-node__edgebtn--x:hover { background: #fffbeb; border-color: #fcd34d; }
.scripts-node__delbtn { color: #94a3b8; }
.scripts-node__delbtn:hover { background: #fef2f2; border-color: #fca5a5; color: var(--color-danger); }

/* --- F1: picker узлов (floating dropdown, показывает slug, хранит uid) --- */
.scripts-edge-picker {
    position: fixed;
    z-index: 1200;
    min-width: 180px;
    max-width: 280px;
    max-height: 320px;
    overflow-y: auto;
    padding: 4px;
    border: 1px solid #d8dee8;
    border-radius: 3px;
    background: #fff;
    box-shadow: 0 16px 40px rgba(15, 23, 42, 0.24);
}
.scripts-edge-picker__head {
    padding: 4px 5px 5px;
    font-size: 10px;
    font-weight: 700;
    color: #64748b;
    text-transform: uppercase;
    letter-spacing: 0.3px;
}
.scripts-edge-picker__empty {
    padding: 5px;
    color: #94a3b8;
    font-size: 10px;
}
.scripts-edge-picker__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 5px 5px;
    border: 0;
    border-radius: 3px;
    background: transparent;
    cursor: pointer;
    text-align: left;
}
.scripts-edge-picker__item:hover { background: #eef2ff; }
.scripts-edge-picker__slug {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10px;
    font-weight: 700;
    color: #630633;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.scripts-edge-picker__uid {
    flex: 0 0 auto;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 10.5px;
    color: #94a3b8;
}

/* --- F3: модал подтверждения удаления --- */
.scripts-del-overlay {
    position: fixed;
    inset: 0;
    z-index: 1300;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, 0.45);
    backdrop-filter: blur(2px);
}
.scripts-del-modal {
    width: min(420px, 92vw);
    padding: 5px 5px;
    border-radius: 3px;
    background: #fff;
    box-shadow: 0 24px 60px rgba(15, 23, 42, 0.30);
}
.scripts-del-modal__title {
    margin: 0 0 10px;
    font-size: 13px;
    font-weight: 700;
    color: #1e293b;
}
.scripts-del-modal__body {
    margin: 0 0 18px;
    font-size: 10px;
    line-height: 1.5;
    color: #334155;
}
.scripts-del-modal__slug {
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-weight: 700;
    color: #630633;
}
.scripts-del-modal__uid { color: #94a3b8; font-size: 10px; }
.scripts-del-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
}
.scripts-del-modal__confirm {
    background: var(--color-danger);
    border-color: var(--color-danger);
    color: #fff;
}
.scripts-del-modal__confirm:hover { background: #b91c1c; border-color: #b91c1c; }


/* ============================================================================
   ЕДИНОЕ ОФОРМЛЕНИЕ СПИСОЧНЫХ КОНТРОЛОВ (select / multiselect / dropdown /
   setting-input). Раньше контролы разъезжались: фон select'ов был кто серый
   (--color-bg), кто #f8fafc, кто белый; бордюры — серые/слейтовые; фокус —
   то violet, то indigo; dropdown'ы — местами серый бордюр. Здесь сводим всё
   к ОДНОМУ виду через --control-* токены (см. :root):
     • покой   — белый фон + лавандовый бордюр (#c4b5fd);
     • фокус/раскрытие — фиолетовый бордюр (#7c3aed) + ободок;
     • раскрытый dropdown — белый фон, лавандовый hover (#ede9fe).
   Блок в конце файла → перекрывает прежние per-class фон/бордюр при равной
   специфичности (один класс). Размеры/паддинги per-class не трогаем.
   ============================================================================ */

/* --- Закрытый контрол: фон + бордюр (select'ы, мультиселект, фильтр-инпуты,
       setting-input) --- */
.rules-filter__select,
.rules-filter__multi,
.rules-filter__search,
.settings-select,
.df-select,
.df-input,
.df-date,
.df-num,
.ovw__field input[type="date"],
.tsidebar__select,
.scripts-toolbar__select,
.scripts-node__sw-select,
.m2m-picker__select,
.catalog-filters__input,
.dt-footer__pagesize select,
.dt-toolbar__pagesize select,
.tool-card__ttl-input,
#dpt-input {
    background: var(--control-bg);
    border: 1px solid var(--control-border);
    border-radius: 3px;
}

/* --- Фокус / раскрытие: единый фиолетовый акцент + ободок --- */
.rules-filter__select:focus,
.rules-filter__search:focus,
.settings-select:focus,
.df-select:focus,
.df-input:focus,
.df-date:focus,
.df-num:focus,
.ovw__field input[type="date"]:focus,
.tsidebar__select:focus,
.scripts-toolbar__select:focus,
.scripts-node__sw-select:focus,
.m2m-picker__select:focus,
.catalog-filters__input:focus,
.dt-footer__pagesize select:focus,
.dt-toolbar__pagesize select:focus,
.tool-card__ttl-input:focus,
#dpt-input:focus,
.rules-filter__multi:focus-within {
    outline: none;
    border-color: var(--control-focus);
    box-shadow: 0 0 0 2px var(--control-focus-ring);
}

/* --- Раскрытый dropdown (кастомный type-ahead список) --- */
.rules-filter__dropdown {
    background: var(--dropdown-bg);
    border-color: var(--control-border);
}
.rules-filter__dropdown li:hover,
.rules-filter__dropdown li[aria-selected="true"] {
    background: var(--dropdown-hover-bg);
    color: var(--dropdown-hover-text);
}


/* === Гейт-ромб (.scripts-node--llm_switch): контент компактно ПО ЦЕНТРУ ромба
   (justify-content:center базы держит блок симметрично вокруг самой широкой
   оси), а ширины шапки/заголовка ограничены так, чтобы строки не вылезали за
   наклонные грани. У ромба 366×160 доступная ширина у центра ~270, чуть выше/
   ниже — меньше; поэтому кэпы консервативные. === */
.scripts-node--llm_switch {
    /* Шапку (#uid + edit'ы) опускаем к центру ромба, где доступная ширина
       максимальна (justify-content:center базы + этот сдвиг ставят editbar на
       y≈73 — avail≈290, шапка 240 влезает). Не больше — иначе title/«?»
       уезжают в узкий низ. */
    padding-top: 38px;
}
.scripts-node--llm_switch .scripts-node__editbar {
    justify-content: center;
    /* Кэп ширины шапки: на y≈63-85 доступно ≥290px — берём с запасом. */
    max-width: 240px;
    margin-left: auto;
    margin-right: auto;
}
.scripts-node--llm_switch .scripts-node__idfield--slug {
    flex: 0 1 34%;
}
.scripts-node--llm_switch .scripts-node__idfield--name {
    flex: 0 1 46%;
}
/* Заголовок-вопрос ниже центра (узкее) — кэп + line-clamp базы держат его в
   контуре, не давая вылезти за наклонные грани/низ. */
.scripts-node--llm_switch .scripts-node__title {
    max-width: 165px;
}


/* === Debug-режим диалога (/admin/dialogs): кнопка 🪲 + 3-колоночный сплит === */
.dp__header-actions {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}
/* Кнопка-иконка «жук» рядом со статусом — крупнее бейджа, но скромнее
   квадратной кнопки logout (гармония по высоте с .status-badge). */
.dp__debug-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: 3px;
    background: var(--color-surface);
    cursor: pointer;
    transition: background .15s, border-color .15s, box-shadow .15s;
}
.dp__debug-btn:hover:not(:disabled) {
    background: var(--dropdown-hover-bg);
    border-color: var(--control-border);
}
.dp__debug-btn:disabled { opacity: .4; cursor: not-allowed; filter: grayscale(1); }
.dp__debug-btn.is-on {
    background: var(--dropdown-hover-bg);
    border-color: var(--control-focus);
    box-shadow: 0 0 0 2px var(--control-focus-ring);
}
.dp__debug-icon { width: 18px; height: 18px; display: block; }

/* 3-колоночный сплит debug-режима: таблица | панель диалога | trace —
   по ~1/3 экрана. Включается классом is-debug на .dialogs-page__layout. */
.dialogs-page__layout.is-debug {
    /* table | r1 | панель(1fr) | r2 | trace. Ширины таблицы и trace —
       плавающие (--dbg-table-w / --dbg-trace-w, задаёт JS initDebugResizers
       при перетаскивании r1/r2); панель забирает остаток (1fr). По умолчанию
       все по 1fr → ~1/3. */
    grid-template-columns:
        var(--dbg-table-w, 1fr) 4px 1fr 4px var(--dbg-trace-w, 1fr);
}
.dialogs-trace {
    background: var(--color-card);
    border-left: 1px solid var(--color-border);
    overflow: auto;
    min-width: 0;
}


/* Системный промт на /admin/administration — на всю ширину + шрифт 10px. */
.ovw__sysprompt-hint { font-size: 10px; margin: 4px 0 6px; max-width: none; }
.ovw__sysprompt-form { width: 100%; }
.ovw__sysprompt-textarea {
    width: 100%;
    box-sizing: border-box;
    font-size: 10px;
    line-height: 1.5;
    padding: 5px;
    border: 1px solid var(--control-border);
    border-radius: 3px;
    background: var(--control-bg);
    color: var(--color-text);
    font-family: var(--font-mono, monospace);
    resize: vertical;
}
.ovw__sysprompt-textarea:focus {
    outline: none;
    border-color: var(--control-focus);
    box-shadow: 0 0 0 2px var(--control-focus-ring);
}
.ovw__sysprompt-actions { margin-top: 5px; }


/* === Адаптив панели фильтров (#3): при сужении колонки (debug-режим или
   узкий ресайз) реорганизуем .dt-filters, экономя высоту. .dialogs-list —
   query-контейнер; ширина = ширине колонки таблицы. ===
   Логика рефлоу:
   - широко (≥760px): прежние 3 колонки [main | СООБЩЕНИЙ | ВРЕМЯ];
   - средне (<760px): main на всю ширину (селекты КАНАЛ/ЛИНИЯ/… текут в ряд,
     меньше переносов → ниже), а СООБЩЕНИЙ+ВРЕМЯ бок о бок снизу;
   - узко (<470px): СООБЩЕНИЙ и ВРЕМЯ тоже стопкой (бок о бок уже не влезают).
*/
.dialogs-list {
    container-type: inline-size;
    container-name: dtfilters;
}
@container dtfilters (max-width: 760px) {
    .dt-filters {
        grid-template-columns: 1fr 1fr;
        grid-template-areas:
            "main  main"
            "msgs  times";
        gap: 6px 10px;
    }
    .dt-filters__main          { grid-area: main; }
    .dt-filters__counters--msgs  { grid-area: msgs; }
    .dt-filters__counters--times { grid-area: times; }
    /* В одном ряду снизу обе группы равноправны — у второй убираем левый
       разделитель/отступ, у обеих рамка сверху как мягкий раздел. */
    .dt-filters__counters {
        border-left: 0;
        border-top: 1px solid var(--color-border);
        padding-left: 0;
        padding-top: 4px;
    }
}
@container dtfilters (max-width: 470px) {
    .dt-filters {
        grid-template-columns: 1fr;
        grid-template-areas:
            "main"
            "msgs"
            "times";
    }
}

/* ===================================================================
   Вкладка «Аккаунты» (ADR-0020 P5, A-accounts) — платформенное
   управление тенантами. Переиспользует .rule-card / .rules-list /
   .rule-form; здесь только специфичные элементы списка сотрудников
   и заголовок имени тенанта.
   =================================================================== */

/* Имя тенанта в шапке карточки (аналог title, но не contenteditable). */
.rule-card__name {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text, #1a1a1a);
}

/* Заголовок секции на странице/форме аккаунтов. */
.account-section-title {
    margin: var(--space-lg, 16px) 0 var(--space-sm, 8px) 0;
    font-size: 13px;
    font-weight: 600;
    color: var(--color-text, #1a1a1a);
}

/* Компактный список сотрудников в карточке тенанта. */
.account-emp-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.account-emp-list__item {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 10px;
    line-height: 1.7;
}

.account-emp-list__email {
    color: var(--color-text, #1a1a1a);
    font-family: var(--font-mono, monospace);
}

.account-emp-list__role {
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.account-emp-list__nologin {
    color: var(--color-warning, #b8860b);
    font-style: italic;
}

