/* ============================================ ШРИФТ ИНТЕРФЕЙСА
 *
 * Manrope, положен файлами к нам (`static/vendor/fonts/`), а не взят
 * с Google Fonts. По тому же правилу, что и значки: сторонний адрес —
 * это чужая доступность и чужая аналитика на КАЖДОЙ странице кабинета.
 * Плюс шрифт с чужого домена не отрисуется там, где к нему нет доступа,
 * а кабинет должен работать.
 *
 * ## Почему не Plus Jakarta Sans, который выбрал владелец
 *
 * В НЁМ НЕТ КИРИЛЛИЦЫ. Google отдаёт для него `latin`, `latin-ext`,
 * `vietnamese` и `cyrillic-ext`; последнее — исторические и малые
 * письменности, букв А-Я и а-я там нет вовсе.
 *
 * Поставь мы его — русский текст рисовался бы запасным системным
 * шрифтом, а латинские куски («Basic Skills», «Mother board», «CPU»)
 * этим. Разнобой в одной строке, хуже чем было.
 *
 * Manrope взят как ближайший по характеру: та же геометрия, тот же
 * высокий рост строчных, из-за которых образец и понравился, — но
 * кириллица у него родная, а не дорисованная.
 *
 * ## Почему переменного начертания и три файла
 *
 * Одно переменное начертание вместо шести обычных: 400-800 задаются
 * одним файлом, и вес 500 у пунктов меню не стоит отдельной загрузки.
 *
 * Подмножества разнесены по `unicode-range`, и это не мелочь: браузер
 * скачает кириллицу и не тронет латиницу, пока на странице не появится
 * латинская буква. Всё вместе — 54 КБ.
 *
 * `font-display: swap` намеренно: текст виден сразу запасным шрифтом
 * и перерисовывается, когда доедет свой. Невидимый текст на медленной
 * сети хуже некрасивого.
 *
 * Лицензия SIL OFL 1.1 — см. `static/vendor/fonts/README.md`.
 */
