/* ============================================================================
   Экран «База знаний» — две ступени «Стекла»: карта знаний и раздел.
   ----------------------------------------------------------------------------
   Перенесено с макетов `design/pult-glass-2026-09/kb.html` и `kb-section.html`
   (описи рефов 5 и 6). Своим файлом по той же причине, что `orders.css` и
   `inbox.css`: экран большой, а править его вместе с общим `app.css` значит
   сводить конфликты руками при каждой параллельной правке.

   Что взято ИЗ СИСТЕМЫ и здесь не переопределяется: `.glass`, `.gchip`,
   `.eyebrow-pill`, `.askbar`, `.dotnum` (цифра из точечной матрицы),
   `.ovspark` (спарклайн с пунктирной базой и двумя маркерами), `.rise`
   (каскад), `.skel-line`. Здесь описано только то, чего в системе не было:
   граф-созвездие, лента дат версий, линза-радар, стопка карточек-счётчиков,
   веер Безье, гистограмма версий и шторка настроек базы.

   🔴 Цвет в правилах не пишется — только токены (правило `app.css`, §1).
   Палитра узлов заведена именем во всех трёх темах ниже: золото — промпт,
   розовый — раздел, фиолетовый — заголовок, бирюза — версии. Значения
   один в один с `design/pult-glass-2026-09/_parts/base.css` (строки
   1146–1156 «Стекло», 1189–1199 «Ночь», 1229–1239 «Спокойная»), где они
   проверены на контраст ≥ 4.5:1 для текста и ≥ 3:1 для графики.

   🔴 Три вещи из макета в пульт НЕ поехали, и это решение, а не забывчивость:
   3D-плита экрана раздела (реф 6, деталь 24 — приём макета), тексты 7–9 px
   (минимум 11 px для подписей узлов) и микрофон в строке запроса.
   ========================================================================= */

/* ------------------------------------------------------------ ТЁМНАЯ «НОЧЬ» */
:root {
  --kb-gold-1: #e7cf9d;
  --kb-gold-2: #8d7742;
  --kb-gold-line: #dcc38e;
  --kb-pink-1: #f58fae;
  --kb-pink-2: #9c3f63;
  --kb-violet-1: #b9a7f0;
  --kb-violet-2: #6250ac;
  --kb-teal-1: #5fe0c0;
  --kb-teal-2: #1e6e5c;
  /* Сцена графа: подложка холста и сетка 16 px на ней. */
  --kb-stage: rgba(255, 255, 255, 0.035);
  --kb-stage-line: rgba(255, 255, 255, 0.05);
  /* Обводка подписей узлов = цвет сцены: луч не режет буквы. */
  --kb-halo: #131316;
  --kb-line: rgba(255, 255, 255, 0.28);
  --kb-off: rgba(255, 255, 255, 0.34);
  --kb-glow: 0 6px 22px rgba(0, 0, 0, 0.5);
}

[data-theme="light"] {
  --kb-gold-1: #c79a3f;
  --kb-gold-2: #7d5d16;
  --kb-gold-line: #97701f;
  --kb-pink-1: #c04a70;
  --kb-pink-2: #6b2a48;
  --kb-violet-1: #6250ac;
  --kb-violet-2: #332a5e;
  --kb-teal-1: #1f8f84;
  --kb-teal-2: #0b544c;
  --kb-stage: rgba(255, 255, 255, 0.62);
  --kb-stage-line: rgba(17, 24, 39, 0.05);
  --kb-halo: #f8f9fb;
  --kb-line: rgba(17, 24, 39, 0.26);
  --kb-off: rgba(17, 24, 39, 0.34);
  --kb-glow: 0 4px 14px rgba(20, 30, 50, 0.14);
}

[data-theme="neutral"] {
  --kb-gold-1: #a67c1f;
  --kb-gold-2: #6c4f0f;
  --kb-gold-line: #7d5a11;
  --kb-pink-1: #a4486a;
  --kb-pink-2: #5e2740;
  --kb-violet-1: #5c5680;
  --kb-violet-2: #2f2a4a;
  --kb-teal-1: #1c7350;
  --kb-teal-2: #0d4630;
  --kb-stage: #f8f8f9;
  --kb-stage-line: rgba(43, 38, 30, 0.06);
  --kb-halo: #f8f8f9;
  --kb-line: rgba(43, 38, 30, 0.3);
  --kb-off: rgba(43, 38, 30, 0.38);
  --kb-glow: none;
}

/* Мера содержимого одна на весь пульт и живёт в `layout.css`
   (`--content-max`). Свои 98rem/1560px «База знаний» больше не заводит. */

/* Ступени экрана: карта → раздел → редактор. Видна ровно одна из первых двух. */
.kb-screen {
  display: grid;
  gap: var(--s4);
}

.kb-notice {
  margin: 0 0 var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-ctl);
  background: var(--bad-soft);
  border: 1px solid var(--bad-line);
  color: var(--milk);
  font-size: var(--t-sm);
}

/* Подпись под H1 карты: «разделов ровно десять, добавить — вот так».
   Отзыв оператора 12.09: экран не отвечал на вопрос, ради которого на него
   приходят. Ссылки внутри — цветом текста с подчёркиванием, а не акцентом:
   акцентная строка на стекле роняет контраст ниже 4,5:1. */
.kb-howto {
  margin-top: var(--s2);
  max-width: 62ch;
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--silver);
}

.kb-howto a {
  color: var(--milk);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}

.kb-howto a:hover { color: var(--accent-2); }

/* ------------------------------------------- ТУЛБАР ШЕСТИ КНОПОК (реф 5, п.2) */
.kb-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  row-gap: var(--s2);
}

.kb-tools {
  display: flex;
  align-items: center;
  gap: var(--s1);
  flex-wrap: wrap;
}

.kb-tools .sep {
  width: 1px;
  height: 1.1rem;
  background: var(--line-2);
  margin: 0 var(--s1);
}

button.kb-tool {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  min-height: 2.1rem;
  padding: 0;
  border-radius: var(--r-ctl);
  background: var(--surface-flat);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-btn);
  color: var(--mute);
}

button.kb-tool:hover:not(:disabled) {
  color: var(--milk);
  border-color: var(--line-3);
}

/* Активный режим — заливка и обводка (реф 5, п.2: «курсор» выбран всегда). */
button.kb-tool.is-active {
  background: var(--ink-740);
  border-color: var(--line-3);
  color: var(--milk);
}

/* Включённое состояние фильтра и слоёв — залито акцентом. */
button.kb-tool.is-on {
  background: var(--accent-soft-2);
  border-color: var(--accent-line);
  color: var(--accent-2);
}

/* Седьмая кнопка тулбара — с подписью: «Редактировать раздел…». Иконкой её
   не сделать, в этом и смысл — оператор не нашёл вход в редактор среди
   шести одинаковых квадратов. */
button.kb-tool--wide {
  width: auto;
  gap: var(--s2);
  padding: 0 var(--s3);
  grid-auto-flow: column;
  background: var(--accent-soft-2);
  border-color: var(--accent-line);
  color: var(--accent-2);
  font-size: var(--t-xs);
  font-weight: 600;
}

button.kb-tool--wide:hover:not(:disabled) {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-2);
}

.kb-tool .cnt {
  position: absolute;
  top: -0.4rem;
  right: -0.35rem;
  min-width: 1rem;
  height: 1rem;
  padding: 0 0.2rem;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 0.66rem;
  font-weight: 700;
  line-height: 1rem;
  text-align: center;
}

