/* SPIKE — скринер Bybit.
   Направление: почти чёрное поле, волосяные линии, плотная таблица.
   Ключевое решение — В ИНТЕРФЕЙСЕ НЕТ АКЦЕНТНОГО ЦВЕТА. Цвет уже занят смыслом
   «направление цены»; если красить им ещё и меню, оператор каждый раз тратит долю
   секунды на «это движение рынка или элемент управления». Интерактив выражается
   светлотой (белый = выбрано), волосяной линией и движением.
   Экстремум кодируется яркостью и весом: обычное значение тонет, аномальное выступает. */

:root {
  --ink: #08090b;      /* фон; не чистый чёрный — на OLED он смазывается при скролле */
  --panel: #0e1013;
  --panel-2: #14171c;
  --hair: #1b1e24;
  --chalk: #eceef1;    /* 17.1:1 — основной текст и экстремум */
  /* Вторичный текст. Было #767c87 — 4.54:1 на корпусе, то есть впритык к порогу
     AA и на грани читаемости в панели настроек (4.28:1 на --panel-2). Стало
     6.39:1 на корпусе. Разрыв до --chalk сузился с 4.26x до 2.82x по светлоте,
     поэтому «обычное против экстремума» теперь держится не одной светлотой:
     экстремум добирает весом (см. td.sym, tbody td и .wg-label ниже). */
  --ash: #8f96a1;      /* 6.68:1 на фоне, 6.39:1 на корпусе */

  /* Цветность принадлежит рынку и больше ничему. */
  --up: #35d08a;
  --down: #ff5a52;
  --up-hot: #7bffc6;
  --down-hot: #ff8f88;

  /* Две конструктивные светлоты поверх --hair. Ахроматичны — запрет на
     акцентный цвет они не нарушают.
     --edge: линия, которая РАЗДЕЛЯЕТ. Ритм таблицы, рамка всплывающего меню,
     граница кнопки под курсором. Волосяная и тихая: она подсказывает край, а
     не требует внимания.
     --grip: линия, за которую ХВАТАЮТ, — полоса между окнами деска в правке.
     Единственный указатель на то, что раскладка вообще регулируется, поэтому
     светлее: 3.09:1 к --panel, то есть проходит порог WCAG 1.4.11 для
     нетекстового элемента управления. Обе жили как var(--edge) без
     определения — браузер молча обнулял объявление, и линии не было вовсе. */
  --edge: #2c313a;
  --grip: #5a626e;

  /* Высота шапки. Участвует в четырёх calc(): минимальная высота страницы,
     отступ и высота липкой панели графика, высота деска. Вписанная числом в
     каждое, она расходилась бы с ними молча — панель уезжала бы под шапку
     или вырастала за экран, а виновата была бы правка в другом правиле.
     44px, а не прежние 52: на 16:9 рабочая область начинается тем ниже, чем
     выше хром, и восемь пикселей здесь — это часть строки таблицы. */
  --topbar: 44px;

  /* Отступы оболочки страницы. В переменных, потому что дес их ГАСИТ
     отрицательным полем, чтобы занять экран целиком: пока числа стояли в двух
     правилах порознь, правка отступа увела дес на восемь пикселей под шапку —
     проверено. Верх меньше низа намеренно: сверху страницу отделяет от шапки
     ещё и линия, снизу упор в край окна читался бы как обрыв таблицы. */
  --page-pad-top: 0.75rem;
  --page-pad-x: 1.5rem;
  --page-pad-bottom: 1.25rem;

  /* Заголовки — Onest: кириллический гротеск без постерной манерности, звучит
     прибором, а не афишей. Числа — IBM Plex Mono: шире и спокойнее JetBrains,
     а в таблице на числа смотрят дольше всего.
     Шапка звучит ОТДЕЛЬНО от текста: у моноширинного Tektur все пункты равной
     ширины, поэтому навигация не дёргается при смене активного раздела. */
  --display: "Onest", system-ui, sans-serif;
  --sans: "Inter Tight", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --nav: "Tektur", "IBM Plex Mono", ui-monospace, monospace;

  --fast: 0.14s;
}

* {
  box-sizing: border-box;
}

html {
  color-scheme: dark;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--chalk);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* ---- Навигация ---- */
.topbar {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0 1.25rem;
  height: var(--topbar);
  background: var(--ink);
  border-bottom: 1px solid var(--hair);
  position: sticky;
  top: 0;
  z-index: 10;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.brand .mark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  /* Марка — ссылка на главную, и браузер подчёркивал её по умолчанию.
     Подчёркнутый логотип читается как опечатка вёрстки. */
  text-decoration: none;
  color: var(--chalk);
  font-family: var(--display);
  font-weight: 700;
  /* 1.35rem. Макет держал марку в 15px, но в полосе 52px это читалось как
     подпись, а не как знак. Здесь она в 2.3 раза крупнее пунктов навигации
     (11.5px) — иерархия «марка → разделы» видна без раздумий. Потолок задаёт
     сама полоса: при высоте строки 1 и знаке 1.3em марка занимает 32px из 52,
     и дальше расти ей некуда без роста шапки. */
  font-size: 1.35rem;
  /* Onest шире Unbounded — прежние -0.02em слипали буквы марки. */
  letter-spacing: -0.01em;
  /* Свечение из макета. Белое — цвета не добавляет, а «светится» в этом
     интерфейсе и означает «вышло из ряда». Марка вышла из ряда по определению. */
  text-shadow: 0 0 14px rgba(236, 238, 241, 0.28);
}

/* Знак идёт в кегле надписи, а не в жёстких пикселях: иначе при любой правке
   кегля он отвязывается от буквы и садится на другую высоту. */
.mark-spike {
  width: 1.3em;
  height: auto;
  flex: none;
  /* Линия рисует сама себя один раз при открытии — тот же жест, что и
     расшифровка заголовка: страница открылась, прибор собрался. */
  stroke-dasharray: 50;
  stroke-dashoffset: 50;
  animation: markDraw 1.1s cubic-bezier(0.2, 0.7, 0.2, 1) 0.1s forwards;
}

@keyframes markDraw {
  to { stroke-dashoffset: 0; }
}

/* Движение — украшение поверх смысла: знак обязан быть виден и без него. */
@media (prefers-reduced-motion: reduce) {
  .mark-spike {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* Знак стоит слева от имени и живёт в одной строке с ним. */
.mark-spike {
  color: var(--chalk);
  flex: 0 0 auto;
}

.nav {
  display: flex;
  gap: 0.25rem;
  margin-left: auto;
}

.nav a {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--ash);
  text-decoration: none;
  padding: 0.4rem 0.7rem;
  border-radius: 0;
  /* Шапка звучит отдельно от текста: моноширинный Tektur — регистр прибора, и
     у него все пункты равной ширины, поэтому навигация не дёргается при смене
     активного раздела. Трекинг мельче, чем был: знаки Tektur сами широкие. */
  font-family: var(--nav);
  font-weight: 700;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  transition: color var(--fast), background var(--fast);
}

/* Знак секции. Приглушён, чтобы не спорить с именем раздела за внимание. */
.nav-glyph {
  font-family: var(--mono);
  font-size: 0.8em;
  opacity: 0.55;
}

.nav a:focus-visible {
  outline: 2px solid var(--chalk);
  outline-offset: 2px;
}

.nav a:hover {
  color: var(--chalk);
  background: transparent;
}

.nav a.active {
  color: var(--ink);
  background: var(--chalk);
}

/* ---- Оболочка страницы ---- */
/* Жёсткого капа страницы больше нет: на 4К он отдавал под пустоту больше
   тысячи пикселей с каждой стороны. Ширину ограничивает не страница, а сама
   таблица — по числу колонок (см. .layout ниже). */
.page {
  padding: var(--page-pad-top) var(--page-pad-x) var(--page-pad-bottom);
}

.page-head {
  display: flex;
  align-items: center;
  /* 0.7rem вместо 1rem. Заголовок, вкладки рынков, вкладки окон и правый блок
     состояния в сумме не влезали в 16:9 и переносились: строка вырастала с 30
     до 76 пикселей — 46 пикселей на пустоту под заголовком. Разбирается это
     не кеглем заголовка (он несёт характер интерфейса), а тем, что между
     блоками воздуха было больше, чем нужно, чтобы их различать. */
  gap: 0.7rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
}

/* Заголовок страницы — единственное место, где дисплейный шрифт получает
   масштаб. Прежде он жил на 1.15rem, и характер, заявленный на пяти процентах
   площади, не читался как характер. */
.page-head h1 {
  font-family: var(--display);
  font-size: 1.9rem;
  font-weight: 700;
  margin: 0;
  /* У Onest прописные широкие: -0.02em на этом кегле уже слипало бы. */
  letter-spacing: -0.01em;
  line-height: 1;
  text-transform: uppercase;
}

/* Живой индикатор автообновления. */
.status {
  display: inline-flex;
  align-items: center;
  /* 0.4rem и 0.75rem вместо 0.5rem и 0.8rem: блок занимал 930px из 1662
     доступных и один вытеснял шапку страницы на вторую строку. 0.75rem — тот
     же кегль, что у ячейки таблицы, то есть не мельче того, на что здесь
     смотрят дольше всего. */
  gap: 0.4rem;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.75rem;
  margin-left: auto;
}

/* Пульс канала. Нейтральный, не зелёный: «канал жив» — не движение рынка,
   а зелёный в этой системе означает только рост. */
.status .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--chalk);
  box-shadow: 0 0 0 0 rgba(236, 238, 241, 0.45);
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(236, 238, 241, 0.45); }
  70% { box-shadow: 0 0 0 6px rgba(236, 238, 241, 0); }
  100% { box-shadow: 0 0 0 0 rgba(236, 238, 241, 0); }
}

.status .count {
  color: var(--ash);
}

/* ---- Панель фильтров ----
   Разворачивается над таблицей и сдвигает её вниз, а не накрывает: оператор
   правит порог и тут же видит, как меняется выдача. Активность кодируется
   светлотой рамки и текста — акцентного цвета в интерфейсе нет. */
.filters-toggle.active {
  color: var(--chalk);
  border-color: var(--chalk);
}

.filters {
  border: 1px solid var(--hair);
  background: var(--panel);
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  display: grid;
  /* Автозаполнение, а не фиксированные колонки: на узком экране строки
     перестраиваются сами, медиазапрос под каждую ширину не нужен. */
  grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
  gap: 0.5rem 1.5rem;
}

.filter-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 30px;
}

.filter-row > label:first-child {
  flex: 0 0 8.5rem;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}

/* Панель колонок повторяет сетку фильтров: это соседние настройки одного вида,
   и разное оформление читалось бы как разная природа. */
.columns-panel {
  border: 1px solid var(--hair);
  background: var(--panel);
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 0.35rem 1rem;
}

.col-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 26px;
  cursor: pointer;
}

/* Выключенная колонка остаётся в списке приглушённой: её надо чем-то вернуть. */
.col-row.off {
  color: var(--ash);
}

