/* ---------- палитра и темы -------------------------------------------------
   Вид кабинета — тот же, что у админки `ai-site-engine`: одна система у человека
   на машине, один вид. Обе темы стоят рядом одной строкой — light-dark(светлое,
   тёмное), — а что из них выбрано, решает color-scheme корня: по умолчанию «как
   в системе», а переключатель в шапке ставит на <html> атрибут data-theme
   (система → светлая → тёмная → система). Сохранённый выбор проставляет скрипт
   в <head> (App.razor) до первой отрисовки — иначе тёмный кабинет мигал бы
   белым при каждом переходе.

   Имена токенов — из соседнего проекта; ниже к ним заведены псевдонимы под теми
   именами, которыми кабинет уже пользуется, чтобы правило читалось словом,
   а не переводом с одного словаря на другой. */
:root {
  color-scheme: light dark;
  --bg: light-dark(#f4f7fc, #0b111c);
  --panel: light-dark(#ffffff, #121b2b);
  --panel-2: light-dark(#eef2f9, #1a2438);   /* наведение, чипы, вторичные кнопки */
  --field: light-dark(#ffffff, #1a2438);
  --code: light-dark(#f7f9fd, #070c15);      /* диффы, транскрипты, пути */
  --line: light-dark(#e3e8f0, #23304a);
  --text: light-dark(#101828, #e7ecf6);
  --dim: light-dark(#5b6478, #9aa4b8);
  --accent: light-dark(#1d4ed8, #6f9dff);
  --accent-fg: light-dark(#ffffff, #08101f);
  --accent-soft: light-dark(#e6edfd, rgba(111, 157, 255, .15));
  --ok: light-dark(#0f7b4f, #5fd39a);
  --err: light-dark(#b42318, #ff8a80);
  /* Тени лесенкой: приподнятое над страницей и висящее над всем. Одна тень на два случая
     либо давит карточки, либо не отделяет меню от текста под ним. */
  --shadow-sm: 0 1px 2px light-dark(rgba(16, 24, 40, .06), rgba(0, 0, 0, .3));
  --shadow: 0 12px 32px light-dark(rgba(16, 24, 40, .14), rgba(0, 0, 0, .55));
  --radius: 14px;
  --radius-sm: 10px;
  --radius-xs: 8px;
  --radius-pill: 999px;
  /* Одна скорость на весь кабинет: разнобой в длительностях читается как неисправность. */
  --quick: .13s cubic-bezier(.2, .6, .3, 1);

  /* Шапка тёмная в обеих темах: это граница системы, а не часть страницы. */
  --top-bg: #0f1623;
  --top-fg: #ffffff;
  --top-dim: #98a3b8;
  --top-line: rgba(255, 255, 255, .1);
  --top-hover: rgba(255, 255, 255, .07);
  --top-on: rgba(255, 255, 255, .13);
  /* Акцент шапки светлее страничного: на тёмном фоне синий #1d4ed8 выглядит грязным пятном. */
  --top-accent: #8fb4ff;

  /* Псевдонимы под словарём кабинета. */
  --surface: var(--panel);
  --border: var(--line);
  --muted: var(--dim);
  --bad: var(--err);
  --idle: var(--dim);
}

:root[data-theme="light"] { color-scheme: light }
:root[data-theme="dark"] { color-scheme: dark }

* { box-sizing: border-box }

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font: 14px/1.55 system-ui, "Segoe UI", sans-serif;
  /* пути и адреса длиннее экрана телефона — переносим их, а не страницу вбок */
  overflow-wrap: break-word;
}

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

/* Заголовок страницы после каждого перехода получает фокус — этим читалка объявляет, куда
   человек попал (`FocusOnNavigate`, Routes.razor); отсюда же и `tabindex="-1"` на нём.
   Рамку при этом не рисуем: она обводит не то, чем сейчас пользуются, а место, куда просто
   встал курсор клавиатуры, и появляется на каждой открытой странице. Голос читалки остаётся,
   потому что фокус остаётся; убрана только обводка, и только у того, кто фокус получает сам,
   а не по нажатию Tab. */
h1[tabindex="-1"]:focus,
h1[tabindex="-1"]:focus-visible {
  outline: none;
}

code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 0.88em;
  color: var(--dim);
  word-break: break-all;
}

h1 {
  font-size: 1.45rem;
  font-weight: 600;
  margin: 0 0 1.25rem;
}

h2 {
  font-size: 1rem;
  font-weight: 600;
  margin: 1.75rem 0 0.7rem;
}

/* Подпись страницы: вплотную к заголовку — она его продолжение, — но с отбивкой снизу.
   Без неё таблица начинается сразу под текстом, и подпись читается как её первая строка. */
.lead {
  color: var(--dim);
  margin: -0.85rem 0 1.4rem;
  max-width: 60ch;
}

/* --- значки -----------------------------------------------------------------
   Свои контурные SVG вместо эмодзи (SPEC §3.16): эмодзи рисует шрифт системы — на
   Windows, на маке и на телефоне это три разные картинки, часть цветные там, где вокруг
   всё серое, и у каждой своя базовая линия. Здесь одна сетка, одна толщина и один цвет —
   тот же, каким покрашен текст рядом. */
.icon {
  flex: none;
  /* Значок стоит в строке текста, а не «на» ней: без сдвига он всплывает над базовой линией. */
  vertical-align: -0.18em;
}

/* --- шапка ------------------------------------------------------------------
   Разделы наверху строкой, а не колонкой сбоку: кабинет — рабочее место на день,
   и место под содержимое здесь дороже, чем постоянно видимое меню. */

.topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 9px 18px;
  color: var(--top-fg);
  border-bottom: 1px solid var(--top-line);
  /* Прилипшая шапка полупрозрачна и размывает то, что под ней: так видно, что страница
     уехала под неё, а не оборвалась. Сплошной цвет — запасной вариант для браузеров
     без `backdrop-filter`, поэтому он же и объявлен первым. */
  background: var(--top-bg);
  background: color-mix(in oklab, var(--top-bg) 86%, transparent);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
}

/* Слева — сам кабинет: знак и название. Знак не логотип, а первая буква названия
   в акцентном квадрате: рисовать эмблему тому, у кого её нет, честнее не выйдет. */
.specialist {
  display: flex;
  align-items: center;
  gap: 9px;
  /* Сжимается и не растёт: «Тестовая админка» и «Управление поставщиками» — обычные
     названия (§3.13), и раздвигать ими ряд разделов нельзя. Что не поместилось —
     под многоточием, целиком видно по наведению. */
  flex: 0 1 auto;
  min-width: 0;
  max-width: 15rem;
  padding-right: 14px;
  border-right: 1px solid var(--top-line);
}

.brand-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-xs);
  background: linear-gradient(140deg, var(--top-accent), #3b6fd4);
  color: #0b1220;
}

.specialist-name {
  min-width: 0;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.specialist-title {
  color: var(--top-dim);
  font-size: 12px;
  white-space: nowrap;
}

/* Разделов больше, чем у соседней админки, поэтому часть из них собрана в группы (§3.13),
   а ряд забирает всё свободное место и при нехватке переносится. */
.nav {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
  /* Не помещается — прокручивается вбок, а не переносится вниз (§3.13): перенос отнимает
     у содержимого вторую строку высоты на каждом узком экране. Меню групп при этом не
     обрезается, потому что позиционируется от окна, а не от ряда (position: fixed ниже). */
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  /* Полосы не видно: ряд короткий, и полоса под ним читается как край страницы. */
  scrollbar-width: none;
}

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

/* Пункт — таблетка со значком и словом. Значок из общего словаря (§3.16): тот же, что
   у строк этого раздела в списках и у его плитки на главной. */
.nav-item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  /* Не сжимается: ряд прокручивается вбок (§3.13), и ужимать пункты незачем — сжатые
     подписи всё равно нечитаемы, а прокрутка показывает их целиком. */
  flex: none;
  height: 32px;
  padding: 0 11px;
  border-radius: var(--radius-pill);
  color: var(--top-dim);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--quick), color var(--quick);
}

.nav-item .icon {
  color: var(--top-dim);
  transition: color var(--quick);
}

/* Пункт специалиста: имя бывает длиннее, чем весь остальной ряд, — обрезаем многоточием,
   целиком оно видно по наведению и на его собственной странице. Нижняя граница не даёт
   ему схлопнуться до одного значка: пункт, от которого осталась картинка, перестаёт быть
   именем сотрудника, а он здесь первый по смыслу (§3.16). */
.nav-worker {
  min-width: 9rem;
  max-width: 13rem;
}

.nav-worker > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-item:hover {
  color: var(--top-fg);
  background: var(--top-hover);
}

.nav-item:hover .icon { color: var(--top-fg) }

/* «Где я сейчас» — заливка и акцентный значок. Цветом красится именно значок: подсветить
   и фон, и текст, и рамку значит сказать одно и то же трижды. */
.nav-item.active {
  color: var(--top-fg);
  background: var(--top-on);
}

.nav-item.active .icon { color: var(--top-accent) }

/* Пункт-значок: без подписи ему нужен свой размер, иначе шестерёнка мельче соседних слов.
   Высота при этом строго та же, что у пунктов со словами. */
.nav-item.ic {
  justify-content: center;
  width: 32px;
  padding: 0;
}

/* Кто в кабинете — видно всегда: журнал отвечает именем, и человек должен знать,
   чьим именем он подписывает утверждение (SPEC §3.15). */
.whoami {
  position: relative;
  flex: 0 0 auto;
}

/* Кружок с первой буквой имени: и значок, и ответ на вопрос «под кем я сижу». */
.whoami-face {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  background: var(--top-on);
  color: var(--top-fg);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  user-select: none;
  list-style: none;
  /* Кольцо тенью, а не рамкой: рамка добавляет пиксель к размеру, и кружок перестаёт
     совпадать по высоте с соседними таблетками. */
  box-shadow: 0 0 0 1px var(--top-line);
  transition: box-shadow var(--quick), background var(--quick);
}

.whoami-face::-webkit-details-marker { display: none }

.whoami-face:hover,
.whoami[open] .whoami-face {
  background: color-mix(in oklab, var(--top-accent) 26%, transparent);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--top-accent) 55%, transparent);
}

.whoami-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 10px);
  z-index: 50;
  min-width: 13rem;
  padding: 12px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  animation: drop var(--quick) both;
}

.whoami-name {
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 10px;
  overflow-wrap: anywhere;
}

.whoami form { margin: 0 }
.whoami-menu .btn { width: 100% }

/* Бейдж в шапке — то единственное, ради чего человек в неё смотрит, не собираясь никуда
   идти: он должен быть заметен, поэтому заливка акцентом, а не приглушённым белым. */
.topbar .badge {
  min-width: 18px;
  padding: 0 6px;
  background: var(--top-accent);
  color: #0b1220;
  font-weight: 700;
}

.topbar .btn-quiet {
  background: var(--top-on);
  color: var(--top-fg);
  border-color: transparent;
}

.topbar .btn-quiet:hover {
  background: rgba(255, 255, 255, .2);
}

/* Переключатель темы: оба значка лежат в разметке, нужный показывает CSS по атрибуту
   на <html>. Скрипт сюда не лезет — разметку кнопки Blazor перерисовывает когда захочет,
   и подставленное им пропало бы. */
.theme-btn {
  display: grid;
  place-items: center;
  flex: none;
  width: 32px;
  height: 32px;
  padding: 0;
  cursor: pointer;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--top-dim);
  transition: background var(--quick), color var(--quick);
}

.theme-btn:hover {
  background: var(--top-hover);
  color: var(--top-fg);
}

/* Оба значка в одной клетке грида — кнопка не дёргается по ширине при переключении. */
.theme-btn .icon { grid-area: 1 / 1 }

/* Состояний три — «как в системе», светлая, тёмная, — и показывается ровно то, в котором
   кабинет сейчас. Атрибута нет — значит «как в системе», и у него свой значок: показывать
   в этом состоянии луну значит сделать его неотличимым от тёмной темы. */
.theme-sun,
.theme-moon { display: none }

:root[data-theme="light"] .theme-auto { display: none }
:root[data-theme="light"] .theme-sun { display: block }
:root[data-theme="dark"] .theme-auto { display: none }
:root[data-theme="dark"] .theme-moon { display: block }

/* Группа разделов: щелчок по пункту раскрывает список под ним. Меню абсолютное, поэтому
   у ряда разделов нет прокрутки — она обрезала бы раскрытое меню по высоте строки. */
.nav-drop {
  position: relative;
}

.nav-drop > summary {
  list-style: none;
  cursor: pointer;
}

.nav-drop > summary::-webkit-details-marker { display: none }

/* Что пункт — группа, а не раздел, видно до клика: в ряду одинаковых слов подменю иначе
   находят случайно. Уголок из того же набора, что остальные значки, и поворотом он же
   говорит, что группа сейчас раскрыта. */
.nav-chevron {
  margin-left: -1px;
  opacity: .65;
  transition: transform var(--quick), opacity var(--quick);
}

.nav-drop[open] > summary .nav-chevron {
  transform: rotate(180deg);
  opacity: 1;
}

.nav-drop[open] > summary {
  color: var(--top-fg);
  background: var(--top-on);
}

.nav-drop[open] > summary .icon { color: var(--top-fg) }

/* Группа подсвечена, когда открыт раздел внутри неё: иначе, спрятав раздел под группу,
   мы отняли у человека ответ на вопрос «где я сейчас» — у пунктов вне групп он есть. */
.nav-drop:has(.nav-drop-item.active) > summary {
  color: var(--top-fg);
  background: var(--top-on);
}

.nav-drop:has(.nav-drop-item.active) > summary > .icon:first-child { color: var(--top-accent) }

/* Меню от окна, а не от ряда: ряд разделов прокручивается вбок и обрезал бы всё, что
   лежит внутри него. Координаты проставляет скрипт в момент раскрытия (App.razor);
   без скрипта у `fixed` с автоматическими отступами остаётся статическая позиция —
   то есть ровно под своим пунктом, чего для запасного варианта достаточно. */
.nav-drop-menu {
  position: fixed;
  z-index: 50;
  min-width: 14rem;
  /* На телефоне 14rem — почти весь экран: меню обязано остаться в его пределах, иначе
     половина пунктов оказывается за краем и до них не дотянуться. */
  max-width: calc(100vw - 16px);
  padding: 6px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  animation: drop var(--quick) both;
}

/* Меню появляется, а не возникает: полтора десятка миллисекунд движения показывают,
   откуда оно взялось, — и это единственная анимация в кабинете. */
@keyframes drop {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}

/* Тем, кто просил систему не двигать интерфейс, ничего не двигаем. */
@media (prefers-reduced-motion: reduce) {
  .working .pill svg { animation: none }

  .nav-drop-menu,
  .whoami-menu { animation: none }
}

/* Мостик через отбивку в 8 пикселей между пунктом и списком. Без него мышь, идущая от
   слова к первой строке списка, на миг оказывается вне меню — и раскрытое по наведению
   закрывается ровно на полпути. Мостик прозрачный и принадлежит меню, поэтому «ушла ли
   мышь» считается правильно. */
.nav-drop-menu::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -10px;
  height: 10px;
}

/* Служебный кружок в шапке: шестерёнка «устройства» стоит в одном ряду с «кто в кабинете»
   и темой, и выглядеть должна так же — иначе три соседних значка читаются как три разных
   сорта кнопок (§3.13). */
.round { position: relative }

.round-btn {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  color: var(--top-dim);
  cursor: pointer;
  user-select: none;
  list-style: none;
  transition: background var(--quick), color var(--quick);
}

.round-btn::-webkit-details-marker { display: none }

.round-btn:hover,
.round[open] .round-btn {
  background: var(--top-hover);
  color: var(--top-fg);
}

/* Открытый раздел внутри — тот же ответ на «где я сейчас», что у пунктов ряда. */
.round:has(.nav-drop-item.active) .round-btn {
  background: var(--top-on);
  color: var(--top-accent);
}

.nav-drop-item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border-radius: var(--radius-xs);
  color: var(--text);
  text-decoration: none;
  font-size: 13px;
  white-space: nowrap;
  transition: background var(--quick), color var(--quick);
}

.nav-drop-item .icon { color: var(--dim) }

.nav-drop-item:hover { background: var(--panel-2) }
.nav-drop-item:hover .icon { color: var(--text) }

.nav-drop-item.active {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.nav-drop-item.active .icon { color: var(--accent) }

/* Страница занимает окно, но не бесконечно: на мониторе в 21:9 строка во всю ширину
   не читается — глаз теряет начало следующей. Поэтому потолок выше обычного экрана
   (на 1440 и 1920 ничего не обрезается) и центровка сверх него. Читаемую ширину внутри
   держат сами абзацы (.lead, .duty-text, .empty). */
.content {
  padding: 20px 28px 40px;
  max-width: 1600px;
  margin: 0 auto;
}

/* --- содержимое --- */

.empty {
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  padding: 2rem 1.75rem;
  color: var(--dim);
  max-width: 54ch;
  background: color-mix(in oklab, var(--panel) 60%, transparent);

  /* Пустой экран — единственное объяснение на странице: когда список наполнится, подпись
     под заголовком заменит его. Поэтому он не жмётся к левому краю, а стоит посередине
     и читается как обращение к человеку, а не как примечание к пустоте (SPEC §3.14). */
  margin: 2.5rem auto;
  text-align: center;
}

.empty p { margin: 0 }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 16px;
  margin-bottom: 16px;
}

.grid {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.grid th {
  text-align: left;
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--dim);
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--line);
  /* Шапка таблицы чуть темнее полотна: длинный список без неё сливается в одно пятно. */
  background: color-mix(in oklab, var(--panel-2) 55%, var(--panel));
}

