/* 3D sci-fi on the Antnest palette (~/Projects/Antnest/admin/web/src/theme.css is the source of truth): a canvas scene on black with dots and light,
   the UI is glass plates over it. No borders: transparency, lightness and blur divide the surfaces. */

:root {
  color-scheme: dark;

  /* ── поверхности (Antnest) ─────────────────────────────── */
  --void: #000;
  --glass: rgba(20, 23, 26, .6);
  --glass-strong: rgba(28, 32, 36, .72);
  --line: rgba(255, 255, 255, .08);
  --well: rgba(255, 255, 255, .05);
  --hover: rgba(255, 255, 255, .1);
  --solid: rgba(255, 255, 255, .07);
  --top-glow: inset 0 1px 0 rgba(255, 255, 255, .06);
  /* Скролл: серая палочка чуть заметнее линии-разделителя, под курсором ярче. */
  --scroll: rgba(255, 255, 255, .18);
  --scroll-hot: rgba(255, 255, 255, .32);

  /* ── текст ─────────────────────────────────────────────── */
  --fg: #f4f5f4;
  --muted: #8b8f92;
  --placeholder: #8b8f92;
  /* Третий тон текста, и заведён он не ради светлоты, а ради РОЛИ (#1600, слово
     Ant04 24.08 о счётчике замечаний: «не белые, а такие светло-серые»).
     `--muted` значит «второстепенное, читать необязательно» - на нём стоят
     подписи и подсказки; здесь смысл другой: число читают ВСЕГДА, но спорить с
     именем класса рядом оно не должно. Готового тона между двумя концами в
     палитре не было: ближайший `--status-broken` занят смыслом «техническая
     беда», и брать его значило бы завести второй дом чужому токену. */
  --fg-dim: #c9ccce;

  /* ── акцент ────────────────────────────────────────────── */
  /* Акцент - тон БЕЗ смысла: активный элемент, фокус, свечение, кривые графиков.
     Состояние страницы и покрытие кластера им не красятся никогда, для них есть
     статусные токены ниже. ДОМ ТОНА ОДИН - канал `--accent-rgb`, тройка без
     обёртки: полупрозрачное берётся `rgb(var(--accent-rgb) / alpha)`, копия hex
     рядом запрещена (#1579, канон контура).
     Подстановка не той переменной проверяется разовой подменой КАНАЛА на
     заведомо чужой тон (`--accent-rgb: 255 0 255`) - ни один элемент состояния
     или покрытия перекраситься не должен (#1036-1). Подменяется именно канал, а
     не `--accent`: потребителей у канала два - сама `--accent` и полупрозрачные
     `rgb(... / a)`, - и подмена обёртки оставила бы вторых прежними, то есть
     ложный потребитель акцента среди статусных элементов прошёл бы молча. */
  --accent-rgb: 68 214 44;              /* Razer Green #44D62C, один тон на контур */
  --accent: rgb(var(--accent-rgb));
  --accent-fg: #10130a;
  /* Свечение считается ИЗ канала акцента, а не повторяет его цифрами: пока здесь
     стояла rgba того же лайма, смена акцента оставляла вокруг новой кнопки
     прежний ореол, и дом тона становился вторым (#1036-1). */

  /* ── статусы (Antnest) ─────────────────────────────────── */
  --warn: #fbbf24;
  --danger: #f87171;

  /* ── форма ─────────────────────────────────────────────── */
  --radius: 20px;
  --radius-md: 14px;
  --radius-sm: 12px;
  --blur: 32px;
  --transition: .16s ease;
  --control-h: 36px;
  --gutter: 14px;
  /* Высота шапки, дом ОДИН - `.topbar`; панель выбора ключа идёт во всю высоту слоем поверх шапки.
     Довод - docs/history/2026-09-25-moved-comment-reasons.md. */
  --topbar-h: 64px;
  /* Ширина панели выбора ключа (`.panel-keys`), 408 px (владелец: «данные должны читаться лучше»).
     Довод - docs/history/2026-09-25-moved-comment-reasons.md. */
  --key-w: 408px;
  /* Ширина колонок интерфейса: её читает и панель, и сетка формы внутри неё,
     поэтому значение живёт здесь. */
  /* Левая колонка несёт данные узла, правая - форму правки текстов (владелец,
     2026-07-30). Правой дана ширина под набор: 420px дают в поле описания
     ~55 знаков в строке, 330px давали 40 - меньше половины читаемой меры. */
  /* Границы панели ключей (#1425). Умолчание - до кромки экрана: соседи бывают
     скрыты, и правила `:has` ниже раздвигают панель только под ОТКРЫТОГО. */
  --keys-left: var(--gutter);
  --keys-right: var(--gutter);
  --col-left: 320px;
  --col-base: 420px;
  --col-right: var(--col-base);

  /* ── типографика ───────────────────────────────────────── */
  --font: system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* ── токены сцены: их читает canvas через getComputedStyle ─
     Значения ниже - единственный источник цвета графа; в app.js
     захардкоженных цветов нет. */
  --grid-far: rgba(255, 255, 255, .18);   /* дальняя точечная сетка: точка 2 px, яркость 46 из 255 на чёрном */
  /* Antnest «dots and light» over the opaque fill (Ant04 26.09, #3326; #902-11: an alpha shows the body colour).
     The canvas gradient needs the light's transparent end in the same tone, or the fade goes dark. */
  --bg-light: rgb(var(--accent-rgb) / .09);
  --bg-light-end: rgb(var(--accent-rgb) / 0);
  --bg-edge: rgba(0, 0, 0, .75);
  /* Кольца ярусов - серые и тусклые (владелец): это разметка поля, а не данные,
     и акцентный тон поставил бы их в один ряд с акцентом. */
  --grid-ring: rgba(244, 245, 244, .16);   /* кольца по радиусам раскладки: граница яруса */

  --node-pillar: #f4f5f4;
  --node-specular: rgba(255, 255, 255, .4); /* блик: свет падает сверху-слева */

  /* Восемь токенов `--intent-*` сняты (#2270): они красили узлы по интенту
     запроса, а режима ядра нет с #1395-7 - читателя у них не осталось.
     Довод набора цветов - docs/history/2026-09-06-reviziya-ostatkov-yadra.md. */

  /* Покрытие = обводка кластера. Токен несёт РОВНО этот смысл (#1042): пока он
     был единственным зелёным, им же красились вердикты аудита, состояние плана
     и полоса здоровья, и правка тона покрытия молча перекрасила бы всё это. */
  --status-covered: #c9f73c;
  /* «В норме, работы нет» - вердикт о СТРАНИЦЕ, а не о кластере (#1042): чисто
     по аудиту, длина в коридоре, текст стоит на сайте, страница плана уже
     существует. Значение сегодня то же, что у покрытия, и это не совпадение, а
     свойство момента: разводятся СМЫСЛЫ, чтобы следующая смена тона задела один
     из них, а не оба. Проверяется тем же приёмом, что у акцента, - подменой
     одного токена на заведомо чужой тон. */
  --status-ok: #c9f73c;
  --status-gap: #fbbf24;
  --status-cannibal: #f87171;
  --status-unknown: #4b5054;
  /* Issue severity: its own family, values by VARIABLE, not digits (#1305 п.1); red and yellow lead to the interface family
     (urgency), green and grey to the page family by a named link. Why: docs/history/2026-09-27-help-removed-comments.md. */
  --issue-error: var(--danger);
  --issue-warn: var(--warn);
  --issue-ok: var(--status-ok);
  --issue-info: var(--status-unknown);
  /* Техническая беда - серая: чинят её не те руки, что содержание, и в очередь
     работ по текстам она не встаёт. Светлее «не осмотрено», иначе полое кольцо
     тонет в фоне; внимание к ней ведёт постоянно пульсирующая нить от центра. */
  --status-broken: #9aa0a4;
  /* Оранжевый занят одним делом - третьей полосой здоровья страницы (60-69).
     В статусах узлов его нет: там три состояния, и четвёртый тон между жёлтым
     и красным их бы размыл. */
  --health-poor: #fb923c;
  /* Сотня баллов (#1004-3, Ant04 12.08.2026: «при 100 баллах цвет зелёный
     насыщенный, чтобы отличался от зелёного 90-99»). Красит она ОДНУ вещь -
     дугу балла у безупречной страницы, - и в язык состояний не входит: заливка
     узла и легенда остаются на лайме `--status-ok`. Тон подобран замером,
     а не на глаз: ΔOKLab до ближайшего чужого смысла 14,9 (бирюза интента), до
     лайма 15,5 - то же расхождение, на котором стоит вся шкала. */
  --status-perfect: #00e05a;

  /* Рёбра: свет идёт от родителя и затухает к потомку. Конец не уходит в ноль -
     иначе дальний путь на чёрном фоне пропадает целиком. */
  --edge-cluster-a: rgba(244, 245, 244, .46);
  --edge-cluster-b: rgba(244, 245, 244, .14);
  --edge-query-a: rgba(244, 245, 244, .34);
  --edge-query-b: rgba(244, 245, 244, .10);

  /* Путь к странице, где наш текст написан, но на сайт ещё не попал. Тон взят из
     статусов Antnest (urgent) и в графе больше нигде не занят, поэтому не
     спорит с данными: ошибки красные, предупреждения жёлтые, чистое зелёное. */
  --copy-wait: #a78bfa;

  /* Подложка карточки наведения: темнее плиты и прозрачнее её - сквозь карточку
     видно схему, а blur держит текст читаемым (владелец). */
  --tip-base: rgba(9, 11, 12, .52);
  --label-bg: rgba(8, 10, 11, .68);
  --label-cluster: #b9bdbb;
  /* вторая строка подписи: частотность ключа - мера, а не имя */
  --label-muted: #7e8582;
  /* пустая ветвь каркаса: направление есть, спроса под ним не собрано */
  --dir-empty: #8b918e;
  --hit-ring: #f4f5f4;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--void);
  color: var(--fg);
  font: 14px/1.45 var(--font);
  overflow: hidden;
}

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

.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.ico { width: 16px; height: 16px; flex: none; }

/* ── стеклянная плита: канон Antnest (без бордера, без тени) ── */
/* ── скролл ────────────────────────────────────────────────
   Один вид на всё приложение (владелец, 2026-08-06): тонкая скруглённая палочка
   без фона, стрелок и подложки. Он ИНДИКАТОР положения, а не орган управления -
   тянут за него редко, листают колесом, - поэтому места занимает минимум.
   Прежде скролл прятался совсем, и в длинной форме нечем было понять, много ли
   осталось ниже. */
/* Два пути, а не один: Chrome при заданном scrollbar-width ПЕРЕСТАЁТ применять
   ::-webkit-scrollbar, и полоса выходит его собственной толщины - 10px вместо
   наших четырёх (замер 2026-08-06). Поэтому стандартное свойство включается
   только там, где webkit-селектора нет вовсе, то есть в Firefox. */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: var(--scroll) transparent;
  }
}
::-webkit-scrollbar { width: 4px; height: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scroll); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-hot); }
/* Стрелки и угол между полосами не рисуются вовсе: это части органа управления,
   которым мы не пользуемся. */
::-webkit-scrollbar-button,
::-webkit-scrollbar-corner { display: none; }
/* Тело ПРАВОЙ панели прокручивается без полосы (владелец, 2026-08-06): она шла
   по всей высоте окна и спорила с полосами внутри полей - две шкалы рядом
   читаются как одна сломанная. ЛЕВАЯ панель с 25.08 такая же (#1602, слово
   Ant04 24.08: «саму вот эту полоску скролла просто спрятать»), и прокрутка у
   обеих ОСТАЁТСЯ рабочей - колесом, клавишами, жестом: снимается полоса, а не
   `overflow`. Полоса остаётся в полях правки и во внутренних списках, включая
   перечень страниц под классом замечания: он не панель, а её содержимое, и
   слово владельца туда не дотягивается.
   Путей два по той же причине, что у общего правила выше: `scrollbar-width`
   понимают свежие движки, старый Chrome - только `::-webkit-scrollbar`. */
.sheet-body::-webkit-scrollbar { width: 0; height: 0; }
.panel-left { scrollbar-width: none; }
.panel-left::-webkit-scrollbar { width: 0; height: 0; }
@supports not selector(::-webkit-scrollbar) {
  .sheet-body { scrollbar-width: none; }
}

.plate {
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius);
  box-shadow: var(--top-glow);
}

/* ── каркас ────────────────────────────────────────────────
   Сцена занимает весь экран, интерфейс висит НАД ней плитами:
   так блюр стекла показывает граф и отделяет слои без рамок. */
.app { position: fixed; inset: 0; }

.layout { position: absolute; inset: 0; }

.stage { position: absolute; inset: 0; background: var(--void); }
canvas { position: relative; display: block; width: 100%; height: 100%; cursor: grab; }
/* Курсор объявляет, что под ним живой узел, а не пустая сцена. Тяга важнее:
   правило перетаскивания идёт последним и перебивает наведение. */
canvas.hot { cursor: pointer; }
canvas.dragging { cursor: grabbing; }

/* ── шапка ─────────────────────────────────────────────────
   Одна группа по центру: выбор сайта и переключатель вида. Полоса кликов не
   ловит - иначе прозрачный промежуток между элементами съедал бы перетаскивание
   графа.

   Три колонки при одном элементе намеренны: пустые крайние держат группу ровно
   посередине окна. Без них она уезжала влево, в первую свободную ячейку
   (поймано после сноса плиты с именем сайта, 2026-07-28). */
.topbar {
  position: absolute;
  /* Шапка лежит ВЫШЕ панелей (было 3, панель 4). Замер 12.08.2026 на 1280×720:
     правая панель листа накрывала кнопку пересъёма (её центр 903×46 отдавал
     `elementFromPoint` -> `sheet-top`), то есть кнопка #815 была недостижима
     мышью в Structure ровно там, где ею и пользуются. Пока у съёма была вторая
     дверь в левой панели, дефект не замечали; #902-2 снял её, а #2680 унёс на
     звезду и саму кнопку. Клики у панелей это не отнимает: у самой шапки
     `pointer-events: none`, ловят их только её кнопки. */
  z-index: 5;
  top: var(--gutter);
  left: var(--gutter);
  right: var(--gutter);
  height: var(--topbar-h);
  display: grid;
  /* ГРУППА СТОИТ ПО ЦЕНТРУ ОКНА, и держат её крайние колонки сетки: обе
     растяжимые, средняя по содержимому. Правило менялось ДВАЖДЫ за 20.08, и оба
     решения владельца названы здесь намеренно - иначе следующий читатель вернёт
     утренний вариант как «правильный».
     Утром (#1338, «подвинуть левее панель с кнопками, выровнять по краю блока»)
     группу прижали к краю левой панели полем самой шапки: между панелью и
     группой стояла пустая полоса 185 px, и на снимке она читалась как дыра.
     Вечером (#1359, «верхняя панель сместилась влево, а она должна быть
     посередине экрана») владелец посмотрел вживую и попросил центр обратно -
     утреннее выравнивание по краю блока ОТМЕНЕНО вместе с полем шапки.
     Центр здесь - центр ОКНА, а не свободного места между панелями: сцена
     занимает окно целиком, панели лежат поверх неё, и компенсировать их ширину
     переключателю нечем. */
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  pointer-events: none;
}
/* ШАПКА НЕ ДВИГАЕТСЯ ПРИ ОТКРЫТОЙ ПАНЕЛИ КЛЮЧЕЙ (#1035-3, слово Ant04 13.08
   отменяет прежнее решение исполнителя): панель просто ложится поверх, а колонку
   шапки никто не уступает. Правило `.topbar.is-keys` со сдвигом правого края и
   переход `transition: right` сняты отсюда целиком - владелец посмотрел живьём и
   выбрал третий вариант, которого не было среди двух прежних.
   Что перекрытые инструменты на это время недоступны - принятое им решение, а не
   дефект; клик в это место обязан уходить панели, и держит это её слой (§
   `.panel-keys`). */

/* Структура дерева разошлась с сервером (#950). Живёт в СВОБОДНОЙ колонке сетки
   шапки - с возвратом центра 20.08 (#1359) это снова ЛЕВАЯ колонка, как было до
   утренней правки #1338. Свойство, ради которого колонка и выбиралась, за оба
   переезда не менялось: появление строки не двигает ни одну кнопку, потому что
   колонка растяжимая, а группа прибита к средней. Скрытый элемент места не
   занимает (`display: none`), то есть в обычной работе шапка ровно та же.

   Тон - `--status-gap` (жёлтый «требует внимания»), а не красный: данные целы и
   свежи, устарела только ветвь на экране. Красный здесь занял бы язык беды у
   состояний страницы, где он уже значит другое.

   Строка ОБРЕЗАЕТСЯ многоточием, а не переносится: шапка фиксированной высоты,
   и вторая строка вылезла бы за её край. Полный текст остаётся в `title`. */
.tree-stale {
  grid-column: 1;
  justify-self: start;
  pointer-events: auto;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 12px;
  height: var(--control-h);
  line-height: var(--control-h);
  border-radius: var(--radius-md);
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  color: var(--status-gap);
  font-size: 13px;
}
.tree-stale[hidden] { display: none; }

/* Переключатель стоит на сцене сам по себе: собственная лунка и есть его
   поверхность, плита вокруг была лишним слоем. Блюр нужен, чтобы граф под ним
   не мешал читать подписи режимов.

   Центр здесь - центр окна: группа держится СРЕДНЕЙ колонкой сетки шапки, обе
   крайние растяжимы поровну. Утренний вариант 20.08 (#1338, прижать к краю
   левой панели полем шапки) отменён тем же днём по слову Ant04 (#1359): он
   смотрел вживую и попросил вернуть середину экрана. Довод утреннего решения -
   пустая полоса 185 px между панелью и группой - остаётся фактом, но владелец
   выбрал центр, зная о ней. */
.topbar-mid {
  grid-column: 2;
  justify-self: center;
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: auto;
}
/* Кнопка-папка у лунки: серая, без плиты - редкое действие-источник, а не
   режим взгляда. Блюр тот же, что у лунки: под ней тоже сцена. */
