/* СЛОЙ 3 — КОМПОНЕНТЫ. Карточки, кнопки, поля, бейджи, таблицы, вкладки,
   ключ-значение, лента, пустые состояния — каждый со всеми состояниями:
   покой, наведение, нажатие, фокус, отключено, загрузка. Компонент не должен
   знать, на каком он экране.

   Заказчик: «все элементы и заглушки выглядят дёшево и не детализировано».
   Причина измерена, а не угадана: у каждого компонента было ровно одно
   состояние, а границы контролов не проходили норму 3:1 для нетекстовых
   элементов (WCAG 1.4.11) — рамка поля давала 1.63:1, иконка пустого
   состояния 2.98:1, отключённая кнопка 4.12:1 текстом. Цвета берутся из
   рядов tokens.css; своих значений здесь нет.

   Тени: на кнопках, полях, шапке таблицы и редакторе box-shadow запрещена
   намеренно. Кольцо фокуса в a11y.css — сплошной outline без тени, и тест
   test_focus_ring_is_identical_on_every_control требует computed
   box-shadow: none у сфокусированного контрола. Глубину даём светлотой
   поверхности, как задумано в токенах. */

/* ---------- производные величины ----------
   Ряд нейтральных линий в токенах один: --line (1.40:1 на фоне страницы) и
   --line-strong (1.63:1). Обе ниже нормы 3:1, а норма относится к границе,
   по которой опознают контрол. Ступени между --line-strong и --muted в
   палитре нет, поэтому она ВЫВЕДЕНА из нормы, как выведен коэффициент рамки
   у цветных рядов: доля --muted подобрана так, чтобы 3:1 набиралось на всех
   трёх фонах, где стоят контролы (--field, --surface, --surface-2).
   Измерено после правки: 3.44:1 внутри поля, 3.24:1 на карточке.

   Запасной вариант объявлен первым: браузер без color-mix() возьмёт прежний
   --line-strong и останется на сегодняшнем поведении, а не потеряет рамку. */
:root {
  --ctl-line: #718096;
  --ctl-line-on: var(--muted);
}
:root[data-theme="dark"] { --ctl-line: #62696a; }

/* ---------- карточки ---------- */
/* Author CSS у форм и сеток перебивает встроенное скрытие содержимого
   <details> в Chromium. Из-за этого закрытая карточка имела высоту одного
   summary, а её кнопки и поля рисовались ниже, уже за рамкой. */
details:not([open]) > :not(summary) { display: none !important; }

/* Линию под заголовком карточки рисует base.css (.card > h2) — здесь только
   сама карточка и её варианты. --elev-1 это не размытая тень, а светлая
   кромка сверху: на тёмном фоне читается край, а не ореол. */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: 16px 18px;
  box-shadow: var(--elev-1);
}
/* Уплотнённый вариант: для вложенных панелей и врезок, где 16/18 съедают
   место, а карточка нужна как рамка. */
.card.is-compact { padding: 10px 12px; border-radius: var(--r-ctl); }
/* Вариант с подсветкой: полоса слева внутренней тенью, а не border-left —
   иначе содержимое сдвигается на 3px относительно соседних карточек. */
.card.is-accent {
  border-color: var(--cyan-line);
  box-shadow: var(--elev-1), inset 3px 0 0 var(--cyan-base);
}
.card.is-warn {
  border-color: var(--amber-line);
  box-shadow: var(--elev-1), inset 3px 0 0 var(--amber-base);
}
.card.is-danger {
  border-color: var(--red-line);
  box-shadow: var(--elev-1), inset 3px 0 0 var(--red-base);
}

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 14px; }
.stat { padding: 14px 16px; }
.stat-num {
  display: block; font-family: var(--mono); font-size: var(--t-xl);
  font-weight: 700; line-height: 1.2; font-variant-numeric: tabular-nums;
}
.n-cyan { color: var(--cyan); } .n-green { color: var(--green); }
.n-amber { color: var(--amber); } .n-muted { color: var(--muted); }
/* цифра на дашборде — только цвет: рамку и заливку даёт .chip.warn, не .warn */
.stat-num.warn { color: var(--amber); }
.stat-label {
  display: block; margin-top: 4px; color: var(--muted);
  font-size: var(--t-xs); letter-spacing: .08em; text-transform: uppercase;
}
/* Две колонки только там, где каждая шире 360px. Перелом задан не шириной
   окна, а шириной самого места под сетку: боковое меню съедает 232px, и на
   окне 1000px две «настольных» колонки выходили по 350 — читать кв-списки
   в них нельзя. Правило @media ниже дополнительно схлопывает сетку на 900,
   где меню уже стало горизонтальным и место снова широкое. */