.grid td {
  padding: 0.65rem 0.9rem;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

.grid tbody tr { transition: background var(--quick) }

/* Подсветка строки под мышью — не украшение: в таблице из четырёх колонок глаз теряет
   строку на полпути к последней. */
.grid tbody tr:hover { background: color-mix(in oklab, var(--panel-2) 55%, transparent) }

.grid tr:last-child td { border-bottom: none }

.muted { color: var(--dim) }
.state-ok { color: var(--ok) }
.state-idle { color: var(--dim) }
.state-bad { color: var(--err) }

.badge {
  display: inline-block;
  min-width: 1.4em;
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  background: var(--err);
  color: #fff;
  font-size: 11px;
  text-align: center;
}

/* --- кнопки: «в таблетку», как у соседнего проекта и виджета --- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font: 500 13px system-ui;
  padding: 8px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-fg);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background var(--quick), border-color var(--quick), color var(--quick),
              box-shadow var(--quick), transform var(--quick);
}

.btn:hover { background: color-mix(in oklab, var(--accent) 88%, black) }

/* Нажатие видно самой кнопкой: без этого при медленном ответе сервера человек жмёт второй раз. */
.btn:active { transform: translateY(1px) }

.btn-quiet {
  background: var(--panel-2);
  color: var(--text);
  border-color: var(--line);
  box-shadow: none;
}

.btn-quiet:hover {
  background: var(--panel-2);
  border-color: var(--accent);
  color: var(--accent);
}

.btn-bad {
  background: var(--err);
  border-color: var(--err);
  color: #fff;
}

.btn-bad:hover { background: color-mix(in oklab, var(--err) 88%, black) }

.btn:disabled {
  opacity: .45;
  cursor: default;
  box-shadow: none;
  transform: none;
}

.btn:disabled:hover { background: var(--accent) }
.btn-quiet:disabled:hover {
  background: var(--panel-2);
  border-color: var(--line);
  color: var(--text);
}

.row-actions {
  white-space: nowrap;
  text-align: right;
}

.row-actions .btn {
  padding: 5px 12px;
}

.row-actions .btn + .btn { margin-left: 0.35rem }

.diff,
pre {
  margin: 0.6rem 0 0;
  padding: 10px;
  background: var(--code);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: 12px/1.45 ui-monospace, "Cascadia Mono", Consolas, monospace;
  white-space: pre;
  overflow: auto;
  max-height: 22rem;
}

/* --- обрыв связи --- */

#blazor-error-ui {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 0.7rem 1.25rem;
  background: var(--err);
  color: #fff;
  z-index: 1000;
}