@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("vendor/fonts/manrope-cyrillic.e58febde317b.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("vendor/fonts/manrope-latin-ext.6d36d5bdedfa.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("vendor/fonts/manrope-latin.938c6e8019b6.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Свой шрифт первым, дальше — системные. Запасные не декорация:
   пока файл едет (или если он не доехал), текст обязан читаться. */
:root {
  --tblr-font-sans-serif: "Manrope", -apple-system, BlinkMacSystemFont,
    "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

body {
  font-family: var(--tblr-font-sans-serif);
}

/* Фирменный слой поверх Tabler.
 *
 * Здесь только переменные и несколько своих правил. Tabler не
 * переписывается и не форкается: он обновляется, а форк пришлось бы
 * переносить руками при каждом обновлении.
 *
 * ## Откуда взяты цвета
 *
 * Со study.it-ontime.ru, а не придуманы: тёмная база #0A0F17, синий
 * #0088FF, лайм #C1F742, серый текст #C4C6CF, светлая поверхность
 * #F5F5F8. Кабинет, оформленный иначе, чем сайт, читается учеником
 * как чужой продукт — он приходит по ссылке с того же сайта.
 *
 * ## Тёмная тема основная
 *
 * Потому что сайт школы тёмный. Светлая — выбором, а не наоборот.
 *
 * ## Шрифт не меняется
 *
 * Решение владельца от 11.08: платформой будут пользоваться разные
 * школы, и характерный шрифт одной из них — чужая одежда на всех
 * остальных. Остаётся системный набор Tabler.
 */

/* ------------------------------------------------- размер и воздух */

/* База Tabler — 14px с межстрочным 1.43. Это плотность админ-панели:
 * там смотрят таблицы, а не читают. У нас читают часами, и мелко здесь
 * не ощущение, а факт.
 *
 * Размер задаётся КОРНЮ, а не только тексту. Отступы у Tabler заданы
 * в `rem`, поэтому вместе с буквами растёт и воздух между блоками —
 * иначе крупный текст просто слипается в тех же тесных карточках.
 *
 * Единицы `rem`, а не `px`: на корневом элементе `rem` означает размер,
 * выбранный человеком в настройках браузера. Пиксели обнулили бы этот
 * выбор — а его делают именно те, кому мелко.
 *
 * `clamp` даёт плавный рост от узкого экрана к широкому: на телефоне
 * лишний размер съедает строку, на большом мониторе его не хватает.
 */
/* Селектор `:root`, а не `html`: у Tabler стоит `:root{font-size:16px}`,
   и он весомее по специфичности — правило через `html` не применялось бы
   вовсе, сколько бы файл ни грузился позже. Проверено в браузере:
   размер оставался 16px на любом экране. */
:root {
  font-size: calc(clamp(0.875rem, 0.84rem + 0.15vw, 0.99rem) * var(--ui-scale, 1));
}

/* Масштаб интерфейса — выбор человека. Множитель, а не отдельный
   размер: он умножается на плавную базу выше и потому работает
   одинаково на телефоне и на мониторе.
 *
 * Шаг 6%, а не 12%. Первый набор был вдвое крупнее нужного: владелец
 * посмотрел и сказал, что «плотно» у него читается как «обычно»,
 * а две верхние ступени — «вообще мимо». База опущена на ту же
 * величину, так что нынешнее «обычно» — это прежнее «плотно».
 *
 * Шаг мельче 5% не имеет смысла: разницу в полпикселя человек
 * не увидит, а список из восьми ступеней сделает выбор работой.
 */
:root {
  --ui-scale: 1;
}

[data-ui-scale="compact"] { --ui-scale: 0.94; }
[data-ui-scale="large"] { --ui-scale: 1.06; }
[data-ui-scale="huge"] { --ui-scale: 1.12; }

:root {
  /* Текст — в размер корня, а не 0.875 от него. */
  --tblr-body-font-size: 1rem;
  /* 1.43 — межстрочный для таблиц. Для чтения нужно просторнее. */
  --tblr-body-line-height: 1.55;
}

:root {
  /* Фирменные цвета. Не используются напрямую в разметке — только
     через переменные Tabler ниже: так правка цвета остаётся правкой
     одной строки. */
  --brand-ink: #0a0f17; /* почти чёрный с синевой */
  --brand-blue: #0088ff;
  --brand-blue-deep: #0071d6; /* для светлой темы: белый текст на нём читается */
  --brand-lime: #c1f742;
  /* Глубокий лайм — для СВЕТЛОЙ темы, и это не вкусовщина, а счёт.
     Яркий лайм на белом даёт 1.26:1 при норме 4.5: им нельзя ни писать,
     ни рисовать кольцо фокуса: двухпиксельная обводка на белом просто
     не видна. Этот даёт 5.0 на нашем светлом фоне и 5.45 на белом. */
  --brand-lime-deep: #567312;
  --brand-mute: #c4c6cf;
  --brand-surface: #f5f5f8;

  /* Скругления с сайта: 8 у кнопок, 10 у карточек. У Tabler по
     умолчанию 6 — мельче и суше. */
  --tblr-border-radius: 8px;
  --tblr-border-radius-sm: 6px;
  --tblr-border-radius-lg: 10px;
  --tblr-border-radius-xl: 20px;

  /* Цвет курса. Подменяется на странице курса; здесь — запасной,
     чтобы правила ниже работали и там, где курса нет. */
  --course-color: var(--brand-blue);
}

/* ------------------------------- контракт токенов (образец Luminaux)
 *
 * Три переменные, которых нам не хватало, и словарь движения. Сняты
 * с образца, выбранного владельцем 23.08; разбор целиком —
 * `docs/дизайн-luminaux/`.
 *
 * Почему именно эти три. У Tabler есть `--tblr-primary`, но нет ответа
 * на два вопроса: каким цветом фирменный становится при наведении
 * и каким цветом писать НА нём. Мы отвечали на них по месту — считали
 * `color-mix` в каждой кнопке и ставили белый текст на любой фон.
 *
 * Второе однажды стоило дорого: 21.08 белые буквы на светлом лайме
 * дали контраст 3.16:1 при норме 4.5, и чинили это руками, разводя
 * два токена в макетах сайта. Здесь то же лекарство, но в одном месте
 * и для всего кабинета.
 */
:root {
  /* Текст НА фирменном цвете. Не «белый», а тот, что читается: у синего
     это белый, у лайма — почти чёрный. Задаётся вместе с цветом. */
  --primary-foreground: #fff;
  /* Фирменный при наведении. Отдельным значением, а не расчётом:
     «чуть темнее» у синего и у лайма — разные вещи. */
  --primary-hover: var(--brand-blue-deep);
  /* Кольцо фокуса. До этого его не было вовсе: браузерный пунктир
     на тёмной теме почти не виден, а он — единственная навигация
     для тех, кто ходит клавиатурой. */
  --ring: var(--brand-blue);

  /* Движение одним словарём. Раньше длительности стояли числами
     по месту и разъезжались: 0.15 здесь, 0.2 там, 0.3 ещё где-то. */
  --duration: 0.15s;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  /* Выразительное — только для ПОЯВЛЕНИЯ, не для наведения:
     на наведении оно читается как задержка. */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

/* Кольцо фокуса на всём, что можно взять с клавиатуры.
 *
 * `:focus-visible`, а не `:focus`: второй рисует кольцо и на мышином
 * щелчке, и тогда его начинают убирать целиком — вместе с доступностью
 * для тех, у кого мыши нет.
 */
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

/* ---------------------------------------------------------- светлая */

[data-bs-theme="light"] {
  /* СИНИЙ — цвет платформы по умолчанию (решение владельца 27.08).
   *
   * До этого умолчанием был лайм. Он остался одной из гамм, но плохо
   * годился в умолчание по двум причинам сразу: он светлый, поэтому
   * на светлой теме кнопке нужна обводка (край к фону 1.2:1 против
   * нормы 3), и он яркий настолько, что школа со своим спокойным
   * фирменным стилем первым делом шла его менять.
   *
   * Синий этих бед не имеет: 4.85:1 текста на нём и 4.5:1 края к фону
   * без всяких обводок. Умолчание должно быть тем, что подходит
   * большинству молча.
   */
  --tblr-primary: var(--brand-blue-deep);
  --tblr-primary-rgb: 0, 113, 214;
  --primary-foreground: #fff; /* 4.85:1 */
  /* Темнее, а не светлее: на светлом фоне «активнее» — это «глубже».
     В тёмной теме наведение уходит в другую сторону, и это не разнобой,
     а одно правило — «заметнее относительно фона». */
  --primary-hover: #005bb0;
  --ring: var(--brand-blue-deep);
  --tblr-body-bg: var(--brand-surface);
  --tblr-body-color: #1a1f28;
  --tblr-secondary: #6b7280;
  --tblr-border-color: #e4e5ea;
  --tblr-link-color: var(--brand-blue-deep);
  --tblr-link-hover-color: #005bb0;
}

/* ----------------------------------------------------------- тёмная */

[data-bs-theme="dark"] {
  /* Синий и здесь: умолчание одно на обе темы. Тёмный текст на нём
     даёт 5.46:1 — белый дал бы 3.1 и не прошёл бы норму. */
  --tblr-primary: var(--brand-blue);
  --tblr-primary-rgb: 0, 136, 255;
  --primary-foreground: var(--brand-ink); /* 5.46:1 */
  /* Светлее, а не темнее: на тёмном фоне «ярче» означает «активнее». */
  --primary-hover: #339fff;
  --ring: var(--brand-blue);

  /* Три уровня поверхности, а не два. Тёмная тема без градации
     превращается в плоское пятно, где карточка неотличима от фона,
     и глазу не за что зацепиться. */
  --tblr-body-bg: var(--brand-ink); /* страница */
  --tblr-bg-surface: #121a26; /* карточка */
  --tblr-bg-surface-secondary: #0d141d;
  --tblr-bg-surface-tertiary: #1a2432; /* приподнятое: шапка, наведение */

  --tblr-body-color: #e8eaf0;
  --tblr-secondary: var(--brand-mute);
  --tblr-border-color: rgba(255, 255, 255, 0.08);
  --tblr-link-color: #4da6ff;
  --tblr-link-hover-color: #80c0ff;
}

/* --------------------------------------------------------- скины
 *
 * Фирменный цвет выбором человека. Лайм — основной и лежит выше,
 * в самих темах; здесь только два других, переопределением.
 *
 * Три набора, а не палитра. Свободный выбор цвета выглядит щедростью
 * ровно до первого выбранного: текст НА цвете читается по-разному,
 * и школа, взявшая нежно-жёлтый, сделает кнопки нечитаемыми, не заметив.
 * У каждого набора ниже контраст ПОСЧИТАН, а не подобран на глаз:
 * норма 4.5:1, значения в комментариях.
 *
 * Порядок селекторов важен: `[data-skin]` идёт после `[data-bs-theme]`,
 * поэтому побеждает при равной специфичности. Обе темы перечислены
 * явно — скин без темы означал бы «в тёмной одно, в светлой то же»,
 * а это неверно для любого светлого цвета.
 */
/* Гаммы школы. Синий — умолчание и лежит в базовых блоках выше;
 * здесь он повторён явно, чтобы выбор «Синий» работал и у школы,
 * которая когда-то выбрала другую гамму.
 *
 * КАЖДАЯ ПАРА ПОСЧИТАНА, а не подобрана на глаз. Условий два:
 * текст НА цвете не ниже 4.5:1 (WCAG AA) и край кнопки к фону
 * страницы не ниже 3:1 (WCAG 1.4.11). Числа записаны рядом
 * и закреплены тестом `apps/core/test_skin.py`, который читает
 * этот файл и считает формулой.
 *
 * Тёмный текст на светлой заливке и белый на тёмной — не вкус,
 * а следствие счёта: у лайма белый даёт 1.26:1, и ровно на этом
 * мы обожглись 21.08.
 */
[data-bs-theme="dark"][data-skin="blue"] {
  --tblr-primary: var(--brand-blue);
  --tblr-primary-rgb: 0, 136, 255;
  --primary-foreground: var(--brand-ink); /* 5.46:1 */
  --primary-hover: #339fff;
  --ring: var(--brand-blue);
}

[data-bs-theme="light"][data-skin="blue"] {
  --tblr-primary: var(--brand-blue-deep);
  --tblr-primary-rgb: 0, 113, 214;
  --primary-foreground: #fff; /* 4.85:1 */
  --primary-hover: #005bb0;
  --ring: var(--brand-blue-deep);
}

/* Лайм — бывшее умолчание, теперь одна из гамм. Единственная,
   которой на светлой теме нужна обводка: край к фону 1.2:1. */
[data-bs-theme="dark"][data-skin="lime"] {
  --tblr-primary: var(--brand-lime);
  --tblr-primary-rgb: 193, 247, 66;
  --primary-foreground: var(--brand-ink); /* 15.23:1 */
  --primary-hover: #d0f96e;
  --ring: var(--brand-lime);
}

[data-bs-theme="light"][data-skin="lime"] {
  --tblr-primary: var(--brand-lime);
  --tblr-primary-rgb: 193, 247, 66;
  --primary-foreground: var(--brand-ink); /* 15.23:1 */
  --primary-hover: #a9e01c;
  --ring: var(--brand-lime-deep); /* кольцо — линия, ему нужен контраст
                                     с фоном, а не с заливкой */
}

[data-bs-theme="dark"][data-skin="coral"] {
  --tblr-primary: #f0a04b;
  --tblr-primary-rgb: 240, 160, 75;
  --primary-foreground: var(--brand-ink); /* 8.99:1 */
  --primary-hover: #f5b671;
  --ring: #f0a04b;
}

[data-bs-theme="light"][data-skin="coral"] {
  --tblr-primary: #c2410c;
  --tblr-primary-rgb: 194, 65, 12;
  --primary-foreground: #fff; /* 5.18:1 */
  --primary-hover: #9a340a;
  --ring: #c2410c;
}

[data-bs-theme="dark"][data-skin="violet"] {
  --tblr-primary: #a78bfa;
  --tblr-primary-rgb: 167, 139, 250;
  --primary-foreground: var(--brand-ink); /* 7.06:1 */
  --primary-hover: #bda5fb;
  --ring: #a78bfa;
}

[data-bs-theme="light"][data-skin="violet"] {
  --tblr-primary: #7c3aed;
  --tblr-primary-rgb: 124, 58, 237;
  --primary-foreground: #fff; /* 5.70:1 */
  --primary-hover: #6425d0;
  --ring: #7c3aed;
}

[data-bs-theme="dark"][data-skin="teal"] {
  --tblr-primary: #2dd4bf;
  --tblr-primary-rgb: 45, 212, 191;
  --primary-foreground: var(--brand-ink); /* 10.32:1 */
  --primary-hover: #5ee0d0;
  --ring: #2dd4bf;
}

[data-bs-theme="light"][data-skin="teal"] {
  --tblr-primary: #0d9488;
  --tblr-primary-rgb: 13, 148, 136;
  --primary-foreground: var(--brand-ink); /* 5.13:1 — белый дал бы 3.9 */
  --primary-hover: #0b7b71;
  --ring: #0d9488;
}

[data-bs-theme="dark"][data-skin="rose"] {
  --tblr-primary: #fb7185;
  --tblr-primary-rgb: 251, 113, 133;
  --primary-foreground: var(--brand-ink); /* 7.13:1 */
  --primary-hover: #fc93a2;
  --ring: #fb7185;
}

[data-bs-theme="light"][data-skin="rose"] {
  --tblr-primary: #be123c;
  --tblr-primary-rgb: 190, 18, 60;
  --primary-foreground: #fff; /* 6.29:1 */
  --primary-hover: #9d0f32;
  --ring: #be123c;
}

/* ------------------------------ липкая шапка (шаг 3 карты Luminaux)
 *
 * Шапка кабинета остаётся наверху при прокрутке, фон полупрозрачный
 * с размытием. Приём снят с образца; там же замерены и числа: 80%
 * непрозрачности и `blur(8px)`.
 *
 * Зачем. В шапке живут имя, переключатель темы и выход, а страницы
 * у нас длинные — урок на два экрана, оглавление на десять. Уехавшая
 * наверх шапка означает прокрутку вверх ради одного нажатия.
 *
 * Полупрозрачность, а не сплошной фон: под шапкой должно быть видно,
 * что содержимое продолжается, — иначе она читается как край страницы,
 * и прокрутку не пробуют.
 *
 * `-webkit-backdrop-filter` рядом обязателен: без него Safari
 * показывает просто полупрозрачный фон, сквозь который текст
 * просвечивает и мешает читать.
 */
.navbar.navbar-expand-md {
  position: sticky;
  top: 0;
  z-index: 1020; /* ниже боковой панели Tabler (1030), выше содержимого */
  background: color-mix(in srgb, var(--tblr-bg-surface, var(--tblr-body-bg)) 80%, transparent);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--tblr-border-color);
}

/* Кому размытие противопоказано — сплошной фон. Просьба системы
   «меньше прозрачности» приходит от людей с расстройствами зрения
   и вестибулярного аппарата, и красота здесь не аргумент. */
@media (prefers-reduced-transparency: reduce) {
  .navbar.navbar-expand-md {
    background: var(--tblr-bg-surface, var(--tblr-body-bg));
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* ================= ВТОРОЙ ЗАХОД НА ОБРАЗЕЦ: не цвет, а всё остальное
 *
 * 23.08 сняли с образца цвет и четыре приёма. Владелец сказал, что взяли
 * не всё: ему нравились «эргономика, вёрстка, стиль, иконки, анимация,
 * шрифты, панель навигации». Подключились к шаблону заново и сняли мерки
 * с живой страницы, а не с картинки. Ниже — разница, которая была,
 * и что с ней сделано.
 *
 * Все значения переведены в `rem`. В образце они в пикселях, но у нас
 * корень плавает: `clamp()` умноженный на `--ui-scale` (настройка
 * масштаба интерфейса). Скопируй пиксели — и настройка перестанет
 * работать ровно там, где она нужнее всего.
 */

/* --------------------------------------------- заголовки страниц
 *
 * Было 19px весом 600 без межбуквенного. В образце — 30px весом 700
 * с межбуквенным −0.75px.
 *
 * Это, похоже, и есть та самая «не тот стиль», которую видно на бою:
 * заголовок страницы у нас лишь чуть крупнее обычного текста, и оттого
 * страница читается сплошным полотном без начала.
 *
 * Отрицательное межбуквенное — не украшение. Крупный кегль набирается
 * плотнее мелкого: пробелы между буквами растут вместе с размером,
 * и на 30px они уже разваливают слово. Приём старый, типографский;
 * образец применяет его только к КРУПНОМУ тексту, и мы тоже —
 * на мелком он слепит буквы.
 */
.page-title {
  font-size: 1.875rem;
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: -0.025em;
}

h1, .h1 { letter-spacing: -0.025em; }
h2, .h2 { letter-spacing: -0.02em; }

/* ------------------------------------------- порядок в кеглях
 *
 * Замечание владельца 24.08: подсказки в формах оказались КРУПНЕЕ
 * пунктов меню. Так вышло не нарочно: подсказка не задавала размера
 * и наследовала базовый текст (~15.5px), а меню было задано в rem
 * на ступень ниже. Второстепенное крупнее главного — иерархия
 * вверх ногами.
 *
 * Правило одно на всю платформу: базовый текст — 1rem, всё служебное
 * (подсказки, метки, вторичные строки) — 0.875rem, и НИЧТО служебное
 * не бывает крупнее основного. Дальше размеры задаются только этими
 * ступенями, а не по месту.
 */
.form-hint,
.form-check-label + .form-hint,
.dropdown-header {
  font-size: 0.875rem;
}

.form-label,
.form-check-label {
  font-size: 0.9375rem;
}

/* Обычная кнопка — на той же ступени, что метки и меню. У Tabler она
   размером с подсказку, и «Сохранить» выглядел служебной мелочью.
   `:not(.btn-sm)` обязателен: правило стоит ПОСЛЕ tabler.min.css
   и без оговорки перебило бы и маленькие кнопки. */
.btn:not(.btn-sm) {
  font-size: 0.9375rem;
}

/* ------------------------------------------- панель навигации
 *
 * Мерки образца: 14px весом 500, значок 18px, отступ до подписи 12px,
 * поле 10px на 12px, скругление 8px. У нас было 15.5px весом 400,
 * значок 19px и никакого `gap` — Tabler разводит их полем у значка.
 *
 * Главное здесь не размеры, а СПОСОБ ПОКАЗАТЬ ТЕКУЩИЙ ПУНКТ. В образце
 * непрочитанные пункты приглушены, а текущий — просто полной яркости.
 * Ни заливки, ни полосы сбоку. Приём тихий и работает лучше цветной
 * плашки: глаз ищет самое яркое пятно, и им оказывается ровно то,
 * где человек находится.
 *
 * Мельче, но жирнее — так же намеренно: 14px весом 500 читается лучше,
 * чем 15.5px весом 400, и занимает меньше места.
 */
/* Панель ВСЕГДА тёмная — и в светлой теме тоже.
 *
 * Так в образце: фон `rgb(17,24,39)` на светлой странице. Проверено
 * замером, а не на глаз, — в прошлый раз я снял мерки с одной страницы
 * и решил, что понял весь шаблон.
 *
 * Приём не про красоту. Тёмная полоса слева — это рама, а не часть
 * содержимого: она отделяет «где я нахожусь» от «что я делаю»,
 * и глазу не нужно каждый раз решать, где кончается меню. Заодно
 * снимается вечная беда светлых боковушек: белое меню на белой
 * странице держится на одной тонкой линии.
 *
 * Свои переменные, а не переопределение цветов по месту: внутри панели
 * живут и подписи, и значки, и числа, и разделители, и все они должны
 * считать «фон тёмный» независимо от темы страницы.
 */
.navbar.navbar-vertical {
  --sidebar-bg: #111827;
  --sidebar-fg: #e8eaf0;
  --sidebar-muted: #94a3b8;
  --sidebar-hover: rgba(255, 255, 255, 0.06);
  --sidebar-border: rgba(255, 255, 255, 0.08);

  background-color: var(--sidebar-bg);
  color: var(--sidebar-fg);
  border-right: 1px solid var(--sidebar-border);
  box-shadow: none; /* Tabler рисует край внутренней тенью — она тут лишняя */
}

.navbar.navbar-vertical .navbar-brand,
.navbar.navbar-vertical .navbar-brand a {
  color: var(--sidebar-fg);
}

/* Селектор ДЛИННЫЙ намеренно, и короче его сделать нельзя.
 *
 * У Tabler есть `.navbar-vertical.navbar-expand-lg .navbar-collapse
 * .navbar-nav .nav-link` — четыре класса против моих двух. Короткое
 * правило проигрывало ему по специфичности, и поля пункта оставались
 * тэблеровскими (0.5rem × 1rem вместо 0.625 × 0.75). Обнаружено 26.08
 * при сверке с образцом: кегль применился, поля — нет, и я целый день
 * считал, что панель повторяет образец.
 *
 * Урок тот же, что был с подпунктами: если правило обязано победить,
 * превосходство пишется специфичностью. Проверять — замером
 * вычисленного значения, а не наличием строки в файле.
 */
.navbar-vertical .nav-link,
.navbar-vertical.navbar-expand-lg .navbar-collapse .navbar-nav .nav-link {
  /* 15px, а не 14px из образца. Замечание владельца 24.08: на фоне
     высокой тёмной панели 14px читался слишком мелким. Причина
     в разнице масштабов: у образца корень 16px, у нас он плавает
     от clamp() и обычно меньше — та же дробь даёт меньше пикселей.

     Вес 600, хотя в образце 500, и это НЕ отступление. Начертание
     весит по-разному в разных шрифтах: у образца Plus Jakarta Sans,
     у нас Manrope, и его 500 выглядит почти обычным. Владелец увидел
     это 26.08 — «шрифт в панели кажется обычным». Копировать надо
     впечатление, а не число. */
  font-size: 0.9375rem;
  font-weight: 600;
  gap: 0.75rem;
  padding: 0.625rem 0.75rem;
  border-radius: var(--tblr-border-radius);
  color: var(--sidebar-muted);
  transition: background-color var(--duration) var(--ease),
              color var(--duration) var(--ease);
}

/* Наведение — прямоугольник, а не только цвет.
 *
 * В образце `hover:bg-surface-muted hover:text-foreground`. Прямоугольник
 * важнее, чем кажется: он показывает ГРАНИЦЫ нажимаемого. Без него
 * человек целится в слово, а нажимается вся строка — и промах по пустому
 * месту справа читается как «не сработало». */
/* СОСТОЯНИЯ ПОВТОРЯЮТ ДЛИНУ СЕЛЕКТОРА БАЗЫ, и это обязательно.
 *
 * 26.08 я усилил базовое правило пункта до пяти классов, чтобы
 * побить Tabler, — и оно стало перебивать собственные состояния,
 * которые остались на трёх. Подсветка текущего раздела пропала:
 * рамка и фирменный цвет были написаны, но проигрывали по
 * специфичности СОБСТВЕННОМУ базовому правилу. Владелец увидел
 * это сразу, 27.08.
 *
 * Урок: поднимая специфичность базы, поднимай и состояния. Иначе
 * чинишь одно и ломаешь соседнее, а выглядит это как «пропало само».
 */
.navbar-vertical .nav-link:hover,
.navbar-vertical.navbar-expand-lg .navbar-collapse .navbar-nav .nav-link:hover {
  background-color: var(--sidebar-hover);
  color: var(--sidebar-fg);
}

/* Текущий раздел — фирменным цветом: подложка в 10% и им же подпись.
 *
 * Числа из образца: `rgba(39,127,255,0.10)` со скруглением 8px, подпись
 * `rgb(43,127,255)` — то есть его основной цвет. У нас основной свой
 * у каждой школы, поэтому берём `--tblr-primary`, и раздел красится
 * в цвет школы.
 *
 * Десять процентов, а не сплошная заливка: сплошная кричит громче,
 * чем нужно постоянно висящей отметке, и спорит с кнопкой «Продолжить»,
 * которая покрашена тем же цветом и по-настоящему зовёт нажать. */
.navbar-vertical .nav-link.active,
.navbar-vertical .nav-link.active:hover,
.navbar-vertical.navbar-expand-lg .navbar-collapse .navbar-nav .nav-link.active,
.navbar-vertical.navbar-expand-lg .navbar-collapse .navbar-nav .nav-link.active:hover {
  background-color: color-mix(in srgb, var(--tblr-primary) 10%, transparent);
  color: var(--tblr-primary);
}

.navbar-vertical .nav-link.active .nav-link-icon,
.navbar-vertical.navbar-expand-lg .navbar-collapse .navbar-nav .nav-link.active .nav-link-icon { color: inherit; }

/* `gap` вместо поля у значка: поле сдвигает только значок, а `gap`
   держит расстояние между всем, что стоит в строке, — включая число
   непроверенных работ справа. */
.navbar-vertical .nav-link-icon {
  margin-right: 0;
  margin-inline-end: 0;
}

.navbar-vertical .nav-link-icon svg {
  width: 18px;
  height: 18px;
}

/* ------------------------------------- раскрывающиеся подпункты
 *
 * В образце разделы раскрываются на месте: нажал «Приложения» —
 * под ним развернулись «Скрамборд», «Виджет чата» и остальное.
 * У нас этого не было вовсе: «Настройки» вели на страницу с плитками,
 * и чтобы попасть в тарифы, нужно было сначала попасть в настройки.
 *
 * Подпункты той же высоты и того же кегля, что и родители, — так
 * в образце. Отличаются только отступом: подпись подпункта встаёт
 * под подпись родителя, а не под его значок. Своего значка у подпункта
 * нет намеренно — иначе получается второй столбец картинок, и уровень
 * вложенности перестаёт читаться.
 */
.nav-subitems {
  list-style: none;
  padding: 0;
  /* ВЕСЬ БЛОК вправо на 20px — так в образце (замерено: родитель
   * начинается на x=6, подменю на x=26).
   *
   * Первая правка двигала только ТЕКСТ внутри подпункта, а прямоугольник
   * оставался во всю ширину панели — и подменю читалось продолжением
   * списка, а не содержимым «Настроек». Владелец указал на это дважды,
   * и оба раза был прав: вложенность показывает сдвинутая ПЛАШКА,
   * а не сдвинутая подпись.
   */
  margin: 0.125rem 0 0 1.25rem;
}

/* Селектор с `.navbar-vertical` НАМЕРЕННО, и это исправление ошибки.
 *
 * Первая версия писала `.nav-subitems .nav-link` — та же специфичность,
 * что у базового правила пункта выше, а то задаёт `padding` ШОРТКАТОМ,
 * который затирает и левую сторону. Побеждал порядок в файле, и на бою
 * подпункты стояли прижатыми к краю панели — по ним было не понять,
 * что они внутри «Настроек». Замечено владельцем 24.08.
 *
 * Урок: если правило обязано победить, его превосходство должно быть
 * записано специфичностью, а не позицией в файле — позицию сломает
 * первая же перестановка блоков.
 */
.navbar-vertical .nav-subitems .nav-link {
  /* Поле как у родителя: сдвиг делает сам блок выше. В образце ровно
     так — 12px у обоих, и текст подпункта оказывается на 38px против
     48px у родителя (у того слева ещё значок). */
  padding-left: 0.75rem;
  font-weight: 400;
  font-size: 0.875rem; /* на ступень мельче родителя: уровень вложенности
                          должен читаться и размером, не только отступом */
}

/* Строка раздела: ссылка растянута, стрелка отдельной кнопкой справа.
   Два действия — два места нажатия, и промах по одному не делает
   другого. */
.nav-parent {
  display: flex;
  align-items: center;
}

/* Стрелка раскрытия. Поворачивается, а не подменяется другой:
   поворот показывает СВЯЗЬ между состояниями, подмена — два разных
   значка, между которыми связь надо додумать. */
.nav-caret {
  display: inline-flex;
  transition: transform 0.2s var(--ease);
}

.nav-caret-btn {
  display: inline-flex;
  align-items: center;
  padding: 0.625rem 0.5rem;
  border: 0;
  border-radius: var(--tblr-border-radius);
  background: transparent;
  color: var(--sidebar-muted);
  transition: background var(--duration) var(--ease),
              color var(--duration) var(--ease);
}

.nav-caret-btn:hover {
  background: var(--sidebar-hover);
  color: var(--sidebar-fg);
}

.nav-caret-btn[aria-expanded="true"] .nav-caret { transform: rotate(90deg); }

/* В свёрнутом меню раскрывать нечего: подписей нет. Стрелка убирается,
   а сами подпункты остаются доступны — по нажатию меню развернётся. */
[data-nav="compact"] .nav-caret,
[data-nav="compact"] .nav-subitems { display: none; }

@media (prefers-reduced-motion: reduce) {
  .nav-caret { transition: none; }
}

/* Заголовки разделов меню. Двенадцатым кеглем, прописными, с разрядкой
 * и приглушённо — так в образце.
 *
 * Разрядка у прописных обязательна: заглавные буквы набраны без
 * привычных глазу выносных элементов, и без неё слово превращается
 * в частокол. Тот же приём, что и с заголовками, но в другую сторону —
 * там плотнее, здесь шире, и оба раза по одной причине: кегль.
 */
.nav-section {
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.33;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--tblr-secondary);
  opacity: 0.7;
  padding: 0 0.75rem 0.5rem;
  margin-top: 1rem;
}

/* В свёрнутом меню заголовки разделов не нужны: подписей нет, и
   разделять нечего — остаётся полоса из букв над значками. */
[data-nav="compact"] .nav-section {
  opacity: 0;
  visibility: hidden;
  height: 0;
  margin: 0;
  padding: 0;
}

/* --------------------------------------------------- карточки
 *
 * Скругление 12px и двухслойная тень — с образца. Наша тень была
 * одна и почти невидимая (`0 0 4px` с прозрачностью 4%), отчего
 * карточка на светлой теме не отрывалась от фона.
 *
 * Два слоя, а не один: нижний, размытый, даёт объём, верхний, резкий, —
 * край. Одним слоем получается либо грязь, либо ничего.
 */
[data-bs-theme="light"] .card {
  border-radius: 12px;
  box-shadow:
    0 1px 3px rgba(0, 0, 0, 0.1),
    0 1px 2px -1px rgba(0, 0, 0, 0.1);
}

/* В тёмной теме тени не работают: чёрное на чёрном не видно. Там
   карточку отрывает от фона своя поверхность — она у нас уже есть,
   тремя уровнями. Тень только добавила бы грязи по краю. */
[data-bs-theme="dark"] .card {
  border-radius: 12px;
}

/* ------------------------- меню в значки (шаг 4 карты Luminaux)
 *
 * Боковое меню сворачивается до полосы значков: 15rem → 5rem. Числа
 * из образца, там же и приём — подписи гаснут чуть раньше, чем
 * съезжает ширина, иначе они на середине хода наезжают на край.
 *
 * Зачем вообще. Меню занимает 15rem всегда, а нужно оно раз в несколько
 * минут. На ноутбуке 1366 точек это восьмая часть ширины, и отнимается
 * она у того, ради чего человек пришёл, — у урока и у оглавления.
 *
 * Значки при этом остаются: полностью убранное меню требует вспомнить,
 * что его нужно позвать, а полоса значков видна и жмётся сразу.
 *
 * ПОЧЕМУ ВЫБОР В БРАУЗЕРЕ, А ТЕМА В БАЗЕ. Разные вещи, хоть обе и
 * «настройка вида». Тема — свойство человека: выбравший тёмную хочет
 * её и на телефоне, и вспышка белого на чужом устройстве его добьёт.
 * Ширина меню — ответ на РАЗМЕР ЭКРАНА: на широком мониторе подписи
 * нужны, на узком ноутбуке мешают. Утащи её за человеком — и свёрнутое
 * на ноутбуке меню встретит его свёрнутым на мониторе, где места вдоволь.
 */
@media (min-width: 992px) {
  [data-nav="compact"] .navbar-vertical.navbar-expand-lg {
    width: 5rem;
    overflow-x: hidden;
  }

  [data-nav="compact"] .navbar-vertical.navbar-expand-lg ~ .page-wrapper {
    margin-left: 5rem;
  }

  /* Ход ширины — тот же, которым Tabler уже двигает меню на телефоне.
     Свой был бы вторым языком движения в одном элементе. */
  .navbar-vertical.navbar-expand-lg,
  .navbar-vertical.navbar-expand-lg ~ .page-wrapper {
    transition: width .2s var(--ease), margin-left .2s var(--ease);
  }

  /* Подписи гаснут быстрее хода и не переносятся: на 5rem слово
     «Настройки» сложилось бы в два ряда и растянуло полосу.
   *
   * `max-width: 0` вместе с прозрачностью, а не одна прозрачность.
   * Разница не косметическая, и на ней я обжёгся: невидимая подпись
   * ПРОДОЛЖАЛА занимать свою ширину, кнопка «Свернуть меню» центровала
   * себя вместе с ней, стрелка уезжала за край свёрнутой полосы —
   * и развернуть меню обратно было нечем. Владелец это и заметил. */
  [data-nav="compact"] .nav-link-title,
  [data-nav="compact"] .navbar-brand,
  [data-nav="compact"] .nav-link .badge {
    opacity: 0;
    max-width: 0;
    overflow: hidden;
    visibility: hidden;
    transition: opacity .18s var(--ease), max-width .2s var(--ease), visibility .18s;
  }

  .nav-link-title {
    max-width: 12rem;
    transition: opacity .18s var(--ease) .06s, max-width .2s var(--ease);
  }

  /* Значок по центру полосы: слева от него больше нет подписи,
     и прижатый к краю он выглядит забытым. */
  [data-nav="compact"] .navbar-vertical .nav-link {
    justify-content: center;
  }

  [data-nav="compact"] .navbar-vertical .nav-link-icon { margin-right: 0; }
}

/* Кнопка сворачивания. Видна только там, где меню вообще стоит
   сбоку: ниже 992px Tabler убирает его под гамбургер, и сворачивать
   там нечего. */
.nav-collapse-btn { display: none; }

@media (min-width: 992px) {
  /* Набрана ТОЧНО как пункт меню: те же кегль, начертание, поля и зазор.
   *
   * Раньше отличалась и бросалась в глаза (замечание владельца 24.08):
   * кегль наследовался от `button`, то есть от браузера, а зазор был
   * .5rem против .75rem у пунктов. Кнопка стоит в том же столбце, что
   * и меню, и любое отличие читается как «это что-то другое» — хотя
   * это тот же список, только последняя строка. */
  .nav-collapse-btn {
    display: flex;
    align-items: center;
    gap: .75rem;
    width: 100%;
    padding: .625rem .75rem;
    border: 0;
    border-radius: var(--tblr-border-radius);
    background: transparent;
    font-size: .9375rem;
    font-weight: 500;
    font-family: inherit; /* иначе браузер ставит кнопке свой шрифт */
    text-align: left;
    color: var(--sidebar-muted);
    transition: background var(--duration) var(--ease),
                color var(--duration) var(--ease);
  }

  .nav-collapse-btn {
    color: var(--sidebar-muted);
  }

  .nav-collapse-btn:hover {
    background: var(--sidebar-hover);
    color: var(--sidebar-fg);
  }

  /* Стрелка не сжимается вместе с подписью: она и есть единственное,
     чем меню разворачивают обратно. */
  .nav-collapse-btn svg { flex: none; }

  /* Стрелка разворачивается: одна кнопка на оба состояния, и
     направление показывает, что случится, а не что есть. */
  [data-nav="compact"] .nav-collapse-btn svg { transform: scaleX(-1); }
  .nav-collapse-btn svg { transition: transform .2s var(--ease); }
  [data-nav="compact"] .nav-collapse-btn { justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .navbar-vertical.navbar-expand-lg,
  .navbar-vertical.navbar-expand-lg ~ .page-wrapper,
  .nav-link-title,
  .nav-collapse-btn svg { transition: none; }
}

/* ------------------------------------ скелет ожидания (шаг 5 карты)
 *
 * Мерцающая заглушка вместо пустоты, пока едет ответ HTMX. Пустота
 * на медленной сети читается как поломка: человек нажал, ничего
 * не изменилось, и он нажимает снова.
 *
 * Мерцание выключается при просьбе «меньше движения» — так сделано
 * и в образце. Полоса при этом остаётся: она и без движения отвечает
 * «идёт загрузка».
 */
.skeleton {
  position: relative;
  overflow: hidden;
  height: 0.875rem;
  border-radius: 100px;
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
}

.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in srgb, var(--tblr-body-color) 8%, transparent),
    transparent
  );
  animation: skeleton-shimmer 1.5s infinite;
}

@keyframes skeleton-shimmer {
  to { transform: translateX(100%); }
}

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

/* ------------------------------------------------ кружок человека
 *
 * Решение владельца 23.08: «круто, когда обучение онлайн проходит
 * не в обезличенном формате». Куратор без лица читается как служба
 * поддержки, а не как человек, у которого учишься.
 *
 * Размеры в `rem`, а не в пикселях: кружок стоит рядом с именем,
 * и при укрупнении интерфейса он обязан расти вместе с текстом,
 * иначе строка расползается.
 */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  overflow: hidden;
  border-radius: 100px;
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
  font-weight: 600;
  line-height: 1;
  user-select: none;
}

.avatar img {
  width: 100%;
  height: 100%;
  /* `cover`, а не `contain`: портрет с телефона вытянут, и вписывание
     целиком оставило бы поля по бокам — кружок перестал бы быть кружком.
     Обрезка по центру для лиц работает: их туда и ставят. */
  object-fit: cover;
}

.avatar-sm { width: 1.75rem; height: 1.75rem; font-size: 0.6875rem; }
.avatar-md { width: 2.25rem; height: 2.25rem; font-size: 0.8125rem; }
.avatar-lg { width: 5rem; height: 5rem; font-size: 1.75rem; }

/* Инициалы: цвет приходит из имени, буквы белые.
 *
 * Белые буквы допустимы ровно потому, что палитра кружков подобрана
 * из ТЁМНЫХ цветов (`core/avatars.py`) — там нет ни лайма, ни жёлтого.
 * Иначе повторилась бы беда 21.08: белым по светлому 1.26:1. */
.avatar-initials {
  background: var(--avatar-color, var(--tblr-secondary));
  color: #fff;
}

/* Кружок в тёмной боковой панели: рамка вместо тени, чтобы фотография
   не сливалась с фоном, если она сама тёмная по краю. */
.navbar-vertical .avatar,
.navbar .avatar {
  box-shadow: 0 0 0 1px var(--tblr-border-color);
}

/* Колокольчик с числом. Число НА значке, а не рядом: рядом оно
 * читается как отдельный предмет, а на значке — как его свойство,
 * и это то же самое, что человек уже видел в телефоне тысячу раз.
 */
.nav-bell {
  position: relative;
  display: inline-flex;
  line-height: 1;
}

.nav-bell-count {
  position: absolute;
  top: -0.375rem;
  right: -0.5rem;
  min-width: 1.125rem;
  padding: 0 0.25rem;
  border-radius: 100px;
  background: var(--tblr-danger);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.125rem;
  text-align: center;
}

/* Обложка курса.
 *
 * Высота задана отношением сторон, а не картинкой: школа загружает
 * что угодно — и вертикальный снимок, и широкий баннер, — а ряд
 * карточек на витрине обязан стоять ровно. Без этого каждая карточка
 * тянется по своей обложке, и список выглядит сломанным.
 *
 * `cover` обрезает по центру. Для обложек это правильнее, чем поля
 * по бокам: пустые поля читаются как «картинка не загрузилась».
 */
.course-cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  /* Фон под прозрачным PNG и на время загрузки: без него в тёмной
     теме на месте картинки зияет дыра цвета страницы. */
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
}