/* Выход назван вторым селектором, а не переездом в прямые потомки: имя и кнопка
   ходят парой, и обёртка держит их вместе (#2317). */
.topbar-mid > .btn-icon,
.topbar-who > .btn-icon {
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  background: var(--glass);
  color: var(--muted);
  height: var(--control-h);
  width: var(--control-h);
  box-shadow: var(--top-glow);
}
.topbar-mid > .btn-icon:hover,
.topbar-who > .btn-icon:hover { color: var(--fg); background: var(--hover); }
/* Пометка роли: появляется только у гостя, поэтому места в шапке она
   постоянно не занимает. */
.topbar-role {
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--well);
  font-size: 10.5px;
  letter-spacing: .04em;
  color: var(--fg-dim);
}
.topbar-role[hidden] { display: none; }

/* Поиск в шапке: та же стеклянная поверхность, что у лунки и кнопок, ширина под
   короткий запрос - длинный виден по мере набора, а держать ползоны экрана ради
   редкого длинного пути незачем. */
.topbar-find {
  display: flex;
  align-items: center;
  gap: 7px;
  height: var(--control-h);
  padding: 0 12px;
  border-radius: var(--radius-sm);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  background: var(--glass);
  box-shadow: var(--top-glow);
  pointer-events: auto;
}
.topbar-find .ico { width: 15px; height: 15px; color: var(--muted); flex: none; }
/* Поле внутри плашки не рисует вторую плашку: ни фона, ни рамки, ни своей
   высоты - всё это уже даёт .topbar-find (поймано 2026-07-30, поле выглядело
   вложенным блоком со смещённым текстом). */
.topbar-find input[type="search"] {
  width: 190px;
  height: auto;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  outline: none;
  color: var(--fg);
  font: 400 12.5px/1 var(--font);
}
.topbar-find input[type="search"]::placeholder { color: var(--muted); opacity: 1; }
.topbar-find:focus-within { background: var(--hover); }
.topbar-find input::-webkit-search-cancel-button { filter: invert(1) opacity(.5); cursor: pointer; }

.topbar .seg {
  pointer-events: auto;
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  background: var(--glass);
  box-shadow: var(--top-glow);
}
/* ── кнопки ────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: var(--control-h);
  padding: 0 13px;
  background: var(--well);
  color: var(--fg);
  border: 0;
  border-radius: var(--radius-sm);
  font: 500 12.5px/1 var(--font);
  letter-spacing: .02em;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.btn:hover { background: var(--hover); }
/* Недоступная кнопка обязана ВЫГЛЯДЕТЬ недоступной: правило то же, что у
   сегментированного переключателя. Без него кнопка съёма во время идущего джоба
   и отметка переноса на странице, которой нечего отмечать, читаются рабочими, а
   нажатие проходит впустую. */
.btn[disabled] { opacity: .32; cursor: not-allowed; }
.btn[disabled]:hover { background: var(--well); }
/* СВЕЧЕНИЯ У КНОПОК НЕТ (слово Ant04 31.08.2026: «убрать неоновое свечение
   кнопок, особенно в таблице ключей, выглядит плохо»). Переменная `--glow`
   (`0 0 24px` акцента) снята вместе с четырьмя носителями: акцентная пластина
   переключателя в таблице ключей, кнопка крупного слоя в шапке, главная кнопка
   и кнопка формы ручного адреса. Состояния держит ФОН - акцент против `--well`
   у покоя и `--hover` у наведения, - поэтому снятие тени различимости не
   касается. `--top-glow` это ДРУГОЕ (внутренняя кромка панелей), под слово
   владельца не попадает и остаётся.

   ФАКТ, который правило не чинит: наведение на саму `.btn-primary` не
   отличается от покоя ни фоном, ни тенью - селекторы стоят в одном правиле, и
   существует оно, чтобы отменить `.btn:hover`. Так было со дня написания;
   заводить признак наведения значит менять вид сверх просьбы, и это решение
   владельца, а не правки вкуса (слово тимлида 31.08). */
.btn-primary,
.btn-primary:hover { background: var(--accent); color: var(--accent-fg); }

.btn-icon { position: relative; padding: 0; width: var(--control-h); justify-content: center; gap: 0; }
.btn-icon .ico { width: 17px; height: 17px; }

/* ── переключатель вида: плоско / структура ─────────────
   Сегменты живут в общей лунке, поэтому выбранный читается как
   поднятая пластина, а не как ещё одна кнопка рядом. */
.seg {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  background: var(--well);
  border-radius: var(--radius-sm);
  flex: none;
}
/* `display` правила выше перебивает `[hidden]`, а сегмент видов структуры
   скрывается именно им (#1395-5): без этой строки он оставался на экране там,
   где таблицы ключей нет вовсе. */
.seg[hidden] { display: none; }

.seg-btn {
  display: inline-flex;
  align-items: center;
  height: calc(var(--control-h) - 4px);
  padding: 0 13px;
  background: none;
  border: 0;
  border-radius: 10px;
  color: var(--muted);
  font: 500 12px/1 var(--font);
  letter-spacing: .02em;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.seg-btn:hover { color: var(--fg); }
/* Нажатый сегмент - акцентная пластина (владелец): текущее состояние
   переключателя обязано читаться с одного взгляда. */
.seg-btn[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); }
.seg-btn[aria-pressed="true"]:hover { color: var(--accent-fg); }
/* КНОПКА КРУПНОГО СЛОЯ В ШАПКЕ ГОРИТ АКЦЕНТОМ (#1432, слово Ant04 22.08: «при
   открытии окна ключей, кнопка должна гореть зелёным»). Состояние в разметке
   стояло и раньше (`is-on` плюс `aria-pressed`), правила под него в шапке не
   было ни одного - кнопка выглядела одинаково в обоих состояниях.

   Ограничение селектора шапкой держалось СВЕЧЕНИЕМ, которого у кнопки внутри
   листа не было: разница объяснялась местом. Свечение снято 31.08 (ниже, у
   `.btn-primary`), и оба правила дают теперь один вид - акцентный фон; тела
   совпадают, и это остаток, а не решение. */
.topbar .btn-icon.is-on { background: var(--accent); color: var(--accent-fg); }
.topbar .btn-icon.is-on:hover { color: var(--accent-fg); }
/* Режим без данных не исчезает и не притворяется рабочим: он виден и объясняет
   себя через title, иначе пропажа кнопки читается как поломка приложения. */
.seg-btn[disabled] { opacity: .32; cursor: not-allowed; }
.seg-btn[disabled]:hover { color: var(--muted); }

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

/* ── боковые плиты ─────────────────────────────────────── */
/* Плита ровно по содержимому: пустое стекло до низа экрана читается как
   «данные ещё грузятся». Растёт до нижнего края и дальше прокручивается
   внутри себя. */
.panel {
  position: absolute;
  /* Панель - постоянная плита у кромки, поэтому лежит выше сцены и её слоёв. */
  z-index: 4;
  top: var(--gutter);
  max-height: calc(100% - 2 * var(--gutter));
  overflow-y: auto;
  /* Своего поля у панели нет (владелец, 2026-08-06): подложку сняли, а отступ
     от неё остался и отбивал заголовки от блоков на разное расстояние. Поле
     теперь у каждой карточки своё, панель - только колонка и её ширина. */
  padding: 0;
}
/* Обе панели начинаются у самой кромки экрана (владелец, 2026-07-30): в шапке
   над ними ничего нет - переключатель вида и выбор сайта стоят одной группой по
   центру, - и отступ под несуществующую плиту только съедал бы высоту. */
.panel-left {
  left: var(--gutter);
  width: var(--col-left);
}
.panel-left[hidden] { display: none; }
.panel-right { right: var(--gutter); width: var(--col-right); }
.panel-right[hidden] { display: none; }

/* Блоки `.ctl` левой панели (замечания): боковое поле, плита и своё размытие - общей подложки у панели
   нет. Довод - docs/history/2026-09-25-moved-comment-reasons.md. */
.panel-left > .ctl {
  padding: 10px 12px 12px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-md);
}

/* ── control popup ── Under its button (`placePop`, owner 2026-07-30); clicks pass through so the graph shows what a filter did.
   Above the site picker (#2183), which covers all for a user with no site and hid the access card. Why: docs/history/2026-09-26-progon-comments.md. */
.pop {
  position: fixed;
  z-index: 11;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  pointer-events: none;
}
#pop .pop-box { position: fixed; }
.pop[hidden] { display: none; }

/* Scan-area menu (#902-3): above the section cards, it lives between the press and the choice. No overlay of its own:
   a click outside is taken by the shared layer handler in app.js, as for the card. */
.scan-menu {
  position: fixed;
  z-index: 12;
  display: flex;
  flex-direction: column;
  /* ШИРИНА ПО САМОЙ ДЛИННОЙ СТРОКЕ (слово Ant04: «сделать более компактными»). */
  padding: 3px;
  box-shadow: var(--top-glow), 0 18px 48px rgba(0, 0, 0, .5);
  /* РОСТ ИЗ КРУЖКА (слово Ant04: «окошко выростает из кнопки в правую сторону»).
     Точку роста ставит `placeMenu` по выбранной стороне. */
  animation: scan-menu-grow .14s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .scan-menu { animation: none; }
}
@keyframes scan-menu-grow {
  from { opacity: 0; transform: scale(.4); }
  to { opacity: 1; transform: scale(1); }
}
.scan-menu button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  background: none;
  border: 0;
  /* РАДИУС ПОДСВЕТКИ РАВЕН РАДИУСУ ПЛАШКИ (слово Ant04: «выделение кнопки
     вылазит за пределы блока»). */
  border-radius: var(--radius);
  color: var(--fg);
  font: 400 12px/1.25 var(--font);
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
/* Знак строки приглушён: он называет род действия, а читают подпись рядом. */
.scan-menu .ico { width: 15px; height: 15px; color: var(--muted); }
.scan-menu button:hover,
.scan-menu button:focus-visible { background: var(--solid); }
/* Недоступный пункт судится ЦВЕТОМ и курсором, а не исчезновением: причина
   живёт у него в `title`, и пропавший пункт унёс бы её вместе с собой. */
.scan-menu button[disabled] { color: var(--muted); cursor: not-allowed; }
.scan-menu button[disabled]:hover { background: none; }
/* Правила `.scan-menu-num` больше нет: счётчик страниц снят словом Ant04 18.09,
   довод и цена - docs/history/2026-09-18-plashka-zvezdy.md. */
/* Исход буфера пишется В САМ ПУНКТ (#2702): плашка остаётся открытой, и отказ
   обязан отличаться от удачи не только словом. */
.scan-menu button.is-fail .scan-menu-label { color: var(--issue-error); }

/* Две ширины по роду содержимого (владелец, 2026-07-30): список чипов и
   страница текста читаются по-разному, и одна ширина на оба оставляла у
   фильтров половину карточки пустой. Широкая держит меру строки около 70
   знаков - дальше глаз теряет начало следующей строки. */
.pop-box {
  width: min(276px, 92vw);
  max-height: min(76vh, 720px);
  display: flex;
  flex-direction: column;
  padding: 0;
  pointer-events: auto;
  box-shadow: var(--top-glow), 0 18px 48px rgba(0, 0, 0, .5);
}

.pop-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 14px 10px;
}
.pop-title {
  margin: 0;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
.pop-body { overflow-y: auto; padding: 0 14px 14px; }

/* Наборы чипов внутри одного раздела отбирают одно и то же, но отвечают на разные
   вопросы, и подпись держит их порознь. */
.ctl-group + .ctl-group { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); }
.ctl-group[hidden] { display: none; }
/* Сводка проблем сайта: поверхность даёт `.panel-left > .ctl` выше, поле и радиус плиты сводки -
   правило ниже; здесь отбивка снизу (плашки отказа и пустой сводки стоят своими плитами). */
.issue-block, .issue-sheet { margin-bottom: 10px; }
/* ОДНА ПЛИТА НА СВОДКУ (вариант B, слово Ant04 28.09, #3702): радиус и поле плиты Antnest, у секций
   уровней своей поверхности и оттенка тона нет - уровень несёт только точка. */
.panel-left > .issue-sheet { padding: 14px 8px 8px; border-radius: var(--radius); box-shadow: var(--top-glow); }
.issue-sheet > .issue-block { margin: 0; }
.issue-sheet > .issue-block + .issue-block { margin-top: 14px; }
/* ЗАГОЛОВОК СЕКЦИИ - СТРОКА СВЁРТКИ: точка, слово уровня и число классов
   в одном ряду, поэтому точка стоит потоком, а не абсолютом. */
.issue-block > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px 6px;
  cursor: pointer;
  list-style: none;
}
.issue-block > summary::-webkit-details-marker { display: none; }
.issue-block > summary > .ctl-title {
  margin: 0;
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
}
.issue-block > summary > .issue-n { margin-left: auto; }
/* Свёрнутая секция - строка-кнопка на `--well` со словом уровня белым: строк под ней нет. */
.issue-block:not([open]) > summary { padding: 8px 10px; border-radius: var(--radius-sm); background: var(--well); }
.issue-row { margin: 0; }
/* Строка rounded-xl без разделителей, по центру стоит и иконка объяснения (#1433 «центрируй»);
   подложки под курсором нет - «ОТКЛИК СТРОКИ ЗАМЕЧАНИЯ» в docs/INVARIANTS-app.md. */
.issue-row > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  list-style: none;
  font-size: 13px;
}
.issue-row > summary::-webkit-details-marker { display: none; }
/* Имя НЕ растягивается на всю строку (#1296, поправка Ant04): иконка стоит сразу
   за ним, а растянутое имя утащило бы её к правому краю. Сжимается при нехватке
   места, чтобы длинное имя не выталкивало счёт страниц. */
.issue-name { flex: 0 1 auto; min-width: 0; color: var(--fg); }
/* УРОВЕНЬ НЕСЁТ ТОЧКА, А НЕ ЦВЕТ ИМЕНИ (#2331); она стоит В ПОТОКЕ строки заголовка секции (#2339):
   абсолютная сидела вплотную к кромке, без отступа до края и до слова. */
.issue-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
.issue-block.is-error .issue-dot { background: var(--issue-error); }
.issue-block.is-warn .issue-dot { background: var(--issue-warn); }
.issue-block.is-info .issue-dot { background: var(--muted); }
/* Счёт прижат к правому краю ОТСТУПОМ, а не растянутым соседом: так между
   иконкой и счётом появляется единственное свободное место строки. */
/* Вид счётчика - ОДИН дом, ниже по файлу (`.issue-n` рядом с `details.issue`):
   здесь остаётся только его место в строке. Тон, кегль и пилюля стояли и тут, и
   там, и первое объявление было мёртвым - его перебивало второе; при правке
   цвета 25.08 (#1600) правились бы оба, а увиделось бы одно. */
.issue-row > summary .issue-n { margin-left: auto; }
/* Кнопка объяснения (#1296): стоит сразу после имени класса. Приглушена до
   наведения на строку - у неё в этой строке третий по важности вопрос после
   имени класса и числа страниц. */
.issue-why-btn {
  flex: none;
  display: flex;
  align-items: center;
  background: none;
  border: 0;
  padding: 0;
  color: var(--muted);
  opacity: .45;
  cursor: pointer;
}
.issue-row > summary:hover .issue-why-btn,
.issue-why-btn:hover,
.issue-why-btn:focus-visible { opacity: 1; }
.issue-why-btn .ico { width: 14px; height: 14px; }
.issue-pages {
  list-style: none;
  margin: 2px 0 6px;
  padding: 0;
  max-height: 220px;
  overflow-y: auto;
}
.issue-pages button {
  display: block;
  width: 100%;
  background: none;
  border: 0;
  border-radius: 8px;
  padding: 3px 8px;
  color: var(--muted);
  font: 11.5px/1.4 var(--mono);
  text-align: left;
  cursor: pointer;
  overflow-wrap: anywhere;
}
.issue-more {
  padding: 4px 8px 4px 14px;
  color: var(--muted);
  font: 11px/1.35 var(--mono);
}
.issue-pages button:hover, .issue-pages button:focus-visible {
  background: var(--hover);
  color: var(--fg);
  outline: none;
}
/* Раскрытая строка и её адреса - одна подложка `--well`: список читается частью строки. */
.issue-row[open] > summary { background: var(--well); border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
.issue-row[open] > .issue-pages {
  margin: 0 0 4px;
  padding: 2px 6px 8px 30px;
  background: var(--well);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}

/* Название блока отбора. Ярусом ВЫШЕ подписи группы: под ним лежат
   `.ctl-head`, и одинаковый кегль сделал бы из двух ярусов один список. */
.ctl-title {
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--fg);
}
/* Свёрнутая секция сводки называет уровень белым: строк под ней нет, слово и есть кнопка. */
.issue-block:not([open]) > summary > .ctl-title { color: var(--fg); }
.ctl-head {
  margin: 0 0 6px;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
/* Подпись над полем - тот же ярус, что заголовок блока данных в панели. */
.field > span:first-child {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 8px 0;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
.field > span:first-child { padding: 0 0 8px; }

/* У полей ввода рамки фокуса нет (владелец, 2026-07-30): в поле фокус и так
   виден кареткой, а акцентная обводка вокруг каждого поля формы правки
   превращала набор текста в мигающую зелёную рамку. У кнопок и чипов она
   остаётся - там показать фокус больше нечем. */
input[type="search"]:focus,
input[type="search"]:focus-visible { outline: none; }

/* ── чипы фильтров ─────────────────────────────────────── */
.chips { display: flex; flex-direction: column; gap: 2px; }

.chip {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 6px 9px;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--fg);
  font: 13px/1.3 var(--font);
  text-align: left;
  cursor: pointer;
  user-select: none;
  transition: background var(--transition), opacity var(--transition);
}
.chip:hover { background: var(--well); }
/* Нажатый чип - выбранный: на схеме остаётся только он, поэтому он и выглядит
   как включённая кнопка, а не как приглушённая (владелец). */
.chip.is-on { background: var(--well); box-shadow: inset 0 0 0 1px var(--accent); }
.chip.is-on .count { color: var(--accent); }
.chip .dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  box-shadow: 0 0 10px currentColor;
  background: currentColor;
}
/* Легенда повторяет штрих обводки на сцене: сплошная - покрыто, пунктир - пробел,
   двойное кольцо - каннибализация. */