.col-grip {
  color: var(--ash);
  cursor: grab;
  user-select: none;
}

/* Перетаскиваемая строка приглушается, чтобы под курсором был виден просвет —
   иначе непонятно, куда она встанет. */
.col-row.dragging {
  opacity: 0.4;
}

.col-reset {
  justify-self: start;
}

.filter-row input[type="text"],
.filter-row input[type="search"] {
  width: 5.5rem;
  min-width: 0;
  appearance: none;
  border: 1px solid var(--hair);
  background: var(--ink);
  color: var(--chalk);
  font-family: var(--mono);
  font-size: 0.78rem;
  padding: 0.28rem 0.45rem;
  border-radius: 0;
}

.filter-row input.f-text {
  width: 11.5rem;
}

.filter-row input::placeholder {
  color: #4a5058;
}

.filter-row input:focus-visible {
  outline: none;
  border-color: var(--chalk);
}

.filter-row .f-nocap,
.filter-row .f-noindex {
  font-size: 0.7rem;
  white-space: nowrap;
}

.filter-row .f-win {
  font-family: var(--mono);
  text-transform: none;
  letter-spacing: 0;
}

.chips {
  display: inline-flex;
  gap: 2px;
}

.chips button {
  appearance: none;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.72rem;
  padding: 0.24rem 0.55rem;
  border-radius: 0;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}

.chips button:hover {
  color: var(--chalk);
}

.chips button.active {
  color: var(--chalk);
  border-color: var(--chalk);
}

.filter-foot {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  border-top: 1px solid var(--hair);
  padding-top: 0.65rem;
  margin-top: 0.25rem;
}

.f-hint {
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.72rem;
}

/* ---- Сегментированный переключатель окон (сигнатура) ---- */
.windows,
.markets {
  display: inline-flex;
  gap: 2px;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
}

.windows button,
.markets button {
  appearance: none;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 0.38rem 0.75rem;
  border-radius: 0;
  cursor: pointer;
  transition: color var(--fast), background var(--fast), border-color var(--fast);
}

.windows button:focus-visible,
.markets button:focus-visible {
  outline: 2px solid var(--chalk);
  outline-offset: 2px;
}

.windows button:hover,
.markets button:hover {
  color: var(--chalk);
  border-color: var(--ash);
}

.windows button.active,
.markets button.active {
  color: var(--ink);
  background: var(--chalk);
  border-color: var(--chalk);
}

/* Рынок — более крупный уровень выбора, чем окно: отделяем группы друг от друга. */
.markets {
  margin-right: 0.5rem;
}

/* ---- Группа чекбоксов в форме (рынки алерта) ---- */
.checks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  /* Высота как у input рядом — иначе поле «Рынки» ломает сетку формы. */
  min-height: 34px;
}

.check {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ash);
  cursor: pointer;
  white-space: nowrap;
}

.check:hover {
  color: var(--chalk);
}

.check input {
  accent-color: var(--chalk);
  cursor: pointer;
}

/* ---- Таблица ---- */
.table-wrap {
  border: 1px solid var(--hair);
  border-radius: 0;
  overflow-x: auto;
  background: var(--panel);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
  /* Таблица наследовала line-height: 1.5 от body — интерлиньяж, рассчитанный
     на абзац текста, а не на строку чисел. Он один давал 4px пустоты в каждой
     строке: 25 строк — целый экран, отданный воздуху между цифрами. В макете
     ячейки идут на normal (~1.2). */
  line-height: 1.25;
}

thead th {
  position: sticky;
  top: 0;
  background: var(--panel);
  color: var(--ash);
  font-family: var(--sans);
  /* Подпись колонки читают один раз и запоминают, значение под ней — весь
     день. Поэтому кегль подписи уступает кеглю данных, как в макете (9.5px). */
  font-size: 0.6rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-align: right;
  padding: 0.45rem 0.8rem;
  border-bottom: 1px solid var(--hair);
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}

/* :first-of-type, а не :first-child. Колонки рисуются через <template x-for>
   (screener.html), и сам <template> остаётся первым потомком <tr> — значит
   :first-child не попадает ни в один реальный th, и заголовок «ТИКЕР»
   выравнивался вправо, тогда как значения под ним стоят влево.

   Здесь же тикер становится липким: ради тикеров таблица и существует, а при
   прокрутке вправо колонка уезжала за край, и строка превращалась в набор
   чисел без имени. Фон обязателен — без него содержимое проезжает насквозь. */
thead th:first-of-type {
  text-align: left;
  position: sticky;
  left: 0;
  z-index: 3;
  background: var(--panel);
}

tbody td.sym {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--panel);
}

/* Наведённая строка красит фон — липкая ячейка обязана повторить его, иначе на
   месте тикера остаётся полоса другого цвета. */
tbody tr:hover td.sym {
  background: var(--panel-2);
}

thead th:hover {
  color: var(--chalk);
}

/* Заголовок тянется мышью и меняет порядок колонок. Клик по нему по-прежнему
   сортирует: браузер не шлёт click после перетаскивания, поэтому два действия
   на одном элементе не спорят. */
thead th.dragging {
  opacity: 0.4;
}

thead th .caret {
  color: var(--chalk);
  font-size: 0.62rem;
  margin-left: 0.25rem;
}

tbody td {
  /* Плотность выверена по макету «Матрица SPIKE»: там ячейка 11.5px при
     padding 6px 12px и строке в 29px. Здесь на пиксель-другой свободнее —
     сознательно: макет рисовался под один экран, а на этой таблице сидят
     часами. Замер после правки: строка 30px против прежних 35px. */
  padding: 0.45rem 0.8rem;
  text-align: right;
  border-bottom: 1px solid var(--hair);
  font-family: var(--mono);
  font-size: 0.75rem;
  /* 500, а не 400: на плотной строке в 12px тонкое начертание моноширинного
     теряется, а цифры здесь — главное содержимое страницы. Вес настоящий,
     не синтетический: IBM Plex Mono вендорится переменным, 400-700. */
  font-weight: 500;
  color: var(--ash);
  white-space: nowrap;
}

/* Цена — то, на что смотрят чаще всего: держим в полной яркости. */
tbody td.px {
  color: var(--chalk);
}

tbody tr:last-child td {
  border-bottom: 0;
}

tbody tr:hover td {
  background: var(--panel-2);
}

td.sym {
  text-align: left;
  /* 700, как в макете: тикер — якорь строки, с него читают. */
  font-weight: 700;
  color: var(--chalk);
  letter-spacing: 0.01em;
}

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

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

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

.dash {
  color: var(--ash);
}

/* Метка интервала фандинга: поясняет ставку, а не спорит с ней за внимание. */
.fint {
  color: var(--ash);
  font-size: 0.82em;
}

/* ---- Состояния ---- */
.notice {
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.86rem;
}

.notice.error {
  color: var(--down);
}

[x-cloak] {
  display: none !important;
}

/* ---- Вход через Telegram ---- */
.auth {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.auth-name {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--chalk);
}

.auth-badge {
  font-family: var(--sans);
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ash);
  border: 1px solid var(--hair);
  padding: 0.15rem 0.4rem;
}

/* Кнопка держит палитру интерфейса, а не фирменный синий Telegram: цветность
   в этом проекте принадлежит рынку и больше ничему. */
.auth-btn {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--chalk);
  border: 1px solid var(--chalk);
  padding: 0.4rem 0.7rem;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: opacity var(--fast), color var(--fast);
}

.auth-btn:hover {
  opacity: 0.85;
}

.auth-btn:focus-visible {
  outline: 2px solid var(--chalk);
  outline-offset: 2px;
}

.auth-btn.ghost {
  color: var(--ash);
  background: transparent;
  border-color: var(--hair);
}

.auth-btn.ghost:hover {
  color: var(--chalk);
  opacity: 1;
}

/* ---- Заглушка вместо чужих данных ---- */
/* Гейт — единственное, что видит незалогиненный на закрытой странице. Прежде
   панель прижималась к верху, а под ней оставалось полэкрана пустоты: страница
   читалась как недогрузившаяся, а не как закрытая. Теперь она стоит в середине
   свободного места и ограничена по ширине — так это выглядит выбором. */
/* Два класса, а не один: .panel ниже по файлу задаёт margin-bottom, и при
   равной специфичности перебивал бы здешний auto — автоматическим оставался
   только верхний отступ, и панель утапливалась к низу вместо середины. */
.panel.gate {
  max-width: 34rem;
  /* margin: auto на флекс-элементе, а не justify-content на контейнере:
     центрировать надо ТОЛЬКО панель в оставшемся месте. Центрирование всего
     столбца утащило бы вниз и заголовок страницы, и он повис бы в пустоте. */
  margin: auto;
}

.page:has(.gate) {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--topbar) - var(--page-pad-top) - var(--page-pad-bottom));
}

.gate .panel-body {
  padding: 2.5rem 1.25rem;
  text-align: center;
}

.gate h2 {
  font-family: var(--display);
  font-size: 1rem;
  margin: 0;
  color: var(--chalk);
}

.gate p {
  color: var(--ash);
  font-size: 0.85rem;
  max-width: 46ch;
  margin: 0.6rem auto 1.4rem;
}

@media (max-width: 640px) {
  .page {
    padding: 0.75rem;
  }
  .brand .sub {
    display: none;
  }
  /* На узком экране имя уступает место кнопке: без имени вход понятен,
     без кнопки — нет. */
  .auth-name,
  .auth-badge {
    display: none;
  }
}

/* ---- Алерты: панель создания / редактирования ---- */
/* Панель — тот же прибор, что .wg. Корпус выдан ей самой, а не приписан в
   разметке: .panel используют алерты и журнал, и через неё язык получают обе
   страницы разом, а заодно всё, что появится позже. */
.panel {
  position: relative;
  border: 1px solid var(--hair);
  border-radius: 0;
  background: var(--panel);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
  margin-bottom: 1.25rem;
}

.panel::before,
.panel::after {
  content: "";
  position: absolute;
  width: 8px;
  height: 8px;
  pointer-events: none;
}

.panel::before {
  top: -1px;
  left: -1px;
  border-top: 1px solid var(--chalk);
  border-left: 1px solid var(--chalk);
}

.panel::after {
  bottom: -1px;
  right: -1px;
  border-bottom: 1px solid var(--chalk);
  border-right: 1px solid var(--chalk);
}

.panel-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.9rem;
  border-bottom: 1px solid var(--hair);
  /* Шапка светлее тела — как у остальных приборов: ярлык отделён от
     содержимого светлотой, а не ещё одной линией. */
  background: var(--panel-2);
  border-radius: 0;
}

.panel-head h2 {
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 600;
  margin: 0;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ash);
}

/* Риска слева была частью янтарной темы — акцента больше нет, убираем. */
.panel-head h2::before {
  content: none;
}

.panel-body {
  padding: 1rem;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.85rem 1rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.32rem;
  min-width: 0;
}

.field > label {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ash);
}

.field .hint {
  font-size: 0.68rem;
  color: var(--ash);
  font-family: var(--mono);
}