.grid-2 {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: 12px; margin-bottom: 14px; align-items: start;
}
.grid-2 > * { min-width: 0; }
/* Две карточки верхнего уровня подряд стояли вплотную: у .card своего
   внешнего отступа нет, а промежуток давали только .grid-2 и .filters. */
.content > .card + .card { margin-top: 14px; }

/* ---------- журнал / события ----------
   Лента получила рельс с точками: до этого события отличались от обычного
   списка только рамкой снизу, и «журнал» читался как таблица без шапки.
   Рельс декоративный (1px --line), точка — --ctl-line, 3.24:1 на карточке. */
.feed { list-style: none; position: relative; }
.feed::before {
  content: ""; position: absolute; left: 3px; top: 12px; bottom: 12px;
  width: 1px; background: var(--line);
}
.feed li {
  display: flex; gap: 10px; padding: 8px 0 8px 18px; align-items: baseline;
  border-bottom: 1px solid var(--line); position: relative;
}
.feed li::before {
  content: ""; position: absolute; left: 0; top: 15px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--ctl-line);
}
.feed li:hover::before { border-color: var(--cyan-base); background: var(--cyan-bg); }
.feed li:last-child { border-bottom: 0; }
.feed li > * { min-width: 0; overflow-wrap: anywhere; }
.feed-t {
  font-family: var(--mono); font-size: var(--t-sm); color: var(--muted);
  flex: none; font-variant-numeric: tabular-nums;
}
.feed code { white-space: nowrap; flex: none; }
.feed-note { color: var(--muted); min-width: 0; overflow-wrap: anywhere; }
.feed-note a { color: var(--cyan-base); text-decoration: none; }
.feed-note a:hover { text-decoration: underline; }

/* ---------- кнопки ----------
   Пять видов (основная, обычная, опасная, мелкая, только-иконка) и шесть
   состояний у каждого. Плоскую заливку сменил очень слабый вертикальный
   градиент: он даёт кнопке объём без тени, которая на этом слое запрещена. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  background-color: var(--surface-2);
  /* Блик — тот же приём и та же доля белого, что в --elev-1: светлая кромка
     сверху вместо тени. Тенью нельзя, см. шапку файла. */
  background-image: linear-gradient(180deg, rgba(255, 255, 255, .04), rgba(255, 255, 255, 0));
  color: var(--txt);
  border: 1px solid var(--ctl-line); border-radius: var(--r-ctl);
  padding: 8px 14px; font: inherit; font-size: var(--t-base); font-weight: 600;
  line-height: 1.35; cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background-color .15s, border-color .15s, color .15s, transform .06s;
}
.btn .ic { width: 16px; height: 16px; }
.btn:hover:not(:disabled) { background-color: var(--surface-3); border-color: var(--ctl-line-on); }
/* Нажатие видно смещением на пиксель и снятым бликом: у половины кнопок фон
   меняется слабо, а на тачскрине :hover не срабатывает вовсе. Отключённым
   кнопкам отклика не даём — они ничего не делают. */
.btn:not(:disabled):active {
  background-color: var(--surface); background-image: none;
  transform: translateY(1px);
}