/* Выпадающие наборы фильтра и слоёв: чипы-переключатели. */
.kb-menu {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  padding: var(--s3);
  border-radius: var(--r-card-sm);
  background: var(--glass-3);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-card);
}

button.kb-mchip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 1.9rem;
  padding: 0 var(--s3);
  border-radius: var(--r-ctl);
  background: var(--surface-flat);
  border: 1px solid var(--line-2);
  color: var(--silver);
  font-size: var(--t-xs);
  font-weight: 500;
}

button.kb-mchip.is-on {
  background: var(--accent-soft-2);
  border-color: var(--accent-line);
  color: var(--accent-2);
  font-weight: 600;
}

button.kb-mchip .n {
  font-variant-numeric: tabular-nums;
  color: var(--mute);
}

/* Заголовок выпадающего списка разделов. Своей строкой на всю ширину: меню —
   flex-строка чипов, и без этого заголовок встал бы первым чипом. */
.kb-menu-head {
  flex: 1 0 100%;
  margin-bottom: var(--s1);
  font-size: var(--t-xs);
  color: var(--silver);
}

/* Кнопка входа в редактор: шапка раздела и карточка у узла. Первичная по
   умолчанию (вид берётся у `button` из app.css) — здесь только блик. */
button.kb-edit-main {
  position: relative;
  overflow: hidden;
}

button.kb-edit-main::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 45%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.34), transparent);
  transform: translateX(-120%) skewX(-16deg);
  pointer-events: none;
  opacity: 0;
}

/* Чипы состояния базы справа от тулбара. */
.kb-chips {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
}

.kb-flag {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 1.9rem;
  padding: 0 var(--s3);
  border-radius: var(--r-ctl);
  background: var(--glass-3);
  border: 1px solid var(--line-2);
  color: var(--silver);
  font-size: var(--t-xs);
  font-weight: 500;
  white-space: nowrap;
}

.kb-flag i {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  flex: none;
  background: var(--mute);
}

/* 🔴 Фон у цветного чипа НЕ красится, красятся текст и точка — то же
   решение, что у `.ochip` в orders.css, и по той же причине: янтарный текст
   на янтарной подложке даёт 4,19:1 в «Стекле» и 4,38:1 в «Спокойной», то
   есть ниже порога 4.5:1. Цвет несёт точка, читаемость несёт фон. */
.kb-flag--ok { color: var(--ok); border-color: var(--ok-line); }
.kb-flag--ok i { background: var(--ok); }

.kb-flag--warn { color: var(--warn); border-color: var(--warn-line); }
.kb-flag--warn i { background: var(--warn); }

.kb-flag--bad { color: var(--bad); border-color: var(--bad-line); }
.kb-flag--bad i { background: var(--bad); }

/* --------------------------------------------------------- СЦЕНА КАРТЫ */
.kb-stage {
  position: relative;
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr) 16rem;
  gap: var(--s3);
  padding: var(--s4);
  border-radius: var(--r-card);
  background-color: var(--kb-stage);
  background-image:
    linear-gradient(var(--kb-stage-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--kb-stage-line) 1px, transparent 1px);
  background-size: 16px 16px, 16px 16px;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
}

/* -------------------------------------- ЛЕНТА ДАТ ВЕРСИЙ (реф 5, п.3 и 9) */
.kb-time {
  display: grid;
  align-content: start;
  justify-items: center;
}

.kb-cap {
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}

.kb-time .kb-cap { margin-bottom: var(--s2); }

.kb-tl-svg {
  width: 76px;
  height: auto;
  max-height: 26rem;
}

.tl-spine { fill: none; stroke: var(--line-3); stroke-width: 1.2; }
.tl-tick { stroke: var(--line-2); stroke-width: 1; }
.tl-tick--hot { stroke: var(--kb-gold-line); stroke-width: 1.4; }
.tl-day { font-size: 11px; fill: var(--mute); font-variant-numeric: tabular-nums; }
.tl-day--on { fill: var(--milk); font-weight: 600; }
.tl-pill { fill: var(--glass-3); stroke: var(--line-3); stroke-width: 1; }
.tl-dot { fill: var(--kb-gold-line); }
.tl-hit { fill: transparent; cursor: pointer; }

.kb-day-note {
  margin-top: var(--s2);
  font-size: var(--t-xs);
  color: var(--mute);
  text-align: center;
}

.kb-day-note b { color: var(--milk); }

/* ------------------------------------------------------- ГРАФ-СОЗВЕЗДИЕ */
.kb-canvas { position: relative; min-width: 0; }
.kb-scroll { overflow-x: auto; }
/* 🔴 Потолок созвездия. Граф нарисован в своём `viewBox`, и при ширине 100%
   он масштабируется целиком: на 1100 px узлы разъезжались, подписи разделов
   висели в пустоте по краям, а «Промпт EVA» в центре терял связь с лучами.
   62rem — та ширина, на которой карта утверждалась на 1440. Выше графа
   растёт не он, а поле вокруг него, поэтому холст центрируется. */
.kb-graph-svg {
  display: block;
  width: 100%;
  min-width: 880px;
  max-width: 62rem;
  height: auto;
  margin-inline: auto;
}