input[type="text"],
input[type="number"],
input[type="datetime-local"],
select {
  appearance: none;
  width: 100%;
  background: var(--ink);
  color: var(--chalk);
  border: 1px solid var(--hair);
  border-radius: 0;
  padding: 0.42rem 0.55rem;
  font-family: var(--mono);
  font-size: 0.85rem;
  transition: border-color 0.12s, box-shadow 0.12s;
}

select {
  cursor: pointer;
  /* Мини-каретка вместо системной стрелки. */
  background-image: linear-gradient(45deg, transparent 50%, var(--ash) 50%),
    linear-gradient(135deg, var(--ash) 50%, transparent 50%);
  background-position: right 0.75rem center, right 0.55rem center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 1.6rem;
}

input:focus,
select:focus {
  outline: none;
  border-color: var(--chalk);
}

.field label {
  font-family: var(--sans);
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ash);
}

input::placeholder {
  color: var(--ash);
}

/* Свитч «включён» — направленный зелёный = «работает», как в таблице. */
.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 34px;
  height: 19px;
  flex: none;
  cursor: pointer;
}

.switch input {
  position: absolute;
  opacity: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  cursor: pointer;
}

.switch .track {
  position: absolute;
  inset: 0;
  background: var(--panel-2);
  border: 1px solid var(--hair);
  border-radius: 999px;
  transition: background 0.14s, border-color 0.14s;
}

.switch .thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--ash);
  transition: transform 0.14s, background 0.14s;
}

.switch input:checked + .track {
  background: transparent;
  border-color: var(--chalk);
}

.switch input:checked + .track + .thumb {
  transform: translateX(15px);
  background: var(--chalk);
}

.switch input:focus-visible + .track {
  box-shadow: 0 0 0 2px var(--chalk);
}

/* ---- Кнопки ---- */
.actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-top: 1rem;
}

.btn {
  appearance: none;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 0.45rem 0.9rem;
  border-radius: 0;
  cursor: pointer;
  transition: background var(--fast), border-color var(--fast), color var(--fast),
    opacity var(--fast);
}

.btn:hover {
  color: var(--chalk);
  border-color: var(--ash);
}

.btn:focus-visible {
  outline: 2px solid var(--chalk);
  outline-offset: 2px;
}

.btn-primary {
  background: var(--chalk);
  border-color: var(--chalk);
  color: var(--ink);
}

.btn-primary:hover {
  background: var(--ash);
  border-color: var(--ash);
  color: var(--ink);
}

.btn-ghost {
  background: transparent;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-icon {
  padding: 0.3rem 0.55rem;
  font-size: 0.78rem;
  font-family: var(--mono);
}

/* Единственное место, где красный НЕ про рынок: необратимое действие важнее
   чистоты правила. */
.btn-danger:hover {
  border-color: var(--down);
  color: var(--down);
}

/* Ошибка валидации с бэка — рядом с формой, красным как .down. */
.form-error {
  margin-top: 0.85rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid rgba(229, 83, 75, 0.4);
  background: rgba(229, 83, 75, 0.08);
  border-radius: 0;
  color: var(--down);
  font-family: var(--mono);
  font-size: 0.8rem;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---- Форма алерта: блоки сверху вниз (имя, условия, где искать, пауза, проверка) ---- */
.form-block {
  padding: 1rem 0;
}

.form-block:first-child {
  padding-top: 0;
}

.form-block + .form-block {
  border-top: 1px solid var(--hair);
}

.block-title {
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ash);
  margin-bottom: 0.65rem;
}

/* Условие — строка-предложение: метрика → окно (если есть) → оператор → порог.
   Оверфлоу горизонтальный, а не перенос: строка обязана читаться целиком слева
   направо, не ломаясь посередине фразы. */
.form-block:has(.cond-row) {
  overflow-x: auto;
}

.cond-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0;
  width: max-content;
  min-width: 100%;
}

.cond-row select,
.cond-row input {
  width: auto;
  min-width: 3.2rem;
}

.conj {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--ash);
  width: 1.4rem;
  flex: none;
  text-align: center;
}

/* Крестик убирает строку — необратимо в рамках формы, поэтому на ховере
   краснеет как .btn-danger: единственное осознанное место, где красный не
   про рынок, а про «сотрёт условие». */
.kill {
  appearance: none;
  background: transparent;
  border: none;
  color: var(--ash);
  font-size: 1rem;
  line-height: 1;
  flex: none;
  padding: 0.2rem 0.4rem;
  cursor: pointer;
  transition: color var(--fast);
}

.kill:hover {
  color: var(--down);
}

.add {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  background: transparent;
  border: 1px dashed var(--hair);
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  padding: 0.4rem 0.75rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}

.add:hover {
  color: var(--chalk);
  border-color: var(--ash);
}

.add:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.dim {
  color: var(--ash);
}

/* Проверка: пересказ собранного алерта словами + счётчик совпадений.
   Ноль подходящих — наблюдение (редкий сетап), не отказ, поэтому тем же
   нейтральным цветом, что и остальной текст блока. */
.preview {
  font-family: var(--mono);
  font-size: 0.82rem;
  line-height: 1.7;
  color: var(--chalk);
  background: var(--panel-2);
  border-left: 2px solid var(--hair);
  padding: 0.7rem 0.9rem;
}

/* ---- Список алертов ---- */
.tag {
  display: inline-block;
  padding: 0.16rem 0.5rem;
  border-radius: 0;
  background: transparent;
  border: 1px solid var(--hair);
  color: var(--chalk);
  font-family: var(--mono);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}

td.params {
  text-align: left;
  color: var(--ash);
  font-size: 0.8rem;
  white-space: normal;
  max-width: 340px;
}

td.params .kv {
  color: var(--chalk);
}

td.center {
  text-align: center;
}

.row-actions {
  display: inline-flex;
  gap: 0.35rem;
  justify-content: flex-end;
}

tr.disabled td:not(.center):not(:last-child) {
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  .status .dot {
    animation: none;
  }
  * {
    transition: none !important;
  }
}

/* ---- Живой канал ---- */
/* Погасшая точка = канал цен молчит: гаснет и перестаёт пульсировать. */
.status .dot.off {
  background: var(--hair);
  box-shadow: none;
  animation: none;
}

/* ---- Вспышка изменения цены ---- */
.flash-up,
.flash-down {
  animation: flash 0.4s ease-out;
}

.flash-up {
  --flash-color: rgba(46, 166, 117, 0.35);
}

.flash-down {
  --flash-color: rgba(229, 83, 75, 0.35);
}

@keyframes flash {
  from {
    background: var(--flash-color);
  }
  to {
    background: transparent;
  }
}

/* Мигание — украшение поверх смысла: без него цифры те же. Глушим полностью. */
@media (prefers-reduced-motion: reduce) {
  .flash-up,
  .flash-down {
    animation: none;
  }
}

/* ---- Экстремум ----
   Обычное значение остаётся ash и тонет в поле. Аномальное выходит на полную
   яркость и вес. Приглушать обычное сильнее нельзя: ниже 4.5:1 начинается
   недоступный текст, а третьей ступени светлоты в этом диапазоне просто нет. */
tbody td.hot {
  color: var(--chalk);
  font-weight: 700;
}

tbody td.hot.up {
  color: var(--up-hot);
}

tbody td.hot.down {
  color: var(--down-hot);
}

/* Моушн — украшение поверх смысла: без него данные те же. Глушим полностью. */
@media (prefers-reduced-motion: reduce) {
  .status .dot {
    animation: none;
  }
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
  }
}

/* ---- Панель графика ----
   Таблица и панель — две колонки одной сетки в пропорции 5:3. Панель СДВИГАЕТ
   таблицу, а не накрывает её: строка, ради которой график открыт, обязана
   оставаться видимой. Пропорцию задаёт сетка, а не мышь: 5:3 держит читаемыми
   и колонки, и свечи, а ручка ширины спорила бы с фиксированной раскладкой. */
.layout {
  /* 190px на колонку — столько занимает «$226.63B» или «-12.34%» с отступами
     при 0.8rem моноширинного. Плюс 140px на тикер: он шире прочих.
     Ширина покупает БОЛЬШЕ ДАННЫХ, а не более крупные пиксели: колонок больше —
     таблица шире; колонок пять — она остаётся плотной. */
  /* 172px, а не прежние 190: ячейка похудела до 0.75rem при padding 0.8rem,
     и старая формула оставляла бы между колонками воздух, которого в них
     больше нет. */
  --table-max: calc(var(--cols, 6) * 172px + 130px);
  max-width: var(--table-max);
  /* Узкая таблица (у гостя их пять) встаёт по центру, широкая занимает экран
     целиком. Без этого гость на 4К получал таблицу, прижатую к левому краю, и
     пустую правую половину — то же расточительство, только несимметричное. */
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* НЕ stretch: без фиксированной высоты страницы (низкий или узкий экран)
     панель растянулась бы на всю высоту таблицы — это тысячи пикселей, и
     график внутри уехал бы за пределы экрана. */
  align-items: start;
  min-height: 0;
}

/* График открыт — колонки 5:3, и на ту же пропорцию растёт предел ширины:
   5/5 под таблицу, 3/5 под график, итого 8/5 от ширины, которую таблица
   просила себе одна. Нижняя граница трека: уже 360px свечи не читаются. */
.layout.has-chart {
  max-width: calc(var(--table-max) * 1.6);
  grid-template-columns: minmax(0, 5fr) minmax(360px, 3fr);
}

.layout > .table-wrap {
  min-width: 0;
}

.chart-panel {
  /* Липкая, с высотой по окну — это запасной путь для низкого или узкого
     экрана, где страница прокручивается по-старому. На нормальном экране
     высоту задаёт фиксированная раскладка (см. ниже). */
  position: sticky;
  top: calc(var(--topbar) + var(--page-pad-top));
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--topbar) - var(--page-pad-top) - var(--page-pad-bottom));
  border: 1px solid var(--hair);
  border-left: 0;
  background: var(--panel);
  min-width: 0;
}

.chart-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--hair);
}

.chart-sym {
  font-family: var(--mono);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--chalk);
}

.chart-hint {
  font-family: var(--sans);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ash);
  margin-right: auto;
}

.chart-body {
  flex: 1 1 auto;
  min-height: 0;   /* разрешаем сжиматься: иначе флекс раздувает панель */
  overflow: hidden;
}

#tv-chart {
  height: 100%;
}

/* Выделенная строка — светлотой, не цветом. */
tbody tr.selected td {
  background: var(--panel-2);
  border-bottom-color: var(--ash);
}

tbody tr.selected td.sym {
  box-shadow: inset 2px 0 0 var(--chalk);
}

tbody tr {
  cursor: pointer;
}

@media (max-width: 1100px) {
  /* На узком экране сдвигать некуда: панель уходит под таблицу. */
  .layout.has-chart {
    grid-template-columns: minmax(0, 1fr);
  }
  .chart-panel {
    position: static;
    height: 70vh;
    border-left: 1px solid var(--hair);
  }
}