/* Миниатюра обложки в списке. Прямоугольник со скруглением, а не круг:
   круг здесь спорил бы с кружком человека, а это разные вещи. */
.course-thumb {
  width: 3rem;
  height: 2rem;
  flex: none;
  border-radius: 6px;
  object-fit: cover;
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
}

/* Окно обрезки картинки (crop.js).
 *
 * Высота задана жёстко: Cropper растягивает контейнер по картинке,
 * и вертикальный снимок с телефона делал модалку выше экрана —
 * кнопки «Применить» было не достать. */
/* РАЗМЕР ЗДЕСЬ — не косметика, а суть окна.
 *
 * Первая версия задавала только `max-height`, и Cropper подгонял
 * область под натуральный размер картинки. Снимок 400×300 показывался
 * как 400×300 внутри узкой модалки — то есть «правишь иконку в масштабе
 * один к одному, почти ничего не видно» (замечание владельца 24.08).
 *
 * Теперь область занимает всю ширину окна и имеет свою высоту, а
 * картинка вписывается в неё. Мелкий снимок растягивается, крупный
 * ужимается — в обоих случаях кадрируют глядя, а не угадывая.
 */
.crop-area {
  width: 100%;
  height: min(60vh, 26rem);
  overflow: hidden;
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
  border-radius: var(--tblr-border-radius);
}