.g-link { fill: none; stroke: var(--kb-line); stroke-width: 1; opacity: 0.5; }
.g-ray { stroke: var(--kb-gold-line); stroke-width: 1.4; opacity: 0.9; }
.g-ray--draft { opacity: 0.6; }
.g-ray--off { stroke: var(--kb-off); stroke-width: 1; stroke-dasharray: 4 4; opacity: 0.8; }
.g-twig { stroke: var(--kb-line); stroke-width: 1; }
.g-twig--leaf { opacity: 0.55; }
.g-sub.is-dim { opacity: 0.58; }
.n-body { fill: url(#kbGradSec); }
.n-gloss { fill: rgba(255, 255, 255, 0.72); }
.n-head { fill: url(#kbGradHead); }
.n-head--flat { fill: url(#kbGradHead); opacity: 0.5; }
.n-ver { fill: url(#kbGradVer); }
.n-leaf { fill: var(--kb-line); }
.n-sec.is-draft .n-body { opacity: 0.72; }
.n-sec.is-off .n-body { opacity: 0.4; }
/* День на ленте выбран или включён фильтр: не попавшие узлы гаснут. */
.n-sec.is-mute, .g-ray.is-mute, .g-sub.is-mute { opacity: 0.18; }
.n-focus-ring { fill: none; stroke: var(--kb-gold-line); stroke-width: 1.4; }
.n-root-body { fill: url(#kbGradRoot); }
.n-root-orbit { fill: none; stroke: var(--kb-gold-line); stroke-width: 1; opacity: 0.55; }
.n-body--focus, .n-body--big { fill: url(#kbGradSec); }

/* Подписи: 11 px и обводка цветом сцены, чтобы луч не резал буквы. */
.g-label {
  font-size: 11px;
  font-weight: 600;
  fill: var(--milk);
  paint-order: stroke;
  stroke: var(--kb-halo);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.g-label--root { font-size: 13px; font-weight: 700; }

/* 🔴 У сферы, рядом с которой стоит мини-карточка, своя подпись гаснет
   (ревизия 13.09, дефект 4). Название раздела говорит заголовок карточки, и
   до правки оно стояло дважды в 120 px друг от друга — «Скрипты продаж» на
   карточке и «Скрипты продаж» под сферой. Класс ставит `placeSpot()` и ровно
   тогда, когда карточка действительно легла на холст: на 390 px её нет, и
   подписи там не гаснут (да и сами подписи там спрятаны — см. адаптив). */
.n-sec.is-spot > .g-label,
.n-sec.is-spot > .g-count { opacity: 0; }

.g-count {
  font-size: 11px;
  fill: var(--mute);
  font-variant-numeric: tabular-nums;
  paint-order: stroke;
  stroke: var(--kb-halo);
  stroke-width: 3px;
  stroke-linejoin: round;
}

:root .n-sec .n-body { filter: drop-shadow(0 0 7px rgba(255, 122, 162, 0.45)); }
:root .n-root-body { filter: drop-shadow(0 0 16px rgba(231, 207, 157, 0.4)); }
[data-theme="light"] .n-sec .n-body { filter: drop-shadow(0 2px 5px rgba(20, 30, 50, 0.18)); }
[data-theme="light"] .n-root-body { filter: drop-shadow(0 2px 8px rgba(20, 30, 50, 0.2)); }
[data-theme="neutral"] .n-sec .n-body,
[data-theme="neutral"] .n-root-body { filter: none; }

/* Кольцо выбранного раздела и курсор на всём, что кликается. */
.n-sec, .n-root { cursor: pointer; }

/* ------------------------------------------------- ЛИНЗА-РАДАР (реф 5, п.5) */
.kb-side {
  display: grid;
  align-content: start;
  gap: var(--s3);
  min-width: 0;
}

.kb-lens { display: grid; justify-items: center; }
.kb-lens-svg { width: 200px; height: 200px; max-width: 100%; }
.lens-dash { fill: none; stroke: var(--kb-line); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.8; }
.lens-cross { stroke: var(--kb-line); stroke-width: 1; opacity: 0.55; }
.lens-tick { stroke: var(--kb-line); stroke-width: 1; opacity: 0.75; }
.lens-ring { fill: none; stroke-width: 3.2; stroke-linecap: round; }
.lens-ring--fill { stroke: var(--kb-gold-line); }
.lens-ring--share { stroke: var(--kb-teal-1); }

.lens-cap {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  width: 100%;
  margin-top: var(--s2);
}

.lens-cap b {
  display: block;
  font-size: var(--t-sm);
  font-weight: 700;
  color: var(--milk);
  font-variant-numeric: tabular-nums;
}

.lens-cap span {
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--mute);
}

.lens-cap .r { text-align: right; }

/* ------------------------------------------------ ЛЕГЕНДА И ПЛИТКИ (п. 6–7) */
.kb-legend {
  display: grid;
  gap: var(--s1);
  padding: var(--s3);
  border-radius: var(--r-card-sm);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
}

.kb-legend .h {
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mute);
}

.kb-legend .l {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-xs);
  color: var(--silver);
}

.kb-legend i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex: none;
}

.kb-legend i.sq { border-radius: 2px; }
.lg-gold { background: var(--kb-gold-line); }
.lg-pink { background: var(--kb-pink-1); }
.lg-violet { background: var(--kb-violet-1); }
.lg-teal { background: var(--kb-teal-1); }
.lg-leaf { background: var(--kb-line); }
.lg-off { background: var(--kb-off); }
.lg-draft { background: var(--warn); }

/* Легенда лежит в левом нижнем углу холста и не уезжает с прокруткой графа. */
.kb-legend--float {
  position: absolute;
  left: var(--s1);
  bottom: var(--s1);
  width: 13rem;
}

/* ------------------------- МИНИ-КАРТОЧКА У ВЫБРАННОГО УЗЛА (отзыв 12.09) ---
   Координаты (`left`/`top`) ставит `kb.js`: узел живёт в системе координат
   SVG, а карточка — в пикселях холста, и посчитать это в CSS нечем. Здесь
   только вид.

   🔴 Порядок рисования внутри `.kb-canvas` (разбор дефекта 4, ревизия 13.09).
   Все трое лежат в одном контексте наложения — его задаёт `.kb-stage` своим
   `backdrop-filter`: холст `.kb-scroll` (в потоке, без позиции), плавающая
   легенда (absolute, z-index auto) и эта карточка (absolute, z-index 6).
   Значит подписи графа НИЖЕ карточки, и своего z-index холсту заводить
   нельзя: он тут же перекрыл бы легенду. Призраком карточка выглядела не
   из-за порядка, а из-за кадра с opacity — см. `kb-spot-in` в конце файла. */
.kb-spot {
  position: absolute;
  z-index: 6;
  width: 15.5rem;
  max-width: 70%;
}

.kb-spot-card {
  display: grid;
  gap: var(--s2);
  padding: var(--s3);
  border-radius: var(--r-card-sm);
  background: var(--glass-3);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-card);
}

.kb-spot-card b {
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--milk);
}

.kb-spot-card .m {
  font-size: var(--t-xs);
  color: var(--silver);
  font-variant-numeric: tabular-nums;
}

.kb-spot-card .st {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-xs);
  color: var(--silver);
}

.kb-spot-card .st i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex: none;
  background: var(--kb-pink-1);
}

.kb-spot-card .st i.d { background: var(--warn); border-radius: 2px; }
.kb-spot-card .st i.off { background: var(--kb-off); }

.kb-spot-card .row {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-top: var(--s1);
}

.kb-tiles {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s3);
}

.kb-tile {
  position: relative;
  overflow: hidden;
  padding: var(--s3);
  border-radius: var(--r-card-sm);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
  display: grid;
  gap: var(--s1);
  align-content: start;
}

.kb-tile .cap {
  font-size: var(--t-micro);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--mute);
}

.kb-tile .val {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--milk);
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}

.kb-tile .val small { font-size: var(--t-sm); font-weight: 600; color: var(--mute); }
.kb-tile .sub { font-size: var(--t-xs); color: var(--mute); }
.kb-tile .dotnum { filter: none; }
.kb-tile .dotnum svg { height: 2rem; }
.kb-tile .ovspark { width: 100%; height: 2.6rem; }

/* 🔴 Спарклайн системы нарисован для ГРАДИЕНТНОЙ карточки: его линия и
   маркеры взяты `--on-grad`, то есть белым. На стеклянной плитке «Стекла»
   это белое по белому — ряд правок не видно вовсе. Перекрашиваем на месте,
   как в макете: волна бирюзовая, пунктирная база — цвет веточек графа. */
.kb-tile .ovspark-line { stroke: var(--kb-teal-1); stroke-width: 1.8; }
.kb-tile .ovspark-base { stroke: var(--kb-line); stroke-dasharray: 6 4; }
.kb-tile .ovspark-dot { fill: var(--kb-teal-1); }
.kb-tile .ovspark-dot.is-hollow { fill: none; stroke: var(--kb-teal-1); }
.kb-tile .ovspark-area { fill: none; }

.kb-gauge { width: 100%; max-width: 116px; height: auto; }
.gg-track { fill: none; stroke: var(--line-2); stroke-width: 7; stroke-linecap: round; }
.gg-fill { fill: none; stroke: var(--kb-gold-line); stroke-width: 7; stroke-linecap: round; }

/* --------------------------------- СТРОКА ЗАПРОСА И ПОИСК ПО БАЗЕ (п. 23) */
.kb-ask { max-width: 44rem; margin-inline: auto; width: 100%; }

.kb-search {
  display: flex;
  align-items: center;
  gap: var(--s2);
  height: 2.15rem;
  padding: 0 var(--s3);
  border-radius: var(--r-pill);
  background: var(--glass);
  border: 1px solid var(--line-2);
  color: var(--mute);
  min-width: 15rem;
}

.kb-search input {
  width: 100%;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--milk);
  font: inherit;
  font-size: var(--t-sm);
}

.kb-search input:focus { outline: none; }
.kb-search input::placeholder { color: var(--faint); }

.kb-finds {
  display: grid;
  gap: var(--s1);
  padding: var(--s3);
  border-radius: var(--r-card-sm);
  background: var(--glass-3);
  border: 1px solid var(--line-2);
  box-shadow: var(--sh-card);
  max-height: 18rem;
  overflow-y: auto;
}

.kb-finds .hint { font-size: var(--t-xs); color: var(--mute); }

button.kb-find-row {
  display: grid;
  grid-template-columns: minmax(6rem, auto) minmax(0, 1fr);
  gap: var(--s3);
  align-items: baseline;
  width: 100%;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-ctl);
  background: none;
  border: 1px solid transparent;
  /* 🔴 Тень снимается явно: общее правило `button` в app.css вешает её на
     любую кнопку, и строка списка получала под собой тёмную полосу. */
  box-shadow: none;
  text-align: left;
  color: var(--silver);
  font-size: var(--t-xs);
}

