/* Интерфейс forush.
 *
 * Палитра и базовые классы приезжают из дизайн-ядра: `ui/kernel.css`
 * собирается командой `python3 design/build_ui_css.py` из `design/kernel.py`
 * и руками не правится. Здесь — только то, чего в ядре нет, и только через
 * его токены.
 *
 * До этого у интерфейса была своя палитра (--paper, --sheet, --rule), у ядра
 * своя (--panel, --line, --mass), и экран не мог выглядеть как артборд:
 * он называл цвета другими словами. Ядро одно.
 */

/* Схема названа явно и следует за выбранной темой. Без неё браузер красит поля
   и списки по теме системы: на маке в тёмной теме текст в поле становился
   белым на белом фоне ядра, и шапка выглядела пустыми полосами. Тёмная ветка
   дизайн-кода включается атрибутом data-theme на `.app`, а страница узнаёт о
   ней через `:has` — токены объявлены на `.app`, и `body` их не наследует:
   переменные текут вниз, а не вверх. */
:root{color-scheme:light}
:root:has(.app[data-theme="dark"]){color-scheme:dark}
body{margin:0;background:var(--bg);color:var(--ink)}
/* Фон страницы за пределами приложения. Приложение занимает всю высоту окна,
   но при короткой странице и резиновой прокрутке из-под него видно `body` —
   и в тёмной теме это была бы белая полоса. */
body:has(.app[data-theme="dark"]){background:#101418;color:#EAF1F8}

/* Артборд — полотно фиксированной ширины; живой экран тянется по окну. */
/* Живой экран тянется по окну: у канвы полотно 1440×900 фиксированное, потому
   что это артборд, а не приложение. Ограничитель ширины в 1320 px с центровкой
   был нашим, канва такого не рисует — приложение задумано на весь экран. */
.app{width:auto;min-height:100vh;display:flex;padding:0}

/* Оболочка — из канвы, а не придумана заново: полоса, её шапка и низ, верхняя
   строка раздела с крошками и поиском. Первая редакция развернула ленту в меню
   один к одному и разошлась с канвой по числу пунктов, группам, именам и по
   всему, что стоит в полосе кроме самих пунктов. */
.shell{display:flex;flex:1;min-width:0;align-items:stretch}
.rail{flex:0 0 208px;background:var(--panel2);border-right:1px solid var(--line);
  display:flex;flex-direction:column;padding:12px 0}
.rail__brand{display:flex;align-items:center;gap:9px;padding:4px 16px 14px}
.rail__name{font-family:var(--font);font-weight:700;font-size:15px;letter-spacing:.01em}
.rail__tenant{margin-left:auto;font-size:13px;color:var(--muted);
  border:1px solid var(--line);border-radius:5px;padding:1px 5px}
.rail__me .btn{margin-left:auto;font-size:12.5px;padding:2px 7px}
.rail__foot{margin-top:auto;padding:10px 16px 0;border-top:1px solid var(--line)}
/* Переключатель арендатора в шапке полосы. Значок, а не слово: канва рисует
   там метку арендатора, и слово «Перейти» рядом с именем клиента заняло бы
   строку целиком. Название кнопки читает голосовой доступ — оно спрятано
   классом `vh`, а не выброшено. */
.rail__go{display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;flex:0 0 22px;border:1px solid var(--line);
  border-radius:5px;background:var(--panel);color:var(--muted);cursor:pointer;
  appearance:none;padding:0}
.rail__go:hover:not(:disabled){border-color:var(--mass);color:var(--mass)}
.rail__go:disabled{opacity:.4;cursor:default}
/* Спрятано от глаза, но не от голосового доступа. */
.vh{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0}

/* Окно поверх карточки: разбор балла, экономика, разбор проигрыша. Каждое
   всегда про одну сделку, и открывается из неё, а не из меню. */
.veil{position:fixed;inset:0;z-index:40;background:var(--scrim)}
.sheet{position:fixed;z-index:41;top:4vh;left:50%;transform:translateX(-50%);
  width:min(1120px,94vw);max-height:92vh;display:flex;flex-direction:column;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--shadow);overflow:hidden}
.sheet__h{display:flex;align-items:center;gap:10px;padding:12px 14px;
  border-bottom:1px solid var(--line)}
.sheet__h b{font-size:14px;font-weight:600}
.sheet__h .btn{margin-left:auto}
.sheet__b{flex:1;min-height:0;overflow:auto;padding:14px}

/* ── Командная строка ⌘K ──────────────────────────────────────────────────
 *
 * Раскрытая строка — экран поиска, поднятый над содержимым: та же форма, что
 * рисует канва `Search.dc.html` (`design/pages_j.py`, функция `search`), только
 * поверх раздела, а не вместо него. Числа взяты оттуда, а не подобраны на глаз:
 *
 *   поле     `.sbig`   — 52 в высоту, отступ 16, зазор 11, край 1.5 в цвет
 *                        действия, скругление 12, тень ядра, кегль 16;
 *   ширина   `.wrapc`  — 860 по центру; предел 94vw и высота 92vh взяты у
 *                        `.sheet` выше — это тоже окно поверх содержимого;
 *   строка   `.res`    — зазор 11, отступ 11 по вертикали, черта `--line2`;
 *   значок   `.res__i` — 30×30, скругление 8, подложка `--line2`;
 *   имя      `.res__t` — 13.5 полужирным; пояснение `.res__s` — 12.5 тусклым;
 *   счёт     `.u` в шапке панели — «8 из 23» стоит там же, где на канве стоит
 *                        «4 из 27», а не подписью под списком;
 *   высота списка — 60vh, как у списка закрепления `.dt__pins` в `shell.css`.
 *
 * Чего здесь нет. Своей палитры и своих кеглей: всё через токены ядра и его
 * лестницу. Своего затемнения: под окном лежит `.veil` выше, и второе
 * разошлось бы с первым цветом на первой правке. Своей рамки: коробку списка
 * дают `.panel` и `.panel__h` из ядра, здесь — только то, чем окно отличается
 * от панели в потоке: положение, тень и предел высоты.
 *
 * Выбранная строка помечена тем же, чем выбранная строка колоннады
 * (`.pr__row--on` в `shell.css`): подложка `--mass-bg` и полоса слева.
 * Третьего способа сказать «вот это выбрано» в раме не заводится.
 */
.cmdk{position:fixed;z-index:41;top:4vh;left:50%;transform:translateX(-50%);
  width:min(860px,94vw);max-height:92vh;display:flex;flex-direction:column}
.cmdk__box{flex:0 0 52px;display:flex;align-items:center;gap:11px;height:52px;
  padding:0 16px;background:var(--panel);border:1.5px solid var(--mass);
  border-radius:12px;box-shadow:var(--shadow);color:var(--muted)}
.cmdk__box svg{flex:0 0 19px;color:var(--mass)}
/* Поле голое: край, тень и высоту несёт коробка вокруг. Иначе в окне было бы
   два края — свой у поля и свой у коробки. */
.cmdk__in{flex:1;min-width:0;height:100%;padding:0;border:0;border-radius:0;
  background:none;color:var(--ink);font:inherit;font-size:16px}
.cmdk__in:focus{outline:none}
.cmdk__in::placeholder{color:var(--muted)}
.cmdk__panel{margin-top:10px;min-height:0;display:flex;flex-direction:column;
  box-shadow:var(--shadow);overflow:hidden}
.cmdk__h .u{margin-left:auto}
.cmdk__list{padding:4px 6px 8px;max-height:60vh;overflow:auto}
.cmdk__item{display:flex;align-items:center;gap:11px;width:100%;height:auto;
  padding:11px 8px;border:0;border-bottom:1px solid var(--line2);border-radius:0;
  background:none;font:inherit;font-weight:400;text-align:left;cursor:pointer}
.cmdk__item:last-child{border-bottom:0}
.cmdk__item:hover{background:var(--panel2)}
.cmdk__item[aria-selected="true"]{background:var(--mass-bg);
  box-shadow:inset 2px 0 0 var(--mass)}
.cmdk__i{flex:0 0 30px;height:30px;border-radius:8px;background:var(--line2);
  color:var(--ink2);display:flex;align-items:center;justify-content:center}