.crop-area img {
  display: block;
  max-width: 100%;
}

/* Cropper подставляет свой контейнер вместо картинки — растягиваем
   и его, иначе он остаётся размером с исходник. */
.crop-area .cropper-container {
  width: 100% !important;
  height: 100% !important;
}

/* Круглый предпросмотр для аватарки: рамка у Cropper квадратная,
   скругляем область просмотра — человек видит будущий кружок,
   а не квадрат, из которого «что-то получится». */
.crop-area-round .cropper-view-box,
.crop-area-round .cropper-face {
  border-radius: 50%;
}

/* Плитка медиа-канала на витрине.
 *
 * Значок в фирменном цвете школы, подложка в 10% — тот же приём, что
 * у текущего пункта меню. Каналы у всех школ одни и те же, а цвет
 * свой: плитка должна выглядеть частью этой школы, а не вставкой
 * чужого сервиса.
 */
.channel-tile {
  transition: border-color var(--duration) var(--ease),
              transform var(--duration) var(--ease);
}

.channel-tile:hover {
  border-color: var(--tblr-primary);
  /* Подъём на один пиксель, а не тень: тень на карточке уже есть,
     и вторая по наведению делает её жирной кляксой. */
  transform: translateY(-1px);
}

.channel-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 10px;
  color: var(--tblr-primary);
  background: color-mix(in srgb, var(--tblr-primary) 10%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .channel-tile { transition: none; }
  .channel-tile:hover { transform: none; }
}

/* Заголовок раздела на странице настроек.
 *
 * Между заголовком страницы (1.875rem) и заголовком карточки (1rem):
 * он делит страницу на части, но не спорит с её названием. Линия
 * сверху отделяет раздел от предыдущего сильнее, чем это сделал бы
 * один отступ, — а именно слипшиеся ряды карточек и читались
 * «кучей-малой». */
.settings-section {
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0 0 0.25rem;
  padding-top: 0.5rem;
}

.settings-section + p { margin-top: 0; }

/* Объяснение роли. `<details>` вместо всплывающей подсказки: она живёт
 * по наведению мыши, а на телефоне мыши нет — там подсказка либо
 * не показывается вовсе, либо перекрывает то, на что нажимают.
 */
.role-hint { font-size: 0.875rem; }

.role-hint-more > summary {
  display: inline;
  cursor: pointer;
  color: var(--tblr-link-color);
  font-size: 0.875rem;
  /* Убираем треугольник браузера: он крупный, чёрный и в строке
     с текстом выглядит мусором. Слово «подробнее» само говорит,
     что будет по нажатию. */
  list-style: none;
}

.role-hint-more > summary::-webkit-details-marker { display: none; }
.role-hint-more[open] > summary { color: var(--tblr-secondary); }

.role-hints-item + .role-hints-item {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--tblr-border-color);
}

/* Роль в таблице: пунктир снизу — общепринятый знак «здесь есть
   пояснение при наведении». Без него подсказку не ищут. */
.role-name {
  border-bottom: 1px dotted var(--tblr-border-color);
  cursor: help;
}

/* Логотип школы. Высота задана, ширина свободна: логотипы бывают
 * и квадратными, и вытянутыми в строку, и подгонять их по ширине
 * значит раздавить одни ради других.
 */
.school-logo {
  height: 2rem;
  width: auto;
  max-width: 12rem;
  object-fit: contain;
  /* `contain`, а не `cover`: у вывески нельзя обрезать края. */
}

.navbar-vertical .school-logo { max-width: 9rem; }

/* Два логотипа: обычный и для тёмной темы.
 *
 * Выбирает ТЕМА, а не сервер, и это не прихоть. У человека тема может
 * стоять «как в системе»: тогда светлая она или тёмная, решает браузер
 * уже после ответа, и сервер, выбирая файл, ошибся бы ровно у тех, кто
 * ничего не настраивал.
 *
 * `display: none` у ненужного, а не `filter: invert` у одного: инверсия
 * портит цветные знаки — синий станет оранжевым, — а логотип это
 * фирменный цвет школы, а не картинка, которую можно перекрасить.
 *
 * Классы стоят ТОЛЬКО когда второй логотип загружен (см.
 * `core/_school_logo.html`). Одинокий обычный логотип остаётся без
 * классов и виден всегда: он и должен работать в обеих темах.
 */
.логотип-тёмный { display: none; }

[data-bs-theme="dark"] .логотип-светлый { display: none; }
[data-bs-theme="dark"] .логотип-тёмный { display: inline-block; }

/* Предпросмотр в форме: на шахматке, потому что логотипы часто
   прозрачные и белые — на белой карточке такой просто не виден,
   и владелец решит, что загрузка не сработала. */
.logo-preview {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.75rem 1rem;
  border-radius: var(--tblr-border-radius);
  border: 1px solid var(--tblr-border-color);
  background-color: #6b7280;
  background-image:
    linear-gradient(45deg, rgba(0, 0, 0, .15) 25%, transparent 25%),
    linear-gradient(-45deg, rgba(0, 0, 0, .15) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, .15) 75%),
    linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, .15) 75%);
  background-size: 12px 12px;
  background-position: 0 0, 0 6px, 6px -6px, -6px 0;
}