button.kb-find-row:hover {
  background: var(--ink-740);
  border-color: var(--line-2);
  color: var(--milk);
}

button.kb-find-row b { color: var(--milk); font-weight: 600; }
button.kb-find-row .ln { color: var(--mute); font-variant-numeric: tabular-nums; }
button.kb-find-row .sn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ------------------------------------ ШТОРКА НАСТРОЕК БАЗЫ (реф 5, п.15) */
.kb-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  /* 🔴 Выше шапки пульта (`.topbar`, z-index 60) и ниже окна подтверждения
     (300): шторка — модальная панель, её закрывают Esc и клик по затемнению.
     На 40 шапка накрывала её собственный заголовок с кнопкой «закрыть». */
  z-index: 71;
  width: min(26rem, 100vw);
  overflow-y: auto;
  padding: var(--s5);
  background: var(--glass-3);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-left: 1px solid var(--line-2);
  box-shadow: var(--sh-3);
  display: grid;
  gap: var(--s3);
  align-content: start;
}

.kb-drawer h3 { font-size: var(--t-md); font-weight: 600; letter-spacing: -0.02em; }
.kb-drawer .hint { font-size: var(--t-xs); color: var(--mute); }

.kb-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}

.kb-swrow {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s2) 0;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: none;
  box-shadow: none;
  text-align: left;
  color: var(--milk);
  font-size: var(--t-sm);
  min-height: 2.6rem;
}

.kb-swrow:hover:not(:disabled) { background: none; color: var(--milk); }
.kb-swrow:disabled { cursor: default; opacity: 1; }
.kb-swrow .nm { min-width: 0; flex: 1; }

.kb-swrow .nm small {
  display: block;
  font-size: var(--t-xs);
  color: var(--mute);
  font-variant-numeric: tabular-nums;
}

.kb-sw {
  position: relative;
  width: 2.1rem;
  height: 1.25rem;
  border-radius: var(--r-pill);
  flex: none;
  background: var(--line-2);
  border: 1px solid var(--line-2);
}

.kb-sw::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  background: var(--glass-3);
  box-shadow: 0 1px 2px rgba(20, 30, 50, 0.3);
}

.kb-sw.on { background: var(--accent); border-color: var(--accent); }
.kb-sw.on::after { left: auto; right: 2px; background: var(--accent-ink); }
.kb-sw.lock { background: var(--line); }
.kb-lock { color: var(--mute); flex: none; }

.kb-limits {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s2);
}

.kb-limits div {
  padding: var(--s2) var(--s3);
  border-radius: var(--r-ctl);
  background: var(--glass);
  border: 1px solid var(--line);
}

.kb-limits b {
  display: block;
  font-size: var(--t-base);
  font-weight: 700;
  color: var(--milk);
  font-variant-numeric: tabular-nums;
}

.kb-limits span { font-size: var(--t-micro); color: var(--mute); }

.kb-drawer-out {
  display: grid;
  gap: var(--s2);
  font-size: var(--t-xs);
  color: var(--silver);
  line-height: 1.45;
}

.kb-drawer-out p { display: flex; gap: var(--s2); }

.kb-drawer-out i {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 2px;
  flex: none;
  margin-top: 0.4rem;
}

/* Затемнение под шторкой: клик по нему закрывает панель. */
.kb-scrim {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: var(--backdrop);
}

/* ==================================================== ЭКРАН РАЗДЕЛА (реф 6) */
.kb-sec-grid {
  display: grid;
  grid-template-columns: 14rem 12.5rem 7.25rem minmax(0, 1fr) 19rem;
  gap: var(--s3);
  align-items: start;
  padding: var(--s4);
  border-radius: var(--r-card);
  background-color: var(--kb-stage);
  background-image:
    linear-gradient(var(--kb-stage-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--kb-stage-line) 1px, transparent 1px);
  background-size: 16px 16px, 16px 16px;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
}

.kb-node h3 {
  font-size: var(--t-lg);
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.02em;
  color: var(--milk);
}

.kb-node h3 span { display: block; }

.kb-node h3 #kb-node-file {
  font-weight: 400;
  font-size: var(--t-sm);
  color: var(--mute);
}

/* Носитель общих градиентов: в потоке его нет, но он и не спрятан. */
.kb-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.kb-orb { width: 150px; height: 150px; margin: var(--s2) auto 0; display: block; }
.orb-track { fill: none; stroke: var(--kb-line); stroke-width: 1; opacity: 0.7; }
.orb-ring { fill: none; stroke-width: 3.4; stroke-linecap: round; }
.orb-ring--fill { stroke: var(--kb-gold-line); }
.orb-ring--share { stroke: var(--kb-teal-1); }
.orb-sat { fill: var(--kb-line); }

.kb-parent {
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
  margin-top: var(--s2);
}

.kb-parent .dot {
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 50%;
  flex: none;
  margin-top: 0.15rem;
  background: linear-gradient(150deg, var(--kb-gold-1), var(--kb-gold-2));
}

.kb-parent b { font-size: var(--t-sm); font-weight: 600; color: var(--milk); }
.kb-parent small { display: block; font-size: var(--t-xs); color: var(--mute); }

.kb-tagrow {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s2);
  flex-wrap: wrap;
}

.kb-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  min-height: 1.25rem;
  padding: 0 var(--s2);
  border-radius: var(--r-xs);
  background: var(--glass);
  border: 1px solid var(--line-2);
  font-size: var(--t-micro);
  font-weight: 600;
  color: var(--mute);
  white-space: nowrap;
}

.kb-sq { width: 0.45rem; height: 0.45rem; border-radius: 2px; flex: none; }
.kb-sq--bad { background: var(--bad); }
.kb-sq--warn { background: var(--warn); }
.kb-sq--cool { background: var(--cool, var(--kb-teal-1)); }
.kb-sq--mute { background: var(--faint); }
.kb-sq--gold { background: var(--kb-gold-line); }

/* ------------------------------------- СТОПКА КАРТОЧЕК-СЧЁТЧИКОВ (реф 6, п.3) */
.kb-cards { display: grid; gap: var(--s2); margin-top: 3.2rem; }

button.kb-card {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: 3.5rem;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-ctl);
  background: var(--glass-2);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
  text-align: left;
  color: var(--milk);
}