#blazor-error-ui .reload { color: #fff }

#blazor-error-ui .dismiss {
  cursor: pointer;
  float: right;
}

/* --- вход и формы --- */

/* Экран входа без разделов кабинета: пока неизвестно, кто пришёл, показывать нечего. */
.bare {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: var(--bg);
}

.login { width: min(26rem, 100%) }
.login h1 { margin-top: 0 }

.form,
.login form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* Форма не растягивается на весь экран: поле ввода шириной в монитор человек не читает,
   а промахивается мимо него глазами. Правило общее, а не только для формы в карточке:
   на «Людях» форма стоит прямо на странице, и поле логина было длиной в полтора метра. */
.form,
.login form {
  max-width: 34rem;
}

.form label,
.login label {
  margin-top: 0.6rem;
  font-size: 12px;
  color: var(--dim);
}

.form input,
.form select,
.form textarea,
.login input {
  font: 13px system-ui;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--text);
}

.form textarea { resize: vertical }

.form input:focus,
.form select:focus,
.form textarea:focus,
.login input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.form textarea:disabled {
  background: var(--panel-2);
  color: var(--dim);
}

.form .muted {
  margin: 0.1rem 0 0;
  font-size: 12px;
}

.form h2 { margin-top: 1.4rem }

.row {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 1rem;
}

