/* ===========================================================================
   EVA · пульт — экран «Коннекторы» на «Стекле» (12.09.2026).

   Повод: оператор посмотрел прод и сказал про этот экран «выглядит колхозно».
   Он был прав буквально: пять одинаковых серых плашек в две колонки, без
   херо, без единого фирменного знака системы и без ответа на главный вопрос
   экрана — «что из этого живое прямо сейчас». Девять остальных экранов к тому
   моменту уже были на «Стекле», и «Коннекторы» рядом с ними читались как
   чужая страница.

   Что здесь появилось:
     · СОЗВЕЗДИЕ СВЯЗЕЙ (приём рефа 5, тот же, что на карте знаний): знак EVA
       в центре, пять узлов вокруг, нить к каждому. Цвет нити — состояние:
       золотая, когда живая проверка прошла; серый пунктир, пока проверки не
       было; красная, когда не прошла; погашенная у выключенного канала;
     · карточки на стекле вместо плашек `.card`, с масками ключей моношрифтом,
       чипами переменных окружения и расходом провайдера ЦИФРОЙ ИЗ ТОЧЕК —
       тем же общим компонентом, что и на «Обзоре»;
     · движение по чек-листу макета: узлы дышат, нити прочерчиваются, проверка
       пульсирует и показывает «···», результат вспыхивает один раз.

   Отдельный файл, а не строки в `app.css`, по той же причине, что у
   `orders.css`, `inbox.css` и `access.css`: `app.css` лежит в CRLF и правка
   его из LF-инструмента даёт дифф на семь тысяч строк вместо десяти
   настоящих. Подключается ПОСЛЕ `app.css` намеренно — старые правила
   `.conn-*` он перекрывает, а не дополняет.

   Общие кадры движения (`gDraw`, `gDot`, `gFade`, `gFlash`, `gRow`,
   `gSheen`, `ovSpin`) живут в `overview.css`; второй копии здесь нет.
   ======================================================================== */

:root {
  /* Золото — цвет «работает» для НИТЕЙ, а не для текста: зелёный светофор в
     пульте занят словом в пилюле, и красить им ещё и линии значило бы
     говорить одно и то же дважды одним цветом. Значение берётся у карты
     знаний (`kb.css`, `--kb-gold-line`), где золотом размечено «уезжает
     модели»: один продукт — одно золото, и переопределяется оно там по
     темам само. Второй литерал того же цвета однажды разошёлся бы с первым. */
  --cn-gold: var(--kb-gold-line, #dcc38e);
  /* 🔴 Золото для ТЕКСТА — отдельным токеном и на ступень темнее. Замерено:
     `--kb-gold-line` в «Стекле» (#97701f) даёт на пилюле 4,43:1 — ниже порога
     4.5:1. Для линии этого хватает (графике нужно 3:1), для слова «РАБОТАЕТ»
     капслоком в 0,6rem — нет. В «Ночи» разницы нет: там светлое золото на
     тёмном и так даёт 10,6:1. */
  --cn-gold-ink: var(--cn-gold);
}

[data-theme="light"] {
  --cn-gold-ink: #7d5a11;
}

[data-theme="neutral"] {
  --cn-gold-ink: #7d5a11;
}

/* =============================================================== ХЕРО

   Две половины: слева созвездие, справа — что это значит словами, сводка
   состояний чипами и граница про ключи. Границу читают ДО того, как нажмут
   «Проверить», поэтому она стоит в херо, а не под карточками. */
#tab-connectors .cn-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
  gap: var(--s6);
  align-items: center;
  margin-bottom: var(--s5);
  isolation: isolate;
}

#tab-connectors .cn-aside {
  display: grid;
  align-content: start;
  gap: var(--s3);
  min-width: 0;
}

#tab-connectors .cn-aside h3 {
  margin: 0;
  font-family: var(--f-disp);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--milk);
}

#tab-connectors .cn-aside .eyebrow b {
  color: var(--silver);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

#tab-connectors .cn-aside-lede {
  margin: 0;
  color: var(--mute);
  font-size: var(--t-xs);
  line-height: 1.6;
}

