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

   Имена токенов — из соседнего проекта; ниже к ним заведены псевдонимы под теми
   именами, которыми кабинет уже пользуется, чтобы правило читалось словом,
   а не переводом с одного словаря на другой. */
:root {
  color-scheme: light dark;
  /* Полотно светлой темы заметно темнее белой карточки, а тёмной — темнее её панели:
     карточка на фоне своего же цвета не карточка, а прямоугольник текста. Именно на этом
     разрыве держится «блоки не сливаются» — не на тенях, тени только подпирают. */
  --bg: light-dark(#e9eef7, #080d18);
  --panel: light-dark(#ffffff, #141d31);
  --panel-2: light-dark(#e6ecf9, #1e2a45);   /* наведение, чипы, вторичные кнопки */
  --field: light-dark(#ffffff, #1b2740);
  --code: light-dark(#f5f8fd, #060b14);      /* диффы, транскрипты, пути */
  --line: light-dark(#ccd6e8, #2b3a56);
  --text: light-dark(#0d1526, #eaf0fb);
  --dim: light-dark(#52607a, #a2adc4);
  /* Акцент разведён надвое, и это про тёмную тему. Светло-синий хорош **буквой**: ссылка,
     значок, обводка на тёмном фоне читаются только светлым. Но той же краской залитая кнопка
     выходит бледной наклейкой — главное действие страницы выглядит выключенным. Поэтому
     заливка своя, насыщенная, и белая надпись на ней; в светлой теме оба цвета совпадают. */
  --accent: light-dark(#1a49e6, #7cb0ff);
  --accent-fill: light-dark(#1a49e6, #2f66ec);
  --accent-fg: #ffffff;
  --accent-soft: light-dark(#dce6ff, rgba(124, 176, 255, .2));
  --ok: light-dark(#0a8250, #57dc9d);
  --err: light-dark(#c62a1d, #ff8f84);
  /* Тени лесенкой: приподнятое над страницей и висящее над всем. Одна тень на два случая
     либо давит карточки, либо не отделяет меню от текста под ним.

     У приподнятого их две в одной строке — прижатая контурная и мягкая широкая: одна
     очерчивает край, вторая отрывает карточку от полотна. С единственной мягкой тенью
     край карточки размывается, и получается ровно то «мыло», из-за которого блоки
     и переставали читаться.

     В тёмной теме тень видно плохо — чёрное на почти чёрном, — и работает там не она,
     а **светлый кант** по верхнему краю: на тёмном фоне край предмета отмечает свет,
     а не темнота. Поэтому у тени три слоя, и первый из них в светлой теме пустой. */
  --shadow-sm:
    inset 0 1px 0 light-dark(rgba(255, 255, 255, 0), rgba(255, 255, 255, .05)),
    0 1px 2px light-dark(rgba(13, 22, 46, .08), rgba(0, 0, 0, .5)),
    0 4px 14px light-dark(rgba(13, 22, 46, .07), rgba(0, 0, 0, .4));
  --shadow: 0 16px 40px light-dark(rgba(13, 22, 46, .2), rgba(0, 0, 0, .7));
  --radius: 14px;
  --radius-sm: 10px;
  --radius-xs: 8px;
  --radius-pill: 999px;
  /* Одна скорость на весь кабинет: разнобой в длительностях читается как неисправность. */
  --quick: .13s cubic-bezier(.2, .6, .3, 1);

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

     Цвета два — шапка залита переходом между ними: ровная заливка на широком мониторе
     выглядит куском пластика, а переход даёт полосе объём, не крича. */
  --top-bg: light-dark(#1440cf, #0d1524);
  --top-bg-2: light-dark(#2f6ae8, #121c30);
  --top-fg: #ffffff;
  --top-dim: light-dark(rgba(255, 255, 255, .8), #98a3b8);
  --top-line: light-dark(rgba(255, 255, 255, .22), rgba(255, 255, 255, .1));
  --top-hover: light-dark(rgba(255, 255, 255, .14), rgba(255, 255, 255, .07));
  --top-on: light-dark(rgba(255, 255, 255, .2), rgba(255, 255, 255, .13));
  /* Акцент шапки — то, чем на ней выделяют: на синем это белый, на тёмном — светло-синий.
     Страничный #1a49e6 не годится ни там, ни там: на синем он не виден, на тёмном грязен. */
  --top-accent: light-dark(#ffffff, #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 }

/* --- запасная палитра: браузеры без light-dark() ------------------------------------
   Пары выше — короткая запись, и понимает её не всякий движок: `light-dark()` появился
   в Chrome 123, Firefox 120 и Safari 17.5, то есть весной 2024 года. Всё, что старше, —
   Firefox ESR на линуксе у соседа, встроенный браузер планшета, корпоративный Chromium
   годовалой давности — читает `--bg: light-dark(…)` как **негодное при вычислении**
   значение. И это не «цвет по умолчанию»: свойство берёт начальное значение, фон `body`
   становится прозрачным, а под ним — полотно, которое при тёмной системе чёрное.
   Кабинет выглядел ровно так: разметка цела, всё чёрное, текст белый.

   Поэтому те же цвета объявлены здесь ещё раз, привычным способом: светлые в `:root`,
   тёмные — по системной настройке и по переключателю. Блок собран из тех же пар и обязан
   им соответствовать: за этим следит `ThemeTests` — забытый токен роняет тесты, а не тему
   у человека, до которого не дотянуться.

   Правится он не руками: пары наверху — источник правды, а этот блок пересобирается
   (deploy/theme-fallback.py). */
@supports not (color: light-dark(#000, #fff)) {
  :root {
    --bg: #e9eef7;
    --panel: #ffffff;
    --panel-2: #e6ecf9;
    --field: #ffffff;
    --code: #f5f8fd;
    --line: #ccd6e8;
    --text: #0d1526;
    --dim: #52607a;
    --accent: #1a49e6;
    --accent-fill: #1a49e6;
    --accent-soft: #dce6ff;
    --ok: #0a8250;
    --err: #c62a1d;
    --shadow-sm: inset 0 1px 0 rgba(255, 255, 255, 0), 0 1px 2px rgba(13, 22, 46, .08), 0 4px 14px rgba(13, 22, 46, .07);
    --shadow: 0 16px 40px rgba(13, 22, 46, .2);
    --top-bg: #1440cf;
    --top-bg-2: #2f6ae8;
    --top-dim: rgba(255, 255, 255, .8);
    --top-line: rgba(255, 255, 255, .22);
    --top-hover: rgba(255, 255, 255, .14);
    --top-on: rgba(255, 255, 255, .2);
    --top-accent: #ffffff;
  }

  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
      --bg: #080d18;
      --panel: #141d31;
      --panel-2: #1e2a45;
      --field: #1b2740;
      --code: #060b14;
      --line: #2b3a56;
      --text: #eaf0fb;
      --dim: #a2adc4;
      --accent: #7cb0ff;
      --accent-fill: #2f66ec;
      --accent-soft: rgba(124, 176, 255, .2);
      --ok: #57dc9d;
      --err: #ff8f84;
      --shadow-sm: inset 0 1px 0 rgba(255, 255, 255, .05), 0 1px 2px rgba(0, 0, 0, .5), 0 4px 14px rgba(0, 0, 0, .4);
      --shadow: 0 16px 40px rgba(0, 0, 0, .7);
      --top-bg: #0d1524;
      --top-bg-2: #121c30;
      --top-dim: #98a3b8;
      --top-line: rgba(255, 255, 255, .1);
      --top-hover: rgba(255, 255, 255, .07);
      --top-on: rgba(255, 255, 255, .13);
      --top-accent: #8fb4ff;
    }
  }

  :root[data-theme="dark"] {
    --bg: #080d18;
    --panel: #141d31;
    --panel-2: #1e2a45;
    --field: #1b2740;
    --code: #060b14;
    --line: #2b3a56;
    --text: #eaf0fb;
    --dim: #a2adc4;
    --accent: #7cb0ff;
    --accent-fill: #2f66ec;
    --accent-soft: rgba(124, 176, 255, .2);
    --ok: #57dc9d;
    --err: #ff8f84;
    --shadow-sm: inset 0 1px 0 rgba(255, 255, 255, .05), 0 1px 2px rgba(0, 0, 0, .5), 0 4px 14px rgba(0, 0, 0, .4);
    --shadow: 0 16px 40px rgba(0, 0, 0, .7);
    --top-bg: #0d1524;
    --top-bg-2: #121c30;
    --top-dim: #98a3b8;
    --top-line: rgba(255, 255, 255, .1);
    --top-hover: rgba(255, 255, 255, .07);
    --top-on: rgba(255, 255, 255, .13);
    --top-accent: #8fb4ff;
  }
}

* { 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;
}

/* Значок в начале заголовка отбивается от слова. Там, где заголовок разложен флексом
   (полосы `.pane`), промежуток держит `gap`; у обычных заголовков — списки дел, группы
   документаций — раскладки нет вовсе, и значок прилипал к первой букве: «⊞По графику»
   читается как один слипшийся знак. */
h1 > .icon:first-child,
h2 > .icon:first-child,
h3 > .icon:first-child { margin-right: 0.45rem }

/* ...а там, где промежуток уже держит `gap`, второго не нужно: он был бы двойным. */
.pane h2 > .icon:first-child,
.heading h2 > .icon:first-child { margin-right: 0 }

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

.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);
  /* Заливка **плотная**, а не полупрозрачная с размытием. Прозрачная шапка показывала,
     что страница уехала под неё, — и платила за это тем, что сквозь неё просвечивал
     текст: цвет полосы плыл от страницы к странице, а края букв в ней размывались.
     Что страница уехала под шапку, теперь видно тенью под её краем — это то же
     сообщение, но не за счёт читаемости самой полосы. */
  background: linear-gradient(100deg, var(--top-bg), var(--top-bg-2));
  box-shadow: 0 2px 12px light-dark(rgba(13, 22, 46, .22), rgba(0, 0, 0, .45));
}

/* Слева — сам кабинет: знак и название. Знак не логотип, а первая буква названия
   в акцентном квадрате: рисовать эмблему тому, у кого её нет, честнее не выйдет. */
.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: var(--top-accent);
  color: light-dark(#1440cf, #0b1220);
}

/* Знак на своей форме крупнее, чем в шапке: там его узнают краем глаза, здесь выбирают. */
.brand-mark-big {
  width: 44px;
  height: 44px;
}

/* Свой знак заказчика: подложки под ним нет — она красила бы чужую фигуру нашим градиентом.
   Вписывается целиком (`contain`), а не обрезается: у знака своя форма, и срезанный угол
   эмблемы читается как поломка. */
.brand-mark:has(.brand-pic) { background: none }

.brand-pic {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.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: 18px;
  margin-bottom: 18px;
}

/* Карточка внутри карточки — обычное дело на карточке дела и документации: белая плитка
   на белой плитке даёт два прямоугольника без границы между ними, и человек читает их как
   один блок. Вложенная берёт **другую поверхность** и обходится без тени: она не лежит
   поверх внешней, она её часть. Цвет считается от токенов, а не пишется третьим значением, —
   иначе он разъедется с темой на первой же правке палитры. */
.card .card,
.card .grid,
.card .empty {
  background: color-mix(in oklab, var(--panel-2) 55%, var(--panel));
  box-shadow: none;
}

.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-fill);
  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: light-dark(
    color-mix(in oklab, var(--accent-fill) 88%, black),
    color-mix(in oklab, var(--accent-fill) 84%, white));
}

/* Нажатие видно самой кнопкой: без этого при медленном ответе сервера человек жмёт второй раз. */
.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-fill) }
.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.5rem }

.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;
}

/* Diff внутри таблицы (SPEC §3.9, §3.16). `overflow: auto` на самом `pre` от растягивания
   не спасает: в таблице ширина колонки считается по содержимому, и длинная строка diff-а
   раздвигала ячейку, а вслед за ней таблицу и всю страницу — экран уезжал вправо, за край
   окна, и человек терял и кнопки, и меню.

   `max-width: 0` снимает влияние содержимого на ширину колонки, а `width: 100%` тут же
   говорит, сколько ей на самом деле причитается: всё, что осталось от строки. Без второй
   половины пары колонка съёживалась до ширины самого длинного слова, и название изменения
   стояло в узкой щели рядом с пустотой. Прокрутка при этом достаётся тому, кому и
   предназначалась, — самому diff-у. */
.grid.diffs td:first-child { width: 100%; max-width: 0 }

.grid.diffs .diff { max-width: 100% }

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

#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: 12px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 1rem;
}

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

/* Пара «подпись — поле» внутри ряда: переносится целиком. Без неё ряд цены рвался между
   словом «Валюта» и её списком — подпись оставалась в конце строки, а поле уезжало вниз,
   и читалось это как подпись к соседнему числу. */
.row .pair {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Подпись в паре стоит слева от поля, а не над ним, и отступ сверху ей не нужен. */
.form .row .pair label { margin-top: 0 }

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

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

/* Лента показывает работу, а не текст (SPEC §3.16): ответ абзацем, вызов инструмента —
   приглушённой строкой с раскрытием, ошибка красным и раскрыта сразу. Своей обёртки у ленты
   больше нет: она живёт внутри виджета разговора (`.talker-tape` ниже), а полосой на странице
   не стоит нигде.

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

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

/* Свои реплики — у правого края, чужие у левого: сторона отвечает на «кто это сказал»
   раньше, чем подпись, и в длинной переписке это единственное, что читается на прокрутке.
   Колонки те же две, только зеркально, а лицо переставляет `order` — grid раскладывает
   элементы в его порядке. */
.msg-human {
  grid-template-columns: 1fr 28px;
}

.msg-human .msg-face { order: 2 }

.msg-human .msg-body {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
}

.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,
.msg-worker .talk-answer {
  padding: 0.5rem 0.8rem;
  border-radius: var(--radius);
  max-width: min(48rem, 92%);
  white-space: pre-wrap;
}

/* Своё — акцентной заливкой и справа: в переписке «кто сказал» читается стороной,
   а не подписью, и подпись остаётся для тех случаев, когда сторон больше двух. */
.talk-said { background: var(--accent-soft) }

/* Ответ специалиста — на панели с рамкой: на общей поверхности ленты он иначе сливается
   с ней, и абзац ответа не отличить от служебной строки под ним. */
.msg-worker .talk-answer {
  background: var(--panel);
  border: 1px solid var(--line);
}

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

/* Вывод инструмента — не реплика: он тише текста и отчёркнут слева, чтобы лента не читалась
   как разговор трёх собеседников. */
.talk-tool {
  padding-left: 0.7rem;
  border-left: 2px solid var(--line);
  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);
}


/* Пустая лента: значок, объяснение и пример. Не «сообщений нет» — этого человек и так
   не спрашивал (§3.14). Стоит по центру окна, потому что окно пустое: прижатый к верхнему
   краю текст в пустом прямоугольнике выглядит забытым куском разметки. */
.talk-blank {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  flex: 1;
  min-height: 7rem;
  padding: 0.5rem 1rem;
  text-align: center;
}

.talk-blank p {
  margin: 0;
  max-width: 46ch;
}

.talk-blank-sign {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
}

/* Три точки — единственная «крутилка» в кабинете: она отвечает на вопрос «оно вообще живое»,
   на который лента молчащего прогона не отвечает никак. */
.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) }

/* Окно личного браузера (ROADMAP, «Браузер работника»): картинка того самого chromium,
   в котором работает специалист. Полоса во всю ширину карточки и высотой в две трети экрана —
   человек входит здесь в чужой кабинет руками, и делать это в марке размером с превью нельзя.

   Рамка и тёмная подложка не украшение: внутри чужая страница со своим фоном, и без границы
   непонятно, где кончается кабинет и начинается показанный браузер. Подложка тёмная в обеих
   темах намеренно — за картинкой стоит экран, которого нет, и белый прямоугольник в паузе
   между кадрами читался бы как «сломалось». */
/* Размер окна задаётся ему самому, а не через карточку вокруг: пока правило было написано
   как `.browser-window .browser-screen`, переезд окна в панель вкладки оставил его без ширины
   вовсе — `iframe` встал своим стандартным размером 300×150, и живой браузер показывался
   квадратиком в углу. */
.browser-screen {
  position: relative;
  display: block;
  width: 100%;

  /* Пропорция та же, что у экрана контейнера (`SCREEN` в browser.mjs): картинку noVNC
     вписывает целиком, сохраняя её, и любое расхождение превращается в чёрные поля.
     Потолок по высоте — чтобы окно помещалось в экран человека вместе с шапкой кабинета,
     и парный потолок по ширине, иначе поля возвращаются с другой стороны. */
  aspect-ratio: 16 / 9;
  max-height: 76vh;
  max-width: calc(76vh * 16 / 9);
  margin-top: 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: light-dark(#1f2430, #10131a);
}

.browser-screen iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

/* «На весь экран» — углом картинки, как в видеоплеере: там её ищут рукой, и там она
   не отнимает места у самого окна. Цвета свои, а не токенами кабинета: под кнопкой чужая
   страница любого цвета, и полупрозрачная тёмная подложка со светлым значком читается
   и на белом фоне поиска, и на тёмном фоне почты — в обеих темах одинаково. */
.screen-full {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: rgba(16, 19, 26, .55);
  color: #fff;
  cursor: pointer;
  /* Приглушена, пока на неё не навели: поверх чужой страницы кнопка не должна спорить
     с её содержимым. */
  opacity: .55;
  transition: opacity .12s ease, background .12s ease;
}

.browser-screen:hover .screen-full { opacity: .9 }

.screen-full:hover,
.screen-full:focus-visible {
  opacity: 1;
  background: rgba(16, 19, 26, .8);
}

/* На весь экран рамка и скругления не нужны: там нет ничего, от чего отделяться. */
.browser-screen:fullscreen {
  height: 100%;
  border: 0;
  border-radius: 0;
}

/* Заголовок панели профиля и кнопки над ней — в один ряд: `.heading` разводит их по краям,
   но заголовок карточки не `h1`, и своего отступа сверху ему здесь не нужно. */
.folder-page .heading h2 { margin: 0 }

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

.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 }

/* Полоска инструментов над списком файлов (SPEC §3.16): поиск, папка, загрузка, вид —
   всё в одном ряду. Прежде это были три ряда с двумя пунктирными прямоугольниками
   загрузки высотой в палец, и до первого имени файла человек проматывал пол-экрана. */
.filebar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

.filebar-gap { flex: 1 1 0; min-width: 0 }

/* Поиск: значок внутри поля, а не подписью рядом — так поле остаётся одной вещью
   и не занимает вторую строку на телефоне. */
.filebar-find {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex: 0 1 20rem;
  min-width: 9rem;
  padding: 0 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--field);
  color: var(--dim);
}

.filebar-find:focus-within { border-color: var(--accent) }

.filebar-find input {
  flex: 1;
  min-width: 0;
  padding: 7px 0;
  border: none;
  background: none;
  color: var(--text);
  font: 13px system-ui;
}

.filebar-find input:focus { outline: none }

.filebar-new { margin-bottom: 0.9rem }

.filebar-new .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);
}

/* Кнопка в полоске — на полразмера ниже обычной: ряд из четырёх «таблеток» в полный рост
   спорит с заголовком страницы за внимание. */
.btn-slim {
  padding: 6px 12px;
  font-size: 12.5px;
  box-shadow: none;
}

/* Выключенная кнопка-`label`: `disabled` у label не бывает, а выглядеть она обязана так же,
   как выключенная кнопка рядом. */
.btn-off {
  opacity: .45;
  pointer-events: none;
}

/* --- действия строки: значок и «три точки» (SPEC §3.16) ---------------------
   Пять кнопок в строке — это пять синих пятен на строку и полсотни на экран: список
   начинает читаться справа налево, от кнопок к именам. Наружу вынесено только удаление,
   и то значком; остальное — под тремя точками. */
.rowmenu {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
}

.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--dim);
  cursor: pointer;
  transition: background var(--quick), color var(--quick), border-color var(--quick);
}

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

.btn-icon-small { width: 22px; height: 22px }

/* Удаление красится только под курсором: постоянно красный значок в каждой строке
   превращает список в предупреждение (§3.14). */
.btn-icon-bad:hover {
  background: color-mix(in oklab, var(--err) 12%, transparent);
  border-color: color-mix(in oklab, var(--err) 40%, transparent);
  color: var(--err);
}

.menu-wrap { position: relative; display: inline-flex }

/* Подложка на весь экран ловит клик «мимо меню»: без неё меню остаётся висеть
   поверх соседних строк, пока человек не нажмёт те же три точки второй раз. */
.menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
}