.form .row button:disabled,
.row button:disabled {
  opacity: .5;
  cursor: default;
}

/* --- разговор --- */

/* Лента показывает работу, а не текст (SPEC §3.16): ответ абзацем, вызов инструмента —
   приглушённой строкой с раскрытием, ошибка красным и раскрыта сразу. */
.talk {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: 1rem 0 1.5rem;
}

/* Реплика: лицо слева, подпись и текст справа. Подряд идущие реплики одного собеседника
   подписываются один раз, но место под лицо остаётся — иначе текст съезжает влево и колонка
   рассыпается (SPEC §3.16). */
.msg {
  display: grid;
  grid-template-columns: 28px 1fr;
  gap: 0 10px;
}

.msg + .msg.msg-first { margin-top: 0.5rem }

.msg-face {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
}

.msg-first .msg-face {
  background: var(--panel-2);
  color: var(--dim);
}

.msg-worker.msg-first .msg-face {
  background: var(--accent-soft);
  color: var(--accent);
}

.msg-body { min-width: 0 }

.msg-who {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 2px;
  font-size: 12px;
  font-weight: 600;
}

/* Имя модели — мелко и приглушённо: это подпись под подписью, а не второй собеседник. */
.msg-model {
  font-size: 11px;
  font-weight: 400;
  color: var(--dim);
}

.talk-said {
  padding: 0.4rem 0.7rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
  white-space: pre-wrap;
}

.talk-answer { white-space: pre-wrap }

.talk-tool {
  font-size: 13px;
  color: var(--dim);
}

.talk-tool summary { cursor: pointer }

/* Кабинет в ленте — не собеседник, а система: кружок у него тише человеческих. */
.msg-cabinet.msg-first .msg-face {
  background: transparent;
  color: var(--dim);
  box-shadow: inset 0 0 0 1px var(--line);
}

.talk-working { gap: 0.6rem }

/* Три точки — единственная «крутилка» в кабинете: она отвечает на вопрос «оно вообще живое»,
   на который лента молчащего прогона не отвечает никак. */
.dots {
  display: inline-flex;
  gap: 3px;
}

.dots i {
  width: 5px;
  height: 5px;
  border-radius: var(--radius-pill);
  background: currentColor;
  animation: blink 1.1s infinite ease-in-out;
}

.dots i:nth-child(2) { animation-delay: .15s }
.dots i:nth-child(3) { animation-delay: .3s }

@keyframes blink {
  0%, 80%, 100% { opacity: .25 }
  40% { opacity: 1 }
}

@media (prefers-reduced-motion: reduce) {
  .dots i { animation: none; opacity: .6 }
}

/* Счётчик окна контекста под полем ввода (SPEC §3.16). */
.window {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 12px;
}

.window-bar {
  display: inline-block;
  width: 5rem;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--panel-2);
  overflow: hidden;
}

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

/* Окно на исходе — полоска того же цвета, что и цифры: два разных сигнала об одном
   и том же читаются как два разных повода волноваться. */
.window.state-bad .window-fill { background: var(--err) }

/* --- файловая панель --- */

.crumbs {
  margin: 1.25rem 0 1rem;
  font-size: 13px;
}

.crumbs a {
  color: var(--accent);
  text-decoration: none;
}

.crumbs a:hover { text-decoration: underline }

.crumb-sep {
  color: var(--dim);
  margin: 0 0.35rem;
}

.crumb-here { font-weight: 600 }

.files-actions {
  margin-bottom: 1rem;
  flex-wrap: wrap;
}

.files-actions .grow {
  flex: 1 1 14rem;
  font: 13px system-ui;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--text);
}

/* Штатный InputFile выглядит как системная кнопка «Выберите файл»: прячем его внутрь
   своей и оставляем видимой подпись. */