/* ------------------------------------------------------- сводка состояний
   Четыре чипа «работает · не проверялся · не настроен · ошибка» с числом.
   Чип с нулём не рисуется вовсе: ряд из четырёх нулей отвечает на вопрос
   «сколько чего» словом «ничего» четыре раза подряд. */
#tab-connectors .cn-tally {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

#tab-connectors .cn-tchip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  height: 1.75rem;
  padding: 0 var(--s3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--surface-flat);
  color: var(--silver);
  font-size: var(--t-xs);
  white-space: nowrap;
}

#tab-connectors .cn-tchip i {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: var(--mute);
}

#tab-connectors .cn-tchip b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--milk);
}

#tab-connectors .cn-tchip--ok i { background: var(--cn-gold); }
#tab-connectors .cn-tchip--off i { background: var(--warn); }
#tab-connectors .cn-tchip--warn i { background: var(--bad); }
#tab-connectors .cn-tchip--unknown i { background: var(--mute); }

/* ---------------------------------------------------- квадратные ссылки
   Куда уходят с этого экрана: править потолки — в «Настройки», смотреть
   эфир — в «Статус», проверить ответ словами — в «Тест-чат». Тот же
   компонент, что четыре ссылки в херо «Доступа». */
#tab-connectors .cn-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

#tab-connectors .cn-sq {
  display: grid;
  place-items: center;
  gap: 0.2rem;
  width: 5rem;
  padding: var(--s3) var(--s2);
  border: 1px solid var(--line-2);
  border-radius: var(--r-ctl);
  background: var(--surface-flat);
  box-shadow: var(--sh-btn);
  color: var(--silver);
  font-size: var(--t-micro);
  text-align: center;
  text-decoration: none;
}

#tab-connectors .cn-sq:hover {
  border-color: var(--accent-line);
  color: var(--milk);
}

/* ========================================================== СОЗВЕЗДИЕ

   Поле с фиксированным отношением сторон: нити рисуются в координатах
   viewBox 720×520, а узлы — обычные HTML-кнопки, поставленные процентами от
   ТОГО ЖЕ поля. Совпадение держится ровно на `aspect-ratio: 18 / 13`
   (= 720/520): без него проценты и viewBox разъезжаются, и нити перестают
   попадать в узлы. Гнёзда и радиусы живут в `connectors.js` — там они нужны
   счётом, и второй копии чисел здесь нет.

   🔴 Узлы именно HTML, а не `<text>` в SVG. Причина не вкусовая: у узла есть
   пилюля состояния и подсказка, то есть настоящий текст, который обязан
   набираться тем же шрифтом и проходить те же 4.5:1, что весь пульт; в SVG
   он потерял бы и перенос строки, и токены темы. */
#tab-connectors .cn-sky {
  position: relative;
  width: 100%;
  aspect-ratio: 18 / 13;
  min-width: 0;
}

#tab-connectors .cn-web {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* ------------------------------------------------------------- нити
   Пунктир у «не проверялся» — это рисунок, а не украшение: сплошная линия
   читается как «связь есть», а её как раз ещё никто не проверял. */
.cn-thread {
  fill: none;
  stroke-linecap: round;
}

.cn-thread--unknown {
  stroke: var(--mute);
  stroke-width: 1.8;
  stroke-dasharray: 3 6;
  opacity: 0.6;
}

/* 🔴 Нити берут `--mute`, а не волосок `--line-2`/`--line-3`: те считаны под
   разделители на плоском фоне и на светлой теме дают 1,4:1 — на снимке их
   просто нет. Разница между «не проверялся» и «выключен» держится на
   прозрачности и рисунке штриха, а не на том, видно линию или нет. */
.cn-thread--off {
  stroke: var(--mute);
  stroke-width: 1.4;
  stroke-dasharray: 2 8;
  opacity: 0.32;
}

.cn-thread--ok {
  stroke: var(--cn-gold);
  stroke-width: 2.4;
}

.cn-thread--warn {
  stroke: var(--bad);
  stroke-width: 2;
}

/* Бусина на золотой нити — единственная «искра»: она идёт от узла к ядру и
   означает, что по этой связи ходят слова. У серых и красных нитей её нет.
   Это тот же штрих, что и нить, только короткий: отдельного слоя и второй
   геометрии не заводится — координаты у них общие. */