.chip .ring {
  width: 11px; height: 11px; border-radius: 50%; flex: none;
  border: 2px solid currentColor;
  box-shadow: 0 0 10px currentColor;
}
/* Полый круг с толстой обводкой - тот же глиф, что у сломанного узла на схеме
   (владелец): у здоровых точка налитая, у сломанного пустая. */
.chip .tri {
  width: 11px; height: 11px; flex: none;
  border: 3px solid currentColor;
  border-radius: 50%;
  filter: drop-shadow(0 0 6px currentColor);
}
.chip .dot.copy-wait { color: var(--copy-wait); }
/* An empty slot of the dot's width: the text chips without a glyph keep their labels in one column. */
.chip .mark-none { width: 9px; flex: none; }
.chip .count {
  margin-left: auto;
  font: 12px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.hint { font-size: 12px; line-height: 1.55; color: var(--muted); margin: 0; }
/* Пара адресов переезда (#1055). Моноширинный тон взят у остальных путей панели:
   их читают посимвольно, а слаг товара переносится по любому знаку. Прежний
   адрес зачёркнут, а не приглушён вторым тоном: приглушение здесь уже занято
   подсказкой, и две степени серого читались бы как два разных смысла. */
.moved-path { margin: 4px 0 0; font: 11px/1.4 var(--mono); color: var(--fg); overflow-wrap: anywhere; }
.moved-path.is-old { color: var(--muted); text-decoration: line-through; }

/* ── id звезды ──────────────────────────────────────────
   Имя звезды в разговоре и в командах скана. Кликается: клик кладёт id в буфер,
   потому что дальше его вставляют в чат или в поиск. */
.id-chip {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 9px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--well);
  color: var(--muted);
  font: 11px/1 var(--mono);
  letter-spacing: .04em;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
/* Высота задана САМОМУ чипу, а не одному его контексту (найдено ревью
   2026-08-06): при `line-height: 1` и нулевом поле по вертикали чип вырождался
   в капсулу 11 px без внутреннего поля.
   Правила для шапки ПРАВОЙ панели тут больше нет: чип оттуда снят (#2702), id
   копируется из меню звезды, и мёртвое правило искало бы несуществующий узел. */
.id-chip:hover { background: var(--solid); color: var(--fg); }
.id-chip.done { background: var(--accent); color: var(--accent-fg); }
.tip-id {
  margin-left: 7px;
  font: 10.5px/1 var(--mono);
  color: var(--placeholder);
}

/* ── числа под чипами ───────────────────────────────────
   Полос обхода нет ни в панели, ни на сцене: последняя их плита («Vollständige
   Erfassung», охват осмотра и охват сверки повторов) снята целиком с #1035-2 -
   два её числа совпадали, а дата отвечала на вопрос, которого человек не
   задаёт. Осталась строка чисел, которых чипами не показать. */
.stat-line {
  margin: 9px 0 0;
  font: 11.5px/1.5 var(--mono);
  color: var(--muted);
}
.stat-line:empty { display: none; }
.stat-line b { color: var(--fg); font-weight: 500; font-variant-numeric: tabular-nums; }

/* ── кнопка у выбранной звезды (#1363-1, единственная с #2721) ──────────
   Стоит НАД канвасом в `.stage`, позицию каждым кадром пишет `syncStarMenu`
   (jobs.js) в `style.left/top`. Точка отсчёта - центр звезды, поэтому кнопка
   сдвигается на половину своей ширины сама: измерять её в JS значило бы читать
   раскладку на каждом кадре ради числа, которое умеет посчитать браузер. */
.star-btn {
  position: absolute;
  z-index: 5;
  transform: translateX(-50%);
  white-space: nowrap;
  /* Кадр рисуется под кнопкой, а нажатия она забирает себе: без этого клик по
     кнопке дошёл бы до канваса и снял выбор звезды тем же жестом. */
  pointer-events: auto;
}
.star-btn[hidden] { display: none; }

/* Кружок опций (#2702) стоит СБОКУ от звезды, а не под ней: сдвиг у него
   вертикальный, а по горизонтали место целиком считает `syncStarMenu`.
   МЕНЬШЕ И ПЛОТНЕЕ прочих кнопок (#2721, слово Ant04 18.09: «должна быть немного
   меньше и лучше видна, сейчас они слишком прозрачная»): на поле карты фон
   `--well` в пять сотых читался только по наведению. */
.star-dots {
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur));
  backdrop-filter: blur(var(--blur));
  box-shadow: var(--top-glow), 0 2px 10px rgba(0, 0, 0, .45);
}
.star-dots:hover { background: var(--hover); }
.star-dots .ico { width: 15px; height: 15px; }
/* КРЕСТИК ПРИ ОТКРЫТОЙ ПЛАШКЕ МЕНЬШЕ ТРЁХ ТОЧЕК (слово Ant04: «маленький
   аккуратный крестик белый»); белый он от `.btn`, своего цвета тут не заводится. */
.star-dots.is-open .ico { width: 13px; height: 13px; }

/* ── подсказка у курсора ───────────────────────────────── */
.tooltip {
  position: absolute;
  /* Поверх панелей: подсказка идёт за курсором и обязана быть видна там, где он
     оказался. */
  z-index: 6;
  pointer-events: none;
  /* Ширина фиксирована (владелец, 2026-07-30): карточка одна на все узлы, и
     плашка, скачущая по длине адреса, читается как разные объекты. Число взято
     по товарным путям сайта: медиана 41 знак, 80-й перцентиль 55 - при колонке
     адреса в 219 px (моно 12.5) это ровно две строки. Длинный хвост в 90-й
     перцентиль (66 знаков) ложится в три. */
  width: 300px;
  padding: 8px 11px;
  background: var(--tip-base);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  border-radius: var(--radius-md);
  box-shadow: var(--top-glow), 0 12px 32px rgba(0, 0, 0, .45);
  font-size: 12px;
  color: var(--fg);
}
.tooltip b { font-weight: 600; }

/* Карточка наведения: шапка с адресом и кодом, свойства парами, действие внизу.
   Ярусы разделены не линиями, а воздухом и светлотой - линия внутри плашки в
   120 px читается рамкой и дробит её на части (владелец, 2026-07-30). */
.tip-head {
  display: flex;
  align-items: baseline;
  /* Код прижат к правой кромке (владелец, 2026-07-30): адрес растёт влево-вправо
     по длине пути, а код стоит на месте и читается колонкой, как в списке.
     Под шапкой черта: адрес - имя объекта, ниже его свойства, и это разные роды
     сведений. */
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 8px;
  padding-bottom: 7px;
  box-shadow: inset 0 -1px 0 var(--line);
}
.tip-head b {
  font: 600 12.5px/1.35 var(--mono);
  word-break: break-all;
}
.tip-props {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 3px 12px;
  margin: 0;
  font-size: 11.5px;
}
/* Имя свойства - подпись, а не заголовок: строчными и мельче значения, иначе
   капс спорит с самим значением за внимание (владелец). */
.tip-props dt {
  color: var(--placeholder);
  font-size: 10px;
  white-space: nowrap;
  align-self: center;
}
.tip-props dd { margin: 0; color: var(--fg); }
/* Тон узла - точка того же цвета, что и сама звезда: слово называет состояние,
   а цвет связывает его с тем, что видно на схеме. */
.tip-tone {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 7px;
  border-radius: 50%;
  vertical-align: baseline;
}
/* Кольцо вместо точки - та же разница, что на сцене: у страницы с вложенным
   глиф полый, у конечной налитой. */
.tip-tone.is-ring {
  background: transparent;
  border: 2px solid;
  box-sizing: border-box;
}

/* display в правиле ниже перебивает атрибут hidden, поэтому скрытое состояние
   объявляется явно: иначе пустой оверлей ловит все клики по графу. */
.empty[hidden], .tooltip[hidden] { display: none; }

/* ── оверлей выбора сайта ──────────────────────────────── */
.empty {
  position: absolute;
  z-index: 10;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 22px;
  text-align: center;
  padding: 40px;
  color: var(--muted);
  font-size: 13px;
  white-space: pre-line;
  background: rgba(0, 0, 0, .58);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.picker {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: min(560px, 84vw);
  padding: 20px;
  text-align: left;
  white-space: normal;
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius);
  box-shadow: var(--top-glow), 0 24px 64px rgba(0, 0, 0, .55);
}

.picker h2 {
  margin: 0 0 8px;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}

.core-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  width: 100%;
  padding: 11px 14px;
  background: var(--well);
  border: 0;
  border-radius: var(--radius-md);
  color: var(--fg);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition);
}
.core-item:hover { background: var(--hover); }
.core-item b { font-size: 14px; font-weight: 600; }
.core-item span { font: 11.5px/1.3 var(--mono); color: var(--muted); }

.core-manual {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
  margin-top: 10px;
}

.core-manual input {
  height: var(--control-h);
  background: var(--solid);
  color: var(--fg);
  border: 0;
  border-radius: var(--radius-sm);
  padding: 0 12px;
  font: 12px/1 var(--mono);
}
.core-manual input:focus,
.core-manual input:focus-visible { outline: none; }

.core-manual button {
  height: var(--control-h);
  padding: 0 16px;
  background: var(--accent);
  color: var(--accent-fg);
  border: 0;
  border-radius: var(--radius-sm);
  font: 500 12.5px/1 var(--font);
  cursor: pointer;
}

.core-error {
  grid-column: 1 / -1;
  margin: 0;
  min-height: 15px;
  font: 11.5px/1.3 var(--mono);
  color: var(--danger);
}

/* ── панель деталей ────────────────────────────────────── */
/* Шапка панели: состояние, род страницы и дата съёма в одной строке над
   заголовком. Дата уходит к правому краю - её читают отдельно от рода, а не
   как продолжение фразы. */
.detail-top {
  display: flex;
  align-items: center;
  gap: 8px;
  /* Поле 12 px равно внутреннему полю карточек под шапкой. */
  padding: 0 12px;
}
/* Шапка аудита несёт одно имя раздела (владелец). */
.detail-top.is-audit { margin-bottom: 4px; }
.detail-top.is-audit .detail-title { margin: 0; }

/* Вне шапки-строки метка остаётся блочной: у групп путей заголовок идёт под
   ней, и без block они склеиваются в одну строку. */
.detail-kind {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--accent);
}
.detail-top .detail-kind { display: inline; }
/* Заголовок панели называет её работу, а не объект (владелец, 2026-07-30):
   паспорт звезды переехал в карточку наведения, здесь остался аудит.
   Боковое поле своё: заголовок и подсказки лежат ПРЯМО в панели, а её общее поле
   снято вместе с подложкой - без этого самый крупный кегль панели вставал на
   12 px левее карточек под ним, и сдвинутыми читались они (найдено ревью
   2026-08-06). Внутри шапки поле уже дал .detail-top, там оно снимается. */
.panel > .detail-title,
.panel .dblock > .hint,
.sheet-body > .hint { padding: 0 12px; }
.detail-top .detail-title { padding: 0; }
.detail-title {
  margin: 0 0 2px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
/* ── блоки панели деталей ──────────────────────────────────
   Данные узла разложены по категориям, каждая - свой раскрываемый блок.
   Закрытый блок стоит одной строкой, поэтому список из десяти категорий
   читается целиком, а не прокруткой на два экрана. */
.detail-blocks { margin: 16px 0 0; }

/* Блок - отдельная карточка, как группы в листе текстов (владелец, 2026-08-06):
   один жест чтения на обе панели. Линия сверху делила поток, но не собирала
   строки в предмет, и десяток свёрнутых заголовков читался сплошной лентой. */
.block {
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-sm);
  padding: 0 12px;
}
.block + .block { margin-top: 8px; }

.block > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 11px 0;
  cursor: pointer;
  list-style: none;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
  transition: color var(--transition);
}
.block > summary::-webkit-details-marker { display: none; }
.block > summary:hover { color: var(--fg); }
.block > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.block > summary::after {
  content: "";
  margin-left: auto;
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform var(--transition);
}
.block[open] > summary::after { transform: translateY(1px) rotate(-135deg); }

.block-body { padding: 0 0 14px; font-size: 13px; word-break: break-word; }
.block-body a { color: var(--accent); text-decoration: none; }
.block-body a:hover { text-decoration: underline; }

/* Замечания: тяжесть читается слева полосой, текст - строкой. Плашка «ошибка»
   у каждой строки была бы шумом на двадцати пунктах, поэтому severity кодируется
   цветом полосы, а расшифровка стоит один раз в заголовке группы. */
/* Секции замечаний разведены явно (владелец, 2026-07-30): разделитель и свой
   воздух - подряд идущие типы бед сливались в одну простыню. */
.issue-group {
  margin-top: 8px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-sm);
  padding: 10px 12px 12px;
}
.issue-group:first-child { margin-top: 0; }
.issue-group > h4 {
  margin: 0 0 6px;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
/* Замечание - КАРТОЧКА (владелец, 2026-07-31): своя подложка со скруглением,
   слева цветная полоса тона, фон очень лёгкий и того же тона. Прежде это была
   строка с одной чертой слева: на длинном списке замечания сливались в ленту,
   и глазу не за что было зацепиться, где кончается одно и начинается другое.
   Промежуток между карточками держит эту границу без разделительных линий. */
.issue {
  display: block;
  position: relative;
  padding: 7px 10px 7px 16px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--issue-warn) 7%, transparent);
  font-size: 12.5px;
  line-height: 1.45;
  --issue-tone: var(--issue-warn);
}
/* Полоса тона - отдельная скруглённая палочка, а не граница блока: у границы
   углы повторяют скругление карточки и она читается рамкой, а палочка стоит
   в поле карточки и отбивает её начало, как в сайдбаре. */
.issue::before {
  content: '';
  position: absolute;
  left: 6px;
  top: 7px;
  bottom: 7px;
  width: 3px;
  border-radius: 3px;
  background: var(--issue-tone);
}
.issue.is-error {
  --issue-tone: var(--issue-error);
  background: color-mix(in srgb, var(--issue-error) 8%, transparent);
}
/* Нейтральное замечание: считаем и показываем, но решений по нему не принимаем,
   поэтому ни тона срочности, ни веса в счётчике оно не получает. */
.issue.is-info {
  --issue-tone: var(--issue-info);
  background: color-mix(in srgb, var(--issue-info) 6%, transparent);
  color: var(--muted);
}
/* Состояние съёма без беды - та же карточка, только зелёная: состояние
   называется всегда, а не только когда что-то сломалось. */
.issue.is-ok {
  --issue-tone: var(--issue-ok);
  background: color-mix(in srgb, var(--issue-ok) 7%, transparent);
}
/* Промежуток между карточками: без него скруглённые подложки смыкаются и
   читаются одним блоком, ради разделения которого карточка и заведена. */
.issue + .issue { margin-top: 6px; }

/* Замечание с предметикой раскрывается по клику: внутри пути дублей, имена
   файлов, недостающие поля. Свёрнуто по умолчанию - сначала читают список
   бед, потом открывают ту, с которой работают. */
details.issue > summary {
  display: flex;
  align-items: baseline;
  gap: 7px;
  cursor: pointer;
  list-style: none;
  transition: color var(--transition);
}
/* Счётчик прижат к правому краю панели (владелец): колонка чисел
   читается сверху вниз одним взглядом, а не скачет за длиной текста. */
details.issue > summary .issue-n { margin-left: auto; }
details.issue > summary::-webkit-details-marker { display: none; }
details.issue > summary:hover { color: var(--accent); }
/* Шеврон у правого края - единственный признак того, что строку можно раскрыть
   (поймано живым QA 2026-07-30): маркер списка снят, и строка с предметикой
   читалась как подпись под замечанием; о нажатии говорил только курсор.
   Поворот при открытии показывает, куда ведёт нажатие. */
details.issue > summary::after,
.issue-more > summary::after {
  content: '';
  flex: none;
  width: 5px;
  height: 5px;
  margin-left: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  opacity: .5;
  transform: rotate(-45deg);
  transition: transform var(--transition);
}
details.issue[open] > summary::after,
.issue-more[open] > summary::after { transform: rotate(45deg); }
.issue-n {
  flex: none;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--well);
  font: 600 10.5px/1.4 var(--mono);
  color: var(--fg-dim);
}
/* В плите сводки счёт без пилюли: Antnest несёт число текстом, тон прежний (`--fg-dim`, слово Ant04 24.08). */
#details .issue-sheet .issue-n { padding: 0; background: none; font: 500 12px/1.4 var(--font); font-variant-numeric: tabular-nums; }
/* СЧЁТЧИК ОДНОГО ТОНА В ЛЮБОМ СОСТОЯНИИ (#1600, слово Ant04 24.08: «сейчас
   перекрашен счётчик у ошибки, он всегда должен быть белым... нужно сделать даже
   не белые, а такие светло-серые»). Прежнее правило красило цифры акцентом при
   раскрытии блока, то есть признак «блок раскрыт» жил в ЦВЕТЕ ДАННЫХ. Признак
   остался у шеврона: он поворачивается на 90 градусов - `details.issue[open] >
   summary::after` выше, - и это единственный носитель состояния. */
.issue-list {
  margin: 6px 0 2px;
  padding-left: 14px;
  list-style: disc;
  color: var(--muted);
  font: 11.5px/1.5 var(--mono);
}
.issue-list li { margin: 2px 0; word-break: break-all; }