.kb-card .ic { color: var(--mute); flex: none; display: grid; place-items: center; }
.kb-card b { font-size: var(--t-md); font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.kb-card small { display: block; font-size: var(--t-xs); color: var(--mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.kb-card.is-on {
  border-color: var(--kb-gold-line);
  background: var(--glass-3);
  transform: translateX(7px);
  box-shadow: var(--kb-glow);
}

.kb-card.is-on .ic { color: var(--kb-gold-line); }

/* ------------------------------------------- ВЕЕР И СПИСОК (реф 6, п.4 и 8) */
.kb-fanwrap { align-self: start; overflow: hidden; }
.kb-fan { display: block; max-width: 100%; }
.fan-line { fill: none; }

.kb-vlist {
  position: relative;
  display: grid;
  align-content: start;
  /* 🔴 Верхняя кромка гасится всего на 12 px: «уходит за край» должно быть
     намёком, а не поводом уронить контраст текущей версии ниже 4.5:1. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 12px, #000 calc(100% - 26px), transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 12px, #000 calc(100% - 26px), transparent 100%);
  max-height: 34rem;
  overflow-y: auto;
}

.kb-vrow {
  display: grid;
  grid-template-columns: 0.9rem minmax(0, 1fr);
  gap: var(--s2);
  align-items: start;
  min-height: 3.5rem;
  padding: var(--s1) var(--s2) var(--s1) 0;
  border: 0;
  border-radius: var(--r-ctl);
  background: none;
  box-shadow: none;
  text-align: left;
  color: var(--milk);
}

button.kb-vrow:hover { background: var(--ink-740); }

.kb-vrow .pt {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  margin: 0.35rem 0 0 0.2rem;
  background: var(--kb-line);
}

.pt--cur { background: var(--kb-gold-line); box-shadow: 0 0 0 3px var(--accent-soft); }
.pt--file { background: var(--kb-teal-1); }
.pt--admin { background: var(--kb-violet-1); }
.pt--head { background: var(--kb-violet-1); }
.pt--warn { background: var(--warn); }
.pt--bad { background: var(--bad); }
.pt--link { background: var(--kb-pink-1); }

/* Масштаб списка («+»/«−» в тулбаре раздела) — множитель к шкале размеров,
   а не свой размер в пикселях: `--kb-scale` ставит kb.js на контейнере. */
.kb-vrow .nm {
  font-size: calc(var(--t-sm) * var(--kb-scale, 1));
  font-weight: 600;
  color: var(--milk);
  font-variant-numeric: tabular-nums;
}

.kb-vrow .nm span { font-weight: 400; color: var(--mute); }

.kb-vrow .why {
  display: block;
  font-size: calc(var(--t-xs) * var(--kb-scale, 1));
  color: var(--silver);
  line-height: 1.4;
}
.kb-vrow .tags { display: flex; gap: var(--s1); margin-top: var(--s1); flex-wrap: wrap; }

/* --------------------------------------------- ПАНЕЛЬ ДЕТАЛИ (реф 6, п.6) */
.kb-detail {
  display: grid;
  gap: var(--s3);
  align-content: start;
  padding: var(--s4);
  border-radius: var(--r-card-sm);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
}

.kb-detail .eyebrow-row { display: flex; align-items: center; gap: var(--s2); }
.kb-detail h4 { font-size: var(--t-base); font-weight: 600; line-height: 1.35; letter-spacing: -0.015em; color: var(--milk); }
.kb-detail .eyebrow { font-size: var(--t-xs); color: var(--mute); }
.kb-detail .between { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); }

.kb-big { display: flex; align-items: flex-end; gap: var(--s2); flex-wrap: wrap; }

.kb-big b {
  font-size: var(--t-2xl);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--milk);
  font-variant-numeric: tabular-nums;
}

.kb-hist { width: 100%; height: 56px; }
.hb { fill: var(--kb-line); }
.hb--on { fill: var(--kb-gold-line); }
.hb-limit { stroke: var(--bad); stroke-width: 1; stroke-dasharray: 4 3; opacity: 0.75; }

.kb-2x2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s2); }

.kb-2x2 div {
  padding: var(--s2) var(--s3);
  border-radius: var(--r-ctl);
  background: var(--glass);
  border: 1px solid var(--line);
}

.kb-2x2 b {
  display: block;
  font-size: var(--t-md);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  color: var(--milk);
}

.kb-2x2 span { display: block; font-size: var(--t-micro); color: var(--mute); line-height: 1.3; }

.kb-note { display: grid; gap: var(--s2); }
.kb-note p { display: flex; gap: var(--s2); font-size: var(--t-xs); color: var(--silver); line-height: 1.45; }
.kb-note i { width: 0.45rem; height: 0.45rem; border-radius: 2px; flex: none; margin-top: 0.35rem; }

/* ------------------- ПРИЗЫВ НА ЧЕРНОВОМ РАЗДЕЛЕ (отзыв оператора 12.09) ---
   Девять разделов из десяти — заглушки из анкеты, и метка уезжает в промпт
   вместе с текстом. Строка об этом была только мелким шрифтом в панели
   детали справа; здесь она стоит на пути и с кнопкой. */
.kb-draftcall {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-card-sm);
  background: var(--warn-soft);
  border: 1px solid var(--warn-line);
}

.kb-draftcall p {
  flex: 1;
  min-width: 0;
  font-size: var(--t-sm);
  line-height: 1.45;
  color: var(--milk);
}

.kb-draftcall .kb-sq { width: 0.55rem; height: 0.55rem; }

/* ------------------ СРЕДНЯЯ КОЛОНКА ЭКРАНА РАЗДЕЛА: СПИСОК И ТЕКСТ --------
   Список версий из одной строки оставлял под собой ~450×470 пустоты — высоту
   ряда задаёт панель детали справа, и сжать её нечем (ревизия 13.09, деф. 17).
   Пустоту занял сам текст раздела: до правки его на экране не было вовсе. */
.kb-midcol { display: grid; align-content: start; min-width: 0; }

.kb-peek {
  display: grid;
  gap: var(--s2);
  margin-top: var(--s3);
  padding: var(--s3);
  border-radius: var(--r-card-sm);
  background: var(--glass-2);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--line);
  box-shadow: var(--sh-card);
  min-width: 0;
}

.kb-peek-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
}

/* 🔴 `pre-wrap`, а не `<pre>`: строки раздела длиной с абзац, и горизонтальная
   прокрутка внутри карточки читалась бы хуже переноса. `overflow-wrap:
   anywhere` — на случай ссылки без пробелов. */
.kb-peek-body {
  /* 34rem — та же мера, что у списка версий рядом (`.kb-vlist`, 34rem):
     панель занимает ряд до панели детали справа и не тянет сетку вниз. */
  max-height: 34rem;
  overflow-y: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: var(--t-xs);
  line-height: 1.6;
  color: var(--silver);
}

.kb-peek-body b.h {
  display: inline-block;
  margin-top: var(--s2);
  color: var(--milk);
  font-weight: 700;
}

/* Третья ступень — существующий редактор. Отдельной панелью под разделом. */
.kb-editor { display: grid; gap: var(--s4); margin-top: var(--s4); }

/* 🔴 Две колонки от 1180 px (ревизия 13.09, дефект 17). Поле держали мерой
   чтения `max-width: 74ch` (app.css, `#kb-text`), и в карточке 1371 px справа
   от него пустовала ровно половина. Меру теперь держит ширина самой карточки:
   поле занимает её целиком, а отданные вбок ~22rem заняты подсказкой «Как
   писать» и разбором того, что сейчас в поле. Ниже 1180 px — одна колонка,
   порядок тот же, что в разметке: поле, подсказка, история. */
@media (min-width: 1180px) {
  .kb-editor { grid-template-columns: minmax(0, 1fr) 22rem; }
  .kb-edit-card { grid-column: 1; grid-row: 1; }
  .kb-aside { grid-column: 2; grid-row: 1; align-self: start; }
  /* Построчное сравнение версий живёт в истории — ей нужна вся ширина. */
  .kb-hist-card { grid-column: 1 / -1; }
}