.cn-spark {
  fill: none;
  stroke: var(--cn-gold);
  stroke-width: 3.6;
  stroke-linecap: round;
  opacity: 0.9;
}

/* ---------------------------------------------------------------- ядро
   Знак EVA рисуется В SVG, а не HTML-классом `.eva-orb`: у того фиксированные
   11rem, и при резиновой ширине херо он переставал попадать в концы нитей.
   Форма та же — шар с бликом сверху слева и две пары дуг. */
.cn-core-ball { fill: url(#cnCore); }

/* Ступени градиента — токены `--orb-*` из app.css, те же, что у знака EVA на
   «Обзоре» и в «Инбоксе»: третья копия одного предмета не живёт своими
   хексами (аудит B §6.2). Блик и дуги — `--orb-hi`, у светлой темы свой. */
.cn-core-stop--0 { stop-color: var(--orb-hi); }
.cn-core-stop--1 { stop-color: var(--orb-1); }
.cn-core-stop--2 { stop-color: var(--orb-2); }
.cn-core-stop--3 { stop-color: var(--orb-3); }

.cn-core-arc {
  fill: none;
  stroke: var(--orb-hi);
  stroke-linecap: round;
}

.cn-core-dot { fill: var(--orb-hi); }

.cn-core-floor {
  fill: var(--vignette);
  opacity: 0.5;
}

/* «Спокойная» — светлая без зарева: тени на полу под знаком в ней нет. */
[data-theme="neutral"] .cn-core-floor { display: none; }

.cn-core-glow {
  fill: var(--accent);
  opacity: 0.12;
}

[data-theme="neutral"] .cn-core-glow { display: none; }

/* Подпись ядра стоит ПОД знаком и в системе координат SVG: она часть
   рисунка, а не текст интерфейса, и переносов в ней нет. */
.cn-core-cap {
  fill: var(--mute);
  font-family: var(--f-ui);
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* --------------------------------------------------------------- узлы */
#tab-connectors .cn-node {
  --plate: 3.1rem;
  position: absolute;
  left: calc(var(--x) * 1%);
  top: calc(var(--y) * 1%);
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  width: 8.5rem;
  min-height: 0;
  padding: 0;
  /* Гнездо задаёт центр ПЛИТКИ, а не верх блока: нить приходит в кружок, а
     имя и пилюля висят под ним. Поэтому сдвиг вверх — ровно полплитки. */
  translate: -50% calc(var(--plate) / -2);
  border: 0;
  background: none;
  box-shadow: none;
  color: var(--silver);
  text-align: center;
  /* 🔴 Узел — это `button`, а кнопкам в `app.css` задан `white-space: nowrap`.
     Без возврата к обычному переносу подсказка в пятнадцать слов вытягивалась
     в одну строку на девятьсот пикселей и уносила с собой горизонтальную
     прокрутку всей страницы. */
  white-space: normal;
}

/* Плитка узла — тот же квадрат со знаком, что у карточки ниже: одна форма на
   продукт. Тон состояния живёт в обводке и в цвете контура, заливка остаётся
   нейтральной — пять цветных кругов перетянули бы на себя весь херо. */
#tab-connectors .cn-node .cn-plate {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--plate);
  height: var(--plate);
  border-radius: 50%;
  background: var(--surface-flat);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-chip);
  color: var(--mute);
}

#tab-connectors .cn-node .cn-plate svg {
  width: 1.4rem;
  height: 1.4rem;
}

#tab-connectors .cn-node.is-ok .cn-plate { border-color: var(--cn-gold); color: var(--cn-gold-ink); }
#tab-connectors .cn-node.is-warn .cn-plate { border-color: var(--bad-line); color: var(--bad); }
#tab-connectors .cn-node.is-off .cn-plate { border-color: var(--warn-line); color: var(--warn); }
#tab-connectors .cn-node.is-unknown .cn-plate { color: var(--silver); }

#tab-connectors .cn-node .cn-name {
  max-width: 100%;
  color: var(--silver);
  font-size: var(--t-micro);
  font-weight: 500;
  line-height: 1.3;
}

/* Пилюля состояния. Слово важнее цвета: дальтоники существуют, и «работает»
   обязано читаться без единого пикселя цвета. */