.menu {
  position: absolute;
  z-index: 41;
  top: calc(100% + 4px);
  right: 0;
  min-width: 12rem;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  text-align: left;
}

.menu-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 7px 10px;
  border: none;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--text);
  font: 13px system-ui;
  text-align: left;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.menu-item:hover { background: var(--panel-2) }

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

/* Штатный 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;
}

/* Таблица с меню действий не режет то, что из неё торчит: меню висит над строкой, и общий
   для таблиц `overflow: hidden` обрезал бы его по нижней границе. Скругление углов при этом
   держится не обрезкой, а самими угловыми ячейками — иначе шапка легла бы поверх рамки
   квадратом. */
.grid.menus { overflow: visible }

.grid.menus thead th:first-child { border-top-left-radius: var(--radius) }
.grid.menus thead th:last-child { border-top-right-radius: var(--radius) }
.grid.menus tbody tr:last-child td:first-child { border-bottom-left-radius: var(--radius) }
.grid.menus tbody tr:last-child td:last-child { border-bottom-right-radius: var(--radius) }

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

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

.file-icon {
  margin-right: 0.5rem;
  vertical-align: -0.3em;
}

/* --- краски файловой панели (SPEC §3.16) ------------------------------------
   Цвет здесь не украшение, а способ чтения: в проводнике жёлтое — папка, зелёное —
   таблица, красное — pdf, и человек находит нужную строку в списке из сотни, ни разу
   не прочитав расширения. Серая колонка одинаковых контуров такой привычке не отвечает.

   Обе темы стоят рядом одной строкой: на тёмном полотне те же краски выглядят грязными,
   и им нужен другой, светлый конец шкалы. Значок остаётся одной фигурой в `currentColor` —
   вся палитра живёт здесь, а не внутри контуров (см. Signs.ForFileTone). */