/* На 3440 колонка шире: иначе строка поля уходит за 130 знаков — это уже не
   редактор, а лог (то самое возражение, ради которого и стояло 74ch). */
@media (min-width: 100rem) {
  .kb-editor { grid-template-columns: minmax(0, 1fr) 30rem; }
}

.kb-aside { display: grid; gap: var(--s4); align-content: start; }
.kb-struct { display: grid; gap: var(--s2); }

.kb-struct .kb-heads {
  display: grid;
  gap: var(--s1);
  max-height: 18rem;
  overflow-y: auto;
}

.kb-struct .hrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2);
  align-items: baseline;
  padding: var(--s1) var(--s2);
  border-radius: var(--r-ctl);
  background: var(--glass);
  border: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--milk);
}

.kb-struct .hrow span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.kb-struct .hrow b {
  color: var(--mute);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* «Как писать» — свёрнутая подсказка над полем. */
.kb-how {
  border-radius: var(--r-ctl);
  background: var(--glass-2);
  border: 1px solid var(--line-2);
}

.kb-how summary {
  padding: var(--s2) var(--s3);
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--milk);
  cursor: pointer;
  list-style: none;
}

.kb-how summary::-webkit-details-marker { display: none; }

.kb-how summary::before {
  content: "▸";
  display: inline-block;
  margin-right: var(--s2);
  color: var(--mute);
}

.kb-how[open] summary::before { content: "▾"; }

.kb-how-body {
  display: grid;
  gap: var(--s2);
  padding: 0 var(--s3) var(--s3);
}

.kb-how-body p {
  font-size: var(--t-xs);
  line-height: 1.5;
  color: var(--silver);
}

.kb-how-body b { color: var(--milk); font-weight: 600; }

/* 🔴 Счётчик и «Сохранить» липнут к низу: раздел на двенадцать тысяч
   символов выше экрана, и панель с единственной кнопкой, ради которой сюда
   пришли, уезжала из виду на середине правки. */
.kb-editbar {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: grid;
  gap: var(--s2);
  /* Отрицательные поля ровно на padding карточки (`--s5` в app.css): панель
     обязана лечь на всю её ширину, иначе под ней просвечивает край текста. */
  margin: var(--s2) calc(-1 * var(--s5)) calc(-1 * var(--s5));
  padding: var(--s3) var(--s5);
  background: var(--glass-3);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-top: 1px solid var(--line-2);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

.kb-editbar .buttons { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

/* Есть несохранённые правки — «Сохранить» подсвечена кольцом. Цвет здесь не
   единственный носитель: счётчик рядом говорит это словами. */
#kb-save.is-armed {
  box-shadow: 0 0 0 3px var(--accent-soft-2);
}

/* Версия, которая И ЕСТЬ текущий текст раздела (D2/D5, ручной QA 12.09).
   🔴 Помечена не одной заливкой: та же золотая засечка, что у текущего
   столбика гистограммы, плюс слова «это текущий текст» в подписи строки и
   погашенная кнопка «Откатить». Цвет здесь — четвёртый носитель смысла, а не
   единственный: в чёрно-белой печати и при дальтонизме остаются слова. */
#kb-versions .row.is-current {
  position: relative;
  border-color: var(--kb-gold-line);
  background: var(--accent-soft);
}

#kb-versions .row.is-current::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 2px;
  border-radius: 2px 0 0 2px;
  background: var(--kb-gold-line);
}

/* ------------------------------------------------------------- АДАПТИВ */
@media (max-width: 1180px) {
  .kb-stage { grid-template-columns: 5.5rem minmax(0, 1fr); }
  .kb-side { grid-column: 1 / -1; grid-template-columns: 13rem minmax(0, 1fr); }
  .kb-side .kb-tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .kb-sec-grid { grid-template-columns: 13rem 11.5rem 6.5rem minmax(0, 1fr); }
  .kb-detail { grid-column: 1 / -1; }
  .kb-2x2 { grid-template-columns: repeat(4, 1fr); }
}

/* 🔴 На 390 px подписи узлов ушли бы к 5 px — ниже минимума 11 px. Поэтому на
   телефоне граф показывает СТРУКТУРУ, а подписи и счётчики переезжают в
   список под ним (та же развязка, что в макете). */
.kb-mlist { display: none; }

@media (max-width: 760px) {
  .kb-stage { grid-template-columns: minmax(0, 1fr); padding: var(--s3); }
  .kb-time { justify-items: start; }
  .kb-tl-svg { max-height: 14rem; }
  .kb-side { grid-template-columns: minmax(0, 1fr); }
  .kb-side .kb-tiles { grid-template-columns: minmax(0, 1fr); }
  .kb-lens-svg { width: 180px; height: 180px; }
  .kb-graph-svg { min-width: 0; }
  .kb-graph-svg .g-label, .kb-graph-svg .g-count { display: none; }
  .kb-legend--float { position: static; width: auto; margin-top: var(--s3); }

  .kb-mlist { display: grid; gap: var(--s1); margin-top: var(--s3); }

  button.kb-mrow {
    display: grid;
    grid-template-columns: 0.65rem minmax(0, 1fr) auto;
    gap: var(--s2);
    align-items: center;
    padding: var(--s2) var(--s3);
    border-radius: var(--r-ctl);
    background: var(--glass-2);
    border: 1px solid var(--line);
    box-shadow: none;
    font-size: var(--t-sm);
    color: var(--milk);
    text-align: left;
  }

  .kb-mrow i { width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--kb-pink-1); }
  .kb-mrow.is-off i { background: var(--kb-off); }
  .kb-mrow b { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .kb-mrow span { font-size: var(--t-micro); color: var(--mute); font-variant-numeric: tabular-nums; white-space: nowrap; }

  .kb-sec-grid { grid-template-columns: minmax(0, 1fr); padding: var(--s3); }
  .kb-cards { margin-top: 0; }
  .kb-fanwrap { display: none; } /* веер без списка рядом смысла не несёт */
  .kb-vlist { -webkit-mask-image: none; mask-image: none; max-height: none; }
  .kb-vrow { min-height: 0; padding-bottom: var(--s3); }
  .kb-2x2 { grid-template-columns: 1fr 1fr; }
  /* 🔴 Текст раздела на телефоне не показываем: страница экрана раздела там и
     так под две тысячи пикселей, а раздел в 7 700 символов добавил бы к ней
     ещё экран. Текст читается и правится в редакторе — он на 390 открывается
     той же кнопкой «Редактировать». */
  .kb-peek { display: none; }
  .kb-drawer { width: 100vw; }
  .kb-search { width: 100%; min-width: 0; }
  /* Наведения на телефоне не бывает, а карточка у узла закрыла бы собой треть
     графа. Вход в редактор там другой: список разделов в тулбаре и кнопка
     «Редактировать» в шапке раздела. */
  .kb-spot { display: none; }
  /* Липкая панель сохранения на телефоне нужнее всего: экран раздела там —
     одна колонка в несколько высот. Остаётся как есть. */
  .kb-draftcall { flex-wrap: wrap; }
  .kb-draftcall p { flex: 1 0 100%; }
}

/* 🟡 M-12 (QA 24.09): цели касания карты знаний на телефоне и в альбоме.
   Общий блок тач-габаритов app.css (≤ 56rem) перебит здесь своими размерами:
   поле поиска `#kb-find` было 121 × 20 (у `.kb-search input` `min-height: 0`,
   высоту держала пилюля 2.15rem), инструменты `.kb-tool` — 34 × 34.
   Габарит касания 44, кегль и вид прежние. Граф в альбоме 812 × 375 стоял
   на `min-width: 880px` и прокручивался вбок внутри `#kb-graph` — на
   ≤ 56rem он вписывается в ширину, как уже было ниже 760 px. */