.btn-primary {
  background-color: var(--cyan-base); border-color: var(--cyan-base);
  color: var(--bg);
}
.btn-primary:hover:not(:disabled) {
  background-color: var(--cyan-strong); border-color: var(--cyan-strong);
}
/* Нажатая основная затемняется коэффициентом, а не ступенью ряда. Ступень
   --cyan-line после правки норму по тексту проходит (4.97:1 с тёмной
   подписью), но нажатие обязано читаться как то же самое, что покой, только
   притопленное, — прыжок на другую ступень ряда даёт другой цвет кнопки.
   brightness(.88) держит 8.28:1 и оставляет оттенок на месте. */
.btn-primary:not(:disabled):active {
  background-color: var(--cyan-base); border-color: var(--cyan-base);
  filter: brightness(.88);
}

.btn-danger {
  background-color: var(--red-bg); background-image: none;
  border-color: var(--red-line); color: var(--red-base);
}
.btn-danger:hover:not(:disabled) {
  background-color: var(--red-bg-2); border-color: var(--red-base);
  color: var(--red-strong);
}
.btn-danger:not(:disabled):active { filter: brightness(.9); }

.btn-sm { padding: 5px 10px; font-size: var(--t-sm); }
.btn-sm .ic { width: 14px; height: 14px; }
/* Только-иконка: квадрат, подпись живёт в title/aria-label. Минимум 34px —
   норма WCAG 2.5.8 (24x24) с запасом; на узком экране правило ниже доводит
   до 44. */
.btn-icon { padding: 8px; min-width: 34px; min-height: 34px; }
.btn-icon .ic { width: 18px; height: 18px; }

/* Отключённая кнопка. Была opacity: .5 — подпись падала до 4.12:1 и кнопка
   читалась как поломка отрисовки. Стало осознанное состояние: своя заливка,
   пунктирная рамка (тот же приём, что у пустых состояний — «здесь ничего не
   будет») и подпись --muted, 6.84:1. Прозрачности нет вовсе. */
.btn:disabled, .btn[aria-disabled="true"] {
  opacity: 1; cursor: not-allowed;
  background-color: var(--surface); background-image: none;
  border: 1px dashed var(--ctl-line); color: var(--muted);
  transform: none; filter: none;
}
.btn-primary:disabled, .btn-danger:disabled {
  background-color: var(--surface); border-color: var(--ctl-line); color: var(--muted);
}

/* Загрузка: подпись остаётся на месте, слева встаёт кольцо. Кольцо — обычный
   элемент flex-строки, поэтому ничего не смещается и не позиционируется.
   Клики гасим: кнопка уже работает. */
.btn.is-loading, .btn[aria-busy="true"] { pointer-events: none; color: var(--muted); }
.btn.is-loading::before, .btn[aria-busy="true"]::before {
  content: ""; flex: none; width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid currentColor; border-top-color: transparent;
  animation: spin .7s linear infinite;
}
.btn-primary.is-loading, .btn-primary[aria-busy="true"] { color: var(--bg); }
@keyframes spin { to { transform: rotate(360deg); } }

.btn-row { display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap; }

/* ---------- поля и селекты ----------
   Рамка была rgba(148,163,184,.30): 1.63:1 на карточке при норме 3:1 для
   границы контрола. Поле опознавалось только по чуть более тёмной заливке
   (1.06:1 против карточки) — то есть практически никак. */
input:not([type="checkbox"]):not([type="radio"]), select, textarea {
  background: var(--field); color: var(--txt);
  border: 1px solid var(--ctl-line); border-radius: var(--r-ctl);
  padding: 8px 12px; font: inherit; font-size: var(--t-base);
  transition: border-color .15s, background-color .15s;
}
/* Подсказка внутри поля: было rgba(147,164,187,.75) — 4.59:1, впритык к
   норме 4.5:1 для текста. Сплошной --muted даёт 7.32:1 и при этом заметно
   тусклее набранного значения (--txt, 14.96:1) — их по-прежнему не спутать. */
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }
input:not([type="checkbox"]):not([type="radio"]):hover, select:hover, textarea:hover {
  border-color: var(--ctl-line-on);
}
/* Кольцо фокуса у полей — общее для всех контролов, см. a11y.css. Здесь
   остаётся подсветка собственной рамки: она отмечает, куда попадёт ввод.
   Полупрозрачной тени-кольца нет: сплошной --cyan даёт 9.90:1 на карточке
   против 4.31:1 у rgba(34,211,238,.6) — посчитано по формуле WCAG.

   Цепочка :not() повторена во всех состояниях не для красоты: у базового
   правила выше вес (0,2,1), и короткое input:focus (0,1,1) ему проигрывало —
   поле в фокусе оставалось с серой рамкой. Поймано измерением computed
   стиля, а не чтением. */