.cmdk__m{flex:1;min-width:0}
.cmdk__t{display:block;font-size:13.5px;font-weight:600;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmdk__s{display:block;margin-top:2px;font-size:12.5px;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmdk__item .chip{flex-shrink:0}
/* Пустой ответ — пустым состоянием ядра, поднятым над содержимым: вторая форма
   того же сообщения разошлась бы с первой словами. */
.cmdk .empty{margin-top:10px;background:var(--panel);box-shadow:var(--shadow)}

/* Крошки: раздел кликается, когда поверх него открыта карточка. */
.crumbs__up{appearance:none;border:0;background:none;padding:0;font:inherit;
  color:var(--muted);cursor:pointer}
.crumbs__up:hover{color:var(--mass);text-decoration:underline}

/* Создание одной кнопкой: список того, что заводится, и где. */
.mk{position:relative;display:inline-flex}
/* Подложка меню: ловит клик мимо. Прозрачная — затемнять страницу ради списка
   из тринадцати строк значит объявить его окном, которым он не является. */
.mk__veil{position:fixed;inset:0;z-index:29}
/* Кнопка-переключатель в нажатом состоянии: значок один, состояние — цветом. */
.btn--on{background:var(--mass-bg);border-color:var(--mass);color:var(--mass)}
.mk__list{position:absolute;top:36px;right:0;z-index:30;min-width:230px;
  padding:6px;background:var(--panel);border:1px solid var(--line);
  border-radius:9px;box-shadow:var(--shadow)}
.mk__i{display:block;width:100%;padding:7px 9px;border:0;border-radius:6px;
  background:none;font:inherit;font-size:13px;color:var(--ink);text-align:left;
  cursor:pointer}
.mk__i:hover{background:var(--line2)}
.mk__why{margin:6px 9px 3px;font-size:12.5px;color:var(--muted);line-height:1.4}

.rail__me{display:flex;align-items:center;gap:9px}
.rail__meta{line-height:1.25;overflow:hidden;font-size:13px}
.rail__meta b{display:block;font-size:13px}
.rail__meta span{color:var(--muted)}

.nav{display:flex;flex-direction:column;gap:2px;padding:0 8px;overflow-y:auto}
/* Метка группы стала кнопкой: тридцать девять пунктов подряд прокручиваются,
   и нижние разделы человек не находит вовсе. Свёрнутая группа прячет свои
   пункты; та, в которой стоишь, открыта всегда. */
/* Кегль, отступ и цвет метка берёт у ядра. Повторённое здесь застывает:
   селектор той же силы стоит ниже и перебивает ядро молча — так метка
   осталась бы капсом после того, как ядро капс сняло. Здесь только то,
   чем метка стала кнопкой. */
.nav__label{display:flex;align-items:center;gap:6px;width:100%;
  appearance:none;border:0;background:none;font-family:inherit;text-align:left;
  cursor:pointer}
.nav__label:hover{color:var(--ink2)}
.nav__label svg{margin-left:auto;opacity:.7;transition:transform .12s ease}
.nav__label--folded svg{transform:rotate(-90deg)}
/* Значок пункта не сжимается: длинное название иначе съедало бы его до нуля. */
.nav__item svg{flex:0 0 16px}
.nav__n{margin-left:auto;font-size:12.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.nav__item--on .nav__n{color:var(--mass)}
.nav__item{display:flex;align-items:center;gap:9px;height:30px;padding:0 8px;
  border-radius:6px;color:var(--ink2);font-size:13.5px;cursor:pointer;
  appearance:none;border:0;background:none;font-family:inherit;text-align:left;width:100%}
.nav__item:hover{background:var(--line2);color:var(--ink)}
.nav__item--on{background:var(--mass-bg);color:var(--mass);font-weight:600}
.nav__item--on:hover{background:var(--mass-bg);color:var(--mass)}
.nav__n{margin-left:auto;font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums}
.nav__item--on .nav__n{color:var(--mass)}

.main{flex:1;min-width:0;display:flex;flex-direction:column}
.top{height:52px;flex:0 0 52px;display:flex;align-items:center;gap:12px;
  padding:0 20px;background:var(--panel);border-bottom:1px solid var(--line)}
.crumbs{display:flex;align-items:center;gap:7px;font-size:13px;color:var(--muted)}
.crumbs b{color:var(--ink);font-size:13.5px}
/* Поиск в шапке — кнопка, а не картинка: она открывает раздел поиска, и с
   клавиатуры это единственный путь туда. Кнопке нужны свои оговорки, которых
   канве не нужно: шрифт кнопки браузер берёт свой, подпись переносил по словам
   (поле выходило в две строки и было выше соседей), а при нехватке места
   кнопка сжималась с 320 до 240. */
.search{margin-left:auto;display:flex;align-items:center;gap:8px;flex:0 0 320px;
  width:320px;height:32px;
  padding:0 10px;border:1px solid var(--line);border-radius:7px;background:var(--panel2);
  color:var(--muted);font-family:inherit;font-size:13px;line-height:1;
  white-space:nowrap;overflow:hidden;text-align:left;cursor:pointer}
.search span:first-of-type{overflow:hidden;text-overflow:ellipsis}
.search:hover{border-color:var(--muted)}
/* Единицы сумм и пояс данных — из канвы: человек, читающий число, должен
   видеть, в чём оно и по какому времени, не спрашивая. */
.unit,.tz{display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 9px;
  border:1px solid var(--line);border-radius:7px;font-size:13px;color:var(--muted);
  white-space:nowrap}
.unit b{color:var(--ink);font-weight:600}
.kbd2{font:500 12.5px "IBM Plex Mono",monospace;color:var(--muted);
  border:1px solid var(--line);border-radius:5px;padding:1px 5px}

/* Шапка раздела: заголовок с подписью слева, действия справа. */
.phead{display:flex;align-items:flex-start;gap:16px}
.phead h1{margin:0;font-family:var(--font);font-size:21px;font-weight:600;letter-spacing:-.01em}
.phead .sub{font-size:13px;color:var(--muted);margin-top:2px}
.phead__act{margin-left:auto;display:flex;align-items:center;gap:8px}

/* Раздел — один поток внутри своей области, с отступами страницы. */
.page{flex:1;min-width:0;display:flex;flex-direction:column;
  overflow:auto}

/* Узкое окно: полоса ложится над разделом, а не режет его пополам. */
@media (max-width: 900px){
  .shell{flex-direction:column}
  .rail{flex:none;border-right:0;border-bottom:1px solid var(--line)}
  .nav{flex-direction:row;flex-wrap:wrap;gap:6px}
  .nav__label{display:none}
  .nav__item{width:auto}
  .search{display:none}
}


/* Узкое окно: навигация ложится над разделом, а не режет его пополам. */
@media (max-width: 900px){
  .shell{grid-template-columns:minmax(0,1fr)}
  .nav{position:static;flex-direction:row;flex-wrap:wrap;gap:8px}
  .nav-title{display:none}
}

h1{font-size:21px;margin:0 0 4px}
h2.card-title{font-size:16px;margin:20px 0 6px}
.sub{color:var(--muted);font-size:13px;margin:0 0 16px;max-width:82ch}
/* Панель действий. Имя своё, не `bar`: в дизайн-ядре `.bar` — полоска
   прогресса высотой пять пикселей, и панель под этим именем схлопывалась в
   ниточку. Столкновение имён с ядром — та же ошибка, что своя палитра. */
.toolbar{display:flex;gap:10px;align-items:center;margin-bottom:14px;flex-wrap:wrap}
.grow{flex:1;min-width:220px}
input,select,button{font:inherit;height:32px;padding:0 10px;border:1px solid var(--line);
  border-radius:7px;background:var(--panel);color:var(--ink)}
button{cursor:pointer;font-weight:600;font-size:13px}
button:disabled{opacity:.55;cursor:default}
input:focus,select:focus{outline:2px solid var(--focus);outline-offset:-1px}
.warn{color:var(--high);font-weight:600}
.hidden{display:none}

/* Пустое состояние берётся из ядра; здесь — только разбивка на строки:
   заголовок и объяснение слипались в «Кого ищемУкажите арендатора». */
.empty b{display:block;color:var(--ink);margin-bottom:6px}
.empty span{display:block;max-width:56ch;margin:0 auto}
.err{border-left:3px solid var(--high);background:var(--high-bg);
  padding:10px 14px;margin-bottom:14px;border-radius:0 8px 8px 0}
.err b{display:block;color:var(--high);font-size:13px}
.err span{font-size:13px;color:var(--ink2)}

/* Карточка действия — из ядра; здесь только отступы содержимого. */
/* Панель — карточка канвы: фон, рамка, скругление. У нас она была просто
   отступом, и оттого экраны читались лентой абзацев, а не набором карточек. */
.panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px}
/* Панель с шапкой отступом не управляет: его держат `__h` и `__b`, как в канве.
   Отступ на самой панели остаётся ради сорока экранов, ещё не перебранных под
   артборды: убрать его сразу значило бы прижать их содержимое к рамке. */
.panel:has(.panel__h){padding:0}
/* Широкая таблица прокручивается внутри своей панели, а не вместе со страницей.
 *
 * Заход глазами 02.09, находка Н7: таблица маршрутов при окне 1280 — 1596
 * точек внутри панели в 1198. Прокрутка была, и колонки доставались, но
 * прокручивался `.page` целиком: последние три столбца — «Где стоит» и дело
 * включения, то самое, ради чего экран заведён, — уезжали на фон страницы, а
 * белое поле панели кончалось под ними. Скролл на самой панели держит их на
 * полотне; сузить таблицу нечем — колонки объявлены гранью, и снимать их
 * ради ширины окна значило бы решать вопрос вида потерей ответа.
 *
 * Правило узкое намеренно: `> .tbl` — это полотно списка грани, где таблица
 * лежит прямым ребёнком панели. Таблица внутри раздела (`.sec`) сюда не
 * попадает: у неё своя ширина и свой хозяин. */
.panel:has(> .tbl){overflow-x:auto}

/* Связанный список внутри карточки прокручивается сам.
 *
 * Правило выше нарочно узкое — `> .tbl`, полотно списка грани, — и таблица
 * внутри раздела в него не попадает. Со ступени ос0.5 такие таблицы стали
 * основной формой связанного списка: улики лида, история движения, документы
 * сделки. Улики раскладываются в 1931 точку внутри раздела шириной 1142 и
 * молча обрезаются `overflow:hidden` у `.page`: последние колонки — «Что
 * спросить» и «Откуда известно», то самое, ради чего улики читают, — на экране
 * не существуют вовсе. Прокрутки не было ни одной: ни у раздела, ни у панели,
 * ни у страницы (замер на стенде 09.09).
 *
 * Скролл ставится на самом разделе, а не на панели: панель держит и поля, и
 * списки, и горизонтальная прокрутка на ней уводила бы вбок разделы, которые
 * помещаются. Сужать колонки нечем — они объявлены гранью. */
.sec--flush:has(> .tbl){overflow-x:auto}
.panel__h{display:flex;align-items:center;gap:10px;padding:11px 14px;
  border-bottom:1px solid var(--line)}
.panel__h h2{margin:0;font-size:13.5px;font-weight:600}
.panel__b{padding:14px}
.panel__b b{display:block;margin-bottom:4px}

/* Раскрой карточки — данные экрана, а не сорт класса.
 *
 * Канва рисует каждому разделу свои колонки: сделка 288/1fr/316, разговор
 * 264/1fr/324, метамодель 236/1fr/316, отчёты 236/1fr/276. Модификатор на
 * каждый раскрой давал список, который растёт с каждым новым артбордом, —
 * поэтому числа переехали в переменные, а класс остался один. Экран называет
 * свои колонки там, где их применяет: `style="--left:236px;--right:276px"`.
 *
 * Умолчание — раскрой сделки: он самый частый и был здесь до переменных.
 */
.grid3{display:grid;gap:12px;align-items:start;min-height:0;
  grid-template-columns:var(--left,288px) minmax(0,1fr) var(--right,316px)}
/* Зеркальный раскрой: две колонки фиксированы слева, растяжимая справа —
   так канва рисует переписку (176/320/1fr). */
.grid3--mirror{grid-template-columns:var(--left,176px) var(--mid,320px) minmax(0,1fr)}
@media (max-width: 1200px){ .grid3, .grid3--mirror{grid-template-columns:minmax(0,1fr)} }

/* ── Карточка грани: раскрой в две колонки и плотность разделов ───────────
 *
 * Карточка трёх объектов пути — обращения, лида, сделки — шла одним столбцом:
 * двадцать полей лентой сверху вниз, и «чем кончилось» лежало ниже сгиба под
 * лентой событий. Эталон канвы раскладывает её надвое: узкая колонка слева
 * отвечает «что это за запись», широкая справа — «что с ней было». Сторону
 * называет само объявление раздела (`ui/src/facet/mod.rs`, `Side`), а числа
 * раскроя стоят здесь: движку о ширинах знать нечего.
 *
 * Доли, а не точки: колонка узкая относительно своей карточки, а не абсолютно.
 * Карточка стоит и в полном окне, и рядом с выехавшим ящиком, и фиксированные
 * 340 точек в первом случае узки, во втором — половина ширины.
 *
 * Ниже 1100 точек колонки схлопываются в одну: две колонки по 300 точек
 * читаются хуже одной в 600, а поле «подпись — значение» ломается переносом.
 */
.fcard__cols{display:grid;gap:var(--s3);align-items:start;
  grid-template-columns:minmax(0,1fr) minmax(0,2fr)}
@media (max-width: 1100px){ .fcard__cols{grid-template-columns:minmax(0,1fr)} }

/* Отступ держит раздел, а не панель: панель карточки — полотно, и её
   собственные четырнадцать точек складывались бы с отступом раздела. */
.fcard .panel, .fcard .panel__b{padding:0}

/* Плотность разделов и полей. Правила те же, что у выведенного дизайна
   (`ui/derived.css`, `[data-style="derived"] .sec`/`.fld`), но страница грани
   этой пометки не несёт, и до раскроя разделы карточки не имели ни отступов,
   ни разделительной линии: заголовок раздела рисовался браузерным `h3`, а
   поля шли строками без подписи слева. Токены ядерные, своих чисел два —
   размеры подписи и значения, названные эталоном. */
.fcard .sec{padding:var(--s4);border-bottom:1px solid var(--line2)}
.fcard .sec:last-child{border-bottom:0}
.fcard .sec--flush{padding:0}
.fcard .sec--flush .sec__h{padding:var(--s4) var(--s4) var(--s3);margin-bottom:0}
.fcard .sec__h{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s2)}
.fcard .sec__h h3{margin:0;font-size:13px;font-weight:600;color:var(--muted)}
.fcard .fld{display:flex;align-items:flex-start;gap:var(--s3);padding:var(--s2) 0;
  border-bottom:1px solid var(--line2)}
.fcard .fld:last-child{border-bottom:0}
.fcard .fld__k{flex:0 0 118px;color:var(--muted);font-size:13px}
.fcard .fld__v{min-width:0;font-size:14px}

/* Ссылка обязана выглядеть ссылкой: это единственный путь из списка в
   карточку, и проверяющий его не разглядел. */
.tbl a{color:var(--mass);text-decoration:underline;text-underline-offset:2px}
.tbl a:hover{color:var(--ink)}

/* ── Доска воронки и путь по стадиям ──────────────────────────────────────
 * Взяты с артбордов Funnel и Deal дословно: правило проекта — дизайн берут,
 * а не придумывают заново. В ядре этих классов нет, потому что они живут в
 * CSS страницы; токены — ядерные.
 */
.dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 6px; }
.board { display: flex; gap: 12px; flex: 1; min-height: 0; overflow: hidden; align-items: stretch; }
.col { flex: 0 0 268px; display: flex; flex-direction: column; background: var(--panel2); border: 1px solid var(--line); border-radius: 10px; min-height: 0; }
.col__h { padding: 10px 12px 8px; border-bottom: 1px solid var(--line); }
.col__t { display: flex; align-items: center; gap: 7px; }
.col__t b { font-size: 13px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.col__n { margin-left: auto; font-size: 12.5px; font-weight: 600; color: var(--muted); background: var(--line2); border-radius: 9px; padding: 1px 7px; font-variant-numeric: tabular-nums; }
.col__s { font-size: 13px; color: var(--muted); margin-top: 4px; font-variant-numeric: tabular-nums; }
.col__b { padding: 10px; display: flex; flex-direction: column; gap: 8px; overflow: hidden; }
.dc { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--mass); border-radius: 8px; padding: 10px 11px; box-shadow: var(--shadow-sm); }
.dc--risk { border-left-color: var(--high); }
.dc--warn { border-left-color: var(--medium); }
.dc__t { font-size: 13px; font-weight: 600; line-height: 1.3; }
.dc__a { font-size: 13.5px; font-weight: 600; margin-top: 5px; font-variant-numeric: tabular-nums; }
.dc__o { font-size: 13px; color: var(--muted); margin-top: 3px; }
.dc__f { display: flex; align-items: center; gap: 6px; margin-top: 9px; }
.dc__seam { display: flex; align-items: center; gap: 5px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line2); font-size: 13px; color: var(--high); }
.dc__ava { margin-left: auto; width: 20px; height: 20px; border-radius: 50%; background: var(--low-bg); font-size: 11px; font-weight: 600; display: flex; align-items: center; justify-content: center; color: var(--ink2); }
/* Путь по стадиям — строка, а не столбец. Правила контейнера у нас не было
   вовсе, и шаги с `flex: 1` в блочном родителе вставали друг под другом на всю
   ширину: полоса-простыня вместо ленты стадий. */