@media (max-width: 56rem) {
  .kb-search {
    height: auto;
    min-height: 2.75rem;
  }

  .kb-search input {
    min-height: 2.75rem;
  }

  button.kb-tool {
    width: 2.75rem;
    height: 2.75rem;
    min-height: 2.75rem;
  }

  .kb-graph-svg {
    min-width: 0;
  }

  /* «Спросить EVA по базе» — квадрат 26 px в строке запроса; как на «Обзоре». */
  #tab-kb .askbar-sq {
    width: 2.75rem;
    height: 2.75rem;
    min-height: 2.75rem;
  }

  /* Ссылки внутри подсказки «Как добавить» — строка текста в 17 px. Поле
     касания растёт вертикальными полями строчного элемента: высоту строки
     они не меняют, текст не раздвигается. */
  .kb-howto a {
    padding-block: 0.85rem;
  }
}

/* 🔴 «Карта знаний» на узком телефоне (375 и 360): вбок ехал весь документ —
   scrollWidth 377 при clientWidth 375 и те же 377 при 360.

   Распорку легко назвать неверно. `#kb-map` — грид в одну колонку, и ширину
   ЕДИНСТВЕННОГО трека задаёт самый широкий min-content среди детей; поэтому
   на 375 шапка экрана, тулбар, сцена графа и строка запроса меряются
   одинаковыми 361.06 px, и любой из них выглядит виноватым. Проверка
   поочерёдным `display: none` называет одного: `.askbar-row` — флекс без
   переноса, а в нём три пилюли («EVA · песочница», «CRM заглушена»,
   «разделов в ответе») и кнопка отправки, вместе 335 px, плюс поле строки
   запроса 12 + 12 и рамка 1 + 1. Шапка с поиском по базе, наоборот, ни при
   чём: её 176.53 + 176.53 + 8 — это половинки уже растянутого трека, и в
   треке 343 px она укладывается сама.

   Перенос разрешён только здесь и только ниже 26rem: `.askbar` — общий
   компонент «Обзора», «Заказов» и «Инбокса», и трогать его ради одного
   экрана нельзя. Порог тот же, что у пары панелей в `layout.css` и у веера
   персоны в `app.css`. */
@media (max-width: 26rem) {
  .kb-ask .askbar-row {
    flex-wrap: wrap;
  }
}

/* ===========================================================================
   ДВИЖЕНИЕ (правило 12.09: «не забывай про фишки — моушен, херо, карточки»).

   Границы: в кадре только transform, opacity и stroke-dashoffset; микро ≤
   300 мс, декоративные циклы ≥ 6 с; при prefers-reduced-motion: reduce всё
   статично (блок «ПОКОЙ» в самом низу).
   ======================================================================== */