/* ---- Скринер: страница-прибор ----------------------------------------
   Страница не прокручивается — прокручивается ТОЛЬКО таблица. Так было
   задумано с самого начала, но не сделано: у .table-wrap стоял один
   overflow-x, из-за чего блок становился скролл-контейнером без
   ограничения высоты. Вертикально ехала страница, а `thead { position:
   sticky; top: 0 }` липнул к коробке обёртки, а не к экрану, — и шапка
   таблицы уезжала вверх вместе со строками. Плюс правая колонка своей
   высоты: два независимых скролла на одном экране спорили друг с другом.

   Здесь высота задана явно, и таблица получает ровно остаток. График,
   шапка, лента и плитки видны всегда и никуда не едут.

   Порог: ниже 1101px по ширине график и так уходит под таблицу, ниже 700px
   по высоте фиксировать нечего — там страница прокручивается по-старому. */
@media (min-width: 1101px) and (min-height: 700px) {
  /* Округление высот даёт лишний пиксель прокрутки даже при точном расчёте. */
  body:has(.screener) {
    overflow: hidden;
  }

  .page:has(.screener) {
    height: calc(100dvh - var(--topbar));
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  .screener {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  /* Всё, кроме таблицы, держит свою высоту. Сжимается только раскладка. */
  .screener > .page-head,
  .screener > .guest-bar,
  .screener > .tape,
  .screener > .stat-row {
    flex: 0 0 auto;
  }

  /* Плитки — всегда одна строка. auto-fit при 300px переносил бы четвёртую
     плитку на второй ряд уже на 1200px, и ряд удваивался бы в высоте. */
  .screener > .stat-row {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  /* В фильтрах два десятка строк. Раздвигать ими страницу нельзя — они
     забирают не больше 45% экрана и дальше прокручиваются внутри себя.
     flex-shrink обязателен: на ноутбучных 768px 45vh + шапка + лента +
     плитки не оставляли таблице ничего (замер: 75px, одна строка), а
     фильтры настраивают, ГЛЯДЯ на выдачу — таблица в одну строку делает
     панель бессмысленной. Кто кому уступает, решает min-height ниже. */
  .screener > .filters,
  .screener > .columns-panel {
    flex: 0 1 auto;
    max-height: 45vh;
    overflow-y: auto;
  }

  /* Базис 280px, а не auto, и это не мелочь. При auto базисом раскладки
     становится ВЫСОТА ВСЕЙ ТАБЛИЦЫ (пятнадцать тысяч пикселей), сжатие
     распределяется пропорционально базисам — и панель фильтров схлопывалась
     до 35px, тогда как таблице доставалось всё. С явным базисом сжимаются
     обе, но min-height ниже замораживает таблицу первой: восемь строк
     остаются всегда, остальное отдаётся фильтрам. */
  .screener > .layout {
    flex: 1 1 280px;
    /* 280px — это шапка таблицы и восемь строк по 31px: тот минимум, ради
       которого панель фильтров вообще открывают, ведь её настраивают,
       ГЛЯДЯ на выдачу. */
    min-height: 280px;
    /* Здесь высота уже определена — таблица и график тянутся на неё оба. */
    align-items: stretch;
  }

  /* Тот самый скролл-контейнер: теперь с ограниченной высотой, и thead
     наконец липнет к его верхнему краю, а не уезжает со страницей. */
  .screener .table-wrap {
    min-height: 0;
    overflow: auto;
  }

  /* Липкость больше не нужна: панель и так стоит в фиксированной раскладке. */
  .screener .chart-panel {
    position: static;
    height: auto;
    min-height: 0;
  }
}

/* ================= Витрина =================
   Единственная страница с оркестрованным моушном. Появление идёт один раз при
   загрузке и ничего не двигает после: на витрине движение уместно, в терминале —
   мешает читать. */
.landing {
  max-width: 1080px;
  margin: 0 auto;
  padding: 2rem 0 0;
  position: relative;
  /* Явный контекст наложения. Без него холст с маской заводит собственный
     контекст и перекрывает текст героя, несмотря на меньший z-index, —
     проверено: скрытие холста возвращало заголовок. */
  isolation: isolate;
}

/* Холст живёт за героем и гасится к низу маской: текст обязан читаться, а
   дождь — оставаться фоном. Ширина шире контейнера, чтобы колонки не
   обрывались ровно по краю колонки текста. */
/* Дождь лежит под всем содержимым страницы и не двигается при прокрутке:
   это фон, а не часть потока. Панели непрозрачны, поэтому он виден только в
   зазорах и на полях — плотная таблица от движения не страдает.
   Маска гасит его к низу экрана: внизу обычно длинные списки, и рябь под ними
   мешала бы больше всего. */
/* --- Расшифровка заголовка ---------------------------------------------
   Слово проявляется из иероглифов при открытии страницы: данные пришли —
   заголовок собрался. Проход один, не цикл.

   Настоящая буква лежит В ПОТОКЕ с нулевой непрозрачностью и сама задаёт
   ширину, а знаки накрывают её сверху. Двух слоёв разной ширины здесь нет —
   значит и разъезжаться нечему.

   Заливки под знаками тоже нет: непрозрачная маска вырезала бы в дожде
   прямоугольники. */
.dc-w {
  display: inline-block;
  white-space: nowrap;
}

.dc-ch {
  position: relative;
  display: inline-block;
}

.dc-r {
  font-weight: inherit;
  opacity: 0;
  animation: dcReal 0.01s linear both;
  animation-delay: var(--t);
}

@keyframes dcReal {
  to { opacity: 1; }
}

.dc-m {
  position: absolute;
  inset: -0.08em -0.03em;
  overflow: hidden;
  animation: dcMask 0.01s linear both;
  animation-delay: var(--t);
}

@keyframes dcMask {
  to { opacity: 0; visibility: hidden; }
}

.dc-m > i {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-style: normal;
  font-family: var(--mono);
  font-size: 0.74em;
  color: var(--ash);
  opacity: 0;
  animation: dcRoll 0.27s steps(1) infinite;
}

.dc-m > i:nth-child(2) { animation-delay: 0.09s; }
.dc-m > i:nth-child(3) { animation-delay: 0.18s; }

@keyframes dcRoll {
  0%, 33% { opacity: 1; }
  34%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .dc-r { opacity: 1; animation: none; }
  .dc-m { display: none; }
}

/* --- Закрытые колонки гостя -------------------------------------------
   Колонка стоит на месте, но значения в ней зашифрованы. Именно шифр, а не
   размытие: сервер закрытых значений не отдаёт вовсе, размывать нечего, а
   размытие пришлось бы рисовать фальшивыми числами — прямо против правила
   «ни одного нарисованного числа». */
.enc {
  font-family: var(--mono);
  color: var(--ash);
  /* Мерцание показывает, что за шифром живые данные, а не прочерк. */
  animation: enc 2.6s steps(1) infinite;
}

@keyframes enc {
  0%, 100% { opacity: 0.42; }
  33% { opacity: 0.62; }
  66% { opacity: 0.3; }
}

thead th.locked {
  opacity: 0.65;
  cursor: default;
}

/* Замок рисованный, а не глиф: на 9px глиф не читается. */
.lock {
  color: var(--ash);
  margin-left: 0.3em;
  vertical-align: -1px;
}

@media (prefers-reduced-motion: reduce) {
  .enc { animation: none; opacity: 0.45; }
}

/* Переключатель фона. Стоит в шапке, потому что дождь идёт на всех страницах
   и виден гостю: профиль для этого не годится, его у гостя нет. */
.rain-toggle {
  border: 1px solid transparent;
  background: transparent;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1;
  padding: 0.3rem 0.45rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast), opacity var(--fast);
}

.rain-toggle:hover {
  color: var(--chalk);
  border-color: var(--hair);
}

.rain-toggle:focus-visible {
  outline: 2px solid var(--chalk);
  outline-offset: 2px;
}

/* Выключенный фон: знак приглушён и перечёркнут — состояние читается без
   наведения и без цвета. */
.rain-toggle.off {
  opacity: 0.4;
  text-decoration: line-through;
}

/* Подпись для чтеца с экрана: сам знак ему ничего не говорит. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

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

   Внутренний блик сверху вместо тени: тень пришлось бы красить, а свет —
   это яркость, которой в этом интерфейсе и выражается всё состояние. */
.wg {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--hair);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
}

.wg::before,
.wg::after {
  content: "";
  position: absolute;
  width: 8px;
  height: 8px;
  pointer-events: none;
}

.wg::before {
  top: -1px;
  left: -1px;
  border-top: 1px solid var(--chalk);
  border-left: 1px solid var(--chalk);
}

.wg::after {
  bottom: -1px;
  right: -1px;
  border-bottom: 1px solid var(--chalk);
  border-right: 1px solid var(--chalk);
}

/* Шапка прибора: ярлык слева, состояние справа. Ярлык набран мелко и
   разрядкой — он подписывает панель, а не спорит с её содержимым. */
.wg-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.45rem 0.7rem;
  border-bottom: 1px solid var(--hair);
  background: var(--panel-2);
}

.wg-label {
  font-family: var(--sans);
  font-size: 0.6rem;
  /* Разрядка 0.18em разбивает слово на отдельные буквы, и на 9.6px тонкие
     штрихи рассыпаются — вес компенсирует именно это. */
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ash);
  white-space: nowrap;
}

.wg-meta {
  font-family: var(--mono);
  font-size: 0.6rem;
  color: var(--ash);
  white-space: nowrap;
}

/* --- Развёртка ЭЛТ -----------------------------------------------------
   Постоянная сетка в 3px поверх всей страницы, на пороге видимости. Она не
   украшение: вместе с дождём и моноширинными числами это то, из чего
   складывается ощущение прибора, а не веб-страницы. */
.scanlines {
  position: fixed;
  inset: 0;
  z-index: 50;
  pointer-events: none;
  background: repeating-linear-gradient(
    180deg,
    rgba(236, 238, 241, 0.022) 0 1px,
    transparent 1px 3px
  );
}

@media (prefers-reduced-motion: reduce) {
  .scanlines { display: none; }
}

/* --- Плитки итогов -----------------------------------------------------
   Число плиток растёт с шириной экрана, размер — нет. Растянуть четыре плитки
   на 4К значило бы отдать 600px под трёхзначное число. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 0.875rem;
  margin-bottom: 0.6rem;
}

/* Высоту ряда задаёт самая высокая плитка — та, что со списком. Плитка с одним
   числом обязана поставить его по центру: прижатое к верхнему краю, оно
   читается как обрезанная вёрстка, а не как замысел. */
.stat-row > .wg {
  display: flex;
  flex-direction: column;
}

.stat-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Плотнее прежнего (0.5rem и 0.75/0.8/0.85rem): четыре числа занимали 105px
     высоты при 16px содержимого. Плитка остаётся плиткой — поля видно, — но
     воздух в ней стоил трёх строк таблицы. */
  gap: 0.35rem;
  padding: 0.5rem 0.8rem 0.55rem;
}