.tone-folder { color: light-dark(#d08700, #f3c04a) }
.tone-table { color: light-dark(#12864b, #58d39a) }
.tone-pdf { color: light-dark(#c8321f, #ff8f7d) }
.tone-doc { color: light-dark(#2258c8, #7fa8ff) }
.tone-slides { color: light-dark(#d1620c, #ffa863) }
.tone-image { color: light-dark(#7b3fd4, #bda2ff) }
.tone-mail { color: light-dark(#0b7f9e, #63c8e2) }
.tone-archive { color: light-dark(#96650f, #d6ac5c) }
.tone-media { color: light-dark(#c02b74, #ff92c2) }
.tone-code { color: light-dark(#4a5a72, #9fb2cd) }
.tone-plain { color: light-dark(#6a7488, #9aa4b8) }

.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 }

/* Подсказка под переключателем. Мера строки считается в `ch` — ширине нуля текущего шрифта,
   а шрифт здесь 13px: прежние 70ch давали столбик в 450 пикселей, и подсказка из четырёх
   строк растягивалась на восемь посреди пустой страницы. 110ch — те же комфортные строки,
   но по ширине карточки, а не трети её. */
.check-hint {
  margin: 0 0 1rem 1.6rem;
  color: var(--dim);
  font-size: 13px;
  max-width: 110ch;
}

/* Выбор умений делу (§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 .pill { margin-left: 0.45rem }

.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);
}

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

/* Ширину держит сама полоса, а не строка: текст дела пишут списками и абзацами в две-три
   строки, и колонка в 78 знаков рвала их посреди мысли, оставляя справа пустое поле
   в пол-экрана. Читаемость длинной строки здесь проигрывает целостности написанного. */
.duty-text {
  white-space: pre-wrap;
  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;
}

/* Кнопки дела: на широком экране ряд, на телефоне — переносятся. Без переноса четыре кнопки
   с `white-space: nowrap` растягивали страницу вдвое шире экрана, и вся карточка ездила вбок
   вслед за ними. Промежуток чуть больше прежнего: на телефоне соседние кнопки, стоящие
   вплотную, нажимаются не те. */
.duty-go {
  display: flex;
  gap: 0.6rem;
  flex-wrap: wrap;
  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(--text);
  border-color: var(--line);
}

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

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

/* Разделитель внутри полоски: без него поиск, фильтры и разложение читаются одним рядом
   одинаковых кнопок, и человек не видит, где кончается одно и начинается другое. */
.bar-sep {
  align-self: stretch;
  width: 1px;
  margin: 0 0.25rem;
  background: var(--line);
}

.bar-label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 12.5px;
}

.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(--accent);
}

/* Внутрь, а не «в обрез»: обложка — это узнавание, а обрезанная первая страница узнаётся
   хуже целой. Подложка при этом остаётся папкой — светлым прямоугольником с рамкой. */
.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-fill);
  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.45rem }

/* Описание — не больше двух строк: третья отодвигает соседнюю документацию за край экрана. */
.doc-purpose {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* «Работают этим»: подпись и названия дел метками, а не сплошной строкой через пробел —
   в ней «сверка заявок» и «отчёт по остаткам» слипались в одно длинное имя. */
.doc-used {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem 0.45rem;
  font-size: 13px;
}

.doc-used-label { white-space: nowrap }

.doc-used-case {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 1px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  color: var(--text);
  text-decoration: none;
}

.doc-used-case:hover { border-color: var(--accent); color: var(--accent) }

.doc-topics { margin-top: 0.15rem }

/* Название документации — как название дела, только чуть мельче: списки стоят в соседних
   разделах, и одинаковая крупность читалась бы как один длинный перечень. Без подчёркивания
   по той же причине, что и там: нажимают всегда в одно место, а подчёркнутый текст рябит. */
.doc-title {
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}

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

.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);
}

/* Настоящие вкладки — «папкой»: полоса ярлычков, из которых выбранный сросся с панелью под
   ним. Пилюлями (`.tab`, как в файловой панели) они читались как ряд кнопок-переключателей,
   и связь «этот ярлык — вот это окно» приходилось держать в голове.

   Полоса едет вбок, а не переносится: профилей бывает четыре, имена у них человеческие
   («Ozon, кабинет продавца»), и на ноутбуке они уносили выбранный ярлык во второй ряд,
   а окно под ними — за нижний край экрана. Прокрутка внутри полосы — то же правило, по
   которому в кабинете живёт любое широкое содержимое: едет оно, а не страница целиком. */
.folder {
  display: flex;
  gap: 2px;
  overflow-x: auto;
  /* Вбок — да, вниз — нет: иначе браузер рисует у полосы ещё и вертикальную полоску
     прокрутки, и она висит справа от вкладок мусорной чёрточкой. */
  overflow-y: hidden;
  scrollbar-width: thin;
  margin-top: 1rem;
  /* Ярлычки начинаются ровно там же, где левый край панели под ними: отступ в четверть
     сантиметра сдвигал первый ярлык вправо, и полоса вкладок висела не над своей панелью,
     а чуть в стороне — мелочь, которая читается как съехавшая вёрстка. Верхние углы панели
     здесь и без того прямые (`.folder-page`), обходить скругление нечем и незачем. */
  padding: 0;
  border-bottom: 1px solid var(--line);
}

.folder-tab {
  /* Ярлычок не сжимается: имя, ужатое до «Ozon, каби…», не отличить от соседнего. */
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  /* Минус пиксель снизу — чтобы выбранный ярлык перекрыл линию полосы и слился с панелью. */
  margin-bottom: -1px;
  padding: 0.45rem 0.9rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: transparent;
  color: var(--dim);
  font: 13px system-ui;
  cursor: pointer;
  white-space: nowrap;
}

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

.folder-tab.on {
  background: var(--panel);
  border-color: var(--line);
  border-bottom-color: var(--panel);
  color: var(--text);
}

/* Панель под полосой — это её продолжение, а не отдельная карточка: без общей границы
   вкладка висела бы над пустотой. */
.folder-page {
  margin-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

/* Значок «что это такое» у заголовка: объяснение живёт в подсказке браузера, а не строкой
   под ним. Курсор `help` — единственное, чем значок сообщает, что на него стоит навести. */
.hint-mark {
  display: inline-flex;
  vertical-align: middle;
  margin-left: 0.35rem;
  color: var(--dim);
  cursor: help;
}

.hint-mark:hover { color: var(--accent) }

/* Точка «открыт сейчас» — рядом со словом, а не вместо него: значок в одиночку ничего
   не значит (§3.16), и полное объяснение стоит в самой панели профиля. */
.tab-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
}

/* Таблица внутри окна показа прокручивается сама — и вбок тоже: выгрузка на сорок колонок
   иначе растягивает окно за край экрана. */
.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 }

/* Размер слева, действия справа — те же, что в таблице: плитка без них заставляла
   переключаться в таблицу ради переименования (§3.16). */
.file-tile-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.3rem;
  min-height: 30px;
}

/* Вопрос — под плиткой и в столбик: втиснутый в строку с размером, он показывал бы
   половину слова. */
.file-tile-ask {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.3rem;
}

/* Переключатель вида: две кнопки, слипшиеся в одну — так видно, что выбор один из двух. */
.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). Шапка отбита линией, а не карточкой внутри карточки: у окна показа
   и так есть рамка, а вторая рамка внутри неё читается как отдельный документ. */
.letter-head {
  padding-bottom: 0.6rem;
  margin-bottom: 0.4rem;
  border-bottom: 1px solid var(--line);
}

.letter-subject {
  font-size: 15px;
  font-weight: 600;
  margin-bottom: 0.3rem;
  word-break: break-word;
}

.letter-line {
  font-size: 13px;
  word-break: break-word;
}

/* Подпись строки шапки — ровной колонкой: «От», «Кому» и «Копия» читают взглядом сверху
   вниз, и разъезжающийся левый край заставляет читать их по одной. */
.letter-label {
  display: inline-block;
  min-width: 5.5rem;
  color: var(--dim);
}

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

   Высота своя и нетянущаяся: с `flex: 1` рамка забирала всё место окна и **сжимала соседей**,
   от чего переключатель над телом схлопывался в двухпиксельную полоску, а вложения уезжали
   под нижний край. Не поместилось — прокручивается само окно, как у любого показа. */
.letter-body {
  flex: none;
  height: 52vh;
  background: #fff;
}

/* Переключатель «текст / как отправили» — по содержимому, а не во всю ширину окна:
   растянутый на пол-экрана он читается как поле ввода, а не как выбор из двух. */
.letter-view {
  flex: none;
  align-self: flex-start;
  margin-bottom: 0.5rem;
}

/* Шапка и вложения тоже не сжимаются: это опоры письма, а не то, чем платят за место. */
.letter-head,
.letter-files { flex: none }

/* Вложения — полосой под телом: их пересчитывают глазами («их три»), а не читают списком. */
.letter-files {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.6rem;
}

.letter-file {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
  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 }
}

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

/* Строка списка, по которой прогон идёт прямо сейчас: подсветка слева, а не цветом всей
   строки, — цветная строка в списке из тридцати читается как ошибка. */
.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);
}

/* Метка, которая ведёт куда-то (прикреплённая документация у дела), остаётся меткой на вид:
   подчёркивание внутри пилюли читается как ошибка вёрстки, а не как ссылка. Что по ней можно
   нажать, говорят курсор и подсветка рамкой — тем же приёмом, что у названий дел. */
a.chip {
  text-decoration: none;
  border: 1px solid transparent;
}

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

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

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

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

/* ...кроме той, что осталась формой: на «Браузере работника» широка страница, а не поле —
   имя профиля и адрес прокси человек читает глазами, и растянутый на монитор `input`
   мешает ровно так же, как на узкой странице (см. `.form` выше). */
.wide-page .form.narrow { max-width: 34rem }

/* Заголовок полосы. Был бледной подписью цветом `--dim` — на странице из шести полос
   это шесть одинаковых сереньких строк, и блоки переставали делиться глазами. Теперь
   заголовок написан цветом текста, значок при нём — акцентом, а под ним проведена линия:
   она и есть граница смыслового блока, самая дешёвая из возможных. */
.pane h2 {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 12px;
  padding-bottom: 9px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  color: var(--text);
  text-transform: uppercase;
  letter-spacing: .05em;
}

.pane h2 .icon { color: var(--accent) }

/* --- карточка дела плитками (SPEC §3.7, §3.16) ------------------------------
   Полос на карточке шесть, и в один столбец они выстраиваются в километр, по которому
   человек мотает до «что вышло». На широком экране короткие полосы встают рядом, а те две,
   что читаются строками, — текст дела и прогоны, — занимают ряд целиком. */
.panes {
  display: grid;
  /* `min(...)` не украшение: голый `minmax(23rem, …)` не даёт колонке сузиться меньше
     23rem, и на телефоне полоса вылезала за экран, утаскивая страницу вбок вслед за собой. */
  grid-template-columns: repeat(auto-fit, minmax(min(23rem, 100%), 1fr));
  gap: 18px;
  align-items: start;
}

/* Отступ держит сетка, а не сами карточки: иначе между рядами он двойной. */
.panes > .pane,
.panes .pane { margin-bottom: 0 }

/* Ряд одной высоты. Не всем сеткам подряд: там, где рядом стоят полоса в три строки
   и полоса в тридцать, ровная высота даёт пустой прямоугольник в пол-экрана. Включается
   там, где полосы соседние по смыслу и сравнимы по объёму. */
.panes-even { align-items: stretch }

.panes .wide,
.pane-span { grid-column: 1 / -1 }

/* Колонки внутри группы настроек («Настройки», §3.16). Группа занимает всю ширину — вопрос
   у неё один, и делить его на плитки значит рвать связь между полями, — а вот поля внутри
   стоят колонками: поле ввода шириной в монитор человек не читает, он промахивается мимо
   него глазами.

   `auto-fit` и минимум в 19rem: на широком экране группа из трёх вопросов встаёт тремя
   колонками, на ноутбуке — двумя, на телефоне одной. Ровнять высоту нечем и незачем —
   колонки здесь разной длины по существу, и `align-items: start` не даёт короткой
   растянуться до длинной пустотой.

   Промежуток по горизонтали больше вертикального: между соседними колонками нет линии,
   и различает их только воздух. */
.group-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: 8px 32px;
  align-items: start;
}

/* Ширину поля держит форма, а не колонка: на широком экране колонка вырастает до половины
   монитора, и поле «Название» в ней растягивалось на метр — такое поле человек не читает,
   он промахивается мимо него глазами. Правило перебивает послабление `.wide-page .form`,
   которое здесь уже не нужно: колонок и так несколько. */
.wide-page .group-cols .form { max-width: 34rem }

/* Первый подзаголовок в колонке — вровень с соседями: без сброса он подпирается отступом
   и колонки начинаются на разной высоте. */
.group-cols .form h3:first-child { margin-top: 0 }

/* Заголовок дела: картинка, название и состояние слева, кнопки справа. */
.duty-named {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  min-width: 0;
}

.duty-titles { min-width: 0 }

/* Картинка дела (§3.16). Квадрат постоянного размера: список карточек с картинками разной
   высоты прыгает по мере загрузки, и человек промахивается мимо той, на которую целился.
   Нет картинки — значок способа запуска на подложке, а не пустая рамка. */
.duty-thumb {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  overflow: hidden;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--dim);
}

.duty-thumb img { width: 100%; height: 100%; object-fit: cover }

/* ── Список дел строками-карточками (SPEC §3.7, §3.16) ─────────────────────────────────
   Раньше здесь стояла таблица с сортировкой по столбцам — вид администратора: он хорош,
   когда строк сотни и их сравнивают. Дел десяток, и человек не сравнивает их, а узнаёт
   нужное по названию. Отсюда всё остальное: название крупнее и без подчёркивания (ссылкой
   работает вся строка, а подчёркнутый текст в списке из десяти строк рябит), порядок —
   рукой за рукоятку, а не выбором столбца.

   Устроено так же, как список документаций, и это нарочно: два списка рядом, живущие
   по разным правилам, человек читает как два разных кабинета. */
.case-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.case-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);
}

/* Идущая работа — рамкой и подложкой, а не только пилюлей: строку, которая сейчас
   выполняется, человек ищет взглядом сверху вниз. */
.case-row.row-now {
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Картинка дела — та, которую поставил человек; нет её — значок вида работы. Пустая рамка
   на её месте была бы напоминанием о незаполненном поле, а не подсказкой. */
.case-cover {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  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(--accent);
}

.case-cover img { width: 100%; height: 100%; object-fit: cover }

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

.case-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

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

/* Название крупнее строки текста и без подчёркивания: подчёркивание в списке читается
   как «здесь десять разных ссылок», а нажимают всегда в одно и то же место. */
.case-title {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}

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

/* Первая строка текста — ровно одна: вторая отодвигает соседнее дело за край экрана. */
.case-line {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* «Когда» и метки документаций в одной строке: это ответы на разные вопросы, но оба —
   про то, чем дело обвязано, и порознь они забирали по строке каждый. */
.case-marks {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3rem 0.5rem;
  font-size: 13px;
}

.case-when {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  white-space: nowrap;
}

.case-actions {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.3rem;
}

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

/* Карандаш углом картинки: отдельная кнопка «загрузить обложку» под заголовком отбирает
   строку у названия ради действия, которое делают один раз. */
.thumb-pick {
  position: absolute;
  right: 3px;
  bottom: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-xs);
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--dim);
  cursor: pointer;
  overflow: hidden;
}

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

/* «Убрать» — вторым углом, чтобы не спорить с «поставить»: две кнопки на одном месте
   человек нажимает наугад. */
.thumb-drop {
  right: auto;
  left: 3px;
  padding: 0;
}

.thumb-drop:hover { color: var(--err); border-color: var(--err) }

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

/* Лицо и то, чем его меняют, — в одну строку: круг слева, кнопки и объяснение справа.
   Кнопка «поставить» здесь обычная, а не уголком поверх картинки, как у дел (`.thumb-pick`):
   там картинок тридцать в списке и место дорого, здесь она одна и на своём экране. */
.face-edit {
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
}

.face-pick {
  position: relative;
  overflow: hidden;
}

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

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

/* Действие полосы — у её заголовка, справа. Кнопка под текстом уезжает за нижний край,
   когда текста двадцать строк, и человек дочитывает до конца, чтобы узнать, что его
   вообще можно поправить. */
.head-action { margin-left: auto }

/* Полоса с вопросом специалиста (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;
}

/* Лицо картинкой: рамку — размер, цвет и круг — рисует то место, куда его вставили (шапка,
   лента разговора, список людей), а картинка заполняет её целиком и обрезается по ней.
   `cover`, а не `contain`: фотография редко бывает квадратной, и поля вокруг лица в кружке
   читаются как чужая рамка. Цвета у картинки своих нет — значит и в тёмной теме она та же. */
.face-pic {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* Лицо в списке людей и в полосе работы: тот же круг, что в шапке, только меньше — рядом
   со строкой текста, а не в заголовке. */
.people-named {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 0 0.6rem;
  align-items: center;
}

.people-face,
.working-face {
  display: grid;
  place-items: center;
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--panel-2);
  color: var(--dim);
  font-size: 12px;
  font-weight: 600;
  overflow: hidden;
}

/* У работника лицо своим цветом — тем же, каким оно подписано в его карточке и в ленте:
   в полосе «идёт работа» рядом стоят его лицо и слова о его работе. */
.working-face {
  background: var(--accent-soft);
  color: var(--accent);
}

.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(min(15rem, 100%), 1fr));
  gap: 14px;
}

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

/* Плитка под мышью приподнимается краем: ряд из пяти одинаковых прямоугольников иначе
   не отвечает на наведение вовсе, и человек не понимает, что она целиком про раздел. */
.tile:hover {
  border-color: color-mix(in oklab, var(--accent) 45%, var(--line));
  box-shadow: var(--shadow-sm), 0 0 0 1px color-mix(in oklab, var(--accent) 22%, transparent);
}

/* Заголовок группы плиток: сводка разложена по ролям (§3.15), и подпись над рядом
   говорит, чей это ряд. Мельче h2 страницы — это перегородка, а не новый раздел. */
.group {
  margin: 1.9rem 0 0.8rem;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  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;
  /* Значок раздела — акцентом: ряд серых плиток различается только словами, а слово
     читается вторым. */
  color: var(--accent);
}

.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): свёрнутым кружком
   у нижнего края, окошком в углу или во весь экран. Раскладка у всех трёх видов одна
   и та же — снаружи `position: fixed`, внутри колонка «шапка · плашка · лента · поле», —
   меняются только размеры, поэтому у полноэкранного вида своих правил ровно два.

   Цвета — только токенами: виджет висит над страницей, а не в ней, и своя краска в нём
   разъехалась бы с кабинетом на первой же правке палитры. Обе темы он поэтому проходит
   тем же путём, что карточки: `--panel`, `--line`, `--accent` уже объявлены парами. */
.talker {
  position: fixed;
  right: 18px;
  bottom: 18px;
  /* Ниже окон поверх страницы (`.scrim` 90, `.modal` 91): пока человек заводит дело,
     кружок разговора не должен лежать поверх формы. Раскрытый на весь экран поднимается
     сам — он в этот момент и есть то окно, с которым работают. */
  z-index: 80;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}

/* Во весь экран — окно посреди страницы, а не полоса в углу: длинный монолог читают целиком,
   и ширина в треть экрана превращает каждый шаг работы в лесенку из четырёх строк. */
.talker-full {
  inset: 0;
  z-index: 92;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* Сама обёртка кликов не ловит: под ней лежит подложка, и клик мимо окна должен
     доставаться ей, а не пустому прямоугольнику во весь экран. */
  pointer-events: none;
}

.talker-full > * { pointer-events: auto }

/* --- свёрнутый кружок --- */

/* Лицо специалиста в кружке — единственное, что видно с любой страницы, пока разговор
   свёрнут. Кружок крупный: в него надо попасть пальцем на телефоне, и он же отвечает
   за «кабинет живой» — мелкая точка в углу этого не делает. */
.talker-tray {
  position: relative;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--panel);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: transform var(--quick), box-shadow var(--quick), border-color var(--quick);
}

.talker-tray:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: var(--shadow);
}

/* Само лицо: картинка или буква — тем же кружком, что в шапке и в ленте (§3.16). */
.talker-tray-face {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 17px;
  font-weight: 600;
  line-height: 1;
  overflow: hidden;
}

.talker-tray-face .face-pic,
.talker-head-face .face-pic {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Работает — кружок дышит кольцом. Это единственная долгая анимация кабинета, и потому она
   тихая: кольцо расходится от края и гаснет, а не мигает. Отвечает она на один вопрос —
   «оно вообще живое», — который человек задаёт себе на десятой минуте прогона. */
.talker-tray-busy { border-color: var(--accent) }

.talker-tray-busy::before,
.talker-tray-busy::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--accent);
  animation: talker-ring 2.2s ease-out infinite;
}