input:not([type="checkbox"]):not([type="radio"]):focus, select:focus, textarea:focus {
  border-color: var(--cyan);
}
/* Ошибка ввода. Класс и aria-атрибут вместе: разметка может проставить
   любой, а читалке нужен именно aria-invalid. */
input:not([type="checkbox"]):not([type="radio"])[aria-invalid="true"],
select[aria-invalid="true"], textarea[aria-invalid="true"],
input:not([type="checkbox"]):not([type="radio"]).is-error,
select.is-error, textarea.is-error {
  border-color: var(--red-line); background: var(--red-bg);
}
input:not([type="checkbox"]):not([type="radio"])[aria-invalid="true"]:focus,
input:not([type="checkbox"]):not([type="radio"]).is-error:focus,
textarea[aria-invalid="true"]:focus { border-color: var(--red-base); }
/* Отключённое поле: как у кнопки — не прозрачность, а собственное состояние.
   Пунктир говорит «сюда не вводят», подпись остаётся читаемой (6.84:1). */
input:not([type="checkbox"]):not([type="radio"]):disabled,
select:disabled, textarea:disabled {
  opacity: 1; cursor: not-allowed;
  background: var(--surface); color: var(--muted); border-style: dashed;
}
input:not([type="checkbox"]):not([type="radio"]):read-only { background: var(--surface); }
input[aria-busy="true"], select[aria-busy="true"] { cursor: progress; }
select {
  appearance: none; padding-right: 42px;
  background-color: var(--field);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position:
    calc(100% - 18px) calc(50% - 2px),
    calc(100% - 13px) calc(50% - 2px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
/* Флажки красились системным синим мимо палитры. */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--cyan-base); width: 15px; height: 15px; cursor: pointer;
}
input[type="checkbox"]:disabled, input[type="radio"]:disabled { cursor: not-allowed; }