/* Та же предметика под полем формы правки. */
.issue-more { margin: 2px 0 6px; font-size: 11.5px; }
.issue-more > summary {
  display: flex;
  align-items: baseline;
  cursor: pointer;
  color: var(--muted);
  list-style: none;
}
.issue-more > summary::-webkit-details-marker { display: none; }
.issue-more > summary:hover { color: var(--accent); }
.issue-more ul {
  margin: 5px 0 0;
  padding-left: 14px;
  list-style: disc;
  color: var(--muted);
  font: 11px/1.5 var(--mono);
}
.issue-more li { word-break: break-all; }
/* Строка «und N weitere» сводки - та же ступень, что адреса над ней (#3702); имя класса общее с раскрывашкой выше. */
.issue-pages .issue-more { padding: 3px 8px; }

/* ── блоки замечаний в панели ──────────────────────────────
   Оба блока раскрыты всегда: они и есть причина открыть узел, а шеврон над
   ними добавлял бы щелчок к тому, что должно быть видно сразу (владелец,
   2026-07-28). Заголовок несёт счётчик тяжести и, у второго блока, кнопку
   перехода в правку. */
/* Карточки здесь несут СЕКЦИИ (SEO, контент, соответствие назначению) - это и есть
   смысловые предметы работы, как поля в листе текстов (владелец, 2026-08-06).
   Сам блок остаётся невидимой обёрткой: карточка вокруг карточек читалась бы
   матрёшкой на панели шириной в колонку. */
.dblock { margin-top: 8px; }


/* ── лист текстов ──────────────────────────────────────────
   Форма, а не витрина: тексты пишутся здесь и переносятся отсюда в CMS, поэтому
   поле - редактируемое, рядом длина и норма, а под ним то, что стоит на сайте
   сейчас (сравнивать приходится постоянно). */
/* Поле - отдельная карточка (владелец, 2026-08-06): один предмет работы, и
   заголовок, длина, само поле и то, что стоит на сайте, лежат внутри его рамки.
   Прежде поля шли одной лентой, и «какой сейчас заголовок» терялось между
   соседями. Карточка на ПОЛЕ, а не на группу: группа - это раздел из двух-трёх
   предметов, а правят по одному. Фон карточки - тот же --glass, что был у
   панели; поле ввода внутри светлее (--solid), поэтому не сливается с ней. */
.sheet-field {
  margin-top: 8px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-sm);
  padding: 10px 12px 12px;
}
.sheet-field:first-of-type { margin-top: 0; }
.sheet-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 5px;
  min-height: 24px;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
/* Кнопки поля описания читаются порознь (слово Ant04): без своей раскладки в шапке поля они встают вплотную;
   к правому краю шапки их прижимает `margin-left: auto` (Ant04 01.10, #3969). */
.sheet-head .sheet-acts { display: inline-flex; align-items: center; gap: 6px; margin-left: auto; }
/* The count is a VERDICT on the text, in the tones of the remarks (#1036-1; Ant04 29.09, #3766): empty red, short or long yellow,
   inside the corridor green. */
.len-now.is-empty { color: var(--issue-error); }
.len-now.is-short { color: var(--issue-warn); }
.len-now.is-ok { color: var(--issue-ok); }
/* «Unser | Website» at the right of a block head (#3920). */
.sheet-view-slot { margin-left: auto; align-self: center; font-weight: 400; }
.sheet-view { display: inline-flex; gap: 2px; padding: 2px; border-radius: 999px; background: var(--well); text-transform: none; letter-spacing: 0; }
.sheet-view button { padding: 1px 8px; border: 0; border-radius: 999px; background: none; color: var(--muted); font: 500 11px/1.4 var(--font); cursor: pointer; }
.sheet-view button.is-on { background: var(--fg-dim); color: var(--void); }
.sheet-site-view {
  padding: 7px 9px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
  word-break: break-word;
}

.sheet-field textarea {
  display: block;
  width: 100%;
  min-height: 46px;
  resize: vertical;
  background: var(--solid);
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--fg);
  padding: 9px 11px;
  font: 12.5px/1.5 var(--font);
}
/* Скрытое поле не занимает места: правило display у textarea перебивало
   [hidden] по специфичности, и предпросмотр вставал ВТОРЫМ окном под полем
   вместо того, чтобы его заменить (поймано 2026-07-30). */
.sheet-field textarea[hidden] { display: none; }
.sheet-field textarea:focus,
.sheet-field textarea:focus-visible { outline: none; }

/* A remark under its field on a soft ground of its own tone, no frame (variant «E», Ant04 29.09, #3764):
   a met rule struck through with its points back, a new one tagged «NEU» with its price. */
.sheet-issue {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin: 4px 0 0;
  padding: 4px 8px;
  border-radius: 6px;
  background: color-mix(in srgb, currentColor 10%, transparent);
  font-size: 12px;
  line-height: 1.45;
}
/* One line per remark (Ant04 29.09, #3766): a long name ends in dots, the full phrase is the tip; details open by the count
   «N ▾» at the right (variant «C»), page paths are links. */
.issue-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
details.sheet-issue { display: block; }
.issue-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; list-style: none; cursor: pointer; }
.issue-head::-webkit-details-marker { display: none; }
.issue-count { flex: none; padding: 0 7px; border-radius: 999px; background: rgb(255 255 255 / .12); color: var(--fg-dim); font-size: 11px; font-weight: 600; }
.issue-count::after { content: ' ▾'; }
details[open] > .issue-head .issue-count::after { content: ' ▴'; }
.issue-rows { margin: 4px 0 0; padding-left: 16px; color: var(--fg-dim); font-size: 11.5px; }
.issue-rows li { word-break: break-all; }
.issue-rows a { color: inherit; text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); }
.issue-new { padding: 0 5px; border-radius: 4px; background: var(--issue-error); color: var(--void); font-size: 10.5px; font-weight: 700; }
.sheet-issue.is-warn .issue-new { background: var(--issue-warn); }
.sheet-issue.is-error { color: var(--issue-error); }

/* Полоса «аудит эту страницу не судил» (#902-5). Стоит НАД формой, а не под
   полем: предмет у неё вся страница, и повторять её под каждым из пяти полей
   значило бы кричать пять раз об одном. Тон нейтральный, не красный: это не
   замечание аудита, а объяснение, почему замечаний нет. */
.sheet-gap {
  margin: 0 12px 8px;
  padding: 8px 10px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-left: 2px solid var(--muted);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 12px;
  line-height: 1.45;
}
.sheet-issue.is-warn { color: var(--issue-warn); }
/* «В норме» - такой же полновесный ответ, как замечание: без него правка идёт
   вслепую, и непонятно, поправил ты уже или ещё нет. */
.sheet-issue.is-ok { color: var(--issue-ok); }
/* Замечание снятого аудита рядом со свежей правкой: тусклее, потому что оно
   относится к прежней версии страницы. */
.sheet-issue.is-stale { opacity: .62; }
.sheet-issue.is-info { color: var(--issue-info); }

/* Own count is a pill right after the field name, its fill the tone of the verdict at 16% (Ant04 01.10, #3910, variant A
   with a smaller radius). */
.len-now {
  display: inline-flex;
  align-items: center;
  height: 17px;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 5px;
  background: color-mix(in srgb, currentColor 16%, transparent);
  color: var(--muted);
  font: normal 600 10.5px/1 var(--mono);
  letter-spacing: 0;
  text-transform: none;
}
.len-now[hidden] { display: none; }

/* Текущее значение с сайта - справочная строка под полем, не вторая форма. */
.sheet-site {
  margin: 5px 0 0;
  font: 11px/1.45 var(--mono);
  color: var(--muted);
  word-break: break-word;
}
.sheet-site b { color: var(--fg); font-weight: 500; }
/* Значение с сайта живёт ПОД раскрывашкой, поэтому своего отступа сверху
   ему там не нужно: его даёт сам `<details>`. */
.sheet-site-more > .sheet-site { margin-top: 0; }
/* Точка перед именем блока (#1005): совпадает наш текст с сайтом либо нет.
   Третьего состояния нет - там, где сравнивать нечего, точки нет вовсе, и
   решает это разметка, а не третий тон. Зелёный берётся у смысла «в норме,
   работы нет» (#1042), фиолетовый - у единственного фиолетового палитры, где он
   и значит «страница ждёт нашей работы». */
.sync-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 6px;
  border-radius: 50%;
  vertical-align: middle;
  background: currentcolor;
}
.sync-dot.is-same { color: var(--status-ok); }
.sync-dot.is-off { color: var(--copy-wait); }

/* ── вкладка характеристик (#1326-4) ─────────────────────
   Строка на ОДНУ линию, а не карточка: пар на боевой странице до 41 (замер
   20.08), и карточка с полем в 12px внутреннего поля дала бы панель втрое выше
   экрана. Читаемость держат три решения: имя и значение стоят ДВУМЯ КОЛОНКАМИ,
   как в самой таблице на сайте; значение с сайта печатается только при
   расхождении; плита у списка ОДНА - строки разделяют линии, а не воздух.
   Замер той же страницы: подпись над полем давала 74 px на строку и 3171 px
   прокрутки, две колонки - 44 px и 1944 px при 936 видимых. */
.sheet-tab[hidden] { display: none; }
/* The table sits inside a SEO Content block, on the field tone of its cards (Ant04 01.10, #3970, variant B). */
.prop-list {
  background: rgba(255, 255, 255, .03);
  border-radius: var(--radius-sm);
  padding: 2px 10px;
}
/* Имя слева, значение справа - тем же порядком, каким пара стоит на самой
   странице. Ширина имени долей, а не пикселями: панель живёт в двух ширинах
   (обычной и развёрнутой), и фиксированная колонка в развороте оставила бы
   значению полосу вдвое короче нужной. */
.prop-row {
  display: grid;
  grid-template-columns: minmax(0, 38%) minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 8px;
  padding: 5px 0;
}
.prop-row + .prop-row { border-top: 1px solid var(--well); }
.prop-name {
  display: flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
  font: 11.5px/1.35 var(--font);
  color: var(--muted);
}
.prop-title { min-width: 0; overflow-wrap: anywhere; }
/* Значок связи прижат к ПРАВОМУ краю колонки имени (#1344): значки выстраиваются
   в столбец, и «что уже связано» читается одним взглядом. Размер меньше иконок
   действий - он не нажимается, а сообщает. */
.prop-link {
  flex: none;
  margin-left: auto;
  width: 14px;
  height: 14px;
  color: var(--muted);
  opacity: .55;
}
/* Тона: связь живая - обычный тихий, ещё нет - тот же тихий (это нормальный ход
   работы, а не беда), разрыв - жёлтый «требует внимания», тот же язык, что у
   строки расхождения структуры. Ярче остальных ровно то состояние, где человеку
   надо принять решение. */
.prop-link.is-live { opacity: .85; }
.prop-link.is-lost { color: var(--status-gap); opacity: 1; }
/* Строка под значением идёт ПОД ОБЕИМИ колонками: она про пару целиком. */
.prop-note, .prop-row [data-clash] { grid-column: 1 / -1; }
/* Кнопка снятия тише кнопок формы: их на экране сорок одна, и плита под каждой
   читалась бы рядом опасных действий. Плита появляется под курсором - там, где
   человек уже целится. */
.prop-drop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  flex: none;
  background: none;
  border: 0;
  border-radius: 7px;
  color: var(--muted);
  opacity: .5;
  cursor: pointer;
  transition: background var(--transition), color var(--transition), opacity var(--transition);
}
.prop-drop .ico { width: 13px; height: 13px; }
.prop-drop:hover { background: var(--hover); color: var(--fg); opacity: 1; }
/* Помеченная к снятию читается тоном опасности, а не только нажатой кнопкой:
   нажатость видна, пока смотришь на кнопку, а решение - про строку целиком. */
.prop-drop.is-on, .prop-drop.is-on:hover { background: var(--danger); color: var(--accent-fg); opacity: 1; }
.prop-row:has(.prop-drop.is-on) .prop-input { text-decoration: line-through; opacity: .6; }
/* ПРЕДЕЛ РОСТА ПОЛЯ - ШЕСТЬ СТРОК, и он живёт здесь, а не в коде: число строк
   это раскладка панели. Замер боевого прогона 22.08 (3347 характеристик):
   медиана значения 5 знаков, p95 = 64, p99 = 181, максимум 497. В строке поля
   помещается около 55 знаков, то есть шесть строк накрывают p99 с запасом, а
   прокрутка внутри поля остаётся у 15 значений из 3347 - тех, что длиннее 240
   знаков. Высоту по содержимому в этих границах ставит `growProp` в
   `app/sheet.js`; ручную растяжку снимаем - она разошлась бы с ней молча. */
.prop-input {
  display: block;
  width: 100%;
  background: var(--solid);
  border: 0;
  border-radius: 9px;
  color: var(--fg);
  padding: 6px 10px;
  font: 12.5px/1.4 var(--font);
  max-height: calc(6 * 1.4em + 12px);
  overflow-y: auto;
  resize: none;
}
.prop-input:focus, .prop-input:focus-visible { outline: none; }
.prop-note:empty { display: none; }
/* The state under a pair is a short chip, not a line of text (#3970, B). */
.prop-note .sheet-site {
  display: inline-block;
  margin: 2px 0 0;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--well);
  font-size: 11px;
}
.prop-note .issue-more { margin: 0; }

/* Поля выровнены по ВЕРХУ, а не по центру: значение новой пары растёт вниз, и
   при выравнивании по центру кнопка и имя уезжали бы вместе с ним. */
.prop-add-row { display: flex; gap: 6px; align-items: flex-start; }
.prop-add-row .prop-input { flex: 1 1 0; min-width: 0; }
/* Форма добавления - СВОЙ блок под списком (#3970, B): она не строка списка, и стоять между парами ей нечего. */
.prop-add .prop-add-row {
  background: rgba(255, 255, 255, .03);
  border-radius: var(--radius-sm);
  padding: 8px 10px;
}
.prop-add-row .btn { flex: none; }
.prop-add-note:empty { display: none; }

.btn-inline {
  margin-left: 6px;
  padding: 3px 8px;
  background: var(--well);
  border: 0;
  border-radius: 8px;
  color: var(--fg);
  font: 500 11px/1 var(--font);
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.btn-inline:hover { background: var(--accent); color: var(--accent-fg); }

/* ── рама правки: одна форма, два дома ─────────────────────
   Форма живёт в правой панели и переезжает целиком в окно на весь экран по
   кнопке «развернуть» (владелец, 2026-07-30). Элемент один и тот же: второго
   рендера нет, набранный и несохранённый текст переезд переживает. */
.sheet-frame {
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* Минимум flex-элемента равен содержимому, поэтому без нуля шапка растягивает
     рамку шире панели и выносит форму за экран. */
  min-width: 0;
  flex: 1;
}
/* `display` СИЛЬНЕЕ атрибута `hidden`: правило выше перебивает браузерное
   `[hidden] { display: none }`, и до #996-9 это было безвредно - панель
   пряталась целиком. Теперь у панели два жильца, и без этой строки заголовок
   формы стоял поверх статистики (найдено живым прогоном 13.08). */
.sheet-frame[hidden] { display: none; }
/* Рамка статистики - второй жилец правой панели (#996-9). Устроена как рамка
   формы: та же шапка, то же поле у содержимого, - иначе переключение между
   ними читалось бы как переход в другое окно. */
.stats-frame {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1;
}
.stats-frame[hidden] { display: none; }
.stats-body { padding: 12px; overflow-y: auto; }
/* ── карточки правой панели (#996-8, кривые с #1045) ───────
   Язык один на все графики, и живёт он в двух местах по разделению труда:
   геометрия и палитра кривых - в `app/panel.js` рядом с их сборкой (там же
   довод), здесь только раскладка карточки и типографика. */
.stat-card {
  margin-top: 8px;
  padding: 10px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-md);
}
/* Первая карточка стоит у самого верха панели (#1052, слово Ant04: «подними
   блоки к самому верху»): заголовок панели снят, и отступ над ней держал бы
   пустую полосу на его месте. */
.stat-card:first-child { margin-top: 0; }
/* Шапка карточки - строка с иконкой информации у правого края: под
   иконкой лежат легенда и оговорка. */
.stat-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 6px;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
.stat-head span { flex: 1; min-width: 0; }
.stat-info { margin-left: auto; }
.stat-legend { list-style: none; margin: 0; padding: 0; }
.stat-legend li {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 2px 0;
  font-size: 11.5px;
  color: var(--muted);
}
.stat-legend i { width: 7px; height: 7px; flex: none; border-radius: 2px; }
/* Соседнее число - не серия графика, и это видно отступом вместо метки плюс
   чертой над группой: она отделяет «на кривой» от «рядом с кривой». */
.stat-legend li.is-aside { padding-left: 14px; color: var(--muted); }
.stat-legend li.is-aside + li.is-aside { padding-top: 0; }
.stat-legend li.is-aside:first-of-type { margin-top: 4px; border-top: 1px solid var(--line); padding-top: 6px; }
.stat-legend span { flex: 1; min-width: 0; }
.stat-legend b { font: 12px/1 var(--mono); color: var(--fg); font-weight: 500; }
.stat-note { margin: 8px 0 0; font-size: 11px; }
/* Легенда живёт и ВНУТРИ окна подсказки: дом стиля тот же, здесь только
   отступ от заголовка окна. */
.tooltip .stat-legend { margin-top: 5px; }
/* Кривые карточек (#1035, модель второго референса Ant04; #1045 - остальные три
   прибора). Геометрия и раскладка точек живут в `app/panel.js` рядом со сборкой,
   здесь начертание.
   Тон приходит ОДНОЙ инлайновой строкой `color` на самой картинке - линия,
   свечение, заливка и точки берут его через `currentColor`, поэтому дом тона
   один и правится он в панели вместе со смыслом серии. */
.trend-box { margin-bottom: 6px; }
.trend-label {
  margin: 0;
  font-size: 11px;
  color: var(--muted);
}
.trend-value { display: flex; align-items: baseline; gap: 8px; margin: 1px 0 4px; flex-wrap: wrap; }
.trend-num { font: 600 26px/1 var(--font); color: var(--fg); }
/* Крупное число реконструированного дня несёт ту же пунктирную черту, что подпись
   оси: пометка обязана работать и там, где число стоит отдельно от своей точки. */