.logo-preview img { height: 2.5rem; width: auto; max-width: 14rem; }

/* Кружки образцов в выборе цвета.
 *
 * Цвет задан ЯВНО, а не через `var(--tblr-primary)`. Соблазн был:
 * поставить на кружок `data-skin` и взять переменную. Не работает —
 * скин включается селектором `[data-bs-theme][data-skin]` на КОРНЕ
 * страницы, а не на любом элементе с таким атрибутом. Все три кружка
 * показали бы один и тот же, текущий цвет, и выбор превратился бы
 * в три одинаковых точки.
 *
 * Значения повторяют скины выше. Это дубль, и он намеренный: образец
 * обязан показывать то, что получится, а не то, что сейчас.
 */
.skin-dot {
  display: inline-block;
  width: 1rem;
  height: 1rem;
  border-radius: 100px;
  border: 1px solid var(--tblr-border-color);
}

/* «Без цвета» — перечёркнутый кружок, а не пустой.
   Пустой читается как «цвет ещё не загрузился», перечёркнутый — как
   осознанный выбор, которым он и является. */
.skin-dot-empty {
  background: linear-gradient(
    to top right,
    transparent calc(50% - 1px),
    var(--tblr-secondary) calc(50% - 1px),
    var(--tblr-secondary) calc(50% + 1px),
    transparent calc(50% + 1px)
  );
}

.skin-dot-lime { background: var(--brand-lime); }
.skin-dot-blue { background: var(--brand-blue); }
.skin-dot-coral { background: #f0a04b; }

/* В светлой теме кружок показывает глубокий вариант — тот, который
   там и применится. Иначе владелец выбрал бы яркий, а получил другой. */
[data-bs-theme="light"] .skin-dot-lime { background: var(--brand-lime-deep); }
[data-bs-theme="light"] .skin-dot-blue { background: var(--brand-blue-deep); }
[data-bs-theme="light"] .skin-dot-coral { background: #c2410c; }

/* Тени в темноте не работают: тень — это затемнение, а темнее уже
   некуда. Границу даёт светлая линия, как на сайте школы. */
[data-bs-theme="dark"] .card,
[data-bs-theme="dark"] .navbar {
  box-shadow: none;
  border: 1px solid var(--tblr-border-color);
}

/* Editor.js: значок «+» и значок настроек/перетаскивания блока
   нарисованы фиксированным #1d202b — почти чёрным, для страницы
   на белом фоне. На нашей тёмной поверхности это тёмное на тёмном,
   и оба значка видны только тогда, когда наведение подсвечивает
   область вокруг них своим hover-фоном. Библиотека не берёт цвет
   из CSS-переменных для этих двух кнопок (только popover настроек
   и inline-панель уже параметризованы через --color-*), поэтому
   переопределяем классы напрямую, а не одну переменную. */
[data-bs-theme="dark"] .ce-toolbar__plus,
[data-bs-theme="dark"] .ce-toolbar__settings-btn {
  color: var(--tblr-body-color);
}

[data-bs-theme="dark"] .ce-toolbar__plus:hover,
[data-bs-theme="dark"] .ce-toolbar__plus--active,
[data-bs-theme="dark"] .ce-toolbar__settings-btn:hover,
[data-bs-theme="dark"] .ce-toolbar__settings-btn--active {
  background-color: var(--tblr-bg-surface-tertiary);
}

/* Попап настроек блока (Выше / Ниже / Удалить) и инлайн-панель
   выделения текста (Ж / К / ссылка) уже читают цвет через
   собственные CSS-переменные Editor.js — их достаточно
   переопределить, классы внутри трогать не нужно. */
[data-bs-theme="dark"] .ce-popover {
  --color-background: var(--tblr-bg-surface);
  --color-text-primary: var(--tblr-body-color);
  --color-text-secondary: var(--brand-mute);
  --color-border: var(--tblr-border-color);
  --color-background-item-hover: var(--tblr-bg-surface-tertiary);
}

[data-bs-theme="dark"] .ce-inline-toolbar {
  --color-text-primary: var(--tblr-body-color);
}

/* --------------------------------------------------- цвет курса */

/* Подпись оформления: курс узнаётся по цвету раньше, чем прочитано
   название. Взято из системы школы — на сайте у каждого курса свой
   цвет: Windows+Linux синий, MikroTik лаймовый, Zabbix красный.
   Это навигация, а не украшение: ученик с тремя курсами различает их
   боковым зрением. */
.course-card {
  position: relative;
  overflow: hidden;
}

.course-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--course-color);
}

/* Полоса под шапкой урока: та же метка курса, что и на карточке,
   чтобы вкладка с уроком узнавалась без чтения. */
.course-rule {
  height: 3px;
  border-radius: 3px;
  background: var(--course-color);
}

.progress-course .progress-bar {
  background-color: var(--course-color);
}

/* Точка цвета курса. Третья форма той же метки: полоса у карточки,
   линия под шапкой урока и точка там, где для полосы нет места, —
   в шапке боковой колонки и в строке списка.

   Размер в `em`, как у значков: рядом стоит текст, и при выбранном
   крупном масштабе интерфейса точка обязана вырасти вместе с ним. */
.course-dot {
  display: inline-block;
  width: 0.625em;
  height: 0.625em;
  border-radius: 100px;
  background: var(--course-color);
  flex: none;
}

.badge-course {
  background: color-mix(in srgb, var(--course-color) 18%, transparent);
  color: var(--course-color);
  border: 1px solid color-mix(in srgb, var(--course-color) 35%, transparent);
}

/* ------------------------------------------------------- мелочи */

/* Наведение на основную кнопку — из токена, а не из расчёта.
 *
 * У Tabler свой `--tblr-btn-hover-bg`, но считается он от `primary`
 * одинаково для любого цвета. На синем это работает, на лайме даёт
 * грязь: тёмный лайм читается как другой цвет, а не как тот же
 * в нажатом состоянии.
 */
.btn-primary:hover,
.btn-primary:focus-visible {
  background-color: var(--primary-hover);
  border-color: var(--primary-hover);
  color: var(--primary-foreground);
}

.btn-primary {
  color: var(--primary-foreground);
  transition: background-color var(--duration) var(--ease),
              border-color var(--duration) var(--ease),
              color var(--duration) var(--ease);
}

/* Обводка у лаймовой кнопки в СВЕТЛОЙ теме.
 *
 * Лайм светлый, и на светлой странице он почти сливается с ней: заливка
 * `#c1f742` против фона `#f5f5f8` даёт 1.14:1. Текст на кнопке при этом
 * читается прекрасно (15.23:1) — не видно саму КНОПКУ, её край.
 * WCAG 1.4.11 требует для границ элементов 3:1, и это тот редкий случай,
 * когда правило совпадает с «просто некрасиво»: кнопка без края выглядит
 * пятном краски, а не кнопкой.
 *
 * Глубокий лайм по краю даёт 4.95:1 к фону и оставляет заливку яркой.
 * Это и есть ответ на «сделай лайм лаймом, но чтобы было видно».
 *
 * Только для лайма: синий и оранжевый в светлой теме сами по себе
 * тёмные, край у них виден и без обводки.
 */
[data-bs-theme="light"][data-skin="lime"] .btn-primary {
  border-color: var(--brand-lime-deep);
}

/* Кнопка-переключатель темы: круглая, без рамки, как иконки в шапке. */
.theme-toggle {
  border: 0;
  background: transparent;
  color: inherit;
  padding: 0.25rem 0.5rem;
  border-radius: 100px;
  cursor: pointer;
  line-height: 1;
}

.theme-toggle:hover {
  background: var(--tblr-bg-surface-tertiary, rgba(0, 0, 0, 0.06));
}

/* Значки (см. templates/core/_icon.html).
 *
 * Размер в `em`, а не в пикселях: значок стоит рядом с текстом кнопки
 * и должен расти вместе с ним — у нас есть настройка масштаба, и
 * в пикселях выбравший крупный шрифт получил бы прежние крошечные
 * картинки рядом с выросшими буквами.
 *
 * `stroke-width` уменьшен: набор нарисован для 24 пикселей, а мы
 * показываем его меньше — при том же весе линии значок становится
 * жирным пятном.
 */
.lms-icon {
  width: 1.25em;
  height: 1.25em;
  stroke-width: 1.75;
  vertical-align: -0.2em;
}

/* -------------------------------------- дорожка ближайших шагов */

/* Своя дорожка вместо `steps-vertical` из Tabler.
 *
 * Сначала дорожка была на Tabler: он рисует точки и линию, и это давало
 * ноль своих правил. Держалось это на его же соглашении — «всё до
 * `.active` считается пройденным», — и соглашение неверно для нас:
 * пропущенный необязательный урок стоит в ленте до текущего, но
 * пройденным не является. Обходили это отбором в окно только
 * пройденного (`outline.next_steps`), то есть подгоняли данные под
 * чужую разметку.
 *
 * Здесь состояние у каждого шага своё и написано на нём самом, так что
 * подгонка больше не нужна. Отбор в `next_steps` при этом СОХРАНЁН —
 * он оправдан и сам по себе («дорожка начинается с уже сделанного»), и
 * снимать его надо отдельным решением, а не заодно с оформлением.
 *
 * Заодно вернулись значки из макета: кружок с галочкой, стрелкой или
 * замком говорит, какое состояние, — это видно, не читая подписей.
 *
 * Линия рисуется псевдоэлементом контейнера, а не границей шагов: у
 * границы нет способа не дорисоваться после последнего.
 */
.lesson-path {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.lesson-path::before {
  content: "";
  position: absolute;
  /* От середины первого кружка до середины последнего: линия соединяет
     шаги, а не торчит над первым и под последним. */
  top: 1.375rem;
  bottom: 1.375rem;
  left: 1.625rem;
  width: 2px;
  background: var(--tblr-border-color);
}

.lesson-path-step {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--tblr-border-radius);
}

/* Текущий шаг — заливкой цвета курса. Слабой: это подсказка «ты здесь»,
   а не кнопка, и полная заливка спорила бы с настоящей кнопкой
   «Продолжить», которая стоит в этой же строке. */
.lesson-path-step.is-current {
  background: color-mix(in srgb, var(--course-color) 12%, transparent);
}

/* Кружок со значком. Фон непрозрачный намеренно: он закрывает линию,
   проходящую под ним, и та не просвечивает сквозь значок. */
.lesson-path-mark {
  position: relative;
  z-index: 1;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 2px solid var(--tblr-border-color);
  border-radius: 100px;
  background: var(--tblr-bg-surface, var(--tblr-body-bg));
  color: var(--tblr-secondary);
}

/* Пройденный и текущий — цветом курса. Не заливкой: значок внутри
   пришлось бы красить в цвет, читаемый и на лайме, и на синем,
   а такого цвета нет. Обводка и значок в цвете курса читаются на любом. */
.lesson-path-mark.is-done,
.lesson-path-mark.is-current {
  border-color: var(--course-color);
  color: var(--course-color);
}

/* Текущий отличается от пройденного не только значком: кольцо вокруг
   находится боковым зрением, а стрелка против галочки — уже нет. */
.lesson-path-mark.is-current {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--course-color) 20%, transparent);
}

.lesson-path-title { min-width: 0; }

/* Номер шага. Табличные цифры, чтобы «1.1» и «4.12» не гуляли
   по ширине и колонка названий начиналась в одном месте. */
.lesson-path-number {
  color: var(--tblr-secondary);
  font-variant-numeric: tabular-nums;
}

/* Колонка отметки в списках уроков: пройден, здесь сейчас, закрыт.
 *
 * Ширина ФИКСИРОВАННАЯ, и это главное свойство. Отметка есть не
 * у каждой строки — у непройденного и неоткрытого её нет вовсе, —
 * и без своей колонки названия уроков ездили бы влево-вправо
 * в зависимости от состояния. Список из тридцати строк с плавающим
 * левым краем читается заметно хуже, чем с ровным.
 *
 * Не в пикселях: при выбранном крупном масштабе интерфейса колонка
 * обязана вырасти вместе с текстом. Масштаб множит размер КОРНЯ
 * (`--ui-scale` выше), поэтому `rem` растёт вместе с ним так же,
 * как `em`, — а `em` здесь стояло и однажды разъехалось.
 *
 * ⚠️ ИМЕННО `rem`, А НЕ `em`. `em` считается от шрифта САМОЙ строки,
 * а строк с этой колонкой теперь две: урок обычным кеглем и заголовок
 * столбцов помельче. Колонка выходила разной ширины, и весь список
 * съезжал под заголовком на четыре пикселя — ровно та кривизна,
 * ради которой фиксированная ширина и заведена. Поймано измерением
 * левых краёв, глазом такое не видно.
 */