/* ---------- фильтры ---------- */
.filters { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 14px; }
.filters input[type="text"], .filters input[type="search"], .filters input:not([type]) { flex: 1; min-width: 220px; }
.filters label { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: var(--t-sm); }
.filters label input { min-width: 0; }
.claim-search .search-field {
  position: relative; flex: 1 1 460px; display: block; min-width: 240px;
}
.claim-search .search-field > .ic {
  position: absolute; z-index: 1; left: 13px; top: 50%; width: 17px; height: 17px;
  transform: translateY(-50%); color: var(--muted); pointer-events: none;
}
.claim-search .search-field input { width: 100%; padding-left: 40px; }
.claim-search .search-help {
  flex-basis: 100%; margin: -2px 0 0; color: var(--muted); font-size: var(--t-xs);
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------- вкладки ---------- */
/* overflow-y: hidden — иначе из-за margin-bottom: -1px у вкладок высота
   содержимого на пиксель больше и справа появляется вертикальный скроллбар */
.tabs {
  display: flex; gap: 2px; border-bottom: 1px solid var(--line);
  margin-bottom: 14px; overflow-x: auto; overflow-y: hidden;
}
.tabs a {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px; margin-bottom: -1px; white-space: nowrap;
  color: var(--muted); text-decoration: none;
  font-weight: 600; font-size: var(--t-sm);
  border-bottom: 2px solid transparent;
  border-radius: var(--r-ctl) var(--r-ctl) 0 0;
  transition: color .15s, border-color .15s, background-color .15s;
}
.tabs a:hover { color: var(--txt); background: var(--surface); }
.tabs a:active { background: var(--surface-2); }
.tabs a.on { color: var(--txt); background: var(--surface-2); border-bottom-color: var(--cyan); }
.tabs a[aria-disabled="true"] { color: var(--muted); pointer-events: none; opacity: .6; }

/* ---------- бейджи и плашки статусов ----------
   Заливки набирались прозрачностями .08/.1/.16/.45 — четыре значения без
   системы. Теперь ряды: bg — заливка, line — рамка, base — текст. Точка
   слева читается как индикатор и отличает плашку от обычного слова в тексте
   даже в чёрно-белой печати формы. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--t-xs); font-weight: 700; letter-spacing: .04em;
  padding: 3px 10px 3px 8px; border-radius: var(--r-pill);
  border: 1px solid var(--ctl-line); background: var(--surface-2);
  color: var(--muted); white-space: nowrap; vertical-align: 1px;
}
.chip::before {
  content: ""; flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
}
.st-0 { color: var(--green-base); border-color: var(--green-line); background: var(--green-bg); }
.st-1 { color: var(--muted); border-color: var(--ctl-line); background: var(--surface-2); }
/* только для плашек: без .chip правило цепляло .stat-num.warn на дашборде
   и рисовало рамку с заливкой вокруг цифры */
.chip.warn { color: var(--amber-base); border-color: var(--amber-line); background: var(--amber-bg); }
.st-err { color: var(--red-base); border-color: var(--red-line); background: var(--red-bg); }
/* Плашка внутри ссылки или строки-ссылки подсвечивается вместе с ней. */
.rowlink:hover .chip, a:hover > .chip { border-color: var(--ctl-line-on); }
.st-0:hover, .chip.warn:hover, .st-err:hover { filter: brightness(1.08); }

/* ---------- честные счётчики списков ---------- */
/* сколько найдено против сколько показано + предупреждение об усечении */
.found { color: var(--muted); font-size: var(--t-sm); margin: 0 2px 10px; }
.found b { color: var(--txt); font-family: var(--mono); font-variant-numeric: tabular-nums; }
.result-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 0 10px;
}
.result-count {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 28px; padding: 4px 9px;
  color: var(--muted); font-size: var(--t-sm);
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--surface);
}
.result-count b {
  color: var(--txt); font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.result-stale { border-color: var(--amber-line); background: var(--amber-bg); }
/* Образец полосы просрочки: цвет в таблице без подписи читается как украшение.
   Сам образец декоративен (aria-hidden), смысл несёт текст рядом. */
.legend-stale {
  display: inline-block; width: 3px; height: 11px; margin-right: 6px;
  vertical-align: -1px; background: var(--amber-base); border-radius: 1px;
}
.notice {
  margin: 0 0 14px; padding: 10px 14px;
  border-radius: var(--r-ctl); font-size: var(--t-sm); line-height: 1.45;
  color: var(--amber-base);
  border: 1px solid var(--amber-line); border-left-width: 3px;
  background: var(--amber-bg);
}

/* ---------- таблицы ----------
   Плотность не трогаем: это служебная консоль, оператор смотрит по двести
   строк. Ячейка остаётся 8/12, размер текста прежний. Меняются только
   различимость строк и состояния. */
.table-scroll {
  overflow-x: auto; overflow-y: hidden; max-height: none;
  border: 1px solid var(--line); border-radius: var(--r-ctl);
  -webkit-overflow-scrolling: touch;
}
/* 13.5px — единственный размер мимо шкалы токенов, и он оставлен нарочно.
   Замерено на 25 видимых строках: 12.5px (--t-sm, «второстепенный текст»)
   сжимает таблицу с 1812 до 1600px, но это основной текст оператора, а не
   второстепенный; 15px (--t-base) растит её до 2343px — плюс 531px прокрутки
   на каждую страницу списка при том же числе строк. Плотность важнее
   единообразия шкалы. */
.tbl { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13.5px; }
.table-scroll .tbl { min-width: 720px; }
/* Липкая шапка. Тени под ней быть не может (см. шапку файла), поэтому край
   держат две линии: собственная нижняя --line-strong и заливка --surface-2,
   которая на две ступени светлее строк. */
.tbl th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2);
  text-align: left; color: var(--muted); font-size: var(--t-xs); font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em;
  padding: 8px 12px; border-bottom: 1px solid var(--line-strong);
}
.tbl th.num { text-align: right; font-family: inherit; }
.tbl th.sortable { cursor: pointer; user-select: none; transition: background-color .15s, color .15s; }
.tbl th.sortable:hover { color: var(--txt); background: var(--surface-3); }
.tbl th.sortable:active { background: var(--cyan-bg); }
/* Значка «столбец сортируется» в покое нет намеренно. Пробовал ставить его
   прозрачным, чтобы не дёргалась ширина колонки: глиф ⇅ подтягивает
   запасной шрифт с более высокой строкой, и шапка на 1280 выросла с 50 до
   66.5px, на 375 с 48 до 64.5 — замерено. Сортируемость показывают курсор
   и подсветка шапки под ним. */