#tab-connectors .cn-node .cn-pill {
  padding: 0.05rem 0.4rem;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--surface-flat);
  color: var(--mute);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}

#tab-connectors .cn-node.is-ok .cn-pill {
  border-color: var(--cn-gold);
  color: var(--cn-gold-ink);
}

#tab-connectors .cn-node.is-warn .cn-pill {
  border-color: var(--bad-line);
  background: var(--bad-soft);
  color: var(--bad);
}

#tab-connectors .cn-node.is-off .cn-pill {
  border-color: var(--warn-line);
  background: var(--warn-soft);
  color: var(--warn);
}

/* Подсказка: та же строка «что делать», что и в карточке ниже. Появляется
   на наведении И на фокусе с клавиатуры — подсказка, доступная только мыши,
   для половины пульта не существует. */
#tab-connectors .cn-tip {
  --tip-x: -50%;
  position: absolute;
  left: 50%;
  top: calc(100% + 0.4rem);
  z-index: 3;
  width: 15rem;
  max-width: calc(100vw - 3rem);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--sh-3);
  color: var(--silver);
  font-size: var(--t-micro);
  line-height: 1.5;
  text-align: left;
  opacity: 0;
  pointer-events: none;
  translate: var(--tip-x) 4px;
  transition:
    opacity var(--dur-2) var(--ease),
    translate var(--dur-2) var(--ease);
}

/* «Что делать» — своей строкой: одной простынёй подсказка читается как
   ошибка сервера, а не как инструкция. */
#tab-connectors .cn-tip-hint {
  display: block;
  margin-top: var(--s2);
  color: var(--mute);
}

#tab-connectors .cn-node:hover .cn-tip,
#tab-connectors .cn-node:focus-visible .cn-tip {
  opacity: 1;
  translate: var(--tip-x) 0;
}

/* Нижние два узла показывают подсказку НАД собой: под ними край карточки, и
   всплывшая вниз подсказка обрезалась бы стеклом. */
#tab-connectors .cn-node.cn-node--low .cn-tip {
  top: auto;
  bottom: calc(100% + 0.4rem);
  translate: var(--tip-x) -4px;
}

#tab-connectors .cn-node.cn-node--low:hover .cn-tip,
#tab-connectors .cn-node.cn-node--low:focus-visible .cn-tip {
  translate: var(--tip-x) 0;
}

/* 🔴 Боковые узлы прижимают подсказку к своему краю, а не центрируют её:
   при ширине поля 310 px (телефон) центрированная подсказка бокового узла
   уезжала на 40 px за левый край документа, и страница получала
   горизонтальную прокрутку на всю ширину подсказки. */
#tab-connectors .cn-node--l .cn-tip {
  --tip-x: 0;
  left: 0;
}

#tab-connectors .cn-node--r .cn-tip {
  --tip-x: 0;
  left: auto;
  right: 0;
}

/* ================================================== КАРТОЧКИ КОННЕКТОРОВ

   Колонки, а не колоночная вёрстка `columns`: у карточек разная высота, и
   `break-inside: avoid` раскладывал их так, что «Провайдер» и «CRM» могли
   оказаться в разных колонках на разной высоте, а порядок чтения — сверху
   вниз по колонке — расходился с порядком в созвездии.

   🔴 Сетка нарезана на ШЕСТЬ дорожек, а карточка занимает две. Половинные
   дорожки нужны ровно для одного: неполный последний ряд делит остаток
   ПОРОВНУ (две карточки по три дорожки), а не оставляет пустую ячейку. До
   13.09 тут стоял `auto-fill` + `align-items: start`, и на 1440 и 3440 блок
   выглядел так: карточки ряда 623 / 356 / 482 px, правый столбец обрывался
   на середине, а под ним зияла пустая ячейка 446×562 — заполнено 67,9 %
   площади. Стало 96,8 % (замер — `test/browser/smoke.mjs`, проверка «сетка
   коннекторов ровная»).

   Второе следствие: `align-items` больше не `start`, и карточки ряда тянутся
   до его высоты. Это не растянутая пустота: у `.conn-foot` стоит
   `margin-top: auto`, и кнопки «Проверить» всех карточек ряда встают на одну
   линию. Прежний `start` этот замысел как раз и ломал.

   Число карточек в ряду задают медиазапросы, а не `auto-fill`: хвостовому
   правилу надо ЗНАТЬ ширину ряда, иначе остаток разложится мимо. */
