/* ===== eTabl Сервер — дизайн-система ===== */
/* Цвета на oklch + light-dark(): автоматическая светлая/тёмная тема по системе. */

:root {
  color-scheme: light dark;

  --bg:        light-dark(oklch(98% 0.004 250), oklch(20% 0.012 260));
  --surface:   light-dark(oklch(100% 0 0),       oklch(25% 0.013 260));
  --surface-2: light-dark(oklch(96.5% 0.005 250), oklch(28.5% 0.014 260));
  --line:      light-dark(oklch(91% 0.006 250),  oklch(34% 0.015 260));
  --ink:       light-dark(oklch(28% 0.02 260),   oklch(93% 0.01 250));
  --muted:     light-dark(oklch(56% 0.018 260),  oklch(68% 0.018 260));

  --brand:      light-dark(oklch(52% 0.11 165), oklch(72% 0.12 165));
  --brand-ink:  light-dark(oklch(99% 0 0),      oklch(22% 0.04 165));
  --brand-soft: light-dark(oklch(95% 0.03 165), oklch(32% 0.05 165));

  --ok:    light-dark(oklch(55% 0.13 150),  oklch(75% 0.14 150));
  --ok-bg: light-dark(oklch(95% 0.04 150),  oklch(33% 0.06 150));
  --warn:  light-dark(oklch(58% 0.12 75),   oklch(80% 0.13 85));
  --warn-bg: light-dark(oklch(95% 0.06 85), oklch(35% 0.07 75));
  --err:   light-dark(oklch(55% 0.18 25),   oklch(72% 0.17 25));
  --err-bg: light-dark(oklch(95% 0.04 25),  oklch(33% 0.08 25));
  --info:  light-dark(oklch(55% 0.13 250),  oklch(75% 0.12 250));

  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 1px 2px oklch(0% 0 0 / .04), 0 4px 16px oklch(0% 0 0 / .06);
  --sidebar-w: 240px;
  accent-color: var(--brand);
  scrollbar-width: thin;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Inter", "Open Sans", system-ui, -apple-system, sans-serif;
  font-size: 14px;
  background: var(--bg);
  color: var(--ink);
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
}
body.auth { display: block; }

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ===== Sidebar ===== */
.sidebar {
  grid-row: 1 / -1;
  background: var(--surface);
  border-right: 1px solid var(--line);
  padding: 18px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: sticky;
  top: 0;
  height: 100vh;
}
.side-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 6px 0 14px 12px; }
.brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: 700; color: var(--ink); min-width: 0;
}
.brand:hover { text-decoration: none; }
.brand-dot { width: 18px; height: 18px; flex: 0 0 18px; border-radius: 5px; background: var(--brand); }
.brand-mark { width: 20px; height: 20px; flex: 0 0 20px; border-radius: 5px; display: block; }
.brand-mark-lg { width: 30px; height: 30px; flex: 0 0 30px; border-radius: 7px; }
.brand-text { font-size: 16px; font-weight: 700; min-width: 0; }
.side-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }
.side-nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: var(--radius-sm);
  color: var(--muted); font-weight: 500;
}
.side-nav a:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.side-nav a.active { background: var(--brand-soft); color: var(--brand); }
.side-nav .ico { width: 18px; height: 18px; flex: 0 0 18px; opacity: .85; }

/* Группа «Настройки»: <details> вместо скрипта — раскрытие работает и без JS. */
.nav-group > summary {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  padding: 9px 12px; border-radius: var(--radius-sm);
  color: var(--muted); font-weight: 500; list-style: none;
}
.nav-group > summary::-webkit-details-marker { display: none; }  /* своя стрелка */
.nav-group > summary:hover { background: var(--surface-2); color: var(--ink); }
.nav-group > summary.active { color: var(--brand); }
.nav-caret { width: 14px; height: 14px; margin-left: auto; opacity: .7; transition: transform .15s; }
.nav-group[open] > summary .nav-caret { transform: rotate(180deg); }
/* Подпункты с отбивкой и направляющей — видно, что это ветка, а не свой раздел. */
.nav-sub { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 2px 21px; padding-left: 9px; border-left: 1px solid var(--line); }
.nav-sub a { padding: 7px 10px; font-size: 13px; }
.nav-sub .ico { width: 16px; height: 16px; flex: 0 0 16px; }
/* Значок «откроется в новой вкладке» — у ссылок наружу админки (Swagger). */
.nav-ext { width: 12px; height: 12px; margin-left: auto; opacity: .55; flex: 0 0 12px; }
html.nav-collapsed .nav-ext { display: none; }
.side-foot { display: flex; flex-direction: column; gap: 10px; }
/* Версия сервера — служебная строка: тише имени оператора, но читаемая. */
.side-version { font-size: 12px; color: var(--muted); letter-spacing: .3px; }
/* Кто вошёл: аватар с инициалами, имя с ролью и выход одной иконкой справа. */
.who {
  display: flex; align-items: center; gap: 10px;
  padding: 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.avatar {
  width: 32px; height: 32px; flex: 0 0 32px; border-radius: 50%; background: var(--brand);
  color: var(--brand-ink); display: grid; place-items: center; font-weight: 700; font-size: 13px;
}
.who-name { display: flex; flex-direction: column; line-height: 1.25; overflow: hidden; min-width: 0; flex: 1 1 auto; }
.who-name b { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-name small { color: var(--muted); font-size: 11px; }
.logout-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex: 0 0 32px; padding: 0;
  border: none; border-radius: var(--radius-sm);
  background: none; color: var(--muted); cursor: pointer;
  transition: background .12s, color .12s;
}
.logout-btn:hover { background: var(--err-bg); color: var(--err); }
.logout-btn svg { width: 16px; height: 16px; flex: 0 0 16px; }
/* Иконки sticky-сайдбара — на свой слой, чтобы штрихи SVG не «дрожали»
   при перерисовке во время прокрутки основного тела. */
.sidebar svg { transform: translateZ(0); }

.nav-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; flex: 0 0 auto;
  border: none; border-radius: var(--radius-sm);
  background: none; color: var(--muted); cursor: pointer;
  transition: background .12s, color .12s;
}
.nav-toggle:hover { background: var(--surface-2); color: var(--ink); }
.nav-toggle svg { width: 18px; height: 18px; transition: transform .15s; }

/* ===== Свёрнутый сайдбар (только иконки) ===== */
html.nav-collapsed { --sidebar-w: 68px; }
html.nav-collapsed .sidebar { padding: 18px 10px; }
html.nav-collapsed .brand { display: none; }            /* лого скрыто */
html.nav-collapsed .side-top { justify-content: center; padding: 6px 0 14px; }
html.nav-collapsed .nav-label { display: none; }
html.nav-collapsed .side-nav a { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
/* В режиме иконок остаётся только аватар: он же подсказывает, кто вошёл. */
html.nav-collapsed .who { flex-direction: column; gap: 8px; padding: 8px 0; background: none; border: 0; }
html.nav-collapsed .who-name { display: none; }
html.nav-collapsed .nav-toggle { color: var(--brand); }  /* подсветка активного «свёрнуто» */
/* В режиме иконок группа не сворачивается: стрелку и направляющую убираем,
   подпункты встают такими же иконками, иначе до них не добраться одним кликом. */
html.nav-collapsed .nav-caret { display: none; }
html.nav-collapsed .nav-group > summary { justify-content: center; gap: 0; padding-left: 0; padding-right: 0; }
html.nav-collapsed .nav-sub { margin-left: 0; padding-left: 0; border-left: 0; }
html.nav-collapsed .nav-sub a { padding-left: 0; padding-right: 0; }

/* ===== Content ===== */
.content { padding: 28px 32px; min-width: 0; }
body.auth .content { padding: 0; }
h1 { font-size: 22px; margin: 0 0 4px; }
h3 { font-size: 15px; margin: 0 0 8px; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.page-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 22px; gap: 16px; flex-wrap: wrap; }
.page-head h1 { margin: 0; }
.page-kicker { margin: 0 0 4px; color: var(--muted); font-size: 13px; font-weight: 500; }
.actions, .form-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
/* Компактная кнопка (в ячейках таблиц). */
.btn-sm { min-height: 0; padding: 5px 12px; font-size: 12px; }
/* Ячейка «обнаружить закрытые»: не переносить текст кнопки по буквам (в отличие от
   .payload с word-break), результат — рядом с кнопкой. */
.cell-detect { white-space: nowrap; }
/* Резервируем ширину под результат («17 / 24 закрыто» ≈109px), чтобы его появление
   после «обнаружить закрытые» не расширяло колонку и не сдвигало остальные столбцы. */
.cell-detect .detect-result { display: inline-block; min-width: 115px; margin-left: 8px; font-family: ui-monospace, Menlo, monospace; font-size: 12px; color: var(--muted); }

/* ===== Buttons ===== */
.btn-primary, .btn-secondary, .btn-danger {
  padding: 9px 16px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 600;
  cursor: pointer; border: 1px solid transparent; transition: filter .12s, background .12s;
  display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
}
.btn-primary:disabled, .btn-secondary:disabled, .btn-danger:disabled {
  opacity: 0.45; cursor: not-allowed; filter: none;
}
.btn-primary { background: var(--brand); color: var(--brand-ink); }
.btn-primary:hover { filter: brightness(1.07); text-decoration: none; }
.btn-secondary { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn-secondary:hover { background: var(--surface-2); text-decoration: none; }
.btn-danger { background: var(--err); color: oklch(99% 0 0); }
.btn-danger:hover { filter: brightness(1.07); }
.btn-link { background: none; border: 0; color: var(--brand); cursor: pointer; font: inherit; padding: 0; }
.btn-link:hover { text-decoration: underline; }
.inline { display: inline; margin: 0; }
/* Стрелочная иконка в кнопках/ссылках — центрируется флексом, без проблем глифа */
.btn-ico { width: 15px; height: 15px; flex: 0 0 auto; }

/* ===== Cards ===== */
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow); }
.card + .card { margin-top: 16px; }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.postmat-card { display: flex; flex-direction: column; gap: 6px; color: var(--ink); transition: transform .12s, box-shadow .12s; }
.postmat-card:hover { text-decoration: none; transform: translateY(-2px); box-shadow: 0 6px 24px oklch(0% 0 0 / .1); }
.postmat-card h3 { margin: 6px 0 8px; font-size: 16px; }
.card-top { display: flex; gap: 8px; align-items: center; }
.kv { display: flex; gap: 8px; font-size: 13px; margin: 2px 0; color: var(--muted); align-items: baseline; }
.kv b { color: var(--ink); font-weight: 600; word-break: break-all; }

/* ===== Status badges ===== */
.status { font-size: 11px; padding: 3px 9px; border-radius: 999px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.status-online { background: var(--ok-bg); color: var(--ok); }
.status-offline { background: var(--err-bg); color: var(--err); }
.status-unknown { background: var(--surface-2); color: var(--muted); }
.badge-off { font-size: 11px; padding: 2px 8px; border-radius: 999px; background: var(--err-bg); color: var(--err); }
.evtype { font-size: 12px; padding: 2px 9px; border-radius: 999px; background: var(--brand-soft); color: var(--brand); font-weight: 600; }

/* ===== Tables ===== */
.grid { width: 100%; border-collapse: collapse; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
/* В таблице обнаружения ячейки содержат кастомный select, чей раскрытый список
   (.select-panel, position:absolute) клиппился .grid overflow:hidden — у нижних
   строк dropdown обрезался и ломал таблицу. Не клиппим; верхние углы шапки
   скругляем вручную (overflow:hidden больше их не режет). */
/* .grid-boxed — тот же приём для таблиц вне .discovery-panel (напр. список датчиков):
   при border-collapse:collapse внешняя рамка схлопывается с рамками ячеек и визуально
   пропадает; separate её возвращает, углы скругляем вручную. */
.discovery-panel .grid,
.grid-boxed { overflow: visible; border-collapse: separate; border-spacing: 0; }
.discovery-panel .grid thead th:first-child,
.grid-boxed thead th:first-child { border-top-left-radius: var(--radius); }
.discovery-panel .grid thead th:last-child,
.grid-boxed thead th:last-child { border-top-right-radius: var(--radius); }
.discovery-panel .grid tbody tr:last-child td:first-child,
.grid-boxed tbody tr:last-child td:first-child { border-bottom-left-radius: var(--radius); }
.discovery-panel .grid tbody tr:last-child td:last-child,
.grid-boxed tbody tr:last-child td:last-child { border-bottom-right-radius: var(--radius); }
.grid th, .grid td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line); font-size: 13px; }
.grid th { background: var(--surface-2); color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: 11px; letter-spacing: .03em; }
.grid tr:last-child td { border-bottom: 0; }
.grid tbody tr:hover { background: var(--surface-2); }
.nowrap { white-space: nowrap; }
.payload { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; color: var(--muted); word-break: break-all; }

/* ===== Login ===== */
/* ===== Экран входа ===== */
.login-wrap {
  min-height: 100vh; display: grid; place-items: center; gap: 18px; padding: 24px;
  align-content: center;
  background:
    radial-gradient(60% 55% at 50% 0%, color-mix(in oklch, var(--brand) 22%, transparent), transparent 70%),
    radial-gradient(45% 40% at 85% 100%, color-mix(in oklch, var(--info) 16%, transparent), transparent 70%),
    var(--bg);
}
.login-card {
  width: min(380px, 100%); display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 18px; padding: 34px 32px 30px;
  box-shadow: 0 1px 2px oklch(0% 0 0 / .05), 0 20px 50px oklch(0% 0 0 / .18);
  position: relative; overflow: hidden;
}
.login-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--brand), color-mix(in oklch, var(--brand) 40%, var(--info)));
}
.login-brand { display: flex; align-items: center; gap: 10px; }
.login-brand .brand-dot { width: 18px; height: 18px; border-radius: 6px; }
.login-brand-name { font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.login-brand-name small { font-weight: 500; color: var(--muted); font-size: 16px; margin-left: 6px; }
.login-sub { margin: 8px 0 24px; color: var(--muted); font-size: 14px; }
.login-hint { margin: -16px 0 20px; color: var(--brand); font-size: 13px; font-weight: 600; }
.login-error {
  margin: 0 0 18px; padding: 10px 12px; border-radius: var(--radius-sm);
  background: var(--err-bg); color: var(--err); font-size: 13.5px;
  border: 1px solid color-mix(in oklch, var(--err) 30%, transparent);
}
.login-form { display: flex; flex-direction: column; gap: 16px; }
.login-form .field { display: flex; flex-direction: column; gap: 6px; }
.login-form label { font-size: 13px; color: var(--muted); font-weight: 500; }
.login-btn { width: 100%; justify-content: center; padding: 11px; margin-top: 6px; font-size: 15px; }
.pw-wrap { position: relative; display: flex; }
.pw-wrap input { flex: 1 1 auto; padding-right: 42px; }
.pw-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--muted); cursor: pointer;
}
.pw-toggle:hover { color: var(--ink); }
.pw-toggle svg { width: 18px; height: 18px; }
.pw-toggle.on { color: var(--brand); }
.login-foot { margin: 0; color: var(--muted); font-size: 12.5px; text-align: center; }