.trend-num.is-rebuilt { border-bottom: 1px dotted var(--placeholder); }
/* Чип дельты: подложка считается ИЗ тона строки, поэтому смысловой тон стоит в
   одном месте и не повторяется цифрами (то же правило, что у свечения акцента). */
.trend-delta {
  padding: 3px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, currentcolor 14%, transparent);
  font: 500 11px/1 var(--font);
  white-space: nowrap;
}
.trend { display: block; overflow: visible; }
/* Линий сетки у карточки НЕТ (слово Ant04): высоту называет
   крупное число и подсказка дня. */
.trend-line {
  fill: none;
  stroke: currentcolor;
  stroke-width: 2;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  filter: drop-shadow(0 0 5px currentcolor);
}
/* Заливка берёт СВОЙ градиент атрибутом на самой фигуре: кривых на панели семь, у
   каждой свой id (правило CSS с одним `url(#...)` увело бы все заливки в первый
   объявленный градиент, то есть в тон верхней карточки). */
.trend-fill { stroke: none; }
/* ТОЧЕК НА КРИВОЙ НЕТ с #2395 (слово Ant04 «убери точки из графика»): вместе с
   ними ушло начертание реконструкции, её метки - крупное число и окно дня. */
/* Курсор ОБЫЧНЫЙ, а не `help` (слово Ant04 18.08: «показывается курсор, знак
   вопроса»): вопрос стоял у нативной подсказки, а её здесь больше нет. */
.trend-hit { fill: transparent; cursor: default; }
/* ПОДСВЕТКА ДНЯ держится `:hover` группы, а не классом от обработчика: показывает
   её и окно одно и то же наведение, поэтому разойтись они не умеют. Нить
   отвечает «какой день», точки на каждой кривой - «какое значение у ряда». */
.trend-thread { stroke: var(--fg); stroke-width: 1; vector-effect: non-scaling-stroke; opacity: 0; }
.trend-mark { fill: currentcolor; opacity: 0; }
.trend-day:hover .trend-thread { opacity: .28; }
.trend-day:hover .trend-mark { opacity: 1; filter: drop-shadow(0 0 6px currentcolor); }
/* Подписи оси - ДНИ НЕДЕЛИ по равным долям: место на оси занимает день
   недели, а не строка данных, и проценты берутся от того же viewBox. */
.trend-axis { position: relative; height: 12px; margin-top: 2px; }
.trend-axis .day-tick { position: absolute; transform: translateX(-50%); white-space: nowrap; }
.day-tick { font: 10px/1 var(--mono); color: var(--muted); }
/* Сегодняшний день назван ПОДПИСЬЮ, а не точкой: крупное число карточки его же,
   и без метки читатель ищет, к какому дню оно относится. */
.day-tick.is-today { color: var(--fg); }
/* Прошлая неделя ПОЗАДИ: тонкая линия без свечения. Свечение делает серую кривую
   такой же заметной, как цветная, и «задний план» перестаёт быть задним. */
.trend-back .trend-line { stroke-width: 1.4; filter: none; opacity: .75; }
/* Ответ сервиса под раскрывашкой (#1032). Один вид на все три места показа:
   экран выбора, карточка графика, полоса съёма. Моноширинный и приглушённый -
   это машинный текст, и выглядеть он обязан машинным, иначе человек прочтёт
   русскую строку как обращение к себе. Перенос по словам обязателен: сообщения
   сервиса бывают в строку длиннее панели. */
.fail-more { margin-top: 6px; font-size: 11px; color: var(--muted); }
.fail-more > summary { cursor: pointer; }
.fail-more pre {
  margin: 4px 0 0;
  padding: 6px 8px;
  background: var(--well);
  border-radius: var(--radius-sm);
  font: 11px/1.4 var(--mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Шапка панели: имя раздела слева, состояние записи и возврат - справа
   (владелец, 2026-08-06). Нижняя полоса убрана, и всё, что было в ней, стоит
   здесь; своего поля у шапки нет - его дают карточки под ней. */
.sheet-top {
  display: flex;
  align-items: center;
  /* Тесно - ряд действий уезжает на ВТОРУЮ СТРОКУ: втроём вкладки,
     состояние и кнопки требуют 460 px при 420 в панели. */
  flex-wrap: wrap;
  gap: 8px;
  /* Боковых полей нет: плашка вкладок стоит вровень с левой кромкой карточек (Ant04 28.09,
     #3663), кнопка сброса - с правой (владелец, 2026-08-06). */
  padding: 0 0 8px;
}
.sheet-acts-top {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 1 1 auto;
  justify-content: flex-end;
}
.sheet-acts-top .sheet-state { flex: 0 1 auto; min-width: 0; }
/* The status sits on a glass chip: bare text drowned in the scene's stars and lines (Ant04 01.10, #3887 variant A). */
.sheet-acts-top .sheet-state:not(:empty) {
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
}
/* Вкладки держат свою ширину, тянется ряд справа: при `flex: 1` базис равен
   нулю, и длинная строка состояния (до 84 знаков) отдавала заголовку 0 px - имя
   раздела ломалось на две строки и печаталось поверх состояния. Правило было
   написано вторым домом выше по файлу и перебивалось этим блоком, то есть не
   работало вовсе (найдено ревью 2026-08-06). Вкладки унаследовали его вместе с
   местом (#1326-4).

   Форма переключателя ОДНА на приложение - `.seg` с `.seg-btn` (#1326-4): вид
   экрана в шапке и предмет работы в панели это один вопрос «что я сейчас
   смотрю», и вторая форма вкладок была бы вторым домом этого языка. Отличается
   РАНГ, а не устройство: главный переключатель экрана носит акцентную пластину,
   вкладки панели - тихую, иначе два акцента в одном кадре спорят за взгляд. */
.seg.is-tabs {
  flex: 0 0 auto;
  /* Плашка вкладок лежит прямо на сцене и размывает её, как карточки под ней (слово Ant04 28.09). */
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
}
.seg.is-tabs .seg-btn {
  /* Ниже шапочного: панель прокручивается уже сегодня (1027 против 955 видимых),
     и полный `--control-h` отнял бы у текста ещё 12 px на каждом открытии. */
  height: 24px;
  padding: 0 7px;
  gap: 6px;
  font: 500 11.5px/1 var(--font);
}
.seg.is-tabs .ico { width: 15px; height: 15px; }
/* Имя только у открытой вкладки, у закрытой - иконка с подсказкой (#3663). */
.seg.is-tabs .seg-label { display: none; }
.seg.is-tabs .seg-btn[aria-pressed="true"] .seg-label { display: inline; }
.seg.is-tabs .seg-btn[aria-pressed="true"] {
  padding: 0 11px 0 9px;
  background: var(--hover);
  color: var(--fg);
  box-shadow: none;
}
/* Ключ страницы - свой блок, как поля формы, и стоит первым: под него пишут
   тексты. Блок обязательный: пустой ключ это не отсутствие данных, а работа,
   которую надо сделать, поэтому на его месте стоят замечания раздела relevance. */
.sheet-key-block {
  margin-bottom: 8px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-sm);
  padding: 10px 12px 12px;
}
/* Подпись этого блока стоит ВНУТРИ карточки, а не между ними, поэтому своего
   бокового поля не получает: у остальных разделов оно есть, и здесь сложилось
   бы с полем самой карточки (владелец, 2026-08-06 - заголовок съехал вправо). */
.sheet-key-block .sheet-group-head { padding: 0; }
/* Балл стоит СБОКУ от всего блока, а не в строке подписи (владелец, 2026-08-06):
   в строке он растягивал её по своей высоте, и между заголовком и ключом
   зияла дыра. Теперь колонка «подпись плюс ключ» и круг шкалы - соседи. */
.sheet-key-block { display: flex; align-items: center; gap: 12px; }
/* ЗАМОК НА КЛЮЧЕ (#1601, переехал в поле #1792 словом Ant04 31.08.2026 «поставить
   прямо перед ключом в поле инпута»). Кнопка стоит ВНУТРИ рамки ввода слева, поле
   отступает на её ширину. Прежде она жила в строке подписи блока, и довод был
   «рядом с полем спорила бы за клик»: он снят решением владельца, а спор за клик
   закрыт геометрией - кнопка занимает свои 22 пикселя, поле начинается за ними.
   Состояния ДВА и различаются формой значка (дужка) плюс тоном, а не одним
   тоном: разница только в цвете невидима при беглом взгляде и недоступна
   дальтонику. */
.key-lock {
  position: absolute;
  left: 5px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px; height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg-dim);
  cursor: pointer;
}
.key-lock .ico { width: 15px; height: 15px; }
.key-lock:hover { color: var(--fg); background: var(--well); }
.key-lock.is-on { color: var(--accent); }
.key-lock:disabled { opacity: .5; cursor: default; }
/* Запертое поле не редактируется, и это видно ДО попытки: тон гаснет, курсор
   говорит «не сюда». Правило на весь блок, а не на input: `readonly` у поля
   стоит от разметки, а вид у него общий с соседями. */
.sheet-key-block.is-locked .sheet-key-input {
  color: var(--fg-dim);
  cursor: default;
}
.sheet-key-main { flex: 1; min-width: 0; }
/* ЗАГОЛОВОК БЛОКА ТОНА НЕ НЕСЁТ (#1056, Ant04 14.08): подпись называет предмет,
   а не состояние, и окрашенная читалась как значение. Сигнал «ключа нет» при
   этом не пропал - под полем стоит либо замечание relevance, либо строка
   `sheet.key.none`, и обе идут тоном `.sheet-issue.is-warn`. Соседние блоки
   формы уже устроены так же: там окрашены счётчик и точка, а не подпись. */

/* Ключ ПОДБИРАЕТСЯ полем ввода (#997): вид тот же, что у полей формы, - подпись
   серым капсом, под ней поле. */
/* Частотность лежит В ПОЛЕ, справа (#1056). Абсолютом, а не соседом в строке:
   поле держит ширину блока, и вынесенное число отняло бы у текста ключа тем
   больше, чем длиннее сама частотность. Клики сквозь него проходят - иначе
   нажатие по правому краю поля не открывало бы панель. */
.sheet-key-pick { position: relative; --key-core-gap: 6px; }
/* ВОЗДУХ ВОКРУГ КНОПКИ ОДИН СО ВСЕХ ТРЁХ СТОРОН (#2130, Ant04 04.09 13:10:
   «отступа стикера Core снизу, сверху и справа стоит выровнять»). Держится он
   растяжкой группы, а не подобранной высотой: высота поля дробная (12,5 px при
   1,35 плюс отступы), и заданное число разъезжалось бы при смене шрифта. */
.sheet-key-pick .key-side {
  position: absolute;
  right: var(--key-core-gap);
  top: var(--key-core-gap);
  bottom: var(--key-core-gap);
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}
/* Тянется ОДНО гнездо кнопки, а не все дети группы: частотность рядом обязана
   стоять по центру, а `flex` внутри нужен потому, что кнопка живёт под `span`. */
.sheet-key-pick .key-side > [data-key-core] { display: flex; align-self: stretch; }
/* Пустое гнездо убирается совсем: флекс держит зазор и у нулевой ширины, и
   число уезжало бы от края поля на эти 6 px там, где кнопки нет. */
.sheet-key-pick .key-side > [data-key-core]:empty { display: none; }
.sheet-key-input {
  display: block;
  width: 100%;
  background: var(--solid);
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--accent);
  padding: 8px 11px;
  font: 500 12.5px/1.35 var(--font);
}
/* Room for the volume, else a long key slides under it: 66px was sized 17.08 by the longest volume then («165.000»).
   Left, the same trick frees room for the lock (#1792): 32px is the 22px button plus gaps. Why: docs/history/2026-09-26-progon-comments.md. */
.sheet-key-input { padding-right: 66px; padding-left: 32px; }
.sheet-key-input:focus, .sheet-key-input:focus-visible { outline: none; }
.sheet-key-input::placeholder { color: var(--muted); opacity: 1; font-weight: 400; }
/* Панель выбора ключа (#1019-1). Дропдауна под полем больше нет: он падал ПОД
   соседние блоки и просвечивал насквозь, и оба дефекта ушли вместе со сменой
   места. Панель стоит слева от правой, во всю рабочую высоту - ту же, что у
   обеих панелей (кромка экрана здесь не годится: панели держат общий отступ, и
   плита вплотную к краю ломала бы ряд).

   Раскладку она не двигает - сдвиг перекладывал бы всю страницу ради выбора
   одного значения.

   ВЫСОТА С #1027-3 - полная, как у обеих панелей (слово владельца отменяет
   прежнее «начинается под шапкой»: отступ сверху он назвал слишком большим).
   Столкновение с кнопками шапки решено СЛОЕМ (#1035-3, слово Ant04 13.08
   отменяет прежний сдвиг шапки): z-index 6 против 5 у шапки, поэтому панель
   красит поверх неё И ЛОВИТ КЛИК. Второе здесь важнее первого: у самой шапки
   `pointer-events: none`, но её кнопки клик ловят, и панель слоем ниже пускала
   бы нажатие сквозь себя - человек, целясь в строку списка, запускал бы съём.
   Замер живым прогоном 13.08: панель 812..1152, кнопки шапки 519..1080.
   Недоступность перекрытых инструментов на это время - решение владельца, а не
   дефект: прятать их не нужно.

   Поверхность по канону Antnest: панель лежит НАД страницей, значит
   `--glass-strong`, а не `--glass`; saturate рядом с blur обязателен - без него
   цвет под стеклом уходит в серость. Ровно прозрачность и была второй половиной
   жалобы владельца. */
.panel-keys {
  position: absolute;
  z-index: 6;
  top: var(--gutter);
  right: calc(var(--col-right) + 2 * var(--gutter));
  width: var(--key-w);
  height: calc(100% - 2 * var(--gutter));
  display: flex;
  flex-direction: column;
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius);
  box-shadow: var(--top-glow), 0 24px 64px rgba(0, 0, 0, .55);
}
.panel-keys[hidden] { display: none; }
.key-panel-top {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 10px 6px 16px;
}
.key-panel-head {
  flex: 1;
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
}
/* Адрес страницы в панели: выбор идёт для КОНКРЕТНОЙ звезды, а панель стоит
   поодаль от поля, и без адреса не видно, чей ключ сейчас правится. */
.key-panel-path {
  margin: 0;
  padding: 0 16px 8px;
  font: 11px/1.3 var(--mono);
  color: var(--muted);
  overflow-wrap: anywhere;
}
/* Стоящий ключ (#1027-1) стоит НАД списком и вне его прокрутки: он отвечает на
   вопрос «меняю или ставлю первый», и ответ обязан быть виден при любом
   сужении. Тон обычный, не приглушённый, - это значение, а не подпись. */
.key-panel-now {
  margin: 0;
  padding: 0 16px 10px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--fg);
  overflow-wrap: anywhere;
}
.key-panel-body {
  flex: 1;
  overflow-y: auto;
  padding: 0 8px 12px;
}
/* This page's keys and the pool split by one hairline `--line`, the `.ctl-group` device (#3658). */
.key-list { list-style: none; margin: 0; padding: 0; }
.key-list + .key-list { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--line); }
.key-row { display: flex; align-items: center; border-radius: 5px; }
/* Подсветка строки СВЕТЛЕЕ фона, а не темнее: панель уже стоит на
   `--glass-strong`, и `--glass` поверх неё читался бы как нажатие. */
.key-row:hover, .key-row:focus-within { background: var(--hover); }
.key-opt {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  border-radius: 5px;
  padding: 3px 4px 3px 8px;
  color: var(--fg);
  font: 12px/1.35 var(--font);
  text-align: left;
  cursor: pointer;
}
.key-opt:focus-visible { outline: none; }
.key-opt-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.key-opt-tier { font: 10px/1 var(--mono); color: var(--muted); flex: none; width: 28px; }
.key-opt-role { flex: none; width: 10px; color: var(--accent); text-align: center; }
/* Niche mark as a dot left of the phrase: as a text line it took a second line from every other key. */
.key-opt-niche { flex: none; align-self: center; width: 6px; height: 6px; border-radius: 50%; background: var(--muted); }
/* The actions keep their place at rest (visibility, not display): a row that grows under the cursor jumps. */
.key-acts { display: flex; flex: none; gap: 2px; padding-right: 4px; visibility: hidden; }
.key-row:hover .key-acts, .key-row:focus-within .key-acts { visibility: visible; }
.key-act {
  width: 22px; height: 22px;
  background: var(--well);
  border: 0;
  border-radius: 5px;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
}
.key-act:hover, .key-act:focus-visible { color: var(--fg); outline: none; }
.key-act:disabled { visibility: hidden; }
/* Частотность моноширинная и правым краем: числа сравнивают столбцом. */
.key-opt-vol { font: 11px/1 var(--mono); color: var(--muted); flex: none; }

/* КНОПКА CORE стоит ПОСЛЕ частотности, у правого края поля (#2130, решение
   Ant04 04.09 13:10): состояние у неё одно - фразы в ядре нет и клик заведёт её.
   Тон берётся каналом акцента, копии hex рядом нет (#1579). */
.key-core {
  display: inline-flex;
  align-items: center;
  font: 10px/1 var(--mono);
  letter-spacing: .04em;
  color: var(--accent-fg);
  background: var(--accent);
  border: 0;
  border-radius: 999px;
  padding: 0 8px;
  cursor: pointer;
  pointer-events: auto;
}
.key-core:disabled { opacity: .5; cursor: default; }
.key-opt-tag { font: 10px/1 var(--mono); flex: none; }
.key-opt-tag.is-taken { color: var(--warn); }
/* Строка стоящего ключа: лунка под ней. Цветом строку не красим - в списке уже
   есть предупреждающий тон занятости, и второй цвет читался бы как вторая беда. */