#tab-connectors .conn-grid {
  --col-gap: var(--s4);
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--col-gap);
  columns: auto;
}

#tab-connectors article.conn { grid-column: span 2; }

/* Хвост из двух (пять карточек, восемь…) — по три дорожки каждой. Хвост из
   одного (четыре, семь…) — во всю ширину: одинокая карточка в треть ряда
   читается как «эта чем-то отличается», а дыра рядом с ней — как блок,
   который не догрузился. */
#tab-connectors article.conn:nth-child(3n + 1):nth-last-child(2),
#tab-connectors article.conn:nth-child(3n + 2):last-child {
  grid-column: span 3;
}

#tab-connectors article.conn:nth-child(3n + 1):last-child {
  grid-column: span 6;
}

/* Планшет — две карточки в ряд (три по 23rem уже не помещаются), телефон —
   одна. Хвостовые правила пересчитываются на новую ширину ряда. */
@media (max-width: 76rem) {
  #tab-connectors .conn-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Сначала гасим ВСЕ спаны трёхколоночного ряда — иначе правило хвоста из
     базового блока (у него специфичность выше, чем у голого `article.conn`)
     оставит карточке шесть дорожек из четырёх, и ряд уедет за край. */
  #tab-connectors article.conn,
  #tab-connectors article.conn:nth-child(3n + 1):nth-last-child(2),
  #tab-connectors article.conn:nth-child(3n + 2):last-child,
  #tab-connectors article.conn:nth-child(3n + 1):last-child {
    grid-column: span 2;
  }

  #tab-connectors article.conn:nth-child(2n + 1):last-child {
    grid-column: span 4;
  }
}

@media (max-width: 52rem) {
  #tab-connectors .conn-grid { grid-template-columns: minmax(0, 1fr); }
  #tab-connectors article.conn,
  #tab-connectors article.conn:nth-child(3n + 1):nth-last-child(2),
  #tab-connectors article.conn:nth-child(3n + 2):last-child,
  #tab-connectors article.conn:nth-child(3n + 1):last-child,
  #tab-connectors article.conn:nth-child(2n + 1):last-child {
    grid-column: auto;
  }
}

#tab-connectors article.conn {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin-bottom: 0;
  padding: var(--s5);
  scroll-margin-top: 5rem;
  transition: translate var(--dur-2) var(--ease);
}

#tab-connectors article.conn:hover { translate: 0 -2px; }

#tab-connectors .conn-head {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) auto;
  gap: var(--s3);
  align-items: center;
  margin-bottom: 0;
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--line);
}

#tab-connectors .conn-icon {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--r-ctl);
  background: var(--surface-flat);
}

#tab-connectors .conn-icon.gold {
  border-color: var(--cn-gold);
  color: var(--cn-gold-ink);
}

#tab-connectors .conn-head h3 {
  font-family: var(--f-disp);
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: -0.02em;
}

#tab-connectors .conn-state {
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

#tab-connectors .conn-state.ok {
  border-color: var(--cn-gold);
  background: none;
  color: var(--cn-gold-ink);
}

#tab-connectors .conn-detail {
  margin: 0;
  color: var(--silver);
}

#tab-connectors .conn-hint {
  margin: 0;
}

#tab-connectors .conn-fields,
#tab-connectors .conn-env {
  margin: 0;
}

#tab-connectors .conn-fields div {
  border-top-color: var(--line);
}

/* --------------------------------------------- расход провайдера цифрой
   Та же матрица точек, что на «Обзоре» и в «Журнале»: деньги — главное
   число этой карточки, и набирать его тем же кеглем, что имя переменной
   окружения, значит прятать его. */
#tab-connectors .conn-spend {
  display: grid;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-card-sm);
  background: var(--ink-860);
}

#tab-connectors .conn-spend .dotnum {
  color: var(--milk);
}

#tab-connectors .conn-caps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s3);
  margin: 0;
  color: var(--mute);
  font-size: var(--t-xs);
}