.stat-value {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.6rem;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--chalk);
}

.stat-sub {
  font-family: var(--sans);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ash);
}

/* --- Ритм таблицы ------------------------------------------------------
   Волосяная линия каждую пятую строку, а не зебра. На строке шириной в экран
   глазу нужно за что-то держаться, чтобы связать тикер слева с дельтой
   справа, — и для этого не требуется ни одного нового цвета. */
tbody tr:nth-child(5n) td {
  border-bottom-color: var(--edge);
}

/* Экстремум светится. Свечение белое — цвета оно не добавляет, а вес и
   яркость в этом интерфейсе и означают «вышло из ряда». */
tbody td.hot,
tbody td .hot {
  text-shadow: 0 0 14px rgba(236, 238, 241, 0.28);
}

/* --- Импульс по строке-аномалии ---------------------------------------
   Свет проходит слева направо ТОЛЬКО там, где метрика перешла порог. Это
   единственное движение в таблице, привязанное к смыслу: не «где красиво», а
   «где сигнал». Идёт редко и вразнобой, чтобы не превращаться в мигание. */
tbody tr.imp {
  background-image: linear-gradient(
    90deg,
    transparent,
    rgba(236, 238, 241, 0.09),
    transparent
  );
  background-size: 220% 100%;
  background-repeat: no-repeat;
  background-position: -120% 0;
  animation: impulse 7s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}

@keyframes impulse {
  0%, 62% { background-position: -120% 0; }
  86%, 100% { background-position: 220% 0; }
}

/* --- Бегущая лента -----------------------------------------------------
   Единственный элемент, который сам по себе кричит «терминал». Живёт под
   шапкой и несёт те же строки, что и таблица, — нарисованных чисел здесь нет. */
.tape {
  height: 34px;
  overflow: hidden;
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--hair);
  background: var(--panel);
  position: relative;
  z-index: 1;
}

.tape-run {
  display: inline-flex;
  gap: 2.2rem;
  padding-right: 2.2rem;
  white-space: nowrap;
  animation: tape 42s linear infinite;
}

@keyframes tape {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

.tape-item {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55rem;
  font-family: var(--mono);
  font-size: 0.68rem;
}

.tape-sym { color: var(--chalk); font-weight: 700; }
.tape-px { color: var(--ash); }

@media (prefers-reduced-motion: reduce) {
  tbody tr.imp { animation: none; background-image: none; }
  .tape-run { animation: none; }
}

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

   Порядок задаётся --i в разметке, а не :nth-child: блоки показываются
   условно (x-show), и порядковый номер в DOM не совпал бы с порядком на
   экране. */
.rise {
  animation: rise 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}

@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .rise { animation: none; }
}

/* --- Фишки вида: сначала вид, потом место -------------------------------
   Выбранная фишка «взводит» постановку — дальше остаётся указать грань. Так на
   окно уходит два действия вместо трёх, и пустого окна не возникает ни на миг:
   вид известен до того, как окно появилось. */
.add-kinds {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.add-kinds-label {
  font-family: var(--mono);
  font-size: 0.62rem;
  color: var(--ash);
  margin-right: 0.15rem;
}

.add-kind {
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}

.add-kind:hover:not(:disabled) {
  color: var(--chalk);
  border-color: var(--ash);
}

/* Взведённая фишка — светлотой, не краской: цвет принадлежит рынку. */
.add-kind.on {
  background: var(--chalk);
  border-color: var(--chalk);
  color: var(--ink);
}

.add-kind:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.add-kind:focus-visible {
  outline: 2px solid var(--chalk);
  outline-offset: 2px;
}

.rain-layer {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 0;
  /* Размер задан ЯВНО, и это не украшательство. Для замещаемого элемента
     (canvas) inset: 0 при width: auto размера не задаёт — он берёт собственный,
     из атрибута. А resize() пишет атрибут из измеренного CSS-размера, тот
     тянет за собой CSS-размер, ResizeObserver срабатывает снова — и холст
     удваивается каждый круг, пока браузер не откажет в памяти. Так и было:
     614400 x 307200 вместо 1456 x 835. */
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.45) 40%, rgba(0, 0, 0, 0.2) 100%);
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.45) 40%, rgba(0, 0, 0, 0.2) 100%);
}

/* Содержимое обязано лежать НАД дождём: без явного слоя фон проступал бы
   сквозь прозрачные участки шапки и полей. */
.topbar,
.page {
  position: relative;
  z-index: 1;
}

.hero {
  padding: 3rem 0 3.5rem;
  border-bottom: 1px solid var(--hair);
  position: relative;
  z-index: 1;
}

/* ---- Ширина рынка ---- */
.breadth {
  padding: 1.75rem 0 1.5rem;
  border-bottom: 1px solid var(--hair);
  position: relative;
  z-index: 1;
}

.breadth-bar {
  display: flex;
  height: 3px;
  gap: 2px;
}

.breadth-up {
  background: var(--up);
}

.breadth-down {
  background: var(--down);
}

.breadth-legend {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-top: 0.6rem;
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--ash);
}

.breadth-legend b {
  font-weight: 700;
}

.breadth-legend .up b {
  color: var(--up);
}

.breadth-legend .down b {
  color: var(--down);
}

.breadth-title {
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
}

/* ---- Борды лидеров ---- */
/* Зазор между приборами: без него четыре корпуса слипаются в одну плиту и
   уголки соседей встают вплотную. */
/* Зазор между приборами: без него четыре корпуса слипаются в одну плиту и
   уголки соседей встают вплотную. Нижняя грань секции тоже убрана — её роль
   теперь играют сами корпуса. */
.boards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.875rem;
  padding-bottom: 2.25rem;
  position: relative;
  z-index: 1;
}

.board {
  padding: 1.25rem 1.15rem 1.15rem;
  /* Разделителя больше нет: корпус даёт .wg, и своя левая грань была бы
     вторым контуром поверх первого. */
}

.board:first-child {
  border-left: 0;
  padding-left: 0;
}

.board h3 {
  font-family: var(--sans);
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--chalk);
  margin: 0 0 0.9rem;
}

/* Окно метрики — часть её названия, а не примечание: «рост» без «24ч»
   ничего не значит. */
.board h3 span {
  color: var(--ash);
  font-weight: 600;
  letter-spacing: 0.1em;
}

.board-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.3rem 0;
  font-family: var(--mono);
  font-size: 0.82rem;
  text-decoration: none;
  color: var(--ash);
  transition: color var(--fast);
}

.board-row:hover .board-sym,
.board-row:focus-visible .board-sym {
  color: var(--chalk);
}

.board-sym {
  color: var(--ash);
  letter-spacing: 0.02em;
  transition: color var(--fast);
}

.board-empty {
  font-family: var(--mono);
  font-size: 0.74rem;
  color: var(--ash);
  margin: 0;
}

.foot-credit {
  font-family: var(--mono);
  font-size: 0.72rem;
}

.eyebrow {
  font-family: var(--sans);
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--ash);
  margin: 0 0 1.25rem;
}

.hero h1 {
  font-family: var(--display);
  font-weight: 700;
  /* Кегль чуть ниже прежнего: «Инфраструктура» длиннее первого слова старого
     лозунга и на 3.1rem вылезала бы из строки. Ширина задана в ch, потому что
     жёсткого <br /> больше нет — расшифровка пересобирает заголовок по буквам
     и такой перенос потеряла бы. */
  font-size: clamp(1.8rem, 4.4vw, 2.8rem);
  line-height: 1.08;
  letter-spacing: -0.025em;
  max-width: 17ch;
  margin: 0 0 1.25rem;
  color: var(--chalk);
}

.lede {
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.6;
  /* Первый абзац витрины — не подпись, а текст, который читают целиком.
     Вторичной светлоты ему мало: держим на полшага ярче остального --ash. */
  color: #a8aeb8;
  font-weight: 400;
  max-width: 54ch;
  margin: 0 0 2.25rem;
}

/* Живое поле: та же механика, что в терминале — обычное тонет, экстремум выступает. */
.hero-field {
  border: 1px solid var(--hair);
  background: var(--panel);
  padding: 0.5rem 0 0;
  margin-bottom: 2rem;
}

.hf-row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 1.25rem;
  align-items: baseline;
  padding: 0.4rem 1rem;
  font-family: var(--mono);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
}

.hf-sym {
  color: var(--chalk);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.hf-px,
.hf-boost {
  color: var(--ash);
  text-align: right;
  min-width: 6.5rem;
}

.hf-boost.hot {
  color: var(--chalk);
  font-weight: 700;
}

.hf-empty,
.hf-note {
  padding: 0.9rem 1rem;
  font-family: var(--sans);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--ash);
  border-top: 1px solid var(--hair);
  margin: 0.5rem 0 0;
}

.hf-empty {
  border-top: 0;
  font-family: var(--mono);
}

.hero-cta {
  display: inline-block;
  text-decoration: none;
  padding: 0.7rem 1.4rem;
  font-size: 0.75rem;
}

/* ---- Живые цифры ---- */
.numbers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1px;
  background: var(--hair);
  border: 1px solid var(--hair);
  margin: 3rem 0;
}

.num {
  background: var(--ink);
  padding: 1.5rem 1.25rem;
}

.num-value {
  display: block;
  font-family: var(--mono);
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--chalk);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.num-label {
  display: block;
  margin-top: 0.4rem;
  font-family: var(--sans);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--ash);
}

/* ---- Что он считает ---- */
.does h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.3rem;
  text-transform: uppercase;
  letter-spacing: -0.02em;
  margin: 0 0 1.5rem;
}

.does-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.5rem;
}

.does-item h3 {
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--chalk);
  margin: 0 0 0.6rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--hair);
}

.does-item p {
  font-family: var(--sans);
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--ash);
  margin: 0;
}

.does-foot {
  font-family: var(--sans);
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--ash);
  margin: 2rem 0 0;
  max-width: 60ch;
}

.landing-foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-top: 3.5rem;
  padding: 1.25rem 0 2.5rem;
  border-top: 1px solid var(--hair);
  font-family: var(--sans);
  font-size: 0.72rem;
  color: var(--ash);
}

.landing-foot a {
  color: var(--chalk);
  text-decoration: none;
  border-bottom: 1px solid var(--hair);
}

/* ---- Появление ----
   Одна оркестровка на загрузку: заголовок, подзаголовок, затем строки поля
   волной. Дальше страница неподвижна. */
.hero .eyebrow,
.hero h1,
.hero .lede,
.hero .hero-cta,
.hero .hf-row {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.5s ease-out, transform 0.5s ease-out;
}

.hero.ready .eyebrow { opacity: 1; transform: none; transition-delay: 0.05s; }
.hero.ready h1 { opacity: 1; transform: none; transition-delay: 0.12s; }
.hero.ready .lede { opacity: 1; transform: none; transition-delay: 0.2s; }
.hero.ready .hero-cta { opacity: 1; transform: none; transition-delay: 0.3s; }

.hero.ready .hf-row {
  opacity: 1;
  transform: none;
  transition-delay: calc(0.34s + var(--i) * 0.045s);
}