.key-opt.is-current { background: var(--well); }
.key-empty { margin: 0; padding: 8px; color: var(--muted); font-size: 12px; }
.sheet-key-note { margin: 6px 0 0; font-size: 11px; color: var(--muted); }
.sheet-key-note[data-writer-revision]:empty { display: none; }
/* Key card footer: who set the key on the left, the writer revision on the right; an empty footer takes no room. */
.sheet-key-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2px 12px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}
.sheet-key-foot .sheet-key-note { margin: 0; }
.sheet-key-foot:not(:has(.sheet-key-note:not(:empty))) { display: none; }



/* Кнопка в шапке поля: тот же язык, что у прочих иконочных кнопок интерфейса. */
.btn-plain {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  flex: none;
  background: var(--well);
  border: 0;
  border-radius: 9px;
  color: var(--muted);
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.btn-plain:hover { background: var(--hover); color: var(--fg); }
.btn-plain[hidden] { display: none; }

/* Панель правки не прокручивается целиком: скроллится только тело формы, а
   шапка с именем раздела и подвал с кнопкой сохранения остаются на месте. */
/* display у id перебивает `.panel-right[hidden]` по специфичности, поэтому
   скрытие объявляется здесь же - иначе пустая плита висит поверх сцены. */
#copy-panel { padding: 0; display: flex; overflow: hidden; }
#copy-panel[hidden] { display: none; }
/* Тело формы прокручивается само: рама держит шапку и подвал на месте. */
.sheet-body {
  flex: 1;
  min-height: 0;
  /* Боковых полей нет (владелец, 2026-08-06): их держат карточки, а общий отступ
     тела сдвигал блоки относительно заголовка панели. Снизу поле остаётся -
     последняя карточка не должна упираться в кромку экрана. */
  padding: 0 0 14px;
  overflow-y: auto;
}
/* Короткие поля идут лентой, а в растянутой панели - по два в ряд. Порог по
   ширине самой панели (container query), а не окна: решает то место, где поля
   стоят. Описание в сетку не входит - оно всегда ниже и во всю ширину. */
#copy-panel { container-type: inline-size; }
/* Зазор между карточками задаёт сетка: собственный margin поля складывался бы с
   `gap` и разводил соседей на разное расстояние. */
.sheet-meta { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
.sheet-meta > .sheet-field { margin-top: 0; }
.sheet-big { margin-top: 6px; }
#copy-panel .sheet-field.is-big textarea { min-height: 120px; }
@container (min-width: 720px) {
  .sheet-meta { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  #copy-panel .sheet-field.is-big textarea { min-height: 300px; }
}

/* A field group says WHERE its text is seen (search results or the page); without it five fields read as one list.
   Since #3887 (variant A) the group is its own glass surface with the title inside, its field cards drop their own blur. */
.sheet-group {
  padding: 8px;
  border-radius: var(--radius-md);
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
}
.sheet-group + .sheet-group { margin-top: 12px; }
.sheet-group .sheet-field {
  background: var(--well);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.sheet-group-head {
  padding: 4px 6px 2px;
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 4px;
  font: 600 12px/1.3 var(--font);
  color: var(--fg);
}
.sheet-group-sub { margin-left: auto; font: 400 11px/1.3 var(--font); color: var(--muted); white-space: nowrap; }
/* A feed field folds into its name row while empty; the limits sit in the i tip, the fold sign at the right (#3970, B). */
.feed-sum { cursor: pointer; list-style: none; margin-bottom: 0; }
.feed-sum::-webkit-details-marker { display: none; }
.feed-sum::after { content: '+'; margin-left: auto; font: 400 15px/1 var(--font); color: var(--muted); }
.feed-field[open] > .feed-sum { margin-bottom: 5px; }
.feed-field[open] > .feed-sum::after { content: '\2212'; }
.feed-info {
  display: inline-grid; place-items: center;
  width: 15px; height: 15px;
  border: 1px solid var(--line); border-radius: 50%;
  font: 600 9.5px/1 var(--font); letter-spacing: 0; text-transform: none;
  color: var(--muted); cursor: help;
}

/* In the unfolded description the group surface goes, as the field card does: the text takes the panel whole. */
#copy-panel.is-solo .sheet-group {
  padding: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Разворот описания на всю панель: остальные группы прячутся, поле растёт по
   высоте панели - абзацы набирают во весь рост, а не в щель под мета-полями. */
#copy-panel.is-solo .sheet-group-head,
#copy-panel.is-solo .sheet-meta,
/* Блок ключа уходит целиком - вместе с баллом здоровья (владелец, 2026-08-06):
   в развороте на экране только текст и то, что стоит на сайте, всё остальное
   отвлекает и отнимает высоту. */
#copy-panel.is-solo .sheet-key-block,
#copy-panel.is-solo [data-tab-body="copy"] .sheet-group:not(:has(.sheet-big)) { display: none; }
/* В развороте прокручивается СОДЕРЖИМОЕ полей, а не панель: поле занимает всю
   высоту, и прокрутка самой панели уводила бы его за край экрана. */
#copy-panel.is-solo .sheet-body { display: flex; flex-direction: column; }
/* Тело вкладки - звено цепочки высот: без него поле кода стоит в rows. */
#copy-panel.is-solo .sheet-tab:not([hidden]) { flex: 1; min-height: 0; display: flex; flex-direction: column; }
#copy-panel.is-solo .sheet-group:has(.sheet-big) { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* В развороте карточка снимается: поле занимает панель целиком, и рамка вокруг
   него отнимала бы у текста и высоту, и ширину. */
#copy-panel.is-solo .sheet-field {
  background: none;
  padding: 0;
  margin: 0;
  /* Блюр снимается вместе с заливкой: без этого прозрачная карточка оставляет
     во весь экран композитный слой, размывающий сцену. */
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
#copy-panel.is-solo .sheet-group + .sheet-group { margin: 0; }

/* Сравнение с сайтом в развороте описания (владелец, 2026-07-31): слева то,
   что стоит на сайте сейчас, справа то, что пишем. Вне разворота левой колонки
   нет вовсе - в панели 420 px две колонки дали бы меру строки вдвое короче
   читаемой, а мера и есть то, ради чего разворот делался. */

/* В развороте кнопки поля уезжают в правый верхний угол панели (владелец,
   2026-07-31): в шапке поля они стояли над правой колонкой и читались её
   заголовком, хотя относятся ко всему полю. Угол - единственное место, где
   они не спорят ни с одной из двух колонок. */

/* Развёрнутое описание идёт и на всю ВЫСОТУ окна (владелец, 2026-07-31): по
   ширине панель уже во весь экран, и обрезанная снизу колонка заставляла бы
   прокручивать то, что помещается. Поля по кромкам те же, что у всех плит. */
/* Затемнение на время разворота описания (#902-1, Ant04 12.08.2026; слой и сила
   исправлены #902-12, слово владельца: «затемняется не только фон, но и сами
   блоки, текст и кнопки... затемнение должно быть самым нижним слоем и не
   перекрывать элементы»).
   Плёнка одна, псевдоэлементом на корне: отдельный узел разметки пришлось бы
   создавать и снимать руками, а класс переживает любую перерисовку формы.
   Плёнка висит на `.app`, а НЕ на `body`, и это рубеж, а не вкус: `.app` стоит
   `position: fixed`, то есть заводит свой контекст наложения, и плёнка на body
   оказывалась ЕГО СОСЕДОМ - при любом своём z-index она красила всё дерево
   приложения целиком, а поднятая панель правки из-под неё не выбиралась, потому
   что её слой считается внутри другого контекста. Ровно это владелец и увидел
   12.08: «затемняется не только фон, но и сами блоки, текст и кнопки».
   Слой 2 внутри `.app` - выше сцены (1) и ниже ВСЕГО интерфейса (панели 4,
   шапка 5): гаснет только то, что за панелями. Прежний подъём
   `.is-solo-dim #copy-panel` до 7 снят вместе с причиной - он ничего и не решал.
   Нажатия плёнка НЕ ловит: разворот снимается своей кнопкой, а перехвати она
   клик - схема потеряла бы зум и перетаскивание у человека, который просто
   отвёл курсор. */
.is-solo-dim .app::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  /* .80 (владелец разрешил сильнее): предел в том, чтобы
     сцена за плёнкой ещё читалась сценой, а не чёрным прямоугольником. */
  background: rgba(0, 0, 0, .8);
  pointer-events: none;
}

#copy-panel.is-solo {
  top: var(--gutter);
  bottom: var(--gutter);
  max-height: none;
}
/* Тела колонок тянутся за сеткой, а не задают высоту сами: два разных
   calc-потолка разъезжались бы на любой смене шапки или подвала. */
#copy-panel.is-solo :is(.sheet-rich, .sheet-live-body) {
  min-height: 0;
  max-height: none;
}
/* Высота идёт ЦЕПОЧКОЙ от панели к полю, а не константой (найдено ревью
   2026-08-06). Прежний потолок `100vh - 190px` складывался из шапки со своим
   полем и подвала - подвал сняли этим же коммитом, поле шапки обнулили, а число
   осталось: под полем висело 70 px мёртвой высоты, то есть три-четыре абзаца на
   экран. Цепочка не требует пересчёта при следующей смене шапки.
   Мера ОДНА на все три вида поля (превью, редактор, разметка): два разных
   потолка разъезжаются при первом же переключении, и колонки перестают стоять
   строка в строку. */
#copy-panel.is-solo .sheet-big,
#copy-panel.is-solo .sheet-field.is-big {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
#copy-panel.is-solo .sheet-field.is-big > .sheet-compare { flex: 1; min-height: 0; }
/* Колонка - элемент СЕТКИ, и её min-height по умолчанию `auto`: строка меряется
   по полной высоте обёрнутого текста, а не по высоте сетки. Без этого звена
   колонки вылетали за панель (замер: 426 px за кромку при окне 1000x800), и
   достать хвост можно было только прокруткой тела панели, у которой полоса
   намеренно скрыта - о тексте ниже кромки не сообщало ничто. Регрессия правки
   от 2026-08-06: прежняя константа высоты этого не допускала, потому что была
   определённой (найдено ревью, круг 5). */
#copy-panel.is-solo .sheet-col { min-height: 0; }
#copy-panel.is-solo .sheet-col > :is(.sheet-live-body, .sheet-rich, textarea:not([hidden])) {
  /* Окно сравнения размывает сцену под плёнкой, как карточки панели (слово Ant04 28.09, #3665). */
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
  max-height: none;
}

/* Якорь для кнопок - само поле, а не панель: панель абсолютная и прижата к
   правой кромке, и смена её position выбрасывала её из этого позиционирования
   в поток (поймано живым прогоном 2026-07-31: панель уезжала влево). */
/* Кнопки поля в развороте стоят в правом верхнем углу ПАНЕЛИ и не уезжают при
   прокрутке (владелец, 2026-07-31): поле длиной в экран, и привязанные к нему
   кнопки уходили вверх вместе с текстом. Якорь - тело формы, оно и есть
   прокручиваемая область. */
/* В шапке панели кнопки прижаты к правой кромке, рядом с названием раздела. */
#copy-panel.is-solo .sheet-top .sheet-acts { margin-left: auto; display: flex; gap: 6px; }
/* Ряд оформления прижат к правому краю (владелец, 2026-07-31): правится правая
   колонка, и кнопки, стоящие слева, оказывались над чужим текстом - над тем,
   который не редактируется вовсе. */
#copy-panel.is-solo .rich-bar { justify-content: flex-end; }

/* Панель аудита в развороте скрыта (владелец, 2026-07-31): развёрнутое
   описание идёт до 1920 px и упиралось в неё. Замечания по полю никуда не
   деваются - они стоят под самим полем, там, где его и правят. */
#copy-panel.is-solo ~ #details,
body:has(#copy-panel.is-solo) #details { display: none; }

/* Колонки сравнения устроены одинаково: подпись, размер, тело. */
.sheet-col { min-width: 0; }
.sheet-col-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  font-size: 10.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 0 2px 6px;
}
.sheet-col-head .len-now { font-style: normal; letter-spacing: 0; }

.sheet-compare { display: contents; }
/* Вне разворота левая колонка не показывается вовсе. */
#copy-panel:not(.is-solo) .sheet-col[aria-label] { display: none; }
#copy-panel:not(.is-solo) .sheet-col-head { display: none; }
/* «Website» on the body outside the unfolded view: the site column takes the editor's place (#3895), never lower than the editor
   was (`--site-min`, set at the switch), up to the editor's own cap; the toggle and the format bar stay out of the unfolded view. */
#copy-panel:not(.is-solo) .sheet-field.is-site .sheet-col[aria-label] { display: block; }
#copy-panel:not(.is-solo) .sheet-field.is-site :is(.sheet-col.is-new, .rich-bar) { display: none; }
#copy-panel:not(.is-solo) .sheet-field.is-site :is(.sheet-live-body, .sheet-live-code) { min-height: var(--site-min, 120px); max-height: 30vh; }
/* Formatting lives only in the unfolded view (Ant04 01.10, #3911): its button and its row of tags stay out of the panel. */
#copy-panel:not(.is-solo) :is(#sheet-format, .rich-bar) { display: none; }

#copy-panel.is-solo .sheet-compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  /* Растяжка, а не `start`: тело колонки берёт высоту от неё, и при выравнивании
     по верху тянуться было бы не за чем. */
  align-items: stretch;
}
#copy-panel.is-solo .sheet-col { display: flex; flex-direction: column; }
/* Текст сайта приглушён и не правится, но выделяется и копируется: это
   источник сравнения, а не поле ввода. */
/* Колонка сайта повторяет редактор ВО ВСЁМ, кроме контраста текста (владелец,
   2026-07-31: один и тот же текст выглядел по-разному). Отличаться в
   типографике им нельзя по построению: колонки сравнивают построчно, и разные
   отступы или кегль заголовка читаются как разница в самом тексте. */
#copy-panel.is-solo .sheet-live-body,
#copy-panel .sheet-field.is-site .sheet-live-body {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--solid);
  border: 1px solid transparent;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.55;
  overflow-x: hidden;
  overflow-y: auto;
  overflow-wrap: anywhere;
  user-select: text;
  -webkit-user-select: text;
}

/* Режим разметки в развороте: сырой HTML стоит на месте редактора и тянется
   так же. Раскладка от смены вида не меняется - меняется только показ. */

/* Разметка сайта повторяет рабочее поле ВО ВСЁМ, кроме контраста и правки
   (владелец, 2026-07-31: в кодовом виде колонки снова разошлись). Отличаться
   им нельзя по построению: в коде сравнивают теги строка в строку, и разный
   кегль или поля читаются как разница в самой разметке. */
.sheet-field .sheet-live-code {
  display: block;
  width: 100%;
  background: var(--solid);
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--muted);
  padding: 9px 11px;
  resize: none;
  outline: none;
}
.sheet-field .sheet-live-code[hidden] { display: none; }
/* Шрифт кода ОДНИМ правилом на обе колонки: теги сверяют строка в строку. */
.sheet-field .sheet-col > textarea {
  font: 12.5px/1.5 var(--mono);
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

/* Раздел пройденного: свёрнут, тон спокойный - по нему работы нет, он отвечает
   на вопрос «что вообще проверяли». */
/* Тот же стандарт карточки, что у остальных блоков (владелец, 2026-08-06):
   пройденное - такой же раздел панели, и стоять голой строкой между карточками
   ему не по чину. Своя карточка нужна и потому, что блок раскрывается: без неё
   раскрытый список замечаний вываливался бы прямо на сцену. */
.block-passed {
  margin-top: 8px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
}
.block-passed > summary {
  display: flex;
  align-items: baseline;
  gap: 7px;
  cursor: pointer;
  list-style: none;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
  transition: color var(--transition);
}
.block-passed > summary::-webkit-details-marker { display: none; }
.block-passed > summary:hover { color: var(--accent); }
.block-passed > summary .issue-n { margin-left: auto; }
.block-passed > summary::after {
  content: '';
  flex: none;
  width: 5px;
  height: 5px;
  margin-left: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  opacity: .5;
  transform: rotate(-45deg);
  transition: transform var(--transition);
}
.block-passed[open] > summary::after { transform: rotate(45deg); }
.block-passed > .issue:first-of-type { margin-top: 8px; }

/* ── редактор описания ─────────────────────────────────────
   Описание правится в том виде, в каком его увидит посетитель, а «код» - тот же
   текст тегами (владелец, 2026-07-30). Оба вида стоят на одном месте и одной
   высоты: переключение не должно двигать форму под рукой. */
.rich-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0 0 6px;
}
.rich-bar[hidden] { display: none; }
.rich-btn {
  min-width: 26px;
  height: 26px;
  padding: 0 7px;
  background: var(--well);
  border: 0;
  border-radius: 8px;
  color: var(--muted);
  font: 600 11.5px/1 var(--mono);
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.rich-btn:hover { background: var(--hover); color: var(--fg); }
.rich-btn:active { background: var(--accent); color: var(--accent-fg); }

.sheet-rich {
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--solid);
  border: 1px solid transparent;
  color: var(--fg);
  font-size: 13px;
  line-height: 1.55;
  /* Half the former height, scrolling only up and down: the full text opens unfolded (Ant04 01.10, #3894). */
  min-height: 120px;
  max-height: 30vh;
  overflow-x: hidden;
  overflow-y: auto;
  overflow-wrap: anywhere;
  outline: none;
}
.sheet-rich[hidden] { display: none; }
/* Wide content wraps or shrinks to the field instead of pushing a sideways scroll. */
:is(.sheet-rich, .sheet-live-body) :is(img, video, iframe) { max-width: 100%; height: auto; }
:is(.sheet-rich, .sheet-live-body) pre { white-space: pre-wrap; }
.sheet-rich:focus { border-color: var(--line); }
/* Разметка внутри редактора выглядит как на странице: иначе правишь вслепую. */
:is(.sheet-rich, .sheet-live-body) :is(h1, h2, h3, h4) { margin: 14px 0 6px; font-size: 14px; color: var(--accent); }
:is(.sheet-rich, .sheet-live-body) :first-child { margin-top: 0; }
:is(.sheet-rich, .sheet-live-body) p { margin: 0 0 8px; }
:is(.sheet-rich, .sheet-live-body) :is(ul, ol) { margin: 0 0 8px; padding-left: 18px; }
:is(.sheet-rich, .sheet-live-body) li { margin: 2px 0; }
:is(.sheet-rich, .sheet-live-body) a { color: var(--accent); }
:is(.sheet-rich, .sheet-live-body) table { border-collapse: collapse; margin: 0 0 8px; width: 100%; table-layout: fixed; }
:is(.sheet-rich, .sheet-live-body) :is(td, th) { padding: 3px 8px; border: 1px solid var(--line); text-align: left; }
.btn-plain.is-small { width: 24px; height: 24px; border-radius: 7px; }
.btn-plain.is-small .ico { width: 14px; height: 14px; }
.btn-plain.is-on { background: var(--accent); color: var(--accent-fg); }