.path { display: flex; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: var(--panel); border-radius: 10px; overflow: hidden; }
.path__s { flex: 1; position: relative; padding: 9px 12px 9px 24px; font-size: 13px; color: var(--muted); background: var(--panel2); border-right: 1px solid var(--line); }
.path__s:first-child { padding-left: 14px; }
.path__s::after { content: ""; position: absolute; right: -9px; top: 50%; width: 16px; height: 16px; transform: translateY(-50%) rotate(45deg); background: var(--panel2); border-top: 1px solid var(--line); border-right: 1px solid var(--line); z-index: 2; }
.path__s:last-child::after { display: none; }
.path__s--done { background: var(--mass-bg); color: var(--mass); }
/* Стрелка висит над границей двух стадий: своей половиной над своей, второй —
   над соседней. Цвет пройденной стадии полупрозрачный, и над соседней такой же
   он ложится вторым слоем — в тёмной теме это видно ромбами поверх пути.
   Непрозрачная подложка панели плюс тот же цвет ровно одним слоем: результат
   совпадает с фоном самой стадии. */
.path__s--done::after { background: var(--panel); box-shadow: inset 0 0 0 99px var(--mass-bg); }
.path__s--now { background: var(--mass); color: #fff; font-weight: 600; }
.path__s--now::after { background: var(--mass); border-color: var(--mass); }
.path__d { font-size: 12px; opacity: .8; margin-top: 1px; }
.path__go { flex: 0 0 auto; display: flex; align-items: center; padding: 0 12px; background: var(--panel); }
.f__k { flex: 0 0 108px; color: var(--muted); font-size: 13px; }
.f__v { flex: 1; min-width: 0; }
.f__v--edit { border-bottom: 1px dashed var(--line); cursor: text; }

/* Доска на живом экране прокручивается вбок и не режет колонки. */
.board{overflow-x:auto;padding-bottom:6px}
.col{min-height:120px}
.col__b{overflow:visible}
.dc{cursor:pointer}
.dc a{color:inherit;text-decoration:none}
.dc:hover{border-color:var(--mass)}

/* Аватар в строке таблицы стоит рядом с именем, а не над ним: в ядре он
   рассчитан на боковую панель, где идёт столбцом. */
.tbl .ava{display:inline-flex;vertical-align:middle;margin-right:7px;
  width:22px;height:22px;font-size:11px}

/* Группы находок и карточки правил — с артбордов Day и Rules. */
/* Две колонки: основная и боковая. Ширина боковой — переменная экрана:
   канва держит 340 у большинства, 336 у денег, 330 у реестров, 360 у состояний
   и 320 у загрузки. */
.grid2 { display: grid; grid-template-columns: minmax(0,1fr) var(--side,340px); gap: 12px; flex: 1; min-height: 0; }
/* Зеркало: узкая колонка слева — карточка клиента (320), права и роли (268). */
.grid2--mirror { grid-template-columns: var(--side,320px) minmax(0,1fr); }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; display: flex; flex-direction: column; min-height: 0; overflow: hidden; }
.card__h { display: flex; align-items: center; gap: 9px; padding: 11px 14px; border-bottom: 1px solid var(--line); }
.card__h b { font-size: 13px; }
.card__n { margin-left: auto; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.card__b { padding: 12px 14px; overflow: hidden; display: flex; flex-direction: column; gap: 10px; }
.gr { border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.gr__h { display: flex; align-items: baseline; gap: 9px; padding: 10px 13px; background: var(--panel2); border-bottom: 1px solid var(--line); }
.gr__t { font-size: 13px; font-weight: 600; }
.gr__m { font-size: 13px; color: var(--muted); }
.gr__n { margin-left: auto; font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.fnd { display: flex; align-items: flex-start; gap: 10px; padding: 9px 13px; border-top: 1px solid var(--line2); }
.fnd:first-child { border-top: none; }
.fnd__t { font-size: 13px; font-weight: 500; }
.fnd__e { font-size: 13px; color: var(--muted); margin-top: 2px; }
.fnd__a { margin-left: auto; display: flex; gap: 6px; align-items: center; }
.fnd__sum { font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.cond { font-family: "IBM Plex Mono", monospace; font-size: 12px; color: var(--ink2); background: var(--panel2); border: 1px solid var(--line); border-radius: 7px; padding: 8px 10px; line-height: 1.5; }
.pill { display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 8px; border-radius: 11px; font-size: 12px; font-weight: 600; }
.pill--on { background: var(--mass-bg); color: var(--mass); }
.pill--off { background: var(--low-bg); color: var(--muted); }
.hint { font-size: 13px; color: var(--muted); line-height: 1.5; }

/* Застревание на текущей стадии: белым по синей заливке. Красный по синему
   почти нечитаем, а это ровно то, ради чего пришли из находки. */
.path__s--now .warn{color:#fff;font-weight:600;text-decoration:underline;
  text-underline-offset:2px}

/* ── Ступень v0.7: наряд, часы, деньги ────────────────────────────────────
 * Классы с артборда `Work`. В ядре их нет, потому что живут в CSS страницы;
 * токены — ядерные, своих цветов ни одного.
 */
.f { display: flex; align-items: baseline; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.f:last-child { border-bottom: none; }
.colc { display: flex; flex-direction: column; gap: 12px; min-height: 0; }
.kpi4 { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 10px; margin-bottom: 14px; }
.kpi4 > div { border: 1px solid var(--line); border-radius: 9px; padding: 11px 13px; background: var(--panel); }
.kpi4 b { display: block; font-size: 22px; font-weight: 700; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; margin: 3px 0 2px; }
.kpi4 b.warn { color: var(--high); }

/* Часы строкой: кто, когда, сколько. Правки суммы полем нет. */
.hh { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.hh:last-child { border-bottom: none; }
.hh__w { flex: 1; min-width: 0; }
.hh__n { font-size: 13px; font-weight: 600; }
.hh__d { font-size: 13px; color: var(--muted); margin-top: 1px; }
.hh__h { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }

/* План против факта: синяя часть — в оценке, охряная — сверх неё. */
.pf { display: flex; align-items: center; gap: 10px; }
.pf__b { flex: 1; height: 8px; border-radius: 4px; background: var(--line2); position: relative; overflow: hidden; }
.pf__b i { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 4px; }

/* Путь наряда. Состояний три — столько их и в домене. */
.st { display: flex; gap: 0; align-items: stretch; }
.st__n { flex: 1; padding: 8px 10px; border: 1px solid var(--line); background: var(--panel2); font-size: 13px; }
.st__n:first-child { border-radius: 7px 0 0 7px; }
.st__n:last-child { border-radius: 0 7px 7px 0; }
.st__n--done { background: var(--mass-bg); border-color: var(--mass); color: var(--mass); font-weight: 600; }
.st__n--now { background: var(--medium-bg); border-color: var(--medium); color: var(--medium); font-weight: 600; }

/* Панель на живом экране тянется по окну, а не по кадру артборда. */
.grid2 { align-items: start; }
.panel__b .toolbar:last-of-type { margin-bottom: 0; }
.panel .empty { margin: 0; }

/* Шаги запуска и работы по нему — с артборда `Delivery`. */
.steps { display: flex; gap: 6px; align-items: stretch; flex-wrap: wrap; margin-top: 12px; }
.sn { flex: 1 1 180px; border: 1px solid var(--line); border-radius: 9px; padding: 10px 11px; background: var(--panel2); }
.sn--done { border-color: var(--mass); background: var(--mass-bg); }
.sn--now { border-color: var(--medium); background: var(--medium-bg); }
.sn__t { font-size: 13px; font-weight: 600; }
.sn__s { font-size: 12.5px; color: var(--muted); margin-top: 4px; }
.chk { display: flex; align-items: center; gap: 9px; padding: 8px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.chk:last-child { border-bottom: none; }
.chk__m { flex: 1; min-width: 0; }
.chk__s { font-size: 13px; color: var(--muted); margin-top: 1px; }

/* ── Табель и этапы: артборды `Load` и `Project` ────────────────────────── */
.wk { display: flex; gap: 2px; }
.wk i { flex: 0 0 8px; height: 22px; border-radius: 3px; background: var(--line2); display: block; }
.wk i.wk__d--full { background: var(--mass); }
.wk i.wk__d--part { background: var(--medium); }
.wk i.wk__d--none { background: var(--high-bg); }
/* День впереди: норма есть, часов ещё не может быть. Красная клетка «не
   отработал» на завтрашнем дне — враньё, поэтому у плана своя, пустая. */
.wk i.wk__d--ahead { background: var(--panel2); box-shadow: inset 0 0 0 1px var(--line); }

.ph { display: flex; gap: 10px; padding: 11px 0; border-bottom: 1px solid var(--line2); }
.ph:last-child { border-bottom: none; }
.ph__m { flex: 1; min-width: 0; }
.ph__t { font-size: 13px; font-weight: 600; }
.ph__s { font-size: 13px; color: var(--muted); margin-top: 2px; }
.ph__h { text-align: right; white-space: nowrap; font-size: 13px; }
.gan { display: flex; height: 7px; border-radius: 4px; overflow: hidden; margin-top: 7px; background: var(--line2); }
.gan i { display: block; height: 100%; background: var(--mass); }

/* ── Лента обращения, очередь инженера, дорожки диспетчерской ────────────
 * Артборды `Lifecycle`, `Engineer`, `Dispatch`. Дорожки диспетчерской названы
 * `.lanes`, а не `.lane`: на артборде `Lifecycle` `.lane` — это пилюля
 * дорожки события, и в одном листе стилей два разных `.lane` схлопнули бы
 * ленту в сетку. Столкновение имён — та же ошибка, что своя палитра.
 */
.band { display: flex; gap: 3px; }
.bd { flex: 1; padding: 9px 11px; background: var(--panel2); border: 1px solid var(--line); }
.bd:first-child { border-radius: 8px 0 0 8px; }
.bd:last-child { border-radius: 0 8px 8px 0; }
.bd--on { background: var(--mass-bg); border-color: var(--mass); }
.bd__t { font-size: 13px; font-weight: 600; }
.bd__s { font-size: 12.5px; color: var(--muted); margin-top: 3px; min-height: 15px; }

.ev { display: flex; gap: 11px; padding: 10px 0; border-bottom: 1px solid var(--line2); }
.ev:last-child { border-bottom: none; }
.ev__m { flex: 1; min-width: 0; }
.ev__h { display: flex; align-items: baseline; gap: 8px; }
.ev__h b { font-size: 13px; font-weight: 600; }
.ev__at { margin-left: auto; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.ev__b { font-size: 13px; color: var(--ink2); margin-top: 3px; line-height: 1.45; }

.jb { display: flex; gap: 11px; padding: 11px 0; border-bottom: 1px solid var(--line2); }
.jb:last-child { border-bottom: none; }
.jb__m { flex: 1; min-width: 0; }
.jb__t { font-size: 13.5px; font-weight: 600; }
.jb__s { font-size: 13px; color: var(--muted); margin-top: 3px; }
.jb__f { display: flex; align-items: center; gap: 7px; margin-top: 7px; flex-wrap: wrap; }
.jb__r { text-align: right; white-space: nowrap; }
.chip.warn { border-color: var(--high); }

.lanes { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 10px; }
.ln { display: flex; flex-direction: column; background: var(--panel2); border: 1px solid var(--line); border-radius: 10px; }
.ln__h { padding: 9px 11px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 7px; }
.ln__h b { font-size: 13px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.ln__n { margin-left: auto; font-size: 12.5px; font-weight: 600; color: var(--muted);
  background: var(--line2); border-radius: 9px; padding: 1px 7px; font-variant-numeric: tabular-nums; }
.ln__b { padding: 9px; display: flex; flex-direction: column; gap: 7px; }
.ln__e { font-size: 13px; color: var(--muted); padding: 6px 2px; }
.tc { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--low);
  border-radius: 8px; padding: 9px 10px; box-shadow: var(--shadow-sm); cursor: pointer; }
.tc:hover { border-color: var(--mass); }
.tc--h { border-left-color: var(--high); }
.tc--m { border-left-color: var(--medium); }
.tc__t { font-size: 13px; font-weight: 600; line-height: 1.3; }
.tc__c { font-size: 13px; color: var(--muted); margin-top: 3px; }
.tc__f { display: flex; align-items: center; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.eng { display: flex; align-items: center; gap: 9px; padding: 7px 0; border-bottom: 1px solid var(--line2); }
.eng:last-child { border-bottom: none; }

/* ── Ступень v0.8: разговор, входящий звонок, переписка ───────────────────
 * Страничные классы трёх экранов. Взяты с артбордов:
 *   `Call`         — design/pages_e.py, функция call()      → .grid3 (264/1fr/324), .cl*, .marks/.mk, .rep*
 *   `IncomingCall` — design/pages_h.py, функция incoming()  → .pop*, .who*, .qq*, .note, .btn--red
 *   `Inbox`        — design/pages_d.py, функция inbox()     → .grid3--mirror (176/320/1fr), .fold*, .ml*, .body
 * В ядре их нет, потому что они живут в CSS страницы; токены — ядерные,
 * своих цветов ни одного (белый — контраст к акценту, как в ядре).
 *
 * Раскрой звонков (264/1fr/324) и почты (176/320/1fr) — разные, и раньше это
 * держали два модификатора. Теперь колонки называет сам экран переменными:
 * столкновение имён в общем листе стилей ушло вместе со списком имён.
 */
/* Раскрои звонков и почты стояли здесь двумя модификаторами; теперь их
   колонки называет сам экран переменными — правило одно, список не растёт. */

/* ── Журнал разговоров: строка звонка ──────────────────────────────────── */
.cl { display: flex; gap: 9px; padding: 9px 10px; border-radius: 7px; cursor: pointer; }
.cl:hover { background: var(--panel2); }
.cl--on { background: var(--mass-bg); }
.cl--on:hover { background: var(--mass-bg); }
.cl__m { flex: 1; min-width: 0; }
.cl__t { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cl__s { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.cl__d { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Полоса под разговором: доля речи и паузы. На артборде она идёт под дорожкой
   записи; дорожки здесь нет — звук лежит во внешнем хранилище. */
.marks { display: flex; gap: 14px; margin-top: 9px; font-size: 12.5px; color: var(--muted); flex-wrap: wrap; }
.mk { display: inline-flex; align-items: center; gap: 5px; }

/* ── Расшифровка: реплика ──────────────────────────────────────────────── */
.rep { display: flex; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--line2); }
.rep:last-child { border-bottom: none; }
.rep__t { flex: 0 0 44px; font-size: 12.5px; color: var(--muted); padding-top: 2px; }
.rep__w { flex: 0 0 92px; font-size: 13px; font-weight: 600; }
.rep__b { flex: 1; min-width: 0; font-size: 13px; line-height: 1.5; }
.rep--c .rep__w { color: var(--medium); }
/* Язык реплики. На артборде на этом месте стоял перевод узбекской фразы;
   сервер отдаёт язык, а не второй текст, и место занимает то, что есть. */
.rep__l { font-size: 12.5px; color: var(--muted); font-weight: 400; margin-top: 2px; }

/* ── Карточка входящего звонка ─────────────────────────────────────────── */
/* На артборде карточка висит поверх затемнённого рабочего экрана. Здесь экраны
   ступени идут стопкой на одной странице, и всплывающее окно закрыло бы
   соседей: та же карточка, поставленная в поток. */
.pop { max-width: 460px; background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: var(--shadow); overflow: hidden; margin-top: 14px; }
.pop__h { display: flex; align-items: center; gap: 11px; padding: 14px 16px;
  background: var(--mass); color: #fff; }
.pop__h b { font-size: 14px; font-weight: 700; }
.pop__h .mono { margin-left: auto; font-size: 13px; }
.pop__b { padding: 14px 16px; }
.pop__f { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: 12px 16px; background: var(--panel2); border-top: 1px solid var(--line); }

/* ── Подбор записи строкой (`kit::pick`, ступень ц0.2) ─────────────────────
   Список подсказок и плашка — та же `.pop`, поставленная под поле, а не в
   поток: поле живёт в форме, и карточка в потоке раздвигала бы соседей на
   каждую букву. Чип — форма `.pick button` (высота, скругление, обводка),
   но именем своим: `.pick` — группа кнопок отбора, и «выбранная запись»
   под тем же словом разошлась бы с ней на первой правке. */
.picker { position: relative; }
.picker__list, .picker__pop { position: absolute; left: 0; top: 100%; z-index: 30;
  margin-top: 4px; max-width: none; }
.picker__list { right: 0; max-height: 300px; overflow: auto; }
.picker__pop { min-width: 260px; max-width: 460px; }
.picker__row { padding: 8px 12px; font-size: 13px; color: var(--ink); cursor: pointer;
  border-bottom: 1px solid var(--line2); }
.picker__row:last-child { border-bottom: none; }
.picker__row--on, .picker__row:hover { background: var(--panel2); }
.picker__chip { display: inline-flex; align-items: center; gap: 4px; height: 29px;
  padding: 0 6px 0 11px; border: 1px solid var(--line); border-radius: var(--rp);
  background: var(--panel); color: var(--ink); font-size: 13px; font-weight: 600;
  max-width: 100%; }
.picker__chip button { border: 0; background: none; padding: 0 4px; font: inherit;
  color: inherit; cursor: pointer; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
.picker__chip button:last-child { color: var(--muted); font-weight: 400; }
.who { display: flex; align-items: center; gap: 12px; }
.who .ava { width: 44px; height: 44px; flex: 0 0 44px; font-size: 15px; border-radius: 12px;
  background: var(--mass-bg); color: var(--mass); }
.who__n { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
.who__s { font-size: 13px; color: var(--muted); margin-top: 2px; }
.qq { display: flex; gap: 8px; padding: 9px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.qq:last-of-type { border-bottom: none; }
.qq__m { flex: 1; min-width: 0; }
.qq__s { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.note { margin-top: 11px; padding: 9px 11px; background: var(--medium-bg); border-radius: 8px;
  font-size: 13px; line-height: 1.45; }
/* Отклонить — действие с потерей: красный отличает его от «Ответить» формой
   заливки, а не только оттенком. */
.btn--red { background: var(--high); border-color: var(--high); color: #fff; font-weight: 600; }

/* ── Переписка: отборы, список писем, тело ─────────────────────────────── */
.fold { display: flex; align-items: center; gap: 8px; height: 30px; padding: 0 9px;
  border-radius: 6px; font-size: 13px; color: var(--ink2); cursor: pointer; }
.fold:hover { background: var(--line2); }
.fold--on { background: var(--mass-bg); color: var(--mass); font-weight: 600; }
.fold--on:hover { background: var(--mass-bg); }
.fold__n { margin-left: auto; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.ml { padding: 10px 12px; border-bottom: 1px solid var(--line2); cursor: pointer; }
.ml:last-child { border-bottom: none; }
/* Ждёт нашего ответа — жирным: это то же «непрочитанное» артборда, но
   считанное из направления последнего письма, а не из чьей-то пометки. */
.ml--un .ml__f { font-weight: 700; }
.ml--on { background: var(--mass-bg); border-left: 3px solid var(--mass); padding-left: 9px; }
.ml__t { display: flex; align-items: baseline; gap: 8px; }
.ml__f { font-size: 13px; font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml__at { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.ml__s { font-size: 13px; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml__p { font-size: 13px; color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ml__b { display: flex; gap: 5px; margin-top: 6px; flex-wrap: wrap; }
.body { margin-top: 12px; }
.body p { margin: 0 0 11px; font-size: 13.5px; line-height: 1.6; }
.body p:last-child { margin-bottom: 0; }

/* Поле ответа. В ядре textarea нет: полоса ответа артборда рисует кнопки, а
   письмо в одну строку не пишут — форма поля взята от полей ядра. */
.wr { display: block; width: 100%; min-height: 92px; margin: 8px 0; padding: 9px 11px;
  font: inherit; border: 1px solid var(--line); border-radius: 7px;
  background: var(--panel); color: var(--ink); resize: vertical; }
.wr:focus { outline: 2px solid var(--focus); outline-offset: -1px; }

/* ── Ступень v0.8: договор и его бумаги ───────────────────────────────────
 * Классы двух экранов: `Contract` (ui/src/contract.rs) и `Deed`
 * (ui/src/deed.rs). В ядре их нет — живут в CSS страницы, токены ядерные,
 * своих цветов ни одного (белый только контрастом к акценту).
 *
 * Откуда взяты:
 *   design/pages_h.py, contract(), строки 13–165 — `.stp__d` (подпись под
 *     ступенью пути; сама полоса уже есть в style.css как `.st`/`.st__n`,
 *     поэтому подпись названа `.st__d`), `.doc`/`.doc__m`/`.doc__t`/`.doc__s`.
 *   design/pages_i.py, deed(), строки 263–370 — `.kinds`/`.kind`/`.kind--on`,
 *     `.row2`, `.fld`/`.fld__l` (здесь `.ask`/`.ask__l` — см. ниже), `.chips`.
 *
 * Чего с артбордов сюда не переехало: `.mod`/`.dim` (окно поверх затемнения —
 * экран живёт лентой страницы), `.lnk` (панель «Связанные записи»: в ответе
 * есть ключ сделки, но не её название), `.inp`/`.inp--area` и `.kbd2` (поля
 * артборда — нарисованные прямоугольники, здесь это живые `input` и `select`,
 * которым хватает правил `.ask input`).
 */

/* Подпись под ступенью пути: дата отправки, чьи подписи стоят, срок действия.
   Ступень без подписи остаётся ровно такой же высоты — путь не прыгает. */
.st__d { font-size: 11px; opacity: .85; margin-top: 2px; }

/* Бумага договора строкой: род и номер, чей ход, состояние, акты. */
.doc { display: flex; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--line2); }
.doc:last-child { border-bottom: none; }
.doc__m { flex: 1; min-width: 0; }
.doc__t { font-size: 13px; font-weight: 600; }
.doc__s { font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* Род бумаги — плитками, а не строкой списка: родов три, и выбор одного из
   трёх виден целиком, без раскрытия. */
.kinds { display: flex; gap: 6px; margin-bottom: 15px; flex-wrap: wrap; }
.kind { flex: 1 1 160px; display: flex; flex-direction: column; align-items: center;
  gap: 4px; height: auto; padding: 11px 6px; border: 1px solid var(--line);
  border-radius: 9px; background: var(--panel); color: var(--ink);
  font-size: 13px; font-weight: 400; text-align: center; cursor: pointer; }
.kind b { font-size: 13px; font-weight: 600; }
.kind span { color: var(--muted); font-size: 12.5px; }
.kind--on { border-color: var(--mass); background: var(--mass-bg); color: var(--mass); }
.kind--on span { color: var(--mass); }

/* Поле, которое спрашивает: подпись сверху, ввод под ней. Парные поля — в два
   столбца.

   На артборде (`design/pages_i.py`) это `.fld`, и здесь оно так и называлось.
   В одном документе имя занято: выведенный дизайн зовёт `.fld` строкой
   «подпись — значение», и она стоит рядом на тех же страницах. Формой они
   расходятся полностью — та строка, эта столбик, — а по имени различить их
   было нечем, кроме имени ребёнка: `fld__l` против `fld__k`. Разведено 28.08:
   спрашивающее поле здесь зовётся `ask`, показывающее осталось `fld`.
   На артборде переименования нет — там у каждого свой лист, и столкновения
   не было. */
.ask { margin-bottom: 12px; }
.ask__l { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 4px; }
.ask input, .ask select { width: 100%; height: 34px; background: var(--panel2); }
.row2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }

/* Короткий выбор — чипами: валюта, а не выпадающий список из трёх строк.
   Правило заперто в поле формы: имя `.chips` уже стоит на списке людей экрана
   прав (ступень v0.5), и глобальное правило молча перекрасило бы чужой экран. */
.ask .chips { display: flex; gap: 6px; flex-wrap: wrap; }
.ask .chips button { height: 28px; padding: 0 11px; border-radius: var(--rp);
  border: 1px solid var(--line); background: var(--panel); color: var(--ink);
  font-size: 13px; font-weight: 500; }
.ask .chips button.on { background: var(--mass-work); border-color: var(--mass-work); color: #fff;
  font-weight: 600; }

/* Партнёрский канал: экраны `Partners`, `PartnerCard`, `PartnerOrder`.
 *
 * Взято с артбордов дословно — генератор `design/pages_k.py`:
 *   .lvl, .lvl i  — partners()      строки 19–21
 *   .grid3 (300/1fr/320) — partner_card() строка 188
 *   .lim, .lim i  — partner_card()  строки 145–146
 * Цвет метки уровня приходит на элемент токеном ядра (`var(--mass)`,
 * `var(--medium)`, `var(--low)`) — так же, как на артборде: своего цвета в
 * правиле нет.
 *
 * Цепочка состояний заказа (`chain` на артборде `PartnerOrder`) своих правил
 * не просит: в `style.css` уже есть `.st`/`.st__n`/`--done`/`--now` с артборда
 * `Work`, форма та же. Второе имя для одной формы — второй источник правды в
 * вёрстке.
 *
 * `.grid2`, `.colc`, `.kpi4`, `.f`, `.gr`, `.fnd`, `.hint`, `.err` уже
 * объявлены и берутся как есть.
 */

/* Три колонки карточки партнёра: реквизиты, заказы, разбор отступлений.
   Модификатор, а не второй `.grid3`: базу с раскроями звонков и почты завёл
   экран разговора, и правило без модификатора перебило бы их обоих — на
   артбордах это три разных раскроя под одним именем. */
/* Раскрой карточки партнёра (300/1fr/320) экран называет переменными. */

/* Уровень программы меткой: квадрат цвета плюс слово. Цвет — на элементе. */
.lvl { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; }
.lvl i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; font-style: normal; }

/* Занятость кредитного лимита. Заполнение — это долг, второго числа под него
   нет: ширина приходит на элемент долей от лимита. */
.lim { height: 10px; border-radius: 5px; background: var(--line2); overflow: hidden; display: flex; }
.lim i { display: block; height: 100%; }

/* Строка таблицы, которая открывает разбор. Наведение красит ядро, курсор —
   единственное, чего в нём нет, а без него строка не выглядит нажимаемой. */
.tbl tr.pick { cursor: pointer; }

/* Страничные классы кабинетов — экраны `Portal`, `PortalTicket`,
 * `PartnerPortal` (`ui/src/portal.rs`, `portal_ticket.rs`, `partner_portal.rs`).
 *
 * Взято с артбордов: `design/pages_g.py` — `portal_shell()` (строки 285–321),
 * `portal()` (323–444), `portal_ticket()` (446–553); `design/pages_k.py` —
 * `partner_portal()` (450–600). Только токены ядра.
 *
 * Остальная форма кабинетов собрана из того, что в `ui/style.css` уже есть:
 * `.kpi4`, `.grid2`, `.colc`, `.panel`, `.f`, `.jb` (строка списка с артборда:
 * `.li` в `portal()`, `.pr` в `partner_portal()` — одна и та же форма),
 * `.ev` (лента «что происходит»), `.ph`/`.gan` (установка с ходом по шагам),
 * `.st` (полоса пути обращения — артбордный `.stp`), `.pill`, `.hint`, `.err`,
 * `.empty`. Заводить им вторые имена значило бы развести две формы одного.
 */

/* Шапка кабинета: артбордный `.pt` — своя рама внешнего входа вместо рельса
 * сотрудника. На живом экране от неё остаётся одна строка: рисовать вторую
 * навигацию внутри страницы, где экраны идут лентой, значит обещать переходы,
 * которых нет. Организация справа приходит из привязки входа, а не из выбора. */
.ptop { display: flex; align-items: center; gap: 12px; height: 44px; padding: 0 14px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  margin-bottom: 14px; }
.ptop__n { font-size: 14px; font-weight: 700; letter-spacing: -.01em; }
.ptop__who { margin-left: auto; font-size: 13px; color: var(--muted); }

/* Многострочное поле «в чём беда» с артборда `PortalTicket`: беда в одну
 * строку не помещается, а у ядра поля только однострочные. Границы, фон и
 * фокус — как у `input` в `ui/style.css`, иначе поле выглядит чужим. */
.ta { display: block; width: 100%; box-sizing: border-box; font: inherit; font-size: 13px;
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--panel); color: var(--ink); resize: vertical; line-height: 1.5; }
.ta:focus { outline: 2px solid var(--focus); outline-offset: -1px; }
.ta::placeholder { color: var(--muted); }

/* Полоса пути обращения в кабинете: надпись шага по центру (артбордный
 * `.stp__n { text-align: center }`). Внутренние экраны читают шаги слева, у
 * клиента полоса стоит одна на панель, и центр держит её ровной. */
.st--mid .st__n { text-align: center; }

/* ── Ступень v0.8: касания, контур вендора, шаблоны ──────────────────────
 * Классы с артбордов `Care`, `Vendor`, `Templates`. Своих цветов нет,
 * токены ядерные.
 */

/* Приглушённая строка вне таблицы. В ядре `.dim` объявлен только внутри
   `.tbl`, и подпись в панели действий выглядела обычным текстом. */
.dim{color:var(--muted);font-size:13px}

/* Список бумаг с артборда `Templates`: выбранная подсвечена заливкой
   акцента, а не рамкой — рамка на светлом фоне не читается с метра. */
.tp { display: flex; align-items: center; gap: 9px; padding: 9px 10px; border-radius: 7px;
  cursor: pointer; }
.tp:hover { background: var(--panel2); }
.tp--on, .tp--on:hover { background: var(--mass-bg); }
.tp__m { flex: 1; min-width: 0; }
.tp__t { font-size: 13px; font-weight: 600; }
.tp__s { font-size: 12.5px; color: var(--muted); margin-top: 1px; }

/* Строка листа подстановки: маркер, название поля, происхождение. */
.fldlist { display: flex; align-items: center; gap: 8px; padding: 7px 0;
  border-bottom: 1px solid var(--line2); font-size: 13px; }
.fldlist:last-child { border-bottom: none; }
.fldlist .mono { color: var(--mass); background: var(--mass-bg); border-radius: 4px;
  padding: 1px 5px; font-size: 12px; }
/* ── Ступень v0.6: деньги ────────────────────────────────────────────────
 * Классы с артбордов `Quote`, `Invoice`, `Receivables`, `Economics`. В ядре
 * их нет, потому что живут в CSS страницы; токены — ядерные.
 */

/* Итог котировки и счёта: скидка и НДС стоят отдельными строками, а не
   растворяются в одной цифре «к оплате». */
.tots { margin-top: 14px; margin-left: auto; max-width: 420px; }
.tot { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0;
  font-size: 13px; border-bottom: 1px solid var(--line2); }
.tot:last-child { border-bottom: none; }
.tot .dim { color: var(--muted); }
.tot--big { border-top: 1px solid var(--line); margin-top: 6px; padding-top: 10px;
  font-size: 15px; font-weight: 700; }

/* График платежей: транш — условие, срок и сумма, а не строка таблицы. */
.sched { margin-top: 4px; }
.pay { display: flex; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--line2); }
.pay:last-child { border-bottom: none; }
.pay__m { flex: 1; min-width: 0; }
.pay__t { font-size: 13px; font-weight: 600; }
.pay__s { font-size: 13px; color: var(--muted); margin-top: 1px; }

/* Полоса старения долга: четыре корзины по границам домена. */
.age { display: flex; height: 26px; border-radius: 7px; overflow: hidden; }
.age i { display: block; height: 100%; }
.agel { display: flex; gap: 20px; margin-top: 9px; font-size: 13px;
  color: var(--muted); flex-wrap: wrap; }

/* Разрез числа полосой: имя, доля, сумма. */
.hbar { display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 13px; }
.hbar__n { flex: 0 0 152px; }
.hbar__b { flex: 1; height: 16px; border-radius: 4px; background: var(--line2); overflow: hidden; }
.hbar__b i { display: block; height: 100%; border-radius: 4px; }
.hbar__v { flex: 0 0 148px; text-align: right; font-variant-numeric: tabular-nums;
  font-weight: 600; }

/* Экран скоринга (артборд `Scoring`). Классы взяты с артборда именем в имя:
   балл крупно слева, разбор вклада строками, полоса растёт от середины —
   минусы влево, плюсы вправо. Своих цветов здесь нет, только токены ядра. */
.big { display: flex; align-items: center; gap: 18px; }
.big__v { font-size: 52px; font-weight: 700; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; line-height: 1; }
.big__w { flex: 1; min-width: 0; }
.fac { display: grid; grid-template-columns: 190px minmax(120px, 1fr) minmax(0, 1.2fr) 64px auto;
  gap: 12px; align-items: center; padding: 9px 0; border-bottom: 1px solid var(--line2);
  font-size: 13px; }
.fac:last-child { border-bottom: none; }
.fac__t { font-weight: 600; }
.fac__b { height: 8px; border-radius: 4px; background: var(--line2); position: relative; }
.fac__b i { position: absolute; top: 0; bottom: 0; border-radius: 4px; }
.fac__z { position: absolute; top: -4px; bottom: -4px; width: 1.5px;
  background: var(--muted); opacity: .5; }
.fac__w { color: var(--muted); font-size: 13px; }
.fac__n { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; }

/* История балла столбиками: рисуется изменение, а не частота прогонов. */
.hist { display: flex; align-items: flex-end; gap: 8px; height: 110px; }
.hist > div { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; }
.hist i { width: 100%; border-radius: 4px 4px 0 0; background: var(--mass); }
.hist span { font-size: 12px; color: var(--muted); }
.hist b { font-size: 12.5px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* Разбор проигрыша (артборд `Loss`): причины плашками, поля в два столбца. */
.pick { display: flex; flex-wrap: wrap; gap: 6px; }
.pick button { display: inline-flex; align-items: center; height: 29px; padding: 0 11px;
  border-radius: 15px; border: 1px solid var(--line); background: var(--panel);
  color: var(--ink); font-size: 13px; cursor: pointer; }
.pick button.on { background: var(--high); border-color: var(--high); color: #fff;
  font-weight: 600; }

/* ── Карточка сделки: с артборда `Deal`, правила перенесены дословно ──────
 *
 * Шапка записи (`.dh`), лента (`.tl*`), швы (`.seam*`), балл (`.score*`),
 * владение данными (`.own*`) и дела (`.deed*`). До этого карточка несла три
 * панели из девяти, и половине блоков артборда просто негде было появиться.
 *
 * Правила взяты из `design/Deal.dc.html` без правки: артборд — источник, и
 * «похожие» числа развели бы канву с экраном ровно там, где их сверяют.
 */
.dh { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; }
.dh__top { display: flex; align-items: flex-start; gap: 16px; padding: 14px 16px 12px; }
/* Имя занимает всё свободное место, сумма и действия уходят к правому краю.
   Без этого имя, сумма и кнопки жались друг к другу слева, а полстроки справа
   пустовало. */
.dh__t { flex: 1; min-width: 0; }
/* Сумма и её подпись — одной строкой: подпись под числом читалась оторванной
   от него и наезжала на кнопки. */
.dh__amt { display: flex; align-items: baseline; gap: 8px; margin-left: auto; }
.dh__amt b { display: inline; }
.dh__amt .dim { white-space: nowrap; }
.dh__act { margin-left: 0; }
.dh__t h1 { margin: 0; font-size: 19px; font-weight: 600; letter-spacing: -.015em; line-height: 1.25; }
.dh__meta { display: flex; align-items: center; gap: 9px; margin-top: 6px; font-size: 13px; color: var(--muted); }
.dh__amt { text-align: right; white-space: nowrap; }
.dh__amt b { font-size: 25px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; display: block; }
.dh__act { display: flex; gap: 7px; align-items: center; }

/* Лента */
.tabs { display: flex; gap: 2px; }
.tab { height: 26px; display: flex; align-items: center; padding: 0 9px; border-radius: 6px;
  font-size: 13px; color: var(--muted); cursor: pointer; }
.tab--on { background: var(--line2); color: var(--ink); font-weight: 600; }
.tl { display: flex; gap: 10px; padding: 11px 0; border-bottom: 1px solid var(--line2); }
.tl:last-child { border-bottom: none; }
.tl__i { flex: 0 0 26px; height: 26px; border-radius: 7px; background: var(--line2); color: var(--ink2);
  display: flex; align-items: center; justify-content: center; }
.tl__m { flex: 1; min-width: 0; }
.tl__h { display: flex; align-items: baseline; gap: 8px; }
.tl__h b { font-size: 13px; font-weight: 600; }
.tl__at { margin-left: auto; font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.tl__b { font-size: 13px; color: var(--ink2); margin-top: 3px; line-height: 1.45; }
.tl__f { display: flex; align-items: center; gap: 7px; margin-top: 6px; }

/* Панель смысла */
.seam { padding: 10px 0; border-bottom: 1px solid var(--line2); }
.seam:last-child { border-bottom: none; }
.seam__h { display: flex; align-items: center; gap: 7px; }
.seam__h b { font-size: 13px; font-weight: 600; flex: 1; }
.seam__w { font-size: 13px; color: var(--ink2); margin-top: 4px; line-height: 1.45; }
.seam__q { margin-top: 6px; padding: 6px 9px; border-left: 2px solid var(--line); background: var(--panel2);
  border-radius: 0 6px 6px 0; font-size: 13px; color: var(--muted); font-style: italic; line-height: 1.4; }
.seam__q b { font-style: normal; color: var(--ink2); font-weight: 600; }
.seam__a { display: flex; gap: 6px; margin-top: 8px; }
.score { display: flex; align-items: center; gap: 12px; }
.score__v { font-size: 30px; font-weight: 700; letter-spacing: -.02em; color: var(--medium); font-variant-numeric: tabular-nums; }
.own { display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 13px; border-bottom: 1px solid var(--line2); }
.own:last-child { border-bottom: none; }
.own__k { flex: 1; color: var(--ink2); }
.dot { width: 6px; height: 6px; border-radius: 50%; flex: 0 0 6px; }

/* ── Карточка клиента: плитки и строки контактов ──────────────────────────
 * Классы с артборда `Account`. Числа перенесены как есть: «похожие» значения
 * разводят канву с экраном ровно там, где их потом сверяют.
 */
.kpi3 { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 10px; }
.kpi3 > div { border: 1px solid var(--line); border-radius: 8px; padding: 10px 12px; background: var(--panel2); }
.kpi3 b { display: block; font-size: 19px; font-weight: 700; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; margin-top: 3px; }
/* Просроченный долг — охрой, а не красным: на карточке клиента это
   предупреждение, а не отказ, и красный тут спорил бы с «просрочено» в
   дебиторке, где он значит другое. */
.kpi3 b.warn { color: var(--medium); }

/* Строка человека: кружок, имя, роль с каналами. */
.rel { display: flex; align-items: center; gap: 9px; padding: 8px 0; border-bottom: 1px solid var(--line2); }
.rel:last-child { border-bottom: none; }
.rel__m { flex: 1; min-width: 0; }
.rel__n { font-size: 13px; font-weight: 600; }
.rel__r { font-size: 13px; color: var(--muted); margin-top: 1px; }

/* ── Обращение строкой рядом с планом запуска: артборд `Delivery` ─────────
 * Кружок, тема с номером, срок с исполнителем. Числа перенесены с канвы как
 * есть: «похожие» значения разводят канву с экраном ровно там, где их потом
 * сверяют.
 */
.tk { display: flex; gap: 9px; padding: 9px 0; border-bottom: 1px solid var(--line2); }
.tk:last-child { border-bottom: none; }
.tk__t { font-size: 13px; font-weight: 600; }
.tk__s { font-size: 13px; color: var(--muted); margin-top: 2px; }

/* ── Конструктор отчёта: поле и место, куда поле кладут ───────────────────
 * Классы с артборда `Reports`, числа перенесены как есть. Поле — кнопка, а не
 * строка списка: клик по нему меняет разрез или меру, то есть это действие.
 * Отсюда сброс собственного вида кнопки — иначе браузер рисует рамку и серый
 * фон поверх правил канвы.
 */
.fl { display: flex; align-items: center; gap: 7px; padding: 5px 8px; border-radius: 6px;
  font-size: 13px; color: var(--ink2);
  width: 100%; text-align: left; background: none; border: 0; height: auto;
  font-family: inherit; cursor: pointer; }
.fl:hover { background: var(--line2); }
/* Выбранное поле — цветом ядра, как на канве: там оно помечено `var(--mass)`. */
.fl--on { color: var(--mass); font-weight: 600; }
.drop { border: 1px dashed var(--line); border-radius: 7px; padding: 7px 9px; min-height: 34px;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* ── Метамодель: список объектов и подвал панели — с артборда `Admin` ──────
 * Числа перенесены как есть. Строка объекта в коде — кнопка, а не `div`
 * канвы: она переключает поля, и клавиатурой по списку иначе не пройти.
 * Собственный вид кнопки снимается здесь же, иначе браузер рисует поверх
 * артборда свою рамку и свой шрифт.
 */
/* Высота нижняя, а не жёсткая: имя объекта метамодели длиной в строку иначе
 * ложится поверх соседа — та же ловушка, что у узла дерева (см. `.tn`).
 * `min-height: max-content` в паре с ней обязателен: `<button>` считает свою
 * высоту по первой строке и переносом не растёт, замерено — 32 при
 * содержимом 45. */
.obj { display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  min-height: 30px; padding: 4px 9px; border-radius: 6px; font-size: 13px;
  color: var(--ink2); }
button.obj { min-height: max-content; }
button.obj { width: 100%; border: 0; background: transparent; font-family: inherit; text-align: left; cursor: pointer; }
.obj--on { color: var(--mass); font-weight: 600; }
/* Заливка выбранного объекта названа вместе с кнопкой: правило `button.obj`
   иначе перебивает её прозрачным фоном — специфичность выше. */
button.obj.obj--on { background: var(--mass-bg); }
.obj__n { margin-left: auto; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Подвал панели: на артборде он записан прямо в разметке, здесь — правилом,
   потому что в коде повторяется на каждой перерисовке. */
.panel__f { border-top: 1px solid var(--line); padding: 10px 14px; font-size: 13px;
  color: var(--muted); background: var(--panel2); border-radius: 0 0 10px 10px; line-height: 1.5; }

/* ── Права и роли: список ролей, матрица разделов, видимость — артборд `Roles`
 * Числа перенесены как есть. Раскрой берут `.grid2 .grid2--mirror` с
 * `--side:268px`, своего класса сетки здесь нет.
 *
 * Строка роли в коде — кнопка, а не `div` канвы: она переключает матрицу, и
 * клавиатурой по списку иначе не пройти. Собственный вид кнопки снимается
 * здесь же, иначе браузер рисует поверх артборда свою рамку и свой шрифт.
 */
.rl { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 7px; }
button.rl { width: 100%; border: 0; background: transparent; font-family: inherit; text-align: left; cursor: pointer; }
.rl--on { background: var(--mass-bg); }
/* Заливка выбранной роли названа вместе с кнопкой: правило `button.rl` иначе
   перебивает её прозрачным фоном — специфичность выше. */
button.rl.rl--on { background: var(--mass-bg); }
.rl__m { flex: 1; min-width: 0; }
/* `display: block` — плата за кнопку: внутри неё лежат `span`, а не `div`
   канвы (содержимое кнопки — строчная разметка), и без этого имя роли и
   подпись встали бы в одну строку. */
.rl__t { display: block; font-size: 13px; font-weight: 600; }
.rl__s { display: block; font-size: 12.5px; color: var(--muted); margin-top: 1px; }

/* Матрица прав: имя раздела слева, знаки по центру. */
.mx th { text-align: center; }
.mx th:first-child { text-align: left; }
.mx td { text-align: center; }
.mx td:first-child { text-align: left; }
.pm { display: inline-flex; width: 22px; height: 22px; border-radius: 6px; align-items: center;
  justify-content: center; }
.pm--y { background: var(--mass-bg); color: var(--mass); }
.pm--o { background: var(--medium-bg); color: var(--medium); }
.pm--n { background: var(--line2); color: var(--muted); }

/* Строка видимости записей. */
.sc { display: flex; align-items: center; gap: 9px; padding: 8px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.sc:last-child { border-bottom: none; }

/* ── Лиды: шаги конверсии и врезка — с артборда `Leads` ────────────────────
 * Числа перенесены как есть (22 px кружок, 12.5 px подпись, 7 px радиус
 * врезки): «похожие» значения развели бы канву с экраном ровно там, где их
 * потом сверяют. Раскрой `.grid2` и колонка `.colc` уже есть выше — второго
 * класса сетки под этот экран не заводится.
 */
.step { display: flex; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--line2); }
.step:last-child { border-bottom: none; }
.step__n { flex: 0 0 22px; height: 22px; border-radius: 50%; border: 1.5px solid var(--line);
  display: flex; align-items: center; justify-content: center; font-size: 12.5px; font-weight: 600; color: var(--muted); }
.step__n--on { background: var(--mass); border-color: var(--mass); color: #fff; }
.step__n--done { background: var(--mass-bg); border-color: var(--mass); color: var(--mass); }
.step__m { flex: 1; min-width: 0; }
.step__t { font-size: 13px; font-weight: 600; }
.step__d { font-size: 13px; color: var(--muted); margin-top: 2px; }
.mini { border: 1px solid var(--line); border-radius: 7px; padding: 8px 10px; margin-top: 7px; background: var(--panel2); }

/* ── Сделки · список: рамка таблицы, полоса выбора, подвал — с артборда
 * `Deals` ─────────────────────────────────────────────────────────────────
 * Четыре правила перенесены из `design/Deals.dc.html` дословно: числа канвы
 * (8/14 px полосы, 9/14 px подвала, 12.5 px шага стадии) идут как есть —
 * «похожие» значения развели бы канву с экраном ровно там, где их сверяют.
 * `.views`, `.view`, `.view__n`, `.filters`, `.fchip`, `.cb`, `.tbl__sel`
 * уже приехали в интерфейс из ядра (`ui/kernel.css`) — второй раз их здесь
 * не заводим.
 */
.tbl-wrap { flex: 1; min-height: 0; overflow: hidden; background: var(--panel);
  border: 1px solid var(--line); border-radius: 10px; }
.tbl-wrap .tbl th { padding-top: 10px; background: var(--panel2); position: sticky; top: 0; }
.bulk { display: flex; align-items: center; gap: 9px; padding: 8px 14px; background: var(--mass-bg);
  border-bottom: 1px solid var(--line); font-size: 13px; }
.bulk b { font-weight: 600; }
.stage-pill { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; }
.foot { display: flex; align-items: center; gap: 12px; padding: 9px 14px; border-top: 1px solid var(--line);
  font-size: 13px; color: var(--muted); background: var(--panel2); }

/* Живой чип отбора выбирает значение, а не только показывает выбранное:
 * артборд рисует итог («Владелец любой ×»), а экрану нужен сам выбор. Список
 * вложен в чип, чтобы отбор и его снятие лежали в одном месте, — второй ряд
 * с теми же тремя списками был бы тем же самым дважды. Своих цветов и
 * размеров правило не заводит: всё наследуется от `.fchip`.
 */
.fchip select { border: 0; background: transparent; color: var(--ink);
  font: inherit; font-weight: 600; padding: 0; max-width: 190px; cursor: pointer; }
.fchip__x { border: 0; background: transparent; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; }
/* Набор и галка строки — кнопки, а не `div` и `span`: набор — главная
 * навигация экрана, галка — выбор строки, и брать их обязано с клавиатуры.
 * Элемент в обход табуляции недоступен вовсе. Вид остаётся ядерным: правила
 * ниже гасят только собственную обводку и шрифт кнопки. */
button.view { border: 0; background: transparent; font-family: inherit; }
button.cb { padding: 0; cursor: pointer; }
button.cb:not(.cb--on) { background: transparent; }

/* ── Разрез по позициям: экономика сделки, ui/src/economics.rs ─────────────
 *
 * Панель под раскроем артборда `Economics`: строка на строку счёта. Артборд
 * её не рисует — он показывает год портфеля, — и расхождение разобрано в
 * `docs/parts-v0.9.md`.
 *
 * Ячейка закупочной несёт три вещи разом: слово «не заведена», пояснение про
 * снимок и действие. Базовое правило ядра `.tbl td` рассчитано на одну строку
 * (`--row`, `nowrap`), и без правил ниже кнопка выдавливала бы содержимое за
 * край ячейки. Числовых колонок это не касается.
 */
.cut { margin-top: 14px; }
.cut__cost { white-space: normal; padding-top: 7px; padding-bottom: 7px; }
.cut__cost .dim { max-width: 28ch; margin-left: auto;
  font-size: 12px; line-height: 1.35; white-space: normal; }
.cut__go { height: 26px; margin-top: 5px; }
.cut__fix { display: flex; gap: 6px; justify-content: flex-end; margin-top: 6px; }
.cut__fix input { width: 130px; height: 26px; }
.cut__fix button { height: 26px; }
/* Разрыв разреза со счетами читается как отказ, а не как подпись: `.hint`
   стоит в файле позже `.warn` и перекрасил бы его обратно в приглушённый. */
.cut__gap { color: var(--high); font-weight: 600; }

/* ── Обзор предприятия: плитки, стадии, находки, прогноз ──────────────────
 * Классы с артборда `Main`, числа перенесены как есть: «похожие» значения
 * разводят канву с экраном ровно там, где их потом сверяют.
 *
 * Раскроя `.ov` здесь нет намеренно. Две колонки обзора (1.35fr к 1fr) кладёт
 * общий зеркальный `.grid2`: ширину называет экран переменной, а класс сетки
 * один на интерфейс — иначе их список рос бы с каждым артбордом.
 */
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; }
.kpi { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 13px 15px; }
.kpi__v { font-size: 25px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; margin-top: 4px; }
.kpi__d { display: flex; align-items: center; gap: 5px; font-size: 13px; margin-top: 6px; color: var(--muted); }
.srow { display: grid; grid-template-columns: 132px 1fr 118px 42px; gap: 12px; align-items: center;
  padding: 7px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.srow:last-child { border-bottom: none; }
.att { display: flex; gap: 11px; padding: 11px 0; border-bottom: 1px solid var(--line2); }
.att:last-child { border-bottom: none; }
.att__m { flex: 1; min-width: 0; }
.att__t { font-size: 13px; font-weight: 600; }
.att__w { font-size: 13px; color: var(--ink2); margin-top: 3px; }
.fcast { display: flex; flex-direction: column; gap: 9px; }
.fcast__r { display: grid; grid-template-columns: 96px 1fr 132px; gap: 10px; align-items: center; font-size: 13px; }

/* ── Согласование и его маршруты, ступень v0.9 ──────────────────────────────
 *
 * Правила перенесены дословно с артбордов `Approval` и `Routes`: числа канвы
 * («похожие» значения разводят канву с экраном ровно там, где их потом
 * сверяют). Раскрой берётся из `.grid2` — свой класс сетки здесь не заводится,
 * колонку экран называет числом: `--side:360px` и `--side:288px`.
 */

/* Ход согласования: ступени маршрута лентой слева направо. С артборда
 * `Approval`. От `.sn` отличается тем, что узлы стоят встык и растягиваются
 * поровну — ход читается одной линией, а не плиткой. */
.flow { display: flex; align-items: stretch; gap: 0; }
.node { flex: 1; border: 1px solid var(--line); border-radius: 9px; padding: 11px 12px; background: var(--panel2); }
.node--done { border-color: var(--mass); background: var(--mass-bg); }
.node--now { border-color: var(--medium); background: var(--medium-bg); box-shadow: 0 0 0 3px var(--medium-bg); }
.node__t { font-size: 13px; font-weight: 600; }
.node__k { font-size: 12px; color: var(--muted); margin-top: 2px; }
.node__s { font-size: 13px; color: var(--muted); margin-top: 6px; }

/* Журнал решений строками. С артборда `Approval`. */
.jr { display: flex; gap: 9px; padding: 7px 0; border-bottom: 1px solid var(--line2); font-size: 13px; align-items: baseline; }
.jr:last-child { border-bottom: none; }
.jr__n { flex: 0 0 20px; font-family: "IBM Plex Mono", monospace; font-size: 12.5px; color: var(--muted); text-align: right; }
.jr__c { flex: 0 0 110px; font-weight: 600; }

/* Строка списка маршрутов. С артборда `Routes`. */
.rt { display: flex; align-items: center; gap: 9px; padding: 9px 10px; border-radius: 7px; }
.rt--on { background: var(--mass-bg); }
.rt__m { flex: 1; min-width: 0; cursor: pointer; }
.rt__t { font-size: 13px; font-weight: 600; }
.rt__s { font-size: 12.5px; color: var(--muted); margin-top: 1px; }

/* ── Автоматизация: опись правил, блоки «Когда · Если · То», журнал
 * срабатываний — с артборда `Automation` (ui/src/automation.rs) ────────────
 *
 * Числа канвы перенесены как есть: 9/10 px строки правила, 12/12.5/13 px
 * шрифтов, 27 px жетона. «Похожие» значения развели бы канву с экраном ровно
 * там, где их потом сверяют.
 *
 * `.cond` — единственное столкновение имён за перенос: в интерфейсе это уже
 * занятое имя (моноширинный блок условия на экране «Правила», строка 325), а
 * на артборде `Automation` — ряд жетонов. Правило ниже поэтому взято
 * вложенным (`.blk .cond`): чужой экран остаётся каким был, а свой получает
 * ряд. Четыре объявления сверх канвы (шрифт, фон, рамка, отступ) гасят то,
 * что достаётся от одноимённого правила выше; без них ряд жетонов приезжал
 * бы в моноширинной рамке.
 */
.rule { padding: 9px 10px; border-radius: 7px; border: 1px solid transparent; }
.rule--on { background: var(--mass-bg); border-color: var(--mass); }
.rule__t { font-size: 13px; font-weight: 600; }
.rule__s { font-size: 13px; color: var(--muted); margin-top: 2px; }
/* Правило в описи — кнопка, а не `div`: это главный выбор экрана, и брать его
   обязано с клавиатуры. Элемент в обход табуляции недоступен вовсе. Вид
   остаётся с канвы: правило гасит только собственную обводку и высоту
   кнопки, а заливку выбранного оставляет `.rule--on`. */
button.rule { display: block; width: 100%; height: auto; text-align: left;
  font: inherit; cursor: pointer; }
button.rule:not(.rule--on) { background: transparent; }

.blk { border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.blk__h { display: flex; align-items: center; gap: 8px; padding: 9px 12px; background: var(--panel2);
  border-bottom: 1px solid var(--line); }
.blk__h b { font-size: 12.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.blk__b { padding: 11px 12px; display: flex; flex-direction: column; gap: 8px; }
.blk .cond { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 13px;
  font-family: inherit; background: transparent; border: 0; padding: 0; }
.tok { display: inline-flex; align-items: center; gap: 5px; height: 27px; padding: 0 9px;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel); font-size: 13px; }
.tok--f { font-weight: 600; }
.tok--v { background: var(--mass-bg); border-color: var(--mass); color: var(--mass); font-weight: 600; }
/* Второе и следующие условия связаны с первым уголком: ряд жетонов без него
   читается как перечень равноправных строк, а это одно условие из нескольких. */
.link-v { position: relative; padding-left: 15px; }
.link-v::before { content:""; position: absolute; left: 5px; top: -9px; bottom: 50%; width: 9px;
  border-left: 1.5px solid var(--line); border-bottom: 1.5px solid var(--line); border-radius: 0 0 0 5px; }

.run { display: flex; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--line2); font-size: 13px; }
.run:last-child { border-bottom: none; }

/* ── Карточка клиента: люди и числа ───────────────────────────────────────
 * Классы с артборда `Account` (24.08): человек карточкой (`.person*`) и
 * строка «ключ — число» (`.kv*`). Числа перенесены как есть.
 *
 * Ленты здесь нет намеренно: артборд зовёт её `.tc`, но `.tc` в интерфейсе
 * уже занят карточкой задачи на столе диспетчера, а строй ленты у карточки
 * клиента тот же, что у карточки сделки, — и его несёт `.tl*` выше. Второе
 * имя одному строю развело бы канву с экраном на первой же правке.
 */
.person { display: flex; gap: 9px; padding: 9px 0; border-bottom: 1px solid var(--line2); }
.person:last-child { border-bottom: none; }
.person__m { flex: 1; min-width: 0; }
.person__n { font-size: 13px; font-weight: 600; }
.person__r { font-size: 13px; color: var(--muted); margin-top: 1px; }
.person__t { font-size: 12.5px; color: var(--muted); margin-top: 3px; display: flex; align-items: center; gap: 6px; }
.person__t--cold { color: var(--high); }

/* Установленная база: что у клиента стоит. Строкой, а не строкой таблицы:
   колонка карточки — триста точек, а ячейка таблицы ядра не переносится, и
   название лицензии выехало бы за край. Товарной строки предмета сделки здесь
   тоже нет намеренно: у той цена, скидка и НДС, у этой редакция, места и срок
   — разные вопросы к разным сущностям. */
.ib { padding: 8px 0; border-bottom: 1px solid var(--line2); }
.ib:last-child { border-bottom: none; }
.ib__n { font-size: 13px; font-weight: 600; }
.ib__s { font-size: 12.5px; color: var(--muted); margin-top: 1px; }
/* Места, срок и метка в одну строку с переносом: в узкой колонке они уезжают
   под себя, а не режутся. */
.ib__t { display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  font-size: 12.5px; margin-top: 3px; }
.kv { display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 13px;
  border-bottom: 1px solid var(--line2); }
.kv:last-child { border-bottom: none; }
.kv__k { flex: 1; color: var(--ink2); }
.kv__v { font-weight: 600; font-variant-numeric: tabular-nums; }

/* ── Карточка человека: каналы связи ──────────────────────────────────────
 * Классы с артборда `Contact` (ступень и0.1). Перенесены дословно: числа
 * канвы («12.5px», «8px 0») идут как есть — «похожие» значения разводят канву
 * с экраном ровно там, где их потом сверяют.
 *
 * Здесь только каналы. Остального с артборда в интерфейсе уже хватает, и
 * второе имя одному строю развело бы канву с экраном на первой же правке:
 * лента (`.tc` канвы) — это `.tl*` карточки сделки; места работы (`.at`
 * канвы) — это `.person*` карточки клиента; строка «ключ — число» (`.kv*`) и
 * шапка записи (`.dh*`) стоят там же выше.
 */
.ch { display: flex; align-items: center; gap: 9px; padding: 8px 0;
  border-bottom: 1px solid var(--line2); font-size: 13px; }
.ch:last-child { border-bottom: none; }
.ch__i { flex: 0 0 24px; height: 24px; border-radius: 6px; background: var(--panel2);
  color: var(--ink2); display: flex; align-items: center; justify-content: center; }
.ch__m { flex: 1; min-width: 0; }
.ch__v { font-weight: 600; }
.ch__s { color: var(--muted); margin-top: 1px; }

/* ── Рабочее место оператора: полоса разговора, очередь, компания ──────────
 * Классы с артборда `CallDesk` (ступень и0.2 арки взаимодействия). Числа
 * канвы («12.5px», «11px 14px») перенесены как есть: «похожие» значения
 * разводят канву с экраном ровно там, где их потом сверяют.
 *
 * Ленты здесь нет: артборд зовёт её `.tc`, но `.tc` в интерфейсе занят
 * карточкой задачи на столе диспетчера, а строй ленты у стола тот же, что у
 * карточки сделки и карточки человека, — его несёт `.tl*` выше.
 *
 * Строка человека (`.who*`) и заметка (`.note*`) в интерфейсе уже есть — их
 * поставил экран «Входящий звонок», и там у них другая работа. Стол правит их
 * под себя внутри `.desk`, а не заводит вторые имена одному строю.
 */
.desk { display: flex; flex-direction: column; min-height: 0; }
.desk .grid3 > .panel { padding: 0; }

/* Полоса живого звонка над всем: пока говорят, она главная на экране. */
.live { display: flex; align-items: center; gap: 12px; padding: 11px 14px; margin-bottom: 12px;
  background: var(--mass-bg); border: 1px solid var(--mass); border-radius: 10px; }
.live__d { width: 10px; height: 10px; border-radius: 50%; background: var(--mass); flex: 0 0 10px; }
.live__t { font-size: 14px; font-weight: 600; }
.live__s { font-size: 13px; color: var(--ink2); margin-top: 2px; }
.live__c { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.live__n { font: 600 17px/1 "IBM Plex Mono", monospace; font-variant-numeric: tabular-nums;
  color: var(--mass); }

/* Очередь: звонки, обращения и молчание клиента одной лентой по срочности. */
.q { display: flex; gap: 9px; padding: 9px 10px; border-radius: 7px; cursor: pointer; }
.q--on { background: var(--mass-bg); }
.q:hover { background: var(--line2); }
.q__i { flex: 0 0 26px; height: 26px; border-radius: 7px; background: var(--panel2);
  color: var(--ink2); display: flex; align-items: center; justify-content: center; }
.q__m { flex: 1; min-width: 0; }
.q__t { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.q__s { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.q__d { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.q__d--hot { color: var(--high); font-weight: 600; }

/* Человек: кто на линии и чем он был занят до этого звонка. */
.desk .who { padding: 12px 14px; border-bottom: 1px solid var(--line); }
.desk .who .ava { border-radius: 50%; }
.who__act { margin-left: auto; display: flex; gap: 7px; }

/* Второй уровень шапки: компания разговора и поиск клиента. Своей строкой, а
 * не хвостом имени: у имени человека и у выбора компании разный вес, и внутри
 * одной строки они спорят за место. */
.whoco { display: flex; align-items: center; gap: 9px; padding: 9px 14px;
  border-bottom: 1px solid var(--line); background: var(--panel2); font-size: 13px; }
.whoco__k { display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
.whoco__d { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.whoco__find { margin-left: auto; display: flex; align-items: center; gap: 7px;
  width: 300px; height: 28px; padding: 0 10px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 7px; color: var(--muted); }
.whoco__find input { flex: 1; min-width: 0; height: 26px; padding: 0; border: none;
  background: transparent; font-size: 13px; }
.whoco__find input:focus { outline: none; }
.pickco { display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 8px;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel);
  color: var(--ink); font-weight: 600; font-size: 13px; max-width: 220px; }

/* Заметка пишется во время разговора, а не после: после — уже по памяти. */
.desk .note { display: flex; align-items: flex-start; gap: 9px; margin-top: 0;
  padding: 10px 12px; border-top: 1px solid var(--line); background: var(--panel2);
  border-radius: 0 0 10px 10px; }
.desk .note__in { flex: 1; min-height: 34px; padding: 7px 11px; background: var(--panel);
  border: 1px solid var(--line); border-radius: 7px; font: inherit; font-size: 13px;
  color: var(--ink); resize: vertical; }

/* Правая колонка: чем помочь оператору прямо сейчас. */
.hint2 { padding: 9px 0; border-bottom: 1px solid var(--line2); font-size: 13px;
  line-height: 1.45; }
.hint2:last-child { border-bottom: none; }
.hint2 b { display: block; font-size: 13px; font-weight: 600; margin-bottom: 3px; }

/* Жетон слоя видения: нарисованное на канве, чего ступень не строит. */
.vision { display: inline-flex; align-items: center; gap: 5px; height: 20px; padding: 0 7px;
  border: 1px dashed var(--medium); border-radius: 5px; color: var(--medium);
  font-size: 12px; }

/* ── Канал связи карточкой (экран «Интеграции», ступень и0.4) ───────────────
 *
 * Форма с артборда `Integrations`: знак, имя с состоянием, во что превращается
 * событие, числа за сутки и действия справа. В ядре такого компонента нет —
 * он живёт здесь и только через его токены.
 *
 * Строкой (`.ch`) канал не рисуется намеренно: у строки нет места ни под
 * причину отказа словами, ни под числа за сутки, а без них карточка канала
 * отвечает «работает» и не отвечает «что через него прошло».
 */
.chan { display: flex; align-items: flex-start; gap: 12px; padding: 14px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
  margin-bottom: 10px; }
.chan:last-child { margin-bottom: 0; }
.chan__i { flex: 0 0 38px; height: 38px; border-radius: 10px; background: var(--panel2);
  color: var(--ink2); display: flex; align-items: center; justify-content: center; }
.chan__m { flex: 1; min-width: 0; }
.chan__n { font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.chan__s { font-size: 13px; color: var(--muted); margin-top: 3px; line-height: 1.45; }
.chan__f { display: flex; align-items: center; gap: 14px; margin-top: 9px; font-size: 13px; }
.chan__a { display: flex; flex-direction: column; gap: 6px; align-items: flex-end; }

/* ── Оргструктура (экран «Оргструктура», ступень о0.1) ──────────────────────
 *
 * Формы с артборда `Org`: дерево подразделений слева, человек строкой в
 * центре, ближайший день рождения и вставка списком. В ядре таких компонентов
 * нет — они живут здесь и только через его токены.
 *
 * Вложенность показана отступом, а не значком с чертой: у структуры
 * предприятия три уровня, и рисовать под них разводку линий значит завести
 * четвёртый способ показать то же самое.
 */
.tree { display: flex; flex-direction: column; gap: 1px; }
/* Узел дерева переживает длинное имя.
 *
 * «Департамент клиентского обслуживания и связей с общественностью» — 61 знак
 * в колонке 320 точек. Имя стояло в `span` без `flex` и `min-width`, поэтому
 * не сжималось: строка распирала кнопку, текст вылезал за неё влево и ложился
 * поверх соседнего узла. Число справа с `margin-left:auto` довершало —
 * выдавливало имя наружу. У предприятия таких имён девять из двадцати двух.
 *
 * Знак стоит на первой строке, а не по центру: у имени в две строки центр
 * приходится между ними, и знак оказывался напротив пустоты. */
.tn { display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start; gap: 8px; width: 100%; min-height: max-content; padding: 7px 9px;
  border: none; background: transparent; border-radius: 7px; color: var(--ink);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
/* Кнопка не сжимается: `.tree` — flex-колонка, а её дети по умолчанию
 * сжимаемы, и узел с именем в две строки получал высоту одной. Замерено:
 * высота 32 при содержимом 45 — тринадцать точек ложились на соседа. */
.tn { flex: 0 0 auto; }
.tn > svg { flex: 0 0 auto; margin-top: 2px; }
.tn > span:not(.tn__n) { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.tn:hover { background: var(--line2); }
.tn--on { background: var(--mass-bg); color: var(--mass); font-weight: 600; }
.tn__n { margin-left: auto; font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums; }
.tn--on .tn__n { color: var(--mass); }
.tn--l2 { padding-left: 26px; }
.tn--l3 { padding-left: 43px; }
.tn--l4 { padding-left: 60px; }

/* Человек строкой: лицо, имя с должностью, роль в системе. */
.pr { display: flex; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid var(--line2); }
.pr:last-child { border-bottom: none; }
.pr__m { flex: 1; min-width: 0; }
.pr__n { font-size: 13px; font-weight: 600; }
.pr__t { font-size: 13px; color: var(--muted); margin-top: 1px; }
.pr__b { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.pr--head { background: var(--panel2); border-radius: 7px; padding: 9px 10px;
  margin-bottom: 4px; border-bottom: none; }

/* Ближайшие дни рождения. */
.bd { display: flex; align-items: center; gap: 9px; padding: 7px 0;
  border-bottom: 1px solid var(--line2); font-size: 13px; }
.bd:last-child { border-bottom: none; }
.bd__m { flex: 1; min-width: 0; }
.bd__d { color: var(--muted); white-space: nowrap; }
.bd__d--soon { color: var(--mass); font-weight: 600; }

/* Заведение списком: вставил, разобрали, показали построчно. */
.paste { width: 100%; min-height: 96px; padding: 10px 12px; background: var(--panel2);
  border: 1px dashed var(--line); border-radius: 8px; color: var(--ink);
  font: 400 12.5px/1.6 "IBM Plex Mono", ui-monospace, monospace; resize: vertical; }
.paste:focus { outline: none; border-color: var(--mass); }
.paste::placeholder { color: var(--muted); }
.rl2 { display: flex; align-items: baseline; gap: 8px; padding: 6px 0;
  border-bottom: 1px solid var(--line2); font-size: 13px; }
.rl2:last-child { border-bottom: none; }
.rl2__n { flex: 0 0 26px; color: var(--muted); font-variant-numeric: tabular-nums; }
.rl2__m { flex: 1; min-width: 0; }
.rl2__w { color: var(--high); }

/* ── Срочность строки: одно правило подсветки на весь продукт ────────────────
 *
 * Требование пришло от четверых разных людей за одну книгу требований, значит
 * болит: «выделять как-то красным, если там какая-то аларма» (35:55), «всё,
 * что должно просрочиться либо просрочено» (36:03), «позвонит вам кто-то — и
 * вот вы зайдёте, и сразу» (12:14). Срочное читается из списка, а не из
 * карточки, в которую надо провалиться.
 *
 * Состояние строки считает домен (`forush_domain::urgency`, зеркало на экране
 * — `crate::sales_api::alarm`), а не каждый список сам. Здесь только форма.
 *
 * ЗАЛИВКА ВСЕЙ СТРОКИ, а не значок в углу — прямая просьба: «и не такими
 * блямбами, Лёш, это неудобно. Просто вся строка» (36:30).
 *
 * МИГАНИЯ ЗДЕСЬ НЕТ НАМЕРЕННО. Просили «должно как-то мигать», но тем же
 * голосом назвали боль: «боль Битрикса в том, что он мигает очень мелко»
 * (36:12). Мигание решает задачу «заметить» и проваливает задачу «работать»:
 * через час список из двухсот строк, где десяток мерцает, превращается в
 * стробоскоп, и мерцание перестают видеть — ровно та же слепота, что от
 * уведомлений в общем чате. Постоянная заливка видна с первого взгляда и
 * остаётся читаемой весь день. Решение обратимо: мигание добавляется сюда
 * одним `animation`, домен и компоненты на него не завязаны.
 *
 * ЦВЕТ ОДИН НЕ ОТВЕЧАЕТ. Рядом с заливкой строка обязана сказать словом и
 * числом, почему горит («просрочено 2 дня», «до срока 47 мин»): цвет не
 * читается вслух, не виден дальтонику и не переживает чёрно-белую печать.
 * Слова даёт то же правило домена, поэтому на всех экранах они одни.
 * Вторая опора формой — полоса слева: заливка и полоса различают строку даже
 * там, где оттенок съеден проектором.
 */
.tbl tr.row--hot > td { background: var(--high-bg); }
.tbl tr.row--soon > td { background: var(--medium-bg); }
.tbl tr.row--hot > td:first-child { box-shadow: inset 3px 0 0 var(--high); }
.tbl tr.row--soon > td:first-child { box-shadow: inset 3px 0 0 var(--medium); }

/* Наведение не гасит заливку, а сгущает полосу: иначе строка под курсором
   перестаёт быть срочной ровно в тот момент, когда её собрались открыть. */
.tbl tr.row--hot:hover > td:first-child { box-shadow: inset 6px 0 0 var(--high); }
.tbl tr.row--soon:hover > td:first-child { box-shadow: inset 6px 0 0 var(--medium); }

/* Карточка потока диспетчерской — не строка таблицы, но правило то же и класс
   тот же: два имени для одного состояния разошлись бы в первый месяц. */
.tc.row--hot { background: var(--high-bg); border-left-color: var(--high); }
.tc.row--soon { background: var(--medium-bg); border-left-color: var(--medium); }

/* Причина словом и числом. Живёт в той колонке, которая этот вопрос уже
   задаёт, — второй подписи о том же на строке не заводится. */
.row__why { font-size: 12.5px; font-weight: 600; color: var(--high);
  font-variant-numeric: tabular-nums; }
.row__why--soon { color: var(--medium); }

/* Строка списка работ (рабочее место инженера) — не таблица, правило то же.
   Отступ и вылет полями держат заливку по всей строке: без них она обрывалась
   бы по краю текста и читалась той самой блямбой. */
.jb.row--hot, .jb.row--soon { padding-left: 10px; padding-right: 10px;
  margin-left: -10px; margin-right: -10px; border-radius: 7px; }
.jb.row--hot { background: var(--high-bg); box-shadow: inset 3px 0 0 var(--high); }
.jb.row--soon { background: var(--medium-bg); box-shadow: inset 3px 0 0 var(--medium); }

/* Причина в один ряд с именем: строка списка сделок одноэтажная, и второй
   этаж только у горящих сбил бы ритм таблицы там, где ритм и есть чтение. */
.row__why--inline { margin-left: 8px; }

/* ── Путь в два уровня, верхний уровень. Взято из галереи
   (`design/crm-styles.html`, правило `.arc` и комментарий над ним) — правила
   перенесены как есть, своих цветов и шрифтов здесь нет: всё через токены
   ядра.

   Нижний уровень — полоса стадий `.path__s` выше по этому же листу. Она
   построена раньше и не трогается: арка встаёт над ней, а не вместо неё.

   Отступ снизу — единственное, чего в галерее нет. Там компонент стоит в
   демонстрационной рамке с собственными полями, здесь он первым блоком
   карточки, и без отступа шапка записи прилипает к нему вплотную. */
.arc { display: flex; align-items: stretch; gap: 0; border: 1px solid var(--line);
  border-radius: 8px; overflow: hidden; background: var(--panel); margin-bottom: 12px; }
.arc__o { display: flex; flex-direction: column; justify-content: center;
  padding: 10px 14px; border-right: 1px solid var(--line); min-width: 0; }
.arc__o:last-child { border-right: 0; }
.arc__o--done { background: var(--panel2); flex: 0 0 auto; }
.arc__o--on { flex: 1; min-width: 0; }
.arc__t { display: flex; align-items: center; gap: 7px; font-size: 15px; font-weight: 600;
  white-space: nowrap; }
.arc__o--done .arc__t { color: var(--muted); font-weight: 400; }
.arc__o--done .arc__t b { color: var(--ink2); font-weight: 600; }
.arc__d { font-size: 13px; color: var(--muted); margin-top: 3px; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.arc__ok { flex: 0 0 17px; height: 17px; border-radius: 50%; background: var(--mass-bg);
  color: var(--mass); display: inline-flex; align-items: center; justify-content: center; }

/* Звено, которого не было (`arc__o--next` в галерее), своего правила там не
   имеет и не получает его здесь: цвет мимо ядра — первый способ увести дизайн,
   а сказать «не было» звену хватает слова в `arc__d`. */

/* Открываемое звено говорит об этом курсором. Цвета оно не меняет: подсветка
   пройденного звена спорила бы с заливкой `--done`, которая уже значит
   «пройдено». */
.arc__o--go { cursor: pointer; }

/* ── Продукт окошком (`ui/src/leads.rs`, `ProductPeek`). Требование визионера
   28.08: продукт кликабелен в обращении, лиде и сделке, наведение показывает
   минимум, нажатие ведёт в справочник направлений.

   Своего правила в ядре у окошка нет: это не блок экрана, а поведение чипа,
   и завести под него токен значило бы говорить о бренде там, где речь о
   наведении мышью. Цвета и отступы — только через `var(--…)`.

   Окошко живёт наведением и фокусом сразу: клавиатурный обход обязан видеть
   то же, что мышь, иначе подсказка существует только для одной руки. */
.peek { position: relative; display: inline-flex; }
.peek__b { display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  border: 0; font: inherit; }
.peek__w { position: absolute; z-index: 32; left: 0; top: calc(100% + 6px);
  display: none; min-width: 232px; max-width: 320px; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
  box-shadow: var(--shadow); text-align: left; white-space: normal; }
.peek:hover .peek__w, .peek:focus-within .peek__w { display: block; }
.peek__w b { display: block; font-size: 13px; font-weight: 600; color: var(--ink); }
.peek__k { display: block; margin-top: 3px; font-size: 12px; color: var(--muted); }
.peek__go { display: block; margin-top: 7px; font-size: 12px; color: var(--mass-work); }