/* Моушн — украшение поверх смысла. Содержимое обязано остаться на месте. */
@media (prefers-reduced-motion: reduce) {
  .hero .eyebrow,
  .hero h1,
  .hero .lede,
  .hero .hero-cta,
  .hero .hf-row {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

@media (max-width: 640px) {
  .landing {
    padding: 1rem 0 0;
  }
  .hero {
    padding: 1.5rem 0 2rem;
  }
}

/* ── Профиль ───────────────────────────────────────────────────────────── */

.profile-rows {
  display: grid;
  gap: 0.5rem;
}

.profile-rows > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid var(--hair);
}

.profile-rows > div:last-child {
  border-bottom: none;
}

.profile-rows b {
  font-family: var(--display);
  font-size: 0.9rem;
  color: var(--chalk);
}

/* ── Пресеты фильтров ──────────────────────────────────────────────────── */

.preset-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.presets .chips {
  flex-wrap: wrap;
  gap: 4px;
}

.preset {
  display: inline-flex;
}

/* Удаление прижато к имени: отдельная кнопка в ряду читалась бы как «удалить
   выделенное», а выделения здесь нет. */
.preset-del {
  border-left: none !important;
  padding: 0.24rem 0.4rem !important;
  color: var(--ash);
}

.preset-del:hover {
  color: var(--down);
}

.preset-add {
  display: inline-flex;
  gap: 0.35rem;
}

.preset-add input {
  width: 12rem;
}

/* ── Избранное ─────────────────────────────────────────────────────────── */

.star {
  cursor: pointer;
  color: var(--ash);
  transition: color var(--fast);
}

.star:hover,
.star.on {
  color: var(--warm, #e8b339);
}

/* ── Колонки под замком ────────────────────────────────────────────────── */

.col-row.locked {
  opacity: 0.55;
  cursor: default;
}

.col-lock {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--ash);
  text-decoration: none;
  white-space: nowrap;
}

.col-lock:hover {
  color: var(--chalk);
}

/* ── Гостевая граница ──────────────────────────────────────────────────── */

.guest-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.6rem 0.85rem;
  margin-bottom: 0.6rem;
  border: 1px solid var(--hair);
  background: var(--panel);
}

.guest-note {
  color: var(--ash);
  font-size: 0.8rem;
  max-width: 70ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* ---- Стол ---- */
/* Поле держит окна в абсолютных координатах: они считаются из дерева, и
   раскладка не зависит от порядка элементов в DOM — а он не меняется никогда. */
/* Полотно стола отделено от окон слоем, а не рамкой: рамка добавила бы
   четвёртую линию к трём уже существующим (грань окна, шапка, ряды таблицы). */
/* Дес — рабочая поверхность во весь экран, а не документ на странице: отступы
   .page ему возвращаются под окна.

   Высоту поля считает flex, а не calc(). Прежде здесь стояло
   calc(100vh - 52px - 3.5rem), то есть шапке отводилось ровно 56px, — а
   .desk-head h1 остался без правила и брал браузерный дефолт в ~79px. Поле
   уезжало за экран, нижний ряд окон срезало, страница получала вертикальную
   прокрутку. Магическое число всегда рано или поздно разойдётся с тем, что
   шапка занимает на самом деле: она несёт вкладки, счётчики и сообщения об
   ошибках переменной длины. Поэтому шапка берёт сколько нужно, а поле —
   ровно остаток. */
.desk {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--topbar));
  /* Гасим отступы .page теми же переменными, а не их копией числом: копия уже
     разошлась с оригиналом и увела поле на восемь пикселей под шапку. */
  margin: calc(-1 * var(--page-pad-top)) calc(-1 * var(--page-pad-x))
    calc(-1 * var(--page-pad-bottom));
  padding: 0.5rem 0.6rem;
}

/* В стопке (узкий экран) окна идут колонкой и страница обязана прокручиваться:
   фиксированная высота здесь отрезала бы всё ниже первого экрана. */
.desk.is-stacked {
  height: auto;
}

.desk.is-stacked .desk-field {
  flex: none;
}

.desk-field {
  position: relative;
  flex: 1 1 auto;
  /* Без min-height:0 flex-элемент не сжимается меньше содержимого, и поле
     снова выдавило бы страницу — ту самую прокрутку, ради которой всё это. */
  min-height: 0;
  background:
    radial-gradient(120% 80% at 50% 0%, rgba(20, 23, 28, 0.9), transparent 70%),
    var(--ink);
}

.desk-field.stacked {
  /* Дети (.pane) — position: absolute и не дают auto-контейнеру высоты сами
     по себе: без min-height поле схлопывается в ноль, measure() читает
     clientHeight равный нулю, а строки (Layout.stackRects кладёт их на
     i * (rowHeight + gap)) держать нечему — страница до них не прокручивается.
     Реальную высоту (число окон * высота строки) даёт inline
     style="min-height" из desk.html (Alpine, см. stackedMinHeight()) —
     здесь только запасной вариант на случай, если он почему-то не применился. */
  height: auto;
  min-height: 100%;
}

.pane {
  position: absolute;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--hair);
  /* Тот же внутренний блик, что у остальных приборов: окно деса — такой же
     корпус, как таблица скринера или борт витрины. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
  overflow: hidden;
  transition: border-color var(--fast), background var(--fast);
}

/* Уголки-скобки окна. Отдельными элементами, а не ::before/::after: у .pane
   псевдоэлементы уже заняты полосой перетаскивания, а overflow: hidden всё
   равно обрезал бы их по грани. Поэтому рисуем внутренними углами. */
.pane::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 8px;
  height: 8px;
  border-top: 1px solid var(--chalk);
  border-left: 1px solid var(--chalk);
  pointer-events: none;
  z-index: 4;
}

.pane::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: 8px;
  height: 8px;
  border-right: 1px solid var(--chalk);
  border-bottom: 1px solid var(--chalk);
  pointer-events: none;
  z-index: 4;
}

/* Активное окно — грань светлее, шапка плотнее, заголовок ярче. Фокус не
   красится: цвет занят рынком, а состояние окна выражают яркость и вес. */
.pane.active {
  border-color: #2c313a;
  background: var(--panel-2);
}

.pane.active .pane-head {
  border-bottom-color: #2c313a;
}

.pane.active .pane-title {
  color: var(--chalk);
}

.pane-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.5rem;
  height: 26px;
  border-bottom: 1px solid var(--hair);
  font-size: 0.68rem;
}

.pane-title {
  font-family: var(--display);
  letter-spacing: -0.01em;
  color: var(--ash);
  margin-right: auto;
}

.pane-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

.pane-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--mono);
  font-size: 0.72rem;
}

.pane-table th {
  position: sticky;
  top: 0;
  background: var(--panel);
  border-bottom: 1px solid var(--hair);
  color: var(--ash);
  font-weight: 600;
  text-align: right;
  padding: 0.25rem 0.4rem;
}

/* Колонка тикера липкая: ради тикеров таблица и существует, а при прокрутке
   вправо она уезжала за край — строка превращалась в набор чисел без имени.
   Фон обязателен: без него содержимое проезжает НАСКВОЗЬ под липкой ячейкой. */
.pane-table th:first-child,
.pane-table td:first-child {
  text-align: left;
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--panel);
}

/* Заголовок липнет сразу по двум осям, поэтому перекрывает и строки, и
   соседние заголовки. */
.pane-table th:first-child {
  z-index: 3;
}

/* Выбранную и наведённую строку липкая ячейка подхватывает сама: у правил
   .pane-table tr.picked td и .pane-table tbody tr:hover td специфичность выше,
   и их фон перекрывает здешний. Отдельные правила были бы мёртвым кодом.
   Активное окно — другое дело: его фон задан на .pane, а не на ячейке. */
.pane.active .pane-table th:first-child,
.pane.active .pane-table td:first-child { background: var(--panel-2); }

.pane-table td {
  padding: 0.2rem 0.4rem;
  text-align: right;
  border-bottom: 1px solid rgba(27, 30, 36, 0.5);
}

/* Выбранная строка — светлотой, не краской: цвет занят направлением цены. */
/* Выбранная строка — та, что сейчас на графике. В списке из двадцати пяти
   одинаковых строк её надо находить не вглядываясь, поэтому берём сразу три
   признака: фон, полную яркость текста и светлую полосу слева. Краской
   нельзя — цвет в этом интерфейсе принадлежит рынку. */
.pane-table tr.picked td {
  color: var(--chalk);
  background: var(--panel-2);
  border-bottom-color: var(--edge);
}

.pane-table tr.picked td:first-child {
  box-shadow: inset 2px 0 0 var(--chalk);
  font-weight: 600;
}

/* Наведение слабее выбора: иначе строка под курсором спорила бы с выбранной
   за внимание, и обе читались бы одинаково. */
.pane-table tbody tr:hover td {
  background: rgba(20, 23, 28, 0.6);
  cursor: pointer;
}

.pane-table tbody tr.picked:hover td {
  background: var(--panel-2);
}

/* Гостю сервер отдаёт только верхушку списка. Молчать об этом нельзя: человек
   решит, что на рынке всего двадцать пять инструментов. */
.pane-more {
  padding: 0.45rem 0.4rem;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.66rem;
  border-top: 1px solid var(--hair);
}

/* Полоса захвата границы: сама граница — волосяная линия, а тянуть мышью
   по ней без прицеливания нельзя, поэтому зона курсора шире линии. */
/* Переключатель режима. Включённое состояние — светлотой и рамкой, как везде
   в этом интерфейсе: краской нельзя, цвет занят направлением цены. */
.desk-edit {
  background: transparent;
  border: 1px solid var(--hair);
  color: var(--ash);
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 0.35rem 0.7rem;
  margin-left: 0.4rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}

.desk-edit:hover,
.desk-edit:focus-visible {
  color: var(--chalk);
  border-color: var(--edge);
}

.desk-edit.on {
  color: var(--ink);
  background: var(--chalk);
  border-color: var(--chalk);
}

/* В работе рамки окон уходят почти в фон: экран занят рынком, а не сеткой.
   В правке — проступают, потому что именно их и двигают. */
.desk .pane {
  border-color: #101318;
}

.desk.editing .pane {
  border-color: var(--hair);
}

.desk-add-pane {
  background: transparent;
  border: 1px solid var(--hair);
  color: var(--ash);
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 0.35rem 0.7rem;
  margin-left: 0.6rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}

.desk-add-pane:hover,
.desk-add-pane:focus-visible {
  color: var(--chalk);
  border-color: var(--edge);
}

/* Заголовок окна — кнопка настроек. Выглядит как текст, пока на него не навели:
   в работе это подпись, и только под курсором становится ясно, что он живой. */
.pane-title {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-right: auto;
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  color: var(--ash);
  font-family: var(--display);
  font-size: 0.68rem;
  letter-spacing: -0.01em;
  cursor: pointer;
  text-align: left;
  transition: color var(--fast);
}

.pane-title:hover,
.pane-title:focus-visible,
.pane-title.on {
  color: var(--chalk);
}