.sheet-field.is-big { display: flex; flex-direction: column; flex: 1; }

/* Подвал рамы: кнопка сохранения и строка состояния. Стоит вне прокручиваемого
   тела, поэтому ничего собой не накрывает. */
/* Нижняя полоса удалена целиком (владелец, 2026-08-06): кнопки сохранения нет,
   возврат переехал в шапку, и держать ради него отдельную строку под формой
   значило отнимать высоту у текста. */
.sheet-top .btn-icon { width: 28px; height: 28px; padding: 0; flex: none; }
/* Отметка переноса нажата: заливка акцентом, как у включённого фильтра. Тон
   говорит «эта страница из очереди вышла», а не «текст стоит на сайте» - второе
   подтверждает только скан. */
.sheet-top .btn-icon.is-on { background: var(--accent); color: var(--accent-fg); }
.sheet-state { font-size: 11.5px; color: var(--muted); text-align: right; }
.sheet-state.is-pending { color: var(--warn); }
/* Тем же доводом, что у счётчика длины: строка несёт не только «сохранено», но и
   «текст стоит на сайте с такой-то даты» (`page.applied`), то есть СОСТОЯНИЕ
   страницы, а соседние исходы уже жёлтый и красный (#1036-1). */
.sheet-state.is-done { color: var(--status-ok); }
/* Правка не записана (сервис отказал, не ответил либо запрос не ушёл) - это отказ, а не
   предупреждение: цвет тот же, которым панель показывает ошибки аудита. */
.sheet-state.is-alarm { color: var(--danger); }

/* Пояснение под числами: тот же вес, что у подписи, но без капса - это фраза,
   а не ярлык. */
.subtle { margin-top: 8px; font-size: 12px; line-height: 1.5; color: var(--muted); }
.subtle b { color: var(--fg); font: 500 11.5px/1.5 var(--mono); word-break: break-all; }

/* Тег страницы: подпись с длиной сверху, значение целиком под ней - обрезать
   title и description нельзя, их длина и есть предмет проверки. */
.tag + .tag { margin-top: 10px; }
.tag > span {
  display: block;
  font: 600 9.5px/1 var(--font);
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
}
.tag > p { margin: 4px 0 0; font-size: 12.5px; line-height: 1.5; }

.facts dd.is-warn { color: var(--warn); }

.url-list { margin: 6px 0 0; padding-left: 15px; }
.url-list li {
  font: 11px/1.5 var(--mono);
  color: var(--muted);
  word-break: break-all;
  margin-bottom: 2px;
}

/* ── детали узла структуры ─────────────────────────────── */

/* Факты страницы - пары «ключ - число»; двумя колонками они читаются столбцом
   чисел, а не лентой предложений.

   Обе колонки объявлены с minmax(0, ...): без нижней границы в ноль длинное
   значение забирало всю ширину под max-content, а колонка ключа сжималась до
   одной буквы и слово рассыпалось по вертикали (поймано 2026-07-28 в блоке
   разметки). Теперь при нехватке места переносится значение, а не ключ. */
.facts {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 5px 12px;
  margin: 0;
  font-size: 12.5px;
}
.facts dt { color: var(--muted); overflow-wrap: anywhere; }
.facts dd {
  margin: 0;
  text-align: right;
  font: 12.5px/1.45 var(--mono);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* Команда скана: приложение только читает, поэтому вместо кнопки «сканировать»
   тут лежит готовая строка для терминала (инвариант слоя apps/). */
.cmd {
  margin: 6px 0 0;
  padding: 10px 11px;
  background: var(--solid);
  border-radius: var(--radius-md);
}
/* Перенос по пробелам, а не по любому символу: команда с разорванным посреди
   слова флагом выглядит опечаткой. Ломается только то, что длиннее строки, - путь. */
.cmd code {
  display: block;
  font: 11px/1.65 var(--mono);
  color: var(--fg);
  white-space: pre-wrap;
  word-break: normal;
  overflow-wrap: anywhere;
  user-select: all;
}

/* ── адаптация под узкие экраны ────────────────────────── */
/* ── ход съёма: снизу по центру (#1019-2) ────────────────
   Прежде состояние джоба жило строкой в левой панели, где за ним неудобно
   следить: во время съёма внимание на схеме и на правой панели. Место одно,
   дубля в панели нет - два показа одного состояния расходятся при первой же
   правке. Слой выше панелей и панели ключей, ниже попапа. */
.scan-bar {
  position: fixed;
  z-index: 6;
  left: 50%;
  bottom: var(--gutter);
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(560px, 92vw);
  padding: 9px 9px 9px 14px;
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-md);
  box-shadow: var(--top-glow), 0 24px 64px rgba(0, 0, 0, .55);
}
.scan-bar[hidden] { display: none; }
/* Орб задания: размер несут атрибуты канваса (их читает `app/jobs.js`), `auto` и `flex: none` держат
   его от растяжки правилом `canvas`. Довод - docs/history/2026-09-25-moved-comment-reasons.md. */
.scan-bar-orb { flex: none; display: block; width: auto; height: auto; }
.scan-bar-orb[hidden] { display: none; }
.scan-bar-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.scan-bar-said { font: 500 12.5px/1.3 var(--font); color: var(--fg); }
.scan-bar[data-state="failed"] .scan-bar-said { color: var(--danger); }
/* Адрес страницы моноширинным и приглушённо: съём мог уйти по одной звезде,
   пока человек смотрит на другую, и без адреса полоса говорит не о том. */
.scan-bar-path {
  font: 11px/1.2 var(--mono);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Счётчик хода: числа сервиса, моноширинные и приглушённые. Пусто -
   узла на экране нет вовсе, а не пустое место рядом с полосой. */
.scan-bar-count { font: 11px/1 var(--mono); color: var(--muted); white-space: nowrap; }
.scan-bar-count:empty { display: none; }
/* Полоса НЕОПРЕДЕЛЁННАЯ по умолчанию и становится определённой ТОЛЬКО при
   известном «всего» (#1022): объём шага известен не всякому виду задания, и
   выдуманный знаменатель дал бы полосу, доехавшую до конца и застрявшую, - это
   хуже неопределённой. Долю считает СЕРВИС, вкладка её не выводит. */
.scan-bar-track {
  position: relative;
  flex: none;
  width: 96px;
  height: 3px;
  border-radius: 2px;
  background: var(--well);
  overflow: hidden;
}
.scan-bar-track > i {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 38%;
  border-radius: inherit;
  background: var(--accent);
  animation: scan-slide 1.15s ease-in-out infinite;
}
.scan-bar-track.is-known > i {
  animation: none;
  width: var(--part, 0%);
  transition: width .3s ease;
}
/* Полоса живёт только у незакончившегося джоба: у исхода двигать нечего. */
.scan-bar:not([data-state="queued"]):not([data-state="running"]) .scan-bar-track { display: none; }
.scan-bar[data-state="queued"] .scan-bar-track > i { animation-duration: 2.4s; opacity: .5; }
@keyframes scan-slide {
  from { transform: translateX(-105%); }
  to { transform: translateX(370%); }
}

@media (max-width: 1240px) {
  :root { --col-left: 262px; --col-base: 340px; }
}

/* ЦЕНТР ОКНА И ПРАВАЯ ПАНЕЛЬ НЕ ПОМЕЩАЮТСЯ ВМЕСТЕ НА УЗКОМ ЭКРАНЕ (#1343), и это
   арифметика, а не вкус: группа шапки шириной 561 px, стоя по центру, кончается
   на `W/2 + 280`, а панель начинается на `W - 434` (её 420 плюс зазор). Равенство
   даёт W = 1429 - ниже него три круглые кнопки уходят ПОД панель листа, и это
   уже стреляло: перекрытая кнопка съёма была недостижима мышью (замер 12.08 на
   1280x720, из-за него шапка и лежит выше панелей по z-index). Замер 20.08 после
   возврата центра: на 1280 заезд 75 px, на 1920 - ноль.

   Компенсация включается ТОЛЬКО в узкой полосе и ТОЛЬКО при открытой панели:
   слово Ant04 20.08 - «посередине экрана», и там, где центр окна возможен, он и
   остаётся. Ниже 1241 px правило не нужно вовсе - там свои узкие колонки
   (`--col-base` 340), и заезда нет.

   Порог пересчитывается по формуле выше, если сменится ШИРИНА ГРУППЫ либо
   `--col-base`: это единственная запись, которая от них зависит. */
@media (min-width: 1241px) and (max-width: 1428px) {
  body:has(.panel-right:not([hidden])) .topbar {
    padding-right: calc(var(--col-right) + var(--gutter));
  }
  /* ЛЕВАЯ ПАНЕЛЬ КОМПЕНСИРУЕТСЯ ТАК ЖЕ, И ЭТО ТОТ ЖЕ КЛАСС (#1388): правило выше
     отодвигало группу от правой панели, а левая оставалась незакрытой, и группа
     наезжала на неё ровно тем же способом. Замер 21.08 на 1280x720 при обеих
     открытых панелях: группа 164..681, левая панель 14..334, то есть 170 px
     заголовка блока проблем лежали ПОД шапкой - шапка выше панелей по z-index, и
     мышь до заголовка не доходила вовсе (браузерный путь `issuewhy` падал по
     таймауту наведения, а не вердиктом).

     Полностью развести их на этой ширине нечем, и это арифметика: между панелями
     512 px, группа 517. После компенсации она вылезает на 3 px с каждой стороны -
     на порядок меньше прежних 170, и заголовок панели (до 322) свободен. Довод
     тот же, что у правой стороны: центр ОКНА в узкой полосе уже отменён, и
     отменён он ради достижимости мышью. */
  body:has(.panel-left:not([hidden])) .topbar {
    padding-left: calc(var(--col-left) + var(--gutter));
  }
}

/* Узкий экран: колонки ужимаются до доли ширины окна, иначе панели съедают
   сцену целиком. Выбранная человеком ширина правой панели пересчитывается в
   app.js - инлайн-значение бьёт эту таблицу. */
@media (max-width: 980px) {
  :root { --col-left: 44vw; --col-base: 44vw; }
  .topbar { gap: 8px; }
}

/* ── site loading screen (#1053) ── Top layer, out of flow [owner]; NO BACKGROUND OF ITS OWN [Ant04 14.08, #1062]: the scene's `paintScene`
   shows through; reasons: docs/history/2026-09-16-fon-odnotonnyj.md («Экран загрузки»). Why: docs/history/2026-09-26-progon-comments.md. */
.boot {
  --boot-x: 50%;
  --boot-y: 50%;
  --boot-r: 88px;
  position: fixed;
  z-index: 20;
  inset: 0;
  transition: opacity .42s ease;
}
.boot[hidden] { display: none; }
.boot.is-out { opacity: 0; pointer-events: none; }

/* Пока экран жив, из интерфейса виден только ХОЛСТ - на нём фон. Прячется всё
   остальное, и прячется `visibility`, а не `display`: раскладка обязана остаться
   прежней, иначе сцена под экраном строится в одном размере, а показывается в
   другом, и «без прыжка» из #1053 перестанет быть свойством устройства. */
.app.is-booting > .topbar,
.app.is-booting > .empty,
.app.is-booting > .layout > :not(.stage) { visibility: hidden; }
/* Возврат интерфейса - ПРОЯВЛЕНИЕ за те же .42s, что уход экрана (слово Ant04:
   «элементы будут просто плавно появляться»); только `from`: конечная прозрачность своя. */
.app.is-waking > .topbar,
.app.is-waking > .empty,
.app.is-waking > .layout > :not(.stage) { animation: boot-wake .42s ease; }
@keyframes boot-wake { from { opacity: 0; } }

/* Орб стоит на МЕСТЕ главной звезды и её размером, без подсветки за ним: центр и
   радиус пишет `startLoading` переменными из той же раскладки; до него - середина окна. */
.boot-orb {
  position: absolute;
  left: var(--boot-x);
  top: var(--boot-y);
  width: calc(var(--boot-r) * 2);
  height: calc(var(--boot-r) * 2);
  translate: -50% -50%;
}
.boot-bar {
  position: absolute;
  left: var(--boot-x);
  top: calc(var(--boot-y) + var(--boot-r) + 24px);
  translate: -50% 0;
  width: 200px;
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .10);
  overflow: hidden;
}
/* Ширину пишет кадр экрана, поэтому перехода тут нет: сглаживание уже сделано
   догоняющей долей в load.js, и второй сглаживатель дал бы отставание от неё. */
.boot-bar > i {
  display: block;
  width: 0;
  height: 100%;
  background: var(--accent);
}

/* ── уважение к prefers-reduced-motion ─────────────────── */
/* Движение сцены выключает сам app.js (matchMedia): интро, пульс выбранного узла
   и инерция панорамы не запускаются, кадр рисуется сразу конечным. Здесь снимаются
   переходы интерфейса. */
@media (prefers-reduced-motion: reduce) {
  * { transition-duration: .001ms !important; animation-duration: .001ms !important; }
  /* Неопределённая полоса без движения не значит НИЧЕГО: правило выше гасит её
     бег, и на экране остался бы обрубок, который читается как застрявший
     прогресс. Здесь полоса убирается совсем - ход съёма называет текст. */
  .scan-bar-track { display: none; }
}


/* ── таблица ключей во вкладке структуры (#1395-5) ─────────────────────────
   Лежит поверх схемы во всю сцену: это ВТОРОЙ вид того же предмета, а не
   врезка рядом с деревом - две половины экрана дали бы и мелкую схему, и
   узкую таблицу на восемь колонок. */
/* ПАНЕЛЬ КЛЮЧЕЙ СТОИТ МЕЖДУ СОСЕДЯМИ (#1425, слово владельца 22.08: «посередине
   (между левой и правой панелью)»). Прежде она лежала `inset: 0` поверх всей
   сцены, а панели уступали ей экран классом на `body`; теперь уступать нечего -
   границы панели считаются от ширины соседей, и обе остаются на месте.

   ГРАНИЦЫ СЧИТАЮТСЯ ЧЕРЕЗ ПЕРЕМЕННЫЕ, а не числами: соседи умеют быть скрытыми,
   и в этих состояниях панель обязана забирать освободившееся место. Значения
   переменных двигает `body.is-keyleft` / `is-keyright` ниже - по одному правилу
   на каждого соседа, потому что скрыты они бывают порознь.

   СВЕРХУ УСТУПАЕТСЯ ТОЛЬКО ПОЛОСА ШАПКИ, и это уже другое число, чем было.
   Прежнее (`--topbar-h + --control-h + --gutter * 3`) держало ДВА ряда органов:
   ряд фильтров таблицы наезжал на переключатели вида и поиск схемы, стоявшие
   ПОД шапкой на сцене (снимок 21.08). Переключателей вида больше нет вовсе -
   их заменила кнопка шапки, - и лишний `--control-h` оставил бы 36 px пустоты
   над панелью при и без того тесном центре в 469 px на окне 1265. */
.keys-view {
  position: absolute;
  top: calc(var(--topbar-h) + var(--gutter) * 2);
  bottom: var(--gutter);
  left: var(--keys-left);
  right: var(--keys-right);
  display: flex;
  flex-direction: column;
  gap: var(--gutter);
  padding: var(--gutter);
  border-radius: var(--radius);
  box-shadow: var(--top-glow);
  background: var(--glass-strong);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  container: keys / inline-size;
  /* Слой ВЫШЕ сцены и НИЖЕ панелей: под панель ключей соседи не уезжают, а
     нажатия по схеме под ней перехватываются намеренно - «пока смотришь ключи,
     работаешь с ключами» (решение владельца 22.08). */
  z-index: 3;
}
/* `display` СВОЕГО ПРАВИЛА ПЕРЕБИВАЕТ `[hidden]`, поэтому скрытие объявляется
   здесь же - ровно как у `#copy-panel` строкой 2105. Без этой строки секция
   висела растянутой поверх сцены и в режиме дерева: нажатия по канвасу и по
   правой панели уходили в неё, а не по назначению. Поймано браузерным набором
   формы 22.08 - два красных пути (`starhit`, `proptab`) на коде, который
   выглядел рабочим, потому что мои проверки кликали по узлам программно. */
.keys-view[hidden] { display: none; }

/* Верхний ряд одной строкой (#3667): поиск тянется на свободное, за ним пилюли, меню пометок и
   кнопки. На узкой панели ряд переносится, а поиск не режется: в него человек печатает. */
.keys-top {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: none;
}

.keys-find::placeholder { color: var(--placeholder); }

/* Пилюли отбора в духе Antnest: выбранная - на лунке акцентным текстом, а не акцентной пластиной. */
.keys-pills { display: flex; gap: 2px; flex: none; }
#keys-view .keys-pills .seg-btn { height: 30px; padding: 0 12px; border-radius: 999px; color: var(--fg); font-size: 12.5px; }
#keys-view .keys-pills .seg-btn:hover { background: var(--hover); }
#keys-view .keys-pills .seg-btn[aria-pressed="true"],
#keys-view .keys-pills .seg-btn[aria-pressed="true"]:hover { background: var(--well); color: var(--accent); }