/* Второе кольцо с задержкой в половину такта: одно уходит, следующее уже вышло, и пульс
   читается ровным дыханием, а не отдельными вспышками. */
.talker-tray-busy::after { animation-delay: 1.1s }

@keyframes talker-ring {
  from { opacity: .5; transform: scale(1) }
  to { opacity: 0; transform: scale(1.45) }
}

/* Ждёт ответа — метка с вопросом поверх лица: красная, на своей подложке и с кантом цвета
   кружка, чтобы читаться и на светлом, и на тёмном. Такое нельзя не заметить, иначе
   остановившаяся работа простоит до вечера. */
.talker-tray-asks { border-color: var(--err) }

.talker-tray-mark {
  position: absolute;
  top: -2px;
  right: -2px;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  background: var(--err);
  color: light-dark(#ffffff, #1a0f0e);
  box-shadow: 0 0 0 2px var(--panel);
}

/* --- раскрытое окно --- */

.talker-window {
  display: flex;
  flex-direction: column;
  width: min(27rem, calc(100vw - 2rem));
  /* Высота от экрана, а не от содержимого: окно, растущее вместе с лентой, прыгало бы
     на каждом шаге прогона. */
  height: min(34rem, calc(100vh - 7rem));
  padding: 0;
  margin: 0;
  overflow: hidden;
  box-shadow: var(--shadow);
  animation: talker-rise var(--quick) both;
}

.talker-full .talker-window {
  width: min(64rem, 100%);
  height: 100%;
}

@keyframes talker-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .talker-window { animation: none }

  .talker-tray-busy::before,
  .talker-tray-busy::after { animation: none; opacity: .3 }
}