/* Связь с таблицей — словом, а не значком: ⬤ и ⚓ надо запоминать. */
.pane-follow {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  color: var(--ash);
}

.pane-settings {
  border-bottom: 1px solid var(--hair);
  background: var(--panel-2);
  padding: 0.5rem 0.6rem;
}

.pane-set-rows {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.pane-set-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.pane-set-k {
  flex: 0 0 5.5rem;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.pane-set-row button {
  appearance: none;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.68rem;
  padding: 0.18rem 0.45rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}

.pane-set-row button:hover,
.pane-set-row button:focus-visible {
  color: var(--chalk);
}

.pane-set-row button.on {
  color: var(--chalk);
  border-color: var(--chalk);
}

.pane-set-sym {
  width: 9rem;
  appearance: none;
  border: 1px solid var(--hair);
  background: var(--ink);
  color: var(--chalk);
  font-family: var(--mono);
  font-size: 0.72rem;
  padding: 0.2rem 0.4rem;
  text-transform: uppercase;
}

.pane-set-sym:focus-visible {
  outline: none;
  border-color: var(--chalk);
}

.pane-set-hint {
  margin: 0.1rem 0 0;
  color: var(--ash);
  font-size: 0.66rem;
}

/* Заголовок колонки — кнопка сортировки. Оформление то же, что у заголовка:
   он и остаётся заголовком, просто на него можно нажать. */
.pane-table th button {
  appearance: none;
  background: transparent;
  border: 0;
  padding: 0;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
}

.pane-table th button:hover,
.pane-table th button:focus-visible {
  color: var(--chalk);
}

/* Пустое окно спрашивает, чем стать. Крупные кнопки, а не мелкие значки:
   это первое, что человек видит в новом окне, и оно же объясняет, какие
   виджеты вообще бывают. */
.pane-choose {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  height: 100%;
  padding: 1rem;
}

.pane-choose-q {
  margin: 0;
  color: var(--ash);
  font-size: 0.78rem;
  text-align: center;
}

.pane-choose-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
}

.pane-choose-row button {
  appearance: none;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--chalk);
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 0.5rem 0.9rem;
  cursor: pointer;
  transition: border-color var(--fast), color var(--fast);
}

.pane-choose-row button:hover:not(:disabled),
.pane-choose-row button:focus-visible:not(:disabled) {
  border-color: var(--edge);
}

/* Потолок графиков достигнут: кнопка остаётся видимой, но не притворяется
   рабочей — иначе нажатие молча ничего не делало бы. */
.pane-choose-row button:disabled {
  color: var(--ash);
  cursor: not-allowed;
}

/* Крест «куда добавить окно»: четыре стрелки, разложенные крестом под шапкой
   окна. Направление читается и со стрелки, и с её места в кресте — слово
   «вертикально», которое понимается двояко, не нужно вовсе.

   Крест висит под шапкой, а не по граням окна: у таблицы левый край занят
   колонкой тикеров, и кнопка там перекрывала бы данные — «＋» вплотную к
   тикеру читается как испорченная строка, а не как элемент управления. */
/* Причина отказа раскладки. Не ошибка инфраструктуры, а ответ на действие,
   поэтому цветом не кричит: тот же ash, что у прочих подписей шапки. */
/* Подсказка про отмену. В правке человек делает необратимые на вид вещи —
   про Ctrl+Z надо сказать один раз и не кричать. */
/* Знак «здесь есть настройки» у заголовка окна. Приглушён сильнее подписи:
   он сообщает о возможности, а не спорит с именем окна за внимание. */
.pane-title-caret {
  color: var(--ash);
  font-size: 0.62rem;
  opacity: 0.55;
  margin-left: 0.15rem;
  transition: opacity var(--fast);
}

.pane-title:hover .pane-title-caret,
.pane-title.on .pane-title-caret {
  opacity: 1;
}

/* Поле переименования деса живёт прямо во вкладке: ширина по содержимому,
   чтобы вкладки не прыгали при входе в правку имени. */
.desk-tab-input {
  width: 9ch;
  border: 1px solid var(--chalk);
  background: var(--ink);
  color: var(--chalk);
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 0.25rem 0.4rem;
}

.desk-tab-input:focus-visible {
  outline: none;
}

/* Признак того, что у вкладки есть действия. Правый клик их открывает и так,
   но о нём не догадаться, а на тач-экране его нет вовсе. */
.desk-tab-menu {
  border: 0;
  background: transparent;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.8rem;
  line-height: 1;
  padding: 0 0.25rem;
  cursor: pointer;
  transition: color var(--fast);
}

.desk-tab-menu:hover { color: var(--chalk); }

.desk-hint {
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--ash);
}

/* Выход из ещё не сделанного выбора вида. */
.pane-choose-cancel {
  margin-top: 0.8rem;
  border: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.4rem 0.8rem;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast);
}

.pane-choose-cancel:hover {
  color: var(--chalk);
  border-color: var(--ash);
}

.desk-refusal {
  font-family: var(--sans);
  font-size: 0.72rem;
  color: var(--ash);
  border-left: 1px solid var(--hair);
  padding-left: 0.7rem;
}

/* Мишень «куда добавить окно» — во ВСЁ окно, а не крестик под шапкой.
   Прежний крест висел на top:28px right:4px внутри overflow:hidden, поэтому в
   окне ниже ~102px нижняя стрелка была физически недостижима, а мишени в 26px
   не росли вместе с окном. Полосе перетаскивания границы мишень не мешает:
   она поверх (z-index выше .pane-edge) и живёт только пока открыта. */
.pane-add-menu {
  position: absolute;
  inset: 0;
  z-index: 7;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr;
  gap: 2px;
  padding: 2px;
  background: rgba(8, 9, 11, 0.82);
}

.pane-add {
  border: 1px dashed var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  transition: color var(--fast), border-color var(--fast), background var(--fast);
}

.pane-add:hover,
.pane-add:focus-visible {
  color: var(--chalk);
  border-color: var(--chalk);
  border-style: solid;
  background: var(--panel-2);
}

/* Место мишени в сетке повторяет сторону, которую она означает: верхняя занимает
   всю верхнюю полосу, боковые — середину по краям. */
.pane-add.top { grid-column: 1 / 4; grid-row: 1; }
.pane-add.left { grid-column: 1; grid-row: 2; }
.pane-add.right { grid-column: 3; grid-row: 2; }
.pane-add.bottom { grid-column: 1 / 4; grid-row: 3; }

/* Середина — выход. Без неё закрыть мишень можно только промахом мимо окна. */
.pane-add-cancel {
  grid-column: 2;
  grid-row: 2;
  border: 0;
  background: transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}

.pane-add-cancel:hover {
  color: var(--chalk);
}

/* Полоса — это ЗОНА ЗАХВАТА (11px), а не видимый шов. Сама она прозрачна;
   линию внутри рисует ::after. Прежде вся полоса красилась фоном на наведении,
   и при расширении зоны это была бы жирная плита вместо границы. */
.pane-edge {
  position: absolute;
  z-index: 5;
  /* Без этого палец на границе прокручивает страницу, а не тянет её: браузер
     считает жест своим раньше, чем до нас доходит первый pointermove. */
  touch-action: none;
}

/* Видимая линия. В правке она есть ВСЕГДА, а не только под курсором: иначе о
   том, что границу можно тянуть, догадаться неоткуда — а полосы и живут только
   в правке. Это и была причина «дес не регулируется»: механизм работал, но
   ничто на него не указывало. */
.pane-edge::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  /* Не --edge: разделитель и рукоятка — разные вещи. Эту линию ищут глазами,
     чтобы схватить, поэтому она светлее любой другой линии на странице. */
  background: var(--grip);
  transition: background var(--fast), box-shadow var(--fast);
}

.pane-edge.lr {
  cursor: col-resize;
}

.pane-edge.lr::after {
  width: 1px;
  height: 100%;
}

.pane-edge.tb {
  cursor: row-resize;
}

.pane-edge.tb::after {
  height: 1px;
  width: 100%;
}

/* Под курсором линия белеет и утолщается: понятно, что схвачено именно её. */
.pane-edge:hover::after {
  background: var(--chalk);
  box-shadow: 0 0 0 1px var(--chalk);
}

/* Движение — украшение, а не смысл: тому, кто отключил анимации в системе,
   мы его не навязываем. Сам факт наведения/фокуса остаётся виден и без него. */
@media (prefers-reduced-motion: reduce) {
  .pane, .pane-edge {
    transition: none;
  }
}

/* График: iframe TradingView занимает всё тело окна. */
.pane-chart, .pane-chart > div:first-child { height: 100%; }

.pane-tools { display: flex; gap: 0.15rem; }

.pane-btn {
  background: transparent;
  border: 0;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 0.66rem;
  padding: 0.1rem 0.3rem;
  cursor: pointer;
}

/* Включённое состояние тумблера/кнопки — светлотой, не краской: цвет
   принадлежит рынку, а не элементам управления столом. */
.pane-btn.on { color: var(--chalk); }
.pane-btn:hover { color: var(--chalk); }

/* Оверлей на время жеста: перехватывает указатель поверх окон (в том числе
   iframe-графиков), чтобы window не теряла pointermove/pointerup, когда курсор
   идёт через чужой документ просмотра. Ни цвета, ни контраста — сам жест
   уже виден по сдвигу границы, оверлей не должен быть заметен глазу. */
/* Заслонка над телом окна в режиме правки. Окно с графиком нельзя было
   сделать активным мышью вовсе: mousedown внутри чужого iframe до .pane не
   доходит, и подсветка активного окна на графиках не зажигалась никогда.
   В правке содержимое всё равно не трогают — там расставляют окна. */
.pane-guard {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: transparent;
}

.desk-drag-overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  background: transparent;
}

.desk-drag-overlay.lr {
  cursor: col-resize;
}

.desk-drag-overlay.tb {
  cursor: row-resize;
}

.desk-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex: 0 0 auto;
  min-height: 34px;
  margin-bottom: 0.5rem;
}

/* Заголовок деска живёт в .desk-head, куда правило .page-head h1 не достаёт, —
   и без собственного правила брал браузерный дефолт: 2em плюс margin 0.67em,
   почти 79px под одно слово на рабочем экране.
   Шрифт тот же дисплейный, что у заголовков всех страниц: дес обязан звучать
   тем же голосом. Меняется только масштаб — на рабочей поверхности заголовок
   подписывает раздел, а не открывает документ. */
.desk-head h1 {
  margin: 0;
  font-family: var(--display);
  /* Было 0.95rem — вдвое мельче заголовков остальных страниц (1.9rem), и дес
     на их фоне читался как подраздел. 1.6rem держит паритет и при этом влезает
     в 30px шапки без роста: line-height здесь 1, а не полтора. */
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  white-space: nowrap;
}

.desk-save-error {
  font-size: 0.75rem;
  color: var(--ash);
}

.pane-card { padding: 0.8rem; font-family: var(--mono); }