@keyframes kb-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.04); } }
@keyframes kb-ray { 0%, 100% { opacity: 0.62; } 50% { opacity: 1; } }
@keyframes kb-spin { to { transform: rotate(360deg); } }
@keyframes kb-pulse { 0% { transform: scale(1); opacity: 0.85; } 70%, 100% { transform: scale(1.45); opacity: 0; } }
@keyframes kb-draw { from { stroke-dashoffset: var(--len, 100); } to { stroke-dashoffset: 0; } }
@keyframes kb-rise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes kb-grow { from { transform: scaleY(0.04); } to { transform: scaleY(1); } }
@keyframes kb-slide-day { from { transform: translateY(-62px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes kb-drawer-in { from { opacity: 0; transform: translateX(26px); } to { opacity: 1; transform: none; } }
@keyframes kb-sheen { from { transform: translateX(-120%) skewX(-16deg); } to { transform: translateX(260%) skewX(-16deg); } }
@keyframes kb-dot { from { opacity: 0.12; transform: scale(0.4); } to { opacity: 1; transform: none; } }
@keyframes kb-float { 0%, 100% { transform: translateY(-4px); } 50% { transform: translateY(4px); } }
@keyframes kb-unfold { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

/* Сферы разделов «дышат» с разной фазой — карта не выглядит скриншотом. */
.n-sec .n-body,
.n-sec .n-gloss,
.n-root-body {
  transform-box: fill-box;
  transform-origin: center;
  animation: kb-breathe 7.5s ease-in-out infinite;
  animation-delay: var(--d, 0s);
}

.n-root-body { animation-duration: 9s; }

/* Золотые лучи пульсируют: видно, что раздел включён и уезжает модели.
   Выключенный — серый пунктир — не пульсирует вовсе: мёртвая линия и должна
   быть мёртвой. */
.g-ray { animation: kb-ray 8s ease-in-out infinite; animation-delay: var(--d, 0s); }
.g-ray--off { animation: none; }

/* Корень вращает кольцо-орбиту. Пунктир обязателен: сплошное кольцо в
   повороте неотличимо от неподвижного. */
.n-root-orbit {
  stroke-dasharray: 3 6;
  transform-box: fill-box;
  transform-origin: center;
  animation: kb-spin 28s linear infinite;
}

/* Выбранный узел — расходящееся кольцо-пульс. */
.n-focus-ring {
  transform-box: fill-box;
  transform-origin: center;
  animation: kb-pulse 3.4s ease-out infinite;
}

/* Наведение на сферу: подъём и блик, «дыхание» на это время на паузе. */
.n-sec .n-body, .n-sec .n-gloss { transition: transform 220ms var(--ease); }
.n-sec:hover .n-body { transform: scale(1.16); animation-play-state: paused; }
.n-sec:hover .n-gloss { transform: scale(1.3); animation-play-state: paused; }
.g-sub { transition: opacity 200ms ease; }
.kb-graph-svg:hover .g-sub:not(.is-mute) { opacity: 0.42; }
.kb-graph-svg .g-sub:hover { opacity: 1; }
.n-sec, .g-ray, .g-sub { transition: opacity 250ms ease; }

/* Лента дат: пилюля выбранного дня переезжает на своё место. */
.tl-pill, .tl-day--on { animation: kb-slide-day 700ms var(--ease) both; }

/* Линза наводится: кольца прочерчиваются, прицел подъезжает. */
.kb-lens-svg { animation: kb-rise 420ms ease-out both; }
.lens-ring, .orb-ring { animation: kb-draw 900ms var(--ease) both; }
.lens-ring--share, .orb-ring--share { animation-delay: 160ms; }
.kb-lens { transition: transform 250ms var(--ease); }

/* Плитки каскадом; гейдж заполняется, спарклайн прочерчивается. */
.kb-tile { animation: kb-rise 380ms ease-out both; }
.kb-tile:nth-child(2) { animation-delay: 90ms; }
.kb-tile:nth-child(3) { animation-delay: 180ms; }
.gg-fill { stroke-dasharray: 100; --len: 100; animation: kb-draw 1s var(--ease) both; animation-delay: 160ms; }
/* `pathLength="100"` ставит kb.js на самой линии: длина пути зависит от
   данных, а в CSS её не вычислить. */
.kb-tile .ovspark-line { stroke-dasharray: 100; --len: 100; animation: kb-draw 1.2s ease-out both; animation-delay: 240ms; }
.kb-tile .ovspark-dot { transform-box: fill-box; transform-origin: center; animation: kb-dot 260ms ease-out both; animation-delay: 1.25s; }
.kb-tile .ovspark-dot.is-hollow { animation-delay: 1.35s; }

/* Цифра из точек зажигается волной: каждая точка со своей задержкой.
   Индекс `--i` ставит `kb.js` при отрисовке — селектором его не вычислить. */
.dotnum--wave circle {
  transform-box: fill-box;
  transform-origin: center;
  animation: kb-dot 320ms ease-out both;
  animation-delay: calc(var(--i, 0) * 9ms);
}

/* Стеклянные карточки: блик проезжает по стеклу при наведении. */
.kb-tile, .kb-card, .kb-legend { position: relative; overflow: hidden; }

.kb-tile::after,
.kb-card::after,
.kb-legend::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 40%;
  background: linear-gradient(90deg, transparent, var(--sheen-bar, rgba(255, 255, 255, 0.14)), transparent);
  transform: translateX(-120%) skewX(-16deg);
  pointer-events: none;
  opacity: 0;
}

[data-theme="light"] .kb-tile::after,
[data-theme="light"] .kb-card::after,
[data-theme="light"] .kb-legend::after { --sheen-bar: rgba(255, 255, 255, 0.5); }

[data-theme="neutral"] .kb-tile::after,
[data-theme="neutral"] .kb-card::after,
[data-theme="neutral"] .kb-legend::after { --sheen-bar: rgba(255, 255, 255, 0.55); }

.kb-tile:hover::after,
.kb-card:hover::after,
.kb-legend:hover::after { opacity: 1; animation: kb-sheen 620ms ease-out; }

/* Первичная кнопка входа в редактор — тот же блик по стеклу (чек-лист 12.09:
   «первичная — sheen при наведении»). */
button.kb-edit-main:hover:not(:disabled)::after { opacity: 1; animation: kb-sheen 620ms ease-out; }

/* Карточка у выбранного узла: подъём 200 мс. Пересобирается на каждую смену
   фокуса, поэтому анимация играет при каждом переезде линзы.

   🔴 Кадр здесь БЕЗ opacity, и это не вкусовщина (ревизия 13.09, дефект 4).
   `kb-rise` гонит opacity, Chromium поднимает карточку на свой слой, а слой
   этот живёт внутри «корня подложки» — `.kb-stage` с `backdrop-filter`. После
   `finished` (progress 1, computed opacity 1) слой оставался с ранним
   значением: карточка рисовалась призраком в ~20 % яркости, и подписи графа
   читались сквозь неё. Замер: самый яркий пиксель карточки 79,69,74 при
   заголовке #f6efe6 на фоне #2e1e29. Подъём один, без прозрачности, — и
   карточка рисуется своим цветом. */
@keyframes kb-spot-in { from { transform: translateY(8px); } to { transform: none; } }

.kb-spot-card { animation: kb-spot-in 200ms ease-out both; }

/* Редактор разворачивается 240 мс — сверху вниз, из-под кнопки, которой его
   открыли. `hidden` меняет display, и браузер проигрывает кадр заново. */
.kb-editor { animation: kb-unfold 240ms var(--ease) both; }
.kb-how[open] .kb-how-body { animation: kb-unfold 240ms var(--ease) both; }
.kb-draftcall { animation: kb-rise 260ms ease-out both; }

.kb-tile, .kb-card, .kb-legend { transition: transform 200ms var(--ease); }
.kb-tile:hover, .kb-legend:hover { transform: translateY(-2px); }
.kb-card:hover:not(.is-on) { transform: translateY(-2px); }

/* Кнопки и переключатели. */
.kb-tool, .kb-mchip, .kb-sw, .kb-vrow, .kb-card {
  transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease, transform 180ms var(--ease);
}
.kb-tool:active, .kb-mchip:active { transform: scale(0.97); }
.kb-sw::after { transition: left 180ms var(--ease), right 180ms var(--ease); }

/* Шторка настроек выезжает справа. */
.kb-drawer { animation: kb-drawer-in 260ms var(--ease) both; }

/* ------------------------------------------------- РАЗДЕЛ: херо и веер */
.kb-orb { animation: kb-float 6s ease-in-out infinite; }

.kb-orb .n-gloss {
  transform-box: view-box;
  transform-origin: 75px 75px;
  animation: kb-spin 20s linear infinite;
}

.kb-orb .orb-sat {
  transform-box: view-box;
  transform-origin: 75px 75px;
  animation: kb-spin 16s linear infinite;
}

/* 🔴 Веер прорисовывается stagger'ом — то, чего не удалось сделать в макете.
   Там любая анимация на путях внутри 3D-плиты (`.kb-plate-in` с rotateX)
   заставляла Chromium не рисовать кривые вовсе (разбор — `analysis.html`
   §6д). Плиты в пульте нет, индексы `--i` генератор ставит на каждую
   кривую — значит прорисовка работает штатно: 70 мс на линию. */
.fan-line {
  stroke-dasharray: 200;
  --len: 200;
  animation: kb-draw 260ms ease-out both;
  animation-delay: calc(var(--i, 0) * 70ms);
}

/* Список и гистограмма — каскадом; строка отзывается на наведение. */
.kb-vrow { animation: kb-rise 320ms ease-out both; animation-delay: calc(var(--i, 0) * 55ms); }
.kb-vrow:hover { transform: translateX(3px); }

.hb {
  transform-box: fill-box;
  transform-origin: bottom;
  animation: kb-grow 480ms var(--ease) both;
  animation-delay: calc(240ms + var(--i, 0) * 45ms);
}

.kb-detail { animation: kb-rise 420ms ease-out both; animation-delay: 120ms; }
.kb-card { animation: kb-rise 340ms ease-out both; animation-delay: calc(var(--i, 0) * 60ms); }
/* Разбор поля в правой колонке редактора — тем же каскадом, что список. */
.kb-heads .hrow { animation: kb-rise 300ms ease-out both; animation-delay: calc(var(--i, 0) * 40ms); }

/* --------------------------------------------------------------- ПОКОЙ */
@media (prefers-reduced-motion: reduce) {
  .n-sec .n-body,
  .n-sec .n-gloss,
  .n-root-body,
  .n-root-orbit,
  .n-focus-ring,
  .g-ray,
  .tl-pill,
  .tl-day--on,
  .kb-lens-svg,
  .lens-ring,
  .orb-ring,
  .kb-tile,
  .gg-fill,
  .kb-tile .ovspark-line,
  .kb-tile .ovspark-dot,
  .dotnum--wave circle,
  .kb-drawer,
  .kb-orb,
  .kb-orb .n-gloss,
  .kb-orb .orb-sat,
  .fan-line,
  .kb-vrow,
  .hb,
  .kb-detail,
  .kb-card,
  .kb-heads .hrow,
  .kb-spot-card,
  .kb-editor,
  .kb-how[open] .kb-how-body,
  .kb-draftcall {
    animation: none !important;
    transition: none !important;
    stroke-dashoffset: 0 !important;
    opacity: 1;
    transform: none !important;
  }

  .kb-tile::after, .kb-card::after, .kb-legend::after,
  button.kb-edit-main::after { display: none; }
  /* Приглушение по фильтру, дню и состоянию — это СМЫСЛ, а не движение: оно
     остаётся. 🔴 Порядок тот же, что в основной части файла: у выключенного
     черновика оба класса сразу, вес селекторов равный, и «выключен» обязан
     побеждать — иначе раздел, которого нет в промпте, выглядит как обычный. */
  .n-sec.is-mute, .g-ray.is-mute, .g-sub.is-mute { opacity: 0.18; }
  .n-sec.is-draft .n-body { opacity: 0.72; }
  .n-sec.is-off .n-body { opacity: 0.4; }
}