/* Шапка — граница окна, а не его первая строка: под ней плашка и лента, и без своей
   поверхности имя специалиста читалось бы началом переписки. */
.talker-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: color-mix(in oklab, var(--panel-2) 55%, var(--panel));
  border-bottom: 1px solid var(--line);
}

.talker-head-face {
  display: grid;
  place-items: center;
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  overflow: hidden;
}

/* Имя и состояние — колонкой и с обрезкой: имя бывает длинным («Анастасия Петровна,
   отчётность»), а кнопки справа важнее его хвоста. */
.talker-who {
  flex: 1;
  min-width: 0;
}

.talker-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.talker-mood {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 12px;
}

.talker-head .btn-icon { flex: none }

/* --- вкладки дорожек --- */

/* Дорожек две (SPEC §3.7), и вкладок столько же: разговор и работа. Подчёркиванием, а не
   пилюлями: пилюли в шапке узкого окна спорят с кнопками рядом, а подчёркивание читается
   вкладкой само по себе и занимает одну строку.

   Вкладка «Прогон» появляется вместе с работой и уходит вместе с ней, поэтому полоса
   не резервирует места под неё: в спокойном кабинете её просто нет. */
.talker-tabs {
  display: flex;
  gap: 2px;
  padding: 0 8px;
  background: color-mix(in oklab, var(--panel-2) 55%, var(--panel));
  border-bottom: 1px solid var(--line);
}