.lesson-mark {
  flex: none;
  width: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tblr-secondary);
}

/* Колонка номера урока в оглавлении ученика — «1.1», «2.7».
 *
 * Ширина задана, а цифры табличные, по той же причине, что и на странице
 * курса у сотрудника: без этого «1.1» и «2.7» разной ширины, и столбец
 * номеров идёт волной. Под «12.10» — самый широкий номер, который
 * реально встречается.
 */
.lesson-num {
  flex: none;
  width: 2.75rem;
  font-size: 0.8125rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* Тип урока значком — своей колонкой, как у сотрудника.
 *
 * Просьба владельца 04.09: в раскрытом оглавлении не видно, что за урок
 * впереди. Разница существенная: лекция на двадцать минут и практика
 * на час требуют разного времени и разного настроя, а список выглядел
 * ровным полотном из одинаковых строк.
 *
 * Колонкой, а не значком при названии: по типу ищут глазами сверху вниз
 * («где тут практика»), и для этого он обязан стоять на одном месте
 * в каждой строке.
 */
.lesson-kind-mark {
  flex: none;
  /* 1.5rem, а не по значку: в строке заголовков ту же колонку занимает
     слово «Тип», и колонка обязана вместить оба. Меньше — заголовок
     вылезает на название и ломает ровный левый край списка. */
  width: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tblr-secondary);
}

.lesson-kind-mark .lms-icon {
  width: 1.05rem;
  height: 1.05rem;
}

/* Длительность урока — ПОСЛЕДНЕЙ колонкой и с фиксированной шириной.
 *
 * Последней, а не сразу после названия, именно ради ровного столбца:
 * между названием и правым краем стоят метки («продолжить»,
 * «необязательный», недоделка), они есть не у каждой строки, и время,
 * поставленное перед ними, ездило бы по горизонтали от строки к строке.
 *
 * Пустая при незаполненном времени: прочерк здесь читался бы как
 * «урок на ноль минут», а это разные вещи — «быстро» и «не измеряли».
 */
/* Ширина — под ПОДПИСЬ, а не под «30 мин»: в строке заголовков ту же
 * колонку занимает слово «Длительность», и оно здесь длиннее всех
 * значений. Колонка уже подписи — это подпись, вылезающая на название,
 * то есть ровно та кривизна, ради которой фиксированная ширина
 * и заведена.
 *
 * 5.5rem мерено в браузере: «Длительность» кеглем строки заголовков
 * занимает 80px при корне 14.88px, колонка — 82px.
 */
.lesson-time-mark {
  flex: none;
  width: 5.5rem;
  text-align: right;
  font-size: 0.8125rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: var(--tblr-secondary);
}

/* Строка заголовков над уроками модуля.
 *
 * Замечание владельца 04.09: подписи сливались со списком — той же
 * величины, того же цвета, на том же фоне, и глаз читал их как ещё
 * один урок. Заголовок обязан отличаться, иначе он не заголовок.
 *
 * Отличается тремя вещами сразу: мельче, плотнее по начертанию и на
 * подложке. Одной подложки мало — на светлой теме она получается
 * настолько бледной, что видна только рядом с наведённой строкой.
 *
 * Подложка задана каждой теме своя, а не выведена из цвета границы
 * через `color-mix`. Так здесь и стояло: в светлой теме граница
 * непрозрачная и разбавление давало ровно то, что нужно, а в тёмной
 * она сама полупрозрачная — и разбавление возвращало почти белую
 * полосу поверх тёмной карточки. Поймано измерением, глазами такое
 * ловится только на бою.
 *
 * Ширины колонок при этом НЕ едут: они в `rem`, то есть считаются
 * от корня, а не от кегля этой строки. Ровно ради этого случая они
 * и переведены в `rem` (см. `.lesson-mark` выше).
 */
.lessons-head-student {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  background: rgba(0, 0, 0, 0.04);
}

/* В тёмной теме «заметнее фона» означает светлее, а не темнее:
   тот же уровень поверхности, что и у шапки карточки. */
[data-bs-theme="dark"] .lessons-head-student {
  background: var(--tblr-bg-surface-tertiary);
}

/* Узкий экран: колонки поджимаются, чтобы название не осталось
 * в сто пикселей.
 *
 * Понадобилось ровно тогда, когда колонка времени выросла под подпись
 * «Длительность»: на телефоне ширины у названия и так было впритык,
 * и лишние двадцать пикселей давали лишнюю строку переноса в каждом
 * втором уроке. Мерено: название 111px до правила, 140px после —
 * при 375px экрана.
 *
 * `!important` у зазора не от лени: он приходит утилитой `gap-3`
 * прямо на строке, а утилита Bootstrap перебивает любой класс.
 */
@media (max-width: 36rem) {
  .lessons-student .list-group-item {
    gap: 0.5rem !important;
  }
  /* 2.5rem — всё ещё под «12.10», самый широкий реальный номер:
     цифры табличные, и он занимает 33px при 37px колонки. Ужимать
     дальше нельзя, номер полез бы на тип урока. */
  .lessons-student .lesson-num {
    width: 2.5rem;
  }
}

/* Числовые колонки внутри заголовка — кеглем заголовка, а не своим.
   Иначе «№» и «Длительность» крупнее «Названия» в той же строке,
   и подпись выглядит собранной из двух разных. */
.lessons-head-student .lesson-num,
.lessons-head-student .lesson-time-mark {
  font-size: inherit;
}

/* Кнопка-значок не должна быть меньше пальца. 2.25rem — нижняя граница
   удобного нажатия; без неё ряд из пяти значков превращается в игру
   «попади в нужный», а промах здесь удаляет урок. */
.btn-icon.btn-sm {
  min-width: 2.25rem;
  min-height: 2.25rem;
}

/* Уважение к настройке «меньше движения» — не украшение, а условие
   доступности: анимация мешает людям с вестибулярными нарушениями. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ------------------------- полоса прокрутки в боковой панели
 *
 * Tabler задаёт панели `overflow-y: scroll` — ЖЁСТКО, а не `auto`.
 * Windows на такое рисует полосу со стрелками вверх и вниз всегда,
 * даже когда крутить нечего, и в тёмной панели она читается как
 * элемент управления. Владелец 24.08 спросил, что это и зачем.
 *
 * `auto` вместо `scroll`: полоса появляется, только когда пунктов
 * правда больше экрана. Прятать её совсем нельзя — на маленьком
 * экране меню тогда обрезалось бы без всякого признака, что ниже
 * что-то есть.
 *
 * Оформление полосы своё: системная светлая на тёмной панели
 * выглядит заплаткой. `scrollbar-width: thin` для Firefox,
 * `::-webkit-scrollbar` для остальных — у них разные механизмы,
 * и одного правила на оба не существует.
 */
.navbar.navbar-vertical {
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}

.navbar.navbar-vertical::-webkit-scrollbar { width: 6px; }
.navbar.navbar-vertical::-webkit-scrollbar-track { background: transparent; }

.navbar.navbar-vertical::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.2);
  border-radius: 100px;
}

/* Стрелки на концах полосы — то, что владелец и просил убрать.
   В Firefox их не бывает вовсе, поэтому правило только для webkit. */
.navbar.navbar-vertical::-webkit-scrollbar-button { display: none; }

/* Ширина панели — 16rem, как в образце (там 255px при корне 16).
 *
 * У Tabler 15rem, и при нашем плавающем корне это давало 230px против
 * 255. Разница в четверть сантиметра кажется мелочью ровно до русского
 * интерфейса: «Тариф и оплата» и «Витрина и цвет» в подменю тянутся
 * длиннее английских «Billing» и «Storefront», а с весом 600 они ещё
 * и шире. Обнаружено 26.08 при сверке с образцом.
 *
 * Свёрнутая ширина не меняется: там стоят значки, и им 5rem хватает.
 */
@media (min-width: 992px) {
  .navbar-vertical.navbar-expand-lg {
    width: 16rem;
  }

  .navbar-vertical.navbar-expand-lg ~ .page-wrapper {
    margin-left: 16rem;
  }
}

/* ============ ВЫБОР ЦВЕТА: САМ ЦВЕТ И ЕСТЬ КНОПКА
 *
 * Замечание владельца и Владимира с прогона 27.08: раньше рядом стояли
 * кружок-переключатель и кружок-образец, и нажимать надо было в первый.
 * Два кружка в строке, из которых нажимается один, — это загадка,
 * а не выбор: глаз идёт к цветному, палец идёт к цветному, ничего
 * не происходит.
 *
 * Теперь переключатель СПРЯТАН, а плиткой служит сам цвет. Спрятан,
 * а не удалён: он остаётся в форме, работает с клавиатуры и читается
 * программами чтения с экрана. Убери его — и выбор станет недоступен
 * тем, кто не пользуется мышью.
 */
.color-pick {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0.375rem;
  cursor: pointer;
}

/* `clip` вместо `display:none`: скрытое через `display` поле выпадает
   из порядка обхода клавиатурой, и до выбора нельзя дотянуться Tab. */
.color-pick input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.color-pick-swatch {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 10px;
  background: var(--pick-color, var(--tblr-border-color));
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 12%);
  transition: transform var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease);
}

.color-pick:hover .color-pick-swatch { transform: scale(1.06); }

/* Выбранная плитка — кольцом ВОКРУГ, с зазором. Обводка вплотную
   сливалась бы с самим цветом у тёмных гамм; зазор цвета страницы
   отделяет кольцо от плитки и виден на любой заливке. */
.color-pick input:checked + .color-pick-swatch {
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 12%),
              0 0 0 2px var(--tblr-body-bg),
              0 0 0 4px var(--tblr-body-color);
}

/* Кольцо фокуса — отдельно от выбора: ходящий клавиатурой должен
   видеть, ГДЕ он, даже если это не выбранный вариант. */
.color-pick input:focus-visible + .color-pick-swatch {
  outline: 2px solid var(--ring);
  outline-offset: 4px;
}

.color-pick-name {
  font-size: 0.8125rem;
  color: var(--tblr-secondary);
}

.color-pick input:checked ~ .color-pick-name {
  color: var(--tblr-body-color);
  font-weight: 600;
}

/* «Без цвета» — перечёркнутая плитка. Пустая читалась бы как
   «не загрузилось», перечёркнутая — как осознанный выбор. */