.keys-menu-wrap { position: relative; flex: none; }
.keys-select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 10px 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--well);
  color: var(--fg);
  font: 500 12.5px/1 var(--font);
  cursor: pointer;
}
.keys-select:hover { background: var(--hover); }
.keys-select::after { content: "▾"; color: var(--muted); font-size: 10px; }
.keys-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 160px;
  padding: 4px;
  border-radius: var(--radius);
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .42);
}
.keys-menu[hidden] { display: none; }
.keys-menu-item {
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--fg);
  font: 13px/1 var(--font);
  text-align: left;
  cursor: pointer;
}
.keys-menu-item:hover { background: var(--hover); }
.keys-menu-item[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); }

.keys-sep { width: 1px; height: 20px; background: var(--line); flex: none; }
.keys-ibtn {
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex: none;
  border: 0;
  border-radius: 999px;
  background: var(--well);
  color: var(--muted);
  cursor: pointer;
}
.keys-ibtn:hover { background: var(--hover); color: var(--fg); }
.keys-ibtn.is-on { background: var(--accent); color: var(--accent-fg); }
/* Поле файла невидимо, нажимают по метке: родной вид поля в стекло не встаёт. */
.keys-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.keys-add { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; width: 100%; }
.keys-add input[type='text'] {
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: inherit;
  font: inherit;
  font-size: 13px;
  padding: 4px 8px;
}
.keys-add .keys-add-phrase { flex: 2 1 160px; }
.keys-add .keys-add-path { flex: 1 1 120px; }
.keys-add-role { color: var(--muted); font-size: 12px; display: flex; align-items: center; gap: 4px; }
.keys-add-role input { accent-color: var(--accent); margin: 0; }

.keys-wrap { flex: 1 1 auto; overflow: auto; }

/* Строка на ключ без разделителей, как списки Antnest (#3667): подсветку несёт наведение. Ширины стоят
   у заголовков: содержимое колонок постоянно по форме - тысячи, цена с валютой, счёт до ста. */
.keys-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; table-layout: fixed; }
.keys-table th,
.keys-table td { padding: 7px 8px; text-align: left; vertical-align: middle; }
.keys-table th.keys-pick { width: 30px; }
.keys-table th.keys-col-page { width: 28%; }
.keys-table th.keys-col-volume { width: 78px; }
.keys-table th.keys-col-cpc { width: 62px; }
.keys-table th.keys-col-competition { width: 40px; }
.keys-table th.keys-col-acts { width: 66px; }
.keys-table td.keys-pick,
.keys-table th.keys-pick { padding-right: 0; text-align: center; }
.keys-table .keys-pick input { cursor: pointer; margin: 0; accent-color: var(--accent); }
/* Галка строки видна при наведении, фокусе и отметке: в покое список остаётся чистым. */
.keys-table td.keys-pick input { opacity: 0; }
.keys-table tr:hover td.keys-pick input,
.keys-table td.keys-pick input:checked,
.keys-table td.keys-pick input:focus-visible { opacity: 1; }

/* Капсула действия плавает над списком и появляется по выбору либо исходу; ширина по содержимому до краёв панели. */
.keys-act {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: calc(var(--gutter) + 44px);
  width: fit-content;
  margin-inline: auto;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 42px;
  padding: 5px 6px 5px 16px;
  border-radius: 999px;
  background: rgba(28, 32, 36, .92);
  box-shadow: 0 18px 48px rgba(0, 0, 0, .42), var(--top-glow);
}
.keys-act:empty { display: none; }
/* Пока капсула видна, у списка запас снизу: иначе она закрывала бы последнюю строку страницы. */
#keys-view:has(.keys-act:not(:empty)) .keys-wrap { padding-bottom: 56px; }
.keys-count { color: var(--fg); font-size: 13px; white-space: nowrap; }
.keys-said { color: var(--muted); font-size: 12px; }
.keys-said.is-fail { color: var(--warn); }
.keys-act .keys-act-btn {
  height: 30px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
  white-space: nowrap;
}
.keys-act-clear {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  border: 0;
  border-radius: 999px;
  background: var(--well);
  color: var(--muted);
  cursor: pointer;
}
.keys-act-clear:hover { background: var(--hover); color: var(--fg); }
#keys-view .keys-act-clear .ico { width: 14px; height: 14px; }
.keys-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  border-bottom: 1px solid var(--line);
  background: var(--glass-strong);
  backdrop-filter: blur(var(--blur));
  color: var(--muted);
  font: 600 10px/1.4 var(--font);
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.keys-table td.is-num,
.keys-table th.is-num { text-align: right; font-variant-numeric: tabular-nums; }
.keys-table tbody tr:hover td { background: var(--hover); }
.keys-table tbody td:first-child { border-radius: 10px 0 0 10px; }
.keys-table tbody td:last-child { border-radius: 0 10px 10px 0; }
/* Главный ключ помечен звездой у фразы: признак виден в списке (слово владельца «видно, какой ключ чем занят»). */
.keys-star { color: var(--accent); font-size: 12px; }

/* Ключ переносится по знакам, а не режется: на узкой панели пропал бы хвост фразы, по которому её узнают. */
.keys-phrase { color: var(--fg); }
.keys-line { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.keys-kw { overflow-wrap: anywhere; }
.keys-src { font-size: 11px; color: var(--muted); letter-spacing: .02em; }
/* Пометка - плашка с заливкой, источники - текст: одним серым тоном они читались продолжением друг друга. */
.keys-mark {
  padding: 0 7px;
  border-radius: 999px;
  background: var(--well);
  color: var(--muted);
  font: 600 10px/18px var(--font);
  letter-spacing: .03em;
  white-space: nowrap;
}
/* Чужая ниша своим тоном: она не мусор, а сырьё соседней ниши. */
.keys-mark.is-niche { color: var(--warn); background: rgba(255, 209, 102, .12); }
.keys-vol { font-weight: 600; }
/* Путь режется многоточием, а `+N` не режется никогда: прочие страницы ключа не прячутся молча (владелец 31.08). */
.keys-pagelink { display: flex; align-items: center; gap: 6px; min-width: 0; }
.keys-pagelink .keys-mark { flex: none; }
.keys-page-under { display: none; margin-top: 2px; }

/* Действия строки видны при наведении и фокусе, место под них держится всегда: строка не дёргается. */
.keys-acts { text-align: right; white-space: nowrap; }
.keys-row-act {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
}
/* Тот же приём, что у галки: прозрачность, а не `visibility` - кнопка остаётся в порядке Tab в обе стороны. */
.keys-table tr:hover .keys-row-act,
.keys-table tr:focus-within .keys-row-act { opacity: 1; }
.keys-row-act:disabled { cursor: progress; }
.keys-row-act:hover { background: var(--hover); color: var(--fg); }
#keys-view .keys-row-act .ico { width: 15px; height: 15px; }

/* Поиск - пилюля на свободном месте ряда, общее число ключей отбора внутри неё. */
.keys-findbar {
  display: flex;
  align-items: center;
  gap: 9px;
  flex: 1 1 220px;
  min-width: 0;
  height: 34px;
  padding: 0 14px;
  border-radius: 999px;
  background: var(--solid);
}
.keys-findbar .ico { width: 16px; height: 16px; flex: none; color: var(--muted); }
.keys-findbar .keys-find {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--fg);
  font: inherit;
}
.keys-findbar .keys-find:focus { outline: none; }
.keys-total {
  flex: none;
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Узкая панель (окно 1265 при обеих боковых даёт 469 px): страница встаёт под ключ, поиск - во всю строку,
   числа ужимаются. Иначе на ключ оставалось 42 px и фраза рвалась по слогам (замер круга #3667). */
@container keys (max-width: 720px) {
  .keys-table .keys-col-page,
  .keys-table .keys-page-cell { display: none; }
  .keys-page-under { display: flex; }
  .keys-top .keys-findbar { flex-basis: 100%; }
  .keys-table th.keys-col-volume { width: 70px; }
  .keys-table th.keys-col-cpc { width: 56px; }
  .keys-table th.keys-col-competition { width: 34px; }
  .keys-table th.keys-col-acts { width: 58px; }
}

/* Листание стрелками у правого края; подписи ушли в `aria-label`. */
.keys-pager {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 6px;
  flex: none;
}
.keys-pos {
  margin-right: 4px;
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.keys-page { width: 30px; height: 30px; padding: 0; }
.keys-page .ico { width: 15px; height: 15px; }

.keys-sort {
  padding: 0;
  color: inherit;
  font: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}
.keys-sort:hover { color: var(--fg); }
.keys-sort.is-on { color: var(--accent); }

.keys-link {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0;
  color: var(--muted);
  font: inherit;
  font-size: 12.5px;
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline dotted;
}
.keys-link:hover { color: var(--fg); }

.keys-note { margin: 0; padding: var(--gutter); color: var(--muted); }
.keys-note.is-fail { color: var(--danger); }

/* ПАНЕЛИ БОЛЬШЕ НЕ УСТУПАЮТ ЭКРАН (#1425, 22.08). Решение #1395-5 «таблица
   забирает экран целиком» отменено владельцем: панель ключей стоит МЕЖДУ левой и
   правой, обе остаются на месте, и работа с ключами страницы идёт при открытом
   листе её текстов.

   СУЖАТЬ ПРАВУЮ ПАНЕЛЬ НЕ ПОНАДОБИЛОСЬ, и это замер, а не догадка. Опасение
   было названное: группа шапки на 1265 px переполняла отведённое место при
   сегменте из двух СЛОВ (Baum, Keywords) и, переполнившись, накрывала полосу
   вкладок правой панели - падение пути `proptab` 22.08. Замер живой вкладки
   22.08 на боевом прогоне, обе панели открыты: группа шапки 412 px при 497 px
   между панелями (левая кончается на 334, правая начинается на 831), запас
   85 px; на 1600 доступно 832. Иконочная кнопка вместо двух слов сняла причину
   целиком, поэтому ширины соседей не трогаются вовсе.

   КНОПКА ЗВЕЗДЫ ПРЯЧЕТСЯ И ТЕПЕРЬ: она лежит слоем 5, выше панели ключей (3), и
   торчала бы поверх таблицы вместе со своим нажатием. Предмет у неё - выбранная
   звезда на схеме, а схема под панелью недоступна намеренно (решение владельца
   22.08: «пока смотришь ключи, работаешь с ключами»). */
body.is-keytable .star-btn { display: none; }

/* Границы панели ключей: между соседями, а при скрытом соседе - до кромки.
   Дом чисел ОДИН, эти переменные; сама `.keys-view` их только читает.

   СКРЫТОСТЬ СОСЕДА ЧИТАЕТСЯ ИЗ РАЗМЕТКИ (`:has`), а не из второго класса на
   `body`: панели прячут и показывают чужие модули (панель узла, лист текстов),
   и класс-зеркало пришлось бы двигать из каждого - ровно тот второй писатель
   состояния, из-за которого прежнее правило возвращало панели не в то
   положение. Приём в проекте не новый, `#copy-panel.is-solo` устроен так же. */
body:has(.panel-left:not([hidden])) { --keys-left: calc(var(--col-left) + var(--gutter) * 2); }
body:has(#copy-panel:not([hidden])) { --keys-right: calc(var(--col-right) + var(--gutter) * 2); }

/* ── вход человека ─────────────────────────────── */

/* Своей палитры у экрана входа НЕТ: он берёт те же токены, что приложение, и
   потому остаётся системой на вид, а не отдельной страницей рядом с ней. */
.auth-body { margin: 0; background: var(--void); color: var(--fg); font: 400 13px/1.5 var(--font); }
.auth-shell { min-height: 100vh; display: grid; place-items: center; padding: var(--gutter); }
/* Накладка ЛОЖИТСЯ ПОВЕРХ РАБОТЫ, а не заменяет её: слой выше панели ключей и
   шапки, но набранный текст под ним цел. */
.auth-overlay { position: fixed; inset: 0; z-index: 90; background: rgb(0 0 0 / .72); }
.auth-card {
  width: min(340px, 100%);
  padding: 26px;
  border-radius: var(--radius);
  background: var(--glass-strong);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  box-shadow: var(--top-glow);
}
.auth-title { margin: 0 0 16px; font-size: 16px; font-weight: 600; letter-spacing: .01em; }
.auth-note { margin: 0 0 14px; color: var(--muted); font-size: 12.5px; }
.auth-field { display: block; margin-bottom: 12px; }
/* `display` у класса СИЛЬНЕЕ атрибута `hidden`, и без этой строки поля
   регистрации стояли на экране входа, спрашивая код приглашения. */
.auth-field[hidden] { display: none; }
.auth-field span { display: block; margin-bottom: 5px; color: var(--muted); font-size: 12px; }
.auth-field input {
  width: 100%;
  height: var(--control-h);
  padding: 0 11px;
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--well);
  color: var(--fg);
  font: 400 13px/1 var(--font);
}
.auth-field input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.auth-error { margin: 0 0 12px; color: var(--danger); font-size: 12.5px; }
.auth-submit { width: 100%; justify-content: center; background: var(--accent); color: var(--accent-fg); }
.auth-submit:hover { background: rgb(var(--accent-rgb) / .85); }
.auth-switch {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 6px;
  border: 0;
  background: none;
  color: var(--muted);
  font: 400 12.5px/1.4 var(--font);
  cursor: pointer;
}
.auth-switch:hover { color: var(--fg); }

/* Кто вошёл: хвост центральной группы шапки (слово Ant04): у правого края
   блок ложился бы на панель листа. */
.topbar-who { display: flex; align-items: center; gap: 8px; }
/* Имя под своей плашкой: блок уехал со свободного края на сцену, а имя над
   графом без поверхности не читается (соседи по шапке одеты так же). */
.topbar-who-login {
  max-width: 12ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0 10px;
  height: var(--control-h);
  line-height: var(--control-h);
  border-radius: var(--radius-sm);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  background: var(--glass);
  box-shadow: var(--top-glow);
  color: var(--muted);
  font-size: 12.5px;
  border: 0;
  cursor: pointer;
}
/* Логин - ТУМБЛЕР карточки доступов: отклик тот же, что у соседних
   кнопок шапки, чтобы «нажимается» читалось до нажатия. */
.topbar-who-login:hover { color: var(--fg); background: var(--hover); }
.topbar-who-login[aria-pressed="true"] { color: var(--fg); background: var(--hover); }
/* Имя уходит первым, кнопка остаётся всегда: с именем группа шире просвета
   между панелями до окна 1399, без него - до 1295. */
@media (max-width: 1399px) {
  .topbar-who-login { display: none; }
}

/* ── карточка доступов (#2183) ───────────────────────────
   Строка «кто плюс действие» и формы выпуска: поля и кнопки те же, что в
   соседних карточках, поэтому здесь только раскладка. */
.access-list { margin-bottom: 12px; }
.access-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  font-size: 12px;
  color: var(--fg);
}
.access-row > span:first-child { flex: 1 1 auto; min-width: 0; }
.access-role { color: var(--fg-dim); }
/* Форма переносится по строкам: карточка узкая, и кнопка «выпустить» в одной
   строке с двумя полями рвалась на два слова и вылезала за кромку. */
.access-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 6px 0 10px;
}
.access-form input { flex: 1 1 120px; min-width: 0; }
.access-form input[type="number"] { flex: 0 0 92px; }
.access-form select { flex: 0 0 auto; }
.access-form .btn { flex: 0 0 auto; white-space: nowrap; }
.access-row .btn { flex: none; white-space: nowrap; }
/* Выпущенный код показывается ОДИН раз: моноширинный и переносится целиком -
   его копируют глазами и мышью. */
#access-new {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-family: var(--mono);
  color: var(--fg);
}

/* ── лог наших правок страницы (#2839) ─────────────────────
   Строка одна на правку и НЕ переносится: длинное имя поля режется многоточием,
   иначе одна правка характеристики занимала бы две строки списка. */
.change-log { display: flex; flex-direction: column; gap: 8px; }
/* Плита на день (слово Ant04 28.09, #3663), как у карточки поля соседней вкладки
   (#2845, «блюра нет»): строки лежали прямо на сцене, сквозь них шла клетка. */
.change-day {
  display: flex;
  flex-direction: column;
  gap: 1px;
  background: var(--glass);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
  backdrop-filter: blur(var(--blur)) saturate(140%);
  border-radius: var(--radius-sm);
  padding: 4px 6px 6px;
}
.change-day-head { display: flex; align-items: baseline; gap: 8px; padding: 6px 6px 7px; border-bottom: 1px solid var(--line); margin-bottom: 2px; }
.change-day-name { font-size: 12.5px; font-weight: 600; }
.change-day-name.is-today { color: var(--accent); }
.change-day-date { font-size: 11.5px; color: var(--muted); }
.change-day-count { margin-left: auto; font-size: 11px; color: var(--muted); }
.change-line {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 110px minmax(0, 5.5rem) auto;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  border-bottom: 1px solid var(--line);
  border-radius: 8px;
  font-size: 12px;
  line-height: 18px;
  white-space: nowrap;
}
.change-line:last-child { border-bottom-color: transparent; }
.change-line:hover { background: var(--well); }
.change-field, .change-actor, .change-mark { overflow: hidden; text-overflow: ellipsis; }
.change-actor, .change-when, .change-mark { color: var(--muted); }
.change-line.is-human .change-actor { color: var(--accent); }
.change-when { font: 11px/18px var(--mono); }
.change-mark { text-align: center; }
/* Шкала объёма от оси: снятое влево, добавленное вправо (слово Ant04, #3438). */
.change-meter { display: grid; grid-template-columns: 1fr 1px 1fr; align-items: center; }
.change-axis { height: 12px; background: var(--muted); opacity: .5; }
.change-half { display: flex; align-items: center; }
.change-bar { height: 8px; }
.change-half.is-del .change-bar { margin-left: auto; background: var(--danger); border-radius: 4px 0 0 4px; opacity: .8; }
.change-half.is-add .change-bar { background: var(--accent); border-radius: 0 4px 4px 0; opacity: .85; }
.change-num { font: 500 10px/18px var(--mono); }
.change-num.is-add { margin-left: auto; color: var(--accent); }
.change-num.is-del { color: var(--danger); }