.tbl th.asc::after { content: " ↑"; color: var(--cyan); }
.tbl th.desc::after { content: " ↓"; color: var(--cyan); }
.tbl td { padding: 8px 12px; border-bottom: 1px solid var(--line); vertical-align: top; }
.tbl tbody tr:last-child td { border-bottom: 0; }
.tbl tbody tr { transition: background-color .12s; }
/* Зебра была rgba(148,163,184,.03) — 1.03:1 между соседними строками, то
   есть её не существовало. Ступень --surface-2 даёт 1.17:1: полосы видно,
   но они не спорят с наведением (1.48:1). */
.tbl tbody tr:nth-child(even) { background: var(--table-zebra); }
.tbl tbody tr:hover { background: var(--surface-3); }
.tbl tr.rowlink { cursor: pointer; }
/* Строка-ссылка под курсором подсвечивается голубым — тем же цветом, что
   кольцо фокуса и ссылки: «сюда можно нажать». Обычная строка просто
   светлеет. */
.tbl tbody tr.rowlink:hover { background: var(--cyan-bg-2); }
/* нажатие на строку заметно до того, как успеет открыться карточка */
.tbl tbody tr.rowlink:active { background: var(--cyan-bg); }
/* Просрочено — без движения дольше двух суток (тот же критерий, что у
   напоминаний). Полосу рисует внутренняя тень первой ячейки, а не
   border-left: при border-collapse: separate рамка сдвинула бы текст на
   2px, и первая колонка разъехалась бы со всеми остальными строками. */
.tbl tbody tr.is-stale > td:first-child { box-shadow: inset 3px 0 0 var(--amber-base); }
/* Просроченная строка под курсором остаётся янтарной: голубой «сюда можно
   нажать» затирал бы единственную пометку «требует внимания». */
.tbl tbody tr.is-stale:hover, .tbl tbody tr.rowlink.is-stale:hover { background: var(--amber-bg-2); }
.tbl tbody tr.rowlink.is-stale:active { background: var(--amber-bg); }
/* тикеты и серийники не рвём по дефису — таблица и так прокручивается вбок */
.tbl .t-mono, .tbl td code, .tbl td.mono {
  white-space: nowrap; font-variant-numeric: tabular-nums; letter-spacing: .01em;
}
.tbl .t-mono code { font-size: var(--t-sm); }
.tlink { text-decoration: none; }
.tlink:hover code { text-decoration: underline; }
.pos { color: var(--muted); font-family: var(--mono); font-size: var(--t-sm); }
/* Итог живёт в tfoot: не сортируется и не уезжает на вторую страницу.
   sticky снизу — чтобы он оставался на экране и при прокрутке длинной таблицы. */