.form label, .ct-form label { display: flex; flex-direction: column; gap: 5px; font-size: 13px; color: var(--muted); }
.login-card input, .form input, .form select, .filters select, .board-add input, .board-add select,
.used-cells input, .inline-form input, .cab-add select, .kind-row select,
.ct-form input, .ct-form select, .ct-form textarea,
.sensor-cal input {
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: 14px; background: var(--bg); color: var(--ink);
}
.login-card input:focus-visible, .form input:focus, .form select:focus,
.ct-form input:focus, .ct-form select:focus, .ct-form textarea:focus {
  outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand);
}
.error { color: var(--err); margin: 0 0 8px; }

/* ===== Forms ===== */
.form { display: flex; flex-direction: column; gap: 14px; max-width: 580px; }
.form fieldset { border: 1px solid var(--line); border-radius: var(--radius-sm); display: flex; flex-direction: column; gap: 10px; padding: 14px 16px; }
/* display:flex выше перебивает [hidden] из UA-стилей — возвращаем скрытие. */
.form fieldset[hidden], .form-actions[hidden] { display: none; }
.form legend { font-size: 13px; font-weight: 600; color: var(--ink); padding: 0 6px; }
.form .check { flex-direction: row; align-items: center; gap: 8px; color: var(--ink); }
.warn-banner {
  display: flex; align-items: flex-start; gap: 10px; max-width: 760px; margin-bottom: 16px;
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--warn-bg); border: 1px solid var(--warn); color: var(--warn);
  font-size: 13.5px; line-height: 1.5;
}
/* Точка восклицательного знака — line нулевой длины: без round-скругления
   браузер её не рисует, и знак выглядит без точки. */
.warn-banner svg { width: 18px; height: 18px; flex: 0 0 18px; margin-top: 1px;
  stroke-linecap: round; stroke-linejoin: round; }
/* Баннер внутри карточки — отделяем от заголовка с кнопкой. */
.card > .warn-banner { margin: 12px 0 0; max-width: none; }
.warn-banner code { background: var(--surface); padding: 1px 5px; border-radius: 4px; font-size: 12.5px; color: var(--ink); }

.token-banner { background: var(--warn-bg); border-color: var(--warn); display: flex; flex-direction: column; gap: 8px; max-width: 760px; }
.token-banner code { word-break: break-all; font-size: 13px; background: var(--surface); padding: 8px; border-radius: var(--radius-sm); }
.token-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.token-row code { flex: 1 1 240px; }
.token-copy { flex: 0 0 auto; padding: 7px 12px; }

/* ===== Карточка постамата: живое состояние ===== */
.state-card { max-width: 760px; margin-bottom: 16px; }
.state-top { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
.state-conn { display: flex; align-items: center; gap: 8px; font-size: 15px; }
.state-conn .dot { width: 11px; height: 11px; }
.gauges { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px 24px; margin-top: 16px; }
.gauge-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; color: var(--muted); margin-bottom: 6px; }
.gauge-head b { color: var(--ink); font-size: 15px; font-variant-numeric: tabular-nums; }
.gauge-bar { height: 8px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.gauge-fill { display: block; height: 100%; border-radius: 999px; background: var(--ok); transition: width .3s; }
.gauge-fill.is-mid { background: var(--warn); }
.gauge-fill.is-hi { background: var(--err); }
.gauge-sub { margin-top: 5px; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.no-gauges { margin: 14px 0 0; }
.state-diag { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); font-size: 13px; }
.state-diag .dot { width: 9px; height: 9px; flex: 0 0 9px; }
.state-diag-link { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; font-size: 13px; }
@media (max-width: 540px) { .gauges { grid-template-columns: 1fr; } }

/* Обзор устройства на карточке: сводные плитки-ссылки (без дублирования метрик) */
.overview-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 16px; }
.ov-item {
  display: flex; flex-direction: column; gap: 5px; padding: 12px 14px; color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
  transition: border-color .12s, background .12s;
}
.ov-item:hover { text-decoration: none; border-color: color-mix(in oklch, var(--brand) 45%, var(--line)); background: var(--surface-2); }
.ov-label { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.ov-value { font-size: 15px; font-weight: 600; display: inline-flex; align-items: center; gap: 7px; }
.ov-value .dot { width: 9px; height: 9px; flex: 0 0 9px; }
.ov-value.ok { color: var(--ok); }
.ov-value.warn { color: var(--warn); }
.ov-value.bad { color: var(--err); }
@media (max-width: 560px) { .overview-grid { grid-template-columns: 1fr; } }
/* Сводка на «Главной»: 4 плитки статусов парка. */
.dash-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-top: 16px; max-width: 760px; }
.dash-stats .ov-value { font-size: 22px; }
.dash-cta { margin-top: 18px; }
@media (max-width: 720px) { .dash-stats { grid-template-columns: repeat(2, 1fr); } }

/* ===== Карточка постамата: хаб-навигация по разделам ===== */
.hub-nav { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 16px; max-width: 760px; }
.hub-link {
  display: flex; gap: 12px; align-items: center; padding: 14px 16px; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  transition: border-color .12s, box-shadow .12s, transform .12s;
}
.hub-link:hover {
  text-decoration: none;
  border-color: color-mix(in oklch, var(--brand) 45%, var(--line));
  box-shadow: 0 4px 18px oklch(0% 0 0 / .08);
}
.hub-link .ico { width: 22px; height: 22px; flex: 0 0 22px; color: var(--brand); }
.hub-link h3 { margin: 0 0 2px; font-size: 14px; }
.hub-link p { margin: 0; font-size: 12.5px; color: var(--muted); line-height: 1.4; }
.hub-link.is-disabled { opacity: .5; pointer-events: none; cursor: not-allowed; filter: grayscale(1); }
/* Раздел, который ждёт действия оператора (топология собрана, но не применена). */
.hub-link.needs-attention { border-color: color-mix(in oklch, var(--warn) 55%, var(--line)); background: color-mix(in oklch, var(--warn) 8%, var(--surface)); }
.hub-link.needs-attention .ico { color: var(--warn); }
.hub-badge {
  display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 600; vertical-align: middle;
  color: var(--warn); background: color-mix(in oklch, var(--warn) 16%, transparent);
  border: 1px solid color-mix(in oklch, var(--warn) 45%, transparent);
}
.hub-note { margin: -8px 0 16px; }

/* ===== Карточка постамата: сгруппированная информация ===== */
.info-card { max-width: 760px; }
.info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 22px 36px; }
.info-sec h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 10px; }
.info-sec .kv { justify-content: space-between; gap: 16px; margin: 4px 0; }
.info-sec .kv span { flex: 0 0 auto; }
.info-sec .kv b { text-align: right; }
.info-sec .btn-link { margin-top: 8px; }

/* Подсказка «?» с всплывающим описанием у поля */
.hint {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; margin-left: 6px; flex: 0 0 auto; vertical-align: middle;
  border-radius: 50%; border: 1px solid var(--line);
  font-size: 10px; font-weight: 700; color: var(--muted);
  cursor: help; position: relative;
}
.hint:hover, .hint:focus-visible { color: var(--brand); border-color: var(--brand); outline: none; }
/* Тултип на самом ярлыке (без значка «?») — пунктирное подчёркивание + всплывашка */
.has-tip { position: relative; cursor: help; outline: none;
  text-decoration: underline dotted; text-underline-offset: 3px;
  text-decoration-color: color-mix(in oklch, var(--muted) 55%, transparent); }
.has-tip:hover, .has-tip:focus-visible { text-decoration-color: var(--brand); color: var(--ink); }
.hint::after, .has-tip::after {
  content: attr(data-tip);
  position: absolute; bottom: calc(100% + 8px); left: -5px;
  width: max-content; max-width: 240px; padding: 8px 11px;
  background: var(--ink); color: var(--surface);
  border-radius: 8px; font-size: 12px; font-weight: 500; line-height: 1.45;
  text-align: left; white-space: normal; letter-spacing: normal; text-transform: none;
  opacity: 0; visibility: hidden; transition: opacity .12s, visibility .12s;
  box-shadow: 0 8px 24px oklch(0% 0 0 / .22); z-index: 20; pointer-events: none;
}
.hint::before, .has-tip::before {
  content: ""; position: absolute; bottom: calc(100% + 3px); left: 3px;
  border: 5px solid transparent; border-top-color: var(--ink);
  opacity: 0; visibility: hidden; transition: opacity .12s, visibility .12s;
}
.hint:hover::after, .hint:hover::before,
.hint:focus-visible::after, .hint:focus-visible::before,
.has-tip:hover::after, .has-tip:hover::before,
.has-tip:focus-visible::after, .has-tip:focus-visible::before { opacity: 1; visibility: visible; }
.card-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
.pm-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pm-title .dot { width: 12px; height: 12px; }

/* Детальный экран: колонка контента (760) + боковая справка */
.detail-grid { display: flex; gap: 24px; align-items: flex-start; }
.detail-main { flex: 0 0 760px; max-width: 100%; min-width: 0; }
.detail-grid .ct-info { flex: 1 1 320px; min-width: 280px; position: sticky; top: 24px; align-self: flex-start; }
/* Контент 760 + справка ≥280 + разделитель/зазоры не помещаются примерно до 1400px
   (с учётом сайдбара) → переносим справку под контент, чтобы не было тонкой полоски. */
@media (max-width: 1400px) {
  .detail-grid { flex-direction: column; }
  .detail-main { flex-basis: auto; order: 0; }
  .detail-grid .ct-info { position: static; min-width: 0; flex-basis: auto; order: -2; }
  .detail-grid .ct-divider { align-self: stretch; height: 1px; flex-basis: 1px; order: -1; }
  .detail-grid .ct-info p { max-width: 820px; }
}

/* ===== Control / результаты ===== */
.control-grid { display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0; align-items: center; }
.ctl-card { display: flex; flex-direction: column; gap: 18px; max-width: 760px; }
.ctl-group { display: flex; flex-direction: column; gap: 8px; }
.ctl-group-title { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.ctl-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.diag-result { margin-top: 14px; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius-sm); }

/* Обновление ПО: окно обслуживания */
.upd-status { margin: 16px 0; }
.upd-status .diag-summary { font-weight: 600; }
.upd-days { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 7px; }
.day-chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm); font-size: 13px; cursor: pointer; user-select: none; }
.day-chip input { width: 15px; height: 15px; }
.upd-time { display: flex; gap: 20px; align-items: center; flex-wrap: wrap; }
.upd-time label { display: flex; flex-direction: row; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }
.time-select { display: inline-flex; align-items: center; gap: 3px; }
.time-select select { width: auto; padding: 6px 8px; }
.time-colon { color: var(--muted); font-weight: 600; }
.pm-own-fields[hidden] { display: none; }
.inline-form { display: inline-flex; gap: 6px; align-items: center; }
.inline-form input { width: 120px; }
.cmd-result:empty, #upd-result:empty { display: none; }
.result-box { margin-top: 14px; padding: 12px 16px; border-radius: var(--radius-sm); border: 1px solid var(--line); }
.result-ok { background: var(--ok-bg); border-color: var(--ok); }
.result-error { background: var(--err-bg); border-color: var(--err); }
.diag-list { list-style: none; padding: 0; margin: 10px 0 0; display: flex; flex-direction: column; gap: 7px; }
.diag-list li { display: flex; gap: 9px; align-items: baseline; font-size: 14px; }
.htmx-request { opacity: .55; transition: opacity .1s; }

/* ===== Конфигуратор ===== */
.cab-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.cab-head h3 { margin: 0; }
.cab-title { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.kind-badge {
  font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px; white-space: nowrap;
}
.kind-badge.kind-fridge { background: color-mix(in oklch, var(--info) 16%, transparent); color: var(--info); }
.kind-badge.kind-ambient { background: var(--surface-2); color: var(--muted); }

.ct-discover { display: flex; align-items: center; justify-content: space-between; gap: 16px 24px; flex-wrap: wrap; margin-bottom: 16px; }
.ct-discover-txt { max-width: 62ch; }
.ct-discover-txt b { display: block; margin-bottom: 3px; font-size: 15px; }
.ct-discover-txt p { margin: 0; }
.ct-discover .btn-primary { flex-shrink: 0; }
/* Скан и пересбор с нуля стоят рядом: на узком экране — столбиком, чтобы
   «Обнаружить заново» не оказалось случайной целью большого пальца. */
.ct-discover-actions { display: flex; flex-shrink: 0; gap: 10px; flex-wrap: wrap; }

/* Плата из прежнего скана, не отозвавшаяся в последнем: строка приглушена, но
   читаема — её ещё нужно опознать по адресу, прежде чем удалять. */
.grid tr.is-missing td { background: var(--err-bg); }
.grid tr.is-missing td.payload { color: var(--muted); }
.sect-board.is-missing { border-color: var(--err); }
.badge-off + .badge-off, .payload .badge-off { margin-left: 8px; }
/* Кнопка с индикацией: во время htmx-запроса htmx вешает .htmx-request на саму
   кнопку → лейбл меняется на «Сканирование…» со спиннером, кнопка не сдвигается. */
.btn-busy .btn-loading { display: none; align-items: center; gap: 8px; }
.btn-busy.htmx-request .btn-label { display: none; }
.btn-busy.htmx-request .btn-loading { display: inline-flex; }
.spinner {
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--brand);
  animation: spin .7s linear infinite;
}
/* На брендовой кнопке спиннер белый — контраст на тёмном фоне. */
.btn-primary .spinner { border-color: color-mix(in oklab, #fff 40%, transparent); border-top-color: #fff; }
/* Спиннер рядом с текстом (индикация ленивой проверки в diag-summary). */
.spinner-inline { width: 13px; height: 13px; flex: 0 0 13px; display: inline-block; vertical-align: -2px; }
@keyframes spin { to { transform: rotate(360deg); } }
.cab { display: flex; flex-direction: column; gap: 16px; }
/* Отступ между блоками топологии: карточки лежат в РАЗНЫХ контейнерах
   (#discovery, #topology), поэтому смежное правило .card + .card не срабатывает —
   секция прилипала к блоку обнаружения. */
#discovery > .card:first-child,
#topology > .card:first-child { margin-top: 16px; }
/* Индикация раскладки секции (вместо ручного «Смонтировано ячеек») */
.cab-layout { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.cab-layout .btn-secondary { margin-left: auto; }
.cab-sub { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 8px; }
/* Режим + датчик секции — в одну линию (короткие поля). */
.cab-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 640px) { .cab-row { grid-template-columns: 1fr; } }
.cab-kind { display: flex; flex-direction: column; gap: 6px; }
.cab-kind > label { font-size: 13px; color: var(--muted); }
.cab-kind .filter-select { min-width: 0; width: 100%; }
.cab-boards .grid { margin: 0; }
.cab-boards .col-actions { width: 40px; text-align: right; }

.board-add, .used-cells { display: flex; gap: 8px; align-items: center; margin-top: 12px; flex-wrap: wrap; }
.board-add .filter-select { min-width: 150px; height: 38px; }
.board-add input { width: 140px; }
.used-cells input { width: 90px; }

.cab-usage { border-top: 1px solid var(--line); padding-top: 14px; }
.usage-head { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; color: var(--muted); margin-bottom: 8px; }
.usage-head b { color: var(--ink); font-variant-numeric: tabular-nums; }

.cab-add { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.cab-add .filter-select { min-width: 200px; }
.filters { display: flex; gap: 10px; margin-bottom: 16px; align-items: center; flex-wrap: wrap; }
.filter-select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  height: 38px; padding: 0 32px 0 12px; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background-color: var(--surface); color: var(--ink);
  font-size: 14px; min-width: 200px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%238a93a6' stroke-width='1.6'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
}
.filter-select:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand); }
.events-filters { margin-bottom: 16px; }
.col-data { width: 80px; text-align: center; }
.ev-modal {
  border: 1px solid var(--line); border-radius: 14px; padding: 0;
  background: var(--surface); color: var(--ink); width: min(560px, 92vw);
  box-shadow: 0 18px 50px oklch(0% 0 0 / .35);
}
.ev-modal::backdrop { background: oklch(0% 0 0 / .5); }
.ev-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.ev-modal > div[data-ak-body] { padding: 16px 18px; max-height: 60vh; overflow: auto; }
.ev-modal pre {
  margin: 0; padding: 16px 18px; max-height: 60vh; overflow: auto;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 13px;
  line-height: 1.5; color: var(--ink); white-space: pre-wrap; word-break: break-word;
}
.pager { display: flex; gap: 10px; margin-top: 16px; }