#tab-connectors .conn-caps b {
  color: var(--silver);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------- «Как подключить витрину»
   Сворачивается: сниппет в двадцать строк стоял развёрнутым на каждой
   загрузке экрана и отодвигал кнопку «Проверить» ниже сгиба, при том что
   читают его один раз в жизни витрины. */
#tab-connectors .conn-snippet {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-card-sm);
  background: var(--ink-860);
}

/* Стрелка раскрытия берётся общая (`details summary::before` в `app.css`) —
   та самая, что нарисована штрихом, а не набрана знаком «▸». Своей здесь
   нет намеренно: две стрелки на одном заголовке уже были и читались как
   опечатка в вёрстке. */
#tab-connectors .conn-snippet > summary {
  padding: var(--s3) var(--s4);
  color: var(--silver);
  font-size: var(--t-xs);
  font-weight: 500;
}

#tab-connectors .conn-snippet[open] > summary { margin-bottom: 0; }

#tab-connectors .conn-snippet-body {
  padding: 0 var(--s4) var(--s4);
}

#tab-connectors .conn-snippet .raw {
  max-height: 17rem;
  margin: 0 0 var(--s3);
}

/* ------------------------------------------------------- подвал карточки */
#tab-connectors .conn-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  align-items: center;
  margin-top: auto;
  padding-top: var(--s3);
  border-top: 1px solid var(--line);
}

#tab-connectors button.conn-check {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s2);
  min-height: 1.9rem;
  padding: 0 var(--s3);
  border: 1px solid var(--line-2);
  border-radius: var(--r-ctl);
  background: var(--surface-flat);
  box-shadow: var(--sh-btn);
  color: var(--silver);
  font-size: var(--t-xs);
  font-weight: 500;
}

#tab-connectors button.conn-check:hover:not(:disabled) {
  border-color: var(--accent-line);
  color: var(--milk);
}

#tab-connectors button.conn-check:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Цена стоит НА КНОПКЕ, а не рядом: решение «жать или не жать» принимают,
   глядя на кнопку, и цена, отодвинутая в подпись, в это решение не входит. */
#tab-connectors button.conn-check small {
  color: var(--faint);
  font-size: var(--t-micro);
}

#tab-connectors .conn-check-note {
  color: var(--mute);
  font-size: var(--t-micro);
  line-height: 1.45;
}

#tab-connectors .conn-check-note.is-bad { color: var(--bad); }

/* Точки «···», пока живая проверка в полёте: кнопка выключена, и без них
   экран выглядит так, будто нажатие потерялось. */
#tab-connectors .conn-dots {
  display: inline-flex;
  gap: 0.2rem;
  vertical-align: middle;
}

#tab-connectors .conn-dots i {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--mute);
}

/* ------------------------------------------------------------- телефон */
@media (max-width: 62rem) {
  #tab-connectors .cn-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s5);
  }
}

@media (max-width: 40rem) {
  /* 🔴 Отношение сторон поля на телефоне НЕ меняется, хотя руки тянутся:
     проценты HTML-узлов и координаты viewBox совпадают ровно потому, что
     поле и рисунок одной формы. Сделать поле квадратнее — значит включить
     `meet`-поля внутри svg, и нити разъедутся с узлами на два десятка
     пикселей. Вместо формы ужимается сам узел: плитка, имя и пилюля.
     Пилюля при этом остаётся — это ответ экрана, а не украшение. */
  #tab-connectors .cn-node {
    --plate: 2.2rem;
    width: 5.4rem;
    gap: 0.2rem;
  }

  #tab-connectors .cn-node .cn-plate svg { width: 1.05rem; height: 1.05rem; }
  /* M-16 (QA 24.09): имя узла было 9.3 px, пилюля 8.3 px — ниже порога
     11 px. Узел ужимается плиткой и шириной, а не кеглем. */
  #tab-connectors .cn-node .cn-name { font-size: 0.6875rem; }
  #tab-connectors .cn-node .cn-pill { font-size: 0.6875rem; padding: 0.02rem 0.3rem; }
  #tab-connectors .cn-tip { width: 11rem; }

  #tab-connectors .cn-sq { width: 4.4rem; }
}