.upload {
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.upload input[type="file"] {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

/* Имя занимает всё свободное место, а переносится только по границе слова:
   «выгруз / ки 1С» читается хуже, чем строка, ушедшая на второй ряд целиком. */
.grid.files td:first-child,
.grid.files th:first-child {
  width: 100%;
  overflow-wrap: break-word;
}

.grid.files a {
  color: var(--text);
  text-decoration: none;
}

.grid.files a:hover { text-decoration: underline }

.file-icon {
  margin-right: 0.45rem;
  color: var(--dim);
}

.grid .num {
  text-align: right;
  white-space: nowrap;
}

.grid.files td:nth-child(3) { white-space: nowrap }

/* Переключатель и его объяснение (SPEC §3.16). Раньше `.check` не имел стилей вовсе,
   и три переключателя с длинными подписями читались как сплошной текст, в котором
   квадратики теряются. Теперь строка — это строка: квадратик, слово, под ними подпись
   с отступом ровно на ширину квадратика. */
.check {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0.75rem 0 0.25rem;
  cursor: pointer;
  font-weight: 500;
}

.check input { margin-top: 0.2rem }

.check-hint {
  margin: 0 0 1rem 1.6rem;
  color: var(--dim);
  font-size: 13px;
  max-width: 70ch;
}

/* Выбор умений делу (§3.5): строка на умение, а не строка из умений. Иначе десяток
   названий с пилюлями сливается в абзац, и человек отмечает не то, что хотел. */
.picks {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0.75rem 0 0;
}

.pick {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: 0.2rem 0.6rem;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}

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

.pick input { margin-top: 0.25rem }

/* Название и пилюля — первой строкой, «когда открывать» — второй под ним. Прижатая
   к правому краю подсказка выглядела второй колонкой таблицы, и глаз читал строку дважды. */
.pick-name { font-weight: 500 }

.pick-name, .pick .pill { grid-column: 2 }

.pick .pill { justify-self: start; margin-top: -0.15rem }

.pick-when {
  grid-column: 2;
  color: var(--dim);
  font-size: 13px;
  max-width: 70ch;
}

/* Опасный угол карточки (SPEC §3.14): то, что не отменяется одним нажатием, отбито
   чертой и стоит последним — чтобы человек доходил до него, а не натыкался. */
.danger {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px dashed var(--line);
}

/* --- карточка дела --- */

.duty-text {
  white-space: pre-wrap;
  max-width: 78ch;
  font-size: 15px;
  line-height: 1.55;
  margin: 0 0 1rem;
}

/* Три вопроса подряд — что делает, когда делает, что вышло, — поэтому три карточки,
   а не форма с полями (SPEC §3.7, §3.13). */
.duty-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.duty-head h1 { margin-bottom: 0.5rem }

.duty-meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.duty-go {
  display: flex;
  gap: 0.5rem;
  padding-top: 0.35rem;
}

.pill {
  /* Значок и слово в одной пилюле выравниваются по середине: при inline-block значок садился
     на базовую линию и пилюля в столбце состояния выглядела перекошенной. */
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  vertical-align: middle;
  padding: 2px 10px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  background: var(--panel-2);
  border: 1px solid transparent;

  /* Пилюля не переносится: «есть что посмотреть», разложенное на две строки внутри
     скруглённой рамки, читается как сломанная вёрстка. Колонка раздвинется — таблица
     на это и рассчитана. */
  white-space: nowrap;
}

.pill-ok {
  color: var(--ok);
  border-color: var(--ok);
}

.pill-idle { color: var(--dim) }

/* Листы книги и тома файловой панели вкладками (SPEC §3.16): книга с тремя листами,
   показанная одним, — это книга, из которой человек увидел треть и не узнал об этом.
   Вкладка-ссылка выглядит так же, как вкладка-кнопка: разница в устройстве, не в смысле. */
a.tab { text-decoration: none; display: inline-flex; align-items: center; gap: 0.35rem }

/* Вторая кнопка загрузки — папкой (§3.16). Уже первой: перетаскивают в неё редко, её берут
   осознанно, когда несут дерево целиком. */
.dropzone-small { flex: 0 0 auto; min-width: 12rem }

/* Список документаций — широкие строки с обложкой (SPEC §3.16): через год их три десятка,
   и нужную человек находит взглядом, а не чтением. */
.doc-tools {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0.6rem 0 1rem;
}

.doc-search {
  max-width: 32rem;
}

.doc-group {
  font-size: 13px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .05em;
  margin: 1.2rem 0 0.4rem;
}

.doc-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.doc-row {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  padding: 0.8rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.doc-cover {
  position: relative;
  flex: 0 0 auto;
  width: 84px;
  height: 108px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: calc(var(--radius) / 1.5);
  background: var(--bg);
  color: var(--dim);
}

/* Внутрь, а не «в обрез»: обложка — это узнавание, а обрезанная первая страница узнаётся
   хуже целой. Подложка при этом остаётся папкой — светлым прямоугольником с рамкой. */
.doc-cover img { width: 100%; height: 100%; object-fit: contain; padding: 4px }

/* Число файлов уголком: «папка» без счёта не отличается от одинокого документа. Уголок
   лежит **внутри** обложки: вынесенный за край, он обрезался её же `overflow: hidden`
   и выглядел не счётчиком, а огрызком синего прямоугольника. */
.doc-count {
  position: absolute;
  right: 4px;
  bottom: 4px;
  min-width: 20px;
  padding: 1px 5px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 11px;
  line-height: 1.4;
  text-align: center;

  /* Ободок цветом подложки: поверх светлой первой страницы уголок иначе сливается с ней. */
  box-shadow: 0 0 0 2px var(--panel);
}

/* Шапка строки документации: значок, название и пилюля в одну строку. Общий `.named`
   здесь не годится — он про «значок и блок», и пилюля в нём схлопывалась в столбец. */
.doc-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.doc-head .pill { flex: none; white-space: nowrap }

.doc-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.3rem }

.doc-title { font-weight: 600 }

.doc-meta {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.2rem;
  font-size: 13px;
}

/* Метка, по которой можно нажать: тема с самой строки, а не поиск её в фильтрах. */
.chip-button { cursor: pointer; border: 1px solid transparent; font: inherit }

.chip-on { color: var(--accent); background: var(--accent-soft); border-color: var(--accent) }

/* На телефоне обложка уходит наверх строки, а мета — под текст: три колонки в 360 пикселей
   не помещаются, и первым страдает то, ради чего строка и заведена, — название. */
@media (max-width: 720px) {
  .doc-row { flex-wrap: wrap }
  .doc-meta { flex-direction: row; align-items: center; gap: 0.6rem; width: 100% }
}

.tabs {
  display: flex;
  gap: 0.25rem;
  flex-wrap: wrap;
  margin: 0.8rem 0 1rem;
}

/* Подпись под вкладками — уже не продолжение заголовка, а объяснение выбранного тома,
   поэтому она отбита сверху: вплотную к вкладкам она читалась как их подпись. */
.tabs + .lead { margin-top: 0 }

.tab {
  padding: 0.3rem 0.7rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--dim);
  font-size: 13px;
  cursor: pointer;
}

.tab.on {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

/* Таблица внутри окна показа прокручивается сама — и вбок тоже: выгрузка на сорок колонок
   иначе растягивает окно за край экрана. */
.viewer-table {
  flex: 1;
  overflow: auto;
  max-height: 70vh;
  margin-top: 0.4rem;
}

.viewer-table .grid { font-size: 13px }

.viewer-table .grid th {
  position: sticky;
  top: 0;
  background: var(--panel);
  z-index: 1;
}

.viewer-table .grid td {
  white-space: nowrap;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Плитки каталога (SPEC §3.16). Ширина фиксированная, а не «сколько влезет»: превью первой
   страницы читается только начиная с некоторого размера, а плитка в полэкрана — это уже
   не список. */
.files-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0.9rem;
  margin-top: 0.6rem;
}

.file-tile { min-width: 0 }

/* Само превью — в рамке с постоянным соотношением сторон: без неё сетка прыгает по мере
   загрузки картинок, и человек промахивается мимо той, на которую целился. */
.file-tile-face {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 3 / 4;
  padding: 0;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--dim);
  cursor: pointer;
  transition: border-color var(--quick);
}

.file-tile-face:hover { border-color: var(--accent) }

.file-tile-face.folder { aspect-ratio: 3 / 2 }

.file-tile-face img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

/* Имя — в две строки и не длиннее: длинное имя файла иначе растягивает всю сетку. */
.file-tile-name {
  margin-top: 0.35rem;
  font-size: 13px;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}

.file-tile-note { font-size: 12px }

/* Переключатель вида: две кнопки, слипшиеся в одну — так видно, что выбор один из двух. */
.switch {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.switch-item {
  display: flex;
  align-items: center;
  padding: 0.35rem 0.6rem;
  background: transparent;
  border: none;
  color: var(--dim);
  cursor: pointer;
}

.switch-item.on {
  background: var(--accent-soft);
  color: var(--accent);
}

/* Окно показа файла. Высота — от окна браузера, а не от содержимого: pdf на сорок страниц
   иначе растягивает модалку за пределы экрана. */
.viewer {
  width: min(1000px, 94vw);
  max-height: 92vh;
  display: flex;
  flex-direction: column;
}

.viewer h2 {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.viewer-doc {
  flex: 1;
  min-height: 65vh;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
}

.viewer-image {
  max-width: 100%;
  max-height: 78vh;
  object-fit: contain;
  align-self: center;
}

.viewer-text {
  flex: 1;
  overflow: auto;
  max-height: 78vh;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 13px;
}

/* Идущая работа (SPEC §3.16). Полоса, а не значок в углу: «сейчас работает» — то, ради чего
   человек и открыл экран, и оно должно попадаться на глаза само, а не находиться.
   Пропадает вместе с прогоном, поэтому места под себя не резервирует. */
.working {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.6rem;
}

.working .pill {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Пульс — единственная анимация в кабинете, и она здесь по делу: работа идёт молча,
   и неподвижная строка «сейчас работает» через минуту читается как забытая.
   Тем, кто просил меньше движения, она не мигает — блок ниже это и делает. */
.working .pill svg { animation: working-pulse 1.8s ease-in-out infinite }

@keyframes working-pulse {
  0%, 100% { opacity: 1 }
  50% { opacity: 0.35 }
}

/* Строка списка, по которой прогон идёт прямо сейчас: подсветка слева, а не цветом всей
   строки, — цветная строка в списке из тридцати читается как ошибка. */
.grid tbody tr.row-now { background: var(--accent-soft) }
.grid tbody tr.row-now td:first-child { box-shadow: inset 3px 0 0 var(--accent) }

.pill-bad {
  color: var(--err);
  border-color: var(--err);
}

/* Строка списка со значком: значок слева, название и пояснение справа. Значок помогает
   найти строку глазами в списке из тридцати, но смысла в одиночку не несёт — рядом всегда
   стоит слово (SPEC §3.16). */
.named {
  display: grid;
  grid-template-columns: 1rem 1fr;
  gap: 0 0.6rem;
  align-items: start;
}

/* Значок строки: приглушён, потому что он про «найти», а не про «прочесть». Цвет наследуется
   там, где строка сама цветная, — исход прогона в журнале красится вместе со словом. */
.sign {
  margin-top: 2px;
  color: var(--dim);
}

.state-ok > .sign,
.state-bad > .sign,
.sign.state-ok,
.sign.state-bad { color: inherit }

/* Метка: тема документации, прикреплённая документация у дела. Метками, а не строкой через
   запятую, — по ним ищут глазами, а запятая между двумя длинными названиями теряется. */
.chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem;
  margin: 0 0.25rem 0.25rem 0;
  padding: 1px 8px;
  border-radius: var(--radius-pill);
  background: var(--panel-2);
  color: var(--dim);
  font-size: 12px;
  white-space: nowrap;
}

/* Прикрепление на запись — единственное, что отличает метку по смыслу, а не по названию. */
.chip-write {
  color: var(--accent);
  background: var(--accent-soft);
}

.pane {
  margin-bottom: 16px;
  max-width: 62rem;
}

/* Страница, которой узко: «Установки и секреты» — это десятки полей, и колонка в 62rem
   заставляет мотать там, где всё помещается рядом. Ширину держит сама страница (.content):
   до предела и дальше по центру. */
.wide-page .pane { max-width: 100% }

.wide-page .form { max-width: 100% }

.pane h2 {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .05em;
}

.pane > .btn { margin-top: 1rem }

/* Полоса с вопросом специалиста (SPEC §3.3). Единственное место, где карточка дела зовёт
   человека что-то сделать прямо сейчас, — поэтому цветом, а не только порядком. */
/* Полоса решения по результатам прогона (SPEC §3.8): пометка, объяснение и две кнопки. */
.run-review {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

.pill-ask {
  color: var(--accent);
  background: var(--accent-soft);
  text-decoration: none;
}

.pane-ask {
  border-left: 3px solid var(--accent);
  background: var(--accent-soft);
}

/* Вариант графика — строка, по которой можно попасть пальцем, а не точка размером с букву. */
.choices {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.choice {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

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

.choice.picked {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.choice input[type="radio"] {
  accent-color: var(--accent);
  flex: none;
  width: auto;
}

.when-detail {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
}

.when-detail label {
  color: var(--dim);
  font-size: 12px;
}

.when-detail select,
.when-detail input[type="time"] {
  font: 13px system-ui;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--text);
}

.form input[type="time"] { max-width: 8rem }

.nowrap { white-space: nowrap }

/* «Всегда» и «Никогда» — выбор в одну строку: их всего два, и колонкой они выглядят
   длиннее самого правила (SPEC §3.6). */
.kinds {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 0.8rem;
}

/* Отключённое правило остаётся читаемым, но видно, что оно не в силе. */
tr.off td { opacity: .55 }

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

/* --- страница специалиста -------------------------------------------------- */

.worker {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 1.25rem;
}

/* Значок специалиста — буква его имени в круге. Рисовать лицо тому, у кого его нет,
   честнее не выйдет, а место в шапке экрана он занимает то же (SPEC §3.16). */
.worker-face {
  display: grid;
  place-items: center;
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 20px;
  font-weight: 600;
}

.worker-who {
  min-width: 0;
  /* Забирает свободное место, чтобы «Поговорить» встало у правого края, а не приклеилось
     к должности: действие специалиста — не часть его подписи. */
  flex: 1 1 auto;
}

.worker-who h1 { margin: 0 0 2px }

.worker-do {
  display: flex;
  flex: none;
  gap: 8px;
  flex-wrap: wrap;
}

.worker-state {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 6px;
  font-size: 13px;
}

/* Сводка — строка чисел, а не панель приборов: плитками она забивает половину экрана
   ради трёх цифр, которые читаются взглядом за секунду (SPEC §3.16). */
.stats {
  display: flex;
  gap: 1.25rem;
  flex-wrap: wrap;
  margin-top: 8px;
  color: var(--dim);
  font-size: 13px;
}

.stats b {
  font-size: 15px;
  color: var(--text);
}

/* --- сводка на главной ------------------------------------------------------
   Плитка на раздел: название, число, поясняющая строка и одно действие (SPEC §3.16).
   Ширина колонки — по самой длинной подписи действия («Завести документацию»), а не
   по числу: кнопка, переносящаяся на вторую строку, читается как две кнопки. */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 12px;
}

.tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 14px 16px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* Заголовок группы плиток: сводка разложена по ролям (§3.15), и подпись над рядом
   говорит, чей это ряд. Мельче h2 страницы — это перегородка, а не новый раздел. */
.group {
  margin: 1.6rem 0 0.7rem;
  font-size: 13px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .05em;
}

.group .muted {
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}

.tile-title {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  color: var(--dim);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  text-decoration: none;
}

/* Значок раздела — тот же, что у его строк в списках. Крупнее подписи, но не крупнее
   числа: главное на плитке — число (§3.16). */
.tile-sign {
  font-size: 15px;
  letter-spacing: 0;
}

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

.tile-value {
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.2;
  /* Длинное значение — имя модели, а не число: пусть переносится, но не растягивает сетку. */
  overflow-wrap: anywhere;
}

/* Имя модели вместо числа: мельче, и перенос по словам, а не посреди слова. */
.tile-value-long {
  font-size: 1rem;
  line-height: 1.35;
  overflow-wrap: break-word;
  word-break: normal;
}

.tile-note {
  color: var(--dim);
  font-size: 12px;
}

/* Действие прижато к низу: у плиток разной высоты кнопки иначе стоят вразнобой, и ряд
   перестаёт читаться как ряд. */
.tile-do {
  margin-top: auto;
  padding-top: 10px;
  align-self: flex-start;
}

/* Плитка, которой нужно внимание: рамка, а не красный фон — фоном она кричит громче
   раздела «Требует решения», который стоит выше и говорит то же самое словами. */
.tile-warn { border-color: var(--err) }
.tile-warn .tile-value { color: var(--err) }

/* «Требует решения»: в спокойном кабинете этого блока нет вовсе (§3.16). */
.trouble { border-left: 3px solid var(--err) }

.trouble h2 { margin-top: 0 }

.trouble-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.trouble-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.heading {
  justify-content: space-between;
  align-items: baseline;
}

.heading h2 { margin-bottom: 0.4rem }

.pager {
  justify-content: center;
  gap: 14px;
  margin-top: 1rem;
}

/* --- разговор виджетом ------------------------------------------------------
   Своя лента с прокруткой внутри себя и своё поле ввода: без этого лента растёт вниз
   и уносит из виду всё, что под ней, — а под ней стоят дела (SPEC §3.16). */
.chat {
  display: flex;
  flex-direction: column;
  margin-top: 1.5rem;
}

.chat h2 {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .05em;
}

.chat .talk {
  margin: 0;
  /* Лента занимает половину экрана и прокручивается сама: поле ввода должно оставаться
     на виду, сколько бы специалист ни наговорил. */
  max-height: 44vh;
  min-height: 6rem;
  overflow-y: auto;
  padding-right: 4px;
}

.chat-say {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.chat-say .row {
  margin-top: 0.6rem;
}

/* --- окно поверх страницы ---------------------------------------------------
   Заводят документацию редко, а список читают каждый день: форма, постоянно висящая
   внизу, отбирает у списка место и внимание (SPEC §3.16). Подложка ловит клик мимо
   окна — этим оно и закрывается. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: light-dark(rgba(16, 24, 40, .35), rgba(0, 0, 0, .55));
  /* Лёгкое размытие под окном: видно, что страница на месте, но сейчас не про неё. */
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: fade var(--quick) both;
}

@keyframes fade {
  from { opacity: 0 }
}

.modal {
  position: fixed;
  z-index: 91;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(34rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  overflow-y: auto;
  box-shadow: var(--shadow);
  margin: 0;
  animation: raise var(--quick) both;
}

@keyframes raise {
  from {
    opacity: 0;
    transform: translate(-50%, calc(-50% + 6px));
  }
}

@media (prefers-reduced-motion: reduce) {
  .scrim,
  .modal { animation: none }
}

.modal h2 {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .05em;
}

.modal .form {
  max-width: none;
}

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

@media (max-width: 760px) {
  /* Шапка в две строки: разделы уезжают на свою и прокручиваются сами — перенос
     съедал бы пол-экрана под меню. */
  .topbar {
    gap: 8px;
    padding: 10px 12px;
    flex-wrap: wrap;
  }

  .nav {
    order: 2;
    flex-basis: 100%;
  }

  .topbar > .whoami,
  .topbar > .theme-btn { order: 1 }

  /* Название кабинета на узкой шапке уступает место разделам: знак слева остаётся —
     по нему видно, что это то же приложение. */
  .specialist {
    padding-right: 0;
    border-right: 0;
  }

  .specialist-name { display: none }

  /* Подписи разделов на телефоне не помещаются в строку; значки — да. Слова нет даже
     у активного: со словом ряд переносится на вторую строку, и шапка съедает треть
     экрана, ради которого его и открыли. «Где я сейчас» при этом видно — заливка
     таблетки и акцентный значок различимы и без подписи. */
  .nav-item > span:not(.badge) { display: none }

  .nav {
    gap: 0;
    /* Восемь значков в один ряд: третья строка шапки на телефоне съедает экран, ради
       которого его и открыли. */
    justify-content: space-between;
  }

  .nav-item {
    gap: 5px;
    padding: 0 7px;
  }

  /* На телефоне подписи нет ни у кого, поэтому и запаса под неё быть не должно:
     пункт специалиста здесь такой же кружок со значком, как остальные. */
  .nav-worker {
    min-width: 0;
    max-width: none;
  }

  /* Уголок группы на телефоне убран: он стоил бы одного раздела в строке, а что список
     раскроется, человек и так узнаёт первым же касанием. */
  .nav-chevron { display: none }

  /* Знак кабинета забирает первую строку, «кто я» и тема уезжают к правому краю. */
  .specialist { flex: 1 1 auto }

  /* Заголовок с кнопкой во всю ширину и подпись под ним: отрицательный отступ подписи
     рассчитан на заголовок в одну строку и на телефоне наезжает на кнопку. */
  .heading + .lead { margin-top: 0.4rem }

  /* Меню пользователя на узком экране прижимается к правому краю окна, а не к кружку:
     иначе оно уезжает за экран ровно настолько, насколько кружок сдвинут от края. */
  .whoami-menu { right: 0 }

  .content { padding: 12px }
  .card { padding: 12px; margin-bottom: 12px }

  /* палец толще курсора: у всего кликабельного — высота строки, а не текста */
  .btn {
    min-height: 40px;
    padding: 10px 16px;
  }

  .row-actions .btn { min-height: 36px }
  .nav-item { height: 36px }
  .whoami-face,
  .theme-btn {
    width: 36px;
    height: 36px;
  }

  /* Заголовок и кнопка рядом не помещаются: кнопка уходит под заголовок во всю ширину —
     на телефоне это и есть главное действие экрана. */
  .heading {
    align-items: stretch;
    gap: 8px;
  }

  .heading > .btn,
  .heading > .upload { width: 100% }

  h1 { font-size: 1.3rem }

  /* Таблица прокручивается вбок внутри своей рамки, а страница вбок не едет. Прокрутка тут
     честнее «карточек из строк»: в списке дел сравнивают колонки, а разложенная в столбик
     строка сравнению не поддаётся.

     Ширина при этом **не** `max-content`: длинное описание в первой колонке растягивало
     таблицу на пол-экрана вбок, и до последней колонки надо было листать метр. Текст
     переносится, а ширину держит только первая колонка — чтобы название не превратилось
     в столбик по одному слову. */
  .grid {
    display: block;
    width: auto;
    min-width: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .grid th,
  .grid td { white-space: normal }

  /* Колонке нужен нижний предел, иначе она сжимается до одного слова в строку — «тем/нет»
     столбиком читается хуже, чем прокрутка на пару сантиметров. */
  .grid th,
  .grid td { min-width: 5.5rem }

  .grid th:first-child,
  .grid td:first-child { min-width: 11rem }

  /* Числа и даты не переносим никогда: «15.08.» на одной строке и «26» на другой — не дата. */
  .grid .num,
  .grid .nowrap,
  .grid .num *,
  .grid .nowrap * { white-space: nowrap }

  /* Сводка в одну колонку: две плитки по 15rem на телефоне не встают рядом всё равно. */
  .tiles { grid-template-columns: 1fr }

  /* Специалист: лицо и подпись строкой, действия — под ними во всю ширину. */
  .worker {
    flex-wrap: wrap;
    gap: 12px;
  }

  .worker-face {
    width: 40px;
    height: 40px;
    font-size: 17px;
  }

  .worker-do {
    flex-basis: 100%;
    flex-wrap: nowrap;
  }

  .worker-do .btn { flex: 1 1 0 }

  /* Лента разговора занимает экран, а не треть его: на телефоне под ней ничего нет. */
  .chat .talk { max-height: 52vh }

  /* Поле ввода в разговоре — на три строки: в две помещается полтора слова, а просьбу
     человек пишет фразой. */
  .chat-say textarea { min-height: 92px }

  /* 16 пикселей — не про читаемость, а про Safari: поле с меньшим шрифтом он зумит
     при фокусе, и страница остаётся увеличенной после того, как человек закончил печатать. */
  .form input,
  .form select,
  .form textarea,
  .login input { font-size: 16px }

  /* Окно на телефоне уезжает снизу и во всю ширину — так его открывает всё остальное,
     чем человек пользуется с телефона. */
  .modal {
    top: auto;
    bottom: 0;
    left: 0;
    transform: none;
    width: 100%;
    max-height: 88vh;
    border-radius: var(--radius) var(--radius) 0 0;
    animation: sheet var(--quick) both;
  }

  @keyframes sheet {
    from { transform: translateY(12px) }
  }
}

/* Планшет: шапка целиком помещается, а вот три-четыре плитки в ряд уже нет — и таблицы
   с пятью колонками тоже не всегда. */
@media (min-width: 761px) and (max-width: 1100px) {
  .content { padding: 16px 18px 32px }
  .tiles { grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)) }

  .grid {
    display: block;
    width: max-content;
    min-width: 100%;
    overflow-x: auto;
  }

  /* Палец на планшете тот же, что на телефоне: кнопки крупнее курсорных. */
  .btn { min-height: 38px }
}

/* Любой указатель толще курсора — не только на узком экране: планшет бывает и в альбоме,
   и подпираться шириной окна тут неправильно. */
@media (pointer: coarse) {
  .btn { min-height: 38px }
  .nav-item { height: 36px }
  .nav-drop-item { padding: 10px }
  .chip { padding: 3px 10px }
}

/* --- файловая панель: одна на «Файлы» и на документацию (SPEC §3.16) ------------------ */

/* Перетаскивание держится не обработчиком, а разметкой: скрытый `input type=file`
   растянут по всему прямоугольнику, и файл, брошенный на него, попадает туда же,
   куда попал бы выбранный кнопкой. Меньше кода — и никакого чтения файлов из JS. */
.dropzone {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 0.6rem 0;
  padding: 14px;
  border: 1px dashed var(--line);
  border-radius: 10px;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}

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

.dropzone input[type="file"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}

.dropzone-off {
  opacity: 0.5;
  pointer-events: none;
}

/* Крошки и папки внутри панели — кнопки, а не ссылки: адрес папки принадлежит странице,
   а панель об этом не знает и знать не должна. Выглядеть они обязаны как ссылки. */
.crumb-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
}

.crumb-link:hover { text-decoration: underline }

textarea.code {
  width: 100%;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 13px;
  line-height: 1.45;
}

/* Файлы документации — во всю ширину карточки: колонка из трёх столбцов в узкой панели
   рвётся по слогам, а список файлов читают глазами сверху вниз. */
.pane.wide { grid-column: 1 / -1 }

/* --- карточка дела: «из чего · что делать · что вышло» (SPEC §3.16) ------------------- */

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

.sources li + li {
  margin-top: 0.7rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--line);
}

.sources .pill { margin-left: 6px }

.run-row { padding: 0.6rem 0 }

.run-row + .run-row { border-top: 1px solid var(--line) }

/* Последний прогон — тот, за которым пришли: он выделен подложкой, а не размером шрифта,
   иначе история рядом читается как что-то второсортное, а она такая же работа. */
.run-first {
  margin-bottom: 0.2rem;
  padding: 0.7rem;
  border-radius: 10px;
  background: var(--accent-soft);
}

.run-line {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: baseline;
}

.run-links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 0.3rem;
}

/* --- сортировка просмотром (SPEC §3.16) ---------------------------------------------- */

th.sortable {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

th.sortable:hover { color: var(--ink) }

.sort-mark {
  margin-left: 4px;
  color: var(--accent);
}

/* --- ручной порядок перетаскиванием (SPEC §3.9) --------------------------------------- */

.grip {
  width: 1.4rem;
  color: var(--dim);
  cursor: grab;
  user-select: none;
  text-align: center;
}

tr[draggable="true"]:hover .grip { color: var(--ink) }

tr.dragging {
  opacity: 0.45;
  background: var(--accent-soft);
}