.talker-tab {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 8px 10px;
  border: none;
  /* Подчёркивание рисуется своей рамкой, а не тенью: так оно ложится ровно на линию
     под полосой и не двигает содержимое при переключении. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  background: none;
  color: var(--dim);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: color var(--quick), border-color var(--quick);
}

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

.talker-tab-on {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* Метка на вкладке отвечает за неё, пока смотрят на соседнюю: точка — идёт работа,
   красная — ждёт ответа. Значка тут нет намеренно: на двенадцати пикселях он превратился бы
   в пятно, а слово рядом уже сказано — это название дорожки. */
.talker-tab-mark {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
}

.talker-tab-live {
  background: var(--accent);
  animation: talker-beat 1.6s ease-in-out infinite;
}

.talker-tab-asks { background: var(--err) }

@keyframes talker-beat {
  0%, 100% { opacity: 1 }
  50% { opacity: .35 }
}

@media (prefers-reduced-motion: reduce) {
  .talker-tab-live { animation: none }
}

/* --- плашка задачи --- */

/* Что именно сейчас делается и чем это остановить. Своя поверхность и цветная кромка слева:
   плашка отвечает на другой вопрос, чем лента под ней, и отделяться должна до чтения.
   Цвет — подмешанный акцент, а не своя краска: в тёмной теме плотная заливка светилась бы
   ярче ленты и перетягивала бы взгляд с того, ради чего окно открыли. */
.talker-task {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  background: color-mix(in oklab, var(--accent) 8%, var(--panel));
  font-size: 13px;
}

/* Остановленная работа — та же плашка, но красным: она не идёт, она ждёт человека. */
.talker-task-asks {
  border-left-color: var(--err);
  background: color-mix(in oklab, var(--err) 8%, var(--panel));
}

/* Законченная работа — тише обеих: её лента уже ушла, и строка эта не зовёт ничего делать,
   а отвечает на «чем кончилось» тому, кто отходил от экрана. */
.talker-task-done {
  border-left-color: var(--line);
  background: color-mix(in oklab, var(--panel-2) 55%, var(--panel));
}

/* «Идёт 12:04 · молчит 3:20» — своей строкой под названием работы: кнопки остановки важнее
   и остаются справа от него (`order`), а числа читают вторым взглядом. */
/* Подпись под ответом: чем его считали и во сколько токенов он встал (§3.16). Цвет берётся
   классом `muted` — своей пары light-dark здесь не заводится, чтобы подпись не разошлась
   с остальным мелким текстом при смене темы. */
.talker-spent {
  margin-top: 0.25rem;
  font-size: 11px;
  letter-spacing: 0.01em;
}

.talker-since {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  flex-basis: 100%;
  order: 3;
  font-size: 12px;
}

.talker-task-do { order: 2 }

.talker-task-what {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  flex: 1 1 10rem;
  min-width: 0;
}

.talker-task-what > .icon {
  flex: none;
  color: var(--dim);
}

.talker-task-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

a.talker-task-name { color: var(--accent) }

.talker-task-do {
  display: flex;
  gap: 6px;
  flex: none;
}

/* Прочие, кто ждёт ответа, — строкой мелких меток под плашкой. */
.talker-others {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--line);
  font-size: 12px;
}

/* --- лента --- */

/* Прокрутка перевёрнута: последний шаг виден сам, без крутилки на javascript, а человек,
   отлиставший вверх, не теряет место при следующем событии. Поверхность своя — та же, что
   у вложенных карточек: в кабинете это уже означает «внутри чего-то». */
.talker-tape {
  display: flex;
  flex-direction: column-reverse;
  flex: 1;
  gap: 0.55rem;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 12px;
  background: color-mix(in oklab, var(--panel-2) 45%, var(--panel));
}

/* Пузыри в окне уже страницы, и потолок в 48rem им незачем: место кончается раньше него. */
.talker-tape .talk-said,
.talker-tape .msg-worker .talk-answer {
  max-width: 100%;
}

/* Пустая лента в перевёрнутой колонке прижимается к низу — возвращаем её на середину. */
.talker-tape .talk-blank { margin: auto 0 }

/* Шаг работы: тише реплики и с отбивкой под лицо, чтобы колонка не рассыпалась. Тело вызова
   прокручивается внутри себя — команда с длинным путём не должна растягивать окно. */