/* 🔵 M-19 (QA 24.09): цели касания «Каналов» на телефоне. «Проверить»
   (`button.conn-check`) держали свои 1.9rem поверх общего тач-блока app.css,
   «Обновить» `#conn-refresh` — `.bare`-квадрат 34 px, ссылка «изменить в
   Настройках» — строка текста в 20 px. Габарит касания 44, вид прежний:
   у ссылки поле касания растёт внутрь отрицательными полями, строка не
   становится выше. */
@media (max-width: 56rem) {
  #tab-connectors button.conn-check {
    align-items: center;
    min-height: 2.75rem;
  }

  #tab-connectors #conn-refresh {
    width: 2.75rem;
    height: 2.75rem;
    min-height: 2.75rem;
  }

  #tab-connectors summary {
    min-height: 2.75rem;
  }

  #tab-connectors .conn-caps a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;
    margin-block: -0.75rem;
  }
}

/* ===========================================================================
   ДВИЖЕНИЕ

   Границы те же, что на «Обзоре», в «Заказах» и в «Доступе»: в кадре только
   transform / opacity / stroke-dashoffset, микроанимации ≤ 300 мс,
   декоративные циклы ≥ 6 с, на 390 px наведения нет (палец наводится
   нажатием). Общие кадры берутся из `overview.css`.
   ======================================================================== */

/* Узел плывёт ±4 px. Период и фаза у каждого свои (`--t`, `--d` ставит
   `connectors.js`): одинаковые — и пять узлов качаются строем, как метроном,
   то есть перестают быть «живой связью» и становятся заставкой загрузки. */
@keyframes cnFloat {
  0%,
  100% { transform: translateY(-4px); }
  50% { transform: translateY(4px); }
}

/* Ядро дышит. Поворота у шара нет намеренно: блик на нём нарисован сверху
   слева, и поворот означал бы, что вокруг карточки ездит источник света. */
@keyframes cnBreathe {
  from { scale: 1; }
  to { scale: 1.05; }
}

/* Бусина идёт по золотой нити от узла к ядру. Смещение штриха — то же
   свойство, которым нить прочерчивается, поэтому лишних слоёв не заводится. */
@keyframes cnSpark {
  from { stroke-dashoffset: var(--cn-len, 200); }
  to { stroke-dashoffset: 0; }
}

/* Узел, у которого идёт проверка: мягкий пульс кольца. */
@keyframes cnPing {
  0% {
    opacity: 0.75;
    transform: scale(1);
  }
  70%,
  100% {
    opacity: 0;
    transform: scale(1.9);
  }
}

/* «···» в подписи проверки. Те же три точки и та же длительность, что у
   пузыря «EVA печатает…» в «Инбоксе»: одна форма ожидания на продукт. */
@keyframes cnType {
  0%,
  80%,
  100% { transform: translateY(0); opacity: 0.45; }
  40% { transform: translateY(-3px); opacity: 1; }
}

#tab-connectors .cn-node .cn-plate {
  animation: cnFloat var(--t, 8s) var(--ease) infinite;
  animation-delay: var(--d, 0s);
}

#tab-connectors .cn-core-ball,
#tab-connectors .cn-core-glow {
  transform-box: fill-box;
  transform-origin: center;
  animation: cnBreathe 7.4s var(--ease) infinite alternate;
}

/* Дуги вращаются вокруг центра ядра — тот же кадр, что у знака на «Входе» и
   в «Настройках» (`ovSpin` из `overview.css`), и тот же период. */
#tab-connectors .cn-core-arcs {
  transform-box: view-box;
  transform-origin: var(--cx, 360px) var(--cy, 206px);
  animation: ovSpin 22s linear infinite;
}

/* Нити прочерчиваются от ядра наружу, шаг 70 мс. Длину штриха для каждой
   ставит `connectors.js` (`--cn-len`): нити разной длины, и одна константа
   либо не дорисовывала бы короткие, либо тянула бы длинные. */
#tab-connectors .cn-thread {
  --g-dash: var(--cn-len, 200);
  animation: gDraw 720ms var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 70ms);
}

#tab-connectors .cn-spark {
  animation: cnSpark 3.4s linear infinite;
  animation-delay: calc(var(--i, 0) * 420ms);
}