.color-pick-swatch.is-empty {
  background:
    linear-gradient(to top right,
      transparent calc(50% - 1px), var(--tblr-secondary) calc(50% - 1px),
      var(--tblr-secondary) calc(50% + 1px), transparent calc(50% + 1px)),
    var(--tblr-bg-surface-secondary, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .color-pick-swatch { transition: none; }
  .color-pick:hover .color-pick-swatch { transform: none; }
}

/* Цвет плитки гаммы. Берётся из ТЕХ ЖЕ значений, что и сама гамма
 * (блоки `[data-skin=…]` выше), поэтому образец не может разойтись
 * с тем, что получится после выбора.
 *
 * Показывается вариант ТЕКУЩЕЙ темы: в тёмной школа увидит светлые
 * заливки, в светлой — глубокие. Показать один вариант в обеих значило
 * бы обещать не тот цвет половине смотрящих.
 */
.skin-blue   { --pick-color: var(--brand-blue-deep); }
.skin-teal   { --pick-color: #0d9488; }
.skin-lime   { --pick-color: var(--brand-lime); }
.skin-coral  { --pick-color: #c2410c; }
.skin-rose   { --pick-color: #be123c; }
.skin-violet { --pick-color: #7c3aed; }

[data-bs-theme="dark"] .skin-blue   { --pick-color: var(--brand-blue); }
[data-bs-theme="dark"] .skin-teal   { --pick-color: #2dd4bf; }
[data-bs-theme="dark"] .skin-coral  { --pick-color: #f0a04b; }
[data-bs-theme="dark"] .skin-rose   { --pick-color: #fb7185; }
[data-bs-theme="dark"] .skin-violet { --pick-color: #a78bfa; }

/* ─── Страницы входа и регистрации ─────────────────────────────────────
 *
 * ⚠️ КЛАССЫ ЗДЕСЬ ЛАТИНИЦЕЙ, И ЭТО НЕ СТИЛЬ, А НЕОБХОДИМОСТЬ.
 * Сначала они были написаны кириллицей — как всё в проекте, — и страница
 * приехала БЕЗ ЕДИНОГО СТИЛЯ. Причина: сервер отдаёт `Content-Type: text/css`
 * БЕЗ `charset=utf-8`, браузер разбирает файл другой кодировкой, и селектор
 * `.вход-карточка` перестаёт совпадать с классом в разметке. Раньше это
 * не всплывало: кириллица в `brand.css` жила только в комментариях,
 * а там ошибка декодирования не видна.
 *
 * Решение владельца 28.08.2026: карточка по центру на светлом фоне,
 * образец — окно регистрации Okdesk. До этого здесь были две колонки
 * с постоянной тёмной панелью на 40 % ширины.
 *
 * ⚠️ СВЕТЛО ВСЕГДА, ДАЖЕ У ТОГО, КТО ВЫБРАЛ ТЁМНУЮ ТЕМУ. Это не забывчивость
 * и не спор с настройкой: вход — единственная страница, которую человек
 * видит ДО того, как стал кем-то, а тема хранится у вошедшего. Гостю
 * досталась бы тема прошлого хозяина браузера. Прежняя вёрстка решала
 * это тем же способом с другой стороны — там панель была тёмной всегда.
 *
 * Атрибуты стоят ОБА (`data-bs-theme` и `data-skin`) на одном узле:
 * правила скина написаны как `[data-bs-theme][data-skin]`, и один
 * атрибут без второго их не включит.
 */
.auth-body { background: var(--brand-surface); }
.auth-page {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  padding: 2rem 1rem;
  /* Поверхность кабинета (#f5f5f8), а не белый: с 03.09 сайт, кабинет
   * и вход стоят на одной подложке. */
  background: var(--brand-surface);
  /* ⚠️ ЦВЕТ ТЕКСТА ЗАДАЁТСЯ ЯВНО, И БЕЗ ЭТОГО СТРАНИЦА НЕЧИТАЕМА.
   * `data-bs-theme` на обёртке меняет ПЕРЕМЕННЫЕ, но само свойство
   * `color` объявлено у `body` один раз, из темы кабинета. У того, кто
   * выбрал тёмную, светлый текст доставался светлой карточке. */
  color: var(--tblr-body-color);
  /* Выделение текста и каретка — в цветах платформы, а не браузера:
   * это самая дешёвая примета собранной, а не сложенной страницы. */
  accent-color: var(--brand-lime-deep);
  caret-color: var(--brand-ink);
}
.auth-page ::selection { background: var(--brand-lime); color: var(--brand-ink); }
/* Центрирование через `margin: auto`, а не `align-items: center`:
 * при выравнивании по центру содержимое ВЫШЕ экрана обрезается сверху. */
.auth-col {
  width: 100%;
  max-width: 27rem;
  margin: auto;
}
/* ─── Карточка ────────────────────────────────────────────────────────
 *
 * ПЕРЕКРАШЕНА 03.09.2026 ПОД ЗНАК «СТУПЕНЬ»: чернила и лайм, синего нет
 * (решение владельца). Раскладка Okdesk от 28.08 сохранена: одна карточка
 * по центру, всё в ней. Изменились цвета, знак и отклик, а не устройство.
 *
 * Белая карточка на поверхности #f5f5f8, тень подкрашена чернилами и с
 * смещением: раньше карточка была «на волос темнее» белого фона и без
 * тени, на серой подложке это перестало читаться как карточка вовсе.
 *
 * ⚠️ СВОЙ РАЗМЕР ШРИФТА В `em`, А НЕ `rem`. Кабинет сжимает текст под
 * таблицы; для страницы из шести строк это мелко («текста не видно
 * нифига», владелец 28.08). Tabler задаёт `.small`, `.form-label` и
 * `.btn` от родителя, и поднятая база тянет всё дерево. `rem` считался бы
 * от корня в 14px и сделал бы мельче. */
.auth-card {
  background: var(--tblr-bg-surface, #fff);
  border: 1px solid var(--tblr-border-color);
  border-radius: var(--tblr-border-radius-xl);
  padding: 2.5rem 2rem;
  box-shadow: 0 1px 2px rgba(10, 15, 23, 0.04), 0 16px 40px -20px rgba(10, 15, 23, 0.18);
  font-size: 1.15em;
  /* Приглушённый текст темнее общего: #6b7280 на белом даёт 4,8 при
   * 12 пикселях, это впритык. #545a6b — 6,9. */
  --tblr-secondary: #545a6b;
  --tblr-secondary-rgb: 84, 90, 107;
}
.auth-card .text-secondary { color: var(--tblr-secondary) !important; }
/* ⚠️ ЭТИМ ТРЁМ РАЗМЕР ЗАДАЁТСЯ ОТДЕЛЬНО: Tabler объявил их в `rem`,
 * то есть от КОРНЯ, и подъём базы карточки их не трогает. */
.auth-card .form-label,
.auth-card .form-check-label { font-size: 0.9em; }
.auth-card .form-control,
.auth-card .btn:not(.social-btn) { font-size: 0.94em; }
.auth-card .hr-text { font-size: 0.8em; color: var(--tblr-secondary); }
/* «Забыли пароль?» живёт внутри подписи и считается от неё. */
.auth-card .form-label .small,
.auth-card .form-label a { font-size: 1em; }

/* ─── Ссылки и кнопка: платформа лаймовая, школа своим цветом ─────────
 *
 * Атрибут `data-skin` на `.auth-page` ставит `auth_base.html`: скин
 * ШКОЛЫ, если вход идёт в известную школу, и `lime` для платформы
 * (lk.anyskills.ru/login/ один на всех, там школы ещё нет). Кнопка
 * берёт цвет из переменных скина, как весь кабинет.
 *
 * ⚠️ ЛАЙМ ТЕКСТОМ НА БЕЛОМ НЕ ЧИТАЕТСЯ (1,4). Поэтому ссылки на лаймовой
 * платформе — тёмный лайм #567312 (--brand-lime-deep, 5,3 на белом),
 * тот же, что на сайте. У других скинов ссылки остаются синими
 * кабинета: они не сталкиваются с кнопкой того же цвета. */
.auth-page[data-skin="lime"] {
  --tblr-link-color: var(--brand-lime-deep);
  --tblr-link-color-rgb: 86, 115, 18;
  --tblr-link-hover-color: #3f5510;
}
.auth-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  font-weight: 800;
  font-size: 1.375rem;
  letter-spacing: -0.02em;
  color: var(--brand-ink);
  margin-bottom: 1.5rem;
  text-decoration: none;
}
.auth-brand svg { width: 2rem; height: 2rem; flex: none; }
.auth-card h2 {
  font-size: 1.75rem;
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
}
/* Поля и кнопки: скругление 10 кабинета, высота 3rem. Раньше стояло
 * 0,75rem «как на пороге»; порог теперь не отличается от дома. */
.auth-card .form-control,
.auth-card .btn {
  border-radius: var(--tblr-border-radius-lg);
  min-height: 3rem;
}
.auth-card .form-control {
  background: var(--brand-surface);
  /* Рамка на #f5f5f8 к белому даёт 1,05: поле держится заливкой
   * и границей вместе, граница 3,3 к белой карточке. */
  border-color: #7d86a3;
  transition: border-color 160ms cubic-bezier(0.23, 1, 0.32, 1),
              box-shadow 160ms cubic-bezier(0.23, 1, 0.32, 1),
              background-color 160ms ease;
}
.auth-card .form-control::placeholder { color: #6f7688; opacity: 1; }
.auth-card .form-control:focus {
  background: #fff;
  border-color: var(--brand-ink);
  box-shadow: 0 0 0 3px rgba(193, 247, 66, 0.55);
}
.auth-card .form-label { font-weight: 600; margin-bottom: 0.375rem; }
/* Главная кнопка: 160 мс, только transform, лёгкое сжатие на нажатии —
 * интерфейс слышит руку. Тень не нужна: лайм и так самый громкий
 * предмет на карточке. */
.auth-card .btn-primary {
  font-weight: 700;
  box-shadow: none;
  transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1),
              background-color 160ms ease, border-color 160ms ease;
}
.auth-card .btn-primary:active { transform: scale(0.97); }
.auth-card .btn-primary:focus-visible {
  outline: 2px solid var(--brand-ink);
  outline-offset: 2px;
  box-shadow: none;
}
/* Ошибка входа: текст названием проблемы, без красной плиты во всю
 * ширину. Плита звучала как авария, а это опечатка в пароле. */
.auth-card .alert-danger {
  background: #fff3f2;
  border: 1px solid #f0c2be;
  color: #8a2a25;
  border-radius: var(--tblr-border-radius);
}
@media (prefers-reduced-motion: reduce) {
  .auth-card .form-control, .auth-card .btn-primary { transition: none; }
  .auth-card .btn-primary:active { transform: none; }
}

/* ─── Кнопки соцсетей: ряд квадратов в фирменных цветах ───────────────
 * Раскладка из Tikkee (решение владельца 28.08). Цвета фирменные, а не
 * наши: #fc3f1d Яндекса, #07f VK, #229ED9 Telegram — своя палитра здесь
 * мешала бы узнаванию, ради которого значок и ставят. */
.social { margin-top: 1.25rem; }
.social-label,
.social-note { text-align: center; color: var(--tblr-secondary); }
.social-label { font-size: 0.85em; margin-bottom: 0.625rem; }
.social-row { display: flex; justify-content: center; gap: 0.75rem; }
.social-btn {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  border: 0;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-size: 17px;
  text-decoration: none;
  cursor: pointer;
  transition: transform 160ms cubic-bezier(0.23, 1, 0.32, 1), opacity 160ms ease;
}
.social-btn:hover, .social-btn:focus { color: #fff; opacity: 0.88; }
.social-btn:active { transform: scale(0.95); }
.social-btn--yandex   { background: #fc3f1d; }
.social-btn--vk       { background: #07f; }
.social-btn--telegram { background: #229ed9; }
.social-note { font-size: 0.8em; margin-top: 0.625rem; }
/* Подпись под карточкой — вторая дверь. Своей плашкой, чтобы довести
 * до неё взгляд; без рамки и тени, это не вторая карточка. */
.auth-below {
  margin-top: 1rem;
  text-align: center;
  background: #eceef3;
  border-radius: var(--tblr-border-radius-lg);
  padding: 1rem;
  font-size: 1.05em;
}
.auth-platform {
  margin-top: 1rem;
  text-align: center;
  font-size: 0.8125rem;
  color: #545a6b;
}
@media (max-width: 30rem) {
  .auth-page { padding: 1rem 0.75rem; }
  .auth-card { padding: 1.75rem 1.25rem; }
}
/* Наведение на строку урока в правке курса — замечание владельца 27.08.
 *
 * Тот же приём, что в панели навигации: подложка показывает ГРАНИЦЫ
 * нажимаемого. В списке из шестисот строк курсор-«рука» отвечает лишь
 * «тут что-то нажимается», а подложка — «нажимается вот это, отсюда
 * досюда», и попадать по строке становится не в пример легче.
 *
 * Нейтральная подложка, а не фирменный цвет: фирменным в этом списке
 * уже говорят кнопки и отметки, и цветная полоса на каждое движение
 * мыши спорила бы с ними.
 */
.lesson-row.list-group-item {
  transition: background-color var(--duration) var(--ease);
}

.lesson-row.list-group-item:hover {
  background-color: var(--tblr-bg-surface-secondary, rgba(0, 0, 0, 0.04));
}

/* Панель действий в редакторе урока: отделена линией от заголовка —
   это граница между «где я» и «что я делаю». */
.editor-toolbar {
  padding-top: 0.75rem;
  border-top: 1px solid var(--tblr-border-color);
}

/* ---------------- выпадающие меню внутри прокручиваемых таблиц
 *
 * Замечание владельца 28.08: «три точки» в строке ученика раскрывались
 * ВНУТРЬ таблицы, и список обрезало с полосой прокрутки — пункты были,
 * а видно их не было.
 *
 * Причина в самой природе `.table-responsive`: ему положен
 * `overflow: auto` ради горизонтальной прокрутки на узких экранах,
 * а overflow режет ВСЁ выходящее за край, включая абсолютно
 * спозиционированное меню.
 *
 * Лечится на время открытого меню: пока внутри есть `.show`, контейнер
 * перестаёт резать. Прокрутка при этом не страдает — прокручивать
 * и держать меню открытым одновременно всё равно нельзя, меню
 * закрывается первым же нажатием.
 *
 * `:has()` здесь не роскошь, а способ НЕ трогать чужие шаблоны:
 * страница учеников сейчас в работе у другой сессии, и правка одним
 * общим правилом в своём файле не создаёт конфликта. Поддержка `:has`
 * во всех живых браузерах с 2023 года; в ископаемом браузере правило
 * молча не применится, и станет ровно как было — обрезано, но не
 * сломано.
 */
.table-responsive:has(.dropdown-menu.show),
.card:has(> .table-responsive .dropdown-menu.show) {
  overflow: visible;
}

/* --------------------- редактор урока: одна колонка содержимого
 *
 * Замечание владельца 28.08: шапка стояла левее, текст урока — по
 * центру карточки, и глаза разъезжались. Причина была В ПОСТРОЕНИИ:
 * шапка жила над всем рядом «карточка + рейка», а Editor.js центрирует
 * текст внутри карточки — их центры не совпадали, и отступами это
 * не чинилось. Теперь шапка перенесена внутрь колонки редактора,
 * и обе части центрируются ОДНОЙ парой правил ниже: разъехаться им
 * больше не из чего.
 *
 * Заодно зона текста расширена: у Editor.js вшито 650px, для лекции
 * с таблицами и кодом это мало. 46rem — шире почти на треть, но ещё
 * в пределах читаемой строки.
 */
/* Editor.js вшивает блокам собственные 650px — УЖЕ, чем полотно
 * `#editor`, которому шаблон редактора задаёт 100ch, ту же меру, что
 * у `.lesson-content` ученика («правишь в ту же ширину, что смотришь»,
 * см. комментарий в lesson_editor.html). Снимаем внутренний предел:
 * ширину задаёт полотно, и мера остаётся ОДНА. Свою вторую меру здесь
 * заводить нельзя — она молча разойдётся с шириной у ученика.
 */
.ce-block__content,
.ce-toolbar__content {
  max-width: none;
}

/* Шапка редактора — тем же 100ch и тем же центрированием, что полотно:
   одна колонка, разъезжаться не из чего. */
.editor-head {
  max-width: 100ch;
  margin-inline: auto;
}

/* Шеврон сворачивания темы. Свёрнутая тема смотрит стрелкой вправо —
   тот же язык, что у раскрывашек всюду; значок один, его только крутим:
   второй значок был бы вторым местом, которое забудут обновить. */
.module-collapse svg {
  transition: transform 0.15s ease;
}

.module-collapse.is-collapsed svg {
  transform: rotate(-90deg);
}

/* Название урока в шапке редактора — поле, стилизованное под заголовок:
   рамка появляется на наведении и в фокусе, до того шапка читается
   как заголовок, а не как форма. Размер — page-title Таблера. */
.editor-title-input {
  border-color: transparent;
  background: transparent;
  font-size: 1.25rem;
  font-weight: var(--tblr-font-weight-bold, 600);
  padding-left: 0.5rem;
}

.editor-title-input:hover,
.editor-title-input:focus {
  border-color: var(--tblr-border-color);
  background: var(--tblr-bg-forms, var(--tblr-body-bg));
}

/* Карточка вебинара у ученика. Цвет — фирменный, а не «синий эфир»:
   в школе с бордовой гаммой синяя плашка читалась бы чужой вставкой. */
.webinar-card {
  border-left: 3px solid var(--tblr-primary);
}

.webinar-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  color: var(--tblr-primary);
  background: var(--tblr-primary-lt, rgba(var(--tblr-primary-rgb), 0.1));
}

/* Обложка курса в шапке его страницы. Не во всю ширину: страница курса
   это рабочий стол — список тем и кнопки, — и баннер отодвинул бы вниз
   то, ради чего сюда приходят. Мера 10rem даёт узнать картинку, не
   споря с заголовком. */
.course-page-cover {
  display: block;
  width: 10rem;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 8px;
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
}

/* Пустое место вместо обложки — только тому, кто может её загрузить.
   Пунктир, а не сплошная рамка: сплошная читается как настоящая
   картинка, которая не загрузилась. */
.course-page-cover-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--tblr-border-color);
  background: none;
  color: var(--tblr-secondary);
  font-size: 0.75rem;
  text-decoration: none;
}

.course-page-cover-empty:hover {
  border-color: var(--tblr-primary);
  color: var(--tblr-primary);
}

/* Кнопка прямо в шапке страницы не растягивается на всю ширину.
 *
 * Замечание владельца 31.08: «Мои курсы» на странице курса ученика
 * выглядит нелепо — широкая пустая плашка с крошечной надписью
 * посередине.
 *
 * Причина не в кнопке, а в контейнере: `.page-header` у Tabler —
 * колоночный флекс, и любой прямой ребёнок растягивается по ширине
 * (измерено: 946px при надписи в 80). Сама кнопка при этом центрирует
 * свой текст — отсюда и плашка.
 *
 * Правим ЗДЕСЬ, а не в шаблоне: так чинится весь класс ошибки, а не
 * один экран. Кнопка в шапке страницы не бывает во всю ширину нигде.
 */
.page-header > .btn {
  align-self: flex-start;
}

/* Подпись поля отличается от значения — а то они сливались.
 *
 * Замечание владельца 31.08: «пункты меню сливаются с их параметрами».
 * Измерено на боевой теме: подпись 13.95px/500 цветом #e8eaf0 и
 * значение в поле 13.02px/400 тем же цветом #e8eaf0. Разница —
 * меньше пикселя кегля и один шаг веса при ОДИНАКОВОМ цвете; глазу
 * разделять нечем.
 *
 * Разводим тремя признаками сразу: подпись мельче, плотнее и тише
 * значения. Тише — цветом `--tblr-secondary`, который в теме уже
 * есть и проверен: 10.26:1 на тёмной карточке и 4.84:1 на светлой,
 * то есть выше порога 4.5:1 в обеих темах.
 *
 * От подсказки под полем (тот же приглушённый цвет) подпись отличают
 * вес и кегль: подсказка — обычный текст, подпись — плотная и мелкая.
 * Прописных не вводим: в форме из десяти полей они кричат.
 */
.form-label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--tblr-secondary);
  margin-bottom: 0.3rem;
}

/* Подпись у флажка — не подпись поля, а сам выбор («Опубликован»),
   и приглушать её нельзя: это то, что человек читает и отмечает. */
.form-check .form-label,
.form-check-label {
  font-size: inherit;
  font-weight: 400;
  color: var(--tblr-body-color);
}

/* Обложка курса в кабинете ученика.
 *
 * Просьба владельца 31.08: курс должен узнаваться картинкой. Размеров
 * два — миниатюра в списке и покрупнее в шапке страницы курса, — но
 * пропорция и скругление общие: ряд карточек обязан стоять ровно, что
 * бы школа ни загрузила.
 *
 * Фон под прозрачным PNG и на время загрузки — тот же приём, что
 * у `.course-cover` на витрине: без него в тёмной теме на месте
 * картинки зияет дыра цвета страницы.
 */
.course-mini-cover,
.course-page-cover-student {
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
  flex: none;
  background: var(--tblr-bg-surface-secondary, var(--tblr-border-color));
}

.course-mini-cover {
  width: 6rem;
}

.course-page-cover-student {
  width: 12rem;
  border-radius: 10px;
}

/* На узком экране крупная обложка ужимается: 12rem рядом с названием
   на телефоне не оставляют заголовку места, и он ломается по слогам. */
@media (max-width: 575.98px) {
  .course-page-cover-student {
    width: 100%;
    max-width: 20rem;
  }
}

/* Плитка курсов. Второй вид списка — просьба владельца 01.09.
 *
 * Полоса цвета курса сверху, как у карточки ученика: метка курса должна
 * работать одинаково в обоих кабинетах, иначе она перестаёт быть меткой.
 */
.course-tile {
  border-top: 3px solid var(--course-color, var(--tblr-border-color));
  overflow: hidden;
}

/* Место обложки у курса без картинки. Не пустая рамка и не серый
   прямоугольник: полоса цвета курса отвечает «какой это курс» хотя бы
   цветом, а плитки в ряду держат одну высоту. */
.course-tile-blank {
  aspect-ratio: 16 / 9;
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--course-color, var(--tblr-primary)) 22%, transparent),
    transparent 70%
  );
}

/* Разрядка между цифрами сводки: `gap-x-3` у Tabler нет, а перечисление
   через точку без зазора слипается. */
.course-tile .gap-x-3 {
  column-gap: 0.75rem;
}

.course-tile .gap-y-1 {
  row-gap: 0.25rem;
}

/* Страница урока у ученика: шапка и рельс не уезжают при прокрутке.
 *
 * Просьба владельца 01.09 — «сделать как в редакторе». Причина та же,
 * что была у редактора: урок читают в несколько экранов, и на середине
 * человек перестаёт понимать, где он и что дальше; название темы,
 * номер урока и навигация остаются наверху, куда надо возвращаться.
 *
 * Липнут ПОД шапкой кабинета, а не к нулю: та сама липкая (см.
 * `.navbar.navbar-expand-md` выше), и прилипшее к нулю уехало бы под
 * неё. Высоту меряет `core/_измерить_шапку.html` — числом её вписывать
 * нельзя, у кабинета есть масштаб интерфейса.
 *
 * `z-index` ниже шапки кабинета (1020) и выше содержимого: шапка урока
 * обязана проходить ПОД ней, а не сталкиваться с ней.
 */
.lesson-sticky {
  position: sticky;
  top: var(--шапка-кабинета, 3.5rem);
  z-index: 1010;
  /* Фон обязателен: без него текст урока проступает сквозь шапку. */
  background: var(--tblr-body-bg);
  padding-top: 0.75rem;
  padding-bottom: 0.5rem;
  /* Край снизу — тот же приём, что в редакторе: цвет линии подмешан
     к цвету ТЕКСТА, потому что штатной рамки на этом фоне не видно
     (измерено в светлой теме: шапка и полотно отличаются на 1,09).
     Линия задаёт границу, тень показывает, что это слой выше. */
  border-bottom: 1px solid
    color-mix(in srgb, var(--tblr-border-color) 60%, var(--tblr-body-color));
  box-shadow: 0 6px 12px -8px rgb(0 0 0 / 0.35);
  padding-inline: 1rem;
  margin-inline: -1rem;
}

/* Рельс со своей прокруткой: в теме бывает по двадцать уроков, и без
   неё нижние недостижимы — прилипший блок обрезается окном, а не
   продолжается за ним. */
.lesson-rail {
  position: sticky;
  top: var(--шапка-кабинета, 3.5rem);
  max-height: calc(100vh - var(--шапка-кабинета, 3.5rem) - 1.5rem);
  overflow-y: auto;
}

/* Колонка чтения — той же меры, что полотно редактора.
 *
 * Было 46rem (~736px) и зажимало текст урока, рассчитанный на 100ch
 * (`lesson.css`). Методист правил в одной ширине, ученик читал
 * в другой — переносы расходились, и предпросмотру нельзя было верить.
 *
 * Запас в 3rem — на поля карточки: без него текст внутри сузился бы
 * обратно, и вся правка свелась бы к нулю. */
.lesson-column {
  min-width: 0;
  max-width: calc(100ch + 3rem);
}

/* ⚠️ ОСНОВА НУЛЕВАЯ, и это не украшение — без неё рельс уезжает ВНИЗ.
 *
 * Замечание владельца 03.09: «навигация по курсу, которая должна была
 * быть справа, уехала под текст урока». Сломал это я же, расширив
 * колонку чтения: у флекса с переносом ширина строки считается по
 * ИСХОДНЫМ размерам детей, а не по тому, сколько они готовы уступить.
 * Измерено: ряд 1139px, колонка просила 999, рельс 297, зазор 24 —
 * 1320 в 1139 не влезает, и рельс переносится на новую строку.
 *
 * `flex-basis: 0` означает «начинай с нуля и займи, сколько осталось
 * после рельса». Тогда сумма не превышает строку никогда, а ширину
 * текста всё равно держит `max-width` выше: на широком мониторе
 * колонка дорастает до той же меры, что полотно редактора, на узком —
 * просто отдаёт место рельсу.
 *
 * Только с 992px: ниже рельс намеренно уходит под урок, и там колонке
 * нужна полная строка, а не остаток. */
@media (min-width: 1200px) {
  .lesson-column {
    flex: 1 1 0;
  }
}

/* На узком экране рельс уходит ПОД урок, и липнуть ему не к чему:
   прилипший он занял бы пол-экрана над текстом. Шапку там тоже
   отпускаем — липкая шапка на телефоне съедает половину видимого
   урока. То же правило и по той же причине, что в редакторе. */
@media (max-width: 1199.98px) {
  .lesson-sticky,
  .lesson-rail {
    position: static;
    max-height: none;
    overflow: visible;
    padding-top: 0;
  }
}