/* ===== Метрики / экспорт ===== */
.range-tabs { display: flex; gap: 8px; margin-bottom: 14px; }
.range-btn.active { background: var(--brand); color: var(--brand-ink); border-color: var(--brand); }
#chart { width: 100%; }
.uplot { font-family: inherit; }

/* Текущий срез метрик: статус сверху, шкалы нагрузки под ним во всю ширину */
.metrics-now { display: flex; flex-direction: column; gap: 16px; margin-bottom: 16px; }
.now-status { display: flex; flex-direction: column; gap: 5px; }
.now-status .state-conn { font-size: 16px; }
.now-status .state-conn .dot { width: 11px; height: 11px; }
.metrics-now .gauges { margin-top: 0; }
.metrics-now .no-gauges { margin: 0; }

/* График + шапка с переключателем периода */
.chart-card { margin-bottom: 16px; }
.chart-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.chart-head h3 { margin: 0; }
.chart-empty { padding: 28px 0; text-align: center; }

/* Сегментированный переключатель (Час / Сутки / Неделя) */
.seg { display: inline-flex; padding: 3px; gap: 2px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px; }
.seg-btn {
  border: 0; background: none; cursor: pointer; font: inherit; font-size: 13px; font-weight: 600;
  color: var(--muted); padding: 6px 16px; border-radius: 999px; transition: background .12s, color .12s;
}
.seg-btn:hover { color: var(--ink); }
.seg-btn.active { background: var(--surface); color: var(--brand); box-shadow: 0 1px 3px oklch(0% 0 0 / .1); }

/* Диагностика */
.diag-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.diag-head h3 { margin: 0; }
.diag-summary { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; }
/* display выше сильнее атрибута hidden — без этого спрятать сводку нельзя. */
.diag-summary[hidden] { display: none; }
.diag-summary .dot { width: 9px; height: 9px; flex: 0 0 9px; }
.diag-summary.ok { color: var(--ok); }
.diag-summary.warn { color: var(--warn); }
.diag-summary.err { color: var(--err); }
.dot-warn { background: var(--warn); }
.diag-detail { color: var(--muted); }
.diag-ts { margin-left: auto; flex: 0 0 auto; white-space: nowrap; }

/* Состояние оборудования — понятный список */
.diag-list2 { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; }
.diag-item { display: flex; align-items: center; gap: 10px; padding: 11px 2px; border-bottom: 1px solid var(--line); font-size: 14px; }
.diag-item:last-child { border-bottom: 0; }
.diag-item .dot { width: 10px; height: 10px; flex: 0 0 10px; }
.diag-name { font-weight: 600; flex: 0 0 auto; }
.diag-why { flex: 1 1 auto; min-width: 0; color: var(--muted); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.diag-state { margin-left: auto; flex: 0 0 auto; font-size: 12px; font-weight: 600; padding: 3px 10px; border-radius: 999px; white-space: nowrap; }
.diag-state.ok { background: var(--ok-bg); color: var(--ok); }
.diag-state.warn { background: var(--warn-bg); color: var(--warn); }
.diag-state.off { background: var(--surface-2); color: var(--muted); }
.diag-state.err { background: var(--err-bg); color: var(--err); }
.diag-foot { margin: 12px 0 0; }
.export-head { display: flex; justify-content: space-between; align-items: center; }
.export-head h3 { margin: 0; }
.code-block {
  background: light-dark(oklch(22% 0.02 260), oklch(16% 0.01 260)); color: oklch(88% 0.02 250);
  padding: 16px; border-radius: var(--radius-sm); font-size: 13px;
  line-height: 1.55; margin-top: 10px;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  /* Переносим длинные строки: горизонтальная прокрутка в тексте документации
     прячет половину команды и не заметна на тачпаде. Продолжение — с отступом. */
  white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 2;
}

/* Блок кода с кнопкой копирования: обёртку и кнопку добавляет скрипт страницы. */
/* Ширину держит обёртка: у .code-block она ограничена, и кнопка иначе
   вставала на пустом месте справа от блока. */
.code-wrap { position: relative; max-width: 780px; }
.code-copy {
  position: absolute; top: 8px; right: 8px;
  width: 28px; height: 28px; border-radius: var(--radius-sm);
  background: light-dark(oklch(28% 0.02 260), oklch(22% 0.01 260));
  color: oklch(72% 0.02 250); opacity: 0; transition: opacity .12s, color .12s;
}
/* Кнопка появляется по наведению: в статичном тексте она лишний акцент, но с
   клавиатуры до неё нужно добираться — отсюда :focus-visible. */
.code-wrap:hover .code-copy, .code-copy:focus-visible { opacity: 1; }
.code-copy:hover { color: oklch(96% 0 0); background: light-dark(oklch(34% 0.02 260), oklch(28% 0.01 260)); }
.code-copy.is-done { color: var(--ok); opacity: 1; }
.code-copy svg { width: 15px; height: 15px; }
/* Подсветка: своя, на регулярках — тянуть библиотеку ради двух языков незачем. */
.code-block .tok-str { color: oklch(80% 0.11 140); }
.code-block .tok-key { color: oklch(78% 0.11 230); }
.code-block .tok-num { color: oklch(82% 0.12 80); }
.code-block .tok-lit { color: oklch(76% 0.13 300); }
.code-block .tok-cmd { color: oklch(88% 0.02 250); font-weight: 600; }
.code-block .tok-flag { color: oklch(72% 0.06 250); }
.future { margin-top: 16px; }

/* ===== Дашборд: список + панель статистики (референс Addreality) ===== */
.dash-grid { display: grid; grid-template-columns: 1fr 280px; gap: 20px; align-items: start; }
.dash-side { position: sticky; top: 28px; display: flex; flex-direction: column; gap: 16px; }
.stat-card h3 { margin: 0 0 12px; }
.stat-big { font-size: 38px; font-weight: 700; line-height: 1; display: flex; flex-direction: column; gap: 4px; }
.stat-big small { font-size: 12px; font-weight: 500; color: var(--muted); }
.stat-bar { height: 8px; border-radius: 999px; background: var(--err-bg); overflow: hidden; margin: 16px 0; }
.stat-bar-fill { display: block; height: 100%; background: var(--ok); border-radius: 999px; transition: width .3s; }
.stat-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.stat-list li { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }
.stat-list li b { margin-left: auto; color: var(--ink); font-size: 15px; }
.dot { width: 9px; height: 9px; border-radius: 50%; }
.dot-online { background: var(--ok); }
.dot-offline { background: var(--err); }
.dot-unknown { background: var(--muted); }
.dot-pending { background: var(--warn); }  /* не инициализирован, но хост в сети */

/* ===== Главная: плитки обзора парка ===== */
.home-stats {
  display: grid; gap: 16px; max-width: 920px;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
.stat-tile {
  display: flex; flex-direction: column; gap: 8px; color: var(--ink);
  transition: transform .12s, box-shadow .12s, border-color .12s;
}
.stat-tile:hover {
  text-decoration: none; transform: translateY(-2px);
  border-color: color-mix(in oklch, var(--brand) 45%, var(--line));
  box-shadow: 0 6px 24px oklch(0% 0 0 / .1);
}
.stat-tile-num { font-size: 30px; font-weight: 700; display: flex; align-items: center; gap: 10px; line-height: 1; }
.stat-tile-num .dot { width: 12px; height: 12px; }
.stat-tile-label { color: var(--muted); font-size: 13px; }
@media (max-width: 900px) { .dash-grid { grid-template-columns: 1fr; } .dash-side { position: static; } }

/* ===== Тулбар + поиск + плотная таблица ===== */
.toolbar { display: flex; gap: 10px; margin-bottom: 14px; align-items: center; }
.search {
  flex: 0 1 420px; padding: 9px 14px; border: 1px solid var(--line);
  border-radius: var(--radius-sm); font-size: 14px; background: var(--surface); color: var(--ink);
}
.search:focus { outline: 2px solid var(--brand); outline-offset: -1px; border-color: var(--brand); }
.toolbar .btn-primary { flex: 0 0 auto; }
/* В обычном тулбаре поиск тянется до конца строки (списочные .ct-toolbar — своя ширина) */
.toolbar .search { flex: 1 1 auto; }
.grid-rows tbody tr { cursor: pointer; }
.grid-rows tbody tr:hover { background: var(--brand-soft); }

/* ===== Список постаматов: сводка парка + карточки ===== */
.fleet-summary { display: flex; gap: 18px; flex-wrap: wrap; align-items: center; margin-bottom: 16px; font-size: 14px; }
.fleet-summary .fs-item { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); }
.fleet-summary .fs-item b { color: var(--ink); font-weight: 700; }
.fleet-summary .dot { width: 9px; height: 9px; flex: 0 0 9px; }

/* Модалка добавления постамата */
.pm-modal { width: min(640px, 94vw); }
.pm-modal .ct-form { max-height: 88vh; overflow: auto; max-width: none; gap: 16px; }
.pm-modal h3 { margin: 0; }
.pm-modal #pm-form-error:empty { display: none; }
.pm-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.pm-grid .span2 { grid-column: 1 / -1; }
/* Строка SIP: адрес АТС тянется, порт узкий, номер техподдержки — по содержимому.
   Одной сеткой, а не .pm-grid: там две равные колонки, и порт получал столько же
   места, сколько адрес. */
.sip-line { display: grid; grid-template-columns: 1fr 110px 1fr; gap: 14px; align-items: end; }
@media (max-width: 720px) { .sip-line { grid-template-columns: 1fr; } }
.pm-create textarea {
  padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  font: inherit; font-size: 14px; background: var(--bg); color: var(--ink); resize: vertical;
}
.pm-create textarea:focus { outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand); }
/* DaData-подсказки */
.dadata-wrap { position: relative; display: block; }
.dadata-list {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 30;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: 0 10px 28px oklch(0% 0 0 / .18); overflow: hidden; max-height: 260px; overflow-y: auto;
}
.dadata-item {
  display: block; width: 100%; text-align: left; border: 0; background: none; cursor: pointer;
  padding: 9px 12px; font: inherit; font-size: 13.5px; color: var(--ink);
}
.dadata-item:hover { background: var(--brand-soft); }
.dadata-wrap input { width: 100%; box-sizing: border-box; }
@media (max-width: 520px) { .pm-grid { grid-template-columns: 1fr; } }

/* ── Форма редактирования постамата: широкая, секции-карточки, две колонки ── */
.pm-edit.form { max-width: 100%; gap: 0; }
.pm-edit .detail-main { display: flex; flex-direction: column; gap: 16px; }
.pm-edit .card > h3 { margin: 0 0 4px; font-size: 15px; }
.pm-edit .card > h3 + .muted.small, .pm-edit .card > .muted.small { margin: 0 0 14px; }
.pm-edit .card > h3:last-child, .pm-edit .card > h3 + .pm-grid { margin-top: 14px; }
.pm-h3-sub { font-weight: 400; }
.pm-h3-sub::before { content: "— "; }
/* Учётка стоит над выбором АТС: без верхнего отступа радиокнопки липнут к
   полям ввода и читаются как их продолжение. */
.pm-edit .sip-modes { display: flex; flex-direction: column; gap: 12px;
  margin: 22px 0 2px; }
.pm-edit .sip-modes .form-check { align-items: flex-start; }
.pm-edit .sip-modes .form-check > span { display: flex; flex-direction: column; gap: 2px; color: var(--ink); }
.pm-edit .sip-modes .form-sub { margin-top: 0; }
.pm-edit .sip-custom { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); }
/* .pm-grid задаёт display:grid и перебивает UA-правило [hidden]{display:none} — возвращаем */
.pm-edit .sip-custom[hidden] { display: none; }
.pm-edit .form-actions { margin-top: 2px; }
.pm-edit .card .pm-active { margin-top: 14px; }
/* Пара мелких карточек (Доступ + Плеер) в одну строку, равной высоты */
.pm-edit .pm-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: stretch; }
.pm-edit .pm-row > .card { margin-top: 0; }
@media (max-width: 640px) { .pm-edit .pm-row { grid-template-columns: 1fr; } }