#tab-connectors .cn-node .cn-name,
#tab-connectors .cn-node .cn-pill {
  animation: gFade 300ms var(--ease) backwards;
  animation-delay: calc(420ms + var(--i, 0) * 70ms);
}

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

   🔴 У узла кольцо висит на САМОМ узле, а не на плитке: плитке нужен
   `overflow: hidden` под бегущий блик, и кольцо, разрастающееся до 1.9,
   обрезалось бы ею ровно по своей границе — то есть не было бы видно. */
#tab-connectors .cn-node.is-checking::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: var(--plate);
  height: var(--plate);
  translate: -50% 0;
  border-radius: 50%;
  border: 1px solid var(--accent);
  pointer-events: none;
  animation: cnPing 1.6s var(--ease-out) infinite;
}

#tab-connectors article.conn.is-checking .conn-icon::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: inherit;
  border: 1px solid var(--accent);
  animation: cnPing 1.6s var(--ease-out) infinite;
}

#tab-connectors .conn-icon { position: relative; }

#tab-connectors .conn-dots i {
  animation: cnType 1.2s var(--ease) infinite;
}

#tab-connectors .conn-dots i:nth-child(2) { animation-delay: 0.16s; }
#tab-connectors .conn-dots i:nth-child(3) { animation-delay: 0.32s; }

/* Результат проверки — ОДНА вспышка по карточке, которая гаснет. Подсветка,
   которая остаётся, читается как «с этой карточкой что-то не так». */
#tab-connectors article.conn.is-fresh::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  background: var(--accent-soft-2);
  pointer-events: none;
  animation: gFlash 1.4s var(--ease-out) both;
}

/* Блик под курсором — общее правило `.glass:hover > .g-gloss::after` в
   `overview.css`; здесь только контекст наложения. */
#tab-connectors .glass { isolation: isolate; }

/* Наведение на узел: блик по плитке — тот же бегущий блик, что по стеклу. */
#tab-connectors .cn-node .cn-plate {
  overflow: hidden;
  transition: border-color var(--dur-2) var(--ease);
}

#tab-connectors .cn-node .cn-sheen {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--g-gloss);
  translate: -120% 0;
  pointer-events: none;
}

#tab-connectors .cn-node:hover .cn-sheen,
#tab-connectors .cn-node:focus-visible .cn-sheen {
  animation: gSheen 620ms var(--ease-out) backwards;
}

#tab-connectors button.conn-check:active:not(:disabled),
#tab-connectors .cn-sq:active { translate: 0 1px; }

@media (max-width: 40rem) {
  /* Палец «наводится» нажатием: бегущий блик на телефоне играл бы при
     каждом касании и читался бы как отклик кнопки, которой он не является. */
  #tab-connectors .cn-node:hover .cn-sheen { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  #tab-connectors .cn-node .cn-plate,
  #tab-connectors .cn-core-ball,
  #tab-connectors .cn-core-glow,
  #tab-connectors .cn-core-arcs,
  #tab-connectors .cn-thread,
  #tab-connectors .cn-spark,
  #tab-connectors .cn-node .cn-name,
  #tab-connectors .cn-node .cn-pill,
  #tab-connectors .cn-node .cn-sheen,
  #tab-connectors .conn-dots i,
  #tab-connectors .cn-tip {
    /* 🔴 `opacity: 1` здесь НЕ ставится (та же грабля, что в `access.css`):
       селектор с `#tab-connectors` весит больше базового правила, и свечение
       ядра (`opacity: 0.12`) стало бы сплошным розовым кругом, а спрятанная
       подсказка — вечно раскрытой. Анимации с `backwards` сами возвращают
       свойству его собственное значение, стоит их выключить. */
    animation: none !important;
    transition: none !important;
    stroke-dashoffset: 0 !important;
    transform: none !important;
    scale: none !important;
  }

  #tab-connectors .conn-snippet > summary::before { transition: none; }

  #tab-connectors .cn-node.is-checking::after,
  #tab-connectors article.conn.is-checking .conn-icon::after,
  #tab-connectors article.conn.is-fresh::after { display: none; }

  #tab-connectors article.conn:hover { translate: 0; }

  /* Бусина без движения — просто точка посреди нити, то есть мусор. */
  #tab-connectors .cn-spark { display: none; }
}