.card-sym {
  font-family: var(--display);
  font-size: 0.8rem;
  color: var(--ash);
  letter-spacing: 0.04em;
}

.card-price { font-size: 1.9rem; color: var(--chalk); line-height: 1.2; }

.card-row {
  display: flex;
  justify-content: space-between;
  font-size: 0.74rem;
  color: var(--ash);
  border-top: 1px solid var(--hair);
  padding: 0.3rem 0;
}

.card-empty { color: var(--ash); font-size: 0.74rem; }

.desk-tabs { display: flex; align-items: center; gap: 0.15rem; margin-left: auto; }

.desk-tab { display: flex; align-items: center; position: relative; }

/* Меню стола по правому клику. Пункты словами, а не значками: удаление —
   необратимое действие, и оно обязано называться. */
.desk-menu {
  position: absolute;
  z-index: 20;
  top: 100%;
  right: 0;
  min-width: 11rem;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  border: 1px solid var(--edge);
  padding: 2px;
}

.desk-menu button {
  appearance: none;
  background: transparent;
  border: 0;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.72rem;
  text-align: left;
  padding: 0.4rem 0.6rem;
  cursor: pointer;
  transition: color var(--fast), background var(--fast);
}

.desk-menu button:hover:not(:disabled),
.desk-menu button:focus-visible:not(:disabled) {
  color: var(--chalk);
  background: var(--panel-2);
}

.desk-menu button:disabled {
  color: #3d434c;
  cursor: not-allowed;
}

.desk-tab button {
  background: transparent;
  border: 0;
  border-bottom: 1px solid transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-weight: 600;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 0.35rem 0.6rem;
  cursor: pointer;
}

/* Активная вкладка — светлотой и волосяной линией под ней, без акцентного
   цвета: цвет принадлежит рынку. */
.desk-tab.active button { color: var(--chalk); border-bottom-color: var(--chalk); }
.desk-tab-x, .desk-tab-add {
  background: transparent;
  border: 0;
  color: var(--ash);
  font-size: 0.66rem;
  padding: 0.2rem 0.4rem;
  cursor: pointer;
}
.desk-tab-x:hover, .desk-tab-add:hover { color: var(--chalk); }


/* ---- Ширина рынка: плитка скринера ----
   Имена областные (stat-breadth-*), а НЕ .breadth: под этим именем на лендинге
   уже живёт свой, более крупный компонент выше по файлу. Общее имя означало бы,
   что два разных состава правил дерутся за один селектор, и побеждал бы стоящий
   ниже — то есть мой, молча переделывая лендинг.
   Полоса, а не два числа: отношение выросших к упавшим считывается формой
   раньше, чем прочитываются цифры. Цвет здесь законен — это ровно направление
   цены, единственное, чему он в этом интерфейсе принадлежит. */
.stat-breadth {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.stat-breadth-bar {
  display: flex;
  height: 6px;
  gap: 2px;
  background: var(--panel-2);
}

.stat-breadth-up { background: var(--up); }
.stat-breadth-down { background: var(--down); }

.stat-breadth-legend {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.7rem;
}

.stat-breadth-legend .up { color: var(--up); }
.stat-breadth-legend .down { color: var(--down); }

/* Плитка без данных: прочерк держит высоту, чтобы ряд плиток не прыгал, когда
   метрики появятся. */
.stat-value-muted { color: var(--ash); }

/* Строка сводки. Ссылка или кнопка, а не div: по ней переходят — на график
   инструмента и в журнал. */
.wg-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  width: 100%;
  padding: 0.45rem 0.7rem;
  border: 0;
  border-top: 1px solid var(--hair);
  background: transparent;
  text-align: left;
  text-decoration: none;
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--chalk);
  cursor: pointer;
}

/* Первая строка примыкает к шапке корпуса — её линия уже нарисована. */
.wg-row:first-child { border-top: 0; }

.wg-row:hover,
.wg-row:focus-visible {
  background: var(--panel-2);
}

.wg-row-k {
  color: var(--chalk);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wg-row-sub,
.wg-row-t {
  color: var(--ash);
  font-size: 0.66rem;
}

/* Значение-экстремум. В макете оно зелёное, здесь — нет: зелёный в этом
   интерфейсе означает РОСТ ЦЕНЫ, а буст объёма — не направление. Выделяем тем
   же, чем выделен экстремум в таблице: весом и белым свечением. */
.wg-row-v {
  font-weight: 700;
  color: var(--chalk);
  text-shadow: 0 0 14px rgba(236, 238, 241, 0.28);
  white-space: nowrap;
}

.wg-empty {
  margin: 0;
  padding: 0.7rem;
  color: var(--ash);
  font-size: 0.7rem;
}

/* --- Список внутри плитки итогов ---------------------------------------
   Прежде эти корпуса стояли в правой колонке, где вертикаль была бесплатной.
   В горизонтальной плитке каждая строка списка отнимается у таблицы, поэтому
   строка ужата до 25px: четыре записи вместе с шапкой держат плитку в 130px
   против 85px у плитки с одним числом. */
.stat-row .wg-list {
  display: flex;
  flex-direction: column;
}

.stat-row .wg-row {
  padding: 0.28rem 0.7rem;
  font-size: 0.68rem;
  line-height: 1.35;
}

/* Подпись срабатывания занимает всё, что осталось между тикером и временем, и
   обрезается многоточием: длина имени метрики не должна ломать строку. */
.stat-row .wg-row-sub {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Пустой список — по центру плитки, как и одинокое число у соседей: высоту
   ряда задаёт самая высокая плитка, и прижатая к верху строка читалась бы
   как обрезанная вёрстка. */
.stat-row .wg-empty {
  margin: auto 0;
}


/* ---- Каскад появления окон деска ----
   Проявляется СОДЕРЖИМОЕ окна, а не корпус. Ставить .rise на .pane нельзя:
   он двигает transform, а положение и размер окна каждый кадр пишет скрипт
   (terminal.js) — окно залипает на смещении и вылезает за поле.

   Намеренно БЕЗ animation-fill-mode. При fill: both кадр «до старта» — это
   opacity: 0, и стоит анимации не отыграть (замороженный таймлайн в фоновой
   вкладке, throttling), рабочий стол открылся бы пустым. Без fill элемент
   лежит в своём естественном виде — видимым, — а анимация лишь добавляет
   появление. Отказ такой пары всегда в сторону «данные видно». */
.pane > .pane-head,
.pane > .pane-body {
  animation: paneIn 0.42s cubic-bezier(0.2, 0.7, 0.2, 1);
  animation-delay: calc(var(--i, 0) * 70ms);
}

@keyframes paneIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .pane > .pane-head,
  .pane > .pane-body {
    animation: none;
  }
}

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

   Скелетон появляется вместе с остальным героем — .hero .hf-row держит его на
   opacity: 0 до .ready, как и настоящие строки. Раньше ready ставился только
   после загрузки, и увидеть сообщение о ней было нельзя в принципе. */
.hf-skel {
  /* Строки скелетона приходят все разом: это одно состояние, а не поток
     данных, и рассыпать его каскадом значило бы изображать загрузку по частям,
     которой нет. */
  transition-delay: 0.34s !important;
}

.sk {
  display: block;
  /* Полоса тонкая, но СТРОКА обязана быть той же высоты, что настоящая, иначе
     корпус подпрыгнет в момент прихода данных. Замер до правки: строка
     скелетона 21px против 32px у настоящей — 88px скачка на восьми строках.
     Поэтому высоту добирают поля: 0.62em самой полосы плюс два поля до полного
     строчного бокса в 1.5em — того же, что задаёт line-height тексту. */
  height: 0.62em;
  margin: calc((1.5em - 0.62em) / 2) 0;
  border-radius: 1px;
  background: linear-gradient(
    90deg,
    var(--hair) 0%,
    var(--edge) 42%,
    var(--hair) 84%
  );
  background-size: 260% 100%;
  animation: skWave 1.5s ease-in-out infinite;
}

/* Ширины разные и неровные — ровные полосы читаются как элемент интерфейса,
   а неровные как «здесь будет текст». */
.sk-sym { width: 5.2em; }
.sk-px { width: 4.4em; justify-self: end; }
.sk-boost { width: 2.6em; justify-self: end; }

@keyframes skWave {
  from { background-position: 130% 0; }
  to { background-position: -30% 0; }
}

/* Движение — украшение поверх смысла: без него скелетон всё так же держит
   форму и высоту строки. */
@media (prefers-reduced-motion: reduce) {
  .sk { animation: none; }
}


/* ---- Служебный вход ---------------------------------------------------
   Отладочная ручка, а не часть интерфейса: занимать ею внимание нельзя. В
   покое это прозрачный квадратик 18×18 в левом нижнем углу — под курсором и
   под фокусом с клавиатуры он проступает, дальше раскрывается поле пароля.
   Разметки нет вовсе, пока в .env не задан DEV_LOGIN_PASSWORD (base.html). */
.dev-corner {
  position: fixed;
  left: 0;
  bottom: 0;
  /* Выше развёртки (.scanlines, z-index 50), иначе сетка ляжет поверх поля. */
  z-index: 60;
  display: flex;
  align-items: flex-end;
}

.dev-dot {
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ash);
  font-family: var(--mono);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--fast);
}

.dev-dot:hover,
.dev-dot:focus-visible,
/* Поле раскрыто — метка перестаёт прятаться: теперь это кнопка «свернуть», и
   невидимая она оставляла бы в углу необъяснимый зазор в 18px. */
.dev-dot[aria-expanded="true"] {
  opacity: 0.55;
}

.dev-form {
  display: flex;
  align-items: stretch;
  /* Рамка со всех сторон, кроме нижней: панель стоит не в самом углу — слева
     от неё метка, — и открытый левый край читался бы как обрыв. */
  border: 1px solid var(--hair);
  border-bottom: 0;
  background: var(--panel);
}

.dev-form input {
  /* 9ch — на пароль и не больше: это поле не должно выглядеть приглашением. */
  width: 9ch;
  padding: 0.3rem 0.5rem;
  border: 0;
  background: transparent;
  color: var(--chalk);
  font-family: var(--mono);
  font-size: 0.72rem;
}

.dev-form input:focus {
  outline: none;
}

/* Отказ — цветом поля, без отдельной строки с текстом: причина всегда одна. */
.dev-form.bad input {
  color: var(--down);
}

.dev-form button {
  padding: 0.3rem 0.6rem;
  border: 0;
  border-left: 1px solid var(--hair);
  background: transparent;
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}

.dev-form button:hover {
  color: var(--chalk);
}

/* Вошли служебным — угол перестаёт прятаться. Забыть, что сидишь под полными
   правами, нельзя: под ними интерфейс показывает не то, что увидит гость. */
.dev-badge {
  padding: 0.25rem 0.5rem;
  border: 1px solid var(--edge);
  border-left: 0;
  border-bottom: 0;
  background: var(--panel);
  color: var(--ash);
  font-family: var(--sans);
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  cursor: pointer;
}

.dev-badge:hover {
  color: var(--chalk);
}
