/* Ключи Мастерства · LMS — дизайн-система
   Снята с действующего сайта. Холодный светлый фон, тёплые оранжевые акценты.
   Шрифты темы BuddyBoss: Open Sans (текст) + Roboto Condensed (заголовки).
   Кнопки НЕ таблетки — радиус 10px, как у полей и плашек. */

/* Атрибут hidden обязан побеждать display компонентов.
   Почти каждый класс здесь задаёт display: flex или block, и он перебивает
   правило браузера для [hidden] — спрятанный элемент остаётся на экране.
   Ловится это только глазами: разметка выглядит правильной. */
[hidden] {
  display: none !important;
}

:root {
  /* Цвет */
  --km-accent: #E8703A;
  --km-accent-light: #EE8B5F;
  --km-peach: #FBE3D5;
  --km-warm-bg: #FDF2EB;
  --km-warm-border: #F3C9AE;
  --km-warm-text: #6B5347;
  --km-warm-text-strong: #8A4A28;

  --km-text: #3F4C5C;
  --km-text-secondary: #77869A;
  --km-text-muted: #9AA7B6;

  --km-page: #F6F8FB;
  --km-surface: #FFFFFF;
  --km-surface-alt: #FAFBFD;
  --km-border: #E7EBF0;
  --km-border-soft: #F1F4F8;
  --km-track: #EDF1F6;

  /* Служебное — то, что видит ведущий и не видит курсант.
     Холодный серо-синий: он не спорит с тёплыми акцентами занятия
     и сразу читается как «это не часть материала».
     Числа из макета `design/conditional-staff-note-v1.html`. */
  --km-staff: #5B7089;
  --km-staff-bg: #EEF2F7;
  --km-staff-border: #D6DEE8;

  /* Жёлтая плашка — «не сломалось, а не дописано».
     Заведена 27 августа 2026 под строку условного блока «блок не покажется
     никому». Отдельно от `--km-danger`: красный говорит «сломалось», и там,
     где человек просто не заполнил поле, он врёт про причину.
     Числа из макетов `conditional-block-v1.html` и `lesson-blocks-v2.html`. */
  --km-warn-bg: #FEF8E7;
  --km-warn-border: #EFD9A0;
  --km-warn-text: #8A6A16;

  /* Фирменные цвета курса — тёмно-синий заголовка и бирюзовый.
     Сняты 6 сентября 2026 с продающей страницы курса вычисленными
     значениями (`getComputedStyle`), а не подобраны на глаз:
     promo.kluchimasterstva.ru/probuzhdenie-chakr-sentjabr-2026,
     заголовок там разбит на две части ровно так же.

     Токенами, а не по месту: цвет спрашивают заголовок, надзаголовок,
     подпись и имя ведущей, и второе написание разошлось бы молча. */
  --km-brand-navy: #33315E;
  --km-brand-teal: #237C82;

  --km-danger: #B4432B;
  /* Красный «идёт эфир» — свой, а не danger: тревоги тут нет, есть «сейчас». */
  --km-live: #D33A3A;
  --km-danger-bg: #FBE0DA;
  --km-danger-border: #F0BDB0;

  /* Типографика */
  --km-font: 'Open Sans', system-ui, sans-serif;
  --km-font-display: 'Roboto Condensed', 'Open Sans', sans-serif;
  --km-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Форма */
  --km-ok: #2E7D5B;
  /* Жёлтый «скоро понадобится» — между зелёным «ничего не надо»
     и красным «надо сейчас». До таблицы зачислений его в системе
     не было: цветов было два, а срочностей три. */
  --km-warn: #C77700;

  /*
   * Отступ под подписью блока: подпись прижата к тому, что под ней,
   * а не стоит посередине между верхним и нижним.
   *
   * Числом, а не литералом в двух местах: так стоит подпись на карточке
   * и так же должна стоять подпись блока на странице — человек видит
   * эти два места рядом на одном экране, и разъехаться им негде.
   */
  --km-gap-label: 10px;

  /*
   * Расстояние МЕЖДУ группами: между тем, что подписью друг другу
   * не является. Внутри группы — `--km-gap-label`.
   *
   * Число не новое: это то же шестнадцать, которым `km-stack` разносит
   * блоки по умолчанию. Оно и стояло литералом в трёх местах — здесь
   * получило имя, и литералов не осталось: `.km-stack` и отступ вкладок
   * читают отсюда. Одно число, одно написание.
   *
   * Заведено 3 сентября 2026 по карточке кабинета: там все пять
   * промежутков стояли на `--km-gap-label`, то есть групп не было вовсе,
   * и срок читался второй подписью к полосе прогресса — он стоял к ней
   * на 11 пикселей при 12 у самой подписи.
   */
  --km-gap-group: 16px;

  --km-radius-card: 16px;
  --km-radius-control: 10px;
  --km-radius-badge: 6px;
  --km-shadow-card: 0 2px 12px rgba(63, 76, 92, 0.06);
  --km-shadow-lift: 0 6px 18px rgba(63, 76, 92, 0.14);

  /* Сколько сверху занимает чужая обвязка: липкая шапка темы, панель
     администратора, промо-полоса Elementor. Считает `assets/js/chrome.js`
     живым замером — числом это записать нельзя, потому что слагаемых три
     и каждое приходит и уходит само: панель видна администратору,
     промо-полоса закрывается крестиком, шапка есть всегда.

     Ноль по умолчанию — значение для стенда и для страницы без темы.
     Ошибка выбрана в заметную сторону: при нуле липкая колонка уезжает
     под шапку, и это видно; при завышенном числе над колонкой пустая
     полоса, и это читается как «так задумано». */
  --km-chrome-top: 0px;

  /* Размер знака узла — ОДНО ЧИСЛО на дерево и на перечни.
     Дерево переопределяет его по уровням (топик мельче, колонка плеера
     мельче обоих), перечень берёт как есть. Написанное дважды, оно
     разъехалось бы на первой же правке лестницы — и разъехалось бы
     незаметно, на несколько пикселей. */
  --km-glyph: 19px;
}

/* ---------------------------------------------------------------------------
   Чужая обвязка: единственное место, где дизайн-система правит не своё
   ---------------------------------------------------------------------------

   `.site` — контейнер темы BuddyBoss, и у него `overflow: hidden auto`.
   Горизонтальная половина там осмысленна: тема гасит боковую прокрутку
   от своих виджетов. Вертикальная — довесок сокращённой записи, и она
   **отменяет липкость всего, что лежит внутри**: `position: sticky` считает
   от ближайшего предка с прокруткой, а `.site` сам не прокручивается —
   прокручивается окно. Прилипать не к чему, и элемент просто стоит
   на месте.

   Так и вышло: колонка навигации плеера и боковая колонка страницы курса
   объявлены липкими с 0.72.0 и 0.73.0 — и ни одна не липла на боевом.
   Обе проверялись стендом, а `.site` там нет вовсе: стенд собран одним
   файлом без темы. Ровно тот класс, что записан в `CLAUDE.md` про экран
   и стенд — каждая часть по отдельности права, а видно это только
   на собранной странице.

   `clip` вместо `hidden` оставляет обрезку и **не заводит прокрутки**:
   для человека поведение то же, для липкости — разница между «работает»
   и «не работает». Проверено живым замером на боевом: колонка едет вдоль
   страницы, горизонтальной прокрутки не появилось.

   **Селектор подобран под чужой, а не взят с потолка.** Тема пишет так:

       body:not(.page-template-page-fullscreen, .elementor-page) .site
       { overflow-x: hidden }

   Это (0,2,1) — простой `.site` слабее и молча проиграл бы. Наш селектор
   той же силы и стоит позже: `design-system.css` подключается при отрисовке
   шорткода, то есть печатается в подвале, после всех стилей темы (проверено
   на боевом — 197-й из 200). Понадобится усилить — усиливать надо зная,
   с чем меряемся, поэтому чужое правило выписано здесь целиком.

   `:has()` держит охват на наших экранах: правило нужно там, где стоит
   липкая колонка, а не на всех страницах сайта. Двух корней хватает
   и они устойчивы — `.km-page` у всех наших экранов, `.km-player`
   у плеера, где `.km-page` нет по устройству.

   Найдено живым замером на боевом: до правки колонка при прокрутке
   не сдвигалась ни на пиксель, после — едет вдоль страницы, и
   горизонтальной прокрутки не появилось. */
body .site:has(.km-page, .km-player) {
  overflow-x: clip;
  overflow-y: visible;
}

/* Основа */
body {
  margin: 0;
  background: var(--km-page);
  font-family: var(--km-font);
  font-size: 16px;
  line-height: 1.65;
  color: var(--km-text);
  -webkit-font-smoothing: antialiased;
}
* { box-sizing: border-box; }
a { color: var(--km-accent); text-decoration: none; }
a:hover { color: var(--km-accent-light); }

/* Типографика: H1 36 / H2 32 / H3 26 / H4 24 / H5 20 — по кастомайзеру темы */
.km-h1, .km-h2, .km-h3, .km-h4, .km-h5 {
  font-family: var(--km-font-display);
  font-weight: 700;
  margin: 0;
}
.km-h1 { font-size: 36px; line-height: 1.2; }
.km-h2 { font-size: 32px; line-height: 1.25; }
.km-h3 { font-size: 26px; line-height: 1.3; }
.km-h4 { font-size: 24px; line-height: 1.35; }
.km-h5 { font-size: 20px; line-height: 1.4; }

.km-text { font-size: 16px; line-height: 1.65; }
.km-text-secondary { font-size: 14px; line-height: 1.6; color: var(--km-text-secondary); }
.km-meta { font-size: 12px; line-height: 1.4; color: var(--km-text-muted); }

/* Надзаголовок раздела — заглавными, заметная черта сайта */
.km-eyebrow {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--km-text-muted);
}
.km-section-title {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--km-text-muted);
}

/* Кнопки */
.km-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  border: none;
  border-radius: var(--km-radius-control);
  padding: 13px 26px;
  cursor: pointer;
  transition: background 0.16s, border-color 0.16s, color 0.16s;
}
.km-btn--primary { background: var(--km-accent); color: #fff; }
.km-btn--primary:hover { background: var(--km-accent-light); }.km-btn--secondary,
.km-btn--secondary:focus{
  background: var(--km-surface);
  color: var(--km-text);
  border: 1px solid var(--km-border);
  padding: 12px 25px;
}
.km-btn--secondary:hover { border-color: var(--km-warm-border); color: var(--km-accent); }

.km-btn--muted { background: var(--km-peach); color: var(--km-warm-text-strong); }
.km-btn--muted:hover { background: #F7D6C3; }.km-btn--text,
.km-btn--text:focus{ background: transparent; color: var(--km-accent); padding: 12px 14px; }
.km-btn--text:hover { color: var(--km-accent-light); }

/* Неяркое второстепенное действие: «Отметить пройденным» */
.km-btn--quiet,
.km-btn--quiet:focus {
  background: transparent;
  color: var(--km-text-secondary);
  border: 1px solid var(--km-border);
  font-weight: 500;
  font-size: 14px;
  padding: 12px 22px;
}
.km-btn--quiet:hover { color: var(--km-accent); border-color: var(--km-warm-border); }

.km-btn--danger {
  background: var(--km-surface);
  color: var(--km-danger);
  border: 1px solid var(--km-danger-border);
  font-weight: 500;
}
.km-btn--danger:hover { background: #FDF0EC; }

.km-btn--sm { font-size: 13px; padding: 9px 18px; }
.km-btn--lg { font-size: 16px; padding: 15px 32px; border-radius: 12px; }
.km-btn--block { width: 100%; }
.km-btn[disabled] { background: #F1F4F8; color: #B6C0CC; cursor: not-allowed; }

/* Бейджи */
.km-badge {
  display: inline-block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: var(--km-radius-badge);
  padding: 5px 9px;
  white-space: nowrap;
}
.km-badge--accent { background: var(--km-accent); color: #fff; }
.km-badge--peach { background: var(--km-peach); color: #A8592F; }
.km-badge--neutral { background: #F1F4F8; color: var(--km-text-secondary); }
.km-badge--outline {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  color: var(--km-text-muted);
  padding: 4px 8px;
}
.km-badge--danger { background: var(--km-danger-bg); color: var(--km-danger); }

/* Крупный бейдж — шапка курса. Отдельный размер, а не «тот же, что в списке»:
   в макете он 11px с разрядкой 0.06em, потому что читается издали и стоит
   один-два, а не двадцать в строке таблицы. */
.km-badge--lg {
	font-size: 11px;
	letter-spacing: 0.06em;
	border-radius: 8px;
	padding: 6px 12px;
}

/* Поверх обложки бейджи полупрозрачны: сплошная заливка на светлой картинке
   выглядит наклейкой, а сквозь неё видно, что под ней фотография.

   ЗДЕСЬ СТОЯЛ ОДИН ЭТОТ КОММЕНТАРИЙ, А ПРАВИЛА ПОД НИМ НЕ БЫЛО. Обещание
   без кода: бейдж носил `km-badge--peach` — светлую заливку, — и на светлой
   обложке не читался вовсе. Найдено 3 сентября 2026 по слову человека.

   Подложка взята у бейджей шапки гостевой (`course-page-guest-v2.html`):
   белая с прозрачностью 0,9 и тёплый тёмный текст. Она перебивает
   `--peach` и любой другой вид: поверх картинки решает место, а не вид,
   и это единственное правило, у которого есть право так делать. */

/* ТЕКСТ ГЛАВНОЙ КНОПКИ ЗАДАЁТСЯ ЯВНО во всех состояниях.
 *
 * Шапка с наложением красит всё внутри белым, а тема добавляет своё
 * правило на нажатую ссылку — и на активной кнопке надпись пропадала:
 * белый по оранжевому превращался в оранжевый по оранжевому. Наследование
 * здесь не годится: цвет кнопки не зависит от того, на каком фоне
 * она стоит. */
.km-btn--primary,
.km-btn--primary:link,
.km-btn--primary:visited,
.km-btn--primary:hover,
.km-btn--primary:focus,
.km-btn--primary:active { color: #fff; }

/* Кнопка ПЕРЕД ценой — так в макете гостя. В липкой полосе порядок
   обратный, и это тоже макет: там цена читается первой, а кнопка стоит
   у самого края. Разметка одна, порядок решает место. */
.km-hero__body .km-actions { flex-direction: row-reverse; justify-content: flex-end; gap: 18px; }

/* ЦЕНА И СЧЁТ КУРСАНТОВ В ШАПКЕ — СВОИМИ ЦВЕТАМИ И РАЗМЕРАМИ.
 *
 * Общие правила писаны под светлый фон: цена тёмная, «от» и счёт серые,
 * подпись мелкая. Поверх обложки это нечитаемо — на снимке боевой цена
 * тонула в картинке, а счёт был почти невидим.
 *
 * Наследованием это не лечится: у цены свой цвет по дизайн-системе,
 * и он верен там, где она стоит на белом. Поэтому правила именно
 * для шапки с наложением, а не правка общих. */
/* Высокая шапка — только продающая страница: там она первое, что видит
   человек, и в неё вложены бейджи, подпись, цена с кнопкой и курсанты.
   У участника шапка это заголовок над рабочим экраном, и 460 отнимали
   у него пол-экрана ни за чем. */
.km-hero--tall .km-hero__body { min-height: 500px; }

.km-hero--over .km-price { color: #fff; font-size: 17px; }
.km-hero--over .km-price__from { color: rgba(255, 255, 255, 0.85); font-size: 15px; }
/* Число платежей поверх обложки: белым, как сама цена, а не приглушённым
   серым. Затемнение обложки к низу почти сходит на нет (0,05 у края),
   и на светлой картинке `#9AA7B6` пропадал — измерено на боевом.

   Значения не новые: цвет от `.km-hero--over .km-price` строкой выше,
   кегль от `.km-price__from` — подпись и подпись. Непрозрачным, в отличие
   от «от»: «от» уточняет, а «× 3» меняет смысл суммы втрое. */
.km-hero--over .km-price__stages { color: #fff; font-size: 15px; }
.km-hero--over .km-meta { color: rgba(255, 255, 255, 0.9); font-size: 14px; }
.km-hero--over .km-hero__lead { color: rgba(255, 255, 255, 0.9); font-size: 16px; max-width: 560px; }

/* Строка курсантов: кружки и подпись рядом, как в макете. */
.km-hero__learners { display: flex; align-items: center; gap: 12px; margin-top: 8px; }

.km-hero--over .km-badge--peach { background: rgba(251, 227, 213, 0.95); color: var(--km-warm-text-strong); }
.km-hero--over .km-badge--accent { background: rgba(232, 112, 58, 0.95); }

/* Крошки: где человек находится. Разделитель — свой элемент, а не
   `::before` у соседа: он есть в макете подписью, и текст экрана должен
   читаться целиком, а не собираться из содержимого псевдоэлементов. */
.km-crumbs {
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: 13px;
	color: var(--km-text-muted);
}

/* Крошки не переносятся, а последняя обрезается многоточием.
 *
 * С переносом длинное название уезжало на вторую строку, а разделитель
 * оставался висеть в конце первой — «Мои курсы ·» и пустота. Ровно та же
 * висящая точка, что у надстрочника модуля на 375, и лечится тем же:
 * строка обязана остаться одной.
 *
 * Обрезать название здесь не потеря: оно стоит целиком строкой ниже,
 * в шапке, крупным. */
.km-crumbs > * { flex: none; }

/* Ссылка в крошках цвета самих крошек, а не общего для ссылок оранжевого.
   Так во всех трёх макетах, где крошки нарисованы: «Мои курсы» серым,
   текущее место тёмным. Оранжевая ссылка в этой строке — единственное
   яркое пятно над обложкой, и глаз идёт к ней вместо названия курса.
   Наведение возвращает акцент: кликабельность остаётся, но по требованию. */
.km-crumbs a { color: inherit; }
.km-crumbs a:hover { color: var(--km-accent); }

.km-crumbs__here {
	flex: 1 1 auto;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	color: var(--km-text);
}

/* Бейдж поверх обложки — левый верхний угол, отступ 10px */
.km-cover { position: relative; overflow: hidden; border-radius: var(--km-radius-card); }
.km-cover__badge {
  position: absolute;
  top: 10px;
  left: 10px;
  pointer-events: none;
  /*
   * ПОДЛОЖКА ЖИВЁТ ЗДЕСЬ, а не вторым определением рядом с комментарием
   * про полупрозрачность: у имени в CSS два определения не спорят,
   * а складываются, и через сессию их читают как один вид. Признак
   * записан в `CLAUDE.md` пятым случаем общего пространства имён.
   *
   * Перебивает `--peach` и любой другой вид намеренно: поверх картинки
   * решает место, а не вид.
   */
  background: rgba(255, 255, 255, 0.9);
  color: var(--km-warm-text-strong);
}
.km-cover__badge--right { left: auto; right: 10px; }

/* Плашки */
.km-note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border-radius: var(--km-radius-control);
  padding: 12px 14px;
  font-size: 14px;
  line-height: 1.55;
}
.km-note--warm {
  background: var(--km-warm-bg);
  border: 1px solid var(--km-warm-border);
  color: var(--km-warm-text);
}
.km-note--neutral {
  background: var(--km-page);
  border: 1px solid var(--km-border);
  color: var(--km-text-secondary);
}
.km-note--danger {
  background: #FDF0EC;
  border: 1px solid var(--km-danger-border);
  color: #8E3B27;
}
.km-note__icon { flex: none; margin-top: 1px; }

/* Знак цветом акцента. Плашка красит текст в свой приглушённый цвет,
   и знак, наследуя его, теряется на тёплом фоне — видно только глазами. */
.km-note__icon--accent { color: var(--km-accent); }
/* Текст плашки — **одним блоком**, а не россыпью детей.

   `.km-note` это флекс-строка (значок слева, текст справа), и всякий прямой
   ребёнок становится в ней отдельной колонкой. Плашка со ссылкой внутри
   фразы разъезжалась на три колонки: «…Они в разделе», «Мои работы», «.» —
   ссылка вставала у правого края, точка ещё правее. Разметка при этом
   правильная целиком, и ловится это только глазами.

   Отсюда правило: есть в плашке что-то, кроме голого текста, — текст
   заворачивается в `.km-note__text`. */
.km-note__text { flex: 1; }

/* Плашка с действием справа: знак, текст, кнопка.
   Выравнивание по центру, а не по верху: кнопка выше строки текста,
   и при `flex-start` она свисала бы вниз. Кнопка не сжимается —
   иначе на узком экране надпись переносится по буквам.

   Строка переносится (`flex-wrap`), и это не запас на будущее: надпись
   на кнопке — название группы сообщества, а они длинные. «Практикум
   по Местам Вознесения 2026» даёт кнопку в 302 пикселя, и на 375 она
   вылезала за экран на сотню. Нашёл стенд, мерка «ничего не вылезает
   за ширину». */
.km-note--action { align-items: center; flex-wrap: wrap; }
.km-note--action > .km-btn { flex: none; margin-left: auto; }
@media (max-width: 640px) {
  /* На узком кнопка уходит на свою строку целиком: прижатая вправо,
     она читалась бы как продолжение текста. */
  .km-note--action > .km-btn { width: 100%; margin-left: 0; text-align: center; }
}

/* Карточки */
.km-card {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
}
.km-card--warm { border-color: var(--km-warm-border); }

/* Приглушённая карточка: тёплая подложка, тёплая рамка, без тени. Секция
   «Что изменится» в макете `course-page-guest-v2.html`.

   Отдельно от `--warm`, у которого меняется только рамка: там карточка
   остаётся белой и выделяется среди белых, здесь — целая секция другого
   тона, и выделять внутри неё нечего. */
.km-card--quiet {
  background: var(--km-warm-bg);
  border-color: var(--km-warm-border);
  box-shadow: none;
}
.km-card--current { border-color: var(--km-warm-border); }
.km-card__body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: var(--km-gap-label); }
.km-card__pad { padding: 24px; }

.km-course-card { display: flex; flex-direction: column; overflow: hidden; }
/*
 * ОБЛОЖКА ДЕРЖИТ ПРОПОРЦИЮ, А НЕ РАСТЁТ ПОД КАРТИНКУ.
 *
 * Найдено 3 сентября 2026 на боевых данных: у двух карточек из
 * одиннадцати обложка выходила 240 px вместо 202 при ОДИНАКОВОЙ ширине
 * карточек. Развалившимися выглядели колонки, а колонки были ровные —
 * разброс ширин ноль на всех проверенных ширинах окна.
 *
 * Причина: обложка — элемент внутри колонки (`.km-course-card` это flex),
 * и `min-height` у неё `auto`, то есть «не меньше содержимого».
 * Содержимое — картинка, и её собственная высота при ширине колонки
 * больше, чем даёт пропорция, когда картинка выше 16:9. Тогда `auto`
 * побеждает `aspect-ratio`, и обложка растёт.
 *
 * Задевало ровно тех, у кого картинка выше 16:9: «Техники трансформации»
 * 1521×1014 и «Самоценность» 1920×1280 — обе 3:2. Остальные девять
 * от 1,78 и шире, и у них 202.
 *
 * Проверено опытом на живых данных: с `min-height: 0` все одиннадцать
 * дают 202.
 */
.km-course-card__cover { position: relative; aspect-ratio: 16 / 9; min-height: 0; }
.km-course-card__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* НАЗВАНИЕ НА ОБЛОЖКЕ КАРТОЧКИ — КАК В ШАПКЕ КУРСА.
   Правка Алены 6 сентября 2026, вторая редакция по образцу.

   Первая ставила название внизу, одной строкой и с затемнением во всю
   обложку. Образец другой: наверху под бейджем, двумя строками и двумя
   цветами, а текст лежит на светлой части картинки — ровно как в шапке.

   Кегль 22 — подобран под ширину карточки 359. При 26 «Практикум по Местам
   Вознесения» уходил в четыре строки, при 18 название переставало быть
   главным на карточке.

   Цвета — те же токены, что у шапки: второго написания не заводится. */
.km-cover__title {
	position: absolute;
	left: 14px;
	right: 14px;
	/* Под бейджем: он стоит на 10 сверху и высотой 27. Просвет 21 —
	   правка 6 сентября 2026, название просили опустить. */
	top: 58px;
	margin: 0;
	font-family: var(--km-font-display);
	font-size: 26px;
	font-weight: 700;
	line-height: 1.02;
	letter-spacing: -0.01em;
	text-transform: uppercase;
	z-index: 1;
}

/* Две строки: первое слово одной, остальные другой — как в шапке. */
.km-cover__title-lead,
.km-cover__title-rest { display: block; }

/* Длинные названия обрезаются, а не выталкивают карточку. */
.km-cover__title-lead { overflow-wrap: anywhere; }

.km-cover__title-rest {
	/* Просвет между первой частью и второй — отступом, а не межстрочным:
	   межстрочный раздвинул бы и строки ВНУТРИ второй части, а она бывает
	   двухстрочной («по Местам Вознесения»). Правка Алены 6 сентября 2026. */
	margin-top: 4px;
	display: -webkit-box;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
	overflow: hidden;
}

/* Тёмное название на светлой обложке — затемнения нет вовсе, как в шапке. */
.km-cover--ink .km-cover__title-lead { color: var(--km-brand-navy); }
.km-cover--ink .km-cover__title-rest { color: var(--km-brand-teal); }

/* Светлое — на тёмной обложке. Затемнение МЯГКИМ ПЯТНОМ под текстом,
   а не полосой поперёк всей обложки. Решение человека 6 сентября 2026.

   Полоса во всю ширину съедала картинку: в карточке обложка и так теряет
   бока — коробка 16:9 режет широкой по 122–550 пикселей, — и затемнять
   сверху донизу значило отнимать у неё второй раз.

   Пятно радиальное и смещено к левому верху, где стоят бейдж и название;
   к низу и вправо оно сходит на нет, и сюжет остаётся виден. */
.km-cover--lume .km-cover__title-lead,
.km-cover--lume .km-cover__title-rest { color: #fff; }

.km-cover--lume::after {
	content: '';
	position: absolute;
	inset: 0;
	background: radial-gradient(
		125% 78% at 6% 12%,
		rgba(28, 26, 46, 0.74) 0%,
		rgba(28, 26, 46, 0.46) 38%,
		rgba(28, 26, 46, 0.12) 66%,
		rgba(28, 26, 46, 0) 82%
	);
	pointer-events: none;
}

/* Бейдж поверх затемнения и названия. */
.km-cover__badge { z-index: 2; }
.km-course-card__title {
  font-family: var(--km-font-display);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.3;
}

/*
 * КАРТОЧКА ПО СВОЕМУ СОДЕРЖИМОМУ — СЕТКА ЕЁ НЕ РАСТЯГИВАЕТ.
 *
 * Решение человека 3 сентября 2026, и оно ОТМЕНЯЕТ вчерашнее выравнивание
 * по ряду. Низы карточек в ряду не совпадут — это принято.
 *
 * СВОИМ ИМЕНЕМ, а не у `.km-grid-cards` целиком, и это не украшение.
 * Ту же сетку берут ещё двое: каталог и «Варианты участия» гостевой.
 * У вторых прижатие к низу — действующее решение 7.4 (`.km-plan .km-btn
 * { margin-top: auto }`), и оно держится ровно растяжкой: у одной карточки
 * три строки про содержимое, у другой пять. Сними растяжку у всех — и там
 * кнопки встанут лесенкой, то есть починка кабинета сломала бы гостевую.
 *
 * Второе определение `.km-grid-cards` завелось бы и без этого довода:
 * у имени стало бы два смысла, и они смешались бы молча — ровно случай
 * `.km-hint`, записанный в CLAUDE.md.
 *
 * Отсюда снято всё, что под растяжку заводилось:
 *   `align-items: stretch` у сетки — умолчание, поэтому явный `start`;
 *   `flex: 1 1 auto` у тела — растить его больше не нужно и нечем;
 *   `margin-top: auto` у кнопки — свободного места в карточке нет,
 *     `auto` вычислялся бы в ноль, и правило стало бы мёртвым.
 *
 * Разрыв перед кнопкой теперь один и тот же у всех: обычный ритм тела.
 * Ради этого и снимали — растянутая карточка отдавала весь излишек
 * в одно место, и разрыв гулял от карточки к карточке.
 */
.km-grid-cards--own { align-items: start; }

/*
 * ЧИСЛО СТРОК ЗАКРЕПЛЕНО — и это про ДЛИНУ ТЕКСТА, а не про растяжку.
 *
 * ЗАКРЕПЛЕНИЕ ВЫСОТЫ СНЯТО 3 сентября 2026, обрезка осталась. Здесь
 * стояло `height: calc(...)` у обоих — под выравнивание по ряду, — и оно
 * пережило отмену растяжки, хотя заводилось ровно ради неё.
 *
 * ЧТО ОНО ДЕЛАЛО НА ЭКРАНЕ. Коробка на две строки при тексте в одну —
 * это пустая вторая строка, и в зазоре она не видна: зазор честно
 * отвечал 10 при провале в 40. Замер диапазонами по боевым данным,
 * одиннадцать карточек:
 *
 *   пустота в названии   29 у ВОСЬМИ из одиннадцати (текст 23, коробка 52)
 *   пустота в описании   28 у «Диагностики чакр маятником» (17 против 45)
 *   буквы назв.→опис.    14 у трёх двухстрочных, 40 у восьми однострочных
 *   буквы опис.→прогр.   14 у десяти, 37 у «Диагностики»
 *
 * ПОЧЕМУ МЕРИЛОСЬ НЕ ТО. Расстояние между соседними строками — не только
 * их зазор: у элемента с закреплённой высотой пустая строка лежит ВНУТРИ
 * коробки, и `getBoundingClientRect()` про неё молчит. Видно её только
 * диапазоном — `Range.selectNodeContents()`, — и это записанный в проекте
 * класс «текст меряется диапазоном, а не коробкой ячейки».
 *
 * ЧЕМ ЗА ЭТО ПЛАТИМ, названо вслух. Строки ниже названия по ряду теперь
 * разойдутся: у карточки с двухстрочным названием всё под ним стоит
 * на 29 пикселей ниже, чем у соседки с однострочным. Срок на одной линии
 * не встанет. Это принято — растяжка отменена категорически, низы в ряду
 * и так не совпадают, и держать пустоту ради совпадения одной строки
 * дороже, чем совпадение стоит.
 *
 * `line-clamp` остаётся и без высоты: он обрезает длинное. Половина,
 * которая «не поднимает короткое», больше не нужна — поднимать и есть
 * то, ради чего высота снята.
 */
.km-course-card__title,
.km-course-card .km-card__body > .km-text-secondary {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
/*
 * ДВЕ ГРУППЫ ВНУТРИ КАРТОЧКИ, а не пять равных промежутков.
 *
 * Тело карточки разносит своих детей на `--km-gap-label` — на десять,
 * то есть «подпись и то, к чему она относится». Для соседей внутри группы
 * это верно, а между группами читается неправдой: строка «Доступ до…»
 * стояла к полосе прогресса ближе, чем сама полоса к своей подписи —
 * 11 пикселей между буквами против 12, — и человек читал срок второй
 * подписью к прогрессу. Найдено глазами на боевом 3 сентября 2026.
 *
 * Разница в буквах при равных зазорах коробок берётся из выносного
 * просвета: у текстовой строки он есть, у `<progress>` нет вовсе.
 * Поэтому равные десятки на экране равными не выглядят.
 *
 * Группы такие:
 *   название + описание   — про что курс;
 *   строка прогресса + полоса — где человек в нём;
 *   срок                  — до какого числа он открыт;
 *   кнопка.
 *
 * ГРУППА — НАСТОЯЩИЙ УЗЕЛ, а не отступ на строке, и это не украшение.
 *
 * Первая редакция вешала `margin-top` прямо на строку прогресса и на строку
 * срока. Она провалила общую проверку стендов «у абзацев в колонках нет
 * своих полей» — и провалила по делу: та проверка заведена на браузерном
 * поле у `<p>`, которое молча складывается с `gap`, и отличить наше поле
 * от чужого она не может по устройству. Ослабить её значило бы завести
 * второе место, где решают, «какой отступ правильный».
 *
 * Узлом это выражается честнее: группа на экране есть, значит есть
 * и в разметке. Поле стоит на обёртке, а не на абзаце, и проверка
 * молчит не потому, что её обманули, а потому, что беды нет.
 *
 * Отступ ставится СВЕРХУ у каждой группы, а не снизу: последняя группа
 * не должна отодвигать кнопку — до неё ритм остаётся тесным.
 *
 * Считается разностью двух имён, а не числом: `gap` тела уже дал десять,
 * добрать надо остаток. Правка любого из двух не разъедется с этим местом.
 *
 * Группа из одной строки — не странность: «до когда» сегодня одна строка,
 * а завтра к ней встанет вторая, и правило не придётся переписывать.
 */
.km-course-card__group {
  display: flex;
  flex-direction: column;
  gap: var(--km-gap-label);
  margin-top: calc(var(--km-gap-group) - var(--km-gap-label));
}
.km-course-card--archived .km-course-card__cover { filter: grayscale(1); }

/* Цена на карточке каталога (7.3).
   Крупно и шрифтом заголовков: на карточке это второе, что читают после
   названия. «от» — приставка, и она тише самой суммы: главное здесь число,
   а «от» только объясняет, что бывает и дороже. */
.km-price {
  font-family: var(--km-font-display);
  font-size: 20px;
  font-weight: 700;
  line-height: 1.2;
  color: var(--km-text);
}

.km-price__from {
  font-family: var(--km-font);
  font-size: 14px;
  font-weight: 600;
  color: var(--km-text-muted);
}

/* Число платежей у цены: «от 6 200 ₽ × 3». Раздел 7.14, решение Алены
   4 сентября 2026.

   Тем же кеглем и цветом, что «от»: обе подписи говорят про одно — что
   написанное число не вся сумма. Крупнее — начнёт спорить с самой ценой;
   мельче — перестанет читаться там, где нужна больше всего, то есть
   у рассрочки втрое дешевле полной оплаты. */
.km-price__stages {
  font-family: var(--km-font);
  font-size: 14px;
  font-weight: 600;
  color: var(--km-text-secondary);
  /* Отступ от суммы, а не пробел в строке: неразрывный пробел давал
     четыре пикселя, и «×» читался хвостом значка валюты. Ступень
     `--km-gap-label` — ровно про это отношение: подпись и то, к чему
     она относится. */
  margin-left: var(--km-gap-label);
  white-space: nowrap;
}

/* Вкладки.
 *
 * Поле сверху и снизу — правка вёрстки 4. `km-stack` разносит блоки
 * страницы на шестнадцать, а макет страницы курса разносит на двадцать
 * четыре; восемь — разница, и написана она разницей, а не итогом:
 * поменяется ритм стопки — вкладки поедут вместе со всем остальным,
 * а не останутся единственным блоком со своим числом.
 *
 * Поле, а не зазор у соседа: полоса вкладок стоит на трёх экранах,
 * и в каждом соседи свои.
 *
 * **Разница считается, а не записана числом.** Восьмёрка была верна ровно
 * до того дня, когда у страницы курса появился свой ритм: макет разносит
 * её блоки на 24, а стопка по умолчанию на 16. Стой здесь литерал —
 * вкладки получили бы 40 воздуха вместо 24, и заметил бы это глаз,
 * а не проверка. */
.km-tabs {
  display: flex;
  gap: 28px;
  border-bottom: 1px solid var(--km-border);
  margin: calc(24px - var(--km-stack-gap, var(--km-gap-group))) 0;
}
.km-tab {
  padding: 0 0 12px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  cursor: pointer;
  background: none;
  border: none;
  font-family: inherit;
}
/* ЦВЕТ ТЕКСТА — ТО ЖЕ ЧУЖОЕ ПРАВИЛО, ШЕСТОЕ СВОЙСТВО. Задача 9.27.
 *
 *   button:focus, button:hover { color: var(--bb-primary-button-text-hover) }
 *
 * ЗНАЧЕНИЕ РАЗНОЕ В РАЗНЫХ СРЕДАХ, и это довод сильнее любого одного цвета:
 * локально переменная равна `#ffffff`, на боевом — `#000000` (прочитано
 * живым запросом 25 августа 2026). То есть подпись в фокусе локально белела,
 * а на боевом чернела; и там и там — не своим цветом.
 *
 * *Здесь стояло «На боевом это белый». Неправда: цвет мерился локально,
 * а записан был про боевой — ровно та ошибка, от которой проект защищается
 * правилом «проверено на среде — это две разные записи».*
 *
 * Наш `:hover` чужой цвет перебивал, а `:focus` — нет, и вкладка в фокусе
 * БЕЗ наведения теряла свой цвет целиком.
 *
 * Пряталось это за наведением: мышью нажимают туда, куда смотрят, и `:hover`
 * с `:focus` приходят вместе — тогда выигрывает наш `:hover`. Видно дефект
 * ровно тогда, когда указатель ушёл, а фокус остался: после клика мышь
 * сдвинули, и подпись пропала. Измерено, а не выведено.
 *
 * Стоит ДО `:hover` намеренно: у обоих (0,2,0), и решает порядок. Ниже —
 * состояние наведения, ещё ниже выбранная вкладка. */
.km-tab:focus { color: var(--km-text-muted); }
.km-tab:hover { color: var(--km-text); }

/* ЧУЖОЕ УМОЛЧАНИЕ КНОПКИ ГАСИТСЯ В ТЕХ ЖЕ СОСТОЯНИЯХ, В КАКИХ ОНО ОБЪЯВЛЕНО.
 *
 * Найдено человеком на боевом 19 августа: при наведении на вкладку под ней
 * появлялась белая скруглённая плашка шире самой вкладки. Нашего `:hover`
 * в этом нет ни строчки — он меняет только цвет.
 *
 * Даёт её тема BuddyBoss, и механизм стоит записать целиком, потому что
 * он повторится:
 *
 *   button:hover, button:focus { background-color: #FFFFFF }   (0,1,1)
 *   .km-tab                    { background: none }            (0,1,0)
 *
 * Мы объявили фон в БАЗОВОМ состоянии, тема — в СОСТОЯНИИ НАВЕДЕНИЯ,
 * и её правило специфичнее нашего. Порядок подключения здесь ни при чём:
 * наш файл печатается позже, и это не помогает — сравниваются классы.
 *
 * Радиус приезжает оттуда же и другим путём: `button { border-radius:
 * var(--bb-button-radius) }` — на боевом это **100px**, — а `.km-tab`
 * радиуса не объявлял вовсе. Пока фон прозрачен, скругления не видно;
 * появился фон — появилась капсула.
 *
 * Прецедент в проекте уже есть и записан рядом: `.km-btn--primary` пришлось
 * задать цвет во всех состояниях, потому что тема красит нажатую ссылку.
 * Правило общее: **чужое умолчание гасится в том состоянии, в котором оно
 * объявлено**, а не в базовом.
 *
 * Стендом это НЕ ЛОВИТСЯ САМО СОБОЙ: стенд собран одним файлом, темы в нём
 * нет, и проверка была бы зелена при любом сломе. Поэтому общая мерка
 * подмешивает то самое правило селектором и специфичностью — и меряет
 * `:focus`, который тема объявляет тем же правилом и который можно вызвать
 * из скрипта. */
.km-tab,
.km-tab:hover,
.km-tab:focus,
.km-tab:active {
  background: none;
  border: none;
  border-radius: 0;
}

/* ТЕНЬ ФОКУСА — ЧЕТВЁРТОЕ СВОЙСТВО ТОГО ЖЕ ЧУЖОГО ПРАВИЛА. Задача 9.27.
 *
 * Найдено человеком на боевом: при нажатии мышью вкладка обводится серым.
 * Правило выше гасило у того же механизма ТРИ свойства — фон, рамку
 * и радиус, — и этого оказалось мало: тема объявляет ещё и тень.
 *
 *   button:focus { box-shadow: var(--bb-primary-button-focus-shadow) }  (0,1,1)
 *   .km-tab:focus { box-shadow: none }                                  (0,2,0)
 *
 * Значение переменной прочитано живым запросом к боевому 25 августа 2026
 * и совпало с локальным побайтно:
 *
 *   0 0 1px 2px rgba(0,0,0,.05), inset 0 0 0 2px rgba(0,0,0,.08)
 *
 * Это серый ободок снаружи плюс серый ободок внутри — та самая рамка.
 *
 * ЭТО ТЕНЬ, А НЕ `outline`, и в этом вся тонкость. Сам `outline` тема гасит
 * нулём в тех же состояниях (`button, button:focus, button:hover,
 * button:active { outline: 0 }`), поэтому браузерного кольца нет вовсе:
 * до этой правки человек, идущий табом, не видел **ничего** — ни нашего,
 * ни чужого, ни своего браузерного.
 *
 * ОБЪЯВЛЕНО У ТЕМЫ НА `:focus`, ЗНАЧИТ И ГАСИТСЯ НА `:focus`. Перенеси
 * гашение на `:focus-visible` — и тень вернётся от мыши, а выглядеть
 * написанное будет аккуратнее прежнего. Отдельная проверка держит именно
 * это, а не только наличие строки.
 *
 * Список — все наши `<button>`. Они страдают одинаково, потому что чужое
 * правило смотрит на имя элемента, а не на наш класс; чинить их по одному
 * значит гарантировать следующий такой же разговор. */
.km-tab:focus,
.km-btn:focus,
.km-chip:focus,
.km-act:focus,
.km-link:focus { box-shadow: none; }

/* Признак фокуса С КЛАВИАТУРЫ — свой, потому что чужого не осталось.
 *
 * `:focus-visible`, а не `:focus`: мышью нажимают в то место, куда смотрят,
 * и обводка там лишняя; идущий табом без неё не знает, где он.
 *
 * `.km-link` и `.km-act` сюда НЕ входят намеренно — у них свой
 * `:focus-visible` уже объявлен, и второе определение того же селектора
 * означало бы два места, где живёт один признак. */
.km-tab:focus-visible,
.km-btn:focus-visible,
.km-chip:focus-visible {
  outline: 2px solid var(--km-accent);
  outline-offset: 2px;
}

/* Те же двое, найденные поиском по той же причине: фон объявлен только
   в базовом состоянии, и на наведении тема подкладывает белое. У них это
   заметно меньше — рамка и радиус свои, — но неверно так же. */
.km-btn--text,
.km-btn--text:hover,
.km-btn--text:focus,
.km-btn--text:active { background: transparent; }

.km-btn--quiet,
.km-btn--quiet:hover,
.km-btn--quiet:focus,
.km-btn--quiet:active { background: transparent; }

/* Выбранная вкладка отличается ЦВЕТОМ, и только им. Правка вёрстки 3,
 * решение человека 8 августа 2026.
 *
 * Подчёркивания под активной вкладкой в `course-page-member.html` нет.
 * Расхождение с `design/library.html` при этом настоящее и названо вслух:
 * там, в библиотеке дизайн-системы, у активной вкладки нарисована черта
 * `inset 0 -2px 0`, и жила она отсюда. Решение принято по экрану курса —
 * тому, на который смотрят люди, — и распространено на компонент целиком:
 * вкладки стоят ещё в кабинете и на «Моих работах», и два разных признака
 * «где я сейчас» у одного компонента были бы двумя правдами.
 *
 * Что различие осталось, охраняет общая мерка стенда «выбранная вкладка
 * отличается видом от невыбранной»: она спрашивает отличие, а не способ. */
.km-tab.is-active,
.km-tab.is-active:focus { color: var(--km-accent); }

/* Чипы-фильтры */
.km-chips { display: flex; flex-wrap: wrap; gap: 8px; }.km-chip,
.km-chip:focus{
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--km-text-secondary);
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  padding: 8px 17px;
  cursor: pointer;
}
.km-chip:hover { color: var(--km-accent); border-color: var(--km-warm-border); }
.km-chip.is-active {
  background: var(--km-peach);
  border-color: transparent;
  color: #A8592F;
  padding: 9px 18px;
}
.km-chip__count { font-weight: 600; opacity: 0.6; }

/* Выбор программы: два экрана, одна карточка.
   Макет `design/program-picker-v1.html`, принят 23 августа 2026.

   И редактор дерева, и корзина показывали только название — карточка
   не отвечала на вопрос «зачем туда идти». Стало: название, строка чисел,
   статус точкой и стрелка вправо. */
.km-picker__head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.km-picker__search { flex: 1 1 240px; min-width: 0; max-width: 340px; }

.km-picker {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  overflow: hidden;
}

.km-picker__row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--km-border-soft);
  text-decoration: none;
  color: inherit;
}

.km-picker__row:last-child { border-bottom: none; }
.km-picker__row:hover { background: var(--km-bg); }

/* Пустое приглушено целиком: ни дерева, ни корзины там нет, и звать
   туда взглядом незачем. Пустая корзина вдобавок не ссылка — заходить
   в неё не за чем; пустое дерево ссылкой остаётся, за этим туда и идут. */
.km-picker__row--empty { opacity: 0.62; }
.km-picker__row--empty:hover { background: transparent; }
a.km-picker__row--empty:hover { background: var(--km-bg); }

.km-picker__body { flex: 1; min-width: 0; }

.km-picker__name {
  display: block;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
}

.km-picker__facts {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  color: var(--km-text-secondary);
  line-height: 1.5;
}

.km-picker__muted { color: var(--km-text-muted); }

/* Статус точкой и словом. Прежде здесь стояла серая плашка капсом:
   она кричала громче названия и говорила то же, что говорит цвет. */
.km-picker__state {
  flex: none;
  font-size: 12px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.km-picker__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: currentColor;
}

.km-picker__state--live { color: var(--km-ok); }
.km-picker__state--draft { color: var(--km-text-muted); }

/* Стрелка: видно, что строка ведёт дальше. Оранжевое название читалось
   как ссылка, но куда — не говорило. */
.km-picker__go {
  flex: none;
  width: 17px;
  height: 17px;
  color: var(--km-text-muted);
}

@media (max-width: 640px) {
  /* Статус уходит под название: в строку с ним и стрелкой он не влезает,
     а перенесённый — рвёт карточку пополам. */
  .km-picker__row { flex-wrap: wrap; }
  .km-picker__body { flex: 1 1 100%; }
  .km-picker__state { order: 3; }
  .km-picker__go { margin-left: auto; order: 4; }
  .km-picker__search { max-width: none; flex: 1 1 100%; }
}

/* Слияние аккаунтов: пара полей и стрелка между ними.
   Макет `design/merge-v1.html`, принят 23 августа 2026.

   Два поля рядом не говорят, откуда куда, а ошибка здесь необратима.
   Сеткой, а не флексом: средняя колонка обязана быть ровно под стрелку,
   а флекс отдал бы ей долю от свободного места и на узком раздавил бы
   поля ради неё. */
.km-pair {
  display: grid;
  grid-template-columns: 1fr 30px 1fr;
  gap: 10px;
  align-items: start;
}

/* Стрелка стоит на уровне поля, а не подписи над ним: `1lh` подписи плюс
   её отступ. Считается это здесь, а не подбирается на глаз, — иначе
   первая же правка кегля подписи сдвинет стрелку молча. */
.km-pair__arrow {
  align-self: start;
  padding-top: calc(1.4em + var(--km-gap-label));
  color: var(--km-text-muted);
}

.km-pair__arrow svg {
  width: 20px;
  height: 20px;
  display: block;
}

/* Выбранный человек — карточкой: имя, почта, номер и крестик.
   Строкой в поле ввода он не помещался, а без почты и номера двух
   однофамильцев не различить. */
.km-picked {
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  padding: 9px 12px;
  background: var(--km-bg);
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.km-picked__body { flex: 1; min-width: 0; }
.km-picked__name { font-weight: 600; }

.km-picked__meta {
  font-size: 12px;
  color: var(--km-text-muted);
  margin-top: 2px;
}

/* Номер аккаунта — моноширинным и мельче почты: его сверяют по знакам,
   а не читают. Своё имя, а не общий `km-num`: тем в системе назван
   крупный показатель с подложкой (см. `km-nums`). */
.km-picked__id {
  font-family: var(--km-mono);
  font-size: 11px;
}

/* Крестик — кнопка, а не значок: он снимает выбор, и нажимать его будут. */
.km-picked__drop {
  flex: none;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  color: var(--km-text-muted);
  background: none;
  border: none;
  padding: 2px;
  cursor: pointer;
}

.km-picked__drop:hover { color: var(--km-accent); }

/* Сводка — в той же карточке, отделена волоском, а не своей секцией. */
.km-sum {
  border-top: 1px solid var(--km-border-soft);
  padding-top: 14px;
}

.km-sum__title {
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  font-weight: 600;
  margin: 0 0 10px;
}

/* Строка сводки: что и сколько. Число справа и не переносится —
   в колонке чисел его и ищут глазами. */
.km-line {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--km-border-soft);
}

.km-line:last-child { border-bottom: none; }

.km-line__glyph {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--km-text-muted);
}

.km-line__text { flex: 1; min-width: 0; }

.km-line__count {
  font-weight: 600;
  white-space: nowrap;
}

/* «Нечего» — приглушённо и без насыщенности: это не число, и вставать
   в один ряд с числами оно не должно. Признак существует не тогда, когда
   поставлен, а когда отличается. */
.km-line__count--none {
  font-weight: 400;
  color: var(--km-text-muted);
}

@media (max-width: 640px) {
  /* Поля встают друг под друга — стрелка вправо показывала бы не туда. */
  .km-pair { grid-template-columns: 1fr; }

  .km-pair__arrow {
    transform: rotate(90deg);
    margin: 0 auto;
    padding-top: 0;
  }
}

/* Журнал действий: запись — строка-фраза, а не набор столбцов.
   Макет `design/audit-log-v1.html`, принят 23 августа 2026.

   Таблицей это было до того дня, и два её столбца — «Что случилось»
   и «Действие» — говорили одно и то же. Строка вместо таблицы читается
   сверху вниз, а не слева направо, и на узком экране не разваливается
   вовсе: колонок в ней две, и вторая — время. */
.km-log {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  overflow: hidden;
}

.km-rec {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 13px 18px;
  border-bottom: 1px solid var(--km-border-soft);
}

.km-rec:last-child { border-bottom: none; }
.km-rec:hover { background: var(--km-bg); }

/* ПРИГЛУШЕНО ТО, ПОСЛЕ ЧЕГО У ЛЮДЕЙ НИЧЕГО НЕ ИЗМЕНИЛОСЬ.

   До 23 августа 2026 все семь записей на боевом выглядели одинаково
   тревожно, а пять из них были «делать было нечего»: глаз переставал
   отличать настоящее событие от пустого.

   Признак существует не тогда, когда поставлен, а когда **отличается**,
   — поэтому меняется цвет самой фразы, а не подложка строки: подложку
   съедает `:hover`, и под указателем разница пропадала бы. */
.km-rec--none .km-rec__text { color: var(--km-text-muted); }

.km-rec__body { flex: 1; min-width: 0; }

/* Фраза — она же кнопка разбора: по ней открывают «было и стало».
   Кнопкой, а не ссылкой: никуда не ведёт. Видом — обычный текст,
   потому что читают её как текст, а не как действие. */
.km-rec__text {
  font: inherit;
  color: var(--km-text);
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  text-align: left;
  line-height: 1.45;
  cursor: pointer;
}

.km-rec__text:hover { color: var(--km-accent); }

.km-rec__meta {
  margin-top: 4px;
  font-size: 12px;
  color: var(--km-text-muted);
  display: flex;
  gap: 7px;
  flex-wrap: wrap;
  align-items: center;
}

/* Время — второй колонкой, и она не сжимается: «21 авг, 14:12» на двух
   строках читается как две даты. */
.km-rec__when {
  flex: none;
  font-size: 12px;
  color: var(--km-text-muted);
  white-space: nowrap;
  text-align: right;
  padding-top: 1px;
}

/* Давность рядом с моментом — и ещё тише его: она подсказка, а не ответ. */
.km-rec__ago { display: block; opacity: 0.75; }

/* Над чем: вход в отбор по этому объекту. Кнопка, а не ссылка — адреса
   у отбора нет, он живёт в состоянии скрипта. */
.km-rec__object {
  font: inherit;
  font-size: 12px;
  color: var(--km-accent);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.km-rec__object:hover { text-decoration: underline; }

.km-sep { color: var(--km-border); }

/* Прогресс */
.km-progress { display: flex; flex-direction: column; gap: 7px; }

/* Полоса и число В СТРОКУ — для ячейки таблицы.

   Столбиком (как на карточке курса) прогресс занимает три высоты строки
   и растит весь список. В строке он занимает одну и читается тем же
   взглядом, что и соседние столбцы. */
.km-progress--row {
  flex-direction: row;
  align-items: center;
  gap: 9px;
  min-width: 130px;
}

.km-progress--row .km-progress__bar { flex: 1; min-width: 60px; }

/* Число — цифрами одинаковой ширины: в столбце из двадцати пяти строк
   разъезжающиеся «1 / 15» и «11 / 15» читаются хуже, чем выровненные. */
.km-progress__num {
  font-size: 13px;
  color: var(--km-text-secondary);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* У не начавшего вместо числа слово, и приглушённое: «0 / 15» глаз
   читает как число и сравнивает с соседями, а сказать надо «не приступал
   вовсе» — это другой человек и письмо ему пишут другое. */
.km-progress__zero {
  font-size: 13px;
  color: var(--km-text-muted);
  white-space: nowrap;
}
.km-progress__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.km-progress__label { font-size: 15px; font-weight: 600; }
.km-progress__percent { font-size: 14px; font-weight: 700; color: var(--km-accent); }
.km-progress__track {
  height: 5px;
  border-radius: 3px;
  background: var(--km-track);
  overflow: hidden;
}
.km-progress__fill { height: 100%; border-radius: 3px; background: var(--km-accent); }
.km-progress__meta { font-size: 12px; color: var(--km-text-muted); }
.km-progress--thin .km-progress__track { height: 4px; border-radius: 2px; }
.km-progress--soft .km-progress__fill { background: var(--km-accent-light); }

/* Полоса прогресса элементом `<progress>`, а не двумя `div` с шириной.
   ------------------------------------------------------------------

   Ширина заливки — единственная величина на весь фронт, которая зависит
   от данных, и посчитать её негде, кроме как в браузере. Через `style`
   это запрещено правилом 13, и правильно: правило заведено, чтобы
   оформление не переезжало из макетов в шаблоны. Но `value` и `max`
   у `<progress>` — **данные, а не оформление**: те же восемь и двадцать,
   что написаны рядом словами. Оформление при этом целиком здесь,
   как и требует правило.

   Заодно полоса получает голос: экранный диктор читает её как «8 из 20»,
   а `div` с шириной сорок процентов не читает никто.

   Псевдоэлементы у `progress` разные в движках, поэтому правил три:
   сама полоса — подложка, `::-webkit-progress-bar` — она же в WebKit,
   а заливку красят `::-webkit-progress-value` и `::-moz-progress-bar`. */
.km-progress__bar {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  min-width: 60px;
  height: 5px;
  border: none;
  border-radius: 3px;
  background: var(--km-track);
  overflow: hidden;
}

.km-progress__bar::-webkit-progress-bar { background: var(--km-track); border-radius: 3px; }
.km-progress__bar::-webkit-progress-value { background: var(--km-accent); border-radius: 3px; }
.km-progress__bar::-moz-progress-bar { background: var(--km-accent); border-radius: 3px; }

/* Полоса второстепенного показателя — тусклее основной. */
.km-progress__bar--soft::-webkit-progress-value { background: var(--km-accent-light); }
.km-progress__bar--soft::-moz-progress-bar { background: var(--km-accent-light); }

/* Колонка чтения: 980px.
 *
 * **Расхождение с макетом, и это решение человека 8 августа 2026, а не
 * недосмотр.** `design/lesson-player.html` рисует 780; измерение на боевом
 * показало, что мы ровно на этом числе и стоим и что тема ничего не режет
 * (`site`, `entry-content`, `site-main` — все во всю ширину). Дефекта
 * не было — узко оказалось само макетное число: на ноутбуке 780 посреди
 * 1440 читается колодцем.
 *
 * Записано здесь потому же, почему записаны исключения правил 6 и 9:
 * молча разошедшееся с макетом число через этап найдут как «отступление
 * от эталона» и вернут обратно.
 *
 * Довод самого ограничения не отменяется: строка во всю ширину монитора
 * не дочитывается до конца, а видео 16:9 на такой ширине занимает экран
 * целиком. Ширину задаёт колонка, а не сами блоки — иначе каждый следующий
 * блок пришлось бы ограничивать заново и по-своему. */
.km-column { width: 100%; max-width: 980px; margin: 0 auto; }

/* ---------------------------------------------------------------------------
   Плеер занятия (5.5)
   ---------------------------------------------------------------------------

   Две колонки по макету `design/lesson-player.html`: слева навигация
   по курсу, справа чтение. Левая липкая и со своей прокруткой — человек
   ходит по занятиям чаще, чем прокручивает страницу до конца, и терять
   навигацию при прокрутке значит заставлять его возвращаться наверх.
   --------------------------------------------------------------------------- */
.km-player {
	display: grid;
	grid-template-columns: 320px minmax(0, 1fr);
	align-items: start;
	min-height: 100vh;
}

.km-player__side {
	position: sticky;
	/* Отступ и высота считаются от чужой обвязки, а не от нуля.
	   При `top: 0` колонка прилипает к краю окна — то есть под липкую шапку
	   темы, — и первым уезжает её верх: «К карте курса», название курса
	   и полоса прогресса. Это ровно то, что в макете видно всегда. */
	top: var(--km-chrome-top);
	/* Высота, а не «не выше»: у короткого курса колонка иначе обрывается
	   белым прямоугольником посреди страницы, и граница читается как конец
	   содержимого. В макете она во весь экран — за вычетом того, что
	   рисует тема. */
	height: calc(100vh - var(--km-chrome-top));
	overflow-y: auto;
	background: var(--km-surface);
	border-right: 1px solid var(--km-border);
	padding: 22px 18px 32px;
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.km-player__main { min-width: 0; padding: 32px 28px 96px; }

/* Створка колонки. На широком экране её нет: колонка открыта всегда,
   и кнопка «свернуть навигацию» там означала бы только способ её потерять. */
.km-player__toggle { display: none; }

/* «К карте курса»: путь назад, а не действие. Поэтому ссылкой, а не кнопкой. */
.km-back {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-size: 13px;
	color: var(--km-text-secondary);
	text-decoration: none;
}

.km-back:hover { color: var(--km-accent); }

/* Компактный вид дерева: тот же компонент, что на карте курса, но плотнее.
   Разделительных черт между узлами в колонке нет — в списке из сорока строк
   они превращают навигацию в решётку. */
/* СТРОКА В КОЛОНКЕ НЕ ПЕРЕНОСИТСЯ, и знак состояния места не теряет.
 *
 * Стояло `flex-wrap: wrap`, и на 283 пикселя галочка уезжала под название
 * отдельной строкой — у «1. Компоненты духовного роста» она висела внизу,
 * а у коротких названий стояла в строке. Человек прочитал это поломкой,
 * и был прав: строка выглядела двумя.
 *
 * Записано это было наблюдением «не задача» и прожило так до 19 августа.
 * Перенос убран целиком: место отдаёт название, а не знак. Название
 * сжимается (`min-width: 0` у него уже есть) и переносится ВНУТРИ своей
 * колонки — знак при этом остаётся на первой строке и стоит по центру.
 *
 * Метки потока и «по желанию» в колонке не показываются (правило ниже),
 * так что переносить, кроме названия, тут нечего. */
.km-tree--compact .km-node { padding: 8px 6px; gap: 9px; border-bottom: none; flex-wrap: nowrap; }

/* Лестница уровней в колонке своя, и она мельче. Кегль здесь стоял и раньше,
   а вес и размеры кружка брались у базового класса — то есть правка 1 подняла
   бы их и тут, в колонке шириной 283 пикселя, где макет плеера рисует знаки
   16 и 13 пикселей. Названо явно, чтобы правка карты не переделывала плеер
   молча. */
.km-tree--compact .km-node__title { font-size: 14px; font-weight: 400; }
.km-tree--compact .km-tree__topics .km-node__title { font-size: 13px; }

/* Счёт над деревом — счёт, доля и полоса, отделённые чертой от списка.
   Нарисован он в одном месте, `design/lesson-player.html`, и виден там же:
   в колонке плеера.

   **На странице курса блока нет вовсе**, и это решение, а не пропажа.
   Макет страницы курса над модулями не рисует ни заголовка, ни счёта —
   счёт стоит выше, в карточке прогресса. Показанный здесь второй раз, он
   отвечал на один вопрос двумя дорогами: карточку считает `course.js`,
   эту строку — `course-map.js`, и разойтись они могли только молча. */
.km-mapcount { display: none; flex-direction: column; gap: 7px; }
.km-mapcount__percent { font-size: 13px; font-weight: 700; color: var(--km-accent); }
.km-mapcount__bar { display: none; }

/* Плотный вид узнаётся по СОСЕДУ, а не по второму классу на себе.
 *
 * Режим объявлен один раз — `km-tree--compact` на самом дереве, — и ставит
 * его тот, кто подключает карту. Счёт стоит строкой выше и класса не несёт;
 * дописать ему второй значило бы объявить один и тот же режим дважды,
 * и однажды поставили бы только один. */
.km-mapcount:has( + .km-tree--compact ) {
  display: flex;
  padding: 0 6px 16px;
  border-bottom: 1px solid var(--km-border-soft);
}
.km-mapcount:has( + .km-tree--compact ) .km-mapcount__bar { display: block; height: 5px; }
/* Счёт слева, доля справа — как в макете. Общее правило прижимает счёт
   вправо (`.km-row > .km-meta { margin-left: auto }`); здесь счёт первый
   в строке, и прижимать надо долю. */
.km-mapcount:has( + .km-tree--compact ) .km-meta { font-size: 13px; color: var(--km-text-secondary); margin-left: 0; }
.km-mapcount:has( + .km-tree--compact ) .km-mapcount__percent { margin-left: auto; }

/* НАЗВАНИЕ МОДУЛЯ — СТРОКА НА СЕРОМ МЕЖДУ КАРТОЧКАМИ, 15 полужирным.
 *
 * Принятый вид 19 августа 2026, `design/course-tree-v3.html`. Обычным
 * текстом: ни верхнего регистра, ни разрядки, ни бледности.
 *
 * Это ОТМЕНЯЕТ надстрочник из `design/lesson-player.html` — «МОДУЛЬ 2 ·
 * ТЕЛО КАК ОПОРА» бледным в одиннадцать пикселей. Довод человека записан
 * дословно: «забракован был не модуль, а способ его подачи — бледный
 * надстрочник с разрядкой». Отменяет и промежуточную редакцию 1.0.0,
 * где заголовок стоял 13 полужирным на белом внутри одной общей карточки.
 *
 * Модуль В ЛЕСТНИЦУ КЕГЛЯ НЕ ВХОДИТ: он разделитель, а не старший
 * уровень. Совпадение с занятием по кеглю (15) поэтому не оплошность —
 * различают их фон под строкой и место: заголовок стоит на сером
 * между карточками, занятие — на белом внутри своей.
 *
 * Тёплым у того модуля, где человек стоит: без отличия колонка не
 * отвечает на вопрос «где я», ради которого она и стоит. */
.km-tree__heading {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--km-text);
  padding: 16px 4px 8px;
}
.km-tree__heading:first-child { padding-top: 0; }
.km-tree__heading--current { color: var(--km-warm-text-strong); }

/* КАРТОЧЕК В КОЛОНКЕ ПЛЕЕРА НЕТ, и это не забытый сброс.
 *
 * Колонка сама лежит на белом (`.km-player__side` — `--km-surface`),
 * и белая карточка на белом фоне не рисует ничего, кроме рамки: сорок
 * рамок подряд на 283 пикселя превращают навигацию в решётку. Серого
 * фона, между которым карточки и разделяются, там нет вовсе.
 *
 * Сбрасывается ПОЛНОСТЬЮ, включая поля: карточка несёт их для страницы
 * курса, а в колонке отступы задаёт сама колонка. */
.km-tree--compact .km-tree__card {
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  overflow: visible;
  margin-bottom: 0;
}
.km-tree--compact .km-tree__card > .km-node,
.km-tree--compact .km-tree__fold-head > .km-node { padding: 8px 6px; border-radius: 10px; }

/* Строка узла в колонке: своя подложка у текущего, скруглённая, как в макете.
   Черты между строками нет — сорок строк с ними превращаются в решётку. */
.km-tree--compact .km-node { border-radius: 10px; }

/* В КОЛОНКЕ ПОДЛОЖКУ НОСИТ ТОЛЬКО ОТКРЫТЫЙ УЗЕЛ. Решение человека
 * 19 августа 2026, вечер.
 *
 * Найдено им на боевом: тёплый фон несли ДВЕ строки — занятие «Протокол
 * сеанса» и топик «1. Введение» под ним. Причина не в том, что занятие
 * получило подложку как контейнер: оно несло её ПО СОБСТВЕННОМУ СОСТОЯНИЮ
 * `started`, а топик — как открытый в плеере. Два разных признака,
 * нарисованных одинаково.
 *
 * Решение записано дословно: **в плеере место человека — это открытый
 * узел.** Открыл топик — он и есть «здесь я». Отдельного места остановки
 * в колонке нет: оно нужно там, где человек ещё ничего не открыл, то есть
 * на странице курса. Экраны при этом не расходятся видом — светится ровно
 * одна строка и там, и там, — расходится только источник.
 *
 * Второго признака не заводится: у места в колонке нет НИКАКОГО. Поэтому
 * гасится не только заливка, но и тёплый шеврон с полужирным названием —
 * иначе строка осталась бы «светящейся» наполовину, и различие, от которого
 * уходили, вернулось бы слабее и незаметнее.
 *
 * На странице курса всё остаётся как было: открытого узла там нет, и
 * подложку носит место.
 *
 * `--active` объявлен ЯВНО и после: у одной строки могут стоять оба класса
 * сразу — человек открыл то самое занятие, где остановился, — и без этой
 * строки погашенное состояние утащило бы за собой и открытый узел. */
.km-tree--compact .km-node--current { background: none; }
.km-tree--compact .km-node--current .km-node__title { font-weight: 400; }
.km-tree--compact .km-node--current .km-tree__chevron { color: var(--km-text-muted); }
.km-tree--compact .km-node--active { background: var(--km-warm-bg); }
.km-tree--compact .km-node--active .km-node__title { font-weight: 700; }

/* В колонке 283 пикселя метки в строку с названием не встают: «Эфир 14 марта»
   ломался вокруг «Весна 2026» и читался как три разных строки. Название
   занимает строку целиком, метки встают под ним с отступом под кружок. */
.km-tree--compact .km-node__link { flex: 1 1 auto; min-width: 0; }
/* Метки потока и «по желанию» в колонке не показываются.
   В макете плеера их нет вовсе, а занимают они целую строку под узлом —
   на сорока строках это сорок лишних. Сведения не теряются: обе метки
   стоят у того же узла на странице курса, где строка вдвое шире. */
.km-tree--compact .km-node > .km-badge { display: none; }
/* В КОЛОНКЕ ПРАВЫЙ СТОЛБЕЦ НЕСЁТ ТОЛЬКО ЗНАКИ, без текста.
 *
 * Решение человека 19 августа 2026: значение у столбца одно и то же
 * на обоих экранах, а способ зависит от ширины — знак в колонке, знак
 * или слово на странице курса.
 *
 * Довод виден на боевом: колонка 283 пикселя, и «вы здесь» рядом
 * с названием не помещается — название ломалось на две строки, а галочка
 * уезжала под него и читалась отдельным элементом списка.
 *
 * Текущий узел в колонке различается подложкой, цветом знака
 * и полужирным названием — три признака, и текст четвёртым не нужен.
 * Место остановки и дата эфира остаются на странице курса, где ширина
 * позволяет.
 *
 * ЗАМОК ЗДЕСЬ НЕ ДОБАВЛЯЕТСЯ: он стоит СЛЕВА, на месте знака содержимого,
 * решением от 19 августа — сервер за замком содержимого не отдаёт вовсе.
 * Второй замок справа повторял бы его в той же строке. */
.km-tree--compact .km-node__state--here,
.km-tree--compact .km-node__state--at,
.km-tree--compact .km-node__state--when { display: none; }
/* Причина закрытия в колонку плеера не помещается и не нужна: 283 пикселя
   заняты названием, а «почему закрыто» человек читает на карте курса.
   Имя сменилось с `km-node__reason` 27 августа 2026 вместе с местом:
   причина переехала из абзаца под строкой в правый столбец самой строки. */
.km-tree--compact .km-node__why { display: none; }

/* Узел, открытый прямо сейчас. Отдельно от состояния `current` из карты:
   то — «здесь человек остановился», это — «на это он смотрит». Совпадают
   они часто, но не всегда: вернувшись к пройденному занятию, человек
   смотрит на одно, а остановился на другом. */
.km-node--active { background: var(--km-warm-bg); border-radius: var(--km-radius-control); }
.km-node--active .km-node__title { font-weight: 700; color: var(--km-text); }

/* Узел в дереве кликабелен: вся строка, а не только название. */
.km-node__link {
	display: flex;
	align-items: center;
	gap: 9px;
	flex: 1;
	min-width: 0;
	color: inherit;
	text-decoration: none;
}

/* В ШАПКЕ СКЛАДКИ ССЫЛКА НЕ РАСТЯГИВАЕТСЯ, и это про нажатие,
   а не про вид.
 *
 * Строка занятия с топиками лежит в `<summary>`. Растянутая ссылка
 * занимает всё свободное место, и нажатие по «пустому» месту строки
 * уходит по адресу — складке остаётся полоска правого столбца.
 * Дефект жил с 1.0.0 и нашёлся руками на боевом: занятие не
 * раскрывалось вовсе.
 *
 * Теперь ссылка шириной по названию, а свободное место принадлежит
 * складке. Шеврон стоит вне ссылки — его приставляет `row()`. */
.km-tree__fold-head .km-node__link {
	flex: 0 1 auto;
	margin-right: auto;
}

.km-divider { height: 1px; background: var(--km-border); }

/* Полоса «Назад · Отметить · Дальше».
   Отметка посередине и неяркая: она доступна всегда (8.3), но не должна
   выглядеть основным способом пройти занятие. */
.km-lesson-nav { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.km-lesson-nav__middle { flex: 1; display: flex; justify-content: center; }

/* Карточка «Дальше»: что ждёт за кнопкой. */

/* ПОДСКАЗКА ПРО ОТМЕТКУ. Решение человека 19 августа 2026.
 *
 * Заменяет окно `km-ask` на странице занятия: то переспрашивало решение,
 * принятое секунду назад, и главной в нём стояла кнопка, которую человек
 * не выбирал.
 *
 * Подсказка **не спрашивает и не удерживает**. Отсюда и вид: она стоит
 * в потоке под кнопками, а не всплывает поверх. Всплывающее перекрывает
 * то, на что человек смотрел, и требует его убрать — то есть снова
 * требует действия.
 *
 * Тёплая, а не тревожная, — по тому же доводу, что у мягкого вопроса
 * ниже: это не ошибка и не запрет, человеку сообщают, что здесь осталось
 * несделанное. Рамки нет: рамка читается как отдельный блок, требующий
 * внимания, а подсказка живёт полторы секунды и уходит вместе со
 * страницей.
 *
 * Галка мельче текста подсказки: она про будущее, а не про сейчас. */
/* ИМЯ СМЕНЕНО 23 АВГУСТА 2026: было `.km-hint`, и оно уже было занято.

   Тем же словом ниже (и на девяти административных экранах) называется
   мелкая серая подпись под полем ввода. Два смысла на одно имя не спорили
   за победу — они **складывались**: подпись получала тёплую подложку
   и отступы отсюда, а кегль и цвет оттуда. Выходила помесь, которой никто
   не задумывал: объяснение под полем в плашке размером с само поле.

   Нашлось глазами на слиянии аккаунтов; тестами не ловится по устройству —
   каждое из двух правил само по себе верно. Ровно та болезнь, что записана
   про общее пространство имён: префикс `km` защищает от чужих и не защищает
   от нас самих во времени. */
.km-nudge {
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--km-warm-bg);
  border-radius: var(--km-radius-control);
  padding: 14px 18px;
}
.km-nudge__text { margin: 0; font-size: 15px; line-height: 1.5; color: var(--km-warm-text); }
.km-nudge__mute { font-size: 13px; color: var(--km-text-secondary); }

/* Мягкий вопрос (8.4).
   Тёплый, а не тревожный: это не ошибка и не запрет — человеку сообщают,
   что здесь осталось несделанное, и обе двери оставляют открытыми. */
.km-ask {
	background: var(--km-warm-bg);
	border: 1px solid var(--km-warm-border);
	border-radius: var(--km-radius-card);
	padding: 22px 24px;
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.km-ask__title { font-size: 17px; font-weight: 700; }

/* СПИСОК ЦЕЛЕЙ ПЕРЕМЕЩЕНИЯ. Задача 21 августа 2026.

   Главная жалоба человека: на 119 узлах перетащить топик через сто строк
   невозможно. Список — то же дерево отступами, и отступ здесь единственное,
   чем видно вложенность: кегль одинаков, потому что это выбор, а не чтение.

   Своя высота с прокруткой: сто целей в окне подтверждения выдавили бы
   кнопки за экран, и человек не нашёл бы, чем согласиться. */
.km-move {
  max-height: 46vh;
  overflow-y: auto;
  border: 1px solid var(--km-border);
  border-radius: 10px;
  background: var(--km-surface);
}

.km-move__row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  border: none;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.km-move__row:hover { background: var(--km-bg); }
.km-move__row[aria-pressed="true"] { background: var(--km-warm-bg); font-weight: 600; }

/* Отступ уровня — переменной, а не тремя правилами: уровней у списка целей
   столько же, сколько у дерева, и заводить класс на каждый значило бы
   переписать лестницу второй раз. */
.km-move__row--1 { padding-left: 32px; }
.km-move__row--2 { padding-left: 56px; }

/* Цена выбора, если она есть: «четвёртый уровень». Приглушённо и справа —
   это не запрет, а предупреждение. */
.km-move__cost { margin-left: auto; font-size: 12px; color: var(--km-text-muted); white-space: nowrap; }
.km-ask__text { font-size: 15px; line-height: 1.6; color: var(--km-warm-text); }
.km-ask__actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* Нехватка словами: «У эфира нет ссылки на трансляцию».
   Не бейдж: бейджи набраны заглавными и коротки по устройству, а здесь
   фраза, которую человек читает целиком. Не `km-meta`: она серая и теряется
   среди прочих подписей строки, а эта говорит, что чего-то не хватает.
   Про любой экран — то же понадобится карточке потока с её чеклистом. */
/* Поля свои сняты: в колонке промежутки задаёт стопка, и абзац со своим
   полем ломает ритм. Найдено стендом адреса материала 4 сентября 2026 —
   общая мерка «у абзацев в колонках нет своих полей» держит это с 3.6,
   а `km-warn` и `km-caution` в колонке до того дня не стояли. */
.km-warn { font-size: 13px; line-height: 1.4; color: var(--km-danger); margin: 0; }

/*
 * НЕЗАКРЫТОЕ, КОТОРОЕ НЕ МЕШАЕТ. Заведено 4 сентября 2026 под чеклист
 * готовности потока.
 *
 * Три пункта из пяти стали предупреждениями: ссылки эфиров, группа
 * и куратор доделываются по ходу и продажу не запрещают. Но с экрана
 * они не уходят — спрятанное предупреждение вернуло бы прежнюю беду
 * целиком.
 *
 * Значит на экране их надо ОТЛИЧАТЬ от запрещающих, а не только
 * от пройденных. Красным, как запрет, — человек решит, что застрял;
 * серым, как пройденное, — не заметит вовсе. Жёлтый здесь тот же,
 * что у «скоро понадобится» в таблице зачислений: между зелёным
 * «ничего не надо» и красным «надо сейчас».
 *
 * Кегль и межстрочный те же, что у `km-warn`: строки стоят в одном
 * списке друг под другом, и разниться им можно только цветом.
 */
.km-caution { font-size: 13px; line-height: 1.4; color: var(--km-warn); margin: 0; }

/* Строка кнопок. Про любой экран, а не про эфир: такая же нужна была
   мягкому вопросу, и `km-ask__actions` осталась там только потому, что
   у неё своя обвязка. Переносить его сюда — отдельная правка, а плодить
   третью копию под каждый новый блок незачем. */
.km-actions { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* Блоки материала: типографика достаётся от темы, здесь только ритм.
   Своих правил на заголовки и абзацы нет намеренно — материал верстают
   штатным редактором, и перебивать его оформление плагином значит
   отобрать у редактора то, ради чего он и нужен.

   РИТМ ЗАДАЁТСЯ `gap`, А НЕ `margin-top`. Правка 27 августа 2026, и она
   не про вкус — про то, что полями этот ритм не держался.

   Было `.km-content > * + * { margin-top: 28px }`, и оно проигрывало
   сбросу полей у типографики: там `.km-note` стоит в общем списке
   с `margin: 0`, специфичность у обоих правил ОДИНАКОВАЯ (один класс),
   а сброс лежит ниже по файлу — значит побеждал он. Полоса обратной связи
   прилипала к соседнему блоку вплотную, зазор ноль при 28 у остальных.
   Измерено на собранной странице: 28 → 0 → 28.

   `gap` этим не перебивается вовсе: он свойство контейнера, и никакой
   сброс полей у ребёнка до него не дотягивается. Чинит по устройству,
   а не по специфичности — иначе следующий, кто допишет сброс ниже,
   сломал бы снова и так же молча.

   Кого это задевало, кроме обратной связи: ВСЕ сообщения об отказе,
   которые блоки печатают правящему (`km-note km-note--danger` у задания,
   звука, вложения и «После просмотра видео»), — они из того же списка. */
.km-content { display: flex; flex-direction: column; gap: 28px; }

/* НАШ БЛОК САМ ДЕРЖИТ ЗАЗОР ТАМ, ГДЕ КОНТЕЙНЕР ЕГО НЕ ДАЁТ.
   Правка 27 августа 2026, найдена измерением на боевой странице.

   Ритм `.km-content` (gap: 28) доходит только до ПРЯМЫХ детей. А материал
   человек собирает как хочет: «Расписание и тариф» целиком завёрнут
   в группу Гутенберга, и у `.km-content` там **один ребёнок** — сама
   группа. Внутри неё расстояние дают поля соседей, а наша карточка
   не даёт ни сверху, ни снизу: измерено — 0 и 0. Под соседом без верхнего
   поля блок слипается с ним вплотную.

   Тема здесь ни при чём, и это проверено: `blockGap` у неё не объявлен,
   и WordPress правил `margin-block-start` детям группы не печатает вовсе.

   Поля, а не `gap`: контейнер чужой, и договориться с ним нельзя. Поля
   при этом СХЛОПЫВАЮТСЯ — два наших блока подряд дадут 28, а не 56,
   и сосед с полем в 16 не прибавит их к нашим.

   Внутри своего ритма поля снимаются: там расстояние задаёт `gap`,
   и сложенные с ним они дали бы 56. Специфичность (0,2,0) против (0,1,0) —
   побеждает по устройству, а не по порядку в файле. */
[data-lms-block] { margin-block: 28px; }
.km-content > [data-lms-block] { margin-block: 0; }

/* БЛОКИ ЗАНЯТИЯ ЧИТАЮТСЯ КАК ОДНА СЕМЬЯ: один радиус и одна вертикаль.
   Макет `design/lesson-blocks-v2.html`, правка 27 августа 2026.

   Измерено на собранной странице до правки: радиусы 20 / 16 / 10 / 20,
   а знаки блоков стояли на 367 и 363 — разъехались на четыре пикселя,
   потому что горизонтальные поля у `.km-files` и `.km-note` разные.
   Четыре пикселя не видны по отдельности и видны в паре: два блока
   читаются как один разъехавшийся, ровно как сказано в макете.

   Правится ТОЛЬКО внутри `.km-content`: `.km-note` живёт ещё на девяти
   административных экранах, и общий радиус там ни при чём. Тот же довод,
   по которому у имени спрашивают не «занято ли», а «сколько определений».

   ОБРАТНОЙ СВЯЗИ В ЭТОМ СПИСКЕ НЕТ, и это не пропуск: с 27 августа она
   `.km-feedback` со своим радиусом 20 выше. Своё имя носит своё число —
   иначе полоса в предпросмотре редактора, где `.km-content` нет, снова
   поехала бы с чужим. */
.km-content > .km-files,
.km-content > .km-note,
/* Двадцать — то же число, что у `.km-player__main .km-card` ниже. Взято
   литералом, а не переменной: переменной с таким смыслом в системе нет,
   а выдуманная читалась бы как настоящая. Меняется здесь и там вместе. */
.km-content > .km-audio { border-radius: 20px; }

.km-content > .km-note { padding-left: 18px; padding-right: 18px; }

/* Карточка вокруг кадра — БЕЗ ПОЛЕЙ.
 *
 * В `design/lesson-player.html` видео идёт встык до краёв карточки:
 * рамка кадра и есть её верх. Поля здесь утапливали кадр на 24 пикселя
 * с каждой стороны — 930 внутри 980, — и карточка читалась как рамка
 * вокруг картинки, а не как сама картинка.
 *
 * `overflow: hidden` обязателен: без него прямые углы кадра торчат
 * из скруглённых углов карточки. */
.km-card--media { padding: 0; overflow: hidden; }
.km-card--media > * { display: block; width: 100%; }

/* Проза занятия в карточке — макет плеера. Поля крупнее обычных 24:
   колонка чтения широкая, и текст, прижатый к рамке, читается тяжело.
   Числа с макета: 28 сверху и снизу, 32 по краям, строка 1.75. */
.km-prose { padding: 28px 32px; }

/* СВОИ ПОЛЯ У ДЕТЕЙ ОБНУЛЯЮТСЯ, И ЭТО НЕ УКРАШЕНИЕ. Правка 8 сентября 2026.

   Содержимое приходит чужой разметкой и живёт по правилам темы, а тема
   даёт каждому абзацу поле снизу:

       p { margin: 0 0 1.6875rem }      ← buddyboss-theme/assets/css/theme.css

   Двадцать семь пикселей темы складывались с нашим промежутком, и между
   абзацами на гостевой странице выходило СОРОК ТРИ. Померено на боевом;
   пустых абзацев в содержимом при этом не было ни одного.

   Расстояние задаётся отношением соседей, а не полем у каждого: тогда
   сумма не набегает, у первого элемента нет лишнего отступа сверху,
   а у последнего снизу — и коробке не приходится его вычитать.

   Обнуляется ТОЛЬКО внутри `.km-prose` и только у прямых детей: абзацы
   вне прозы живут по правилам темы законно. */
.km-prose > * { margin: 0; }
.km-prose > * + * { margin-top: 16px; }
.km-prose p { line-height: 1.75; }

/* Заголовкам внутри прозы воздуха больше: они начинают кусок, а не
   продолжают абзац. Первому сверху не нужно — он и так первый. */
.km-prose > h2 + *,
.km-prose > h3 + *,
.km-prose > h4 + * { margin-top: 12px; }
.km-prose > * + h2,
.km-prose > * + h3,
.km-prose > * + h4 { margin-top: 32px; }

.km-prose ul,
.km-prose ol { padding-left: 22px; }
.km-prose li { line-height: 1.7; }
.km-prose li + li { margin-top: 6px; }

/* Пустой абзац из редактора схлопывается. В содержимом 12-rays таких нет,
   но в чужом тексте они появляются сами: редактор оставляет `<p>&nbsp;</p>`
   между кусками, и к отступу прибавляется целая пустая строка. */
.km-prose > p:empty,
.km-prose > p > br:only-child { display: none; }


/* Шапка занятия — числа с макета плеера.

   Крошка обычной строкой, без верхнего регистра: `km-eyebrow` носит
   надстрочники по всему фронту, и правка его ради одного экрана уехала бы
   на восемь других. Заголовок крупнее общего `km-h3` — колонка чтения
   здесь одна и широкая, и заголовку есть где стоять. */
.km-lesson-place { margin: 0; font-size: 12px; color: var(--km-text-muted); }
.km-lesson-title {
  margin: 0;
  font-family: var(--km-font-display);
  font-size: 34px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

/* Карточки читальной колонки скруглены сильнее общих. Так в макете плеера:
   там радиус 20 у всех — у прозы, у видео, у аудио и у задания, — а общий
   в дизайн-системе 16, и его держит страница курса, где нарисовано 16.
   Разные числа на двух экранах — решение макетов, а не расхождение. */
.km-player__main .km-card { border-radius: 20px; }

/* ---------------------------------------------------------------------------
   Кабинет и страница курса (5.6)
   ---------------------------------------------------------------------------

   Обе — две колонки: слева навигация или содержимое, справа второстепенное.
   На узком обе становятся одной колонкой, и порядок в разметке уже такой,
   каким он нужен телефону: сначала главное.
   --------------------------------------------------------------------------- */
/* Кабинет — одна колонка: левое меню даёт тема (BuddyPanel), а не мы. */
.km-dashboard {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	align-items: start;
}

.km-dashboard__main { min-width: 0; }

.km-course {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 320px;
	gap: 32px;
	align-items: start;
}

.km-course__main { min-width: 0; }
/* Боковая колонка ИДЁТ ВДОЛЬ СТРАНИЦЫ, а не кончается на своей высоте.
 *
 * Обрыва в смысле обрезки здесь не было: ни своей высоты, ни прокрутки,
 * ни `overflow` у колонки нет — прочитано на боевом. Кончалась она просто
 * потому, что кончалось её содержимое: четыре карточки дают 1400 пикселей,
 * а дерево раскрытого модуля — 3250, и `align-items: start` оставляет
 * под колонкой полторы тысячи пустоты.
 *
 * В макете этого не видно: там дерево короткое, и обе колонки кончаются
 * вместе. Случай, которого макет не рисует, — тот же род, что одиночный
 * узел верхнего уровня.
 *
 * Липкость выбрана вместо растягивания: растянутая колонка пустоту
 * не убирает — карточки всё равно лежат сверху, — а липкая едет вместе
 * с человеком, и справа от дерева всегда что-то есть.
 *
 * **Оговорка, и она честная:** колонка выше окна (1400 против 990),
 * и в прилипшем состоянии её низ остаётся за краем. Пока карточек четыре,
 * человек видит их все в начале прокрутки; станет больше — понадобится
 * своя прокрутка у колонки, и это уже другое решение.
 *
 * Отступ сверху — под липкую шапку темы: без него первая карточка
 * уезжает под неё.
 *
 * **Здесь стояло `top: 90px`, и число было выдумано.** Чужая обвязка сверху
 * складывается из трёх слагаемых — шапка темы 70, промо-полоса Elementor 64,
 * панель администратора 32, — и на боевом это 134 у курсанта и 166 у нас.
 * Девяносто не совпадало ни с одним из двух. Считает теперь `chrome.js`
 * живым замером; двадцать сверху — воздух, а не поправка на чужое. */
.km-course__side {
	display: flex;
	flex-direction: column;
	gap: 20px;
	position: sticky;
	top: calc(var(--km-chrome-top) + 20px);
}

/* Счётчик у вкладки: сколько курсов внутри. Тусклее самой вкладки —
   это подсказка, а не значение. */
.km-tab__count { font-weight: 600; opacity: 0.55; margin-left: 6px; }

/* Шапка курса.
 *
 * Два вида, и второй — не украшение. Простой: обложка сверху, название
 * в белой карточке под ней. С наложением (`--over`): название и бейджи
 * лежат **поверх** обложки по градиенту, как нарисовано в обоих макетах
 * страницы курса — и участнику, и гостю.
 *
 * До 9-го этапа комментарий здесь обещал наложение, а код рисовал простой
 * вид. Слом такого комментария ничего не роняет — он и не должен: обещание
 * жило в прозе, а не в правиле.
 */
.km-hero { position: relative; overflow: hidden; border-radius: var(--km-radius-card); }
.km-hero__cover { aspect-ratio: 21 / 9; background: var(--km-track); }
.km-hero__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }

.km-hero__body {
	display: flex;
	flex-direction: column;
	gap: 8px;
	padding: 20px 24px 24px;
	background: var(--km-surface);
	border: 1px solid var(--km-border);
	border-top: none;
	border-radius: 0 0 var(--km-radius-card) var(--km-radius-card);
}

.km-hero__title { font-family: var(--km-font-display); font-size: 32px; font-weight: 700; line-height: 1.25; }

/* Полоса прогресса над вкладками: счёт и доля слева, действия справа.
 *
 * Кнопки не переносятся, пока помещаются, и уходят под полосу, когда нет:
 * `flex-wrap` у полосы плюс `min-width` у левой колонки. Число 260 из
 * макета — ниже него «Пройдено 8 занятий из 20» рвётся на две строки. */
.km-headline {
	display: flex;
	align-items: center;
	gap: 28px;
	flex-wrap: wrap;
}

.km-headline > .km-progress { flex: 1; min-width: 260px; }
.km-headline > .km-actions { flex: none; }

.km-hero__badges { display: flex; flex-wrap: wrap; gap: 8px; }
.km-hero__lead { font-size: 15px; line-height: 1.6; max-width: 500px; text-wrap: pretty; }

/* Наложение. Ставится **только когда обложка есть**: без картинки под
   текстом остался бы белый прямоугольник в 280 пикселей, то есть ровно
   то пустое место, из-за которого в 5.6 решили не рисовать полосу
   обложки вовсе. Решает это тот, у кого есть данные, — скрипт. */
.km-hero--over .km-hero__cover {
	position: absolute;
	inset: 0;
	aspect-ratio: auto;
}

/* Картинка ОБЯЗАНА заполнить коробку, и без этого правила шапка выходит
   пустой: коробка растянута абсолютно, а картинка внутри стоит своим
   размером, и белому тексту наложения лечь не на что. Ровно это
   и случилось в 0.88.0. */
.km-hero--over .km-hero__cover img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Затемнение — псевдоэлементом, а не своим `div`: оно ничего не значит
   и читать его нечем. Разметке от него не должно доставаться ни строки. */
.km-hero--over::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(
		to bottom,
		rgba(63, 52, 46, 0.6) 0%,
		rgba(63, 52, 46, 0.22) 55%,
		rgba(63, 52, 46, 0.05) 100%
	);
}

.km-hero--over .km-hero__body {
	position: relative;
	z-index: 1;
	/* Высота общая — 280. ВЫСОКАЯ ШАПКА ТОЛЬКО У ГОСТЯ, и это правлено
	   после того, как 460 уехали и на страницу участника: класс
	   `km-hero--over` носят обе, и число, поставленное здесь, досталось
	   обеим. Ровно тот случай, что записан правилом про имена: своё
	   пространство общее с самим собой. */
	/* 320: 280 по макету плюс сорок решением человека 8 августа 2026.
	   Прибавка одна на обе страницы — просили и там, и там. */
	min-height: 320px;
	/* Содержимое по центру шапки, а не прижато к верху — так в макете. */
	justify-content: center;
	gap: 12px;
	padding: 30px 36px 34px;
	background: none;
	border: none;
	border-radius: 0;
	color: #fff;
}

.km-hero--over .km-hero__title {
	font-size: 42px;
	letter-spacing: -0.02em;
	line-height: 1.12;
	color: #fff;
}

.km-hero--over .km-hero__lead { color: rgba(255, 255, 255, 0.9); }

/* ФИРМЕННАЯ ШАПКА КУРСА У УЧАСТНИКА. Правка Алены 6 сентября 2026.
 *
 * Своим классом, а не поверх `km-hero--over`: тот носят **обе** страницы,
 * участника и гостя, и это записано абзацем выше. Правка «только шапку
 * участника» через общий класс досталась бы и гостю — ровно та беда,
 * которую здесь уже ловили числом 460.
 *
 * Заголовок тёмный, значит затемнение картинки снимается: оно заведено
 * под белый текст и под тёмным делает ровно обратное.
 */
/* ПРОПОРЦИИ У КОРОБКИ КАРТИНКИ В НАЛОЖЕНИИ НЕТ, и это правка 6 сентября
   2026 по замеру. Стояло `aspect-ratio: 32 / 9` — при ширине 1124 это
   316 пикселей, а шапка задана в 274, и коробка торчала вниз на 42.
   Картинка центрируется внутри коробки, а видно окно шапки, прижатое
   к верху: обрезка выходила 29 сверху и 71 снизу вместо поровну.

   Правило снято целиком, а не переопределено: в наложении коробку задаёт
   `position: absolute; inset: 0` соседним правилом — она и так равна
   шапке, — а на узком экране пропорцию ставит мобильное правило ниже.
   Третьего случая у этой коробки нет: без обложки она спрятана вовсе. */

/* Затемнение снимается только у тёмного заголовка: оно заведено под белый
   текст и под тёмным делает ровно обратное. */
.km-hero--brand.km-hero--ink.km-hero--over::after { content: none; }

/* А у СВЕТЛОГО заголовка затемнение своё, и оно горизонтальное.
   Общее — сверху вниз, и заведено оно под текст, стоявший в шапке
   высотой 320. Текст теперь стоит слева и ниже, где прежний градиент
   уже сошёл на нет, — белые буквы оказывались на светлой картинке.
   Найдено глазами: вид выглядел не законченным, а запасным.

   Слева направо — потому что заголовок прижат влево: тёмное там, где
   буквы, и чистая картинка там, где сюжет. */
.km-hero--brand.km-hero--lume.km-hero--over::after {
	background: linear-gradient(
		to right,
		rgba(28, 26, 46, 0.72) 0%,
		rgba(28, 26, 46, 0.55) 38%,
		rgba(28, 26, 46, 0.12) 72%,
		rgba(28, 26, 46, 0) 100%
	);
}

.km-hero--brand.km-hero--over .km-hero__body {
	color: inherit;
	/* ВЫСОТА ШАПКИ — 374, и число выбрано, а не подобрано на глаз.
	   Правка 6 сентября 2026: было 274, и человек сказал, что шапка узкая.

	   Начали с 420. Картинка дышит — но при ширине 1124 коробка становится
	   выше своей обложки, и та режется ПО БОКАМ: 68 пикселей с каждой
	   стороны у трёхкратных обложек и 159 у «Энергетического апгрейда»,
	   где справа воздушный шар. Поймала это мерка обрезки, заведённая
	   накануне, а не глаза.

	   374 — порог: 1124 делить на 3, самую широкую из ходовых пропорций.
	   На нём обложка показана целиком — бока и верх с низом по нулю, —
	   а тексту достаётся 202 из 374.

	   Поднимут число — мерка покраснеет и назовёт, сколько срезано
	   и с какой стороны. Она же и есть объяснение, почему здесь не 420.

	   И это же число держит форму до ответа: разметка едет пустой,
	   и без него шапка подпрыгивает, когда приходит ответ. */
	min-height: 374px;
	justify-content: center;
	align-items: flex-start;
	gap: 6px;
	padding: 26px 32px 28px;
	color: var(--km-brand-navy);
}

/* Надзаголовок — строкой, без плашки. Плашка была бейджем и спорила
   с заголовком за внимание; здесь она подпись, а не метка. */
.km-hero--brand .km-hero__eyebrow {
	font-family: var(--km-font-display);
	font-size: 13px;
	font-weight: 700;
	letter-spacing: 0.14em;
	text-transform: uppercase;
}

.km-hero--brand.km-hero--ink .km-hero__eyebrow { color: var(--km-brand-navy); }
.km-hero--brand.km-hero--lume .km-hero__eyebrow { color: rgba(255, 255, 255, 0.92); }

/* Заголовок двумя цветами и двумя строками. Части — `span` с `display:
   block`, а не два заголовка: `<h1>` у страницы один, и разбивать его
   на два значило бы соврать про структуру тому, кто читает диктором. */
.km-hero--brand.km-hero--over .km-hero__title {
	font-family: var(--km-font-display);
	font-size: 58px;
	font-weight: 700;
	line-height: 0.98;
	letter-spacing: -0.01em;
	text-transform: uppercase;
}

.km-hero--brand.km-hero--ink.km-hero--over .km-hero__title { color: var(--km-brand-navy); }
.km-hero--brand.km-hero--lume.km-hero--over .km-hero__title { color: #fff; }

.km-hero__title-lead,
.km-hero__title-rest { display: block; }

/* Вторая часть заголовка красится только у тёмного вида: у светлого
   заголовок белый целиком, и второй цвет на затемнении не читался бы. */
.km-hero--brand.km-hero--ink .km-hero__title-rest { color: var(--km-brand-teal); }

.km-hero--brand.km-hero--ink.km-hero--over .km-hero__lead { color: var(--km-brand-navy); }
.km-hero--brand.km-hero--lume.km-hero--over .km-hero__lead { color: rgba(255, 255, 255, 0.92); }

.km-hero--brand.km-hero--over .km-hero__lead {
	font-size: 15px;
	line-height: 1.45;
	max-width: 430px;
}

/* Свои поля абзацам не положены: расстояние задаёт `gap` колонки, и два
   способа раздвигать строки разошлись бы на первой же правке. Проверка
   дизайн-системы это ловит, и она права. */
.km-hero__eyebrow,
.km-hero__author { margin: 0; }

.km-hero--brand .km-hero__author {
	font-size: 14px;
	font-weight: 600;
}

.km-hero--brand.km-hero--ink .km-hero__author { color: var(--km-brand-navy); }
.km-hero--brand.km-hero--lume .km-hero__author { color: rgba(255, 255, 255, 0.92); }

/* ПОЛОСА ПРОГРЕССА ПЛОТНЕЕ. Подпись мелко сверху, под ней счёт,
   под ним полоса с долей справа. Прежде счёт и доля стояли строкой,
   а подписи не было вовсе — высота уходила на пустой промежуток. */
.km-headline .km-progress { gap: 4px; }

.km-headline .km-progress__caption {
	font-family: var(--km-font-display);
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--km-text-muted);
}

.km-headline .km-progress__head { display: block; }

.km-headline .km-progress__label { font-size: 16px; font-weight: 700; }

/* Доля уезжает к полосе, а не стоит своей строкой. */
.km-headline .km-progress__line { display: flex; align-items: center; gap: 12px; }
.km-headline .km-progress__line .km-progress__bar { flex: 1; }
.km-headline .km-progress__line .km-progress__percent { flex: none; }

/* Числа вкладки «Прогресс»: крупное значение, подпись под ним. */
.km-stats { display: flex; flex-wrap: wrap; gap: 28px; }
/* Черты между числами нет, и это решение, а не пропуск.

   В макете страницы курса она нарисована — там три числа в одну строку.
   На настоящем курсе типов пять, строка переносится, и `+`-черта достаётся
   первому числу второго ряда: висящая палка слева от «1 чек-поинтов».
   Средствами CSS «первый в ряду» не спрашивается никак.

   Тот же род, что «мерка, верная на трёх карточках, врёт на пяти», только
   про оформление: макет нарисован под то число элементов, которое в нём
   было. Расстояние разносит числа и без черты. */
.km-stat { display: flex; flex-direction: column; gap: 2px; }
.km-stat__value { font-family: var(--km-font-display); font-size: 28px; font-weight: 700; line-height: 1.1; color: var(--km-accent); }
.km-stat__label { font-size: 13px; color: var(--km-text-secondary); }

/* Файлы занятия — блок вложения.
   Список в одной карточке: знак по типу слева, имя, под ним формат и размер,
   действие справа. Макет `design/lesson-blocks-v1.html`, 19 августа 2026.

   Черта между строками, а не отступ: файлов в блоке сколько угодно,
   и на пяти строках отступы читались бы как пять отдельных карточек. */
/* Группа файлов во вкладке «Материалы» (5.8.3, макет шага 3).

   КАРТОЧКУ НЕСЁТ СТРОКА, А НЕ ГРУППА — правка 28 августа 2026, найдена
   глазами на снимке. Первая редакция надела рамку на группу, и файлы
   внутри слиплись в таблицу с разделительными линиями; в макете
   наоборот: каждый файл — своя карточка, а имя занятия стоит голым
   текстом над списком, без рамки и без подложки.

   Отсюда же и промежуток: 20 между группами берётся у самой группы,
   а не складывается из двух рамок подряд. */
/* Обёртка панели: своя, чтобы промежуток между группами задавала
   только группа. Внутри стопки, раздающей зазор детям, 20 у группы
   складывалось бы с зазором стопки — намерено 20, на экране выходило 44. */
.km-materials { display: block; }
.km-grp { margin-bottom: 20px; }
.km-grp:last-child { margin-bottom: 0; }
.km-grp__h {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin: 0 0 8px;
}
.km-grp__name { flex: 1; min-width: 140px; font-size: 14.5px; font-weight: 700; color: var(--km-text); }
/* Причина не сжимается и переносится целиком: «откроется 5 сентября»
   без даты — не причина, а намёк. */
.km-grp__why { flex: none; font-size: 12.5px; color: var(--km-text-muted); }
/* Закрытая группа глуше, но читается: файлы видны и названы — правило
   отображения закрытых узлов, то же самое (12.2). */
.km-grp--locked .km-grp__name { color: var(--km-text-muted); }

/* СТРОКА-КАРТОЧКА — ТОЛЬКО ВНУТРИ ВКЛАДКИ, и это не осторожность.

   `.km-file` общий с блоком вложения в занятии, и там он выглядит иначе:
   плоская строка внутри карточки `.km-files`. Так рисует его свой макет,
   `lesson-blocks-v2.html`, и так человек видит его с 1.108.0.

   То есть один класс носит два вида в двух макетах — родня `.km-hint`,
   у которого было два определения. Разница в том, что здесь они не
   складываются: вид выбирается тем, внутри чего строка стоит. Сними
   область — и блок в занятии переоденется заодно. */
.km-grp .km-file {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: 14px;
  padding: 12px 16px;
  margin-bottom: 7px;
  border-bottom: 1px solid var(--km-border);
}
.km-grp .km-file:last-child { margin-bottom: 0; border-bottom: 1px solid var(--km-border); }
/* Закрытая — на тусклой подложке, тем же цветом, каким помечены
   спокойные поверхности в системе. Своего цвета для этого заводить
   не пришлось: `--km-surface-soft` в системе не объявлен вовсе,
   а `--km-surface-alt` есть и означает ровно это. */
.km-grp .km-file--locked { background: var(--km-surface-alt); }
.km-grp--locked .km-file__name { color: var(--km-text-muted); }
.km-grp--locked .km-file__glyph { color: var(--km-text-muted); }

.km-files {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  padding: 4px 18px;
}
.km-file {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 13px 0;
  border-bottom: 1px solid var(--km-border-soft);
  text-decoration: none;
  color: inherit;
}
.km-file:last-child { border-bottom: none; }
/* Закрытая строка — не ссылка, и указателя «нажми» у неё нет:
   живая на вид, но неработающая, она обещала бы переход, которого
   не будет. Файл при этом виден и назван (12.2). */
.km-file--locked { cursor: default; }
.km-file--locked .km-file__action { color: var(--km-text-muted); }
.km-file__glyph { flex: none; width: 22px; height: 22px; color: var(--km-accent); }
.km-file__body { flex: 1; min-width: 0; }
/* 14,5 — из макета `design/lesson-blocks-v2.html`, правка 27 августа 2026.
   Было 15, потому что рядом полоса обратной связи держала 14.

   ЧИСЛО СОСЕДА С ТЕХ ПОР СМЕНИЛОСЬ, и строка правится вместе с ним: макет
   `design/feedback-block-v3.html` даёт `.km-feedback__text` те же 14,5.
   Оба числа измерены в макетах, а не подобраны на глаз, и теперь они
   одинаковые — два блока стоят рядом на одной странице. */
.km-file__name { display: block; font-size: 14.5px; line-height: 1.35; color: var(--km-text); }
.km-file__meta { display: block; font-size: 12px; color: var(--km-text-muted); margin-top: 2px; }

/* Действие бледнее подписи намеренно: оно повторяет то, что и так понятно
   из строки, и спорить с именем файла за внимание не должно. */
.km-file__action { flex: none; width: 19px; height: 19px; color: #B7C1CD; }
.km-file:hover .km-file__name { color: var(--km-accent); }
.km-file:hover .km-file__action { color: var(--km-accent); }

/* Файл удалили, строка осталась. Не ссылка и потому не притворяется живой:
   место, за которым нет данных, называет причину. */
.km-file--missing { cursor: default; }
.km-file--missing .km-file__glyph,
.km-file--missing .km-file__name { color: var(--km-text-muted); }

/* ПОЛОСА ОБРАТНОЙ СВЯЗИ — ОДНА КАРТОЧКА, ЗАГОЛОВОК ВНУТРИ НЕЁ.
   Макет `design/feedback-block-v3.html`, 27 августа 2026.

   До этого блок собирался из `.km-note km-note--warm km-note--action`,
   а заголовок стоял НАД плашкой отдельной строкой — и читался как два
   блока подряд: подпись и почему-то тёплая полоса под ней. Макет v2,
   по которому это собиралось, заголовка не содержал вовсе; v3 заменяет
   в нём блок обратной связи целиком и говорит прямо: карточка одна.

   Своё имя, а не модификатор плашки. `.km-note` — флекс-СТРОКА (значок
   слева, текст справа), и всякий прямой ребёнок в ней становится колонкой;
   здесь же две строки друг под другом. Модификатором это выражалось бы
   переопределением `display` у чужого компонента, то есть второй правдой
   о том, как `.km-note` устроен.

   ЗНАК ПЕРЕЕХАЛ НАВЕРХ, К ЗАГОЛОВКУ — довод из макета дословно: «два знака
   на разной высоте в одной карточке читались бы как два блока».

   Радиус 20 — то же число, что у соседних блоков занятия ниже, и стоит оно
   ЗДЕСЬ, а не только в `.km-content`: полосу показывает ещё и предпросмотр
   в редакторе, где `.km-content` нет вовсе. До этой правки она приезжала
   туда с радиусом плашки (10) — то есть предпросмотр показывал не то, что
   увидит курсант. Меняется вместе с числом в `.km-content` ниже. */
.km-feedback {
  background: var(--km-warm-bg);
  border: 1px solid var(--km-warm-border);
  border-radius: 20px;
  padding: 16px 18px;
  color: var(--km-warm-text);
}

/* Шапка: знак и заголовок в одной строке, по центру друг друга. */
.km-feedback__head {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 10px;
}
.km-feedback__glyph { flex: none; width: 22px; height: 22px; color: var(--km-accent); }

/* Заголовок — свой кегль, а не `km-h5`.

   `km-h5` это 20px заголовочным шрифтом: он верен для заголовка НАД
   карточкой и слишком крупен внутри тёплой полосы, где спорил бы
   с самой фразой. Шрифт назван явно: заголовок здесь `h2`, и без этого
   он взял бы заголовочный шрифт темы, которого в макете нет. */
.km-feedback__title {
  margin: 0;
  font-family: var(--km-font);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--km-warm-text);
}

/* Строка под шапкой: фраза и кнопка. Кнопка не сжимается — иначе на узком
   надпись переносится по буквам; строка переносится целиком, потому что
   надпись это название группы сообщества, а они длинные. */
.km-feedback__row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.km-feedback__row > .km-btn { flex: none; }
.km-feedback__text {
  flex: 1;
  min-width: 180px;
  font-size: 14.5px;
  line-height: 1.5;
}

@media (max-width: 640px) {
  /* Кнопка уходит на свою строку целиком: прижатая вправо, она читалась бы
     как продолжение фразы. Число то же, что у `.km-note--action` выше:
     «Практикум по Местам Вознесения 2026» даёт кнопку в 302 пикселя. */
  .km-feedback__row > .km-btn { width: 100%; text-align: center; }
}

/* «После просмотра видео»: одна фраза со ссылкой на занятие.
   Макет `design/lesson-blocks-schedule-v1.html`, 26 августа 2026.

   Своих правил у блока два, и оба про фразу: карточку, заголовок и отступы
   он берёт готовыми — `km-card km-card__pad` и `km-h5`. Заводить под него
   третье имя значило бы завести второе определение карточки. */
.km-after__text { font-size: 16px; line-height: 1.65; margin: 0; }

/* Подчёркивание на наведении — здесь, а не у глобальной ссылки: в сплошной
   фразе ссылку надо отличать от текста не только цветом, а в карточках
   и кнопках подчёркивание было бы лишним. */
.km-after__text a:hover { text-decoration: underline; text-underline-offset: 2px; }

/* КАРТОЧКА БЛОКА В РЕДАКТОРЕ — шапка, тело, метка вида.
   Макет `design/lesson-blocks-schedule-v1.html`, раздел «Изнутри»,
   решение Алены 27 августа 2026.

   До этого блоки выглядели стандартным Gutenberg: чёрные подписи полей,
   объяснение прозой абзацем. Вид у него не наш, и на странице занятия
   человек видит одно, а правит в другом. Карточка возвращает узнаваемость:
   шапка говорит, ЧТО это за блок, метка справа — какого он вида.

   Заведено пока для одного блока. Остальные пять приводятся отдельной
   задачей: у каждого свои поля, и переписывать их скопом значит править
   пять экранов вслепую. */
.km-ed {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  overflow: hidden;
}
.km-ed__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid var(--km-border);
}
.km-ed__title {
  flex: 1;
  min-width: 150px;
  font-size: 13.5px;
  font-weight: 600;
  margin: 0;
}

/* Метка вида — тёплая и мелкая: она подпись к заголовку, а не второй
   заголовок. Спорить с названием блока за внимание ей незачем. */
.km-ed__kind {
  flex: none;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--km-warm-bg);
  color: var(--km-warm-text);
}
.km-ed__body { padding: 14px 16px 16px; }

/* ПРЕДПРОСМОТР В РЕДАКТОРЕ БЛОКА — «Как увидит курсант».
   Макет `design/lesson-blocks-v2.html`, 27 августа 2026.

   Отделён пунктиром намеренно: сплошная черта читалась бы как край
   карточки, и предпросмотр стал бы похож на ещё одно поле. Пунктир
   говорит «дальше не настройка, а результат». */
.km-preview {
  margin-top: 14px;
  padding-top: 13px;
  border-top: 1px dashed var(--km-border);
}
.km-preview__cap {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  margin: 0 0 9px;
}

/* ПРЕДПРОСМОТР ПОКАЗЫВАЕТ БЛОК ТАК ЖЕ, КАК СТРАНИЦА. Правка 27 августа 2026.

   На странице радиус блокам занятия задаёт `.km-content` — контейнера этого
   в редакторе нет, и карточка файла приезжала туда с базовым 16 вместо 20,
   а тёплая плашка с 10. Подпись при этом обещает «как увидит курсант»,
   то есть предпросмотр показывал не то, что обещал.

   Селектор ПОТОМКА, а не прямого ребёнка: `ServerSideRender` кладёт ответ
   сервера внутрь своей безымянной обёртки, и `>` не совпал бы. Проверено
   в настоящем редакторе, а не предположено: цепочка родителей карточки —
   `DIV.` → `DIV.km-preview`. */
.km-preview .km-files,
.km-preview .km-audio,
.km-preview .km-note { border-radius: 20px; }

/* СПРЯТАНО ОТ ГЛАЗ, НО НЕ ОТ КЛАВИАТУРЫ И НЕ ОТ ДИКТОРА.

   Не `display: none` и не `[hidden]`: скрытое ими поле выпадает из обхода
   табуляцией вовсе. Заведено под выбор файла с компьютера — родное поле
   `input[type=file]` рисует серую кнопку браузера, которую не оформить
   ничем, поэтому кнопкой служит подпись `label`, а само поле прячется
   этим классом и остаётся рабочим. */
.km-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* РАСПИСАНИЕ ТРАНСЛЯЦИЙ — строки эфиров в карточке занятия.
   Макет `design/lesson-blocks-schedule-v1.html`, 27 августа 2026.

   ИМЯ `km-schedule`, А НЕ `km-live-row` ИЗ МАКЕТА, и это не своеволие:
   рядом уже живёт семья `.km-live__*` — карточка ближайшего эфира
   на странице курса, с `__row`, `__when`, `__kicker`. Имена `km-live-row`
   и `km-live__row` различаются одним знаком, и спутают их в первый же раз,
   когда кто-нибудь будет искать «где строка эфира».

   Правило проекта спрашивает у имени не «занято ли», а «с чем его спутают».

   Строки идут почти встык (`gap: 2px`): между ними нет черты, и расстояние
   в макете отделяет подложку выделенной строки от соседней, а не строки
   друг от друга. */
/* РАМКИ И ФОНА У РАСПИСАНИЯ НЕТ — решение Алены 27 августа 2026 по снимку:
   на белом фоне занятия карточка не нужна, остаются заголовок и строки.

   Отступ между заголовком и списком поэтому свой: раньше его давала
   `km-stack` карточки, а вместе с карточкой ушёл и он. */
.km-schedule-block { margin-block: 28px; }
.km-schedule-block__title { margin: 0 0 12px; }
.km-schedule { display: flex; flex-direction: column; gap: 2px; }

.km-schedule__row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border-radius: var(--km-radius-control);
}

/* Дата с днём недели и временем — одним куском: перенести их порознь
   значит получить «19:00 МСК» отдельной строкой без даты. */
.km-schedule__when { flex: none; font-size: 14.5px; }
.km-schedule__day { color: var(--km-text-muted); font-weight: 400; }
.km-schedule__dash { flex: none; color: var(--km-text-muted); }
.km-schedule__name {
  flex: 1;
  min-width: 170px;
  font-size: 14.5px;
  color: var(--km-accent);
  text-decoration: none;
}
a.km-schedule__name:hover { text-decoration: underline; text-underline-offset: 2px; }

.km-schedule__tag {
  flex: none;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: var(--km-radius-badge);
}

/* БЛИЖАЙШИЙ ВЫДЕЛЕН ПОДЛОЖКОЙ И МЕТКОЙ — решение Алены. Одной метки мало:
   человек ищет ближайший глазами, а глаз находит пятно раньше слова. */
.km-schedule__row--next { background: var(--km-warm-bg); }
.km-schedule__row--next .km-schedule__when { font-weight: 700; }
.km-schedule__row--next .km-schedule__tag { background: var(--km-peach); color: var(--km-warm-text-strong); }

/* ИДЁТ СЕЙЧАС — красным, и красный здесь свой, а не `--km-danger`:
   тревоги нет, есть «сейчас». Тот же цвет, что у карточки идущего эфира. */
.km-schedule__row--now { background: var(--km-danger-bg); }
.km-schedule__row--now .km-schedule__when { font-weight: 700; color: var(--km-live); }
.km-schedule__row--now .km-schedule__tag { background: #F8D7D3; color: var(--km-live); }
.km-schedule__row--now .km-schedule__name { color: var(--km-live); font-weight: 600; }

/* «Дальше по курсу ещё две трансляции» — приписка к списку, а не строка
   в нём: она про то, чего в списке нет. */
/* Поля у приписки НЕТ: расстояние в карточке задаёт `gap` контейнера,
   и своё поле у абзаца складывалось бы с ним. Общая мерка стендов это
   и поймала — «у абзацев в колонках нет своих полей». */
.km-schedule__more { margin: 0; font-size: 12.5px; color: var(--km-text-muted); padding-left: 12px; }

/* Неделя после последнего эфира: список пуст, и это сказано словами.
   Пустая карточка читалась бы как поломка. */
.km-schedule__empty { font-size: 14.5px; color: var(--km-text-secondary); line-height: 1.6; margin: 0; }

@media (max-width: 560px) {
  /* На узком тире уходит, а название встаёт своей строкой: три куска
     в одну строку не помещаются, и перенос по словам рвал бы дату. */
  .km-schedule__row { gap: 6px; }
  .km-schedule__dash { display: none; }
  .km-schedule__name { min-width: 0; flex-basis: 100%; }
}

/* СЛУЖЕБНАЯ ПОЛОСА — ТО, ЧТО ВИДИТ ВЕДУЩИЙ И НЕ ВИДИТ КУРСАНТ.
   Макет `design/conditional-staff-note-v1.html`, 27 августа 2026.

   ПУНКТИР И ПРИГЛУШЁННЫЙ ЦВЕТ — не украшение: ведущий смотрит ту же
   страницу, что курсант, и сплошная рамка читалась бы как часть занятия.
   Пунктир говорит «это служебное», не крича об этом.

   Радиус тот же, что у блоков занятия (20), а у полосы сверху он на пиксель
   меньше — иначе её угол торчит из угла рамки. */
.km-staff {
  border: 1px dashed var(--km-staff-border);
  border-radius: 20px;
  background: transparent;
}
.km-staff__bar {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
  padding: 8px 14px;
  background: var(--km-staff-bg);
  border-radius: 19px 19px 0 0;
  border-bottom: 1px dashed var(--km-staff-border);
}
.km-staff__eye {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--km-staff);
  position: relative;
  top: 2px;
}

/* Условие — первым и весомее примечания: ведущий видит содержимое
   и первым делом спрашивает, кому оно показано. */
/* Полоса БЕЗ ТЕЛА — когда содержимое идёт следом само, а не внутри рамки.
   Так помечена страница закрытого дрипом занятия (27 августа 2026): материал
   под полосой обычный, и заворачивать его нельзя — плеер собирает прозу
   в карточки и пропускает всё, помеченное `data-lms-block`.

   Нижняя черта снимается: она разделяет полосу и тело, а тела здесь нет,
   и без этого у рамки выходила бы двойная линия снизу. */
.km-staff--alone .km-staff__bar { border-bottom: none; }

/* ПОМЕТКА И «ПОЧЕМУ ВИДНО ВАМ» — оба весомее подписи и оба УМЕЮТ СЖИМАТЬСЯ.

   `flex: none` здесь стоял с 27 августа и был верен ровно до того дня, когда
   в пометку приехало настоящее название занятия: «Не сдал задание „Правила
   участия в курсе“» — 39 знаков, и `0 0 auto` значит «не сжимайся ни за что».
   На 375 полоса от этого уезжала за край вместе со страницей. Та же семья,
   что неразрывное имя тега CRM в строке правила: перенос по словам возможен,
   а флексу запрещено даже пробовать.

   `min-width: 0` обязателен рядом: у флексового элемента минимум по умолчанию
   равен max-content, и без него `flex-shrink` ничего не меняет. */
.km-staff__cond,
.km-staff__you { flex: 0 1 auto; min-width: 0; font-size: 12px; color: var(--km-staff); }

/* Пометка весомее «почему видно вам», и различие это НЕ украшение.

   Первая редакция дала обоим 600 и развела точкой-разделителем через
   `::before`. На 375 полоса переносится ВСЕГДА — измерено на всех четырёх
   фикстурах стенда, — и точка оставалась висеть одна в начале строки,
   семью пикселями ни на чём. Разделитель, видимый только на широком, —
   это разделитель, которого нет там, где он нужнее.

   Весом различаются на любой ширине и без лишнего знака. */
.km-staff__cond { font-weight: 600; }
.km-staff__you { font-weight: 500; }

/* Подпись НЕ РАСТЯГИВАЕТСЯ на остаток строки. `flex: 1` здесь стоял, пока
   она была последней всегда: тянуться ей было некуда, и разницы не было
   видно. С 29 августа у закрытого занятия порядок другой — «Закрыто ·
   Откроется 5 сентября · Вам открыто как редактору», — и растянутая подпись
   отбрасывала бы фразу про редактора к правому краю, разрывая ряд пустотой
   там, где читается предложение. */
.km-staff__label { flex: 0 1 auto; min-width: 0; font-size: 12px; color: var(--km-text-muted); }
.km-staff__body { padding: 18px 20px; }

@media (max-width: 520px) {
  .km-staff__bar { gap: 6px; }
}

/* ПОДПИСЬ БЛОКА В ШАПКЕ КАРТОЧКИ РЕДАКТОРА — курсивом, рядом с названием.
   Тогда блоки различимы, не разворачиваясь.

   Название рядом с ней перестаёт растягиваться: место занимает подпись,
   а метка вида остаётся у правого края. Спрашивается это `:has()`, потому
   что у остальных блоков подписи нет и там название по-прежнему тянется. */
.km-ed__label {
  flex: 1;
  min-width: 110px;
  font-size: 12.5px;
  font-style: italic;
  color: var(--km-text-muted);
}
.km-ed__head:has( .km-ed__label ) .km-ed__title { flex: none; min-width: 0; }

/* УСЛОВИЕ И ЕГО ПОЛЕ — В ОДНОЙ РАМКЕ.
   Макет `design/conditional-block-v1.html`, 27 августа 2026.

   До этого условие, его поле и содержимое блока шли вперемешку, и глазом
   не было видно, где кончается настройка и начинается то, что увидит
   курсант. Рамка отвечает на это молча и сразу.

   Подложка светлее карточки, а не белая: так рамка читается как «здесь
   настройка», а белая область ниже — как «здесь содержимое». */
.km-cond {
  border: 1px solid var(--km-border);
  border-radius: 11px;
  padding: 13px 14px;
  background: var(--km-page);
}

/* СЛЕДСТВИЕ УСЛОВИЯ — тёплой строкой под рамкой.

   Не подсказка и не поле: это ответ на вопрос «и что из этого выйдет».
   Тёплая, как всё, что объясняет, а не предупреждает. */
.km-outcome {
  margin: 12px 0 0;
  background: var(--km-warm-bg);
  border: 1px solid var(--km-warm-border);
  color: var(--km-warm-text);
  border-radius: var(--km-radius-control);
  padding: 10px 13px;
  font-size: 12.5px;
  line-height: 1.55;
}

/* «БЛОК НЕ ПОКАЖЕТСЯ НИКОМУ» — ЖЁЛТЫМ, а не серой подписью.

   Своё имя, а не `km-sum`: тем именем в дизайн-системе названа сводка
   слияния аккаунтов — черта сверху и заголовок. Одно имя на два вида дало бы
   помесь, которую никто не задумывал: у проекта так уже было с `.km-hint`,
   и прожило это девять экранов.

   Цвет предупреждения, а не отказа: это не поломка, а недоделка автора,
   и красный здесь сказал бы «сломалось» там, где «не дописано». */
.km-outcome--warn {
  background: var(--km-warn-bg);
  border-color: var(--km-warn-border);
  color: var(--km-warn-text);
}

/* ОБЛАСТЬ СОДЕРЖИМОГО С ПОДПИСЬЮ «Что увидят подошедшие».

   Пунктир, а не сплошная: сплошная читалась бы как край карточки, и область
   стала бы похожа на ещё одно поле. Тот же довод, что у предпросмотра. */
.km-slot { margin-top: 14px; }
.km-slot__cap {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  margin: 0 0 7px;
}
.km-slot__area {
  border: 1px dashed var(--km-border);
  border-radius: 11px;
  padding: 14px 15px;
  background: var(--km-surface);
}

/* Пусто — говорится словами. Пустая рамка читается как поломка: человек
   видит место, в котором ничего нет, и не знает, так и задумано или нет. */
.km-slot__area--empty { color: var(--km-text-muted); }

/* ТА ЖЕ ОБЛАСТЬ, НО ПОДПИСЬ СБОКУ — заглушки блоков программы в редакторе.
   Правка 2 сентября 2026.

   Роль та же, что у области выше: место, в котором **будет** содержимое,
   и подпись, какое именно. Поэтому модификатор, а не своя семья: третье имя
   рядом стало бы вторым способом сказать одно, и правка пунктира поехала бы
   в одно место из двух.

   Заглушки идут подряд, и подпись сверху удваивала бы высоту каждой:
   четыре блока превратились бы в восемь строк. Сбоку — четыре строки
   и один левый край у всех абзацев.

   ## Ширина столбца задана, и это главное здесь

   До правки подписью служил `<strong>` внутри `.km-note`, а та —
   флексовая строка. Ширины у столбца не было, он подстраивался под слово:
   «Что внутри» переносилось на две строки, «Полоса с ценой» — на одну,
   и текст каждой заглушки начинался со своего отступа.

   Число измерено в настоящем редакторе, а не выбрано: самое длинное имя
   («Варианты участия») занимает 133 пикселя капслоком, остальные 80–114.
   Первая редакция ставила 132 — на один пиксель меньше нужного, — и имя
   переносилось на две строки при верной в остальном раскладке. Ровно тот
   случай, когда комментарий обещает то, чего код не делает: там было
   написано «берут в одну строку». 144 оставляют запас на пятое имя.

   ## Зазор свой, потому что чужого нет

   Редактор блоков между динамическими блоками не ставит ничего, и три
   заглушки подряд слипались в стену. 12 — тот же зазор, что между
   карточками в дизайн-системе. */
.km-slot--aside {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 12px 0;
}
.km-slot--aside .km-slot__cap {
  flex: none;
  width: 144px;
  margin: 0;
  /* Подпись капслоком мельче текста, и по базовой линии она уезжает вверх.
     Два пикселя ставят её вровень с первой строкой абзаца. */
  padding-top: 2px;
}
.km-slot--aside .km-slot__area {
  flex: 1;
  min-width: 0;
}

/* На узком столбца нет: 132 пикселя из 375 забирают треть строки, и абзац
   ломался бы по два слова. Подпись встаёт над областью — то же, что делает
   базовый `.km-slot`. */
@media (max-width: 640px) {
  .km-slot--aside { display: block; }
  .km-slot--aside .km-slot__cap { width: auto; padding-top: 0; margin: 0 0 7px; }
}

/* ПЕРЕКЛЮЧАТЕЛЬ ИСТОЧНИКА ФАЙЛА — одна полоса на три варианта.
   Макет `design/lesson-blocks-v2.html`.

   Имя `km-source`, а НЕ `km-seg`: рядом уже живёт `.km-segments` — чипы
   сегментов на экране зачислений, — и два имени, различающиеся хвостом,
   однажды спутают. Правило проекта спрашивает у имени не «занято ли»,
   а «с чем его спутают».

   Вариантов три, и они взаимоисключающие: это переключатель, а не набор
   кнопок. Отсюда `aria-pressed` у каждой и роль группы у полосы —
   без них экранный диктор прочитал бы три отдельные кнопки. */
.km-source {
  display: flex;
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  overflow: hidden;
  margin-bottom: 14px;
}
.km-source__pick {
  flex: 1;
  border: 0;
  border-right: 1px solid var(--km-border);
  background: var(--km-surface);
  font: inherit;
  font-size: 12.5px;
  line-height: 1.3;
  padding: 8px 6px;
  cursor: pointer;
  color: var(--km-text-secondary);
}
.km-source__pick:last-child { border-right: 0; }
.km-source__pick:hover { color: var(--km-text); }

/* ВЫБРАННЫЙ ОТЛИЧАЕТСЯ, а не просто помечен: признак существует тогда,
   когда он отличается от соседнего. Фон и вес — два признака, потому что
   один цвет плохо виден тем, кто его не различает. */
.km-source__pick[aria-pressed="true"] {
  background: var(--km-warm-bg);
  color: var(--km-warm-text);
  font-weight: 600;
}
.km-source__pick:focus-visible {
  outline: 2px solid var(--km-accent);
  outline-offset: -2px;
}

@media (max-width: 782px) {
  .km-source { flex-direction: column; }
  .km-source__pick { border-right: 0; border-bottom: 1px solid var(--km-border); }
  .km-source__pick:last-child { border-bottom: 0; }
}

/* Срок у ещё не открытого занятия — ОТДЕЛЬНОЙ СТРОКОЙ.
   Ссылка при этом остаётся живой (решение человека 26 августа 2026):
   человек и знает срок, и может сходить посмотреть, что его ждёт.
   Бледнее самой фразы намеренно — это приписка к ней, а не сообщение.

   СТРОКОЙ, А НЕ ХВОСТОМ ТОЙ ЖЕ ФРАЗЫ — правка 27 августа, и она следствие
   другой. Пока точку в конце ставили мы, срок шёл после неё и читался
   отдельно. Теперь фраза принадлежит человеку целиком, и точки в ней может
   не быть вовсе: на стенде вышло «…План работы на месяц кураторам Откроется
   16.03.2099» — два предложения в одно. Разделитель, зависящий от того,
   поставил ли человек точку, — не разделитель. */
.km-after__when { display: block; color: var(--km-text-muted); }

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

   Полоса — `input[type=range]`, и цвет ей задаёт `accent-color`: заливка
   до ползунка и сам ползунок красятся браузером сами. Своя заливка
   потребовала бы ширины из кода, то есть оформления из скрипта, —
   а тот же довод, что у `<progress>` в правиле 13: величина выражается
   разметкой, которая её понимает. Заодно бесплатно приходит перемотка
   с клавиатуры и чтение экранным диктором.

   Видео своих правил не имеет намеренно — его выводит плагин OmniVideo
   со своей вёрсткой. Свой блок видео был написан в 5.2 и убран вместе
   с закрытием занятия по досмотру: мера прохождения у нас сданные работы. */
.km-audio {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  padding: 16px 18px;
}
.km-audio__head { display: flex; align-items: center; gap: 14px; }
.km-audio__play {
  flex: none;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--km-accent);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.km-audio__sign { width: 19px; height: 19px; color: #FFFFFF; }
.km-audio__body { flex: 1; min-width: 0; }
.km-audio__name { display: block; font-size: 15px; line-height: 1.35; color: var(--km-text); }
.km-audio__meta { display: block; font-size: 12px; color: var(--km-text-muted); margin-top: 2px; }
.km-audio__line { margin-top: 14px; display: flex; align-items: center; gap: 12px; }
.km-audio__time { flex: none; font-size: 12px; color: var(--km-text-muted); font-variant-numeric: tabular-nums; }
/* Полоса позиции: заливка и ползунок лежат друг на друге.
   `<progress>` рисует, `<input type=range>` двигает — обоим величину даёт
   разметка, а не посчитанная ширина. Одного ползунка не хватает: браузер
   красит незаполненный остаток дорожки тёмным и хода к этому цвету не даёт. */
.km-audio__bar { position: relative; flex: 1; min-width: 0; height: 12px; display: flex; align-items: center; }
.km-audio__fill {
  position: absolute;
  left: 0;
  right: 0;
  width: 100%;
  height: 4px;
  border: none;
  border-radius: 2px;
  background: var(--km-track);
  -webkit-appearance: none;
  appearance: none;
}
.km-audio__fill::-webkit-progress-bar { background: var(--km-track); border-radius: 2px; }
.km-audio__fill::-webkit-progress-value { background: var(--km-accent); border-radius: 2px; }
.km-audio__fill::-moz-progress-bar { background: var(--km-accent); border-radius: 2px; }
.km-audio__track {
  position: relative;
  width: 100%;
  height: 12px;
  margin: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
}
.km-audio__track::-webkit-slider-runnable-track { height: 12px; background: transparent; }
.km-audio__track::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border: none;
  border-radius: 50%;
  background: var(--km-accent);
}
.km-audio__track::-moz-range-track { height: 12px; background: transparent; }
.km-audio__track::-moz-range-thumb { width: 12px; height: 12px; border: none; border-radius: 50%; background: var(--km-accent); }

/* Сам звук не показывается: играет он, а видит человек разметку выше.
   Спрятанный элемент играет так же, как показанный. */
.km-audio__el { display: none; }

/* Иконка в кружке */
.km-icon-circle {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--km-peach);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--km-accent);
}
.km-icon-circle--lg { width: 42px; height: 42px; }
/* Знак задаётся здесь, а не остаётся при шестнадцати из разметки: кружок
   тут 34, и знак в шестнадцать сидел в нём точкой. Та же правка, что
   у строки узла, и то же решение. */
.km-icon-circle svg { width: 28px; height: 28px; }
.km-icon-circle--lg svg { width: 36px; height: 36px; }
.km-icon-circle--neutral { background: #F1F4F8; color: var(--km-text-muted); }
.km-icon-circle--accent { background: var(--km-accent); color: #fff; }
/* Открытый и нетронутый — пустой кружок с обводкой, как у строки узла
   (`km-node-state--open`). Серый здесь читался бы «закрыт»: на настоящем
   курсе таких модулей два из шести, и оба выглядели бы запертыми. */
.km-icon-circle--open { background: transparent; border: 1.3px solid #C9D3DE; color: var(--km-text-secondary); }
.km-icon-circle svg { display: block; }

/* Курсанты: стопка кружков слева, счёт справа — так в макете страницы курса.
   Заголовка у карточки нет, строка счёта его и заменяет. */
.km-peers { display: flex; align-items: center; gap: 12px; }
.km-peers > .km-avatars { flex: none; }

/* ССЫЛКИ ПРАВКИ НА ФРОНТЕ. Раздел 12.3.
 *
 * Видит их только администратор, и потому строка неяркая: она служебная
 * и не должна спорить с тем, ради чего человек пришёл. Ученик её не видит
 * вовсе — решение принято на сервере, а не спрятано классом.
 *
 * Разделитель между ссылками — точкой через `::before` у соседа: так же,
 * как в остальных перечнях фронта, и без пустых `<span>` в разметке.
 * Первой ссылке точка не ставится, потому что `+` берёт только тех,
 * у кого есть предшественник. */
.km-editlinks {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin: 0;
  font-size: 13px;
  color: var(--km-text-muted);
}
.km-editlinks__label { color: var(--km-text-muted); }
.km-editlinks__item { color: var(--km-text-secondary); text-decoration: none; }
.km-editlinks__item:hover { color: var(--km-accent); }
.km-editlinks__item + .km-editlinks__item::before {
  content: '\00B7';
  margin-right: 10px;
  color: var(--km-text-muted);
}

/* ПЕРЕЧЕНЬ «КУРС ВКЛЮЧАЕТ» — ЗНАК ГОЛЫЙ, КАК В ДЕРЕВЕ.
 *
 * Правка 19 августа 2026 по находке человека: карточка отстала от нового
 * вида дерева на целое решение. Кружки под знаками сняли 19 августа
 * (`km-node-state`), а здесь остался `km-icon-circle` — персиковая заливка
 * 34 пикселя со знаком 28. Рядом с деревом, где знак 19 и голый, карточка
 * читалась как из другой системы.
 *
 * Заливка несла состояние, а в перечне состояния нет вовсе: строка
 * называет вид узлов, а не узел. Кружок отвечал на вопрос, которого
 * здесь не задают.
 *
 * Размер и ритм взяты у карточки дерева одним числом (`--km-glyph`)
 * и одним зазором: обе читаются одной системой, а не «похоже».
 *
 * `km-icon-circle` при этом жив и не тронут — он про знак В КРУЖКЕ,
 * и такой знак ещё понадобится. Просто здесь его не нужно. */
.km-kit { display: flex; flex-direction: column; gap: 10px; --km-node-glyph: var(--km-glyph); }
.km-kit__glyph {
  width: var(--km-node-glyph);
  height: var(--km-node-glyph);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--km-accent);
}
.km-kit__glyph svg { display: block; width: var(--km-node-glyph); height: var(--km-node-glyph); }
/* Закрепления толщины здесь БОЛЬШЕ НЕТ, и это половина смены набора
   29 августа 2026. Разбор — у знака дерева ниже, в одном месте. */

/* Перечень КАРТОЧКАМИ — продающая страница.
 *
 * В сайдбаре участника колонка верна: там 320 пикселей ширины и вертикаль
 * единственная возможная. На продающей странице перечень идёт во всю
 * ширину.
 *
 * ЗДЕСЬ СТОЯЛ `--row`: одна белая плашка на всю ширину, внутри пары
 * «знак + число» в строку. Воздуха выходило больше, чем содержимого,
 * и правки просил человек. Макет `course-page-guest-v2.html` рисует
 * три отдельные карточки в ряд: знак в тёплом квадрате, число крупно,
 * под ним строка пояснения.
 *
 * Модификатор, а не второй компонент: строки те же, знак тот же, меняется
 * раскладка и подача. Прежний `--row` снят целиком — его звало ровно одно
 * место, и два способа сказать «перечень на продающей» разошлись бы молча.
 *
 * ЧИСЛА ИЗ МАКЕТА, и где они разошлись с системой — сказано вслух:
 *   сетка 3 × gap 16      — как в макете;
 *   карточка 22px 24px    — как в макете; рамка, радиус и тень токенами;
 *   квадрат знака 40×40   — как в макете; радиус 12 у макета свой,
 *                           между `--km-radius-control` (10)
 *                           и `--km-radius-card` (16) — взят макетный;
 *   число 22px            — как в макете; в шкале заголовков такого нет
 *                           (20 и 24), и заводить шестую ступень ради
 *                           одного места дороже, чем написать здесь;
 *   пояснение 14px        — у макета 14,5; половин пикселя шкала не знает,
 *                           взято 14 (`--km-text-secondary` того же кегля).
 */
.km-kit--cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.km-kit--cards .km-kit__item {
  align-items: flex-start;
  gap: 14px;
  padding: 22px 24px;
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
}

/* Знак в тёплом квадрате — только в карточках.
   В сайдбаре участника он голый, и накрой это правило базовый класс,
   переоделся бы и он. */
.km-kit--cards .km-kit__glyph {
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--km-warm-bg);
}

.km-kit__item { display: flex; align-items: center; gap: 11px; }
.km-kit__text { font-size: 14px; line-height: 1.4; }

/* Число и пояснение под ним. Столбик, чтобы `margin` не складывался
   с зазором: расстояния задаёт `gap`, а не поля абзацев. */
.km-kit__lines { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.km-kit__value { font-family: var(--km-font-display); font-size: 22px; font-weight: 700; line-height: 1.2; }
.km-kit__note { font-size: 14px; line-height: 1.5; color: var(--km-text-secondary); }

/* На узком — в столбик: три карточки по 116 пикселей не читаются.
   Порог 900 тот же, что у `km-grid-3`: сетки рядом и ломаться обязаны
   вместе, иначе на планшете одна секция в ряд, другая в столбик. */
@media (max-width: 900px) {
  .km-kit--cards { grid-template-columns: 1fr; }
}

/* Вкладка «Прогресс». Задача 9.30, взамен сниппета WPCode 167709411317960.

   Столбики за шестьдесят дней — свой SVG, а не чужая библиотека: на странице
   курса внешних загрузок нет, и шестьдесят прямоугольников их не стоят.

   Высота столбика приходит РАЗМЕТКОЙ (это данные, как `value` у `<progress>`),
   а цвет — ступенями `data-lms-level`, теми же четырьмя, что у насыщенности
   в трекере курса. Правило 13 про оформление: ступеней четыре и они здесь
   названы, а не посчитаны по месту. */
.km-chart { width: 100%; }
.km-chart__svg {
  display: block;
  width: 100%;
  height: 96px;
}
.km-chart__bar {
  fill: var(--km-border);
}
.km-chart__bar[data-lms-level="1"] { fill: var(--km-warm-border); }
.km-chart__bar[data-lms-level="2"] { fill: var(--km-peach); }
.km-chart__bar[data-lms-level="3"] { fill: var(--km-accent-light); }
.km-chart__bar[data-lms-level="4"] { fill: var(--km-accent); }

/* Группы сделанного — свёрнутые створки.

   Свёрнуты по умолчанию намеренно: у «Самоценности» тридцать две формы,
   и развёрнутый список вытеснил бы с экрана всё остальное. Ту же форму
   имеет модуль в дереве, и это не совпадение — там тот же вопрос. */
.km-groups { display: flex; flex-direction: column; }
.km-group + .km-group { border-top: 1px solid var(--km-border-soft); }
.km-group__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 0;
  cursor: pointer;
  list-style: none;
}
.km-group__head::-webkit-details-marker { display: none; }
.km-group__head::after {
  content: '›';
  margin-left: auto;
  color: var(--km-text-muted);
  transition: transform 0.15s;
}
.km-group[open] .km-group__head::after { transform: rotate(90deg); }
.km-group__head:hover { color: var(--km-accent); }
.km-group__count {
  font-size: 12px;
  color: var(--km-text-muted);
  background: var(--km-bg);
  border-radius: 20px;
  padding: 1px 8px;
}

/* Строка формы: вид, название, дальше — что у неё есть.

   Название занимает всю оставшуюся ширину, а не колонку: иначе на узком
   экране оно ужимается до двух слов, а различаются строки именно им. */
.km-formrow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  font-size: 14px;
}
.km-formrow + .km-formrow { border-top: 1px solid var(--km-border-soft); }
.km-formrow__name { flex: 1; min-width: 0; }
.km-formrow__when { flex: none; font-size: 12px; color: var(--km-text-muted); }
.km-formrow__count {
  flex: none;
  font-size: 12px;
  color: var(--km-warm-text-strong);
  background: var(--km-warm-bg);
  border-radius: 20px;
  padding: 1px 8px;
}

/* Достижения — кружок с картинкой и подписью рядом. */
.km-awards { display: flex; flex-wrap: wrap; gap: 10px; }
.km-award {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  background: var(--km-warm-bg);
  border: 1px solid var(--km-warm-border);
  border-radius: var(--km-radius-control);
  padding: 6px 10px;
}
.km-award img {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

/* Перечень строками: у «ещё не заполнено» строки собраны своим классом,
   а не `km-lineup__item` — у них другой состав и своя кнопка. */
.km-lineup--rows { gap: 0; }

/* Календарь занятий и форм — сетка месяца. Задача 9.29.

   Отдельная от `.km-daygrid` семья намеренно: трекер практик это ПУСТОЕ
   МЕСТО под чужой блок, у него нет ни чисел, ни состояний, ни нажатия.
   Свести их в один класс значило бы, что первая же правка живого календаря
   поедет в мёртвую сетку — и наоборот.

   Насыщенность выражена ступенями `data-lms-level`, а не посчитанной
   прозрачностью: правило 13 запрещает оформление по месту, а ступеней
   ровно четыре и они здесь названы. Само число ступени считает браузер
   от самого плотного дня месяца — это данные, как `value` у `<progress>`. */
/* Блок целиком: сетка стоит ближе к своей шапке, чем карточки друг к другу.
   Класс не декоративный — без него `km-stack` разносит шапку, дни недели
   и сетку на общий отступ, и месяц читается как три разных блока. */
.km-month {
  --km-stack-gap: 10px;
}
.km-month__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
/* Месяц — СОСТОЯНИЕ блока, а не его имя, и потому набран не как подпись
   карточки: капслок с разрядкой у `.km-section-title` занят названием
   («ТРЕКЕР КУРСА»), и второй такой строкой месяц спорил бы с ним за роль
   заголовка. Здесь обычное начертание, крупнее подписи и тем же цветом,
   что основной текст. */
.km-month__title {
  margin: 0;
  text-align: center;
  flex: 1;
  font-size: 14px;
  font-weight: 600;
  color: var(--km-text);
}
.km-month__nav {
  font-family: inherit;
  font-size: 15px;
  line-height: 1;
  color: var(--km-text-secondary);
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  padding: 6px 10px;
  cursor: pointer;
  flex: none;
}
.km-month__nav:hover:not(:disabled) {
  color: var(--km-accent);
  border-color: var(--km-warm-border);
}
/* Выключенная стрелка не притворяется живой: вперёд листать некуда,
   будущей активности не бывает. Причина сказана подсказкой на самой
   кнопке — выключенное без причины читается как поломка. */
.km-month__nav:disabled {
  color: var(--km-text-muted);
  background: var(--km-page);
  cursor: default;
}
.km-month__dows,
.km-month__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.km-month__dows {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  text-align: center;
}
.km-month__cell {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  line-height: 1;
  border-radius: 8px;
  background: var(--km-page);
  border: 1px solid var(--km-border-soft);
  color: var(--km-text-muted);
}
/* Пустое место до первого числа месяца: клетки нет вовсе, а не пустая. */
.km-month__cell--void {
  background: none;
  border-color: transparent;
}
/* Четыре ступени насыщенности. Тёплая семья та же, что у всего живого
   в системе: белый — ничего, персиковый — много. */
.km-month__cell[data-lms-level="1"] {
  background: var(--km-warm-bg);
  border-color: var(--km-warm-bg);
  color: var(--km-warm-text);
}
.km-month__cell[data-lms-level="2"] {
  background: var(--km-peach);
  border-color: var(--km-peach);
  color: var(--km-warm-text-strong);
}
.km-month__cell[data-lms-level="3"] {
  background: var(--km-accent-light);
  border-color: var(--km-accent-light);
  color: var(--km-surface);
}
.km-month__cell[data-lms-level="4"] {
  background: var(--km-accent);
  border-color: var(--km-accent);
  color: var(--km-surface);
}
/* Сегодня — рамкой, а не заливкой: заливка спорила бы с насыщенностью
   и меняла бы её смысл на «сегодня плотный день». */
.km-month__cell--today {
  border-color: var(--km-accent);
  font-weight: 700;
}

/* Трекер практик — сетка по дням месяца.

   Клетки пустые и не нажимаются: механика приходит блоком Gravity Forms
   (5.6), здесь только место. Курсора-руки поэтому нет — он обещал бы
   нажатие, которого не будет.

   **НА СТРАНИЦЕ КУРСА ЭТОГО БЛОКА БОЛЬШЕ НЕТ** — решение человека
   25 августа 2026: там встал живой трекер курса (`.km-month`), а две
   сетки подряд были нашим дублем. Классы оставлены намеренно, и это
   не мёртвый код: их потребитель — макет `design/course-page-member.html`
   и трекер бесплатных курсов, к которому вернёмся. Убирать их вместе
   с блоком значило бы вычеркнуть одобренное макетом. */
.km-daygrid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}
.km-daygrid__cell {
  aspect-ratio: 1;
  border-radius: 9px;
  background: var(--km-page);
  border: 1px solid var(--km-border);
}

/* Аватары стопкой.

   Один класс на два вида: `span` — кружок-знак, `img` — настоящее лицо
   курсанта (12.2). Второго класса нет намеренно: кружок у них один и тот же,
   а вся разница в том, есть внутри картинка или нет.

   `object-fit` обязателен: фотографии профиля бывают любых пропорций,
   и без него портрет 3:4 съезжает в овал. `background` при этом остаётся
   и работает подложкой, пока картинка грузится, — и остаётся кружком,
   если она не загрузится вовсе. */
.km-avatars { display: flex; }
.km-avatars__item {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--km-surface);
  background: var(--km-warm-border);
  object-fit: cover;
}
.km-avatars__item + .km-avatars__item { margin-left: -11px; }

/* Дата кружком — эфиры */
.km-date-chip {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: var(--km-peach);
  color: #A8592F;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: none;
  line-height: 1;
}
.km-date-chip__day { font-size: 16px; font-weight: 700; }
.km-date-chip__month { font-size: 9px; letter-spacing: 0.04em; }

/* Дерево курса.
 *
 * Базовая строка — ЭТО ТОПИК: он самый частый (шестьдесят на боевом курсе
 * против двадцати занятий), и черта снизу разделяет топики внутри карточки.
 * Строка занятия перебивает её ниже — у неё свои поля и черты нет вовсе. */
.km-node {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 0;
  border-bottom: 1px solid var(--km-border-soft);
}
.km-node:last-child { border-bottom: none; }

/* СТРОКА ЗАНЯТИЯ — ШАПКА КАРТОЧКИ. Макет v3: поля 14 и 18, черты нет.
 *
 * Селектор двойной, и оба нужны: у занятия с топиками строка лежит
 * в `<summary>` складки, у занятия без топиков — прямо в карточке.
 * Написать один — и половина занятий останется с полями топика, причём
 * та половина, которая на боевом реже (два занятия из двадцати).
 *
 * Скругление снимается: подложку текущего обрезает сама карточка своим
 * `overflow: hidden`, а собственный радиус строки дал бы скруглённый
 * прямоугольник внутри скруглённого — тот самый вид «полосы с обрывами»,
 * от которого отказались. Специфичность здесь (0,2,0) против (0,1,0)
 * у `km-node--current`, так что порядок в файле роли не играет. */
.km-tree__card > .km-node,
.km-tree__fold-head > .km-node {
  padding: 14px 18px;
  gap: 12px;
  border-bottom: none;
  border-radius: 0;
}

/* ЛЕСТНИЦА УРОВНЕЙ. Правка вёрстки 1, решение человека 8 августа 2026.
 *
 * Модуль 20/700 → занятие 17/600 → лист 14/400 вторичным цветом. До правки
 * занятие и лист были одним кеглем и одним весом, и уровень выражал только
 * отступ: на 119 строках подряд иерархия не читалась вовсе.
 *
 * Уровень берётся из РАЗМЕТКИ, а не из класса-модификатора: лист третьего
 * уровня — тот, что лежит в `.km-tree__topics`, и другого способа туда
 * попасть нет. Заведи мы `km-node--topic`, у одного и того же факта стало бы
 * два написания — положение в дереве и класс, — и однажды они разошлись бы
 * молча.
 *
 * Третьего признака у листа нет намеренно (решение человека): справа
 * у него уже стоит тип словами, а фон развёл бы два узла одного уровня
 * так, будто они с разных. */
.km-node__title { flex: 1; min-width: 0; font-size: 15px; font-weight: 600; line-height: 1.35; }
.km-node--done .km-node__title { color: var(--km-text-secondary); }
.km-node--current .km-node__title { font-weight: 700; }
.km-node--locked .km-node__title { color: var(--km-text-muted); }
/* Причина закрытия — под названием, а не под знаком: 49 это поле карточки
   (18) плюс знак (19) плюс зазор строки (12). Числа сложены здесь потому,
   что все три уже объявлены выше и разъехались бы поодиночке.

   ДЕРЕВО КУРСАНТА ЭТИМ КЛАССОМ БОЛЬШЕ НЕ ПОЛЬЗУЕТСЯ — с 27 августа 2026
   причина стоит в самой строке (`km-node__why`). Здесь остались строки
   админки: корзина (`trash.js`) и предпросмотр в редакторе узла. */
.km-node__reason { font-size: 13px; line-height: 1.5; color: var(--km-text-muted); padding: 0 18px 12px 49px; }

/* ПРИЧИНА ЗАКРЫТИЯ — ПРАВЫЙ СТОЛБЕЦ СТРОКИ. Правка 27 августа 2026,
   макет `design/course-map-locked-v1.html`.

   Отдельным абзацем под строкой она растила карточку закрытого занятия
   вдвое против открытого — 108 пикселей против 50 на боевом Практикуме,
   — и под датой оставалось пустое место, читавшееся как «здесь что-то
   будет». Закрытое и открытое обязаны быть одной высоты: различают их
   цвет и замок, а не размер.

   Приглушённый цвет, а не тёплый: рядом в том же столбце стоят состояния
   (`km-node__state`), и они про то, что человеку **делать**. Причина про
   то, чего ждать, и спорить с ними за внимание ей незачем.

   Переносится по словам, а не `nowrap`: в отличие от «вы здесь» и «на
   6:24» причина бывает длинной — «Откроется, когда вы заполните «Отзыв
   о курсе»». Потолок ширины держит название читаемым: без него причина
   съедала бы строку до переноса по слову. */
.km-node__why {
  flex: none;
  max-width: 45%;
  font-size: 12px;
  line-height: 1.4;
  color: var(--km-text-muted);
  text-align: right;
}

/* Ссылка внутри причины остаётся ссылкой и в правом столбце. */
.km-node__why .km-link-out { color: inherit; }

/* ОДИН ДЕНЬ НА ВСЁ ЗАНЯТИЕ — СТРОКОЙ НАД ЧАСТЯМИ.
   Полоса цвета страницы внутри белой карточки: она про всё, что ниже,
   и потому лежит над ними, а не в одной из строк. */
.km-tree__when-all {
  margin: 2px 0 8px;
  padding: 7px 12px;
  border-radius: 9px;
  background: var(--km-page);
  font-size: 13px;
  line-height: 1.45;
  color: var(--km-text-muted);
}

/* И тогда строка самого занятия про день молчит, пока складка раскрыта:
   иначе один и тот же день стоял бы дважды, через восемь пикселей друг
   от друга. Свёрнутой складке он по-прежнему нужен — там его больше
   негде прочитать. */
.km-tree__fold--one-day[open] > .km-tree__fold-head .km-node__why { display: none; }

/* Третья ступень лестницы — лист внутри занятия: топик, практика, дневник,
   план, протокол. Правило одно на всех, потому что уровень у них общий:
   различает их знак в кружке и тип словами справа, а не кегль. */
.km-tree__topics .km-node__title { font-size: 14px; font-weight: 400; color: var(--km-text-secondary); }

/* Размер знака — вторая ступень лестницы уровней: 19 у занятия, 16
   у топика, в колонке плеера 17 и 14. Число живёт переменной на дереве,
   а не в правиле знака: контейнер топиков объявляет своё, и лист получает
   его наследованием — объяви мы размер прямо у `.km-node-state`, лист
   унаследовал бы размер занятия.

   Парная переменная `--km-node-mark` снята вместе с `km-node__meta`:
   от неё считался отступ подписи, переносимой под название на узком
   экране, а подписи в правом столбце больше нет — там знаки и короткие
   слова, которые не переносятся. */
/* ЗНАК ГОЛЫЙ, БЕЗ КРУЖКА. Макет 19 августа 2026.
 *
 * Кружок снят: заливка и обводка несли состояние, а состояние переехало
 * вправо словом и галочкой. Оставленный, кружок отвечал бы на тот же
 * вопрос второй раз и слабее — персиковую заливку от прозрачной человек
 * не различал, о чём и был разбор.
 *
 * Размер знака 19 — из макета, и он же теперь размер всей метки: рамки
 * вокруг больше нет. Лестница уровней осталась на переменных, и третий
 * уровень по-прежнему мельче. */
.km-node-state {
  width: var(--km-node-glyph, 19px);
  height: var(--km-node-glyph, 19px);
  flex: none;
  /* Знак внутри кружка: цвет берётся отсюда через `currentColor`,
     и второго места, где решают цвет состояния, не заводится. */
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
}
.km-node-state svg { display: block; width: var(--km-node-glyph, 16px); height: var(--km-node-glyph, 16px); }

/* ЗАКРЕПЛЕНИЕ ТОЛЩИНЫ СНЯТО ВМЕСТЕ СО СМЕНОЙ НАБОРА, 29 августа 2026.
 *
 * Здесь стояло `vector-effect: non-scaling-stroke` на знаках дерева
 * и перечня, и стояло **правильно** — для прежнего набора. Те рисунки
 * жили в обрезанной рамке `3.5 3.5 13 13`, и без закрепления сжатая рамка
 * утолщала обводку вместе с рисунком: у галочки она уходила с полутора
 * пикселей на три, а у шапки модуля вылезала за кружок.
 *
 * Новый набор — Lucide и Tabler — рисован в рамке 24 со штрихом 2, и
 * закрепление здесь делает ровно ту беду, от которой защищало: 2 px
 * на любом размере. На 19 задумано 1,58, на 14 — 1,17; закреплённый
 * штрих был бы вдвое толще там, где знак мельче, то есть в колонке
 * плеера и у частей.
 *
 * Естественное деление даёт то, подо что рисовано: 1,33 px на шестнадцати.
 * Проверено измерением в браузере, а не расчётом на бумаге.
 *
 * Толщину и скругления задаёт сам `<svg>` (`window.lmsGlyph`), одним
 * написанием на весь набор: по фигурам их не пишут ни Lucide, ни Tabler,
 * и первый же добавленный знак приехал бы с чужой толщиной. */
/* Цвет знака — по состоянию, заливки нет ни у одного. */
.km-node-state--done { color: var(--km-accent-light); }
.km-node-state--locked { color: var(--km-text-muted); }
.km-node-state--current { color: var(--km-accent); }
/* Открытый и нетронутый — пустой кружок с обводкой. Цвет знака **обязан
   быть задан здесь**: у базового класса он белый (под заливку персиком
   и оранжевым), а тут подложки нет вовсе, и знак белым по светлому
   пропадал бы целиком. Найдено глазами после пункта 2: svg на месте,
   размер верный, а в кружке пусто. */
.km-node-state--open { color: var(--km-text-secondary); }

/* ПРАВЫЙ СТОЛБЕЦ. Порядок случаев решает `state()` в скрипте: место
   остановки, дата эфира, галочка, «вы здесь», пусто.

   Слова набраны ОДИНАКОВО — 12 пикселей тёплым, — и это макет v3.
   До него «на 6:24» было приглушённо-тёплым, «в среду, 19:00» серым,
   «вы здесь» полужирным тринадцатым: три вида у одного столбца, и разница
   между ними ничего не значила. Читается столбец как одно место, где
   написано, что с этой строкой сейчас. */
.km-node__state { flex: none; display: flex; align-items: center; }
.km-node__state--done { width: 18px; height: 18px; color: var(--km-accent); }
.km-node__state--at,
.km-node__state--when,
.km-node__state--here {
  font-size: 12px;
  color: var(--km-accent);
  white-space: nowrap;
}

/* ТЕКУЩИЙ — ТЁПЛАЯ ПОДЛОЖКА ПО ШИРИНЕ КАРТОЧКИ.
 *
 * Раньше он различался только заливкой кружка, и человек его не находил:
 * ровно с этого начался разбор состояний. Подложка, оранжевый знак
 * и полужирное название — три признака вместо одного слабого.
 *
 * Скругление здесь про ТОПИК: он лежит внутри карточки с полями, и
 * подложка обязана быть прямоугольником со скруглением, а не полосой
 * от края до края. У строки занятия радиус снимается — её углы даёт
 * сама карточка. */
.km-node--current {
  background: var(--km-warm-bg);
  border-radius: 10px;
}
.km-node--current .km-node__title { font-weight: 700; }

/* Подложка топика выходит за колонку текста на 10 и ровно настолько же
   возвращает подбивкой: черты между топиками остаются на своих местах,
   а тёплый прямоугольник не липнет к буквам. Разъедься эти два числа —
   строка поедет вбок относительно соседей. */
.km-tree__topics .km-node--current {
  margin-left: -10px;
  margin-right: -10px;
  padding-left: 10px;
  padding-right: 10px;
}

/* У ПРОЙДЕННОГО НАЗВАНИЕ ПРИГЛУШЕНО: прочитанное уходит на второй план. */
.km-node--done .km-node__title { color: var(--km-text-muted); }

/* Админские таблицы */
/* Ширина в сто процентов — не косметика, а потолок.

   Без неё таблица берёт ширину по содержимому, и девять столбцов
   с неразрывными значениями вытолкнули страницу за край окна на семь
   пикселей. `overflow-x: auto` тут не спасает: он объявлен на самой
   `<table>`, а таблица не создаёт для него блока прокрутки. */
.km-table { background: var(--km-surface); border: 1px solid var(--km-border); border-radius: 14px; box-shadow: var(--km-shadow-card); overflow-x: auto; width: 100%; }

/* Подписи столбцов — по левому краю, как и значения под ними.
   Браузер по умолчанию ставит `th` по центру, и подпись оказывается
   не над своим столбцом, а между двумя. Первые настоящие `<th>` в проекте
   появились в 5.6, до того таблицы собирались из `div`, и правила
   для них не было. Нашлось глазами на «Моих работах». */
/* ПОДПИСИ СТОЛБЦОВ — ТИХИЕ И ЗАГЛАВНЫМИ.

   До 21 августа 2026 здесь стояло `font-weight: 700` и больше ничего:
   подписи наследовали шестнадцать пикселей основного текста и выходили
   крупнее и темнее значений под ними. Читают в таблице значения,
   а подпись нужна один раз — понять, что это за столбец.

   Ровно этот вид уже был записан в системе для второй разновидности
   таблицы — `.km-table__row--head > *` строкой ниже, у той, что собрана
   из `div`. Одно и то же правило было написано дважды и разошлось:
   у одной таблицы подписи тихие, у другой громкие. Теперь они говорят
   одинаково. */
.km-table th {
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  padding: 12px 12px;
  white-space: nowrap;
  border-bottom: 1px solid var(--km-border);
}

/* Отступов у ячеек не было вовсе: браузер даёт единицу, и строки
   слипались, а разделяло их только то, что содержимое само по себе
   высокое. */
/* КЕГЛЬ ЯЧЕЙКИ ЗАДАН ЯВНО, И ЭТО ПРАВКА У СЕБЯ, А НЕ ГЛУШЕНИЕ ЧУЖОГО.

   Шестнадцать пикселей ячейкам давал наш собственный `body` выше: эта
   таблица стилей грузится и в админке, где у `body` тринадцать, — и
   перебивает их. То есть крупный кегль в списке не наследство WordPress,
   а наше умолчание, поставленное под абзац, который читают.

   Строка списка не абзац: её просматривают глазами по столбцу, и четырнадцать
   здесь — размер значения, а не текста. Померено на стенде: до правки
   «14 фев 2026», «8 августа», «Бесплатно», «самозапись» и прочерк потока
   шли шестнадцатым.

   Мельче остаётся то, что задумано мельче: почта под именем (12),
   рассрочка (12), подписи столбцов (11). */
.km-table td {
  padding: 12px 12px;
  font-size: 14px;
  border-bottom: 1px solid var(--km-border-soft);
  vertical-align: middle;
}

.km-table tbody tr:last-child td { border-bottom: none; }

/* Значение столбца не переносится по словам, если это одна величина.

   «Весна 2026» и «31 дек 2026» — по слову на строку в узкой колонке:
   таблица о девяти столбцах раздаёт им ширину поровну, и всё, что длиннее
   одного слова, встаёт лесенкой в четыре строки. Читаются они при этом
   как несколько значений подряд. */
.km-table td .km-nowrap,
.km-table__name,
.km-state,
.km-progress__num,
.km-progress__zero { white-space: nowrap; }
.km-table tbody tr:hover { background: var(--km-surface-alt); }

/* Имя и почта в строке списка.

   `.km-text` — шестнадцать пикселей с межстрочным 1.65, размер абзаца,
   который читают. Строка таблицы не абзац: имя в ней опознают взглядом,
   и полуторный межстрочный растит каждую строку из двухсот. */
.km-table__name { font-size: 14px; font-weight: 600; line-height: 1.3; }
/* Почта обрезается, а не растит столбец.

   Она длиннее имени и почти всегда длиннее всего остального в строке:
   таблица о девяти столбцах отдаёт ей ширину по содержимому, и вся
   страница уезжает за край окна. Померено: без потолка — на семь
   пикселей шире окна на стенде и заметно больше в админке, где слева
   стоит меню.

   Обрезать её можно, а имя нельзя: человека опознают по имени, почта
   же нужна для поиска — и ищут её не глазами по столбцу, а полем поиска
   наверху. Целиком она остаётся в подсказке. */
.km-table__mail {
  font-size: 12px;
  color: var(--km-text-muted);
  margin-top: 2px;
  display: block;
  max-width: 22ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Приглушённое значение: то, что повторяется в каждой строке и потому
   спорит за внимание с тем, ради чего в таблицу смотрят. Источник,
   прочерк вместо потока, дата у замороженного. */
.km-quiet { color: var(--km-text-muted); }

/* Подпись под значением — второй строкой и мельче. */
.km-sub { display: block; font-size: 12px; margin-top: 2px; }

/* Тревожное. Не «плохое»: красным здесь отмечено то, что требует
   действия в ближайшие дни, — а не то, что уже случилось. */
.km-alarm { color: var(--km-danger); }

.km-nowrap { white-space: nowrap; }

/* СОСТОЯНИЕ — ТОЧКА И СЛОВО.

   Слово рядом с точкой обязательно, и это не запас на всякий случай:
   цвет один не говорит ничего тому, кто его не различает, и ничего
   не говорит вообще, пока не выучишь, какой чему соответствует.

   Точка своя, а не буква и не эмодзи: она обязана быть видна при любом
   шрифте и не зависеть от того, что подставит система. Тот же довод,
   по которому в карточке эфира точка тоже своя. */
.km-state {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  /* Наравне со строкой: состояние — такое же значение столбца, как дата
     рядом, и мельче остального оно читалось бы как подпись. */
  font-size: 14px;
}

.km-state__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
}

.km-state--ok { color: var(--km-text-secondary); }
.km-state--ok .km-state__dot { background: var(--km-ok); }

.km-state--warn { color: var(--km-warn); }
.km-state--warn .km-state__dot { background: var(--km-warn); }

.km-state--alarm { color: var(--km-danger); }
.km-state--alarm .km-state__dot { background: var(--km-danger); }

.km-state--off { color: var(--km-text-muted); }
.km-state--off .km-state__dot { background: var(--km-text-muted); }
.km-table__row { display: flex; align-items: center; gap: 14px; padding: 13px 18px; border-bottom: 1px solid var(--km-border-soft); min-width: 1040px; }
.km-table__row:last-child { border-bottom: none; }
.km-table__row:hover { background: var(--km-surface-alt); }
.km-table__row--head { background: var(--km-surface-alt); border-bottom: 1px solid var(--km-border); padding: 12px 18px; }
.km-table__row--selected { background: var(--km-warm-bg); }
.km-table__row--head > * {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--km-text-muted);
}
.km-table__person { flex: 1 1 260px; min-width: 220px; display: flex; align-items: center; gap: 11px; }

/* Ручка перетаскивания */
.km-drag { cursor: grab; color: #C9D3DE; flex: none; }
.km-drag:active { cursor: grabbing; color: var(--km-accent); }
.km-drop-line { height: 2px; border-radius: 1px; background: var(--km-accent); }
/* Бросок ВНУТРЬ строки — подсветкой самой строки, а не чертой рядом с ней.
   Черта отвечает на вопрос «между какими», подсветка — «в какую»; одним
   признаком три зоны сброса неразличимы, и человек узнаёт, куда попал,
   только после броска. Заведено пунктом 5 правок по узлам. */
.km-drop-into {
  background: var(--km-warm-bg);
  box-shadow: inset 0 0 0 2px var(--km-accent);
  border-radius: var(--km-radius-control);
}
.km-dragging {
  background: var(--km-surface);
  border: 1px solid var(--km-warm-border);
  border-radius: var(--km-radius-control);
  box-shadow: var(--km-shadow-lift);
}

/* Липкая полоса — курс и цена */
.km-sticky-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(246, 248, 251, 0.94);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--km-border);
  padding: 12px 28px;
  display: flex;
  align-items: center;
  gap: 20px;
}

/* ПОЛОСА С ЦЕНОЙ — ПРОДОЛЖЕНИЕ ОБЛОЖКИ, А НЕ СТРОКА НА ФОНЕ СТРАНИЦЫ.
   Решение Алены 4 сентября 2026.

   Модификатор, а не правка `km-sticky-bar`: тот же класс носят четыре
   административных экрана, где полоса — панель действий. Белая карточка
   со скруглением досталась бы им молча.

   Подложка, рамка и тень — карточки (`.km-card`), значения те же.
   `backdrop-filter` здесь снят: подложка непрозрачна, и размывать нечего.

   СТЫК. Зазор стопки снимается отрицательным полем ровно в ступень —
   считается из той же переменной, а не пишется числом. Скругление
   остаётся ОДНО НА ДВА БЛОКА: у обложки только верх, у полосы только низ.
   Скруглить обе стороны стыка значило бы нарисовать два блока рядом,
   а просят один.

   `:has()` — потому что решает СОСЕДСТВО, а не сам блок: обложка без
   полосы под собой скругляется как была. Не поддержи браузер `:has()`,
   обложка останется скруглённой снизу — шов будет виден, но ничего
   не сломается. */
.km-sticky-bar--price {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
  backdrop-filter: none;

  /* ПОЛЯ, ПРОМЕЖУТОК И ВЫСОТА — ОТ КАРТОЧКИ УЧАСТНИКА. Решение человека
     8 сентября 2026: «полоса у гостя куцая по сравнению с полосой
     прогресса».

     Числа не подобраны на глаз, у каждого есть источник, и источник
     назван — иначе через этап их поправят «на глаз» обратно:

       24px  — поля `.km-card__pad`, ими обёрнута карточка участника;
       28px  — промежуток `.km-headline`, между прогрессом и кнопкой;
       149px — измеренная высота той карточки на ширине 1100.

     Высота здесь `min-height`, а не `height`: длинное название потока
     переносит строку, и жёсткая высота обрезала бы её.

     Последнее число живёт содержимым чужого блока: изменится состав
     карточки участника — оно устареет. Ловит это мерка стенда, которая
     сравнивает полосу с живой карточкой, а не с числом. */
  padding: 24px;
  gap: 28px;
  min-height: 149px;
  align-items: center;
}

.km-hero + .km-sticky-bar--price {
  margin-top: calc(-1 * var(--km-gap-group));
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.km-hero:has( + .km-sticky-bar--price ) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

/* Левая часть полосы на продающей странице (7.4): название курса и подпись
   про набор. Занимает всё свободное место, чтобы цена и кнопка прижались
   вправо, и сжимается до нуля — длинное название иначе выталкивает кнопку
   «Записаться» за край, то есть ровно то, ради чего полоса и висит. */
.km-sticky-bar__main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  margin-right: auto;
}

.km-sticky-bar__title {
  font-weight: 700;
  font-size: 15px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ШАПКА ГОСТЯ ДЫШИТ. Решение человека 8 сентября 2026: «между заголовком,
   подзаголовком, именем и кнопкой тесно, всё слиплось в один ком».

   У участника в шапке четыре строки на 374 пикселях высоты — воздух
   остаётся сам собой. У гостя их шесть: добавились цена с кнопкой
   и счётчик курсантов, и тот же промежуток в 6 пикселей превратился
   в ком.

   Шаги взяты из системы, а не выдуманы: `--km-gap-label` — расстояние
   ВНУТРИ группы, `--km-gap-group` — МЕЖДУ группами. Метка вида,
   заголовок, подзаголовок и имя ведущей — одна группа: они называют
   курс. Кнопка и счётчик — другие, и потому отбиты шире.

   Вес селектора (0,4,0) не украшение: `.km-hero--brand.km-hero--over
   .km-hero__body` весит (0,3,0) и задаёт `gap: 6px` — правило полегче
   молча проиграло бы ему, и на экране не изменилось бы ничего.

   Только у гостя: у участника то же место дышит, и трогать его незачем. */
.km-page--guest .km-hero--brand.km-hero--over .km-hero__body,
.km-page--guest .km-hero--brand .km-hero__body {
  gap: var(--km-gap-label);
}

.km-page--guest .km-hero__body > .km-actions,
.km-page--guest .km-hero__body > .km-hero__learners {
  margin-top: calc( var(--km-gap-group) - var(--km-gap-label) );
}

/* Карточка варианта участия (7.4).
   Кнопка прижата к низу: карточки в ряду разной высоты — у одной три строки
   про содержимое, у другой пять, — и без этого кнопки встают лесенкой. */
.km-plan .km-card__body { gap: 12px; height: 100%; }
.km-plan .km-btn { margin-top: auto; }

/* Сетки и ритм */
.km-grid-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(272px, 1fr)); gap: 20px; }

/* Ровно три колонки — секции «Для кого» и «Что изменится» гостевой страницы,
   макет `course-page-guest-v2.html`.

   Отдельно от `km-grid-cards`, и это не дубль: та набирает столько колонок,
   сколько влезет по 272 пикселя, — на широком экране получилось бы четыре
   дорожки под три карточки, и последняя треть строки пустовала бы. Здесь
   карточек всегда три, и они делят ширину поровну, как в макете.

   На узком — в столбик: три карточки по 116 пикселей не читаются. */
.km-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }

/* ОБЁРТКА ЯДРА УБИРАЕТСЯ ИЗ РАСКЛАДКИ, а не из разметки.

   Группе блоков ядро возвращает старый контейнер
   `wp-block-group__inner-container` — на теме без `theme.json`, а BuddyBoss
   именно такая. Контейнер становится единственным ребёнком сетки, и три
   карточки внутри него встают в один столбец: `display: grid` на месте,
   колонок три, а карточки все в первой.

   Отменить это атрибутом блока нельзя — пробовал `layout`, обёртка
   возвращается всё равно. `display: contents` убирает её из раскладки,
   и карточки становятся прямыми детьми сетки; разметка при этом остаётся
   ядровой, и ядро правит её как захочет.

   Найдено стендом измерением: в разметке всё выглядело верно. */
.km-grid-3 > .wp-block-group__inner-container { display: contents; }

@media (max-width: 900px) {
  .km-grid-3 { grid-template-columns: 1fr; }
}
/* Ритм стопки — переменной, а не числом: его читают вкладки, чтобы
   добрать свои 24 воздуха, откуда бы ритм ни шёл. Своего ритма у каждого
   макета свой: 24 у страницы курса, 26 у «Моих работ», 28 у витрины,
   40 у карты. Шестнадцать — умолчание, а не правда про все экраны. */
.km-stack { display: flex; flex-direction: column; gap: var(--km-stack-gap, var(--km-gap-group)); }

/* Страница курса разносит блоки на 24 — так в `design/course-page-member.html`.
   Крошки, обложка и карточка прогресса стояли на 16, и восьми пикселей
   не хватало ровно там, где два экрана кладут рядом. */
.km-page--course { --km-stack-gap: 24px; }
.km-section { display: flex; flex-direction: column; gap: 24px; }

/*
 * Ритм между соседними секциями.
 *
 * Внутри секции расстояния задаёт её собственный `gap`, а между секциями
 * его не задавал никто: в макетах они разнесены руками — инлайновым
 * `margin` у разделительной черты, — и в дизайн-систему это не переехало.
 * Оба административных экрана из-за этого стояли блоками вплотную:
 * кнопка «Новое предложение» упиралась в таблицу, а шапка — в чипы.
 *
 * Нашлось стендом предложений (3.6) измерением промежутков. Стенд
 * зачислений был при этом зелёным — он их не мерил, а глазами «вплотную»
 * читается как «так и задумано».
 */
.km-section + .km-section { margin-top: 28px; }

/*
 * Секция, у которой первый ребёнок — подпись блока.
 *
 * Общий ритм секции — 24, и это расстояние **между блоками** внутри неё.
 * Подпись блоком не является: она про то, что под ней. Оставленная
 * на общем ритме, она стоит ровно посередине между верхним и нижним
 * и читается как принадлежащая верхнему — на экране потоков «ПОТОКИ
 * ПРОГРАММЫ» так и прилипла к полю выбора над собой.
 *
 * Число то же, что у подписи на карточке, и берётся оно из одного места.
 */
.km-section--tight { gap: var(--km-gap-label); }

/*
 * Подпись под заголовком секции — мера чтения, и только она.
 *
 * Цвет и кегль сюда не переписываются: их даёт `km-text-secondary`, и класс
 * ставится рядом с ним. Второе написание того же разошлось бы с ним молча —
 * ровно так `.km-hint` прожил девять экранов в двух смыслах.
 *
 * Мера в `ch`, а не в пикселях: она про длину строки, а длина строки
 * считается знаками. На 1180 пикселях колонки подпись без меры даёт строку
 * знаков в сто тридцать — глаз теряет начало следующей.
 *
 * Заведено под «Для кого этот курс» (макет `course-page-guest-v2.html`,
 * где у секции есть `sec__sub`); готового вида под эту роль в системе
 * не было — проверено поимённо: `km-sub` это мелкая подпись в 12 px,
 * `km-hint` — подсказка под полем, `km-meta` — служебная строка.
 */
.km-section__sub { max-width: 62ch; }

/*
 * Тесная колонка: подпись и то, к чему она относится, стоят рядом,
 * а не через воздух секции. Заведено под заголовок карточки потока
 * и название её программы — 24 пикселя между ними читались бы как два
 * разных блока.
 */
.km-stack--tight { gap: var(--km-gap-label); }

.km-page { max-width: 1180px; margin: 0 auto; padding: 32px 28px 96px; }

/* ПРОДАЮЩАЯ СТРАНИЦА: ЧУЖИЕ ПОЛЯ У ПРЯМЫХ ДЕТЕЙ ОБНУЛЯЮТСЯ.

   Описание программы приходит чужой разметкой и падает прямыми детьми
   колонки, а колонка — это flex с промежутком 16. Тема при этом даёт
   каждому абзацу поле снизу:

       p { margin: 0 0 1.6875rem }      ← buddyboss-theme/assets/css/theme.css

   Двадцать семь пикселей темы складывались с нашими шестнадцатью, и между
   абзацами выходило СОРОК ТРИ. Померено на боевом 8 сентября 2026; пустых
   абзацев в содержимом при этом не было ни одного.

   Расстояние задаёт ПРОМЕЖУТОК КОЛОНКИ — отношение соседей, а не поле
   у каждого абзаца: тогда сумма не набегает, у первого нет лишнего
   отступа сверху, у последнего снизу.

   Модификатор, а не правка `.km-page`: обнули мы поля у детей всякой
   страницы — и абзацы материала в занятии слиплись бы, потому что там
   содержимое живёт по правилам темы законно. То же различение, что
   стоит в комментарии ниже про обнуление своего, а не элементов.

   Только прямые дети: то, что лежит внутри блока-группы, флексовым
   ребёнком не является, и промежутка колонки к нему не применяется. */
.km-page--guest > * { margin: 0; }

/* ---------------------------------------------------------------------------
   Внутри темы (5.9)
   ---------------------------------------------------------------------------
   Тема даёт своим `p` и заголовкам вертикальные отступы — на стенде их не было,
   и наши подписи внутри темы разъезжались: «Программа курса» и счётчик рядом
   с ней вставали на разные уровни, а между секциями появлялся лишний воздух.

   Обнуляется **только своё**: перечислены наши классы, а не элементы. Обнули
   мы `p` внутри `.km-page` — и абзацы материала в занятии слиплись бы в один
   кусок, потому что содержимое приходит чужой разметкой и живёт по правилам
   темы законно.
   --------------------------------------------------------------------------- */
.km-h1, .km-h2, .km-h3,
/*
 * Расстояния задаёт `gap` контейнера, а не поля абзацев.
 *
 * Список был написан и **двое в него не попали** — `.km-text` и `.km-hint`.
 * Браузер даёт `<p>` поле в один кегль, оно складывается с `gap`, и карточки
 * сайдбара держали 32 против заявленных 16, а подсказка под кнопкой — 28.
 * Найдено общей проверкой стенда, а не глазами: разница в шестнадцать
 * пикселей на глаз выглядит просто «просторнее».
 *
 * Та же семья, что склонение по числу в 9.8: правило записано, а перевели
 * не всех.
 */
.km-section-title, .km-meta, .km-eyebrow,
.km-text, .km-text-secondary, .km-hint, .km-note,
.km-hero__title, .km-hero__lead, .km-empty__title, .km-empty__text,
.km-card__title, .km-course-card__title, .km-price,
.km-progress__meta, .km-progress__label {
	margin: 0;
}

/* Счётчик прижимается вправо: заголовок слева, число справа — так в макете. */
.km-row > .km-meta { margin-left: auto; }

/* Заглушка под изображение */
.km-placeholder {
  background: repeating-linear-gradient(135deg, #EDF1F6 0 9px, #E4EAF1 9px 18px);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--km-mono);
  font-size: 11px;
  color: var(--km-text-muted);
  width: 100%;
  height: 100%;
}

/* Правила, которые нельзя нарушать
   1. Один акцентный цвет на экран, одна главная кнопка в блоке.
   2. Оранжевый — только действие и статус, не декор.
   3. Никаких градиентов в интерфейсе, теней жёстче --km-shadow-card, обводок толще 1px.
   4. Закрытый контент показывается с причиной словами, не прячется.
   5. Прогресс — в занятиях («8 из 20»), процент только как дополнение.
   6. Пустые состояния — приглашение, а не извинение.
   7. Никакой геймификации: ни стриков, ни рейтингов активности.
   8. Время везде московское. */

/* ==========================================================================
   ДОБАВЛЕНО: поля, флаги, дерево на три уровня, аккордеон, меню кабинета,
   пустые состояния, пагинация, всплывающая панель.
   Только существующие переменные — новых цветов и радиусов нет.
   ========================================================================== */

/* --- Поля ввода --- */
.km-field { display: flex; flex-direction: column; gap: 7px; }
.km-label { font-size: 13px; font-weight: 600; color: var(--km-text-secondary); }
.km-hint { font-size: 12px; line-height: 1.5; color: var(--km-text-muted); }
.km-hint--error { color: var(--km-danger); }

/* `.km-write__area` здесь же, а не своим набором значений: поле письма
   выглядит как обычное многострочное, и второе написание тех же цветов
   разошлось бы с этим на первой правке. Скругление у него своё — оно
   ниже, там же, где объяснён стык с панелью. */
.km-input,
.km-textarea,
.km-select,
.km-write__area {
  font-family: inherit;
  font-size: 15px;
  line-height: 1.5;
  color: var(--km-text);
  background: var(--km-surface-alt);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  padding: 12px 14px;
  width: 100%;
  transition: border-color 0.16s, background 0.16s;
}
.km-input::placeholder,
.km-textarea::placeholder { color: var(--km-text-muted); }

/* СПИСОК РОСТОМ С ПОЛЕ ВВОДА. Найдено стендом правил 22 августа 2026.
 *
 * `line-height` у `select` движок игнорирует — проверено подстановкой:
 * значение меняется, высота нет. Оттого список выходил на три пикселя ниже
 * соседнего поля, а `.km-row` выравнивает по центру — и подписи двух полей
 * в одной строке вставали на разных уровнях.
 *
 * Общая мерка стендов ловит это с первого этапа («подписи в строке стоят
 * на одном уровне»), и на этом экране она проваливалась молча: отчёт
 * не печатался вовсе.
 *
 * Рост задаётся снизу тем же счётом, каким его набирает поле ввода:
 * строка (1.5em) плюс отступы (24px) плюс рамки (2px). Числом здесь стояло
 * бы 48.5px, и первая же правка кегля развела бы их снова. */
.km-select,
select.km-input { min-height: calc(1.5em + 26px); }

.km-input:hover,
.km-textarea:hover,
.km-select:hover { border-color: var(--km-warm-border); }

.km-input:focus,
.km-textarea:focus,
.km-select:focus {
  outline: none;
  background: var(--km-surface);
  border-color: var(--km-warm-border);
  box-shadow: 0 0 0 3px var(--km-warm-bg);
}

.km-textarea { min-height: 116px; resize: vertical; }

/* Числовое поле — уже, выравнивание по правому краю */
.km-input--number { width: 120px; text-align: right; font-variant-numeric: tabular-nums; }

/* Выпадающий список: своя стрелка вместо системной */
.km-select {
  appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none'%3E%3Cpath d='M3 5.2l4 4 4-4' stroke='%239AA7B6' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
}

/* Поле с датой — иконка календаря слева задаётся обёрткой */
.km-input-group { position: relative; display: flex; align-items: center; }
.km-input-group__icon {
  position: absolute;
  left: 14px;
  display: flex;
  color: var(--km-text-muted);
  pointer-events: none;
  /*
   * РАЗМЕР ЗАДАЁТСЯ ЗДЕСЬ, А НЕ У КАЖДОГО ЗОВУЩЕГО. Найдено глазами
   * 23 августа 2026 на выборе программы.
   *
   * Класс не задавал размера вовсе, и держалось это на том, что первый
   * зовущий вешал его на `<span>`, а `<svg>` внутри размечал атрибутами
   * `width`/`height`. Повесь тот же класс прямо на `<svg>` — и он берёт
   * размер по умолчанию: 300×150. На экране это лупа во весь экран.
   *
   * Ни одна проверка этого не ловит: значок стоит `absolute`, за ширину
   * страницы не вылезает, класс в дизайн-системе есть. Видно только глазами.
   */
  width: 18px;
  height: 18px;
}

.km-input-group__icon svg {
  width: 100%;
  height: 100%;
  display: block;
}
.km-input-group .km-input { padding-left: 40px; }
.km-input-group:focus-within .km-input-group__icon { color: var(--km-accent); }
.km-input[type="date"] { font-variant-numeric: tabular-nums; }

/* Состояния */
.km-input.is-error,
.km-textarea.is-error,
.km-select.is-error { border-color: var(--km-danger-border); background: #FDF0EC; }
.km-input.is-error:focus,
.km-textarea.is-error:focus { box-shadow: 0 0 0 3px var(--km-danger-bg); border-color: var(--km-danger-border); }

.km-input:disabled,
.km-textarea:disabled,
.km-select:disabled {
  background: var(--km-border-soft);
  border-color: var(--km-border);
  color: var(--km-text-muted);
  cursor: not-allowed;
}

/* --- Чекбокс и переключатель --- */
.km-check {
  display: inline-flex;
  align-items: flex-start;
  gap: 10px;
  cursor: pointer;
  font-size: 15px;
  line-height: 1.5;
  user-select: none;
}
/* СТОЛБИК ФЛАЖКОВ — когда выбирают несколько из списка, а не одно из.
   Заведён 29 августа 2026 под выбор потоков в условном блоке: `km-check`
   сам по себе `inline-flex`, и без обёртки два флажка встают в строку,
   а третий переносится — читается как случайная сетка, а не как список.

   Зазор тот же, что между полями формы: строки здесь равноправны. */
.km-checks { display: flex; flex-direction: column; gap: 8px; }

.km-check input { position: absolute; opacity: 0; width: 0; height: 0; }
.km-check__box {
  width: 18px;
  height: 18px;
  flex: none;
  margin-top: 3px;
  border-radius: 5px;
  border: 1.5px solid #C9D3DE;
  background: var(--km-surface);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.16s, border-color 0.16s;
}
.km-check__box svg { opacity: 0; }
.km-check:hover .km-check__box { border-color: var(--km-warm-border); }
.km-check input:checked + .km-check__box { background: var(--km-accent); border-color: var(--km-accent); }
.km-check input:checked + .km-check__box svg { opacity: 1; }
.km-check input:focus-visible + .km-check__box { box-shadow: 0 0 0 3px var(--km-warm-bg); }
.km-check input:indeterminate + .km-check__box { background: var(--km-accent); border-color: var(--km-accent); }
.km-check input:indeterminate + .km-check__box svg { opacity: 1; }
.km-check input:disabled + .km-check__box { background: var(--km-border-soft); border-color: var(--km-border); }
.km-check input:disabled ~ .km-check__label { color: var(--km-text-muted); }

/* Компактный — для массового выбора в строке списка */
.km-check--row { gap: 0; }
.km-check--row .km-check__box { margin-top: 0; }

.km-check__label { display: flex; flex-direction: column; gap: 3px; }
.km-check__note { font-size: 12px; color: var(--km-text-muted); }

.km-switch { display: inline-flex; align-items: center; gap: 12px; cursor: pointer; font-size: 15px; user-select: none; }
.km-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.km-switch__track {
  width: 42px;
  height: 24px;
  flex: none;
  border-radius: 12px;
  background: var(--km-track);
  border: 1px solid var(--km-border);
  padding: 2px;
  display: flex;
  transition: background 0.16s, border-color 0.16s;
}
.km-switch__knob {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--km-surface);
  box-shadow: var(--km-shadow-card);
  transition: transform 0.16s;
}
.km-switch input:checked + .km-switch__track { background: var(--km-accent); border-color: var(--km-accent); }
.km-switch input:checked + .km-switch__track .km-switch__knob { transform: translateX(18px); }
.km-switch input:focus-visible + .km-switch__track { box-shadow: 0 0 0 3px var(--km-warm-bg); }
.km-switch input:disabled + .km-switch__track { background: var(--km-border-soft); }

/* --- Дерево: три уровня вложенности --- */

/* ЛЕСТНИЦА КРУЖКОВ — одна таблица на весь фронт. Правка вёрстки 1.
 *
 * Переменные объявлены на дереве, а не на строке узла: строка стоит внутри
 * контейнера топиков, и объяви их `.km-node`, собственное объявление
 * перебивало бы унаследованное — лист получил бы размер занятия.
 *
 * Кто читает эти числа, кроме самого кружка: отступ подписи на узком экране
 * (подпись переносится под название и встаёт по кружку). Поэтому число одно
 * и живёт здесь. */
/* Знак заполняет кружок, а не сидит в нём точкой — решение человека
   8 августа 2026, ориентир LearnDash. Разница между уровнями осталась
   на размере кружка и знака, но не ценой того, что лист нечитаем:
   у листа знак занимает даже большую долю кружка, чем у занятия.

   Половина дела здесь, вторая — в рамке рисунка (`lmsGlyph`): она была
   вдвое шире чернил, и одни числа тут ничего бы не дали. */
/* КАРТОЧКА НА КАЖДОЕ ЗАНЯТИЕ, серый фон страницы между карточками.
 *
 * Принятый вид от 19 августа 2026, `design/course-tree-v3.html`. Одна
 * карточка на всё дерево держалась ровно один выпуск (1.0.0–1.0.2) и была
 * забракована человеком: пятнадцать занятий и шестьдесят топиков внутри
 * одной рамки читаются сплошной стеной, а модуль внутри белого поля
 * не разделяет ничего.
 *
 * Поэтому поля у самого дерева больше нет: их несёт карточка занятия,
 * а название модуля стоит между карточками, на сером. */
.km-tree { display: flex; flex-direction: column; padding: 0; --km-node-glyph: var(--km-glyph); }
.km-tree__topics { --km-node-glyph: 16px; }
.km-tree--compact { --km-node-glyph: 17px; }
.km-tree--compact .km-tree__topics { --km-node-glyph: 14px; }

/* Карточка занятия. Ставит её `card()` в `course-map.js` — на один уровень,
   тот, что лежит прямо под модулем или прямо в корне. Топики карточек
   не получают ни при каком условии: на этом сломались три захода.

   `overflow: hidden` обязателен и не про красоту: тёплая подложка текущего
   занятия лежит на строке во всю ширину карточки, и без обрезки её прямые
   углы торчали бы из скруглённых углов рамки. */
.km-tree__card {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
  overflow: hidden;
  margin-bottom: 10px;
}

/* СКЛАДКА ЗАНЯТИЯ. Решение человека 19 августа 2026: сворачивается
   занятие, модуль остаётся строкой-заголовком.

   Карточка при этом ОДНА и лежит на корне дерева — ни у занятия,
   ни у топика, ни у модуля своей нет. Три захода сломались на обратном. */
/* `display` У `<details>` НЕ ТРОГАЕМ.
 *
 * Стояло `display: flex`, и складка перестала прятать: топики держали
 * высоту 228 и раскрытыми, и свёрнутыми. Скрытие свёрнутого содержимого
 * браузер делает внутренним механизмом, и смена `display` на флекс его
 * ломает — разметка при этом верная, топики лежат в `<details>`, а не
 * в `<summary>`, и на глаз всё выглядит правильным.
 *
 * Поймано не селектором, а тем, что складку раскрыли и свернули
 * и посмотрели, что спряталось. */
.km-tree__fold { display: block; }

/* СВЁРНУТОЕ ПРЯЧЕТСЯ ЯВНО, а не механизмом браузера.
 *
 * У `.km-tree__topics` свой `display: flex` с седьмого этапа, и он
 * переживал скрытие свёрнутого `<details>`: топики держали высоту 228
 * и раскрытыми, и свёрнутыми. Разметка при этом верная — они лежат
 * в `<details>`, а не в `<summary>`, — и на глаз всё выглядело правильным.
 *
 * Снять флекс совсем нельзя: он держит колонку, и без него топики легли бы
 * строкой. Поэтому он возвращается раскрытому.
 *
 * Правило СУЖЕНО до складки (`.km-tree__fold >`), а не написано на класс
 * целиком: `km-tree__topics` носит контейнер третьего уровня везде, где
 * рисуется дерево, и безусловный `display: none` спрятал бы топики там,
 * где складки нет вовсе. Сегодня таких мест не нашлось, но правило,
 * верное только при сегодняшнем составе, — это ловушка на будущее. */
.km-tree__fold > .km-tree__topics { display: none; }
.km-tree__fold[open] > .km-tree__topics { display: flex; }
.km-tree__fold-head { display: block; cursor: pointer; list-style: none; }
.km-tree__fold-head::-webkit-details-marker { display: none; }

/* Синей рамки фокуса наружу не выходит: умолчание `<details>` обводило
   раскрытое занятие целиком. Клавиатуре признак остаётся — на самой строке. */
.km-tree__fold-head:focus { outline: none; }
.km-tree__fold-head:focus-visible .km-node { outline: 2px solid var(--km-accent); outline-offset: -2px; }

/* Шеврон смотрит ВПРАВО у свёрнутого и вниз у раскрытого — макет v3.
   Путь в скрипте один, поворот здесь: два пути под одно и то же разошлись
   бы молча, а «повёрнут» и «не повёрнут» видно глазом. */
.km-tree__chevron { color: var(--km-text-muted); transition: transform .15s; }
details[open] > .km-tree__fold-head .km-tree__chevron { transform: rotate(90deg); }

/* У занятия, где человек стоит, шеврон тёплый — иначе подложка есть,
   а знак слева остаётся серым и спорит с ней. */
.km-node--current .km-tree__chevron { color: var(--km-accent); }

/* ЛЕСТНИЦА РЕДАКТОРА ДЕРЕВА — АДМИНКА, а не фронт.
 *
 * `km-tree__module`, `km-tree__lesson`, `km-tree__topic` носят строки
 * редактора дерева (`tree-editor.js`), списка потоков (`cohorts.js`)
 * и корзины (`trash.js`). У ученика дерево собирается другими классами:
 * `km-tree__card`, `km-tree__heading`, `km-node`.
 *
 * Имя общее и занято НАМИ ЖЕ, и это стоило отдельной проверки: макет v3
 * называет карточку занятия `km-tree__lesson`, а строка под этим именем
 * здесь уже пятая версия — с полями, чертой снизу и отступом 52. Взять
 * имя из макета значило бы перекрасить редактор дерева молча. */
/* ЛЕСТНИЦА УРОВНЕЙ В РЕДАКТОРЕ ДЕРЕВА. Макет `design/tree-editor-v1.html`,
   21 августа 2026.

   Человек смотрел на боевом и назвал беду: **топик шире занятия**, и
   вложенность читалась наоборот. Отступ и кегль работают только вместе:
   один без другого не читается — отступ без кегля теряется на 375, кегль
   без отступа не говорит, кто чей.

   Числа взяты у макета, а не пересчитаны: 0 / 40 / 72 по отступу,
   15 / 14 / 13 по кеглю, и приглушение на третьем.

   Строка одна на все уровни (`km-row`), а не три разных: до этого дня
   у каждого уровня был свой класс со своим набором правил, и лестница
   разъезжалась при каждой правке одного из трёх. */
.km-row {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 16px;
  border-bottom: 1px solid var(--km-border-soft);
}

.km-row:last-child { border-bottom: none; }
.km-row:hover { background: var(--km-bg); }
.km-row--picked { background: var(--km-warm-bg); }

/* РУЧКА ПОЯВЛЯЕТСЯ ПО НАВЕДЕНИЮ. Висящая у каждой из 119 строк, она
   зашумляет экран сильнее, чем помогает: перетаскивают один узел из ста,
   а видят ручку у всех. */
.km-row__grip {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--km-text-muted);
  opacity: 0;
  cursor: grab;
}

.km-row:hover .km-row__grip { opacity: 1; }

.km-row__chev {
  width: 16px;
  height: 16px;
  flex: none;
  color: var(--km-text-muted);
  cursor: pointer;
  transition: transform 0.15s;
}

/* Свёрнутый — шеврон вбок. Поворотом, а не вторым знаком: два знака
   разошлись бы с состоянием, поворот от него неотделим. */
.km-row--folded .km-row__chev { transform: rotate( -90deg ); }

/* У листа шеврона нет, но место его занято: без этого имена узлов
   разъехались бы на 16 пикселей между листом и контейнером. */
.km-row__chev--empty { visibility: hidden; }

.km-row__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Правый столбец — одним рядом и приглушённо: тип, правило, обязательность
   отвечают на вопросы, которые задают редко, и кричать им незачем. */
.km-row__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

.km-row--module { background: var(--km-surface-soft, #FAFBFD); }
.km-row--module .km-row__name { font-size: 15px; font-weight: 700; }

.km-row--lesson { padding-left: 40px; }
.km-row--lesson .km-row__name { font-size: 14px; font-weight: 600; }

.km-row--topic { padding-left: 72px; }
.km-row--topic .km-row__name { font-size: 13px; font-weight: 400; color: var(--km-text-secondary); }

/* НЕХВАТКА ЗАНИМАЕТ СВОЮ СТРОКУ ЦЕЛИКОМ.
   Найдено глазами на стенде 21 августа 2026: у эфира без даты и без ссылки
   два предупреждения в правом столбце вытолкнули имя в многоточие —
   «Эфир: сложн…». Имя важнее любой подписи рядом с ним.

   Приём тот же, что уже принят для причины закрытия у занятия: строка
   переносится, а объяснение получает всю ширину. Второго правила про то же
   не заводится — это оно и есть, только у новой строки. */
.km-row--explained { flex-wrap: wrap; row-gap: 6px; }
.km-row--explained .km-row__name { flex: 1 0 auto; }
.km-row--explained .km-warn { flex: 1 0 100%; }

/* Что внутри у свёрнутого — иначе непонятно, что сворачиваешь. */
.km-count-in { font-size: 12px; color: var(--km-text-muted); white-space: nowrap; }

.km-rule { font-size: 12px; color: var(--km-text-muted); white-space: nowrap; }
.km-opt { font-size: 12px; color: var(--km-text-muted); white-space: nowrap; }

.km-tag {
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--km-text-secondary);
  background: var(--km-bg);
  border: 1px solid var(--km-border);
  border-radius: 5px;
  padding: 2px 6px;
  white-space: nowrap;
}

.km-tag--cohort {
  background: var(--km-warm-bg);
  border-color: var(--km-warm-bg);
  color: var(--km-accent-dark);
}

/* На узком экране правый столбец уезжает под название: три подписи рядом
   с именем не помещаются, а имя важнее их всех. */
@media ( max-width: 640px ) {
  .km-row { flex-wrap: wrap; row-gap: 6px; }
  .km-row__name { flex: 1 0 100%; white-space: normal; }
  .km-row--lesson { padding-left: 24px; }
  .km-row--topic { padding-left: 44px; }
}

.km-tree__module {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--km-border-soft);
}
.km-tree__module-title { font-family: var(--km-font-display); font-size: 20px; font-weight: 700; line-height: 1.3; }

.km-tree__lesson {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 20px 13px 52px;
  border-bottom: 1px solid var(--km-border-soft);
}
/* Строка с объяснением под ней: причина занимает **свою** строку целиком.
   Во флексе она вставала колонкой между кнопками и сжимала название
   до четырёх строк по слову. Модификатор, а не правка самой строки:
   тот же класс носят строки редактора дерева, где переносить нечего.
   Найдено глазами на стенде корзины 8 августа 2026. */
.km-tree__lesson--explained { flex-wrap: wrap; row-gap: 6px; }
.km-tree__lesson--explained .km-node__reason { flex: 1 0 100%; margin-left: 0; }
.km-tree__lesson-title { flex: 1; font-size: 15px; }

/* Уровень 3 — топики ВНУТРИ карточки своего занятия, на белом.
 *
 * Отступ 28 без черты — правка вёрстки 2, решение человека 8 августа 2026,
 * подтверждённое макетом v3. Число складывается из поля карточки (18)
 * и самого отступа (28): 46 слева, 18 справа, чтобы правый столбец топика
 * стоял под правым столбцом занятия.
 *
 * Довод против черты не «в макете так». Уровень уже сказан лестницей кегля
 * — 15/600 против 14/400 вторичным цветом, — и вертикальная черта повторяет
 * его вторым способом. На шести модулях подряд повтор превращается
 * в решётку, а сдвиг в 61 пиксель на 375 съедает шестую часть экрана. */
.km-tree__topics {
  display: flex;
  flex-direction: column;
  padding: 0 18px 6px 46px;
}

/* НАПРАВЛЯЮЩЕЙ В КОЛОНКЕ БОЛЬШЕ НЕТ. Решение человека 19 августа 2026,
   и оно ОТМЕНЯЕТ правку вёрстки 2 в части плеера.

   Черта была оставлена там по доводу: колонка узкая, лестница кегля в ней
   своя и мельче, и различать уровни нечем, кроме черты. Довод перестал
   быть верным в тот же день: занятие лежит в карточке с подложкой,
   уровень виден и без черты, а черта с отступом съедают ширину —
   на 283 пикселя это видно.

   Отступ заодно уменьшен с 27 (15 поля плюс 12 подбивки) до 14:
   освободившееся место отдано названиям. Половина их ломалась на две
   строки — это и было главным, что колонка теряла. */
.km-tree--compact .km-tree__topics {
  margin: 2px 0 4px 14px;
  padding: 0;
  border-left: none;
}
.km-tree__topic {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 20px 9px 12px;
  border-radius: 8px;
}
.km-tree__topic-title { flex: 1; font-size: 14px; }

/* Редактируемое дерево: вся строка открывает панель узла, и это видно.

   Модификатор, а не правило для всех: у ученика на карте строка ведёт
   ссылкой, и признак нажимаемости ей даёт сама ссылка. Здесь нажимаема
   строка целиком — до 9.7 слушатель висел на одном имени, то есть
   на четверти ширины, и ничем не был помечен. */
.km-tree--editable .km-tree__module,
.km-tree--editable .km-tree__lesson,
.km-tree--editable .km-tree__topic,
.km-tree--editable .km-row { cursor: pointer; }

/* --- Аккордеон модуля: ТОЛЬКО ПРОДАЮЩАЯ СТРАНИЦА И АДМИНКА ---
 *
 * В дереве ученика аккордеона больше нет: модуль стал строкой-заголовком
 * 19 августа 2026, а карточка одиночных узлов снята вместе с ним — она
 * и была белой рамкой со снимка того же дня. Правило `.km-tree > ...
 * .km-accordion__item` ушло отсюда: в `.km-tree` этот класс не попадает
 * ни при каком условии.
 *
 * Живые потребители: `ProgramBlocks` (страница гостя, включая `--bare`)
 * и `templates/admin/enrollments.php`. */
.km-accordion { display: flex; flex-direction: column; gap: 12px; }
.km-accordion__item {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
  overflow: hidden;
}
.km-accordion__item--current { border-color: var(--km-warm-border); }

/* ОДИН БЛОК С РАЗДЕЛИТЕЛЯМИ вместо пяти карточек с воздухом.
 *
 * Макет `course-page-guest-v2.html`: `km-prog` — одна поверхность, модули
 * внутри разделены чертой. Правки просил человек: карточки разъезжались
 * зазором 12, у каждой своя тень, и список читался как пять несвязанных
 * блоков вместо одной программы.
 *
 * Модификатор, а не правка базы: тем же `km-accordion` собрана карта курса
 * (`course-map.js`), и там карточки — верное решение.
 *
 * ЧИСЛА ИЗ МАКЕТА:
 *   поверхность, рамка, радиус, тень — как у карточки, токенами;
 *   `overflow: hidden` — чтобы углы резали шапку первого модуля;
 *   черта между модулями — `--km-border-soft`, как в макете;
 *   шапка 18px 24px — у базы 18px 22px, взято макетное;
 *   отступ списка слева 68 = 24 (поле) + 30 (номер) + 14 (зазор), то есть
 *     строки занятий встают ровно под название, а не под номер.
 *
 * Что НЕ взято из макета и почему — названо, а не подогнано:
 *   название 20px против 17,5 у макета — половин пикселя шкала не знает,
 *     а 20 уже стоит на этом экране и принято;
 *   строки занятий 12px/12 против 9/11 у макета — это `km-lineup`, общий
 *     компонент четырёх экранов; ради трёх пикселей его не разводят.
 */
.km-accordion--joined {
  gap: 0;
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
  overflow: hidden;
}
.km-accordion--joined .km-accordion__item {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.km-accordion--joined .km-accordion__item + .km-accordion__item {
  border-top: 1px solid var(--km-border-soft);
}
.km-accordion--joined .km-accordion__head { padding: 18px 24px; }

/* Название тянется, счёт уходит вправо — как в макете. У базы оба стоят
   столбиком в `__body-head`; здесь они в одной строке. */
.km-accordion--joined .km-accordion__title { flex: 1; min-width: 0; }
.km-accordion--joined .km-accordion__count { flex: none; white-space: nowrap; }

/* Строки встают под название, а не под номер. */
.km-accordion--joined .km-accordion__body { padding: 2px 24px 18px 68px; }

/* У безымянной группы номера нет — отступать не подо что. */
.km-accordion--joined .km-accordion__body--bare { padding-left: 24px; }

/* Кнопка «Развернуть всё» — своей строкой над блоком, прижата вправо.
   В макете она между заголовком и программой, а не рядом с заголовком. */
.km-accordion__above { display: flex; justify-content: flex-end; }

@media (max-width: 640px) {
  /* Под номер отступать нечем: 68 съедает треть ширины. */
  .km-accordion--joined .km-accordion__body { padding-left: 24px; }

  /*
   * ШАПКА НЕ РАСПАДАЕТСЯ НА ЧЕТЫРЕ СТРОКИ.
   *
   * Общее правило узкой шапки ставит детей столбиком, и до 1.135.0 их было
   * трое: номер, колонка «название + счёт», галка. Разведя название и счёт
   * в одну строку ради широкого экрана, я сделал детей четырьмя — и на 375
   * шапка вытянулась на четыре строки, с галкой отдельно внизу.
   *
   * Регрессия моя, найдена глазами на стенде, мерок на неё не было: «ничего
   * не вылезает за ширину» здесь правда, а высота шапки никем не мерилась.
   *
   * Здесь строка возвращается: номер, название и галка в ряд, счёт — под
   * ними, с отступом под название (44 = 30 номера + 14 зазора).
   */
  .km-accordion--joined .km-accordion__head {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
  }
  .km-accordion--joined .km-accordion__title { flex: 1 1 auto; }
  /*
   * `order` ставит счёт ПОСЛЕДНИМ в раскладке, не трогая разметку:
   * иначе перенос по 100% ширины уводил на третью строку галку,
   * стоящую в разметке после счёта. Строк выходило три вместо двух.
   */
  .km-accordion--joined .km-accordion__count { order: 1; flex: 1 0 100%; padding-left: 44px; }
}
.km-accordion__head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px 22px;
  cursor: pointer;
}
.km-accordion__item--current .km-accordion__head { background: var(--km-warm-bg); }
.km-accordion__title { font-family: var(--km-font-display); font-size: 20px; font-weight: 700; line-height: 1.3; }
.km-accordion__count { font-size: 13px; color: var(--km-text-muted); }

/* Шапка модуля продающей страницы — по `design/course-page-guest.html`.
 *
 * Отличается от шапки на карте курса тремя вещами, и все три из макета:
 * кружок с номером слева, счёт ВТОРОЙ СТРОКОЙ под названием, шеврон справа.
 *
 * Шеврон важнее прочего: без него карточка ничем не показывает, что она
 * раскрывается, — шесть одинаковых плашек, и нажимать их незачем. Он же
 * и есть единственный признак состояния, поэтому поворачивается. */
.km-accordion__num {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--km-warm-bg);
  color: var(--km-warm-text-strong);
  font-size: 14px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.km-accordion__body-head { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }

.km-accordion__chevron {
  flex: none;
  color: var(--km-text-muted);
  transition: transform .15s;
}

details[open] > .km-accordion__head .km-accordion__chevron { transform: rotate(180deg); }

/* «Развернуть всё» — у правого края, как в макете. */
.km-section__action { margin-left: auto; }

/* Строка модуля: иконка слева, три подписи, действие справа.
 *
 * Счёт и состояние стоят **второй строкой**, а не в надстрочнике: решение
 * человека 7 августа 2026. Надстрочник называет номер модуля, и дописанное
 * туда состояние («Модуль первый · пройден») читалось на 375 с висящей
 * точкой посреди фразы. */
.km-accordion__state { font-size: 13px; color: var(--km-text-muted); }
.km-accordion__item--current .km-accordion__state { color: var(--km-warm-text); }
.km-accordion__chevron { flex: none; color: var(--km-text-muted); transition: transform 0.18s; }
.km-accordion__item[open] .km-accordion__chevron { transform: rotate(180deg); }
/* Шапка модуля на карте: две строки слева, «посмотреть» справа.
   Разметка с design/course-map-v2.html — надстрочник, заголовок, признак
   сворачивания. Заведено в 9.6, кусок 3. */
.km-accordion__lines { display: flex; flex-direction: column; gap: 4px; flex: 1; }
.km-accordion__toggle { flex: none; font-size: 13px; color: var(--km-accent); }
.km-accordion__body { border-top: 1px solid var(--km-border-soft); padding: 6px 22px 14px; }
/* Тело без шапки — карточка одиночных узлов верхнего уровня (пункт 6 правок).
   Черта сверху здесь отделяла бы содержимое от края карточки, а не от шапки,
   которой нет; поле сверху берётся такое же, как снизу. */
.km-accordion__body--bare { border-top: none; padding-top: 14px; }
.km-accordion__item--current .km-accordion__body { border-top-color: var(--km-warm-border); }

/* Список занятий внутри модуля на продающей странице (7.4).

   Своя строка, а не `.km-row`: та рассчитана на форму — выравнивание
   по нижнему краю и подпись, прижатая вправо через `margin-left: auto`.
   На узком экране название переносилось, а подпись типа уезжала на свою
   строку к правому краю — и вставала между двумя названиями, не относясь
   ни к одному. Черта между строками нужна по той же причине: без неё
   список читается как сплошной текст. */
.km-lineup { display: flex; flex-direction: column; }
.km-lineup__item {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 12px 0;
}
.km-lineup__item + .km-lineup__item { border-top: 1px solid var(--km-border-soft); }
.km-lineup__title { flex: 1; font-size: 15px; line-height: 1.5; }
.km-lineup__type { flex: none; font-size: 12px; color: var(--km-text-muted); }

/* Перечень СО ЗНАКОМ: строка «знак — название». Макет
   `course-page-guest-v2.html`, пункт 3 правок 2 сентября 2026.

   Своя семья, а не `km-lineup--center`, и это найдено стендом на 375.
   `--center` рассчитан на список эфиров и работ: там в строке дата,
   название, пометка и ссылка, и на узком экране он ставит название
   **первым** (`order: -1`, во всю ширину), а остальное под ним. Знак
   при этом уезжал под название — то есть строка читалась «название,
   а под ним непонятный значок».

   Здесь в строке двое, и переносить нечего: знак остаётся слева на любой
   ширине. Тот же класс ошибки, что `km-table__row` в списке потоков —
   имя подошло, устройство нет.

   Знак рода в строке перечня.

   Заменяет собой подпись типа справа: та стояла у каждой строки и на курсе
   из восемнадцати занятий повторяла одно слово восемнадцать раз, то есть
   не различала ничего. Знак различает ровно там, где родов больше одного.

   Своего размера у `svg` внутри нет намеренно — его задаёт скрипт набора
   (1.122.0), как и на карте курса. Ширина здесь на случай, когда знак ещё
   не пришёл: без неё строки прыгали бы при отрисовке. */
.km-lineup--glyph .km-lineup__item { align-items: center; }
.km-lineup__glyph {
  flex: none;
  display: flex;
  width: 17px;
  height: 17px;
  color: var(--km-accent);
}

/* Тот же перечень, но со столбцом даты и пометкой: прошедшие эфиры (кусок 7)
   и работы по курсу (кусок 8). Форма у них одна, и это не совпадение —
   в макете это одна и та же карточка с одной и той же чертой между строками.

   `--center` вместо базового `baseline`: в строке стоит бейдж, и по базовой
   линии он съезжает вниз относительно названия. Базовое выравнивание
   оставлено как было — на продающей странице в строке только текст, и там
   базовая линия читается лучше. */
.km-lineup--center .km-lineup__item { align-items: center; }
.km-lineup__date { flex: none; width: 108px; font-size: 13px; color: var(--km-text-muted); }
/* Того, чего ещё не было, — приглушённым: в макете будущий эфир написан
   тем же серым, что и его дата. Так строка читается как «сюда пока незачем»,
   а не как ещё одна запись, которую забыли открыть. */
.km-lineup__title--ahead { color: var(--km-text-muted); }
/* Момент отправки работы прижат вправо — так в макете: пометка состояния
   стоит рядом с названием, а дата у самого края. */
.km-lineup__date--right { width: 120px; text-align: right; }
.km-lineup__link { flex: none; font-size: 13px; font-weight: 600; }
.km-lineup__badge { flex: none; }

/* Ближайший эфир: кружок даты слева, название и время посередине,
   действия справа. Своя строка, а не `.km-headline`: у той левая колонка
   объявлена полосой прогресса, и переиспользование сделало бы имя враньём —
   ровно то, чем `.km-input-group` однажды прикинулся подписью над полем. */
.km-upnext {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.km-upnext__body { flex: 1; min-width: 220px; display: flex; flex-direction: column; gap: 4px; }
.km-upnext > .km-actions { flex: none; }

/* Приглашение: текст слева, одно действие справа. Заведено под «Поделитесь
   своим опытом» на вкладке «Отзывы» (кусок 9).

   Своя строка, а не `.km-upnext`: у той слева кружок даты и объявлен он
   в имени. Переиспользование сделало бы имя враньём — та же ловушка,
   какой `.km-input-group` однажды прикинулся подписью над полем. */
.km-invite {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
}
.km-invite__body { flex: 1; min-width: 240px; display: flex; flex-direction: column; gap: 5px; }
.km-invite > .km-btn { flex: none; }

/* --- Левое меню кабинета --- */
/* Боковое меню жило здесь под кабинет. Потребителя нет с монтирования:
   левую колонку рисует тема (BuddyPanel), и своя была бы второй такой же.
   Классы убраны, а не оставлены «на будущее» — дизайн-система описывает
   то, что на экранах есть, а не то, что могло бы пригодиться. */

/* --- Пустые состояния: приглашение, а не извинение --- */
/*
 * `.km-empty` бывает и абзацем, и коробкой — и поля браузера у абзаца
 * приходится гасить здесь.
 *
 * Найдено общей меркой стенда на пустом экране файлов: `p.km-empty` нёс
 * свои 16px сверху и снизу, а ритм колонки задаёт контейнер. Правило
 * обнуления рядом (строка 3360) накрывает `__title` и `__text`, а сам
 * блок — нет: его писали коробкой, и абзацем он стал позже, в шести
 * шаблонах сразу.
 *
 * Видно это стало только там, где элемент **показан**: спрятанный
 * измеряется правдоподобно, и мерка его пропускает.
 */
.km-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin: 0;
  text-align: center;
  padding: 40px 28px;
  background: var(--km-surface-alt);
  border: 1px dashed #D9E1EA;
  border-radius: var(--km-radius-card);
}
.km-empty:hover { border-color: var(--km-warm-border); }
.km-empty__icon {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--km-peach);
  color: var(--km-accent);
  display: flex;
  align-items: center;
  justify-content: center;
}
.km-empty__title { font-size: 17px; font-weight: 700; }
.km-empty__text { font-size: 14px; line-height: 1.6; color: var(--km-text-secondary); max-width: 320px; }

/* --- Пагинация --- */
.km-pagination { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.km-pagination__info { font-size: 13px; color: var(--km-text-muted); flex: 1; }
.km-pagination__list { display: flex; align-items: center; gap: 6px; }
.km-page-btn {
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--km-text);
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  padding: 9px 14px;
  cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.km-page-btn:hover { border-color: var(--km-warm-border); color: var(--km-accent); }
.km-page-btn.is-active {
  background: var(--km-accent);
  border-color: var(--km-accent);
  color: #fff;
  font-weight: 700;
}
.km-page-btn[disabled] { color: #C9D3DE; background: var(--km-surface); border-color: var(--km-border); cursor: not-allowed; }
.km-page-gap { font-size: 13px; color: var(--km-text-muted); padding: 0 4px; }
.km-page-jump { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--km-text-secondary); }
.km-page-jump .km-input { width: 72px; padding: 8px 10px; font-size: 13px; text-align: center; }

/* --- Подсказка поиска: список найденного под полем ---

   Заведена для выбора материала в панели узла (9.7). До неё поле «Материал»
   принимало пятнадцатизначный номер записи и больше ничего: понять, что за
   материал выбран и где его взять, было нельзя ни при правке, ни при
   заведении узла.

   Строка списка — `button`, а не `div` с обработчиком: список выбирают
   и с клавиатуры, а кнопка умеет это сама. */
.km-suggest {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-control);
  background: var(--km-surface);
  box-shadow: var(--km-shadow-card);
  max-height: 260px;
  overflow-y: auto;
}
.km-suggest__item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 14px;
  border: none;
  border-bottom: 1px solid var(--km-border-soft);
  background: transparent;
  font-family: inherit;
  font-size: 15px;
  line-height: 1.4;
  color: var(--km-text);
  text-align: left;
  cursor: pointer;
}
.km-suggest__item:last-child { border-bottom: none; }
.km-suggest__item:hover,
.km-suggest__item:focus { background: var(--km-warm-bg); outline: none; }
.km-suggest__where { font-size: 12px; line-height: 1.4; color: var(--km-text-muted); }
.km-suggest__empty { padding: 12px 14px; font-size: 14px; color: var(--km-text-secondary); }

/* Занятое место материала — тем же тёплым, что и всё «обрати внимание».
   Не тревожным: материал в двух курсах это норма (правило 1), а не ошибка. */
.km-suggest__taken { color: var(--km-warm-text-strong); }

/* --- Всплывающая панель сбоку ---

   `fixed`, а не `absolute` и не поток. Бриф 1.6 говорит «открывается сбоку»,
   и до 9.7 это держалось на том, что панель стояла последним блоком страницы:
   на пяти узлах стенда она попадала в окно сама собой, а на боевых 188 —
   уезжала на десять тысяч пикселей вниз. Человек нажимал на узел, экран
   сереел затемнением, и больше не происходило ничего.

   Затемнение тоже `fixed`: `absolute` без положенного предка считается
   от начала документа, то есть накрывает первый экран, а не тот, на который
   человек смотрит.

   Числа z-index не выдуманы: в админке WordPress `#adminmenuwrap` стоит
   на 9990, `#wpadminbar` — на 99999. Панель обязана быть выше обоих, иначе
   её кнопка закрытия уходит под верхнюю полосу и закрыть панель нечем. */
.km-drawer-scrim {
  position: fixed;
  inset: 0;
  z-index: 100000;
  background: rgba(63, 76, 92, 0.28);
}
.km-drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 100001;
  display: flex;
  flex-direction: column;
  width: 480px;
  max-width: 100%;
  background: var(--km-surface);
  border-left: 1px solid var(--km-border);
  box-shadow: var(--km-shadow-lift);
}
.km-drawer__head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 20px 24px;
  border-bottom: 1px solid var(--km-border);
}
.km-drawer__title { font-family: var(--km-font-display); font-size: 24px; font-weight: 700; line-height: 1.3; }
.km-drawer__close {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: var(--km-radius-control);
  border: 1px solid var(--km-border);
  background: var(--km-surface);
  color: var(--km-text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.km-drawer__close:hover { border-color: var(--km-warm-border); color: var(--km-accent); }
.km-drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.km-drawer__foot {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 24px;
  border-top: 1px solid var(--km-border);
  background: var(--km-surface-alt);
}
.km-drawer__foot .km-drawer__spacer { flex: 1; }

/* Строка контролов: кнопки и поля в ряд, с переносом на узком.
   Заведена для шапки экрана зачислений — в макете кнопки стоят строкой,
   а `.km-stack` ставит столбиком. */
.km-row {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: 10px;
}

.km-row > .km-btn {
	flex: none;
}

/*
 * Строка полей, выровненная по верху.
 *
 * У обычной строки выравнивание по низу — так кнопка встаёт на одну линию
 * с полем. Но если под одним полем есть пояснение, по низу выравнивается
 * оно, и соседние поля разъезжаются по высоте: подписи «Продаётся с»
 * и «Продаётся до» оказываются на разных уровнях. Нашлось глазами
 * на стенде предложений (3.6).
 */
.km-row--top {
	align-items: flex-start;
}

/*
 * Строка, выровненная по середине: кнопка и оговорка рядом с ней.
 *
 * У обычной строки выравнивание по низу — так кнопка встаёт на одну линию
 * с полем. Рядом с кнопкой поля нет, а есть строка текста, и по низу она
 * прилипает к самому краю кнопки.
 */
.km-row--middle {
	align-items: center;
}

/*
 * Строка из флагов. У обычной строки поля выровнены по низу — так подпись
 * и поле стоят на одной линии. Флагам это не годится: у них подпись сбоку,
 * и по низу они разъезжаются, когда у одного пояснение в две строки.
 * Отступ больше обычного: между «Бесплатное» и «Один раз на человека»
 * должно быть видно, что это два разных вопроса.
 */
/*
 * Перечень: сводка перед необратимой операцией, состав чего-либо.
 * Маркер точкой, а не своей картинкой: список читают, а не разглядывают.
 */
.km-list {
	margin: 0;
	padding-left: 20px;
	display: flex;
	flex-direction: column;
	gap: 6px;
	font-size: 15px;
	line-height: 1.6;
	color: var(--km-text);
}

.km-list li {
	list-style: disc;
}

.km-row--flags {
	align-items: flex-start;
	gap: 10px 28px;
}

/* Поле внутри строки компактно.

   Само по себе `.km-field` — колонка, и поле в ней растягивается на всю
   ширину родителя: на странице в 1180 пикселей поиск и селекты уезжали
   от края до края. Фильтру столько не нужно, ему нужно ровно столько,
   сколько занимает значение. */
.km-row > .km-field {
	flex: 0 1 240px;
	min-width: 180px;
}

.km-row > .km-field--wide {
	flex: 1 1 320px;
	max-width: 460px;
}

/* ---------------------------------------------------------------------------
   Узкий экран
   ---------------------------------------------------------------------------

   Раздел 12.4 спецификации: экраны ученика проектируются от узкого,
   административные просто не должны разваливаться. Правило для таблиц —
   строка перестаёт быть таблицей и становится карточкой. Горизонтальная
   прокрутка таблицы шириной в тысячу пикселей не годится: на телефоне она
   выглядит сломанной, даже когда технически работает.

   Карточка собрана по мобильному макету `design/course-map-v2-mobile.html`:
   главное сверху крупным, остальное парами «подпись — значение», длинное
   обрезается многоточием, а не переносится в три строки.
   --------------------------------------------------------------------------- */

/* --- Плеер: две колонки только там, где колонке чтения хватает места ---
 *
 * Перелом свой, шире общего в 640, и это решение по числам, а не по вкусу.
 *
 * Колонка навигации 320 пикселей и не сжимается — в ней имена занятий.
 * Значит на 768 читальной колонке остаётся 400, на 1024 — 650. Планшет
 * попадал ровно в эту щель: две колонки формально были, а читать в них
 * было нечем.
 *
 * **Арифметика здесь была неверной, и это правлено вместе с шириной
 * колонки.** Стояло «1100 — то, ниже чего 780 не помещается», а 780 плюс
 * 320 навигации плюс 56 полей это 1156: на 1100 читальной достаётся 725,
 * и никогда не доставалось больше. Комментарий обещал вычисление, которого
 * код не делал, — ровно то, из-за чего через год уберут настоящую защиту,
 * приняв объяснение за неё.
 *
 * Что верно: 1100 — **решение**, проверенное глазами на 768, 1024 и 1440,
 * а не следствие ширины колонки. Потолок в 980 набирается от 1356 и выше;
 * между 1100 и 1356 читальная колонка уже двух колонок, но всё ещё шире
 * планшетной щели, ради которой перелом и заводился. Двигать его вслед
 * за 980 значило бы отобрать вторую колонку у ноутбуков 1280, чего никто
 * не просил.
 *
 * Ниже перелома плеер становится одной колонкой со створкой — тем же
 * видом, что на телефоне, и по той же причине: текст важнее оглавления,
 * а оглавление достаётся нажатием.
 *
 * Число названо здесь и продублировано в `lesson.js`: створку закрывает
 * скрипт, и второго места, где решают «узко ли», не заводится — оно
 * спрашивает медиазапрос, а не считает ширину само.
 */
@media (max-width: 1100px) {
	.km-player {
		grid-template-columns: minmax(0, 1fr);
		min-height: 0;
	}

	.km-player__side {
		position: static;
		height: auto;
		overflow-y: visible;
		border-right: none;
		border-bottom: 1px solid var(--km-border);
		padding: 16px 18px 18px;
	}

	.km-player__main { padding: 24px 24px 80px; }

	/* Створка появляется вместе с одной колонкой.
	 *
	 * Это **единственный** путь к оглавлению на телефоне, поэтому она
	 * собрана как control, а не как заголовок: рамка, знак слева, слово
	 * справа. Прежняя редакция была строкой в семнадцать пикселей с серым
	 * треугольником у края — на экране это читалось разделом, а не дверью.
	 *
	 * Маркер браузера убран: его треугольник рисуется по-разному в каждом
	 * и не попадает в дизайн-систему ни в одном. Свой знак нарисован
	 * разметкой и потому одинаков везде. */
	.km-player__toggle {
		display: flex;
		align-items: center;
		gap: 10px;
		cursor: pointer;
		list-style: none;
		background: var(--km-surface);
		border: 1px solid var(--km-border);
		border-radius: var(--km-radius-control);
		padding: 12px 16px;
	}

	.km-player__toggle::-webkit-details-marker { display: none; }

	.km-player__toggle-title {
		flex: 1;
		min-width: 0;
		font-family: var(--km-font-display);
		font-size: 17px;
		font-weight: 700;
	}

	/* Слово следует за состоянием — его ставит `lesson.js`. Акцентом,
	   как «Открыть» у шапки модуля: одно и то же действие на одном экране
	   не должно выглядеть двумя разными. */
	.km-player__toggle-state {
		flex: none;
		font-size: 13px;
		font-weight: 600;
		color: var(--km-accent);
	}

	.km-player__toggle-chevron {
		flex: none;
		display: block;
		color: var(--km-accent);
		transition: transform 0.18s;
	}

	.km-player__side[open] .km-player__toggle-chevron { transform: rotate(180deg); }

	/* Створка называется так же, как заголовок карты, и на узком они
	   встают друг под другом: «Программа курса» дважды подряд. На широком
	   створки нет, и заголовок нужен — поэтому прячется он, а не она. */
	.km-player__side .km-section-title { display: none; }
}

@media (max-width: 640px) {
	.km-player__main { padding: 20px 18px 72px; }
}

@media (max-width: 640px) {
  /* Шапка модуля складывается: «посмотреть» уходит под подписи.
     На 375 оно стояло справа в одну строку с названием и отнимало
     ширину — «1. Планетарные Места Вознесения» переносилось на две
     строки, а надстрочник разрывался посреди фразы, оставляя висящую
     точку: «МОДУЛЬ ПЕРВЫЙ ·» и «ПРОЙДЕН» на разных строках.

     Ни одна мерка этого не поймала: «ничего не вылезает за ширину» и
     «подписи стоят на одном уровне» здесь правда, а читаемость они
     не мерят. Найдено глазами на настоящем курсе, 7 августа 2026. */
  .km-accordion__head { flex-direction: column; align-items: flex-start; gap: 8px; }
  .km-accordion__lines { width: 100%; }

	/*
	 * Полоса действий переносится по строкам.
	 *
	 * Кнопок в ней столько, сколько действий у экрана, и на узком они
	 * не помещаются в строку никогда: пять уезжают за край экрана вдвое.
	 * Прокрутка, как у чипов, тут не годится — уехавшее действие человек
	 * просто не найдёт, а «Отчислить» находить надо. Дефект жил с 2.8
	 * и не ловился стендом, потому что полоса спрятана, пока никто
	 * не выбран: измерять было нечего.
	 */
	.km-sticky-bar {
		flex-wrap: wrap;
		gap: 10px;
		padding: 12px 16px;
	}

	/* Строка узла на узком экране переносится, а не вылезает за край.
	   Название, метки потока и необязательности, подпись состояния — на широком
	   это одна строка, на телефоне в неё не помещается ничего. Нашлось стендом
	   карты курса: «Открыто» уезжало за правый край и тянуло страницу вбок.
	   Заголовок занимает строку целиком, метки и подпись встают под ним. */
	.km-node {
		flex-wrap: wrap;
		row-gap: 4px;
	}

	/* Кружок и название остаются в одной строке: разнесённые по разным,
	   они читаются как два разных узла. Переносится то, что можно отложить, —
	   подпись состояния и метки. */
	.km-node__title {
		flex: 1 1 auto;
		min-width: 0;
	}

	/* И причина закрытия — строкой целиком, под названием. Она длиннее
	   всего, что переносится рядом: «Откроется, когда вы заполните «Отзыв
	   о курсе»» не делит 375 пикселей с названием ни при каком выравнивании.

	   Встаёт она ПОД НАЗВАНИЕМ, а не под знаком: отступ складывается
	   из знака — он свой на каждом уровне, потому переменной — и зазора
	   строки занятия. */
	.km-node__why {
		flex: 1 0 100%;
		max-width: none;
		text-align: left;
		padding-left: calc( var( --km-node-glyph ) + 12px );
	}

	/* И зазор между строками у занятия свой, мельче бокового.
	   `gap: 12px` у строки занятия ставит оба сразу, а двенадцать пикселей
	   между названием и причиной под ним — это ровно то раздувание,
	   от которого правка. Между колонками они по-прежнему нужны. */
	.km-tree__card > .km-node,
	.km-tree__fold-head > .km-node { row-gap: 4px; }

	/*
	 * То же самое у строк редактора дерева, и нашлось это только в 6.2 —
	 * первым стендом этого экрана. Стендов у него не было вовсе: написан
	 * он в первом этапе, когда их не заводили, а потом писали к новым
	 * экранам. Строка узла несёт название, тип, поток, правило открытия,
	 * обязательность и нехватку словами — на телефоне это не помещается
	 * никогда, и до 6.2 просто уезжало за край.
	 *
	 * Заголовок занимает строку целиком, метки встают под ним: то же
	 * решение, что у карты курса выше.
	 */
	.km-tree__module,
	.km-tree__lesson,
	.km-tree__topic {
		flex-wrap: wrap;
		row-gap: 6px;
	}

	/*
	 * Название остаётся в одной строке с ручкой перетаскивания: разнесённые
	 * по разным, они читаются как две строки списка, а ручка — как пустая
	 * строка ни о чём. Переносится то, что можно отложить, — метки, подпись
	 * правила и нехватка словами.
	 */
	.km-tree__module-title,
	.km-tree__lesson-title,
	.km-tree__topic-title {
		flex: 1 1 auto;
		min-width: 0;
	}

	.km-tree__lesson { padding-left: 32px; }

	/*
	 * Строка перечня с датой на узком: название сверху, всё остальное под ним.
	 *
	 * Дата, название, пометка и ссылка в одну строку на 375 не помещаются
	 * никогда: столбец даты в 108 пикселей съедает треть экрана.
	 *
	 * Порядок меняется, и это решение, а не следствие переноса. `flex-wrap`
	 * переносит **раньше**, чем сжимает, — и без `order` строка складывалась
	 * по-разному от длины названия: короткое оставалось рядом с датой,
	 * длинное уезжало на свою строку, а пометка на третью. Три строки у одной
	 * записи и две у соседней читаются как разные списки.
	 *
	 * Теперь у всех одинаково: название целой строкой, под ним дата,
	 * пометка и ссылка. Порядок чтения экранным диктором остаётся прежним —
	 * он идёт по разметке, а не по `order`.
	 */
	.km-lineup--center .km-lineup__item {
		flex-wrap: wrap;
		row-gap: 6px;
	}

	.km-lineup--center .km-lineup__title {
		order: -1;
		flex: 1 1 100%;
	}

	.km-lineup__date { width: auto; }
	.km-lineup__date--right { width: auto; text-align: left; }

	/*
	 * Действия карточки эфира уходят под текст, а не сжимаются.
	 *
	 * `flex: none` держит их шириной содержимого, и на 375 «Страница эфира»
	 * с «В календарь» не помещаются рядом никогда: вторая уезжала за край
	 * экрана на десять пикселей. Общая мерка ширины про это молчала —
	 * она снималась на вкладке «Курс», а вылезало на «Трансляциях».
	 */
	.km-upnext > .km-actions {
		flex: 1 1 100%;
	}

	/*
	 * Плеер на узком — одна колонка, навигация сверху и свёрнутая.
	 *
	 * Липкую колонку в 320 пикселей на телефон не поставить, а развёрнутое
	 * дерево из сорока строк над занятием означает, что до текста человек
	 * доберётся прокруткой в полтора экрана. Раздел 12.4: плеер на телефоне —
	 * основной способ пользоваться системой, а не запасной.
	 */
	/* Полоса действий занятия переносится: три кнопки в строку не встают. */
	.km-lesson-nav > .km-btn,
	.km-lesson-nav__middle { flex: 1 1 100%; }

	.km-lesson-nav__middle { justify-content: stretch; }

	/* Карточка «Дальше» встаёт столбиком: название и длительность
	   в одну строку не помещаются и обрезались бы до половины. */

	/*
	 * Кабинет и страница курса на узком — одна колонка.
	 *
	 * Сайдбар курса уходит вниз: там счётчики и трекер, они второстепенны
	 * по замыслу. Кабинет одноколоночный на любой ширине — левое меню
	 * рисует тема.
	 */
	.km-course {
		grid-template-columns: minmax(0, 1fr);
		gap: 24px;
	}

	/* Одна колонка — липнуть нечему: сайдбар встаёт под содержимым,
	   и прилипший он закрыл бы собой конец дерева. */
	.km-course__side { position: static; top: auto; }

	.km-hero__title { font-size: 24px; }
	.km-hero__body { padding: 16px 18px 18px; }

	/* Наложение на 375: сорок два пикселя заголовка съедают экран целиком,
	   а 280 пикселей высоты при коротком названии дают полосу пустой
	   картинки под текстом. Высоту держит содержимое. */
	.km-hero--over .km-hero__title { font-size: 26px; }
	.km-hero--over .km-hero__body { min-height: 0; padding: 20px 18px 22px; gap: 10px; }

	/* Фирменная шапка на узком. Свои правила нужны потому, что селектор
	   `.km-hero--brand.km-hero--over` весом (0,2,0) перебивает соседние
	   правила этого же медиазапроса, — и 58px уехали бы на 375. */
	.km-hero--brand.km-hero--over .km-hero__title { font-size: 34px; }
	/* НА УЗКОМ У ТЕЛА СВОЯ ВЫСОТА, И ОНА НЕ НОЛЬ.
	   Настольные 374 сюда протекали по весу — (0,3,0) против (0,2,0)
	   у общего `min-height: 0` — и оставляли под именем ведущей полосу
	   пустого места. А ноль вместо них вернул прыжок: разметка едет
	   пустой, и шапка подрастала в тот миг, когда приходил ответ.
	   200 — измеренная высота заполненного тела на 375. */
	.km-hero--brand.km-hero--over .km-hero__body { padding: 18px 18px 20px; gap: 4px; min-height: 200px; }
	.km-hero--brand .km-hero__eyebrow { font-size: 11px; letter-spacing: 0.1em; }
	.km-hero--brand.km-hero--over .km-hero__lead { font-size: 14px; max-width: none; }
	/* НА УЗКОМ ШАПКА НЕ НАКЛАДЫВАЕТСЯ, А СТАНОВИТСЯ ДВУМЯ ЭТАЖАМИ.
	   Найдено глазами на стенде: надзаголовок, две строки заголовка,
	   двухстрочная подпись и имя ведущей не помещаются в 211 пикселей
	   картинки, тело оказывается выше её — и текст вываливается ниже
	   снимка на фон страницы. Сама разметка при этом права, и ни одна
	   мерка на ширину этого не видит: за край ничего не уезжает. */
	/* НА УЗКОМ ОБЛОЖКА ПОКАЗЫВАЕТСЯ ЦЕЛИКОМ, БЕЗ ОБРЕЗКИ ВОВСЕ.
	   Стояло `aspect-ratio: 16 / 9`, и это резало по бокам: обложка
	   «Пробуждения чакр» — 3:1, в коробку 1.78:1 она влезает только
	   потерей 110 пикселей слева и справа. Справа у неё лотос, то есть
	   весь сюжет. Найдено меркой обрезки, заведённой в тот же день.

	   Пропорция снята, высоту задаёт сама картинка: здесь она стоит
	   отдельным этажом, и обрезать её незачем — места под ней столько,
	   сколько нужно. */
	.km-hero--brand.km-hero--over .km-hero__cover {
		position: static;
		aspect-ratio: auto;
		height: auto;
	}

	.km-hero--brand.km-hero--over .km-hero__cover img { height: auto; }

	.km-hero--brand.km-hero--over .km-hero__body {
		background: var(--km-surface);
		border: 1px solid var(--km-border);
		border-top: none;
		border-radius: 0 0 var(--km-radius-card) var(--km-radius-card);
	}

	/* НА УЗКОМ ОБА ВИДА ВЫГЛЯДЯТ ОДИНАКОВО, И ЭТО НЕ УПРОЩЕНИЕ.
	   Тело съехало на белую карточку, значит белый текст на нём не виден
	   вовсе — светлый вид пропадал целиком. Затемнение там тоже ни к чему:
	   затемнять нечего, картинка выше. Цвет берётся тёмный у обоих. */
	.km-hero--brand.km-hero--lume.km-hero--over::after { content: none; }
	.km-hero--brand.km-hero--lume .km-hero__eyebrow { color: var(--km-brand-navy); }
	.km-hero--brand.km-hero--lume.km-hero--over .km-hero__title { color: var(--km-brand-navy); }
	.km-hero--brand.km-hero--lume .km-hero__title-rest { color: var(--km-brand-teal); }
	.km-hero--brand.km-hero--lume.km-hero--over .km-hero__lead { color: var(--km-brand-navy); }
	.km-hero--brand.km-hero--lume .km-hero__author { color: var(--km-brand-navy); }

	/* ЦЕНА И СЧЁТ КУРСАНТОВ — ПО ТОЙ ЖЕ ПРИЧИНЕ И В ТУ ЖЕ СТОРОНУ.
	   Правка 8 сентября 2026, найдена глазами на 375.

	   Правила выше вернули тёмный цвет заголовку, подписи и имени
	   ведущей — а цена и счёт курсантов остались белыми с
	   `.km-hero--over` и пропали на белой карточке целиком. Мерка
	   «цена и кнопка в шапке» при этом зелена: элемент на месте,
	   он просто невидим. Записанный класс — признак существует
	   не тогда, когда поставлен, а когда отличается от подложки.

	   Цвета не выдуманы: это те самые, что стоят у `.km-price`,
	   `.km-price__from`, `.km-price__stages` и `.km-meta` по умолчанию.
	   Кегль не трогаем — он про размер, а не про видимость. */
	.km-hero--brand.km-hero--over .km-price { color: var(--km-text); }
	.km-hero--brand.km-hero--over .km-price__from { color: var(--km-text-muted); }
	.km-hero--brand.km-hero--over .km-price__stages { color: var(--km-text-secondary); }
	.km-hero--brand.km-hero--over .km-meta { color: var(--km-text-muted); }

	/* Кнопки полосы прогресса переносятся, а не уезжают за край:
	   «Продолжить обучение» и «Карта курса» в 375 рядом не помещаются. */
	.km-headline > .km-actions { flex: 1 1 100%; flex-wrap: wrap; }
	.km-headline > .km-actions > .km-btn { flex: 1 1 auto; justify-content: center; }

	/* Затемнение глубже и ровнее, чем на широком.
	 *
	 * Градиент задан долями высоты шапки: на широком текст занимает её
	 * верхнюю треть и лежит на тёмном, а на узком заголовок переносится
	 * на две строки и текст занимает её почти целиком — подзаголовок
	 * доезжает туда, где прозрачность уже 0.1. На светлой части обложки
	 * он там пропадает, и найдено это глазами, а не меркой: «текст белый»
	 * и «текст поверх обложки» обе остаются правдой.
	 *
	 * Мобильной шапки макет не рисует вовсе, поэтому это решение, а не
	 * воспроизведение: читаемость важнее верности долей. */
	.km-hero--over::after {
		background: linear-gradient(
			to bottom,
			rgba(63, 52, 46, 0.72) 0%,
			rgba(63, 52, 46, 0.5) 100%
		);
	}

	/* Вкладки уезжают прокруткой: пять штук в 375 не помещаются никогда. */
	.km-tabs {
		flex-wrap: nowrap;
		overflow-x: auto;
		gap: 18px;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
	}

	.km-tabs::-webkit-scrollbar { display: none; }
	.km-tabs > * { flex: none; }

	/* Чипы уезжают прокруткой, а не переносятся столбиком. */
	.km-chips {
		flex-wrap: nowrap;
		overflow-x: auto;
		-webkit-overflow-scrolling: touch;
		scrollbar-width: none;
		padding-bottom: 2px;
	}

	.km-chips::-webkit-scrollbar {
		display: none;
	}

	.km-chips > * {
		flex: none;
	}

	/* На узком компактность ни к чему: поле занимает строку целиком. */
	.km-row > .km-field,
	.km-row > .km-field--wide {
		flex: 1 1 100%;
		max-width: none;
	}

	/* Таблица становится списком карточек.
	 *
	 * `display: block` у самой таблицы обязателен. Ниже блочными становятся
	 * строки и ячейки, а сам элемент оставался таблицей — и продолжал
	 * подбирать ширину по содержимому, а не по контейнеру. Пока подписи
	 * столбцов были короткими, это не проявлялось; на воронке отчётов
	 * («Отвал перед узлом» рядом со значением) карточка вылезла за край
	 * экрана и потянула страницу вбок — ровно то, что отвергает раздел 12.4.
	 * Дефект жил с 2.8 на каждом экране и ждал строки подлиннее. */
	.km-table {
		display: block;
		overflow-x: visible;
		background: none;
		border: none;
		box-shadow: none;
		border-radius: 0;
	}

	.km-table thead {
		display: none;
	}

	.km-table tbody,
	.km-table tr,
	.km-table td {
		display: block;
		width: auto;
	}

	.km-table tr {
		position: relative;
		margin-bottom: 10px;
		padding: 14px 16px;
		background: var(--km-surface);
		border: 1px solid var(--km-border);
		border-radius: 14px;
		box-shadow: var(--km-shadow-card);
	}

	.km-table td {
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: 12px;
		padding: 3px 0;
		border: none;
	}

	/* Подпись столбца переезжает в строку карточки. */
	.km-table td[data-label]::before {
		content: attr(data-label);
		flex: none;
		font-size: 12px;
		color: var(--km-text-secondary);
	}

	/* Первая ячейка — выбор, вторая — имя: они держат верх карточки. */
	.km-table td:first-child {
		position: absolute;
		top: 14px;
		right: 16px;
		padding: 0;
	}

	.km-table td:nth-child(2) {
		display: block;
		padding-right: 28px;
		padding-bottom: 8px;
	}

	.km-table td:nth-child(2) .km-text {
		font-size: 15px;
		font-weight: 700;
	}

	/*
	 * Таблица, у которой первая ячейка — название, а не выбор.
	 *
	 * Правила выше написаны под список зачислений: там первой идёт клетка
	 * с флажком, и на узком экране она уезжает в правый верхний угол
	 * карточки, а вторая — имя — держит её верх. Там, где первой стоит
	 * название, то же правило кладёт его поверх следующей строки: в карточке
	 * «Тип» и «Предложение» оказываются друг на друге. Нашлось глазами
	 * на стенде предложений (3.6) — измерения этого не видели, потому
	 * что мерили ширину и наличие, а не пересечение ячеек.
	 */
	.km-table--title-first td:first-child {
		position: static;
		display: block;
		padding: 0 0 8px;
	}

	.km-table--title-first td:first-child::before {
		content: none;
	}

	.km-table--title-first td:nth-child(2) {
		display: flex;
		padding-right: 0;
	}

		/*
	 * Столбец строк внутри ячейки прижимается к правому краю — туда же,
	 * куда `space-between` уводит одиночное значение. Иначе строка
	 * с рассрочкой висела бы слева, а цена справа.
	 */
	.km-lines { align-items: flex-end; }

	/* Значение справа не переносится в три строки, а обрезается. */
	.km-table td > span,
	.km-table td > div {
		min-width: 0;
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
	}

	.km-table td:nth-child(2) > div {
		white-space: normal;
	}
}

/* ─── Карточка предложения ──────────────────────────────────────────────────
 *
 * Макет `design/offers-screen-v1.html`, принят 20 августа 2026. Заменила
 * строку таблицы на экране предложений.
 *
 * Номер стоит первым и набран моноширинным: им заполняется поле `offer_id`
 * у товара в магазине, и человек его **переписывает глазами**. Моноширинный
 * здесь не украшение — в пропорциональном шрифте единица и семёрка в номере
 * из пятнадцати цифр различаются хуже.
 *
 * Снятое с продажи приглушено целиком, а не помечено значком: видно,
 * но не спорит с действующим.
 */
.km-offer {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  padding: 14px 18px;
  margin-bottom: 10px;
}

.km-offer--off {
  opacity: 0.72;
}

.km-offer__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.km-offer__num {
  flex: none;
  font-family: var(--km-mono);
  font-size: 12px;
  color: var(--km-accent);
  background: var(--km-warm-bg);
  padding: 2px 7px;
  border-radius: 5px;
}

.km-offer--off .km-offer__num {
  color: var(--km-text-muted);
  background: var(--km-page);
}

/*
 * Название — кнопка: по нему открывается правка. Кнопкой, а не ссылкой,
 * потому что никуда не ведёт, и не всей карточкой, потому что внутри
 * карточки есть вторая кнопка — «Скопировать», — а кнопка в кнопке
 * разметкой не бывает.
 *
 * Одна строка с усечением: на боевом три предложения названы «Продвинутый
 * курс "Практикум по Местам Вознесения". Февраль 2026 · Новый поток»
 * и длиннее всей остальной строки. Полное показывается подсказкой.
 */
.km-offer__name {
  flex: 1;
  min-width: 200px;
  border: 0;
  background: none;
  padding: 0;
  text-align: left;
  cursor: pointer;
  /* И кнопкой, и ссылкой: на экране предложений название открывает панель
     правки на месте (никуда не ведёт — значит кнопка), а на карточке потока
     ведёт на соседний экран (значит ссылка). Подчёркивание и цвет ссылки
     снимаются здесь, чтобы обе выглядели одинаково: человек читает список
     предложений, а не два разных списка. */
  text-decoration: none;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  color: inherit;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.km-offer__name:hover {
  color: var(--km-accent);
}

.km-offer--off .km-offer__name {
  color: var(--km-text-secondary);
}

/*
 * `--link` СНЯТ 20 АВГУСТА 2026, И ЭТО НЕ ОТКАТ, А ЗАМЕНА.
 *
 * Он красил название в оранжевое там, где оно ведёт на другой экран, —
 * чтобы было видно, что по нему нажимают. Признак верный, но **второй**:
 * ту же работу делает явная кнопка «Править», заведённая на экране
 * предложений в тот день, когда человек написал «ничего не кликается».
 *
 * Двумя разными признаками одного и того же карточки одного семейства
 * стали выглядеть по-разному — а сводили их к одному стилю ровно затем,
 * чтобы человек не узнавал одну и ту же вещь заново на каждом экране.
 * Победил признак, который старше и уже принят.
 */

.km-offer__state {
  flex: none;
  font-size: 12px;
  color: var(--km-ok);
}

.km-offer--off .km-offer__state {
  color: var(--km-text-muted);
}

/*
 * Чья это карточка: программа предложения и программа потока.
 *
 * Стоит между шапкой и условиями и **приглушена сильнее условий**: это
 * не свойство предложения, а место, где оно живёт. Написана одним
 * правилом на оба экрана по той же причине, по какой у них общая карточка
 * `km-offer`, — второе такое же правило разъехалось бы на первой правке.
 *
 * `--off` не приглушает её отдельно: она и так самая тихая строка
 * карточки, и приглушать тише некуда.
 */
.km-offer__program {
  /*
   * Своих полей нет: в колонке промежуток даёт `gap` родителя, и поле
   * тега прибавлялось бы к нему. Это общая проверка стендов — она
   * и поймала здесь `12px` снизу, которые браузер даёт абзацу.
   */
  margin: 0;
  font-size: 12px;
  color: var(--km-text-muted);
  line-height: 1.4;
}

/*
 * А в карточке списка колонки нет: `.km-offer` — обычный блок, и строки
 * в нём разнесены полями, как `__terms` и `__facts` рядом.
 */
.km-offer .km-offer__program {
  margin-top: 6px;
}

.km-offer__terms {
  margin-top: 8px;
  font-size: 13px;
  color: var(--km-text-secondary);
  line-height: 1.6;
}

/*
 * Третья строка карточки: несколько коротких фактов через разделитель.
 *
 * У предложения это товар магазина и его номер, у потока — группа, куратор
 * и число предложений. Форма одна и та же, и написана она **одним правилом
 * на оба имени**: два одинаковых правила разъехались бы на первой правке,
 * а слом одного из них уцелел бы — второе делает то же самое.
 *
 * Имена при этом разные, потому что содержимое разное: `__shop` у потока
 * означал бы товар, которого у него нет.
 */
.km-offer__shop,
.km-offer__facts {
  margin-top: 8px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.km-offer__shop-name,
.km-offer__fact {
  font-size: 12px;
  color: var(--km-text-secondary);
}

/*
 * Ненастроенное — приглушённым, но **написанным**: «группы нет» человек
 * прочтёт и поймёт, а пустое место читается как «экран недорисован».
 * Правило проекта про место, за которым нет данных: либо молчит, либо
 * названо словами.
 */
.km-offer__fact--none {
  color: var(--km-text-muted);
}

.km-offer__shop-id {
  font-size: 11px;
  font-family: var(--km-mono);
  color: var(--km-text-muted);
}

.km-offer__glyph {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--km-text-muted);
}

.km-copy {
  border: 1px solid var(--km-border);
  background: transparent;
  color: var(--km-text-secondary);
  font-family: inherit;
  font-size: 11px;
  padding: 3px 9px;
  border-radius: var(--km-radius-badge);
  cursor: pointer;
}

.km-copy:hover {
  background: var(--km-page);
}

/* Полоса выбора, липнущая к НИЗУ окна. Задача 1.18.0, редактор дерева.

   Наверху у редактора уже висит своя полоса с «Сохранить», и обе с `top: 0`
   легли бы одна на другую. Увидеть такое можно только на собранной странице:
   по отдельности обе правы.

   `top: auto` здесь обязателен, а не аккуратность: базовый класс задаёт
   `top: 0`, и без сброса браузер прилепит полосу к верху, а `bottom`
   проигнорирует. */
.km-sticky-bar--bottom {
  top: auto;
  bottom: 0;
  border-bottom: none;
  border-top: 1px solid var(--km-border);
  flex-wrap: wrap;
  row-gap: 10px;
}

/* Подпись и поле, стоящие В СТРОКУ, а не столбиком.

   `km-label` рассчитан на поле под собой, в колонке `km-field`; в полосе
   он оказывается рядом с полем, и вертикальные отступы колонки здесь лишние. */
.km-label--inline { flex: none; }
.km-select--inline { width: auto; min-width: 190px; }

/* Флажок выбора в строке дерева.

   Не даёт строке утащить себя перетаскиванием: у `km-tree--editable` вся
   строка `draggable`, и захват за флажок начинал бы перенос вместо отметки. */
.km-tree__pick {
  flex: none;
  display: flex;
  align-items: center;
  margin-right: 2px;
}

/* ── Карточка эфира ─────────────────────────────────────────────────────────

   Макет `design/live-page-v2.html`, решения человека 20 августа 2026.

   Три состояния, и различает их **вид, а не только текст**: у прошедшего
   рамка гаснет до обычной, у идущего кикер краснеет и получает точку.
   Признак существует ровно настолько, насколько отличается от соседнего
   случая, — поэтому у стенда мерки на отличие, а не на наличие класса. */
.km-live {
  background: var(--km-surface);
  border: 1px solid var(--km-warm-border);
  border-radius: var(--km-radius-card);
  padding: 20px 22px;
}

/* Прошедший эфир — приглушённая карточка: смотреть в ней больше нечего,
   и тёплая рамка обещала бы обратное. */
.km-live--past { border-color: var(--km-border); }

.km-live__kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--km-text-muted);
}

.km-live__kicker--now { color: var(--km-live); font-weight: 700; }

/* Точка — своя, а не буква и не эмодзи: она обязана быть видна на любом
   шрифте и не читаться экранным диктором как слово. */
.km-live__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--km-live);
  flex: none;
}

/* ДАТА — ГЛАВНОЕ НА КАРТОЧКЕ. Решение человека: день недели, число, время,
   крупно. До эфира человеку нужна не кнопка, которой ещё нет, а когда. */
.km-live__when {
  margin-top: 10px;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.25;
}

.km-live__when small {
  display: block;
  font-size: 14px;
  font-weight: 400;
  color: var(--km-text-secondary);
  margin-top: 4px;
}

.km-live__row { margin-top: 16px; display: flex; gap: 10px; flex-wrap: wrap; }

.km-live__cal {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--km-border);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.km-live__cal-label { font-size: 13px; color: var(--km-text-secondary); }
.km-live__sep { color: var(--km-text-muted); font-size: 13px; }

.km-cal-link {
  font-size: 13px;
  color: var(--km-accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.km-cal-link:hover { border-bottom-color: var(--km-accent); }

.km-live__hint {
  margin-top: 12px;
  font-size: 13px;
  color: var(--km-text-muted);
  line-height: 1.5;
}

/* Значок площадки внутри кнопки трансляции. */
.km-btn__glyph { width: 17px; height: 17px; flex: none; }

/* ── Обсуждение: чат эфира и комментарии занятия ────────────────────────────

   Один вид на оба случая — меняется только заголовок и место на странице.
   Разный вид означал бы, что это две разные вещи, а это одна: комментарии
   к записи материала. */
/* Блок обсуждения — КАРТОЧКА, как в макете (`design/live-page-v2.html`),
   и с собственным отступом сверху.

   Оба правила поставлены после того, как экран посмотрели глазами
   на боевом, и оба про свою беду.

   **Карточка.** Первая редакция полагалась на `km-section`: он даёт воздух
   внутри и не даёт ни фона, ни рамки. Разговор оказывался на голом фоне
   страницы рядом с материалом, который стоит карточкой, — и читался как
   довесок, а не как часть занятия.

   **Отступ сверху свой, а не от соседства.** `.km-section + .km-section`
   даёт ритм соседям, а между кнопками и разговором стоит СПРЯТАННАЯ
   подсказка «отметьте, что прошли». `display: none` селектору соседства
   не виден, элемент в разметке есть — и правило молча не срабатывает.
   Ровно та беда, что уже ловилась на экране потоков; здесь она вылезла
   зазором в 16 пикселей вместо 28. */
/*
 * МЕСТО ДЛЯ ЧУЖОЙ РАЗМЕТКИ. Приёмка человека 21 августа 2026, обобщено
 * 7 сентября.
 *
 * Работа у класса ровно одна — вернуть чужой разметке ОБЫЧНЫЙ ПОТОК.
 * Колонка чтения занятия флексовая (`.km-content`), и чужой блок в ней
 * ведёт себя не так, как на любой обычной странице.
 *
 * ## Двое, и оба измерены
 *
 * **Разговор wpDiscuz.** Тема ставит своему `.comments-area` поля
 * `50px auto`: в потоке `auto` равны нулю и блок занимает всю ширину,
 * во флексе — съедают свободное место и сжимают блок по содержимому.
 * Замерено рядом с эталоном: запись блога — 410 при нулевых полях
 * по бокам, занятие без этого класса — 286 при полях 64.
 *
 * **Шаблон Elementor** внутри материала. Он приходит своим контейнером
 * `.elementor` и своим оформлением, а `wrapProse()` заворачивал его
 * в белую карточку с рамкой и скруглениями — поверх чужой плашки.
 * Измерено на боевом: девять мест с кнопками практик, на каждом ровно
 * один такой узел.
 *
 * ## Почему ОДИН класс, а не два
 *
 * Тело у них было бы побайтно одинаковым, а правило, написанное дважды,
 * прячет само себя: слом одного написания уцелеет на другом. Здесь
 * не «разговор» и не «Elementor» — здесь **чужая разметка в нашей
 * колонке**, и повод у обоих один.
 *
 * Прежнее имя `km-talk-host` называло одного из двух потребителей
 * и потому мешало увидеть второго.
 *
 * Своего воздуха здесь не заводится: сверху и снизу его даёт сама чужая
 * разметка. Класс, у которого одна строка, — это не мелочь, а место,
 * где записано, почему она нужна.
 */
.km-plain-host {
  display: block;
  min-width: 0;
}

.km-talk {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  box-shadow: var(--km-shadow-card);
  padding: 20px 22px;
  gap: 14px;

  /* Воздух с ОБЕИХ сторон, а не только сверху.

     Блок стоит то над кнопками, то под ними — решает состояние эфира, —
     и отступ, поставленный с одной стороны, работает ровно в половине
     случаев. Найдено стендом: на боевом эфир прошёл, разговор стоит внизу,
     и упирающийся в кнопки «Чат эфира» там не увидеть никак. */
  margin: 28px 0;
}

.km-talk__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.km-talk__item {
  border-top: 1px solid var(--km-border);
  padding-top: 14px;
}

.km-talk__item:first-child { border-top: none; padding-top: 0; }

/* Ветка ответов. Раздел 17.8: перенос из LearnDash везёт `comment_parent`,
   и показ обязан его показывать — иначе ответ встанет самостоятельной
   репликой, потеряв, кому отвечал.

   Отступ слева и линия делают вложенность видимой БЕЗ подписи «в ответ
   такому-то»: признак существует не тогда, когда поставлен, а тогда,
   когда отличается от соседнего случая. Верхнее сообщение линии не имеет,
   ответ имеет — этим они и различаются.

   Глубина любая: правило работает на каждом следующем уровне само,
   потому что задано вложенностью, а не номером уровня. */
.km-talk__list--replies {
  margin-top: 14px;
  padding-left: 16px;
  border-left: 2px solid var(--km-border);
  gap: 12px;
}

.km-talk__list--replies .km-talk__item {
  border-top: none;
  padding-top: 0;
}

.km-talk__head {
  margin: 0 0 4px;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.km-talk__author { font-size: 14px; font-weight: 600; }
.km-talk__when { font-size: 12px; color: var(--km-text-muted); }

.km-talk__body { font-size: 14px; line-height: 1.6; color: var(--km-text-secondary); }
.km-talk__body p { margin: 0 0 8px; }
.km-talk__body p:last-child { margin-bottom: 0; }

.km-talk__form { margin-top: 16px; }

/* ── Шапка и фильтры административного экрана ────────────────────────────────

   Два яруса вместо четырёх. Решение человека 21 августа 2026 по экрану
   зачислений: поиск, сегменты, программы, статус с источником и «Ещё фильтры»
   занимали экран целиком, и до первой строки данных надо было прокрутить.

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

.km-toolbar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;

  /* Воздух между ярусами задаётся здесь, а не соседством `.km-section`.

     Ярусы вышли из секций ради сжатия шапки — и вместе с секциями ушёл
     ритм: ряд условий встал вплотную к ряду полей, а ряд сегментов
     к нему. Ни одна общая мерка этого не поймала: «секции разнесены
     по вертикали» спрашивает про `.km-section`, а ярусов среди них
     больше нет. Найдено глазами на боевом, 21 августа 2026. */
  margin-bottom: 16px;
}

/* Заголовок в строке с полем — меньше обычного H1.

   36 пикселей рядом с полем поиска высотой 46 читаются не как заголовок
   экрана, а как помеха: строка распадается на «большое слово» и «всё
   остальное». Правится здесь, а не по месту: правило 13. */
.km-toolbar .km-h1 { font-size: 24px; line-height: 1.2; }

/* Поиск тянется, кнопки — нет: растягивать надо то, куда вводят текст. */
.km-toolbar__search {
  flex: 1 1 240px;
  min-width: 200px;
  max-width: 380px;
}

.km-filters {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

/* «Ещё фильтры» стоит последним в том же ярусе и раскрывается на всю его
   ширину. Три поля рядом с четырьмя списками не поместились бы ни на одном
   экране, а свой ярус ради свёрнутой строки — ровно то, что задача велит
   убрать.

   Разворот делается сменой `flex-basis` у самого `details`: когда оно
   открыто, элемент занимает строку целиком и поля встают под списками. */
.km-filters__more { flex: none; }
.km-filters__more[open] { flex: 1 0 100%; }

.km-filters__more-head {
  cursor: pointer;
  list-style: none;
  font-size: 13px;
  font-weight: 600;
  color: var(--km-text-secondary);
  padding: 8px 4px;
  white-space: nowrap;
}

.km-filters__more-head::-webkit-details-marker { display: none; }
.km-filters__more-head::after { content: ' ▾'; }
.km-filters__more[open] .km-filters__more-head::after { content: ' ▴'; }
.km-filters__more-head:hover { color: var(--km-accent); }

.km-filters__more-body { padding-top: 12px; }

/* ПЛОТНОСТЬ ШАПКИ — ПО МАКЕТУ, И ЭТО ЕЁ СМЫСЛ.

   Общие `.km-input` и `.km-select` заведены под **формы**: поле, в которое
   вводят и подолгу смотрят, обязано быть крупным. Шапка списка — не форма,
   а строка условий: её читают краем глаза и по ней не печатают.

   Разница вышла в четверть экрана. Померено рядом с макетом, 1180:
   поиск 49 против 37, список 49 против 35, кнопка 41 против 35, вся шапка
   151 против 126. Именно она и читается как «ненаглядность»: до первой
   строки данных остаётся ровно столько же прокрутки, сколько было
   до сжатия ярусов.

   Правится **в шапке**, а не в самих `.km-input` и `.km-select`: те же
   классы носят формы редактора узла, потока, предложения — там крупный
   размер верен. На узком возвращается обратно: 35 пикселей — это меньше
   пальца, и список условий там открывают именно пальцем. */
.km-toolbar .km-input,
/* Ряд чипов внутри ряда фильтров обязан переноситься сам.

   Флексовый элемент по умолчанию не сжимается уже своего содержимого
   (`min-width: auto`), и вложенный `.km-chips` вытягивался в одну строку
   в 941 пиксель при экране в 375: переносить внутри себя ему было
   нечего — места ему обещали сколько угодно.

   Ловится это только измерением на узком: на широком экране разметка
   ведёт себя правильно, и оба вида выглядят верными. Поймала общая мерка
   «ничего не вылезает за ширину экрана» 23 августа 2026. */
.km-filters .km-chips {
  flex: 1 1 auto;
  min-width: 0;
}

.km-filters .km-select {
  font-size: 14px;
  padding: 8px 12px;
}

/* Список не растягивается под длинное название.

   `.km-select--inline` шириной `auto` берёт ширину самого длинного пункта,
   а название программы на боевом — «Практикум по Местам Вознесения 2026 ·
   263». Четыре таких списка не поместятся в строку ни на одном экране,
   и ярус, ради которого всё и сжималось, развалится на два. */
.km-filters .km-select { max-width: 260px; }

.km-toolbar__search .km-input { padding-left: 36px; }
.km-toolbar__search .km-input-group__icon { left: 12px; }

.km-toolbar .km-btn { padding: 9px 15px; }

/* Сегменты: подпись и чипы одной строкой.

   Подпись стоит снаружи контейнера чипов — скрипт перерисовывает его
   на каждый ответ и начинает с очистки. */
/* Сегменты: подпись и чипы **одной строкой**, с переносом.

   Подпись стоит снаружи контейнера чипов — скрипт перерисовывает его
   на каждый ответ и начинает с очистки. Но выглядеть это обязано одной
   строкой, а не подписью над рядом кнопок: отдельная строка — это ярус,
   а ярусы здесь и сжимали.

   Поэтому контейнер чипов объявлен `display: contents`: его дети встают
   в этот же ряд, и подпись оказывается перед первым чипом. Роль ему
   при этом нужна — без неё элемент с `display: contents` вместе
   со своим `aria-label` уходит из дерева доступности. */
.km-segments {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.km-segments .km-chips { display: contents; }

.km-segments__label {
  font-size: 12px;
  color: var(--km-text-muted);
  flex: none;
  margin-right: 3px;
}

/* Чип сегмента — обычными буквами и мельче фильтров.

   Общий `.km-chip` набран капсом с разрядкой: он заведён под навигацию,
   где чипов три-четыре и они главные на экране. Сегментов шесть, они
   стоят под рядом фильтров и обязаны читаться тише его, а не громче.
   Правится модификатором, а не самим `.km-chip`: тот же класс носят
   чипы на шести других экранах.

   Отступы у выбранного те же, что у невыбранного: общий `.km-chip`
   прибавляет пиксель на каждую сторону, и ряд из шести чипов дёргался
   при каждом нажатии. */
.km-chip--segment {
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  padding: 5px 11px;
  border-radius: 20px;
}

.km-chip--segment.is-active { padding: 5px 11px; }

/* Число — главное на чипе, и потому темнее и плотнее названия.

   Общий `.km-chip__count` приглушён до шести десятых: в навигации число
   справочное. Здесь наоборот — ради него чип и читают: «нажимаешь,
   чтобы выяснить, что сегмент пуст» и было тем, что правится. */
.km-chip--segment .km-chip__count {
  opacity: 1;
  font-weight: 600;
  color: var(--km-text);
  margin-left: 6px;
}

@media (max-width: 640px) {
  /* На узком заголовок занимает строку целиком, поиск — следующую:
     иначе поле сжимается до пары букв и в него не видно, что набрано. */
  .km-toolbar .km-h1 { flex: 1 0 100%; font-size: 22px; }
  .km-toolbar__search { flex: 1 0 100%; max-width: none; }

  /* Два действия делят строку пополам, а не встают лесенкой каждое своей
     ширины: на 375 «Зачислить вручную» и «Выгрузить CSV» занимали две
     строки разной длины, и шапка читалась как обрывок списка. */
  .km-toolbar .km-btn { flex: 1 1 0; }

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

     Перенос у колонки складывает элементы в несколько колонок, а ширину
     строки задаёт самый широкий из них: ряд чипов с прокруткой хочет
     тысячу с лишним пикселей и растягивал под себя весь экран. Колонка
     без переноса даёт каждому элементу ровно ширину контейнера, и полоса
     чипов уезжает прокруткой внутри себя, как и задумано.

     Найдено измерением на 375 у журнала действий 23 августа 2026:
     на широком экране обе редакции выглядят одинаково верными. */
  .km-filters { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
  .km-filters .km-select { width: 100%; }
  .km-filters__more-head { padding-left: 0; }

  /* Плотность шапки возвращается к обычной: 35 пикселей меньше пальца,
     а список условий на телефоне открывают именно им. Сжатие было
     про экономию высоты на широком, где всё в одну строку. */
  .km-toolbar .km-input,
  .km-filters .km-select {
    font-size: 15px;
    padding: 12px 14px;
  }

  /* Место под иконку возвращается отдельно.

     Сокращённая запись `padding` выше стирает и левое поле, а оно тут
     не косметика: иконка поиска лежит поверх, и без места буква «И»
     оказывается прямо под лупой. Найдено глазами на 375 — ни одна мерка
     наложения текста на иконку не спрашивает. */
  .km-toolbar__search .km-input { padding-left: 40px; }
  .km-toolbar__search .km-input-group__icon { left: 14px; }

  /* Потолок ширины снимается: на узком список занимает строку целиком,
     а 260 пикселей оставляли справа пустое место шире пальца. */
  .km-filters .km-select { max-width: none; }

  .km-toolbar .km-btn { padding: 12px 18px; }
}

/* Несколько строк в одной ячейке — столбцом.

   На узком экране ячейка становится строкой карточки (`display: flex`,
   подпись слева, значение справа), и положенные в неё блоки подряд
   становятся флексовыми элементами: «49 000 ₽» и «Рассрочка: 3 из 3»
   вставали рядом и читались одной фразой. Проверка на текст этого
   не видит — в `textContent` переноса нет ни при каком оформлении.
   Найдено измерением на стенде зачислений, 375, 21 августа 2026.

   Столбец, а не `display: block`: у блока нет промежутка между строками,
   а сжимать его до нуля здесь нечем — строки разной высоты. */
.km-lines {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

/* Обрезается каждая строка по отдельности, а не столбец целиком:
   правило `.km-table td > div` теперь попадает на обёртку, и без этого
   многоточие ставилось бы вместо всех строк разом.

   СУЖЕНО ДО ТАБЛИЦЫ, и это не осторожность. Без `.km-table` впереди
   правило доставало до карточки зачисления, где тем же `.km-lines`
   собраны списки работ, правил и истории: неразрывными становились
   целые фразы, и карточка вылезала за край экрана на 375 почти
   на сотню пикселей. Один класс успел завести два смысла — в ячейке
   таблицы строки короткие и обрезаются, в карточке они переносятся. */
.km-table .km-lines > * {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* ── Карточка зачисления ────────────────────────────────────────────────────

   Всё об одном человеке на одном экране. Решение человека 22 августа 2026.
   Разметка и порядок блоков — по `design/enrollment-card-v1.html`. */

.km-toolbar--card { align-items: flex-start; }

.km-toolbar__who { flex: 1 1 240px; min-width: 240px; }

.km-back {
  font-size: 13px;
  color: var(--km-text-secondary);
  text-decoration: none;
  display: inline-block;
  margin-bottom: 4px;
}

.km-back:hover { color: var(--km-accent); }

.km-toolbar--card .km-h1 { margin-bottom: 3px; }

/* Карточка с полями. `.km-card` в системе есть, но она под обложки курсов
   и потому без внутреннего отступа: у неё содержимое — картинка во всю
   ширину. Здесь отступ нужен, и он модификатором, а не правкой базы. */
.km-card--pad { padding: 16px 18px; margin-bottom: 12px; }

.km-card__t {
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--km-text-muted);
  font-weight: 600;
  margin: 0 0 12px;
}

/* Пары «подпись — значение» сеткой, а не таблицей: подписи разной длины,
   и колонка под самую длинную оставила бы половину карточки пустой. */
.km-facts {
  display: grid;
  grid-template-columns: repeat( auto-fit, minmax( 200px, 1fr ) );
  gap: 12px 22px;
}

/* Пара целиком: она ячейка сетки, и своего оформления ей не нужно —
   но имя у неё быть обязано. Класс, поставленный в разметке и не заведённый
   в системе, не сверяется ни с чем, и первый же опечатанный уехал бы
   на экран незамеченным. */
.km-fact { min-width: 0; }

.km-fact__k { font-size: 12px; color: var(--km-text-muted); }
.km-fact__v { margin-top: 2px; line-height: 1.4; font-size: 14px; }

/* Две колонки для прогресса и работ: их читают вместе, отвечая на вопрос
   «идёт ли человек». На узком встают друг под друга — рядом им нужно
   по трети экрана, а его нет. */
.km-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* Колонке разрешено сжиматься.

   У элемента сетки минимальная ширина по умолчанию `auto`, то есть
   по содержимому: одна неразрывная строка внутри — и колонка перестаёт
   помещаться в свою дорожку, а страница уезжает за край экрана. Внутри
   всё и так умеет обрезаться, мешало только это. */
.km-cols > * { min-width: 0; }

/* Строка ленты: название тянется, метка и дата не жмутся. */
.km-line {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--km-border-soft);
}

.km-line:last-child { border-bottom: none; }

.km-line__n {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 14px;
}

.km-line__d { font-size: 12px; color: var(--km-text-muted); white-space: nowrap; }

/* Метка вида: мельче чипа и не нажимается — она подпись, а не кнопка. */
.km-tag-sm {
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--km-text-secondary);
  background: var(--km-bg);
  border: 1px solid var(--km-border);
  padding: 2px 7px;
  border-radius: 5px;
  white-space: nowrap;
}

/* ПУСТО — ЭТО ОТВЕТ, А НЕ ПРОПУСК.

   Пустой блок человек читает как поломку: он не отличит «работ нет»
   от «работы не загрузились». Поэтому у каждого списка карточки есть
   слова на случай, когда в нём ничего, и выглядят они как текст,
   а не как строка данных. */
.km-empty-line {
  font-size: 13px;
  line-height: 1.5;
  color: var(--km-text-muted);
  /* Отступ полем, а не отступом: внутри колонки расстояния задаёт `gap`,
     и своё поле у ребёнка спорит с ним. Общая мерка стенда это ловит. */
  margin: 0;
  padding-top: 8px;
}

/* Ссылка на чужую установку. Подчёркнута намеренно: она уводит с сайта,
   и выглядеть как обычное значение поля ей нельзя. */
.km-link-out { color: var(--km-accent-dark, #C85A28); text-decoration: underline; }
.km-link-out:hover { color: var(--km-accent); }

/* Восемьдесят девять тегов сеткой — это стена, и открывается она
   по требованию. Свёрнутое `details` отдаёт полные размеры невидимого,
   и стенд обязан открывать его перед измерением (5.5). */
.km-tags { grid-column: 1 / -1; margin-top: 4px; }

.km-tags__head {
  cursor: pointer;
  list-style: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--km-text-secondary);
}

.km-tags__head::-webkit-details-marker { display: none; }
.km-tags__head::after { content: ' ▾'; }
.km-tags[open] .km-tags__head::after { content: ' ▴'; }
.km-tags__head:hover { color: var(--km-accent); }

.km-tags__cloud { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

@media (max-width: 760px) {
  .km-cols { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  /* Действия карточки делят строку: их три, и лесенкой они занимают
     треть экрана ни за чем. */
  .km-toolbar--card .km-btn { flex: 1 1 0; }
  .km-toolbar__who { flex: 1 0 100%; }

  /* Поля в один столбец: две колонки по 160 пикселей режут значения
     вроде «26 фев 2026 — 26 фев 2027» пополам. */
  .km-facts { grid-template-columns: 1fr; }
}

/* Подпись только для тех, кто читает не глазами.

   `display: none` для этого не годится — оно прячет и от экранного диктора,
   то есть ровно от тех, ради кого подпись и написана. */
.km-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect( 0 0 0 0 );
  white-space: nowrap;
  border: 0;
}

/* Столбец действий у строки списка: узкий, прижат вправо. */
.km-cell-act { width: 44px; text-align: right; }

/* ДЕЙСТВИЕ ПОЯВЛЯЕТСЯ ПО НАВЕДЕНИЮ.

   Висящее у каждой из двадцати пяти строк, оно зашумляет экран сильнее,
   чем помогает: открывают карточку одного человека из списка, а видят
   значок у всех. Тот же довод, что у ручки перетаскивания в дереве.

   При этом оно **остаётся видимым для клавиатуры**: спрятанное до наведения
   и не показанное при фокусе, оно недостижимо для того, кто ведёт не мышью. */
.km-act,
.km-act:focus {
  display: inline-flex;
  opacity: 0;
  color: var(--km-text-muted);
  padding: 4px;
  border-radius: 6px;
}

tbody tr:hover .km-act,
.km-act:focus-visible { opacity: 1; }

/* Очертание дописано сюда же, а не отдельным правилом: у `.km-act` признак
   фокуса уже был (проявление), и второе определение того же селектора
   развело бы один признак по двум местам. Задача 9.27. */
.km-act:focus-visible { outline: 2px solid var(--km-accent); outline-offset: 2px; }

.km-act:hover { background: var(--km-border-soft); color: var(--km-text); }
.km-act svg { width: 18px; height: 18px; display: block; }

@media (max-width: 640px) {
  /* На узком наведения нет вовсе, и прятать нечего: карточка становится
     недостижимой. Значок стоит рядом с флажком, в правом верхнем углу. */
  .km-act { opacity: 1; }
  .km-table td.km-cell-act { justify-content: flex-end; }
  .km-table td.km-cell-act::before { content: none; }
}

/* ─── Карточка доставки ─────────────────────────────────────────────────────
 *
 * Макет `design/webhooks-v1.html`, принят 22 августа 2026. Заменила строку
 * таблицы на экране журнала вебхуков.
 *
 * Причина растягивала строку на треть страницы и сталкивала подписи
 * столбцов — а причина здесь главное: с ней в журнал и приходят. В карточке
 * она стоит своим блоком, и рядом с ней стоит действие, а не в другом
 * конце экрана.
 *
 * Выполненное приглушено целиком — тем же приёмом и по тому же доводу,
 * что снятое с продажи предложение: видно, но не спорит с тем, что просит
 * внимания.
 *
 * Состояние берёт ОБЩИЙ `.km-state` с точкой, а не заводит свой: точка
 * и четыре её цвета — это про любое состояние на любом экране, и второй
 * набор разошёлся бы с первым на первой же правке палитры.
 */
.km-hook {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  padding: 14px 18px;
  margin-bottom: 10px;
}

.km-hook--done {
  opacity: 0.75;
}

.km-hook__head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

/* Имя события — моноширинным и первым: по нему отличают оплату от возврата
   с одного взгляда, и оно приходит из чужой системы дословно. */
.km-hook__event {
  flex: none;
  font-family: var(--km-mono);
  font-size: 12px;
  color: var(--km-text);
  background: var(--km-page);
  border: 1px solid var(--km-border);
  padding: 2px 8px;
  border-radius: 5px;
}

.km-hook__who {
  flex: 1;
  min-width: 180px;
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.km-hook__meta {
  margin-top: 7px;
  font-size: 13px;
  color: var(--km-text-secondary);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

/* Номер заказа — моноширинным по тому же доводу, что номер предложения:
   его переписывают глазами, и в пропорциональном шрифте единица и семёрка
   в номере из пятнадцати цифр различаются хуже. */
.km-hook__id {
  font-family: var(--km-mono);
  font-size: 12px;
  color: var(--km-text-muted);
}

.km-hook__sep { color: var(--km-text-muted); }

.km-hook__why {
  margin-top: 11px;
  background: var(--km-page);
  border-radius: 10px;
  padding: 11px 14px;
  font-size: 13px;
  color: var(--km-text);
  line-height: 1.55;
}

.km-hook__why b { font-weight: 600; }

.km-hook__act {
  margin-top: 10px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

/* Тело доставки внутри карточки: показывается по требованию, здесь же,
   а не в панели внизу экрана. Моноширинным — это JSON. */
.km-hook__payload {
  margin-top: 10px;
  font-family: var(--km-mono);
  font-size: 12px;
  line-height: 1.5;
  background: var(--km-page);
  border: 1px solid var(--km-border);
  border-radius: 10px;
  padding: 11px 14px;
  max-height: 320px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--km-text);
}

/* Поле «что решили» — внутри карточки и только когда его открыли: на каждой
   карточке подряд оно спрашивало бы то, чего у человека ещё нет. */
.km-hook__note {
  margin-top: 10px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.km-hook__note .km-input { flex: 1; min-width: 220px; }

@media (max-width: 640px) {
  /* На узком строки карточки встают друг под друга: почта длинная,
     и рядом с именем события ей места нет. */
  .km-hook__who { min-width: 100%; white-space: normal; }
  .km-hook__act > .km-btn { flex: 1; }
}

/* ─── Экран правил ──────────────────────────────────────────────────────────
 *
 * Макет `design/rules-v1.html`, принят 22 августа 2026. Решение человека
 * про прежний экран: «бесполезный и страшный».
 *
 * Бесполезный — потому что у правила было видно одно название: ни включено
 * ли, ни когда срабатывает, ни писало ли хоть раз. Страшный — потому что
 * два выключателя стояли одинаково, а делают разное.
 */

/* Два выключателя, и вид у них разный.
 *
 * Отключённый — тёплой карточкой, работающий — обычной. Одинаковые, они
 * читаются как один выключатель, который однажды нажмут не тот; а разница
 * между «остановить правила» и «закрыть отправку» — это разница между
 * «система молчит» и «система считает, но молчит наружу». */
.km-switches {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

@media (max-width: 760px) {
  .km-switches { grid-template-columns: 1fr; }
}

.km-sw {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: 14px;
  padding: 14px 16px;
}

.km-sw--off {
  background: var(--km-warm-bg);
  border-color: var(--km-warm-border);
}

.km-sw__top {
  display: flex;
  align-items: center;
  gap: 9px;
}

.km-sw__name { flex: 1; font-weight: 600; }

.km-sw__why {
  margin-top: 7px;
  font-size: 13px;
  color: var(--km-text-secondary);
  line-height: 1.5;
}

.km-sw__act { margin-top: 11px; }

/* Правило строкой в общей карточке.
 *
 * Строки лежат внутри одной рамки, а не карточками поодиночке: сценариев
 * четырнадцать, и четырнадцать карточек подряд читаются как четырнадцать
 * разных вещей. Здесь же одна вещь — список. */
.km-rules {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  overflow: hidden;
}

.km-rule {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--km-border-soft);
}

.km-rule:last-child { border-bottom: none; }
.km-rule:hover { background: var(--km-page); }

/* Выключенное приглушено целиком — тем же приёмом, что снятое с продажи
   предложение и выполненная доставка: видно, но не спорит с действующим. */
.km-rule--off { opacity: 0.6; }

.km-rule__body { flex: 1; min-width: 0; }
.km-rule__name { font-weight: 600; line-height: 1.3; }

.km-rule__when {
  font-size: 12px;
  color: var(--km-text-muted);
  margin-top: 3px;
}

.km-rule__num {
  flex: none;
  text-align: right;
  font-size: 13px;
  color: var(--km-text-secondary);
  min-width: 190px;
}

/* Главное число экрана: уходило ли что-нибудь людям.
   Зелёным — только когда уходило: на боевом это одно правило
   из четырнадцати, и отличаться оно должно с одного взгляда. */
.km-rule__sent { color: var(--km-ok); font-weight: 600; }
.km-rule__sent--none { color: var(--km-text-muted); font-weight: 400; }

.km-rule__small {
  display: block;
  font-size: 12px;
  color: var(--km-text-muted);
  margin-top: 2px;
  /* ДЛИННОЕ СЛОВО ЛОМАЕТСЯ. Найдено глазами на боевых данных, 375.
   *
   * В строке особого случая стоит текст отказа движка, а в нём — имя тега
   * CRM: `program:mesta-voznesenija:completed`, тридцать пять знаков без
   * единого пробела. Неразрывное слово задаёт минимальную ширину флексовой
   * ячейке, ячейка — строке, строка — странице: на 375 браузер отъезжал
   * до 510 и показывал экран уменьшенным.
   *
   * На фикстуре этого не видно: длинные имена тегов бывают только
   * у настоящих отказов. Ровно то, о чём правило «фикстуры стенда несут
   * самое длинное из правдоподобного». */
  overflow-wrap: anywhere;
}

/* Кнопки по наведению и справа.
 *
 * Стояли сразу за названием и плясали по горизонтали: названия у правил
 * разной длины, и глазу не за что зацепиться. Место в строке они занимают
 * всегда — иначе строка дёргалась бы под курсором. */
.km-rule__acts {
  flex: none;
  display: flex;
  gap: 6px;
  opacity: 0;
}

.km-rule:hover .km-rule__acts,
.km-rule:focus-within .km-rule__acts { opacity: 1; }

@media (max-width: 760px) {
  /* На узком наведения нет вовсе, и прятать нечего: кнопки становятся
     недостижимыми. Строка при этом встаёт столбиком. */
  .km-rule { flex-wrap: wrap; }
  .km-rule__acts { opacity: 1; }
  /* Ячейка чисел занимает строку целиком И УМЕЕТ СЖИМАТЬСЯ.
   *
   * `flex: none` на широком верен — колонка не должна плясать от длины
   * названия, — а на узком он же не давал ей стать уже своего содержимого:
   * `flex-basis: auto` плюс `flex-shrink: 0` дают ширину по самому длинному
   * слову, и `overflow-wrap` внутри тут бессилен. Имя тега CRM из текста
   * отказа растягивало строку до 488 px при экране в 375, и браузер
   * показывал страницу уменьшенной. */
  .km-rule__num { flex: 1 1 100%; min-width: 0; text-align: left; }
}

/* «Показать ещё» вместо номеров страниц: 463 кнопки — не навигация. */
.km-more { margin-top: 12px; text-align: center; }

/* ── Отчёты: три числа, обрывы, свёрнутая воронка ─────────────────────────
 *
 * Вид по макету `design/reports-v1.html`, решение человека 22 августа 2026:
 * прежний экран был «самой бестолковой страницей» — сто двадцать строк
 * одинакового веса. Здесь вес разный: крупное число ответа, крупное число
 * потери, а полная воронка убрана под раскрытие.
 */

.km-nums { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.km-num {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  padding: 16px 18px;
}
.km-num__value { font-size: 28px; font-weight: 700; line-height: 1.1; }
.km-num__key { font-size: 13px; color: var(--km-text-secondary); margin-top: 5px; line-height: 1.4; }
.km-num__sub { font-size: 12px; color: var(--km-text-muted); margin-top: 6px; }

/* Карточка без своих отступов: строки сами держат поля. */
.km-card--flush { overflow: hidden; }

.km-loss { display: flex; align-items: center; gap: 14px; padding: 13px 18px; border-bottom: 1px solid var(--km-border-soft); }
.km-loss:last-child { border-bottom: none; }
.km-loss__count {
  flex: none;
  width: 54px;
  text-align: right;
  font-size: 20px;
  font-weight: 700;
  color: var(--km-danger);
  font-variant-numeric: tabular-nums;
}
.km-loss__body { flex: 1; min-width: 0; }
.km-loss__title { font-weight: 600; line-height: 1.35; overflow-wrap: anywhere; }
.km-loss__where { font-size: 12px; color: var(--km-text-muted); margin-top: 3px; overflow-wrap: anywhere; }

/* «Показать людей» — по наведению, как кнопки правила: пять кнопок,
   висящих постоянно, спорят по весу с числом потери. */
.km-loss__act { flex: none; opacity: 0; transition: opacity .12s ease; }
.km-loss:hover .km-loss__act,
.km-loss:focus-within .km-loss__act { opacity: 1; }

.km-folds { display: flex; flex-direction: column; gap: 10px; }
.km-fold {
  background: var(--km-surface);
  border: 1px solid var(--km-border);
  border-radius: var(--km-radius-card);
  overflow: hidden;
}
.km-fold > summary {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 18px;
  cursor: pointer;
  font-weight: 600;
  list-style: none;
}
.km-fold > summary::-webkit-details-marker { display: none; }
.km-fold > summary::before {
  content: '';
  flex: none;
  width: 7px;
  height: 7px;
  border-right: 1.5px solid var(--km-text-muted);
  border-bottom: 1.5px solid var(--km-text-muted);
  transform: rotate(-45deg);
  transition: transform .15s ease;
}
.km-fold[open] > summary::before { transform: rotate(45deg); }
.km-fold__meta { font-size: 13px; color: var(--km-text-muted); font-weight: 400; margin-left: auto; }

.km-node-row { display: flex; align-items: center; gap: 12px; padding: 9px 18px; border-top: 1px solid var(--km-border-soft); }
.km-node-row__title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.km-node-row__bar { flex: none; width: 120px; }
.km-node-row__value {
  flex: none;
  /* «171 из 263 · 65%» — знаменатель у каждой строки свой, и в двух
     строках он читается хуже, чем в одной. */
  width: 148px;
  text-align: right;
  font-size: 13px;
  color: var(--km-text-secondary);
  font-variant-numeric: tabular-nums;
}
/* Число потери — узкой колонкой; «не с чем сравнить» у первого в цепочке
   не переносится по слогам, а занимает столько, сколько нужно. */
.km-node-row__drop {
  flex: none;
  min-width: 52px;
  text-align: right;
  white-space: nowrap;
  font-size: 13px;
  color: var(--km-text-muted);
  font-variant-numeric: tabular-nums;
}
.km-node-row__drop--bad { color: var(--km-danger); font-weight: 600; }

/* На узком полоса и колонки уезжают: остаются название и числа строкой. */
@media (max-width: 767px) {
  .km-loss { gap: 10px; padding: 12px 14px; flex-wrap: wrap; }
  .km-loss__count { width: 44px; font-size: 18px; }
  /* На узком наведения нет: спрятанная кнопка была бы недостижимой. */
  .km-loss__act { opacity: 1; flex: 1 1 100%; margin-left: 54px; }

  .km-node-row { flex-wrap: wrap; gap: 4px 10px; padding: 10px 14px; }
  .km-node-row__title { flex: 1 1 100%; white-space: normal; overflow: visible; text-overflow: clip; }
  .km-node-row__bar { display: none; }
  .km-node-row__value { width: auto; text-align: left; }
  .km-node-row__drop { width: auto; margin-left: auto; }
}

/* ── Состав бонусов ──────────────────────────────────────────────
 *
 * Задача 9.25. Взято из макета `design/bonuses-v1.html` без правок:
 * он написан классами `km-`, и переписывать их по месту значило бы
 * заводить второе оформление того же.
 *
 * `km-picks` — список программ с галками, `km-pick--on` поднимается
 * наверх, `km-pick--locked` показывает курсовые бонусы у предложения:
 * их отсюда не меняют.
 */
.km-box {
	background:var(--km-surface);
	border:1px solid var(--km-border);
	border-radius:var(--km-radius-card);
	overflow:hidden;
}

.km-box__head {
	padding:13px 18px;
	border-bottom:1px solid var(--km-border);
	font-size:14px;
	font-weight:600;
}

.km-box__body {
	padding:16px 18px 18px;
}

.km-picks {
	border:1px solid var(--km-border);
	border-radius:10px;
	overflow:hidden;
}

.km-picks__scroll {
	max-height:296px;
	overflow-y:auto;
}

.km-pick {
	display:flex;
	align-items:flex-start;
	gap:11px;
	padding:9px 13px;
	border-bottom:1px solid var(--km-border);
	cursor:pointer;
	background:var(--km-surface);
}

.km-pick:last-child {
	border-bottom:none;
}

.km-pick:hover {
	background:var(--km-bg);
}

.km-pick input {
	margin:2px 0 0;
	width:15px;
	height:15px;
	flex:none;
	accent-color:var(--km-accent);
}

.km-pick input:focus-visible {
	outline:2px solid var(--km-accent);
	outline-offset:2px;
}

.km-pick__main {
	flex:1;
	min-width:0;
}

.km-pick__name {
	font-size:13.5px;
	line-height:1.4;
}

.km-pick__meta {
	font-size:11.5px;
	color:var(--km-text-muted);
	margin-top:2px;
}

.km-pick__side {
	flex:none;
	font-size:11.5px;
	color:var(--km-text-muted);
	padding-top:1px;
	text-align:right;
	max-width:170px;
}

.km-pick--on {
	background:var(--km-warm-bg);
}

.km-pick--on:hover {
	background:#FBE9DD;
}

.km-pick--on .km-pick__name {
	font-weight:600;
}

.km-pick--locked {
	cursor:default;
	background:var(--km-bg);
}

.km-pick--locked:hover {
	background:var(--km-bg);
}

.km-pick--locked .km-pick__name {
	color:var(--km-text-secondary);
}

.km-pick--locked input {
	accent-color:var(--km-text-muted);
}

.km-picks__split {
	padding:6px 13px;
	background:var(--km-bg);
	font-size:11px;
	letter-spacing:.04em;
	text-transform:uppercase;
	color:var(--km-text-muted);
	border-bottom:1px solid var(--km-border);
}

.km-tag--from-course {
	background:var(--km-warm-bg);
	color:var(--km-warm-text);
}

.km-offer__bonus {
	margin-top:9px;
	padding-top:9px;
	border-top:1px solid var(--km-border);
	font-size:13px;
	line-height:1.6;
	display:flex;
	gap:10px;
	align-items:baseline;
	flex-wrap:wrap;
}

.km-offer__bonus-label {
	color:var(--km-text-secondary);
	flex:none;
}

.km-offer__bonus-list {
	flex:1;
	min-width:180px;
}

.km-offer__bonus-list--empty {
	color:var(--km-text-muted);
}.km-link,
.km-link:focus{
	border:0;
	background:none;
	padding:0;
	font:inherit;
	font-size:12.5px;
	color:var(--km-accent);
	cursor:pointer;
	text-decoration:underline;
	text-underline-offset:2px;
	flex:none;
}

.km-link:focus-visible {
	outline:2px solid var(--km-accent);
	outline-offset:2px;
	border-radius:3px;
}

.km-open {
	margin-top:12px;
	padding-top:14px;
	border-top:1px dashed var(--km-border);
}

/*
 * Блок «Что показывать на странице курса». Задача 9.26,
 * макет `design/course-settings-v1.html`.
 *
 * Строка настройки — это название, объяснение и то, чем настройку задают:
 * галка, поле с эхом или строка состояния у того, чем человек не управляет.
 *
 * Из макета вычтено то, что дизайн-система уже рисует: подписи идут
 * `km-hint`, галка — `km-check` с `km-check__box`, коробка — `km-box`.
 * Своё у макета осталось только то, чего в системе не было: сама строка,
 * эхо и строка состояния.
 *
 * **`km-set__row`, а не `km-field`**, и это не придирка к имени: `km-field`
 * в системе — колонка (`flex-direction: column`), а здесь нужна строка.
 * Взятое по созвучию, оно поставило бы эхо под поле на всех экранах, где
 * его потом употребят, — и каждый из них выглядел бы правильным по
 * отдельности.
 */
.km-set {
	padding:15px 18px;
	border-bottom:1px solid var(--km-border);
}

.km-set:last-child {
	border-bottom:none;
}

.km-set__top {
	display:flex;
	align-items:baseline;
	gap:10px;
}

.km-set__name {
	flex:1;
	font-size:13.5px;
	font-weight:600;
}

.km-set__row {
	margin-top:9px;
	display:flex;
	align-items:center;
	gap:10px;
	flex-wrap:wrap;
}

/*
 * `width:auto` обязателен: у `.km-input` в системе стоит `width:100%`,
 * и без снятия поле занимает строку целиком, а эхо уезжает под него.
 * На узком это правильно и делается переносом (`flex-wrap`), на широком —
 * нет: эхо для того и рядом, чтобы читаться вместе с тем, что набрано.
 */
.km-set__row .km-input {
	width:auto;
	min-width:210px;
	flex:1;
	font-size:13px;
	padding:6px 10px;
}

.km-set__row--num .km-input {
	width:auto;
	min-width:150px;
	max-width:220px;
	flex:none;
	font-family:var(--km-mono);
}

/*
 * Эхо. `flex:none` намеренно: оно съезжает на свою строку целиком, а не
 * рвётся посередине — в нём бывают названия форм без пробелов.
 */
.km-echo {
	font-size:12px;
	line-height:1.5;
	flex:none;
}

.km-echo--ok {
	color:var(--km-ok);
}

.km-echo--warn {
	color:var(--km-warn);
}

.km-echo--muted {
	color:var(--km-text-muted);
}

.km-found {
	margin-top:8px;
	font-size:12px;
	color:var(--km-text-secondary);
	line-height:1.7;
}

.km-found__list {
	margin:3px 0 0;
	padding:0;
	list-style:none;
}

/*
 * Названия форм приходят машинными: слаги, скобки, номера. Длинное слово
 * без пробелов задаёт минимальную ширину строке, строка — блоку, блок —
 * экрану; на 375 это уводит страницу за край целиком. Ломаем по буквам.
 */
.km-found__list li {
	color:var(--km-text-muted);
	overflow-wrap:anywhere;
}

.km-found__more {
	color:var(--km-text-muted);
}

/*
 * Строка состояния — там, где решает не человек. Показывает и что решено,
 * и почему: выключенное без причины читается как поломка ровно так же,
 * как пустота.
 */
.km-auto {
	font-size:12px;
	color:var(--km-text-muted);
	background:var(--km-page);
	border-radius:7px;
	padding:7px 11px;
	margin-top:8px;
	line-height:1.55;
}

.km-auto b {
	color:var(--km-text-secondary);
	font-weight:600;
}

.km-auto--on b {
	color:var(--km-ok);
}

/*
 * Кусок машинного текста внутри строки: образец названия формы, ключ, слаг.
 *
 * Заведён в 9.26 — до него моноширинный шрифт ставился шестью частными
 * классами (`km-hook__id`, `km-placeholder` и другими), и общего не было
 * ни одного. Ставится на `<code>`, у которого браузер и так меняет шрифт:
 * класс задаёт кегль и цвет, чтобы образец не выпирал из подписи.
 */
.km-mono {
	font-family:var(--km-mono);
	font-size:.92em;
	color:var(--km-text-secondary);
}

/*
 * Обёртка чужой разметки отзывов. Задача 9.28.
 *
 * Оформления здесь нет ни строчки и быть не должно: внутри — вывод
 * представления GravityView, его вид задаёт человек в чужом экране,
 * и красить его отсюда значило бы завести второе место, где живёт
 * этот вид.
 *
 * Своя у обёртки одна работа — НЕ ДАТЬ ЧУЖОМУ УТАЩИТЬ СТРАНИЦУ ВБОК.
 * Представление рисует список с таблицами и длинными ссылками, ширину
 * которых мы не выбираем; без своей прокрутки первая же широкая строка
 * тянет за собой всю страницу. Этот дефект в проекте уже был дважды —
 * у административных таблиц и у карточки воронки, — и оба раза его
 * находил не тест, а глаз на узком экране.
 */
.km-reviews {
	overflow-x: auto;
	max-width: 100%;
}

/* ─────────────────────────────────────────────────────────────────────────
   ФАЙЛЫ КУРСА. Задача 10.1, макет `design/course-files-concept-v1.html`.

   ИМЯ У СТРОКИ СВОЁ, И ЭТО НЕ ПРИДИРКА. Макет называл строку файла
   `km-row`, а `.km-row` в этой таблице определён ДВАЖДЫ и в двух разных
   смыслах: строка дерева (`padding`, `border-bottom`, `align-items:center`)
   и строка контролов шапки (`flex-wrap`, `align-items:flex-end`). Они
   не спорят, а СКЛАДЫВАЮТСЯ — третий смысл сделал бы помесь ещё гуще.

   `.km-file` тоже занят: это строка файла в блоке вложения на странице
   занятия, со своим `__glyph`/`__body`/`__action`. Переименовывается
   меньшинство, а у нового имени употреблений ноль — поэтому своё имя
   берёт новый, а не старый.
   ───────────────────────────────────────────────────────────────────── */

:root {
	/* Цвет значка по виду файла. Три — те, что на боевом: 279 pdf, 177 mp3,
	   28 jpg плюс 11 png. Остальным общий серый: выдумывать цвет виду,
	   которого почти нет, — значит заводить словарь, за которым не следят. */
	--km-kind-doc: #C0392B;
	--km-kind-audio: #6B4FA8;
	--km-kind-image: #2E7D5B;
	--km-kind-other: #7C8A9B;
}

.km-lib__head {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 12px;
	margin-bottom: 14px;
}

.km-lib__search { flex: 1 1 220px; }

/* Сводка прижимается вправо, к кнопке: она справка, а не заголовок. */
.km-lib__head > .km-meta { margin-left: auto; }

.km-lib__form { padding: 16px 18px; margin-bottom: 14px; }

.km-lib { padding: 4px 18px; }

.km-lib__row {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 11px 0;
	border-bottom: 1px solid var(--km-border);
}

.km-lib__row:last-child { border-bottom: none; }

.km-lib__kind {
	flex: none;
	width: 34px;
	height: 34px;
	border-radius: 9px;
	display: flex;
	align-items: center;
	justify-content: center;
	font-size: 9.5px;
	font-weight: 700;
	color: #FFFFFF;
	background: var(--km-kind-other);
}

.km-lib__kind--doc { background: var(--km-kind-doc); }
.km-lib__kind--audio { background: var(--km-kind-audio); }
.km-lib__kind--image { background: var(--km-kind-image); }

.km-lib__main { flex: 1; min-width: 0; }

.km-lib__name {
	display: block;
	font-size: 14px;
	line-height: 1.4;
	color: var(--km-text);
}

.km-lib__meta {
	display: block;
	font-size: 11.5px;
	color: var(--km-text-muted);
	margin-top: 2px;
}

/*
 * «ГДЕ СТОИТ» — СПРАВА И СЛОВАМИ.
 *
 * Ширина ограничена, чтобы длинная фраза не отжимала название: строк сотня,
 * и глаз идёт по левому краю.
 */
.km-lib__where {
	flex: none;
	max-width: 230px;
	text-align: right;
	font-size: 12px;
	color: var(--km-text-secondary);
}

/*
 * «Нигде не стоит» — тёплым, а не серым: это состояние, на которое смотрят,
 * а не подпись, которую пропускают. Приглушённое, оно потерялось бы ровно
 * там, где заведено, — в списке из сотни строк.
 */
.km-lib__where--none { color: var(--km-warm-text-strong); }

.km-lib__act {
	flex: none;
	display: flex;
	gap: 10px;
}

/*
 * Адрес — моноширинным. Ссылка на Диск это набор знаков без слов, и в ней
 * ищут глазами опечатку: пропорциональный шрифт склеивает `l`, `1` и `I`.
 */
.km-input--mono {
	font-family: var(--km-mono);
	font-size: 12.5px;
}

/*
 * НА УЗКОМ «ГДЕ СТОИТ» УХОДИТ ПОД НАЗВАНИЕ ОТДЕЛЬНОЙ СТРОКОЙ.
 *
 * `flex-basis: 100%` здесь и есть перенос: без него `flex-wrap` у строки
 * укладывает колонку рядом с именем, и обещание «уходит под название»
 * остаётся написанным в комментарии и невыполненным. Найдено глазами
 * на 375: правило применялось целиком — `text-align` и отступ стояли, —
 * а колонка была справа. Мерка при этом молчала: за экран ничего
 * не вылезало, и вылезать было нечему.
 *
 * Отступ 46px равен значку плюс промежуток: строка выравнивается по имени,
 * а не по левому краю карточки.
 */
@media (max-width: 560px) {
	.km-lib__row { flex-wrap: wrap; row-gap: 6px; }

	.km-lib__where {
		flex-basis: 100%;
		max-width: none;
		text-align: left;
		margin-left: 46px;
	}
}

/* ---------------------------------------------------------------------------
 * Сценарии. Спецификация 9.11, макет `design/scenarios-prototype.html`.
 * ------------------------------------------------------------------------- */

/*
 * Шапка экрана: заголовок слева, действия справа.
 *
 * Заведена своим именем, а не собрана из `km-row`: у того ДВА определения
 * в этом же файле — списковая строка с рамкой и подложкой при наведении
 * и строка полей формы, — и они складываются, а не спорят. Довешивать
 * к ним третий смысл значило бы делать хуже то, что уже надо чинить.
 */
.km-head {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
	margin-bottom: 12px;
}

.km-head__acts {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
}

/* Раздел редактора: «Когда срабатывает», «Кому», «Что делаем».
   Кружок с номером отсюда убран 7 сентября 2026: порядок читается
   сверху вниз и без него, а заголовки разделов стали надзаголовками
   (`.km-section-title`) — на странице один H1, как в макете. */
.km-block {
	background: var(--km-surface);
	border: 1px solid var(--km-border);
	border-radius: 14px;
	padding: 16px 18px;
	margin-bottom: 12px;
}


/*
 * Предпросмотр письма: показывает то, что увидит человек.
 *
 * `pre-wrap` — потому что письмо набирают переносами строк, а уходит
 * оно абзацами (`Letter::markup()`). Без него предпросмотр слепляет
 * письмо в один поток и врёт про вид ровно там, где его и смотрят.
 *
 * `anywhere` — потому что в письме бывает адрес курса, а он едет одним
 * словом без пробелов. Неразрывное слово задаёт минимальную ширину
 * блоку, блок — странице: на 375 экран уезжал за край. Найдено глазами
 * на стенде 7 сентября 2026, фикстурой с настоящим адресом.
 */
.km-letter-preview {
	/*
	 * `normal`, а не `pre-wrap`. С 8 сентября 2026 предпросмотр приходит
	 * РАЗМЕТКОЙ — той же, что уедет в письмо, — и переносы в ней ставит
	 * `wpautop` для читаемости исходника, а не для показа. При `pre-wrap`
	 * каждый такой перенос стал бы пустой строкой на экране, и письмо
	 * выглядело бы разреженным вдвое против настоящего.
	 */
	white-space: normal;
	overflow-wrap: anywhere;
}

/* Цепочка шагов: карточки друг под другом. */
/* ---------------------------------------------------------------------------
   Редактор сценария (9.11). Макет `design/scenario-editor-v1.html`
   ---------------------------------------------------------------------------
   Правка 8 сентября 2026 по замечаниям человека к первой сборке экрана.
   Три вещи, которые здесь выражены, — не украшение:

   - три раздела это ОДИН документ, а не три экрана: одна карточка
     с разделительными линиями, читается сверху вниз;
   - подстановки стоят СПРАВА ОТ ПОЛЯ и с примером значения. Без примера
     видно имя поля и не видно, что подставится, — а «половина пользы»
     это и есть та половина;
   - у шага есть шапка: вид, тема строкой и ручка. Голые поля не говорят,
     что это письмо и какое из двух.

   Свёртка шагов отложена (решение человека): `.km-step` всегда раскрыт.
   Метка, рейка и ручка от свёртки не зависят и сделаны сейчас.
   --------------------------------------------------------------------------- */

/* Две колонки: редактор и подстановки. Правая липнет к верху окна,
   чтобы стоять рядом с тем, что печатают. */
.km-scn {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 300px;
	gap: 20px;
	align-items: start;
}

.km-scn-side {
	position: sticky;
	/* Под липкой обвязкой темы: сколько её сверху, замеряет `chrome.js`. */
	top: calc( var(--km-chrome-top, 0px) + 20px );
}

/* Шапка: крошки, «назад», заголовок, состояние и действия. */
.km-scn-head { margin: 12px 0 22px; }

.km-scn-head__row {
	display: flex;
	align-items: center;
	gap: 14px;
	flex-wrap: wrap;
	margin-top: 6px;
}

.km-scn-head__row .km-h1 { margin-right: auto; }

/* Плашка состояния: слово рядом с переключателем. Переключатель говорит,
   что СДЕЛАТЬ, плашка — что ЕСТЬ, и читать их надо вместе. */
.km-scn-state {
	font-size: 12.5px;
	font-weight: 700;
	padding: 5px 11px;
	border-radius: var(--km-radius-badge);
	background: var(--km-warm-bg);
	color: var(--km-warm-text-strong);
	border: 1px solid var(--km-warm-border);
}

.km-scn-state--on {
	background: #E9F4F2;
	color: #2E7D5B;
	border-color: #CBE5DE;
}

/* Раздел внутри общей карточки. Линия сверху — только у не первого. */
.km-scn-block { padding: 22px 24px 24px; }
.km-scn-block + .km-scn-block { border-top: 1px solid var(--km-border-soft); }

.km-scn-block__t {
	display: flex;
	align-items: baseline;
	gap: 12px;
	margin-bottom: 14px;
}

.km-scn-block__d {
	font-size: 13px;
	color: var(--km-text-secondary);
	margin-left: auto;
}

/* Цепочка: рейка слева с линией, шаги справа. */
.km-chain {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 0;
}

.km-chain::before {
	content: "";
	position: absolute;
	left: 17px;
	top: 18px;
	bottom: 18px;
	width: 2px;
	background: var(--km-border);
}

.km-chain > * {
	position: relative;
	display: grid;
	grid-template-columns: 36px minmax(0, 1fr);
	gap: 14px;
	align-items: start;
}

.km-chain__glyph {
	width: 36px;
	height: 36px;
	border-radius: 10px;
	background: var(--km-warm-bg);
	color: var(--km-accent);
	display: flex;
	align-items: center;
	justify-content: center;
	position: relative;
	z-index: 1;
}

.km-chain__glyph--wait {
	width: 24px;
	height: 24px;
	border-radius: 50%;
	margin: 6px;
	background: var(--km-surface);
	color: var(--km-text-muted);
	border: 1.5px solid var(--km-border);
}

/* Шаг: карточка внутри карточки, на полтона темнее. */
.km-step {
	background: var(--km-surface-alt);
	border: 1px solid var(--km-border);
	border-radius: var(--km-radius-card);
}

.km-step__head {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 12px 14px 12px 16px;
	min-height: 50px;
	border-bottom: 1px solid var(--km-border-soft);
	margin-bottom: 12px;
}

.km-step__kind {
	font-size: 11px;
	font-weight: 700;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--km-text-muted);
	flex: none;
}

/* Тема строкой: она и есть имя письма, и по ней их различают. */
.km-step__title {
	font-weight: 600;
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.km-step__title--empty {
	color: var(--km-text-muted);
	font-weight: 400;
}

.km-step__acts {
	display: flex;
	gap: 4px;
	flex: none;
	margin-left: 6px;
}

.km-step__body {
	display: flex;
	flex-direction: column;
	gap: 14px;
	padding: 2px 16px 16px;
}

.km-step .km-textarea,
.km-step textarea.km-input {
	min-height: 260px;
	font-size: 14.5px;
	line-height: 1.6;
}

/* Своих полей нет: расстояние задаёт колонка, в которой стоит абзац.
   Так требует общая мерка стендов, и она права — два источника отступа
   однажды сложатся, и никто не поймёт, какой из них править. */
.km-step__foot {
	font-size: 12.5px;
	color: var(--km-text-muted);
	line-height: 1.5;
	margin: 0;
}

/* Кнопка-значок: убрать шаг. Своё имя, а не `.km-btn`: у той заливка,
   отступы и кегль текста, а здесь квадрат под значок. */
.km-ico {
	width: 30px;
	height: 30px;
	border-radius: 8px;
	border: 1px solid transparent;
	background: transparent;
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: var(--km-text-muted);
	cursor: pointer;
}

.km-ico:hover {
	color: var(--km-accent);
	border-color: var(--km-warm-border);
	background: var(--km-surface);
}

.km-ico--danger:hover {
	color: var(--km-danger);
	border-color: #F0BDB0;
}

/* Ожидание — строка на рейке, а не карточка: оно между письмами,
   а не рядом с ними. Это про данные: шаг ожидания ничего не делает,
   он задаёт срок до следующего. */
.km-wait { padding: 8px 0; }

.km-wait__body {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	min-height: 36px;
	font-size: 14.5px;
	color: var(--km-text-secondary);
}

.km-wait .km-input {
	width: 66px;
	text-align: center;
	padding: 7px 8px;
	font-weight: 600;
	font-variant-numeric: tabular-nums;
}

/* Число и единица набраны ОДИНАКОВО, и вес написан у обоих явно.

   Унаследованный вес разъезжается: у поля он свой, у списка — тот,
   что достался от родителя или от чужой таблицы, и на экране «3»
   и «дней» оказываются набраны по-разному. Написанный у обоих,
   он не зависит ни от того, ни от другого. */
.km-wait .km-select {
	width: auto;
	padding: 7px 34px 7px 12px;
	background-position: right 11px center;
	font-size: 14px;
	font-weight: 600;
}

.km-wait__acts {
	margin-left: auto;
	display: flex;
	gap: 4px;
}

/* Добавить шаг — последняя позиция на рейке. */
.km-chain__add { padding-top: 10px; }

.km-chain__add .km-chain__glyph {
	background: transparent;
	color: var(--km-text-muted);
	border: 1.5px dashed var(--km-border);
}

.km-chain__add-body {
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
	align-items: center;
	min-height: 36px;
}

.km-chain__add-note {
	font-size: 12.5px;
	color: var(--km-text-muted);
	margin-top: 12px;
	padding-left: 50px;
	line-height: 1.5;
}

/* Подстановки: слева поле, которое вставляется кликом, справа пример
   на живом человеке. Пример — половина пользы: без него видно имя поля
   и не видно, что подставится. */
.km-vars { padding: 18px 18px 16px; }

.km-vars__hint {
	font-size: 12.5px;
	color: var(--km-text-muted);
	line-height: 1.5;
	margin: 4px 0 12px;
}

.km-var {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: 10px;
	align-items: center;
	padding: 7px 0;
	border-top: 1px solid var(--km-border-soft);
}

.km-var:first-of-type { border-top: 0; }

.km-var__key {
	font-family: var(--km-mono);
	font-size: 12px;
	color: var(--km-warm-text-strong);
	background: var(--km-warm-bg);
	border: 1px solid var(--km-warm-border);
	border-radius: 6px;
	padding: 3px 7px;
	cursor: pointer;
	white-space: nowrap;
}

.km-var__key:hover { background: var(--km-peach); }

.km-var__ex {
	font-size: 13px;
	color: var(--km-text-secondary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	text-align: right;
}

.km-var__ex--none { color: var(--km-text-muted); }

.km-vars__foot {
	font-size: 12px;
	color: var(--km-text-muted);
	line-height: 1.5;
	margin-top: 12px;
	padding-top: 12px;
	border-top: 1px solid var(--km-border-soft);
}

/* ---------------------------------------------------------------------------
   Поле письма с панелью. Задача от 8 сентября 2026
   ---------------------------------------------------------------------------
   Имя своё, `km-write`: `km-toolbar` в системе уже есть и означает шапку
   административного экрана — шесть определений и четыре экрана. Второе имя
   на тот же класс дало бы помесь, которой никто не задумывал; случай
   с `.km-hint` в проекте уже был и прожил девять экранов.

   Поле рисуется тем же, чем `.km-textarea`: подложка, рамка, скругление,
   кегль. Одно и то же значение в двух местах разошлось бы на первой правке,
   поэтому оба стоят в одном правиле выше, а здесь только то, чего
   у многострочного поля нет. */
.km-write { display: flex; flex-direction: column; }

/* Панель прижата к полю: между ними ни пикселя, скругление одно на два
   блока — панель и поле читаются как один предмет, а не как кнопки
   над чем-то посторонним. */
.km-write__bar {
	display: flex;
	align-items: center;
	gap: 4px;
	flex-wrap: wrap;
	padding: 6px 8px;
	background: var(--km-bg);
	border: 1px solid var(--km-border);
	border-bottom: none;
	border-radius: var(--km-radius-control) var(--km-radius-control) 0 0;
}

.km-write__sep {
	width: 1px;
	align-self: stretch;
	margin: 2px 4px;
	background: var(--km-border);
}

.km-write__btn {
	font-family: inherit;
	font-size: 13px;
	line-height: 1;
	color: var(--km-text-secondary);
	background: transparent;
	border: 1px solid transparent;
	border-radius: var(--km-radius-control);
	padding: 7px 9px;
	min-width: 30px;
	cursor: pointer;
	transition: background 0.16s, color 0.16s, border-color 0.16s;
}

.km-write__btn:hover { background: var(--km-surface); color: var(--km-text); }

/* НАЖАТОЕ ОТЛИЧАЕТСЯ ПОДЛОЖКОЙ И РАМКОЙ, а не одним цветом букв: признак
   существует не тогда, когда поставлен, а когда его видно рядом
   с ненажатым. */
.km-write__btn[aria-pressed="true"] {
	background: var(--km-surface);
	border-color: var(--km-warm-border);
	color: var(--km-accent);
}

.km-write__btn--b { font-weight: 700; }
.km-write__btn--i { font-style: italic; }

/* Само поле. Скругление снизу, сверху прямое — стык с панелью. */
.km-write__area {
	min-height: 220px;
	border-radius: 0 0 var(--km-radius-control) var(--km-radius-control);
	overflow-wrap: break-word;
}

.km-write__area:focus { outline: none; }

/* Разметка внутри поля показывается так же, как приедет в письме:
   человек правит то, что увидит курсант. Отступ отношением соседей,
   а не полем у каждого. */
/* Одно написание на два места: поле правки и предпросмотр. Разойдись они —
   человек правил бы одно, а видел бы в предпросмотре другое, и заметил бы
   это только по пришедшему письму. */
.km-write__area > *,
.km-letter-preview > * { margin: 0; }
.km-write__area > * + *,
.km-letter-preview > * + * { margin-top: 12px; }
.km-write__area h3,
.km-letter-preview h3 { font-size: 17px; font-weight: 700; line-height: 1.3; }
.km-write__area ul,
.km-write__area ol,
.km-letter-preview ul,
.km-letter-preview ol { padding-left: 22px; }
.km-write__area li + li,
.km-letter-preview li + li { margin-top: 4px; }
.km-write__area a,
.km-letter-preview a { color: var(--km-accent); }

/* Пустое поле говорит, чего от него ждут. `:empty` для правки не годится:
   редактор кладёт внутрь пустой абзац, и `:empty` перестаёт совпадать. */
.km-write__area[data-empty="да"]::before {
	content: attr(data-placeholder);
	color: var(--km-text-muted);
	pointer-events: none;
}

/* Строка адреса разворачивается под панелью, а не всплывает над текстом:
   всплывающее перекрывает то самое место, куда смотрит человек. */
.km-write__link {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 8px;
	background: var(--km-bg);
	border: 1px solid var(--km-border);
	border-bottom: none;
}

.km-write__link .km-input { padding: 8px 10px; font-size: 14px; }

/* ---------------------------------------------------------------------------
   Список сценариев. Макет `design/scenarios-list-v1.html`
   ---------------------------------------------------------------------------
   Столбцы описаны ОДИН раз и общие у шапки и строк. Разъезжались они
   ровно потому, что ширины стояли в двух местах: у `<th>` своё, у ячейки
   своё, и первое же длинное значение их расходило.
   --------------------------------------------------------------------------- */

.km-scn-list__head {
	display: flex;
	align-items: center;
	gap: 14px;
	flex-wrap: wrap;
	margin: 12px 0 8px;
}

.km-scn-list__head .km-h1 { font-size: 28px; }
.km-scn-list__head .km-input { width: auto; min-width: 220px; }
.km-scn-list__spacer { flex: 1 1 auto; }

.km-scn-lead {
	font-size: 14px;
	color: var(--km-text-secondary);
	max-width: 70ch;
	margin: 0 0 20px;
	line-height: 1.6;
}

/* Столбцы: ширины одни на шапку и строки. */
.km-scn-col--name { flex: 1 1 300px; min-width: 0; }
.km-scn-col--when { flex: 0 1 230px; min-width: 0; }
.km-scn-col--who { flex: 0 0 128px; }
.km-scn-col--steps { flex: 0 0 64px; text-align: right; }
.km-scn-col--fired { flex: 0 0 78px; text-align: right; }
.km-scn-col--state { flex: 0 0 104px; text-align: right; }

.km-scn-row { cursor: pointer; }
.km-scn-row:hover { background: var(--km-surface-alt); }
.km-scn-row:hover .km-scn-row__name { color: var(--km-accent); }

.km-scn-row__name {
	display: block;
	font-weight: 600;
	font-size: 14.5px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* Безымянный сценарий — законное состояние черновика, и он назван
   словами, а не пустотой: пустое место человек читает как поломку. */
.km-scn-row__name--unnamed {
	color: var(--km-text-muted);
	font-weight: 400;
	font-style: italic;
}

.km-scn-row__sub {
	display: block;
	font-size: 12.5px;
	color: var(--km-text-muted);
	margin-top: 2px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.km-scn-row__when,
.km-scn-row__who {
	font-size: 13.5px;
	color: var(--km-text-secondary);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.km-scn-row__num {
	font-family: var(--km-font-display);
	font-size: 16px;
	font-variant-numeric: tabular-nums;
}

.km-scn-row__num--zero { color: var(--km-text-muted); }

/* Выключенное состояние плашки — третий вид рядом с «черновик»
   и «включён». Все три различаются словом, а не только цветом. */
.km-scn-state--off {
	background: var(--km-track);
	color: var(--km-text-muted);
	border-color: var(--km-border);
}

/* Заглушка отправки — полоса НАД списком, а не сноска внизу.
   Пока она стоит, «Включён» в таблице значит «поставит задачу»,
   а не «пошлёт письмо», и знать это надо до чтения списка. */
.km-scn-warn {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	background: var(--km-warm-bg);
	border: 1px solid var(--km-warm-border);
	border-radius: var(--km-radius-control);
	padding: 11px 14px;
	font-size: 13.5px;
	line-height: 1.55;
	color: var(--km-warm-text);
	margin-bottom: 14px;
}

.km-scn-warn b { color: var(--km-warm-text-strong); font-weight: 700; }
.km-scn-warn svg { flex: none; margin-top: 2px; color: var(--km-warm-text-strong); }

.km-scn-foot {
	display: flex;
	align-items: center;
	gap: 14px;
	flex-wrap: wrap;
	margin-top: 14px;
	font-size: 13px;
	color: var(--km-text-muted);
}

.km-scn-foot a { font-weight: 600; }

/* Узкий экран: строка перестаёт быть таблицей и становится карточкой. */
@media ( max-width: 820px ) {
	.km-scn-list__head .km-h1 { font-size: 24px; flex-basis: 100%; }

	.km-scn-col--name { flex: 1 1 100%; }
	.km-scn-col--when { flex: 1 1 100%; order: 3; }
	.km-scn-col--who { flex: 0 0 auto; order: 4; }
	.km-scn-col--steps,
	.km-scn-col--fired { flex: 0 0 auto; order: 5; text-align: left; }
	.km-scn-col--state { flex: 0 0 auto; order: 2; margin-left: auto; text-align: right; }

	/*
	 * На карточке у числа появляется слово: столбец без подписи
	 * над ним — это просто число, и «1» рядом с «0» не различить.
	 */
	.km-scn-col--steps .km-scn-row__num::after,
	.km-scn-col--fired .km-scn-row__num::after {
		font-family: var(--km-font);
		font-size: 12.5px;
		color: var(--km-text-muted);
		font-weight: 400;
	}

	.km-scn-col--steps .km-scn-row__num::after { content: " шага"; }
	.km-scn-col--fired .km-scn-row__num::after { content: " раз"; }
}

/* Узкий экран: одна колонка, подстановки под редактором. */
@media ( max-width: 860px ) {
	.km-scn { grid-template-columns: 1fr; }
	.km-scn-side { position: static; }
	.km-scn-block { padding: 18px 16px 20px; }
	.km-chain__add-note { padding-left: 0; }
}

/* Пары «что подставится»: имя поля и значение. */
/*
 * Своих полей у списка нет: расстояние задаёт колонка, в которой он стоит.
 * Так требует общая мерка стендов — «у абзацев в колонках нет своих полей»,
 * и она права: два источника отступа однажды сложатся, и никто не поймёт,
 * какой из них править.
 */
.km-kv {
	display: grid;
	grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
	gap: 4px 12px;
	margin: 0;
}

.km-kv dt { color: var(--km-text-secondary); }
.km-kv dd { margin: 0; overflow-wrap: anywhere; }

@media (max-width: 600px) {
	.km-kv { grid-template-columns: minmax(0, 1fr); gap: 2px; }
	.km-kv dd { margin-bottom: 6px; }
}