.talker-tape .talk-tool {
  margin-left: 38px;
  font-size: 12px;
}

.talker-tape .talk-tool pre {
  max-height: 14rem;
  overflow: auto;
}

/* --- поле ввода --- */

.talker-say {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--line);
  background: var(--panel);
}

/* Поле стоит вне `.form`, поэтому вид ему назван здесь — тот же, что у полей кабинета:
   поле ввода, отличающееся от соседних экранов, читается чужим. */
.talker-say textarea {
  font: 13px system-ui;
  width: 100%;
  min-height: 48px;
  max-height: 30vh;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--field);
  color: var(--text);
  resize: vertical;
}

.talker-say textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Закрытое поле не должно выглядеть сломанным: оно другой поверхности, а почему сейчас
   не пишут, сказано плашкой над ним — специалист работает. */
.talker-say textarea:disabled {
  background: var(--panel-2);
  color: var(--dim);
  cursor: not-allowed;
}

.talker-send {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.talker-problem { margin: 0; font-size: 12px }

/* Приложенные файлы — метками над полем. */
.talker-clips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Скрепка: настоящий `<input type=file>` лежит внутри метки — его не видно, а нажимают
   по значку. Своей кнопки у выбора файла в браузере нет, а вид у неё в каждом свой. */
.talker-clip {
  position: relative;
  overflow: hidden;
  flex: none;
}

.talker-clip input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.talker-clip-off {
  opacity: .4;
  pointer-events: none;
}

/* Счётчики окна и денег — в остаток строки и мелко: их читают глазом, а не ищут. */
.talker-send .window,
.talker-send .muted { font-size: 11px }

/* Выбор уровня рассуждения стоит в одной строке с кнопкой «Отправить» и счётчиками, а места
   там немного: тот же вид, что у остальных списков кабинета, только мельче и по содержимому,
   а не во всю строку. Фон назван явно — раскрытый список браузер красит фоном самого
   `select`, и прозрачный в тёмной теме выходит белым прямоугольником. */
.picker-small { flex: none }

.picker-small select {
  width: auto;
  padding: 4px 26px 4px 10px;
  font-size: 11px;
  background: var(--field);
  color: var(--text);
}

.picker-small .picker-sign { right: 7px }

/* --- окно поверх страницы ---------------------------------------------------
   Заводят документацию редко, а список читают каждый день: форма, постоянно висящая
   внизу, отбирает у списка место и внимание (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 {
  /* Значок и слова в ряд с промежутком — как у заголовка полосы: без этого «⚠» прилипал
     к первой букве и читался как её часть. */
  display: flex;
  align-items: center;
  gap: 0.4rem;
  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 }

  /* Таблица с меню действий прокручиваться не умеет: её `overflow: visible` держит
     выпадающее меню строки, а стоит включить прокрутку по горизонтали, как браузер
     включает её и по вертикали — и меню обрезается собственной таблицей. Значит эта
     таблица обязана **помещаться**, и пределы колонок у неё свои, меньшие: имя переносится,
     «размер» и «изменён» жмутся по числу. Без этого страница ехала вбок на телефоне. */
  .grid.menus th,
  .grid.menus td {
    min-width: 3.5rem;
    /* Поля ячеек на телефоне вдвое уже: четыре колонки съедали на них полтора сантиметра
       ширины экрана — больше, чем занимает сама дата. */
    padding: 0.55rem 0.4rem;
  }

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

  /* Размер файла на телефоне уступает место остальным трём колонкам. С телефона в список
     заходят посмотреть, что там лежит, и открыть нужное: имя, дата и действия отвечают
     на это, размер — нет. На широком экране и в плитках он на месте. */
  .grid.files th:nth-child(2),
  .grid.files td:nth-child(2) { display: none }

  .grid.files th:nth-child(3),
  .grid.files td:nth-child(3) { min-width: 4.5rem }

  /* Числа и даты не переносим никогда: «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 }

  /* Разговор на телефоне — лист во всю ширину снизу, как и всякое другое окно здесь:
     окошко в углу шириной в экран минус два сантиметра не окошко, а тот же лист,
     только с полями. Свёрнутый кружок остаётся кружком: он и придуман для того,
     чтобы не занимать экран. */
  .talker {
    right: 12px;
    bottom: 12px;
  }

  .talker-window {
    /* Ширина от родителя, а не `100vw`: в ширину окна входит полоса прокрутки, и лист,
       прижатый к правому краю, съезжал на её ширину за левый. */
    width: 100%;
    height: 82vh;
    border-radius: var(--radius) var(--radius) 0 0;
  }

  /* Раскрытое окно прижато к нижнему краю и растянуто по ширине: до кнопок и поля ввода
     дотягивается большой палец, а шапка кабинета остаётся видна над ним. */
  .talker-panel {
    left: 0;
    right: 0;
    bottom: 0;
    align-items: stretch;
  }

  .talker-full {
    padding: 0;
    align-items: stretch;
    justify-content: flex-end;
  }

  .talker-full .talker-window {
    width: 100%;
    height: 100%;
    border-radius: 0;
  }

  /* Поле ввода — на три строки: в две помещается полтора слова, а просьбу человек
     пишет фразой. */
  .talker-say textarea {
    min-height: 78px;
    /* 16 пикселей — не про читаемость, а про Safari: поле с меньшим шрифтом он зумит
       при фокусе, и страница остаётся увеличенной. */
    font-size: 16px;
  }

  /* 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(min(13rem, 100%), 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. Сама кнопка
   живёт в полоске инструментов (`.filebar`), а не отдельным прямоугольником: до первого
   имени файла человек проматывал из-за него пол-экрана. */

/* Крошки и папки внутри панели — кнопки, а не ссылки: адрес папки принадлежит странице,
   а панель об этом не знает и знать не должна. Выглядеть они обязаны как ссылки. */
.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;
}

/* ── Спойлер внутри формы (SPEC §3.16) ──────────────────────────────────────────────────
   Тот же нативный <details>, что и у разделов страницы, но ростом со строку формы: им
   прячется поле, которое нужно редко, — дополнительные параметры уровня рассуждения.
   Состояние здесь в браузере не помнится намеренно: строк уровней бывает шесть, и раскрытые
   после перезагрузки все разом они заняли бы экран. */
.fold-thin > .fold-thin-head {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  padding: 2px 0 6px;
  font-size: 13px;
  color: var(--dim);
}

.fold-thin > .fold-thin-head::-webkit-details-marker { display: none }
.fold-thin > .fold-thin-head::marker { content: "" }
.fold-thin > .fold-thin-head:hover { color: var(--text) }

.fold-thin[open] > .fold-thin-head .fold-sign { transform: none }

/* Поле для JSON: моноширинное и в несколько строк — объект в одну строку человек правит
   вслепую. Рядом кнопка «выровнять», поэтому у поля свой отступ снизу, а не общий. */
.json-edit textarea.code {
  min-height: 84px;
  background: var(--field);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 10px;
}

.json-edit textarea.code:focus {
  border-color: var(--accent);
  outline: none;
}

.json-edit { padding-bottom: 8px }

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

/* --- страница документации (SPEC §3.9, §3.16) -------------------------------
   На странице ровно две вещи: шапка и файлы. Кто ею работает, как ведётся история и удаление
   живут за кнопками в шапке и открываются окном — полосами под списком файлов они отодвигали
   сам список на пол-экрана, а трогают его раз в месяц. */

/* Описание у названия: подпись мелким шрифтом и карандаш рядом, а не полоса страницы.
   Шире, чем читаемая колонка текста: это одна строка, и переносить её на вторую незачем. */
.doc-note {
  display: flex;
  align-items: baseline;
  gap: 0.3rem;
  margin: -0.6rem 0 0.5rem;
  max-width: 110ch;
  font-size: 13px;
}

/* Окно страницы документации: правка карточки, дела, история, удаление. Высота от окна
   браузера — на телефоне содержимое иначе не помещается целиком. */
.doc-modal {
  width: min(620px, 94vw);
  max-height: 88vh;
  overflow: auto;
}

.doc-modal .heading { margin-bottom: 0.8rem }

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

/* Длинный список внутри полосы — с прокруткой, а не во всю длину. Умений у специалиста
   бывает три десятка, документаций у дела — десяток, и полоса, вытянутая ими вниз,
   уносит «что вышло» за край экрана: до результата работы человек мотает мимо формы
   выбора, которую трогает раз в месяц. */