.tbl tfoot td {
  position: sticky; bottom: 0; z-index: 1;
  padding: 8px 12px; border-top: 1px solid var(--line-strong);
  background: var(--surface-2); font-weight: 700;
}
/* Строки вне текущей страницы прячет класс, а не style="display: none".
   Пустая строка style не удаляет атрибут, и в живом DOM оставалось по
   style="" на каждой строке данных: 103 на архиве, 207 в справочнике СЦ. */
.tbl tbody tr.is-off { display: none; }
/* Загрузка таблицы: данные ещё те, но уже неактуальны — гасим и ставим
   курсор ожидания, не подменяя разметку скелетом (её строит сервер). */
.tbl[aria-busy="true"] tbody, .tbl.is-loading tbody { opacity: .45; cursor: progress; }
.pager { display: flex; gap: 10px; align-items: center; margin: 10px 2px; flex-wrap: wrap; }
.pager .muted { font-size: var(--t-sm); }

/* ---------- ключ-значение ----------
   Метка и значение были одного кегля и различались только цветом. Метка
   ушла в тот же регистр, что подписи групп меню и заголовки карточек:
   11px, uppercase, разрядка. Колонка сузилась со 170 до 150px — освободило
   20px значению, которое переносится чаще. */
.kv { list-style: none; }
.kv li {
  display: grid; grid-template-columns: 150px 1fr; gap: 12px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.kv li:last-child { border-bottom: 0; }
.kv li > * { min-width: 0; overflow-wrap: anywhere; }
.kv span {
  color: var(--muted); font-size: var(--t-xs); font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em; padding-top: 3px;
}
.kv b { font-weight: 600; }
.fault {
  background: var(--amber-bg); border: 1px solid var(--amber-line);
  border-left-width: 3px; border-radius: var(--r-ctl); padding: 10px 12px;
}
.ticket code { font-size: 18px; letter-spacing: .01em; }

/* ---------- пустые состояния ----------
   Заказчик назвал их дешёвыми — и был прав: серый текст, иконка 28px с
   opacity .55 (2.98:1 при норме 3:1 для нетекстовых элементов) и никакой
   границы блока. Стало: утопленная площадка с пунктирной рамкой и угловыми
   засечками, иконка 56px в медальоне из голубого ряда (7.95:1), заголовок
   17px и объяснение 15px на 46 знаков в строке.

   Иконка берётся из спрайта Lucide, который уже есть в base.html; новых не
   добавлено. Медальон рисуется полем самой <svg>: box-sizing: border-box
   стоит глобально, поэтому 56px минус padding 14px дают рисунок 28px. */
.empty {
  position: relative;
  display: grid; justify-items: center; gap: 10px;
  padding: 32px 24px; margin: 2px 0; text-align: center;
  color: var(--muted);
  background: var(--field);
  border: 1px dashed var(--ctl-line); border-radius: var(--r-lg);
}
/* Угловые засечки: две по диагонали. Голубой --cyan-line даёт 4.75:1 на
   заливке площадки — декоративная деталь, но видимая. */
.empty::before, .empty::after {
  content: ""; position: absolute; width: 14px; height: 14px;
  border: 1px solid var(--cyan-line); pointer-events: none;
}
.empty::before { top: 8px; left: 8px; border-right: 0; border-bottom: 0; border-radius: 3px 0 0 0; }
.empty::after { right: 8px; bottom: 8px; border-left: 0; border-top: 0; border-radius: 0 0 3px 0; }
.empty .ic {
  width: 56px; height: 56px; padding: 14px; opacity: 1;
  color: var(--cyan-base); background: var(--cyan-bg);
  border: 1px solid var(--cyan-line); border-radius: var(--r-lg);
  stroke-width: 1.5; vertical-align: 0;
}
.empty b { color: var(--txt); font-size: var(--t-md); font-weight: 700; letter-spacing: .01em; }
.empty span {
  font-size: var(--t-base); line-height: 1.55; max-width: 46ch;
  color: var(--muted); overflow-wrap: anywhere;
}
/* Пусто из-за ошибки, а не из-за отсутствия данных: медальон краснеет.
   Разметка та же — отличает контейнер (.err-block в отчёте). */
.err-block .empty .ic, .empty.is-error .ic {
  color: var(--red-base); background: var(--red-bg); border-color: var(--red-line);
}
.err-block .empty::before, .err-block .empty::after,
.empty.is-error::before, .empty.is-error::after { border-color: var(--red-line); }
/* Пусто, потому что ничего не нашлось по фильтру — янтарь, «поправьте
   запрос». Класс необязателен: без него состояние остаётся голубым. */
.empty.is-filtered .ic {
  color: var(--amber-base); background: var(--amber-bg); border-color: var(--amber-line);
}
/* Засечки красятся заодно с медальоном — иначе площадка выходила янтарной
   в середине и голубой по углам, то есть двухцветной без причины. */
.empty.is-filtered::before, .empty.is-filtered::after { border-color: var(--amber-line); }

/* ---------- загрузка ----------
   Скелет для мест, где разметку рисует JS. Анимацию гасит
   prefers-reduced-motion из слоя доступности — там правило со звёздочкой. */
.skeleton {
  color: transparent !important; border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--surface-2), var(--surface-3), var(--surface-2));
  background-size: 200% 100%; animation: skeleton 1.2s linear infinite;
  user-select: none; pointer-events: none;
}
@keyframes skeleton { from { background-position: 100% 0; } to { background-position: -100% 0; } }
.card[aria-busy="true"], .card.is-loading { position: relative; }
.card[aria-busy="true"] > *:not(h2), .card.is-loading > *:not(h2) { opacity: .5; }