/* Предупреждение-подсказка (напр. незаполненные реквизиты до инициализации) */
.note { border: 1px solid var(--line); border-left: 3px solid var(--warn); border-radius: var(--radius-sm); padding: 12px 14px; margin-top: 14px; background: var(--surface-2); }
.note p { margin: 0; }
.note-list { margin: 8px 0 12px; padding-left: 18px; font-size: 13px; color: var(--muted); }
.note-list li { margin: 2px 0; }

/* Заголовок группы блоков на карточке (напр. над плитками разделов) */
.section-title { font-size: 13px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 24px 0 10px; }

/* Ячейка статуса в списке: кружок + слово (цвет по состоянию) */
/* Состояние постамата — чипом, как тип события в журнале: строки списка держат
   один ритм, а цвет читается быстрее точки с подписью. */
.pm-state { font-size: 12px; padding: 2px 9px; border-radius: 999px; font-weight: 600; background: var(--surface-2); color: var(--muted); }
.pm-state.is-online { background: var(--ok-bg, var(--brand-soft)); color: var(--ok); }
.pm-state.is-offline { background: var(--err-bg); color: var(--err); }
.status-cell { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; white-space: nowrap; }
.status-cell .dot { width: 9px; height: 9px; flex: 0 0 9px; }
.status-cell.state-online { color: var(--ok); }
.status-cell.state-offline { color: var(--err); }
.status-cell.state-unknown { color: var(--muted); }