.scroller {
  max-height: 19rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

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

/* Документации дела — карточками в рамках, как умения рядом: разделительной чертой они
   читались сплошным текстом, в котором не видно, где кончается одна и начинается другая. */
.sources-boxed { display: flex; flex-direction: column; gap: 0.5rem }

.sources-boxed li {
  padding: 0.6rem 0.7rem;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}

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

.sources-boxed .row-actions { margin-top: 0.4rem; text-align: left }

/* Прикрепить документацию — одной полосой во всю ширину: выбор и кнопка слиты в одну вещь,
   потому что это один ход, а не два решения. Рамка и скругление живут на полосе, а не на её
   частях; `overflow: hidden` обрезает углы кнопки по общему скруглению. */
.attach {
  display: flex;
  width: 100%;
  margin-top: 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--field);
  overflow: hidden;
}

.attach:focus-within { border-color: var(--accent) }

.attach .picker { flex: 1; min-width: 0 }

.attach .picker select {
  border: none;
  border-radius: 0;
  padding-left: 14px;

  /* Цвет полосы, а не `transparent`, и это не про внешний вид закрытого поля — тот и так
     совпадает. Раскрытый список браузер красит **фоном самого `select`**, и у прозрачного
     он берёт белый: в тёмной теме выпадающий список бил по глазам белым прямоугольником
     посреди тёмного экрана. */
  background: var(--field);
}

.attach .picker select:hover { border-color: transparent }

/* Прикреплено уже всё: полоса остаётся на месте — она отвечает на вопрос «а можно ещё?», —
   но выбирать в ней нечего, и она об этом говорит сама. */
.attach .picker select:disabled { color: var(--dim); cursor: default }

.attach-go {
  flex: none;
  border-radius: 0;
  box-shadow: none;
}

/* Свой вид у выбора из списка: системный `select` — серый прямоугольник другого проекта,
   и между кнопками кабинета он читается как чужая деталь, попавшая сюда по недосмотру.
   Стрелка своя, поэтому родная убирается `appearance: none`. */
.picker { position: relative; display: flex; min-width: 0 }

.picker select {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  min-width: 0;
  padding: 8px 32px 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--field);
  color: var(--text);
  font: 13px system-ui;
  cursor: pointer;
}

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

/* Строки раскрытого списка — тем же, чем крашено всё поле. Браузер рисует их сам и берёт
   цвета у элемента: не сказать — значит отдать их на усмотрение системы, а она в тёмной
   теме кабинета угадывает не всегда. Правило общее: селекты есть и в настройках, и в графике. */
option {
  background: var(--field);
  color: var(--text);
}

.picker-sign {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--dim);
  pointer-events: 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,
.grid tr:hover .grip,
.doc-row:hover .grip,
.case-row:hover .grip { color: var(--ink) }

/* Столбец рукоятки — по её ширине: без этого таблица отдавала ему долю от общей и отодвигала
   текст правила. */
.grip-head { width: 1.4rem }

/* Перетаскиваемое бледнеет — и в таблице, и в списке. Правило было написано только для `tr`,
   и списки, переехавшие со строк таблицы на строки-карточки, тянулись без всякого отклика:
   человек не понимал, взялось ли. */
.dragging {
  opacity: 0.45;
  background: var(--accent-soft);
}

/* ── Свёрнутые разделы (SPEC §3.16) ─────────────────────────────────────────────────────
   Спойлер на нативном <details>: раскрывается браузером, а состояние помнится в браузере
   же (App.razor). Заголовок остаётся заголовком раздела — меняется только то, что по нему
   можно нажать, поэтому курсор и подсветка есть, а рамки кнопки нет. */
.fold > .fold-head {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

/* Свой значок вместо треугольника: у Safari и Chrome он разный, а раздел выглядит
   одинаково в обоих. */
.fold > .fold-head::-webkit-details-marker { display: none }
.fold > .fold-head::marker { content: "" }

/* Заголовок свёрнутого раздела линии под собой не носит: линия принадлежит всей строке
   с шевроном и приметой, а не одному заголовку в её середине. */
.fold > .fold-head h2 {
  margin: 0;
  padding-bottom: 0;
  border-bottom: none;
}

/* Раскрытый раздел отделён от своего содержимого — иначе шапка раздела читается как первая
   строка того, что в нём лежит. Свёрнутый линии не носит: отделять там нечего. */
.fold[open] > .fold-head {
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.fold > .fold-head:hover h2 { color: var(--accent) }

.fold-sign {
  color: var(--dim);
  flex: none;
  transform: rotate(-90deg);
  transition: transform .15s ease;
}

.fold[open] > .fold-head .fold-sign { transform: none }

/* Примета свёрнутого: сколько полей заполнено, сколько значений ничьих. Без неё свёрнутые
   разделы приходится раскрывать по очереди, то есть спойлер отменяет сам себя. */
.fold-note { margin-left: auto; font-size: 12px }

.fold-body { margin-top: 12px }

/* Подзаголовок внутри группы: почта отправки и почта приёма стоят подряд, и различать их
   по префиксу имени переменной — работа, которую экран должен делать за человека. */
.form h3 {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .05em;
}

.form h3:not(:first-child) { margin-top: 18px }

/* Лента прогона в карточке дела (SPEC §3.16). Окошко с прокруткой внутри себя: лента растёт
   вниз и без ограничения уносит из виду всё, что под ней, — а под ней стоят результаты. */
.runlog-tape {
  display: flex;
  flex-direction: column-reverse;
  gap: 0.35rem;
  max-height: 22rem;
  overflow-y: auto;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--field);
}

/* Перевёрнутая прокрутка держит последний шаг на виду сама: браузер оставляет
   `column-reverse` прижатым к «началу», то есть к концу ленты. Крутилки на javascript
   для этого не нужно, а человек, отлиставший вверх, не теряет место при новом событии. */
.runlog-line {
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.runlog-said {
  padding: 0.35rem 0.6rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
}

.runlog-tool { color: var(--dim) }
.runlog-tool summary { cursor: pointer }

/* Вывод инструмента раскрывается по клику и не должен рвать окошко в высоту. */
.runlog-tape pre.diff {
  max-height: 14rem;
  overflow: auto;
  margin: 0.3rem 0 0;
}

.runlog-count { margin-left: auto; font-size: 13px }

/* Кнопки шапки ленты отодвинуты от заголовка: десяти пикселей общего ряда мало — вплотную
   к заглавным буквам кнопка читается их продолжением, а не отдельным действием. */
.runlog .heading > h2 { margin-right: 0.9rem }

.runlog .heading .btn + .btn,
.runlog .heading .row + .btn { margin-left: 0.2rem }

/* Заголовок вызова — ровно одна строка: команда специалиста бывает длинной, а окошко узкое. */
.runlog-line summary {
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Сведения о прогоне парами «что — чего»: имя слева, значение справа. Не таблица: строк
   тут восемь, и таблица ради восьми строк добавляет рамку, которую потом надо объяснять. */
.facts {
  display: grid;
  grid-template-columns: minmax(8rem, max-content) 1fr;
  gap: 0.45rem 1.25rem;
  margin: 0;
}

.facts dt {
  color: var(--dim);
  font-size: 13px;
}

.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* На узком экране пары складываются в строки: колонка в 8rem съедала половину ширины. */
@media (max-width: 720px) {
  .facts { grid-template-columns: 1fr; gap: 0.15rem }
  .facts dd { margin-bottom: 0.5rem }
}

/* Список прогонов в карточке дела. Строки читаются подряд, поэтому разделяются линией,
   а не рамкой на каждую: рамок было бы двадцать на экран. */
.runs { display: flex; flex-direction: column; gap: 0.2rem }

.runs-more {
  align-self: flex-start;
  margin-top: 0.6rem;
}

/* Строка исхода в карточке прогона: пилюля «сделано» отодвинута и от заголовка, и от того,
   что стоит следом. Вплотную она читается продолжением названия, а не ответом на вопрос
   «чем кончилось». */
.run-mark {
  margin-top: 0.9rem;
  gap: 0.9rem;
}

/* Знак «ходит наружу» у умения. Не красный: красное читается как «сломано», а сказать надо
   «подумай» — данные уйдут в интернет. Янтарный ровно настолько, чтобы глаз его нашёл
   в списке, и не настолько, чтобы список из десяти умений кричал четырьмя строками подряд.
   Пара light-dark: на тёмном фоне тот же янтарь тускнеет до грязного (§CLAUDE, «две темы»). */
.mark-out {
  display: inline-flex;
  vertical-align: middle;
  margin-left: 0.35rem;
  color: light-dark(#b26a00, #f0b429);
  cursor: help;
}

.mark-out:hover { color: light-dark(#8a5200, #ffcc57) }