/* ---------- копирование по клику ---------- */
.copyable { cursor: copy; border-bottom: 1px dashed var(--ctl-line); }
.copyable:hover { color: var(--cyan); border-bottom-color: var(--cyan-line); }
.copyable:active { color: var(--cyan-strong); }

/* ---------- ошибки ----------
   .err-block стоит и на карточке отчёта (class="card err-block") — красную
   плашку рисуем только там, где .err и .err-block вместе. */
.err { color: var(--red-base); font-size: var(--t-sm); margin-top: 12px; }
.err.err-block {
  margin: 0 0 14px; padding: 10px 14px; border-radius: var(--r-ctl);
  background: var(--red-bg); border: 1px solid var(--red-line); border-left-width: 3px;
}
.err-block { margin-bottom: 14px; }

/* ---------- тосты ----------
   Владелец этого куска — toasts.css (слой 6): он грузится после компонентов
   и перебивает всё, что здесь. Оставлено как запасное оформление, чтобы
   уведомления не остались голыми, пока тот слой пуст. */
#toasts {
  position: fixed; right: 16px; bottom: 16px; z-index: 70;
  display: grid; gap: 8px; justify-items: end; pointer-events: none;
}
.toast {
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-left: 3px solid var(--cyan); border-radius: var(--r-ctl);
  padding: 10px 14px; font-size: var(--t-sm); max-width: 340px;
  box-shadow: var(--elev-2);
  animation: toast-in .18s ease;
}
.toast.ok { border-left-color: var(--green); }
.toast.warn { border-left-color: var(--amber); }
.toast.err { border-left-color: var(--red); }
@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }

/* ---------- узкий экран ----------
   Слой экранов (screens.css) доводит до 44px кнопки, поля, пункты меню и
   шапку таблицы. Вкладки и кнопки-иконки в тот список не попали: замерено
   на 375 — вкладка 37.5px по высоте при норме WCAG 2.5.5 в 44. Правило
   живёт здесь, потому что это свойство компонента, а не экрана. */
@media (max-width: 900px) {
  .tabs a { min-height: 44px; }
  .btn-icon { min-width: 44px; min-height: 44px; }
  .empty { padding: 24px 16px; }
  .empty .ic { width: 48px; height: 48px; padding: 12px; }
}