/* ===== Типы ячеек ===== */
.ct-preview {
  display: inline-block; box-sizing: border-box;
  background: var(--c, #8aa0b6); border: 1px solid color-mix(in oklch, var(--c, #8aa0b6) 70%, black);
  border-radius: 4px; min-width: 13px; min-height: 13px; vertical-align: middle;
}
.btn-link.danger { color: var(--danger, #c0392b); font-weight: 700; }

/* Базовая клетка плитки и зазор — от них считаются ширины тулбара/сетки. */
.ct-toolbar, .ct-layout { --ct-col: 200px; --ct-gap: 14px; }
/* Ширина колонки при ряде из четырёх — она же минимум для auto-fill: колонок
   получается ровно четыре там, где влезает, и меньше там, где нет. Нижняя
   граница в 160px не даёт плиткам сплющиться на средних окнах. */
.ct-layout { --ct-col-4: max(160px, calc((100% - var(--ct-gap) * 3) / 4)); }

/* Одна пропорция «работа : справка» на всех разделах. Доли взяты с экрана
   шаблонов, где основная колонка вмещает 4 плитки, а справка — остаток: там
   инструкция читается абзацами, а не колонкой в три слова.
   basis 0 при долевом grow: ширины считаются от свободного места, а не от
   содержимого, иначе правый блок «дышит», когда приезжают htmx-фрагменты. */
.ct-layout { display: flex; align-items: stretch; gap: 24px; --ct-main: 54; --ct-aside: 46; }
.ct-main { flex: var(--ct-main) 1 0; min-width: 0; }
.ct-divider { flex: 0 0 1px; background: var(--line); align-self: stretch; }
.ct-info {
  flex: var(--ct-aside) 1 0; min-width: 0;
  position: sticky; top: 24px; align-self: flex-start;  /* закреплён как навбар */
}

/* На узких экранах справка не помещается сбоку → переносим её НАД плитками.
   Правила ограничены .ct-layout, иначе протекают в .detail-grid (карточка/
   метрики/топология) и справка уезжает влево тонкой полоской. */
@media (max-width: 1500px) {
  .ct-layout { flex-direction: column; gap: 18px; }
  .ct-layout .ct-info { order: -2; position: static; flex: 1 1 auto; }
  .ct-layout .ct-divider { order: -1; align-self: stretch; flex-basis: 1px; height: 1px; }
  .ct-layout .ct-main { order: 0; width: 100%; }
  .ct-layout .ct-info p { max-width: 820px; }
}
.ct-info h3 { margin: 0 0 12px; font-size: 17px; }
.ct-info p { margin: 0 0 12px; font-size: 14px; line-height: 1.6; color: var(--muted); max-width: 560px; }
.ct-info p:last-child { margin-bottom: 0; }
.ct-info p b { color: var(--ink); }
.ct-info .ct-steps-title { margin-bottom: 6px; }
.ct-info .ct-steps { margin: 0; padding-left: 20px; font-size: 14px; line-height: 1.55; color: var(--muted); max-width: 560px; }
.ct-info .ct-steps li { margin-bottom: 10px; }
.ct-info .ct-steps li:last-child { margin-bottom: 0; }
/* Абзац после списка шагов иначе прилипает к последнему пункту: у списка снят
   собственный отступ, а у абзацев он только снизу. */
.ct-info .ct-steps + p { margin-top: 12px; }
.ct-info .ct-steps b { color: var(--ink); }

/* Кнопка — в одну плитку, поиск — в две плитки. */
.ct-toolbar { justify-content: flex-start; gap: var(--ct-gap); }
.ct-toolbar .btn-primary { flex: 0 0 var(--ct-col); justify-content: center; }
/* Поиск занимает всё свободное место колонки — до справки справа. */
.ct-toolbar .search {
  flex: 1 1 auto; min-width: calc(var(--ct-col) * 2 + var(--ct-gap));
  border-radius: var(--radius-sm);
}

.ct-grid {
  display: grid; gap: var(--ct-gap);
  /* До четырёх плиток в ряд, но не любой ценой: минимум колонки — это ширина
     при четырёх колонках, поэтому пятая не влезает никогда, а на узком окне
     auto-fill сам сокращает ряд до трёх и меньше. Жёсткий repeat(4) так не
     умеет — он вылезал за основную колонку на средних окнах. */
  grid-template-columns: repeat(auto-fill, minmax(var(--ct-col-4), 1fr));
  align-items: stretch;
}
.ct-grid .ct-tile + .ct-tile { margin-top: 0; }  /* отступы держит gap, не .card + .card */
.ct-tile { display: flex; flex-direction: column; gap: 10px; padding: 14px; transition: border-color .12s, box-shadow .12s; }
.ct-tile:hover { border-color: color-mix(in oklch, var(--brand) 45%, var(--line)); box-shadow: 0 4px 18px oklch(0% 0 0 / .08); }

.ct-tile-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ct-tile-name { font-size: 15px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-status-dot {
  /* inline-block обязателен: в плитках точка лежит во flex-контейнере, а в
     таблице ключей — в обычном потоке, где inline-элемент игнорирует width и
     схлопывался в вертикальную полоску. */
  display: inline-block; vertical-align: middle;
  width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto;
  box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 16%, transparent);
}
/* Статус в таблице: точка и подпись на одной линии, с зазором под ореол точки. */
.status-cell { display: inline-flex; align-items: center; gap: 8px; }
.ct-status-dot.is-used { background: var(--ok); color: var(--ok); }
.ct-status-dot.is-free { background: var(--muted); color: var(--muted); }

/* Наглядное превью ячейки: прямоугольник в цвете типа с пропорциями col×row. */
.ct-preview-box {
  display: flex; align-items: center; justify-content: center;
  height: 92px; border-radius: var(--radius-sm); overflow: hidden;
  background:
    repeating-linear-gradient(45deg, transparent 0 6px, color-mix(in oklch, var(--line) 50%, transparent) 6px 7px),
    var(--surface-2);
}
.ct-cell-shape {
  aspect-ratio: var(--cols) / var(--rows);
  height: min(76px, calc(var(--rows) * 24px));
  max-width: 130px;
  background: color-mix(in oklch, var(--c, #8aa0b6) 28%, var(--surface));
  border: 1.5px solid var(--c, #8aa0b6);
  border-radius: 6px;
  box-shadow: inset 0 0 0 3px color-mix(in oklch, var(--c, #8aa0b6) 18%, transparent);
}

.ct-tile-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ct-chip {
  font-size: 12px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
  background: var(--brand-soft); color: var(--brand); flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}
/* Техническая ячейка (дверца табло, сервисный отсек): замок есть, товара нет.
   Отличаем от размерного чипа цветом, иначе в списке типов они сливаются. */
.ct-chip.is-service { background: var(--warn-bg); color: var(--warn); }
.ct-dims { font-size: 12px; color: var(--muted); }

/* Чекбокс с поясняющей строкой в модалке типа ячейки. */
.ct-check { display: flex; gap: 10px; align-items: flex-start; margin-top: 12px; }
.ct-check input { margin-top: 3px; flex: 0 0 auto; }
.ct-check small { display: block; margin-top: 2px; font-size: 12px; line-height: 1.4; }

.ct-tile-actions {
  display: flex; align-items: center; justify-content: flex-end; gap: 4px;
  margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line);
}
.ct-tile-actions .inline { display: inline-flex; margin: 0; }
.ct-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; border: none; border-radius: 8px;
  background: none; color: var(--muted); cursor: pointer;
}
.ct-ico svg { width: 17px; height: 17px; }
/* В списках кнопка компактнее: 32px растягивали строку до 53px, и списки шли
   вразнобой с панелью, где строки по 36px. */
.grid td .ct-ico { width: 24px; height: 24px; border-radius: 6px; }
.grid td.col-data { padding-top: 5px; padding-bottom: 5px; }
.ct-ico:hover { color: var(--ink); background: var(--brand-soft); }
.ct-ico-locked { opacity: 0.4; cursor: not-allowed; }
.ct-ico-locked:hover { color: var(--muted); background: none; }
.ct-ico.danger:hover { color: var(--danger, #c0392b); background: color-mix(in oklch, var(--danger, #c0392b) 14%, transparent); }

/* Поля формы типа ячейки (в модалке) */
.ct-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px;
}
.ct-fields label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); }
.ct-fields input, .ct-fields select {
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: 14px; height: 36px; width: 100%;
}
.ct-fields select {
  appearance: none; -webkit-appearance: none; padding-right: 32px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%238a93a6' stroke-width='1.6'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
}
.ct-fields input[type="color"] { padding: 2px; cursor: pointer; }

.ct-modal {
  border: 1px solid var(--line); border-radius: 14px; padding: 0;
  background: var(--surface); color: var(--ink); width: min(560px, 92vw);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
}
.ct-modal::backdrop { background: rgba(0, 0, 0, 0.5); }
.ct-form { display: flex; flex-direction: column; gap: 16px; padding: 22px 24px; }
/* Мультиселект — списком с воздухом: нативный сжимался в строку и не читался. */
.ct-form select[multiple] { padding: 6px; min-height: 116px; }
.ct-form select[multiple] option { padding: 5px 8px; border-radius: var(--radius-sm); }
.ct-form textarea { font: inherit; resize: vertical; min-height: 76px; }
.ct-form h3 { margin: 0; }
.ct-modal-actions { display: flex; justify-content: flex-end; gap: 12px; align-items: center; }
/* Выбор плат секции в модалке: чекбокс · адрес */
.sect-board { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); }
.sect-board + .sect-board { margin-top: 8px; }
.sect-board input[type="checkbox"] { width: 16px; height: 16px; }
.sect-board-addr { font-weight: 600; font-variant-numeric: tabular-nums; }
.sect-board.is-busy { opacity: 0.55; }

/* ===== Общая модалка подтверждения ===== */
.confirm-modal {
  border: 1px solid var(--line); border-radius: 14px; padding: 0;
  background: var(--surface); color: var(--ink); width: min(420px, 92vw);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.35);
}
.confirm-modal::backdrop { background: rgba(0, 0, 0, 0.5); }
.confirm-box { display: flex; flex-direction: column; gap: 20px; padding: 24px; }
.confirm-box p { margin: 0; font-size: 15px; line-height: 1.5; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 12px; align-items: center; }

/* ===== Раскладка шкафа ===== */
.back-link { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; color: var(--muted); text-decoration: none; }
.back-link:hover { color: var(--brand); }
.cab-head-actions { display: flex; gap: 6px; align-items: center; }
.layout-toolbar { display: flex; gap: 14px; align-items: center; margin: 16px 0 18px; flex-wrap: wrap; }
.layout-toolbar .inline { display: inline-flex; gap: 8px; align-items: center; margin: 0; }
.layout-toolbar select {
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: 14px;
}
.layout-stats { margin-bottom: 12px; }
.layout-grid {
  --cell-unit: 64px;
  display: grid; gap: 6px; grid-auto-rows: var(--cell-unit);
  width: max-content; max-width: 100%;
  padding: 14px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface); overflow: auto;
}
.lcell {
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 6px 8px; border-radius: 8px; box-sizing: border-box;
  background: color-mix(in oklch, var(--c, #8aa0b6) 22%, var(--surface));
  border: 1.5px solid var(--c, #8aa0b6); color: var(--ink); overflow: hidden;
}
/* Ячейка шаблона, которой не досталось разъёма: место в сетке занимает, но
   ячейкой не является — ни номера, ни адреса, ни открытия. Раньше такие просто
   не рисовались, и в раскладке зияла дыра: непонятно, ячейку забыли или под неё
   нет замка. Штриховка отличает её от настоящей плитки даже боковым зрением. */
.lcell-off {
  --c: var(--line);
  color: var(--muted);
  border-style: dashed;
  cursor: default;
  background:
    repeating-linear-gradient(45deg, transparent 0 5px,
      color-mix(in oklch, var(--line) 55%, transparent) 5px 6px),
    var(--surface-2);
}
.lcell-off .lcell-id { opacity: .5; }
.lcell-id { font-weight: 700; font-size: 15px; line-height: 1; }
.lcell-num { font-weight: 700; font-size: 15px; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; }
.lcell-meta { font-size: 11px; color: var(--muted); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
.lcell-addr { font-size: 10px; color: var(--muted); opacity: 0.85; font-variant-numeric: tabular-nums; }
.free-slots { margin-top: 16px; }
.free-slots summary { cursor: pointer; color: var(--muted); font-size: 13px; }
.slot-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.slot-chip {
  font-size: 12px; padding: 4px 8px; border-radius: 999px;
  background: var(--brand-soft); color: var(--ink); border: 1px solid var(--line);
}

/* ===== DnD-редактор раскладки ===== */
.layout-work { display: flex; gap: 20px; align-items: flex-start; }
#layout-grid { flex: 1 1 auto; min-width: 0; }
#template-grid { flex: 0 0 auto; }  /* по ширине сетки — колонка рядом, не у края */
.layout-editor-row { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }
.layout-grid.editor { position: relative; }
.layout-grid.editor .gslot {
  border: 1px dashed color-mix(in oklch, var(--line) 80%, transparent);
  border-radius: 6px;
}
.layout-grid.editor.dragging .lcell { pointer-events: none; }
/* Pointer-перетаскивание ячейки: «призрак» под курсором + приглушённый оригинал */
.lcell-ghost { opacity: .9; box-shadow: 0 8px 24px oklch(0% 0 0 / .25); cursor: grabbing; transition: opacity .1s; }
.lcell.dragging-src { opacity: .35; }
/* вынесли за пределы сетки — «призрак» сигналит об удалении */
.lcell-ghost.will-delete {
  background: var(--err-bg); border-color: var(--err); color: var(--err); opacity: .75;
}

/* Подсветка места и сами ячейки — НАД подложкой табло (z-index 2). Иначе
   дверца табло, встав на своё место, оказывалась под ней и была не видна. */
.layout-grid .gslot.drop-ok,
.layout-grid .gslot.drop-bad { position: relative; z-index: 3; }
.layout-grid .gslot.drop-ok { background: color-mix(in oklch, var(--brand) 24%, transparent); border-color: var(--brand); }
.layout-grid .gslot.drop-bad { background: color-mix(in oklch, var(--danger, #c0392b) 22%, transparent); border-color: var(--danger, #c0392b); }
.lcell { cursor: grab; user-select: none; position: relative; z-index: 4; }
.lcell:active { cursor: grabbing; }
.lcell.selected { outline: 2px solid var(--brand); outline-offset: 1px; }
.lcell.is-new { border-style: dashed; }

.layout-palette {
  flex: 0 0 248px; padding: 16px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
  position: sticky; top: 20px; display: flex; flex-direction: column; gap: 12px;
}
.layout-palette > p { margin: 0; line-height: 1.5; }
.pal-title { display: block; }
.back-btn { align-self: flex-start; margin-bottom: 12px; padding: 6px 12px; font-size: 13px; }

/* Правая колонка редактора шаблона: палитра + кнопки + инспектор */
.tpl-side {
  flex: 0 0 260px; position: sticky; top: 20px;
  display: flex; flex-direction: column; gap: 14px;
}
.tpl-side .layout-palette,
.tpl-side .cell-inspector { position: static; flex: none; top: auto; }
.tpl-side-actions { display: flex; flex-direction: column; gap: 10px; }
.tpl-side-actions form { margin: 0; }
.tpl-side-actions .btn-primary,
.tpl-side-actions .btn-secondary { width: 100%; justify-content: center; }
.tpl-dirty { margin-top: 2px; }
.tpl-dirty[hidden] { display: none; }
.tpl-side-actions .result-box { margin: 0; }

/* hidden-атрибут должен побеждать display:flex этих блоков */
.cell-inspector[hidden], .layout-savebar[hidden] { display: none; }
.cell-inspector {
  flex: 0 0 240px; display: flex; flex-direction: column; gap: 12px;
  padding: 16px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface); position: sticky; top: 20px;
}
.cell-inspector label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); }
.cell-inspector select, .cell-inspector input {
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg, var(--surface)); color: var(--ink); font-size: 14px;
}
.cell-inspector .danger { align-self: flex-start; }
.cell-inspector .del-btn { width: 100%; justify-content: center; color: var(--err); }
.cell-inspector .del-btn:hover { background: var(--err-bg); border-color: var(--err); }

.layout-savebar {
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  padding: 10px 14px; margin: 0 0 16px; border-radius: 10px;
  background: var(--brand-soft); border: 1px solid var(--brand);
}
.layout-savebar [data-save-error] { margin: 0; }

/* ===== Шаблоны шкафов ===== */
.tpl-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
.tpl-card { display: flex; flex-direction: column; gap: 10px; }
.tpl-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tpl-head h3 { margin: 0; }
.tpl-boards { display: flex; flex-wrap: wrap; gap: 6px; }
.tpl-actions { display: flex; gap: 10px; align-items: center; margin-top: auto; padding-top: 6px; }
.tpl-add { margin-top: 22px; display: flex; flex-direction: column; gap: 12px; max-width: 640px; }
.tpl-add-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.tpl-add-row input, .tpl-add-row select {
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: 14px;
}
.save-tpl { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 8px; }
.save-tpl input {
  padding: 6px 9px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: 13px; max-width: 180px;
}
.save-tpl-msg.ok { color: var(--brand); font-size: 13px; }
.save-tpl-msg.err { color: var(--danger, #c0392b); font-size: 13px; }
.sub-h { font-size: 16px; margin: 26px 0 10px; }
.tpl-rename { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 8px; }
.tpl-rename label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); }
.tpl-rename input, .tpl-rename select {
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink); font-size: 14px;
}

/* ===== Шаблоны секций ===== */
.tpl-grid {
  display: grid; gap: var(--ct-gap);
  grid-template-columns: repeat(auto-fill, minmax(var(--ct-col-4), 1fr));  /* как в типах ячеек */
  align-items: stretch;
}
.tpl-grid .tpl-tile + .tpl-tile { margin-top: 0; }
.tpl-tile {
  display: flex; flex-direction: column; gap: 10px; padding: 14px;
  transition: border-color .12s, box-shadow .12s;
}
.tpl-tile:hover { border-color: color-mix(in oklch, var(--brand) 45%, var(--line)); box-shadow: 0 4px 18px oklch(0% 0 0 / .08); }
.tpl-tile-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tpl-tile-name { font-size: 15px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tpl-tile-head .status { text-transform: none; font-weight: 600; letter-spacing: 0; font-size: 10.5px; padding: 3px 8px; white-space: nowrap; flex: 0 0 auto; }

/* Мини-превью раскладки секции в плитке — полная сетка целиком, без скролла.
   Клетка не фиксированной величины, а доля от бокса: у высоких секций (4×16)
   фиксированные 13px не помещались в плитку и превью уезжало под скролл.
   Высота задана, ширину держит aspect-ratio → клетки остаются квадратными. */
.tpl-preview {
  /* Высота растёт с числом рядов, но в берегах: 4×3 не выглядит марочкой,
     а 4×16 не растягивает плитку на пол-экрана. */
  height: clamp(150px, calc(14px * var(--rows) + 60px), 320px);
  width: auto; max-width: 100%; margin-inline: auto;
  aspect-ratio: var(--cols) / var(--rows);
  display: grid; gap: var(--tpl-pgap, 3px);
  justify-content: stretch; align-content: stretch;
  grid-template-columns: repeat(var(--cols), 1fr);
  grid-template-rows: repeat(var(--rows), 1fr);
  padding: 12px; border-radius: var(--radius-sm); overflow: hidden;
  background:
    repeating-linear-gradient(45deg, transparent 0 6px, color-mix(in oklch, var(--line) 50%, transparent) 6px 7px),
    var(--surface-2);
  flex: 0 0 auto;
}
.tpl-preview:hover { text-decoration: none; }

/* Плитка шаблона: под превью отведено фиксированное место, поэтому карточки в
   сетке одной высоты — раньше высота считалась от числа рядов, и рядом стояли
   плитки 4×3 и 6×17 с разбегом в полтора раза. Сама раскладка вписывается
   внутрь: сторону клетки берём как минимум из «влезть по ширине» и «влезть по
   высоте», поэтому клетки остаются квадратными, а сетка не выходит за бокс. */
.tpl-preview-box {
  height: var(--tpl-box-h, 190px);
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
}
.tpl-tile .tpl-preview {
  --tpl-pgap: 2px;
  --tpl-ppad: 10px;
  /* Доступная ширина — от фактической плитки, а не от расчётной ширины колонки:
     колонка тянется (до четырёх в ряд), и превью, привязанное к жёстким 200px,
     переставало заполнять карточку. 100% здесь — ширина .tpl-preview-box, то
     есть плитка уже без своих паддингов. */
  --tpl-avail-w: calc(100% - var(--tpl-ppad) * 2
                      - var(--tpl-pgap) * (var(--cols) - 1));
  --tpl-avail-h: calc(var(--tpl-box-h, 190px) - var(--tpl-ppad) * 2
                      - var(--tpl-pgap) * (var(--rows) - 1));
  --tpl-cell: min(calc(var(--tpl-avail-w) / var(--cols)),
                  calc(var(--tpl-avail-h) / var(--rows)));
  width: calc(var(--tpl-cell) * var(--cols) + var(--tpl-pgap) * (var(--cols) - 1)
              + var(--tpl-ppad) * 2);
  height: calc(var(--tpl-cell) * var(--rows) + var(--tpl-pgap) * (var(--rows) - 1)
               + var(--tpl-ppad) * 2);
  aspect-ratio: auto;
  padding: var(--tpl-ppad);
  max-width: 100%; max-height: 100%;
}
.tpl-pslot { border-radius: 3px; background: color-mix(in oklch, var(--line) 45%, transparent); }
.tpl-pcell {
  border-radius: 3px;
  background: color-mix(in oklch, var(--c, #8aa0b6) 32%, var(--surface));
  border: 1px solid var(--c, #8aa0b6);
}
.tpl-disp {
  display: flex; align-items: center; justify-content: center; border-radius: 3px;
  background: color-mix(in oklch, var(--info) 22%, var(--surface));
  border: 1px solid color-mix(in oklch, var(--info) 60%, transparent);
  color: var(--info); font-size: 10px;
}
.tpl-foot { display: flex; flex-direction: column; gap: 4px; }
.tpl-foot-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tpl-foot .ct-dims { font-size: 12px; }
.tpl-tile-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: auto; padding-top: 10px; border-top: 1px solid var(--line); }

/* Модалка шаблона — шире (много полей) */
.tpl-modal { width: min(640px, 94vw); }
.ct-fields .span2 { grid-column: span 2; }
.form-sub { display: block; margin-top: 4px; font-size: 13px; color: var(--muted); }
.form-check { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
/* перебиваем .form label (flex-direction: column), иначе чекбокс встаёт над текстом по центру */
.form .form-check { flex-direction: row; align-items: center; color: var(--ink); }
.form-check input { width: 16px; height: 16px; flex: 0 0 16px; }
.tpl-display {
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 14px; margin: 0;
  display: flex; flex-direction: column; gap: 12px;
}
.tpl-display .form-sub { margin-top: 0; }
.tpl-display[hidden] { display: none; }

/* Редактор шаблона: табло-блок и палитра типов */
/* Техническая ячейка в редакторе шаблона и в палитре типов: она ложится ПОВЕРХ
   табло, поэтому её нужно отличать от товарной — иначе раскладка читается как
   ошибка («ячейка налезла на табло»). */
.layout-grid .lcell.is-service { border-style: dashed; background: var(--warn-bg); }
.layout-grid .lcell.is-service .lcell-meta { color: var(--warn); }
.type-chip.is-service .type-chip-shape { border-style: dashed; }
/* Место табло — подложка, а не элемент управления. `pointer-events: none`
   обязателен: на его клетки ставят технические ячейки (дверцы табло), а пока
   блок ловил мышь, dragover/drop до клетки под ним не доходили — не было ни
   подсветки, ни постановки, хотя логика размещения это уже разрешала. */
.tpl-disp-block {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border-radius: 8px; border: 1.5px dashed var(--muted); color: var(--muted);
  background: color-mix(in oklch, var(--ink) 12%, var(--surface)); font-size: 12px; z-index: 2;
  pointer-events: none;
}
.tpl-disp-block svg { width: 22px; height: 22px; }
.type-chips { display: flex; flex-direction: column; gap: 8px; }
.type-chip {
  display: flex; align-items: center; gap: 10px; cursor: grab;
  padding: 8px 10px; border-radius: 8px; font-size: 13px;
  background: var(--surface-2); border: 1px solid var(--line); user-select: none;
  transition: border-color .12s;
}
.type-chip:hover { border-color: color-mix(in oklch, var(--brand) 45%, var(--line)); }
.type-chip:active { cursor: grabbing; }
/* Мини-превью пропорций ячейки (как на /cell-types) */
.type-chip-preview {
  flex: 0 0 46px; height: 40px; display: flex; align-items: center; justify-content: center;
  border-radius: 6px; overflow: hidden;
  background:
    repeating-linear-gradient(45deg, transparent 0 5px, color-mix(in oklch, var(--line) 50%, transparent) 5px 6px),
    var(--surface);
}
.type-chip-shape {
  aspect-ratio: var(--cols) / var(--rows);
  height: min(32px, calc(var(--rows) * 11px));
  max-width: 40px;
  background: color-mix(in oklch, var(--c, #8aa0b6) 28%, var(--surface));
  border: 1.5px solid var(--c, #8aa0b6); border-radius: 3px;
  box-shadow: inset 0 0 0 2px color-mix(in oklch, var(--c, #8aa0b6) 18%, transparent);
}
.type-chip-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.type-chip-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.type-chip-dims { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ===== Единый стиль выпадающих списков ===== */
/* Своя стрелка вместо нативной для всех select; перечисленные классы имеют ту же
   специфичность, что и их исходные правила, но идут позже — поэтому выигрывают.
   .filter-select и .ct-fields select уже стилизованы тем же чевроном. */
select,
.form select, .filters select, .board-add select, .cab-add select, .kind-row select,
.layout-toolbar select, .cell-inspector select, .tpl-add-row select, .tpl-rename select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%238a93a6' stroke-width='1.6'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
}
select:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand);
}
/* Компактные селекты времени (часы/минуты) — стрелка ближе, поле уже. */
.time-select select {
  padding: 6px 22px 6px 8px;
  background-position: right 6px center;
}

/* ===== Инициализация (bootstrap) ===== */
.bootstrap-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.bootstrap-head h3 { margin: 0 0 4px; }
.bootstrap-head p { max-width: 56ch; }
.bootstrap-run { margin-top: 12px; }
.bootstrap-log {
  max-height: 340px; overflow: auto; white-space: pre-wrap; word-break: break-word;
  margin: 10px 0 0; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.45;
  scrollbar-width: none; -ms-overflow-style: none;
}
.bootstrap-log::-webkit-scrollbar { width: 0; height: 0; }
.bootstrap-log-wrap { margin-top: 10px; }
.bootstrap-log-wrap > summary {
  cursor: pointer; font-size: 12.5px; color: var(--muted);
  user-select: none; width: fit-content;
}
.bootstrap-log-wrap[open] > summary { margin-bottom: 2px; }
.bootstrap-log-wrap .bootstrap-log { margin-top: 8px; }

/* ===== Права: неактивные контролы + тосты ===== */
/* Мутирующие контролы видны всегда; при нехватке прав — приглушены и не активны
   (клик/submit перехватывает perms.js и показывает уведомление). */
.no-edit .perm-edit, .no-control .perm-control { opacity: 0.5; }
.no-edit .perm-edit, .no-edit .perm-edit *,
.no-control .perm-control, .no-control .perm-control * { cursor: not-allowed; }

.toast-host {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 1000; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none;
}
/* Плитка уведомления. Тип виден цветной кромкой и иконкой: удачный исход и
   ошибка приходят одним каналом, и различать их только текстом мало. */
.toast {
  pointer-events: auto;
  display: flex; align-items: flex-start; gap: 10px;
  max-width: min(92vw, 460px); padding: 11px 12px 11px 14px;
  border: 1px solid var(--line); border-left-width: 4px;
  border-radius: var(--radius-sm);
  background: var(--surface); color: var(--ink); font-size: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
  opacity: 0; transform: translateY(8px); transition: opacity 0.2s, transform 0.2s;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast-ok { border-left-color: var(--ok); }
.toast-error { border-left-color: var(--err); }
.toast-ico { flex: 0 0 auto; line-height: 0; margin-top: 1px; }
.toast-ico svg { width: 18px; height: 18px; }
.toast-ok .toast-ico { color: var(--ok); }
.toast-error .toast-ico { color: var(--err); }
.toast-text { flex: 1 1 auto; }
.toast-x {
  flex: 0 0 auto; background: none; border: 0; padding: 2px; cursor: pointer;
  color: var(--muted); line-height: 0; border-radius: 6px;
}
.toast-x:hover { color: var(--ink); background: var(--surface-2); }
.toast-x svg { width: 14px; height: 14px; }

/* ===== Кастомный выпадающий список (selects.js) ===== */
/* Нативный select скрыт, но остаётся в DOM (submit/HTMX/чтение .value).
   Скрываем ПО СТРУКТУРЕ (любой select внутри обёртки .select), а не только по классу
   .select-native: класс навешивает JS, и если он не проставился/затёрся (гонка
   enhance ↔ htmx settle при свапе одинаковых id), нативный список оставался виден
   рядом с кастомным — визуально «задваивался». */
.select > select,
.select-native {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); border: 0; opacity: 0; pointer-events: none;
}
.select { position: relative; display: block; width: 100%; }
.select-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; min-height: 38px; padding: 8px 12px; font: inherit; font-size: 14px;
  text-align: left; color: var(--ink); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-sm); cursor: pointer;
}
.select-trigger:hover { border-color: color-mix(in oklch, var(--brand) 40%, var(--line)); }
.select.open .select-trigger, .select-trigger:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand);
}
.select-trigger:disabled { opacity: 0.6; cursor: not-allowed; }
.select-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.select-value.placeholder { color: var(--muted); }
.select-caret { width: 12px; height: 8px; flex: 0 0 auto; color: var(--muted); transition: transform 0.12s; }
.select.open .select-caret { transform: rotate(180deg); }
.select-panel {
  position: absolute; z-index: 60; top: calc(100% + 4px); left: 0; min-width: 100%;
  max-height: 264px; overflow-y: auto; margin: 0; padding: 4px; list-style: none;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: 0 10px 28px rgba(0, 0, 0, 0.18);
  scrollbar-width: none; -ms-overflow-style: none;  /* скролл есть, полоса скрыта */
}
.select-panel::-webkit-scrollbar { width: 0; height: 0; }
.select-option {
  padding: 8px 10px; border-radius: 7px; font-size: 14px; line-height: 1.3;
  white-space: nowrap; cursor: pointer;
}
.select-option.active { background: var(--surface-2); }
.select-option.sel { color: var(--brand); font-weight: 600; }
.select-option[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

/* Размерные контексты (вместо ширин, что были на самих select). */
.events-filters .select, .toolbar .select { display: inline-block; width: auto; min-width: 200px; }
.cab-add .select { min-width: 200px; }
.board-add .select { min-width: 150px; }
.cab-kind .select { width: 100%; }
.time-select .select { display: inline-block; width: auto; min-width: 0; }
.time-select .select-trigger { min-height: 34px; padding: 6px 8px; gap: 4px; }
.time-select .select-panel { min-width: 64px; }

/* ===== Экран постамата (noVNC) ===== */
.screen-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
/* Кадр камеры вписываем в карточку: у камеры своё разрешение, и без ограничения
   ширины она распирала колонку. */
.camera-frame { display: block; width: 100%; max-width: 640px; height: auto; border-radius: 10px; }
.screen-viewport {
  width: 100%;
  max-width: 1024px;
  aspect-ratio: 1024 / 600;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
  margin-top: 6px;
}
/* noVNC создаёт canvas внутри контейнера — вписываем по ширине */
.screen-viewport canvas { display: block; max-width: 100%; }

/* Консоль постамата: терминал фиксированной высоты, xterm сам рисует внутри. */
.console-viewport {
  width: 100%;
  height: 420px;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
  margin-top: 6px;
  padding: 8px;
}
.console-viewport .xterm { height: 100%; }

/* Мини-превью раскладки в карточке секции: та же сетка, что в плитке шаблона,
   но компактнее (в секции превью — вспомогательный элемент, не главный). */
/* Превью шаблона слева, состояние раскладки + кнопка справа. */
.cab-tpl-view { display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap; }
.cab-tpl-view .tpl-preview { height: clamp(140px, calc(12px * var(--rows) + 50px), 260px); max-width: 100%; flex: 0 0 auto; }
.cab-tpl-view .cab-layout { flex: 1 1 260px; flex-direction: column; align-items: flex-start; gap: 12px; }
.cab-tpl-view .cab-layout .btn-secondary { margin-left: 0; }
/* Выбор шаблона секции */
.cab-tpl { display: flex; flex-direction: column; gap: 6px; }
.cab-tpl > label { font-size: 13px; color: var(--muted); }
.cab-tpl-row { display: flex; gap: 10px; align-items: center; }
.cab-tpl-row .select { flex: 1 1 auto; min-width: 0; }
.cab-tpl-row .btn-secondary { flex: 0 0 auto; }

/* Датчики t° в обнаружении: id не переносим, поле калибровки — компактное. */
#sensors-card .payload { white-space: nowrap; }
#sensors-card .sensor-cal { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#sensors-card .sensor-cal input[type="text"] { width: 96px; }
#sensors-card .sect-head { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 8px; }

/* Плата подсветки секции: выбор виден и когда он единственный (radio disabled —
   секция из одной платы, снимать нечего). */
.board-light { width: 16px; height: 16px; accent-color: var(--brand); cursor: pointer; }
.board-light:disabled { opacity: 1; cursor: default; }

/* «Применить топологию»: ручной экспорт рядом с автопушем. */
.ct-apply-actions { display: flex; align-items: center; gap: 10px; flex-shrink: 0;
  margin-left: auto; }
/* Текст и кнопки — одной строкой; переносим только на узком экране. */
.ct-apply { flex-wrap: nowrap; }
.ct-apply .ct-discover-txt { max-width: none; }
@media (max-width: 720px) { .ct-apply { flex-wrap: wrap; } }

/* Экспорт: YAML длинный — держим страницу в разумной высоте, скроллим сам блок. */
.export-yaml { max-height: 60vh; overflow: auto; }

/* Экран открытия ячеек: плитки раскладки — кликабельные кнопки. */
.cells-open .lcell-open {
  cursor: pointer; text-align: left; font: inherit; color: inherit;
  transition: transform .06s ease, border-color .12s ease;
}
.cells-open .lcell-open:hover:not(:disabled) { border-color: var(--brand); }
.cells-open .lcell-open:active:not(:disabled) { transform: scale(.97); }
.cells-open .lcell-open:disabled { cursor: not-allowed; opacity: .55; }
/* Ссылка-кнопка в состоянии «нельзя» (нет топологии / не инициализирован). */
a.btn-primary.disabled { pointer-events: none; opacity: .5; }
.cells-open { margin-top: 14px; }
/* Карточка секции начинается сразу под панелью команд — без отступа они
   слипались в один блок, и было неясно, где кончается ввод и начинается
   раскладка. */
/* Карточки секций начинаются под панелью команд, а не вплотную к ней. */
.cells-sections { margin-top: 18px; }
/* Заголовок секции — в две строки: номер, под ним состав. В строку название
   шаблона («Головной-2 (Оранжевый)») не помещалось и переносилось само, разрывая
   строку в случайном месте. */
.cells-sections .cab-title { flex-direction: column; align-items: flex-start; gap: 2px; }

/* Дверца открыта по факту (опрос замков), а не «команда ушла»: жёлтая рамка
   держится, пока дверь не закроют. Вспышка при нажатии осталась зелёной — это
   разные вещи, и путать их нельзя. */
.cells-open .lcell-open.is-unlocked {
  border-color: var(--warn); box-shadow: inset 0 0 0 2px var(--warn-bg);
}
.cells-open .lcell-open.is-unlocked .lcell-id { color: var(--warn); }
/* Техническая ячейка (дверца табло, сервисный отсек): замок настоящий, товара
   нет. Стоит она обычно ПОВЕРХ табло, поэтому нужен собственный вид — иначе
   читается как товарная ячейка, случайно попавшая на экран. */
.cells-open .lcell-open.is-service {
  background: var(--warn-bg);
  border-style: dashed;
}
.cells-open .lcell-open.is-service .lcell-meta { color: var(--warn); }
/* Плата не ответила — состояние неизвестно, но плитку не выдаём за исправную. */
.cells-open .lcell-open.is-unknown {
  border-style: dashed; opacity: .75;
}
/* Раскладка на экране открытия — схема, а не редактор: плитку здесь не таскают
   и не читают, по ней кликают. Шаблон на 17 рядов при 64px давал почти 1200px
   на секцию, и постамат из трёх секций не помещался на экран ни при какой
   прокрутке; на 20px весь постамат виден целиком, как схема на двери.
   На плитке остаётся только номер — разъём и тип уехали в подсказку, читать
   их на ходу всё равно не нужно, а выбирают ячейку по месту в раскладке. */
.cells-open { --cell-unit: 20px; gap: 2px; padding: 6px; }
.cells-open .lcell {
  padding: 0 1px; border-width: 1px; border-radius: 4px;
  align-items: center; justify-content: center;
}
.cells-open .lcell-id { font-size: 10px; }
.cells-open .lcell-addr,
.cells-open .lcell-meta { display: none; }

/* Секции в ряд, как шкафы стоят в постамате. Карточка по ширине своей сетки:
   растянутая на всю строку секция из четырёх колонок выглядела бы пустой. */
/* Ряд секций — сеткой равных колонок. Флексом карточки тянулись каждая по
   ширине своей раскладки: секция на 6 плат выходила вдвое шире секции на 2, и
   ряд выглядел рваным, хотя это соседние шкафы одного постамата. */
.cells-sections {
  display: grid; gap: 16px; align-items: stretch;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
/* Карточки ряда одной высоты: соседние шкафы одного постамата, и разная высота
   читалась как разница по важности. */
.cells-sections > .card { max-width: 100%; min-width: 0; align-self: stretch; }
/* Отступы между карточками держит gap сетки. Общий `.card + .card` сдвигал бы
   каждую следующую вниз — в ряду это выглядело как ступенька. */
.cells-sections > .card + .card { margin-top: 0; }
/* Сама раскладка внутри карточки — по центру: колонка шире её сетки. */
.cells-sections .layout-grid { margin-inline: auto; }
/* Пояснение под кнопкой обновления образа — отделяем от неё. */
.base-note { margin-top: 10px; }
/* Ячейка открыта: зелёная вспышка на плитке — вместо текстового «успех». */
.cells-open .lcell-open.opened {
  border-color: var(--ok); background: var(--ok-bg);
  animation: cell-flash 2.5s ease-out;
}
@keyframes cell-flash {
  0%   { box-shadow: 0 0 0 0 color-mix(in oklch, var(--ok) 70%, transparent); }
  60%  { box-shadow: 0 0 0 8px color-mix(in oklch, var(--ok) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--ok) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) { .cells-open .lcell-open.opened { animation: none; } }

/* Журнал событий: «Сбросить» держит своё место всегда — иначе появление кнопки
   после выбора фильтра сдвигало бы селекты и таблицу. */
.events-filters .events-reset { flex: 0 0 auto; margin-left: 4px; }
.events-filters .events-reset.is-empty { visibility: hidden; pointer-events: none; }

/* Версии в настройках: индикатор рядом со значением; блок держит высоту, пока
   идёт ленивая проверка GitLab. */
.ver-state { display: inline-flex; align-items: center; gap: 6px; }
/* Ссылка на релиз всегда занимает свою строку: без неё блок подпрыгивал бы,
   когда проверка GitLab возвращает версию. */
.ver-link.is-empty { visibility: hidden; pointer-events: none; }

/* Детали ключа в модалке: пары «поле — значение», как в карточке устройства. */
.ak-detail h4 { margin: 0 0 12px; font-size: 16px; }
.ak-detail .key-rows { display: flex; flex-direction: column; gap: 7px; font-size: 14px; }
.ak-detail .key-row { display: grid; grid-template-columns: 150px 1fr; gap: 12px; }
.ak-detail .key-row .k { color: var(--muted); }
.ak-detail .key-row .v { color: var(--ink); overflow-wrap: anywhere; }

/* Выданный секрет показывается один раз — переносим его целиком, не обрезая. */
#api-keys .token-banner code { word-break: break-all; }
/* Отозванные и истёкшие приглушены, но остаются в списке — они нужны аудиту. */
#api-keys .row-off td { opacity: .55; }

/* ===== Документация API ===== */
.doc section { margin-bottom: 34px; scroll-margin-top: 16px; }
.doc h2 { margin: 0 0 10px; font-size: 19px; }
.doc p { margin: 0 0 12px; font-size: 14px; line-height: 1.65; color: var(--muted); max-width: 780px; }
.doc p b { color: var(--ink); }
.doc code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px;
  padding: 1px 5px; border-radius: 5px; background: var(--surface-2); color: var(--ink);
}
.doc .code-block { margin: 0 0 12px; }
.doc .code-wrap { margin: 0 0 12px; }
.doc .code-block code { background: none; padding: 0; color: inherit; }
.doc .doc-url { font-size: 13px; }
/* Списки инструкций — тем же кеглем и шириной, что абзацы страницы. */
.doc .doc-list, .doc .doc-steps {
  margin: 0 0 12px; padding-left: 20px; max-width: 780px;
  font-size: 14px; line-height: 1.65; color: var(--muted);
}
.doc .doc-list li, .doc .doc-steps li { margin-bottom: 6px; }
.doc .doc-list b, .doc .doc-steps b { color: var(--ink); }
.doc-steps { margin: 0 0 12px; padding-left: 20px; font-size: 14px; line-height: 1.65; color: var(--muted); max-width: 780px; }
/* Служебный штрихкод в инструкции: белый фон обязателен — сканеру нужен контраст,
   в тёмной теме полосы иначе теряются. */
.doc-figure { margin: 0 0 16px; width: 100%; max-width: 520px; }
.doc-figure img {
  display: block; width: 100%; height: auto; background: #fff;
  padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.doc-figure figcaption { margin-top: 6px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
/* Схема-разметка экрана: рисуется цветами темы, поэтому без белой подложки
   штрихкодов — им она нужна для контраста, схеме мешает. */
.doc-figure-svg { max-width: 420px; }
.doc-figure-svg svg { display: block; width: 100%; height: auto; }
.doc-steps li { margin-bottom: 8px; }
.doc-errors { max-width: 780px; margin: 0 0 12px; }
.doc-errors td code { font-size: 12px; }
.doc-toc nav { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.doc-toc nav a { font-size: 14px; color: var(--muted); }
.doc-toc nav a:hover { color: var(--ink); }

/* ===== Аналитика ===== */
.dash-analytics { margin-top: 22px; }
/* 160px, а не 210: при 210 четвёртая карточка не влезала в ряд, уезжала вниз и
   выглядела обрезанной — в своём ряду её тянуть по высоте нечему. */
.dash-kpi {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 14px; margin-bottom: 18px;
}
.kpi {
  display: flex; flex-direction: column; gap: 4px; padding: 16px 18px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  color: inherit; text-decoration: none;
}
a.kpi:hover { border-color: color-mix(in oklch, var(--brand) 45%, var(--line)); text-decoration: none; }
.kpi-label { font-size: 12px; text-transform: uppercase; letter-spacing: .03em; color: var(--muted); }
.kpi-value { font-size: 30px; font-weight: 700; line-height: 1.1; }
.kpi-value small { font-size: 16px; font-weight: 600; color: var(--muted); margin-left: 2px; }
.kpi-note { font-size: 12px; color: var(--muted); }
.kpi-bad .kpi-value { color: var(--err); }
.kpi.is-skeleton { opacity: .5; }
/* Полоса загрузки: ширина inline, цвет — по месту, чтобы не заводить модификаторы. */
.kpi-bar, .load-bar {
  display: block; height: 6px; border-radius: 999px; background: var(--surface-2);
  overflow: hidden; margin-top: 6px;
}
.kpi-bar > span, .load-bar > span { display: block; height: 100%; background: var(--brand); }
.load-bar { display: inline-block; width: 60px; margin: 0 8px 0 0; vertical-align: middle; }

.dash-tops { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px; margin-top: 14px; }
.top-card { padding: 16px 18px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); }
.top-card h3 { margin: 0 0 10px; font-size: 15px; }
.top-list { margin: 0; padding: 0; list-style: none; counter-reset: top; }
.top-list li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 14px;
}
.top-list li:last-child { border-bottom: 0; }
.top-metric { font-weight: 600; white-space: nowrap; }
.top-metric small { font-weight: 400; font-size: 11px; color: var(--muted); margin-left: 4px; }

.period-switch { display: flex; gap: 8px; justify-content: flex-end; margin-bottom: 14px; }
.period-switch .is-active { border-color: var(--brand); color: var(--brand); }

/* Координатная сетка столбиковых графиков: линии на 0 / 50 / 100% и шкала слева.
   Без них высота столбика говорит только «больше-меньше», а не сколько. */
.plot { display: grid; grid-template-columns: auto 1fr; gap: 10px; margin-top: 12px; }
.plot-y {
  display: flex; flex-direction: column; justify-content: space-between;
  font-size: 10px; color: var(--muted); text-align: right;
  font-variant-numeric: tabular-nums; padding-bottom: 1px;
}
.plot-area {
  /* Ось X — граница снизу; линии сетки рисуем только на середине и сверху,
     иначе у оси оказывались две линии впритык. */
  border-bottom: 1px solid var(--line);
  background-image: linear-gradient(var(--line), var(--line)),
                    linear-gradient(var(--line), var(--line));
  background-size: 100% 1px, 100% 1px;
  background-position: 0 0, 0 50%;
  background-repeat: no-repeat;
}

/* Столбики заказов по дням: свои, без библиотеки — две серии и подписи дат. */
.chart-bars { display: flex; align-items: flex-end; gap: 6px; height: 160px; margin-top: 0; }
/* max-width обязателен: за один день в периоде столбец растягивался на пол-карточки
   и график превращался в заливку. */
.chart-col {
  position: relative; flex: 1 1 0; max-width: 54px; height: 100%;
  display: flex; align-items: flex-end; gap: 2px; min-width: 0;
}
.chart-col .bar { flex: 1 1 0; border-radius: 4px 4px 0 0; min-height: 2px; }
.bar-created { background: color-mix(in oklch, var(--brand) 45%, var(--surface-2)); }
.bar-picked { background: var(--brand); }
.chart-x {
  position: absolute; bottom: -18px; left: 0; right: 0; text-align: center;
  font-size: 10px; color: var(--muted); white-space: nowrap; overflow: hidden;
}
.chart-legend { display: flex; gap: 16px; margin-top: 26px; font-size: 12px; color: var(--muted); }
.chart-legend .lg { display: inline-flex; align-items: center; gap: 6px; }
.chart-legend i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.chart-legend .sw-created { background: color-mix(in oklch, var(--brand) 45%, var(--surface-2)); }
.chart-legend .sw-picked { background: var(--brand); }
.coord-wrap { display: flex; align-items: center; gap: 8px; }
.coord-wrap input { flex: 1 1 auto; min-width: 0; }
.coord-wrap .btn-link { white-space: nowrap; padding: 0; }

/* Строка заказа в ячейке: номер и состав друг под другом. */
.cell-order { display: flex; flex-direction: column; gap: 2px; }
.cell-order .order-id { font-weight: 600; }
.pm-state + .badge-off { margin-left: 6px; }

/* ===== Главная: сводка, карта, события ===== */
/* Одна строка состояния вместо россыпи плашек: цифры читаются подряд, а цвет
   слева сразу говорит, надо ли вмешиваться. */
.park-state {
  display: grid; grid-template-columns: 210px 1fr; gap: 20px; margin-bottom: 18px;
  padding: 16px 20px; border-radius: var(--radius);
  background: var(--surface); border: 1px solid var(--line);
  /* Высота под вердикт и две строки показателей зарезервирована заранее: сводка
     приезжает htmx-ом, и без резерва карта под ней подпрыгивала. */
  min-height: 168px; border-left: 4px solid var(--line);
}
.park-state.is-ok { border-left: 4px solid var(--ok); }
.park-state.is-bad { border-left: 4px solid var(--err); }
/* Вердикт — отдельная колонка слева: сначала «надо ли вмешиваться», потом цифры. */
.ps-verdict {
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
  padding-right: 20px; border-right: 1px solid var(--line);
}
.ps-mark {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 19px; font-weight: 700; background: var(--surface-2); color: var(--muted);
}
.park-state.is-ok .ps-mark { background: var(--ok-bg, var(--surface-2)); color: var(--ok); }
.park-state.is-bad .ps-mark { background: var(--err-bg); color: var(--err); }
.ps-title { font-size: 17px; font-weight: 600; line-height: 1.25; }
.ps-period { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
/* Показатели — сеткой 3×2: числа встают по одной линии и вертикально, и
   горизонтально. Число колонок фиксировано, потому что разделитель у первой
   колонки ряда снимается через :nth-child. */
.ps-facts {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 14px 0; font-size: 13px; color: var(--muted);
}
.ps-fact { padding: 0 18px; border-left: 1px solid var(--line); min-width: 0; }
.ps-fact:nth-child(3n + 1) { padding-left: 0; border-left: 0; }
.ps-value { font-size: 22px; font-weight: 600; color: var(--ink); line-height: 1.15; }
.ps-value small { font-size: 13px; font-weight: 400; color: var(--muted); }
.ps-value.is-bad { color: var(--err); }
.ps-label { margin-top: 3px; color: var(--ink); }
.ps-sub { margin-top: 2px; font-size: 12px; }
.ps-sub.is-bad { color: var(--err); }
.ps-facts a { color: var(--err); }
@media (max-width: 1250px) {
  .park-state { grid-template-columns: 1fr; gap: 14px; }
  .ps-verdict {
    flex-direction: row; align-items: center; gap: 12px;
    padding: 0 0 12px; border-right: 0; border-bottom: 1px solid var(--line);
  }
  .ps-period { margin-left: auto; }
}
@media (max-width: 900px) {
  .ps-facts { grid-template-columns: repeat(2, 1fr); }
  .ps-fact { padding-left: 18px; border-left: 1px solid var(--line); }
  .ps-fact:nth-child(2n + 1) { padding-left: 0; border-left: 0; }
}
/* Полоска занятости — во всю ширину своей колонки, под подписью: врезкой в
   строку она читалась как случайная точка рядом с процентом. */
.ps-bar {
  display: block; width: 100%; height: 5px; border-radius: 999px;
  background: var(--surface-2); overflow: hidden; margin-top: 8px;
}
.ps-bar > i { display: block; height: 100%; background: var(--brand); }

/* Отступ задаём явно: между картой и событиями лежат теги <script> с
   инициализацией карты, и общий селектор `.card + .card` до сюда не достаёт. */
/* Подвал списка: размер страницы слева, листание справа. Один вид на все
   разделы — иначе каждый список листается по-своему. */
.list-pager {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  margin-top: 10px; font-size: 13px; flex-wrap: wrap;
}
.lp-size, .lp-pages { display: inline-flex; align-items: center; gap: 6px; }
.lp-size { gap: 8px; }
.lp-count { margin-left: auto; }
.lp-page {
  min-width: 30px; height: 30px; padding: 0 8px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); font-weight: 600; font-variant-numeric: tabular-nums;
}
.lp-page:hover { background: var(--surface-2); color: var(--ink); text-decoration: none; }
.lp-page.is-current { background: var(--brand-soft); color: var(--brand); }
.lp-arrow { font-size: 18px; line-height: 1; }
/* Стрелка на краю не исчезает, а гаснет: иначе номера прыгают при листании. */
.lp-page.is-off { opacity: .35; pointer-events: none; }
.lp-gap { color: var(--muted); padding: 0 2px; }
.lp-chip {
  padding: 3px 10px; border-radius: 999px; color: var(--muted);
  background: var(--surface-2); font-weight: 600;
}
.lp-chip:hover { color: var(--ink); text-decoration: none; }
.lp-chip.is-active { background: var(--brand-soft); color: var(--brand); }

/* Списки на панели и в аналитике: таблица сама себе подложка, карточки вокруг
   нет (иначе фон в фоне и двойная рамка). */
.list-block { margin-top: 16px; }
.list-block h3 { margin: 0 0 4px; font-size: 15px; }
.list-block > p { margin: 0 0 10px; }
.list-block > h3:only-of-type + table, .list-block > h3 + div { margin-top: 10px; }
/* Скругление низа держит уже tfoot, а не последняя строка тела. */
.events-grid tbody tr:last-child td:first-child,
.events-grid tbody tr:last-child td:last-child { border-radius: 0; }
.events-grid tbody tr:last-child td { border-bottom: 1px solid var(--line); }
.events-grid tfoot td {
  padding: 9px 14px; background: var(--surface-2); border-bottom: 0;
  border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius);
}
.events-grid tfoot a { font-size: 13px; }
.events-grid tfoot tr:hover td { background: var(--surface-2); }

/* Карта парка — одной подложкой, как список событий: заголовок снаружи, рамка
   общая для карты и подвала. */
.map-block { margin-top: 16px; }
.map-block h3 { margin: 0 0 10px; font-size: 15px; }
.map-frame {
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column;
}
.map-foot {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 9px 14px; background: var(--surface-2); font-size: 13px;
}
.park-map { flex: 1 1 auto; height: 460px; width: 100%; background: var(--surface-2); }
.map-empty { min-height: 160px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px 18px; }
.map-empty h3 { margin: 0 0 8px; }

/* Красный чип для аппаратных ошибок: в общем журнале они тоже выделяются. */
.evtype.is-bad { background: var(--err-bg); color: var(--err); }
/* Скелеты: высота строк уже занята, поэтому приход данных ничего не двигает. */
.is-skeleton { opacity: .45; }
.is-skeleton td, .is-skeleton th { color: transparent; }
.is-skeleton .evtype { background: var(--surface-2); }

@media (max-width: 1100px) {
  .park-map { height: 380px; }
}

/* ===== Графики аналитики ===== */
/* Ряд графиков — не .card, поэтому общее правило `.card + .card` его не видит:
   вертикальный ритм задаём самому ряду и тому, что идёт после него. */
.chart-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; margin-top: 16px; }
.chart-row > .card { margin-top: 0; }
.chart-row + * { margin-top: 16px; }

/* Воронка заказов одной полосой: доли видно без чтения чисел. */
/* Цвета по смыслу: выдан — успех, отклонён/просрочен — ошибка, в работе — бренд. */
.seg-picked_up { background: var(--ok); }
.seg-placed { background: var(--brand); }
.seg-reserved { background: color-mix(in oklch, var(--brand) 55%, var(--surface-2)); }
.seg-pending { background: color-mix(in oklch, var(--brand) 30%, var(--surface-2)); }
.seg-retrieval_requested { background: var(--warn, #f0a132); }
.seg-returned { background: color-mix(in oklch, var(--warn, #f0a132) 60%, var(--surface-2)); }
.seg-expired { background: color-mix(in oklch, var(--err) 55%, var(--surface-2)); }
.seg-cancelled { background: var(--muted); }
.seg-rejected { background: var(--err); }

/* Горизонтальные дорожки: общий вид для «что стало с заказами» и «как быстро
   забирают» — оба отвечают на вопрос «сколько чего», и язык у них один. */
.bars-h { display: flex; flex-direction: column; gap: 9px; margin-top: 12px; }
.bh-row {
  display: grid; grid-template-columns: 122px 1fr 34px 38px; gap: 10px;
  align-items: center; font-size: 13px;
}
.bh-label { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Вертикальная сетка на дорожке — четверти, чтобы длину можно было прикинуть. */
.bh-track {
  height: 14px; border-radius: 4px; background: var(--surface-2); overflow: hidden;
  background-image: repeating-linear-gradient(to right, var(--line) 0 1px, transparent 1px 25%);
}
.bh-track > span { display: block; height: 100%; border-radius: 4px; }
/* Заливка по умолчанию — только у дорожек без своего цвета: у статусов заказа он
   свой (seg-*), и общее правило его перекрывало. */
.bh-track > span:not([class]) { background: var(--brand); }
.bh-value { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
.bh-share { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Часы суток: 24 столбика, подписи через три часа — иначе не читаются. */
.hours { display: flex; align-items: flex-end; gap: 4px; height: 150px; margin-top: 0; }
.hour-col { position: relative; flex: 1 1 0; height: 100%; display: flex; align-items: flex-end; min-width: 0; }
.hour-bar { display: block; width: 100%; min-height: 2px; border-radius: 4px 4px 0 0; background: var(--brand); }
.hour-x { position: absolute; bottom: -18px; left: 0; right: 0; text-align: center; font-size: 10px; color: var(--muted); }
.hours + .chart-legend, .hours ~ * { margin-top: 24px; }

/* Заливка базового образа: лента шагов вместо одной строки. Процесс идёт
   минутами, и оператору нужно видеть, где он и что не завис. */
.ota { display: flex; flex-direction: column; gap: 10px; }
.ota-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ota-steps { list-style: none; display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 0; padding: 0; }
.ota-step { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); }
.ota-step .ota-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: color-mix(in oklch, var(--line) 70%, transparent);
}
.ota-step.done { color: var(--ink); }
.ota-step.done .ota-dot { background: var(--brand); }
/* Текущий шаг пульсирует — видно, что процесс живой, а не замер. */
.ota-step.current { color: var(--ink); font-weight: 600; }
.ota-step.current .ota-dot { background: var(--warn); animation: ota-pulse 1.4s ease-in-out infinite; }
@keyframes ota-pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .ota-step.current .ota-dot { animation: none; } }

/* ===== Мобильный слой =====
   Держим в самом конце файла: правила ниже перекрывают десктопные адаптивные
   блоки (.ct-layout на 1500px, .detail-grid на 1400px), которые иначе выигрывали
   бы по порядку и, например, снова поднимали справку над содержимым. */
/* Шапка телефона и подложка меню на десктопе не нужны — там сайдбар на месте. */
.mobile-bar, .nav-backdrop { display: none; }

@media (max-width: 860px) {
  /* Сайдбар в строку не помещается: восемь разделов с иконками и подписями
     съедали пол-экрана над контентом. Уводим его за край и выдвигаем поверх
     страницы, как принято на телефоне. */
  body { grid-template-columns: 1fr; }

  .mobile-bar {
    display: flex; align-items: center; gap: 10px;
    position: sticky; top: 0; z-index: 40;
    padding: 8px 12px; padding-top: max(8px, env(safe-area-inset-top));
    background: var(--surface); border-bottom: 1px solid var(--line);
  }
  .mb-burger {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; flex: 0 0 40px;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--surface); color: var(--ink); cursor: pointer;
  }
  .mb-burger svg { width: 20px; height: 20px; }
  .mb-brand { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--ink); }
  .mb-brand:hover { text-decoration: none; }
  .mb-brand .brand-mark { width: 24px; height: 24px; }
  .mb-user {
    margin-left: auto; width: 34px; height: 34px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--brand-soft); color: var(--brand); font-weight: 700; font-size: 12px;
  }

  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 60;
    width: min(280px, 84vw); height: 100dvh; overflow-y: auto;
    border-right: 1px solid var(--line);
    transform: translateX(-100%); transition: transform .18s ease;
  }
  html.nav-open .sidebar { transform: none; box-shadow: 0 0 40px oklch(0% 0 0 / .35); }
  /* Свёрнутый до иконок режим — десктопная привычка; на выдвижной панели он
     оставил бы меню без подписей. */
  html.nav-collapsed .sidebar { width: min(280px, 84vw); padding: 18px 14px; }
  html.nav-collapsed .brand,
  html.nav-collapsed .nav-label { display: initial; }
  html.nav-collapsed .who-name { display: block; }
  html.nav-collapsed .side-nav a { justify-content: flex-start; gap: 10px; padding-left: 10px; }
  .nav-toggle { display: none; }

  /* Подложка живёт ТОЛЬКО при открытом меню. Без этого условия она висела
     поверх страницы всегда: `display: block` по классу перебивает атрибут
     hidden (у него специфичность стиля браузера), и тап по любому месту
     экрана попадал в неё — то есть открывал меню. */
  html.nav-open .nav-backdrop {
    display: block; position: fixed; inset: 0; z-index: 50;
    background: oklch(0% 0 0 / .45);
  }
  html.nav-open { overflow: hidden; }

  .content { padding: 16px 12px 32px; }
}

/* ===== Мобильный слой: общие компоненты ===== */
@media (max-width: 860px) {
  /* Страницы «главное + справка справа» складываются в одну колонку: узкая
     колонка пояснений на телефоне превращалась в столбик по одному слову. */
  .detail-grid, .dash-grid, .ct-form-grid { grid-template-columns: 1fr; }
  .ct-divider { display: none; }
  .dash-side { position: static; }

  .page-head { flex-direction: column; align-items: stretch; gap: 12px; }
  .page-head .pm-title { font-size: 20px; }

  /* Формы и кнопки под палец: 16px у поля — не косметика, при меньшем размере
     Safari зумит страницу на фокусе и ломает вёрстку. */
  input, select, textarea { font-size: 16px; }
  .inline-form { flex-direction: column; align-items: stretch; }
  .inline-form input, .inline-form select, .inline-form button { width: 100%; }
  .form-actions, .ctl-actions { flex-direction: column; align-items: stretch; }
  .form-actions > *, .ctl-actions > * { width: 100%; }
  .btn-primary, .btn-secondary, .btn-link { min-height: 40px; }

  /* Раскладка ячеек уже компактная, но у неё своя ширина — даём прокрутку
     внутри блока, чтобы страница не ехала вбок целиком. */
  .layout-grid { max-width: 100%; overflow: auto; }
  .cells-sections { gap: 12px; }

  /* Справка уходит ПОД содержимое. На широком экране она стоит сверху как
     вступление и стоит одного взгляда, а на телефоне три абзаца отодвигали
     список постаматов за нижний край — открывая раздел, человек первым делом
     видел текст, а не то, за чем пришёл. */
  .detail-grid .ct-info, .ct-layout .ct-info {
    order: 1; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line);
  }
  /* Оглавление документации — исключение: это навигация по странице, а не
     пояснение к ней. Внизу длинного текста оно бесполезно — им пользуются, чтобы
     до текста добраться. */
  .ct-layout .ct-info.doc-toc {
    order: -1; margin-top: 0; margin-bottom: 4px;
    padding-top: 0; padding-bottom: 16px;
    border-top: 0; border-bottom: 1px solid var(--line);
  }
  .detail-grid .ct-divider { display: none; }

  /* Панели фильтров и инструментов — в колонку: селекты с min-width: 200px
     не влезали в строку и обрезались правым краем экрана. */
  .toolbar, .filters { flex-direction: column; align-items: stretch; }
  .toolbar > *, .filters > * { width: 100%; }
  /* min-width на поиске задан в плитках конфигуратора (две колонки по 200px) —
     на телефоне это шире экрана, и страница ехала вбок. */
  .filter-select, .search, .ct-toolbar .search { min-width: 0; width: 100%; }
  .ct-toolbar { flex-direction: column; }
  .ct-toolbar .btn-primary { flex: 0 0 auto; }
  /* Кнопка сброса фильтров на десктопе всегда занимает место (иначе её появление
     сдвигало бы фильтры вбок). В колонке это целая пустая строка между фильтрами
     и списком — прячем совсем: снизу ей двигать нечего. */
  .events-filters .events-reset.is-empty { display: none; }

  /* Плитки шаблонов и типов ячеек: колонка фиксирована в 200px, и на телефоне
     в ряд вставала одна узкая карточка, а рядом пустовала половина экрана.
     Даём колонкам тянуться: две в ряд на обычном экране, одна во всю ширину на
     совсем узком. */
  .ct-grid, .tpl-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .tpl-cards { grid-template-columns: 1fr; }
  /* Своя высота у селекта — под палец, заодно совпадает с кнопками. */
  .filter-select { height: 42px; }
}

/* ===== Мобильный слой: таблицы карточками =====
   Таблица на 6-8 колонок не помещается в телефон никак: горизонтальная
   прокрутка прячет половину данных и первым делом — колонку с действиями.
   Строка становится карточкой «поле — значение», подписи проставляет
   mobile.js из <th>, разметка остаётся таблицей. */
@media (max-width: 860px) {
  table.grid { border: 0; background: none; }
  table.grid thead { display: none; }
  table.grid tbody, table.grid tr, table.grid td, table.grid th { display: block; width: 100%; }
  table.grid tr {
    background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
    padding: 10px 12px; margin-bottom: 10px;
  }
  table.grid tr:last-child { margin-bottom: 0; }
  table.grid td {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 5px 0; border: 0; text-align: right;
  }
  table.grid td::before {
    content: attr(data-label);
    color: var(--muted); font-size: 12px; font-weight: 500;
    text-align: left; flex: 0 0 auto;
  }
  /* Строка-заглушка («ничего не найдено») подписи не получает — не растягиваем. */
  table.grid td[colspan] { display: block; text-align: left; color: var(--muted); }
  table.grid td[colspan]::before { content: none; }
  /* Кнопки строки — своим рядом во всю ширину: в правом углу по ним не попасть. */
  table.grid td.col-actions {
    justify-content: flex-end; gap: 8px; padding-top: 10px;
    border-top: 1px solid var(--line); margin-top: 6px;
  }
  table.grid td.col-actions::before { margin-right: auto; }
  /* Колонка действий в таблице плат сужена до 40px под десктопную корзину —
     в карточке колонок нет, и разделитель сверху выходил чёрточкой над иконкой. */
  .cab-boards table.grid td.col-actions { width: 100%; }
  table.grid td .btn-secondary, table.grid td .btn-primary { min-height: 36px; }
  /* Подвал таблицы («Смотреть все» в ленте событий) остаётся table-footer-group,
     пока остальное уже блоки, — ссылка выпадала из карточек кривой строкой.
     Делаем её кнопкой во всю ширину под списком. */
  table.grid tfoot, table.grid tfoot tr, table.grid tfoot td { display: block; width: 100%; }
  /* Отступ от последней карточки: у неё margin-bottom снят (иначе список
     заканчивался пустотой), поэтому зазор перед кнопкой держит сам подвал. */
  table.grid tfoot { margin-top: 10px; }
  table.grid tfoot tr { margin: 0; padding: 0; border: 0; background: none; }
  table.grid tfoot td { padding: 0; text-align: center; }
  table.grid tfoot td::before { content: none; }
  table.grid tfoot a {
    display: block; padding: 11px; border: 1px solid var(--line);
    border-radius: 10px; background: var(--surface); text-align: center;
  }
  table.grid tfoot a:hover { text-decoration: none; border-color: var(--brand); }
  /* Лента событий рисует границы и подложку подвала сама (на десктопе они держат
     низ таблицы). В карточках это лишние линии поперёк последней записи и серая
     плашка вокруг ссылки — снимаем ровно эти правила. */
  .events-grid tbody tr:last-child td { border-bottom: 0; }
  .events-grid tfoot td { padding: 0; background: none; border-radius: 0; }
  .events-grid tfoot tr:hover td { background: none; }
  .events-grid tfoot a { font-size: 14px; }
  /* Содержимое ячейки переносится, а не распирает карточку: в таблице
     обнаружения у платы живут селект типа (min-width: 200px) и кнопка опроса
     замков — вместе они шире телефона, и страница ехала вбок целиком. */
  table.grid td { flex-wrap: wrap; row-gap: 6px; }
  table.grid td > *, table.grid td select, table.grid td button {
    min-width: 0; max-width: 100%;
  }
  table.grid td select, table.grid td .filter-select { flex: 1 1 140px; }
  .cell-detect { flex-wrap: wrap; }

  /* Сводка парка: показатели в одну колонку. В две они вставали по 190px, и
     подписи вроде «не доехало до постаматов» переносились в три строки, а
     полоска занятости налезала на соседнюю колонку. Разделяем их линией
     сверху — читается как список фактов, а не как поехавшая таблица. */
  .ps-facts { grid-template-columns: 1fr; gap: 0; }
  .ps-fact,
  .ps-fact:nth-child(2n + 1),
  .ps-fact:nth-child(3n + 1) {
    padding: 12px 0 0; border-left: 0; border-top: 1px solid var(--line);
  }
  .ps-fact:first-child { padding-top: 0; border-top: 0; }
  /* Вердикт: заголовок и период не помещаются в строку — период уходит вниз. */
  .ps-verdict { flex-wrap: wrap; }
  .ps-period { margin-left: 0; flex: 1 1 100%; }

  /* Секция: у кнопки удаления остаётся корзина. Подпись «Удалить секцию» рядом
     с заголовком в строку не помещалась и переносом ломала шапку карточки. */
  .cab-head { flex-wrap: wrap; gap: 8px; }
  .cab-head-actions .btn-text { display: none; }
  .cab-head-actions .btn-danger { padding: 8px 10px; }

  /* Превью шаблона — по центру у КАЖДОЙ секции, пояснение и кнопка под ним.
     В строку превью и текст складывались только у узких раскладок, у широких
     превью переносилось и центрировалось само — секции выглядели по-разному. */
  .cab-tpl-view { flex-direction: column; align-items: center; }
  .cab-tpl-view .tpl-preview { margin-inline: auto; }
  /* flex: 1 1 260px десктопной раскладки в колонке означает высоту 260px —
     под кнопкой оставалась пустая полоса почти в полкарточки. */
  .cab-tpl-view .cab-layout {
    flex: 0 0 auto; width: 100%; align-items: stretch; text-align: center;
  }
  /* Точка состояния идёт в строку с текстом. У сводки display: inline-flex, и
     на трёх строках точка повисала слева от середины абзаца, будто отдельный
     маркер списка. */
  .cab-tpl-view .cab-layout .diag-summary { display: block; }
  .cab-tpl-view .cab-layout .diag-summary .dot { margin-right: 6px; vertical-align: middle; }
  .cab-tpl-view .cab-layout .btn-secondary { width: 100%; }

  /* Редактор шаблона на телефоне: ячейки таскаются pointer-событиями, и без
     touch-action браузер вместо перетаскивания прокручивает страницу. Новые
     ячейки ставятся тапом (палитра → клетка): нативный DnD чипов на touch не
     запускается вовсе. */
  .layout-grid.editor .lcell { touch-action: none; }
  .type-chips { gap: 8px; }
  .type-chip { flex: 1 1 100%; }
}

/* Выбранный тип в палитре — виден и на десктопе: тапом ставят и мышью тоже. */
.type-chip.is-picked {
  outline: 2px solid var(--brand); outline-offset: 1px;
  background: var(--brand-soft);
}
