/* ==========================================================================
   Pootymoty — таблица стилей

   Как здесь всё устроено:
   вся настройка вида собрана в секции 1 (токены). Чтобы поменять цвет,
   отступ, скругление или ширину колонки — правьте переменную там, а не
   отдельные правила ниже. Компоненты берут значения только из токенов,
   поэтому одна правка меняет вид согласованно во всём интерфейсе.

   Оглавление
     1. Токены: цвет, отступы, типографика, размеры
     2. Сброс и базовые элементы
     3. Типографика
     4. Раскладка: контейнеры, секции
     5. Шапка, навигация, мобильное меню
     6. Кнопки
     7. Формы
     8. Таблицы
     9. Карточки, списки, плитки
    10. Компоненты: флеш, модальные окна, баннер cookie, пагинация
    11. Подвал
    12. Утилиты и совместимость
   ========================================================================== */


/* ==========================================================================
   0. ШРИФТ ЗАГОЛОВКОВ
   ==========================================================================
   Manrope, лицензия SIL Open Font License 1.1 (текст — static/fonts/OFL.txt).

   Файлы лежат у нас, а не грузятся со стороннего адреса. Причин две.
   Внешний шрифт — это обращение браузера посетителя на чужой сервер, а
   значит его адрес и страница уходят третьему лицу; при российской
   аудитории и после ухода с сервисов Google это лишнее. И надёжность:
   недоступный чужой сервер задерживает отрисовку текста на всём сайте.

   Разбивка по алфавитам не для красоты: браузер скачивает только тот
   кусок, буквы которого встретились на странице. Русской странице
   греческий кусок нужен ровно из-за π в названии — 9 КБ вместо всего
   набора. font-display: swap — текст виден сразу системным шрифтом и
   подменяется, когда шрифт дойдёт; иначе заголовки пустуют.

   Файл переменный: одно начертание покрывает вес от 500 до 800, поэтому
   отдельных файлов на полужирный и жирный не нужно. */
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url('../fonts/manrope-cyrillic.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: 500 800;
    font-display: swap;
    src: url('../fonts/manrope-cyrillic-ext.woff2') format('woff2');
    unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
    /* Греческий: буква π в названии школы. */
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url('../fonts/manrope-greek.woff2') format('woff2');
    unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url('../fonts/manrope-latin.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;
}
@font-face {
    font-family: 'Manrope';
    font-style: normal;
    font-weight: 500 800;
    font-display: swap;
    src: url('../fonts/manrope-latin-ext.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;
}


/* ==========================================================================
   1. ТОКЕНЫ
   ========================================================================== */
:root {
    /* --- Цвет: светлая тема «Переплёт и бумага» --------------------------
       Ни одного значения с альфа-каналом: полупрозрачный цвет зависит от
       того, что под ним, и контраст становится непредсказуемым. Каждая
       пара проверена по WCAG, минимум 5.9. */
    --background: #F5F0E6;      /* фон страницы — кремовая бумага */
    --surface: #FFFBF2;         /* карточки, таблицы, поля ввода */
    --surface-2: #EDE5D6;       /* приглушённые блоки, вторичные кнопки */
    --surface-3: #E3D9C6;       /* наведение на приглушённое */
    --line: #E0D8C9;            /* границы и разделители */
    /* Граница поля ввода и других элементов управления. Значение
       подобрано под норму WCAG 1.4.11: контраст к поверхности не ниже
       3:1, иначе не видно, где начинается поле. Прежний #CFC3AC давал
       1.69 — рамка сливалась с карточкой. */
    --line-strong: #8E7E67;

    --text: #2A211C;            /* основной текст */
    --text-muted: #5E5148;      /* подписи, вторичное */
    --text-on-accent: #FFFFFF;  /* текст на заливке акцентом */
    --text-on-dark: #F7F0E8;    /* текст на тёмной шапке */

    --accent: #7A2E1E;          /* кирпичный корешок учебника */
    --accent-hover: #632518;
    --accent-soft: #F2E4DC;     /* фон подсветки в тон акценту */

    --danger: #A32118;
    --danger-hover: #871B14;
    --danger-soft: #F8E3E0;
    --success: #3A6B3C;
    --success-soft: #E4EDDF;
    --warning: #8A5209;
    --warning-soft: #F7EBD8;
    --info: #2A4E8F;
    --info-soft: #E2E8F5;

    --header-bg: #4A2018;       /* шапка и подвал */

    --shadow-color: rgba(42, 33, 28, .12);
    --shadow-sm: 0 1px 2px rgba(42, 33, 28, .06);
    --shadow: 0 1px 2px rgba(42, 33, 28, .06), 0 4px 12px rgba(42, 33, 28, .08);
    --shadow-lg: 0 8px 32px rgba(42, 33, 28, .16);

    /* --- Отступы: шаг 4px ------------------------------------------------ */
    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 24px;
    --sp-6: 32px;
    --sp-7: 48px;
    --sp-8: 64px;

    /* --- Размеры --------------------------------------------------------- */
    --radius-sm: 6px;
    --radius: 8px;
    --radius-lg: 12px;
    --radius-pill: 999px;

    --header-h: 84px;           /* высота шапки; от неё считается меню */
    --content-max: 1200px;      /* предельная ширина содержимого */
    --reading-max: 68ch;        /* комфортная длина строки текста */
    --field-max: 34rem;         /* поля ввода не тянутся на всю ширину */

    /* --- Типографика ------------------------------------------------------ */
    --font-body: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    /* Заголовки. Была Georgia с засечками — от неё оставались острые
       уголки на каждой букве, и текст выглядел строже, чем нужно школе.
       Manrope без засечек, с округлыми формами; запасные — системные
       шрифты того же склада, чтобы до загрузки страница не прыгала. */
    --font-head: 'Manrope', "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

    --fs-xs: 13px;
    --fs-sm: 14px;
    --fs-base: 16px;
    --fs-lg: 18px;
    --fs-h3: 20px;
    --fs-h2: 25px;
    --fs-h1: 32px;

    --lh-tight: 1.25;
    --lh-base: 1.6;

    --transition: .15s ease;
}

/* --- Цвет: тёмная тема ----------------------------------------------------
   Задана дважды: по настройке системы и по явному выбору пользователя.
   :not([data-theme="light"]) нужен, чтобы выбор светлой темы победил
   тёмную настройку ОС. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --background: #1A1512;
        --surface: #241D18;
        --surface-2: #322820;
        --surface-3: #3E3228;
        --line: #3B2F27;
        --line-strong: #877162;

        --text: #F2E9E0;
        --text-muted: #B5A597;
        --text-on-accent: #1A1512;
        --text-on-dark: #F2E9E0;

        --accent: #E39C86;
        --accent-hover: #EDB39F;
        --accent-soft: #33241E;

        --danger: #F08A7C;
        --danger-hover: #F5A296;
        --danger-soft: #3A241F;
        --success: #8FC490;
        --success-soft: #23301F;
        --warning: #E0A951;
        --warning-soft: #332818;
        --info: #93A4F2;
        --info-soft: #22253A;

        --header-bg: #241D18;

        --shadow-color: rgba(0, 0, 0, .5);
        --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
        --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 4px 12px rgba(0, 0, 0, .35);
        --shadow-lg: 0 8px 32px rgba(0, 0, 0, .5);
    }
}

:root[data-theme="dark"] {
    --background: #1A1512;
    --surface: #241D18;
    --surface-2: #322820;
    --surface-3: #3E3228;
    --line: #3B2F27;
    --line-strong: #877162;

    --text: #F2E9E0;
    --text-muted: #B5A597;
    --text-on-accent: #1A1512;
    --text-on-dark: #F2E9E0;

    --accent: #E39C86;
    --accent-hover: #EDB39F;
    --accent-soft: #33241E;

    --danger: #F08A7C;
    --danger-hover: #F5A296;
    --danger-soft: #3A241F;
    --success: #8FC490;
    --success-soft: #23301F;
    --warning: #E0A951;
    --warning-soft: #332818;
    --info: #93A4F2;
    --info-soft: #22253A;

    --header-bg: #241D18;

    --shadow-color: rgba(0, 0, 0, .5);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 4px 12px rgba(0, 0, 0, .35);
    --shadow-lg: 0 8px 32px rgba(0, 0, 0, .5);
}

/* Переменные Bootstrap привязаны к нашим токенам.
   Bootstrap задаёт свои цвета один раз и о смене темы не знает: его
   переменные так и остаются светлыми. Компоненты, которые их читают
   (подсказки в полях, .text-secondary, границы таблиц), выпадали из
   тёмной темы чёрным текстом. Значение подставляется в момент
   использования, поэтому одного блока хватает на обе темы. */
:root {
    --bs-body-color: var(--text);
    --bs-body-bg: var(--background);
    --bs-secondary-color: var(--text-muted);
    --bs-secondary-bg: var(--surface-2);
    --bs-tertiary-bg: var(--surface-2);
    --bs-border-color: var(--line);
    --bs-emphasis-color: var(--text);
    --bs-link-color: var(--accent);
    --bs-link-hover-color: var(--accent-hover);
}


/* ==========================================================================
   2. СБРОС И БАЗОВЫЕ ЭЛЕМЕНТЫ
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; padding: 0; }

html {
    /* Якорные переходы не заезжают под закреплённую шапку */
    scroll-padding-top: calc(var(--header-h) + var(--sp-4));
    -webkit-text-size-adjust: 100%;
}

body {
    font-family: var(--font-body);
    font-size: var(--fs-base);
    line-height: var(--lh-base);
    background-color: var(--background);
    color: var(--text);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    /* Долгое нажатие по ссылке-кнопке на телефоне вызывало системное меню
       и позволяло перетащить её в поисковую строку. Здесь это отключается
       для интерфейсных элементов; обычный текст остаётся выделяемым. */
    -webkit-tap-highlight-color: transparent;
}

main {
    /* Сетка в одну колонку нужна ради order: кнопка «Назад» стоит в
       разметке последней, а показывается первой.

       minmax(0, 1fr) здесь обязателен. По умолчанию колонка не сжимается
       ниже ширины своего содержимого, и широкая таблица или карусель
       растягивают всю страницу: на телефоне появлялась горизонтальная
       прокрутка на 800 с лишним пикселей, а заголовки уезжали за край.
       Ноль в minmax снимает этот минимум, и внутренние блоки со своей
       прокруткой снова умещаются в экран. */
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    flex: 1;
    width: 100%;
    padding: var(--sp-5) var(--sp-4);
}
/* Снятие автоматического минимума у блоков внутри main.
   Мало задать колонку в minmax(0, 1fr): у элементов сетки собственный
   минимальный размер считается по содержимому, и блок переполняет свою
   колонку. Из-за этого на телефоне появлялась горизонтальная прокрутка —
   на витрине преподавателей до 800 лишних пикселей, — а заголовки и
   кнопки уезжали за край экрана. */
main > * { min-width: 0; }
/* Центрирование блоков берёт на себя сетка.

   У .content-container и .section стоит margin: 0 auto. В обычной
   раскладке это центрирует блок, а в сетке боковые авто-поля означают
   другое: блок перестаёт занимать колонку и сжимается по содержимому.
   Широкая карусель растягивала его до предела max-width и выносила за
   экран вместе с заголовками. */
main > .content-container,
main > .container,
main > .section {
    justify-self: center;
    width: 100%;
    margin-inline: 0;
}

img, svg, video { max-width: 100%; height: auto; }

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

/* Маркеры снимаются только у служебных списков — меню, панели, карточки.
   В тексте страницы список должен оставаться списком: раньше здесь стояло
   глобальное list-style:none, и перечисления на «Главной», «О нас» и
   «Как всё устроено» теряли и маркеры, и отступ — текст «съезжал». */
nav ul, nav ol,
.navbar ul, .dropdown-menu, .navbar-submenu,
footer ul, .pagination, .list-unstyled,
.knowledge-list ul, .row-actions, .cookie-banner__actions,
.desktop-menu, .tab-list, .breadcrumb,
.link-list, .lessons-list, .news-list, .card ul, .flash-container {
    list-style: none;
    padding-left: 0;
    margin-left: 0;
}

.text-box ul, .text-box ol,
.content-container > ul, .content-container > ol,
.section-content ul, .section-content ol,
.prose ul, .prose ol {
    list-style: disc outside;
    padding-left: var(--sp-5);
    margin-bottom: var(--sp-3);
}
.text-box ol, .content-container > ol,
.section-content ol, .prose ol { list-style: decimal outside; }
.text-box li, .content-container > ul > li,
.section-content li, .prose li { margin-bottom: var(--sp-2); }
.knowledge-list ul li { margin-bottom: var(--sp-2); }

hr {
    border: none;
    border-top: 1px solid var(--line);
    margin: var(--sp-5) 0;
}

code, pre, kbd { font-family: var(--font-mono); font-size: .9em; }

/* Видимый фокус при работе с клавиатуры.
   Раньше стоял outline:none !important — при навигации табом было не
   понять, где находишься. :focus-visible не мешает тем, кто с мышью. */
:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

::selection { background: var(--accent-soft); color: var(--text); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}


/* ==========================================================================
   3. ТИПОГРАФИКА
   ========================================================================== */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-head);
    line-height: var(--lh-tight);
    color: var(--text);
    text-wrap: balance;
    /* Крупный кегль всегда выглядит разреженным: промежутки между
       буквами растут вместе с размером, а глаз ждёт их прежними. Лёгкое
       поджатие возвращает заголовку плотность слова. */
    letter-spacing: -.01em;
}

h1 { font-size: var(--fs-h1); margin-bottom: var(--sp-4); }
h2 { font-size: var(--fs-h2); margin-bottom: var(--sp-3); }
h3 { font-size: var(--fs-h3); margin-bottom: var(--sp-2); }
h4 { font-size: var(--fs-lg); margin-bottom: var(--sp-2); }

/* Заголовки внутри контентных блоков — акцентным цветом */
.content-container h1,
.container h1,
.section h2 {
    color: var(--accent);
}

p { margin-bottom: var(--sp-3); }
p:last-child { margin-bottom: 0; }

small, .small { font-size: var(--fs-sm); }
strong, b { font-weight: 600; }


/* ==========================================================================
   4. РАСКЛАДКА
   ========================================================================== */
.content-container,
.container {
    max-width: var(--content-max);
    margin: 0 auto var(--sp-5);
    padding: var(--sp-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.section {
    max-width: var(--content-max);
    margin: 0 auto var(--sp-5);
    padding: var(--sp-5);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

/* Вложенная рамка — прозрачная.
   Раньше .content-container, .section и карточка внутри неё рисовали по
   собственной рамке с фоном и тенью, и получалась коробка в коробке в
   коробке: особенно заметно на уведомлениях и в списках репетиторов.
   Рамку держит внешний блок и конечная карточка с содержимым; всё, что
   между ними, служит только раскладкой и рисовать себя не должно. */
.content-container .section,
.container .section,
.section .section,
.content-container .content-container,
.card .card,
/* Таблица внутри карточки: рамку уже держит карточка. Полосу прокрутки
   .table-wrap при этом сохраняет — за неё отвечает overflow, не рамка. */
.card .table-wrap,
.tutor-card .table-wrap,
.student-card .table-wrap {
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    border-radius: 0;
}

/* Раз рамок между блоками больше нет, разделяет их воздух, а соседние
   секции — тонкая линия. Иначе содержимое сливается в сплошной текст. */
.content-container > .section + .section,
.container > .section + .section {
    margin-top: var(--sp-5);
    padding-top: var(--sp-5);
    border-top: 1px solid var(--line);
}

.section-content {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    flex-wrap: wrap;
}

.text-box, .image-box { flex: 1 1 300px; min-width: 0; }
.image-box { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }

/* Размеры картинок.
   Раньше каждая страница держала свой <style>, и один и тот же класс
   .section-image означал разное: 100% на «Главной» и «О нас», 12% на
   «Как всё устроено», 10% на «Наших преподавателях». После объединения
   стилей победило 100%, и мелкие значки развернулись во всю ширину.
   Теперь у каждого размера свой класс, а в шаблонах проставлен нужный. */
.section-image, .section-image-big {
    width: 100%;
    height: auto;
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}
.section-image-icon {
    width: 120px;
    max-width: 40%;
    height: auto;
    border-radius: var(--radius);
}
.section-image-bot {
    width: 140px;
    max-width: 45%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 50%;
    box-shadow: var(--shadow-sm);
    transition: transform var(--transition);
}
.section-image-bot:hover { transform: scale(1.05); }

/* Шапка страницы: заголовок слева, действия справа */
.page-header {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: var(--sp-5);
}
.page-header h1 { margin-bottom: var(--sp-1); }
.page-header__sub { color: var(--text-muted); font-size: var(--fs-sm); margin: 0; }
.page-header__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Панель фильтров и поиска */
.toolbar, .search-form {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: flex-end;
    max-width: none;
    margin: 0 0 var(--sp-5);
    background: none;
    box-shadow: none;
    padding: 0;
    border: none;
}
.toolbar__field { flex: 1 1 22ch; min-width: 0; }
.toolbar input, .toolbar select { margin-bottom: 0; }

.search-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    align-items: flex-end;
    width: 100%;
}
.search-item { flex: 1 1 200px; min-width: 0; }
.search-item label {
    display: block;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--text-muted);
    margin-bottom: var(--sp-1);
}
.search-item input, .search-item select { margin-bottom: 0; }
.sort-group { display: flex; gap: var(--sp-2); flex-wrap: wrap; }

/* Кнопки панели поиска.
   Раньше они лежали прямо в .search-item рядом с полями и держались
   только на отступе mr-2 в 8 пикселей. На узком экране .search-item
   растягивался на всю ширину, «Поиск» и «Сброс» вставали вплотную и
   читались как одна кнопка. Здесь у них свой контейнер: на широком
   экране кнопки идут по содержимому, на узком делят строку поровну и
   отделены от полей полноценным отступом. */
.search-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3);
    flex: 0 0 auto;
}

.back-link { margin-top: var(--sp-5); }
.back-link a { color: var(--text-muted); }
.back-link a:hover { color: var(--accent); }

/* Ссылка в тексте — акцентная и подчёркнутая.
   Раньше она была приглушённо-серой и без подчёркивания: рядом с
   галочкой согласия её принимали за обычный текст и не открывали, а это
   документы, которые человек принимает при регистрации. */
.policy-link {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
    font-weight: 600;
}
.policy-link:hover { color: var(--accent-hover); text-decoration-thickness: 2px; }

.button-group { display: flex; flex-wrap: wrap; gap: var(--sp-2); }


/* ==========================================================================
   5. ШАПКА, НАВИГАЦИЯ, МОБИЛЬНОЕ МЕНЮ
   ========================================================================== */

/* Шапка закреплена сверху: мобильное меню отсчитывает свою позицию от её
   высоты, и пока шапка прокручивалась, над меню открывался контент. */
header {
    position: sticky;
    top: 0;
    z-index: 1100;
    min-height: var(--header-h);
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    background-color: var(--header-bg);
    padding: var(--sp-2) var(--sp-4);
    width: 100%;
}

header a, header .auth-links span { color: var(--text-on-dark); }

.auth-links {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}

/* nav занимает свободное место, кнопки справа не сжимаются.
   Раньше у nav стояло width:100%, а лого было таким же гибким элементом —
   на узком экране оба сжимались, лого сваливалось под бургер и они
   налезали друг на друга в левом верхнем углу. */
nav { flex: 1 1 auto; min-width: 0; }
nav ul {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex-wrap: wrap;
}
nav ul li a {
    color: var(--text-on-dark);
    font-size: var(--fs-base);
    font-weight: 600;
}

.header-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex: 0 0 auto;
    margin-left: auto;
}

/* Лого и название — единый блок, выровненный по центру шапки.
   Раньше картинка и подпись стояли по базовой линии текста и «плавали»
   друг относительно друга при разной высоте шапки. */
.logo-img { height: 52px; width: auto; display: block; }
.logo-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    line-height: 1;
}
.logo-text {
    font-size: var(--fs-lg);
    font-weight: 700;
    color: var(--text-on-dark);
    white-space: nowrap;
    line-height: 1;
}
/* Буква π в названии.

   Строчная «пи» ростом с букву x и рядом с заглавной «Ш» проваливается
   вниз строки — название выглядело так, будто знак случайно уменьшили.
   Коэффициент 1.32 доводит её до высоты прописных, сдвиг по базовой
   линии ставит на одну линию с ними. Заглавную греческую Π вместо неё
   взять нельзя: от русской П её не отличить ни в одном шрифте.

   inline-block здесь не годится: строчный элемент не смещает соседей по
   строке, а блочный разорвал бы название. */
.brand-pi {
    /* Множитель считается от шрифта, а не на глаз: в Manrope строчная π
       поднимается на 53 сотых кегля, заглавная Ш — на 73. Отношение и
       даёт 1.38. При смене шрифта заголовков число надо пересчитать
       заново — от Georgia здесь оставалось 1.32, и π выходила ниже
       соседней буквы. */
    font-size: 1.38em;
    line-height: 0;
}
/* На десктопе лого живёт первым пунктом .desktop-menu, здесь оно лишнее */
.mobile-logo { display: none; flex: 0 0 auto; }
/* Первый пункт десктопного меню — лого: без внутреннего отступа ссылки,
   иначе картинка смещена вниз относительно остальных пунктов. */
.desktop-menu > li:first-child > a { padding: 0; display: inline-flex; align-items: center; }

/* Выпадающее меню на десктопе */
.desktop-menu li a,
.desktop-menu .dropdown-toggle {
    color: var(--text-on-dark);
    font-weight: 600;
    display: block;
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-sm);
    transition: background-color var(--transition);
}
.desktop-menu li a:hover,
.desktop-menu .dropdown-toggle:hover {
    background: rgba(255, 255, 255, .12);
    text-decoration: none;
}
.desktop-menu .dropdown-toggle::after { display: none; }
.desktop-menu .dropdown-menu {
    position: absolute;
    top: 100%;
    left: 0;
    min-width: 220px;
    background-color: var(--header-bg);
    border: 1px solid rgba(255, 255, 255, .12);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: var(--sp-2) 0;
    z-index: 1101;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity var(--transition), transform var(--transition), visibility var(--transition);
}
.desktop-menu .dropdown-menu.show {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}
.desktop-menu .dropdown-arrow { display: inline-block; transition: transform var(--transition); }
.desktop-menu li.dropdown .dropdown-toggle.active .dropdown-arrow { transform: rotate(180deg); }

/* Кнопка бургера */
.hamburger {
    display: none;
    position: relative;
    font-size: 26px;
    line-height: 1;
    color: var(--text-on-dark);
    background: none;
    border: 1px solid transparent;
    cursor: pointer;
    padding: var(--sp-2);
    min-width: 44px;
    min-height: 44px;
    border-radius: var(--radius-sm);
    /* Долгое нажатие не должно вызывать системное меню */
    -webkit-touch-callout: none;
    user-select: none;
}
.hamburger:hover { background: rgba(255, 255, 255, .12); }
.hamburger .badge { position: absolute; top: 2px; right: 2px; }

/* Мобильное меню.
   Отсчитывается от высоты шапки через переменную: раньше здесь стояло
   85px в двух местах, и при другой высоте шапка накрывала первый пункт. */
.navbar {
    position: fixed;
    top: var(--header-h);
    right: 0;
    width: 100%;
    height: calc(100dvh - var(--header-h));
    background-color: var(--header-bg);
    /* Выезжает справа — оттуда, где стоит сама кнопка бургера.
       Раньше меню приезжало слева, с противоположной стороны экрана. */
    transform: translateX(100%);
    transition: transform .3s ease;
    z-index: 1000;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-bottom: var(--sp-6);
}
.navbar.show { transform: translateX(0); }

.navbar ul { display: block; width: 100%; }
.navbar ul li { width: 100%; }
.navbar ul li a,
.navbar-dropdown-toggle {
    display: block;
    width: 100%;
    padding: var(--sp-4);
    color: var(--text-on-dark);
    font-weight: 600;
    border-bottom: 1px solid rgba(255, 255, 255, .1);
    background: none;
    border-left: none;
    border-right: none;
    border-top: none;
    text-align: left;
    font-size: var(--fs-base);
}
.navbar ul li a:hover,
.navbar-dropdown-toggle:hover {
    background: rgba(255, 255, 255, .1);
    text-decoration: none;
}

.navbar-dropdown-arrow { float: right; transition: transform var(--transition); }
.navbar-dropdown.active .navbar-dropdown-arrow { transform: rotate(180deg); }

.navbar-submenu {
    max-height: 0;
    overflow: hidden;
    background: rgba(0, 0, 0, .2);
    transition: max-height .3s ease;
}
.navbar-dropdown.active .navbar-submenu { max-height: 100vh; }
.navbar-submenu li a { padding-left: var(--sp-6); font-weight: 400; }

/* Блокировка прокрутки под открытым меню.
   Одного overflow:hidden мало — мобильный Safari его игнорирует.
   position:fixed останавливает прокрутку, а потерю позиции возвращает
   скрипт (см. lockScroll/unlockScroll в script.js). */
body.no-scroll {
    overflow: hidden;
    position: fixed;
    width: 100%;
}

/* Кнопка темы всегда в шапке, вне .desktop-menu — на телефоне она нужна
   не меньше. Размер 44×44 — минимальная площадь касания. */
.theme-toggle {
    /* Рамка обязательна: без неё в шапке виден только значок ☾, и он
       читается как украшение, а не как кнопка — её просто не замечали. */
    background: rgba(255, 255, 255, .10);
    border: 1px solid rgba(255, 255, 255, .35);
    color: var(--text-on-dark);
    padding: var(--sp-2);
    min-width: 44px;
    min-height: 44px;
    border-radius: var(--radius-pill);
    font-size: var(--fs-lg);
    line-height: 1;
}
.theme-toggle:hover { background: rgba(255, 255, 255, .12); }
.theme-toggle .icon-dark { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-dark { display: inline; }
:root[data-theme="dark"] .theme-toggle .icon-light { display: none; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .theme-toggle .icon-dark { display: inline; }
    :root:not([data-theme="light"]) .theme-toggle .icon-light { display: none; }
}


/* ==========================================================================
   6. КНОПКИ

   Заливка — только у главного действия и у опасного. Остальное тише:
   контур или нейтральный фон. Без !important: раньше им переопределялся
   Bootstrap, и перекрасить кнопку в частном случае было нельзя.
   ========================================================================== */
button, input[type="submit"], input[type="button"], .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: 10px 18px;
    border: 1.5px solid transparent;
    border-radius: var(--radius);
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
    text-decoration: none;
    background-color: var(--surface-2);
    color: var(--text);
    transition: background-color var(--transition), border-color var(--transition), color var(--transition);
    /* Долгое нажатие на телефоне вызывало системное меню и позволяло
       утащить ссылку-кнопку в поисковую строку. */
    -webkit-touch-callout: none;
    -webkit-user-drag: none;
    user-select: none;
    touch-action: manipulation;
}

button:hover, .btn:hover { background-color: var(--surface-3); text-decoration: none; }
button:disabled, .btn:disabled, .btn.disabled { opacity: .55; cursor: not-allowed; }

/* Отклик на само нажатие — короткий и одинаковый для всех кнопок.
   На сенсорном экране это единственная честная обратная связь: :hover
   там срабатывает от касания и остаётся висеть. */
button:active, input[type="submit"]:active, input[type="button"]:active,
.btn:active { transform: scale(.98); }
button:disabled:active, .btn:disabled:active, .btn.disabled:active { transform: none; }

/* Главное действие */
.btn-primary, .btn-save, .btn-success, .btn-add,
input[type="submit"] {
    background-color: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
}
.btn-primary:hover, .btn-save:hover, .btn-success:hover, .btn-add:hover,
input[type="submit"]:hover {
    background-color: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
}

/* Опасное действие */
.btn-delete, .btn-danger, .delete-account-btn, form.delete-form button {
    background-color: var(--danger);
    border-color: var(--danger);
    color: #fff;
}
.btn-delete:hover, .btn-danger:hover,
.delete-account-btn:hover, form.delete-form button:hover {
    background-color: var(--danger-hover);
    border-color: var(--danger-hover);
    color: #fff;
}

/* Вторичное — контур. Сюда же btn-outline-secondary: этот класс стоит в
   шаблонах 111 раз и без своего правила брал вид от Bootstrap, из-за чего
   часть кнопок выглядела инородно. */
.btn-outline, .btn-secondary, .btn-outline-secondary,
.btn-outline-primary, .btn-edit, .change-password-btn, .btn-warning, .btn-info {
    background-color: transparent;
    border-color: var(--accent);
    color: var(--accent);
}
.btn-outline:hover, .btn-secondary:hover, .btn-outline-secondary:hover,
.btn-outline-primary:hover, .btn-edit:hover, .change-password-btn:hover,
.btn-warning:hover, .btn-info:hover {
    background-color: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
    text-decoration: none;
}

/* Третичное — нейтральный фон */
.btn-quiet, .btn-light {
    background-color: var(--surface-2);
    border-color: var(--line);
    color: var(--text);
}
.btn-quiet:hover, .btn-light:hover { background-color: var(--surface-3); }

/* Мелкая кнопка. Высота задана явно: одних отступов мало — выходило
   31 пиксель, и попасть пальцем в «Удалить» рядом с «Редактировать»
   получалось не с первого раза. На узком экране высота ещё больше:
   там нажимают пальцем, а не указателем. */
.btn-sm {
    padding: 6px 12px;
    font-size: var(--fs-sm);
    min-height: 36px;
}
.btn-lg { padding: 13px 24px; font-size: var(--fs-lg); }
.btn-block, .btn.w-100 { width: 100%; }

/* Кнопка, выглядящая как ссылка */
.link-button {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: inherit;
    text-decoration: underline;
    cursor: pointer;
}
.link-button:hover { background: none; color: var(--accent); }

/* Ссылка-пункт списка (личный кабинет, «Начать обучение», уроки курса).
   Раньше при наведении она целиком заливалась акцентом. На телефоне
   :hover после касания «залипает» — пункт оставался тёмно-залитым, пока
   не тронешь что-то другое. Теперь заливки нет: подчёркнутая полоса
   слева и мягкий фон, а сама заливка живёт только под настоящим
   указателем (@media hover) и на время нажатия. */
.highlight-link {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius);
    background-color: var(--surface-2);
    color: var(--text);
    font-weight: 600;
    transition: background-color var(--transition), border-color var(--transition), transform var(--transition);
    -webkit-touch-callout: none;
    -webkit-user-drag: none;
}
.highlight-link::after {
    content: "›";
    margin-left: auto;
    color: var(--accent);
    font-size: var(--fs-lg);
    line-height: 1;
}
@media (hover: hover) and (pointer: fine) {
    .highlight-link:hover {
        background-color: var(--accent-soft);
        border-color: var(--accent);
        color: var(--text);
        text-decoration: none;
    }
}
.highlight-link:active { transform: scale(.99); background-color: var(--accent-soft); }

.link-list, .lessons-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}


/* ==========================================================================
   7. ФОРМЫ
   ========================================================================== */
form {
    max-width: var(--content-max);
    margin: 0 auto;
    background: none;
    /* Тени у формы нет намеренно: из-за неё в шаблонах приходилось писать
       box-shadow:none там, где форма — это просто кнопка в таблице. */
    box-shadow: none;
}

.form-group, .mb-3 { margin-bottom: var(--sp-4); }

label, .form-label {
    display: block;
    font-weight: 600;
    font-size: var(--fs-sm);
    color: var(--text);
    margin-bottom: var(--sp-1);
}

/* Звёздочка у обязательного поля — сразу, до попытки отправить форму.
   WTForms сам ставит атрибут required полю с DataRequired/InputRequired,
   а подпись почти везде на сайте стоит прямо перед полем как соседний
   элемент — этого достаточно, чтобы найти нужные подписи через :has() и
   не переписывать каждый шаблон вручную. Ловит только объявленные
   заранее required; поле, которое становится обязательным условно (по
   выбору в другом поле), звёздочкой не отмечается — такого на сайте нет. */
label:has(+ input:required)::after,
label:has(+ textarea:required)::after,
label:has(+ select:required)::after {
    content: " *";
    color: var(--danger);
}

input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="tel"], input[type="search"],
input[type="date"], input[type="time"], input[type="url"],
input[type="file"], textarea, select, .form-control, .form-select {
    display: block;
    width: 100%;
    max-width: var(--field-max);
    padding: 10px 12px;
    margin-bottom: var(--sp-3);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    /* 16px и меньше — iOS увеличивает страницу при фокусе */
    font-size: var(--fs-base);
    font-family: inherit;
    background-color: var(--surface);
    color: var(--text);
    transition: border-color var(--transition), box-shadow var(--transition);
}

/* Поля во всю ширину там, где это осмысленно */
textarea, .form-control-wide { max-width: none; }

input:focus, textarea:focus, select:focus, .form-control:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
    outline: none;
}

/* Текст-подсказка внутри поля.
   Правила для input и textarea было мало: у Bootstrap есть своё
   .form-control::placeholder, и оно весомее (класс против имени тега),
   поэтому подсказка красилась в --bs-secondary-color — почти чёрный. В
   тёмной теме её просто не было видно. Перечисляем классы явно и заодно
   переопределяем саму переменную Bootstrap: её же берут вспомогательные
   классы вроде .text-secondary. */
input::placeholder, textarea::placeholder,
.form-control::placeholder, .form-select::placeholder,
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {
    color: var(--text-muted);
    opacity: 1;
}

/* Автозаполнение. Браузер подставляет собственный ярко-белый фон и тёмный
   текст — в тёмной теме поле вспыхивало белым, а текст оставался нечитаемым.
   Заливка внутренней тенью — единственный способ это перебить. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
select:-webkit-autofill {
    -webkit-box-shadow: 0 0 0 1000px var(--surface) inset !important;
    box-shadow: 0 0 0 1000px var(--surface) inset !important;
    -webkit-text-fill-color: var(--text) !important;
    caret-color: var(--text);
    transition: background-color 5000s ease-in-out 0s;
}

/* Выпадающие списки: в тёмной теме системный список остаётся светлым,
   поэтому цвет задаётся и на option. */
select option { background-color: var(--surface); color: var(--text); }

textarea { min-height: 120px; resize: vertical; }

/* Флажки и переключатели.

   Размер задан явно и крупнее принятого по умолчанию. Браузер рисует
   флажок примерно в 13 пикселей — на телефоне это полоска, в которую
   тяжело попасть, а состояние «отмечено» на таком размере едва
   различимо. Норма для сенсорной цели — 24 пикселя и больше; здесь 22
   плюс отступы вокруг, так что нажимаемая область выходит за сам
   значок.

   accent-color красит галочку в цвет темы, оставляя браузеру его
   собственную отрисовку: своя разметка из span и псевдоэлементов
   выглядит одинаково, но ломает поведение с клавиатуры и в программах
   чтения с экрана. */
input[type="checkbox"], input[type="radio"] {
    width: 22px;
    height: 22px;
    max-width: none;
    flex-shrink: 0;
    margin: 0 var(--sp-2) 0 0;
    accent-color: var(--accent);
    vertical-align: middle;
    cursor: pointer;
}
/* Подпись тоже нажимается — она связана с полем через for/id. Курсор
   об этом говорит, а цель нажатия становится во всю строку. */
.form-check-label, .consent-label { cursor: pointer; }
.form-check { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-2); }
.form-check-input { margin: 0; }
.form-check-label { margin: 0; font-weight: 400; }

fieldset {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--sp-4);
    margin: 0 0 var(--sp-5);
}
legend { padding: 0 var(--sp-2); font-weight: 600; width: auto; font-size: var(--fs-base); }

/* Ошибка: отмечается не только цветом, но и утолщённой рамкой слева */
input[aria-invalid="true"], textarea[aria-invalid="true"], select[aria-invalid="true"] {
    border-color: var(--danger);
    border-left-width: 3px;
}
.form-error, .invalid-feedback, .text-danger, .error {
    display: block;
    color: var(--danger);
    font-size: var(--fs-sm);
    margin-bottom: var(--sp-2);
}
.form-hint, .form-text {
    display: block;
    color: var(--text-muted);
    font-size: var(--fs-sm);
    margin-bottom: var(--sp-3);
}
.required-mark { color: var(--danger); margin-left: 2px; }

.form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-top: var(--sp-5);
}

.file-input-wrapper { margin-bottom: var(--sp-4); }

/* Поле пароля с кнопкой показа */
.password-container { position: relative; max-width: var(--field-max); }
.password-toggle {
    position: absolute;
    right: 12px;
    top: 21px;
    transform: translateY(-50%);
    cursor: pointer;
    color: var(--text-muted);
    background: none;
    border: none;
    padding: 0;
}
.password-toggle:hover { background: none; color: var(--accent); }

.consent-container { margin: var(--sp-4) 0; }
.consent-label { font-weight: 400; font-size: var(--fs-sm); }


/* ==========================================================================
   8. ТАБЛИЦЫ
   ========================================================================== */

/* Обёртка с прокруткой: на узком экране прокручивается таблица,
   а не ломается вся страница. */
.table-wrap {
    overflow-x: auto;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    margin-bottom: var(--sp-5);
}

table, .table {
    width: 100%;
    border-collapse: collapse;
    background: var(--surface);
    color: var(--text);
    margin: 0;
}

table th, .table th {
    text-align: left;
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--line-strong);
    white-space: nowrap;
    background: var(--surface-2);
}

table td, .table td {
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
    background: transparent;
}

table tbody tr:last-child td { border-bottom: none; }
table tbody tr:hover { background: var(--surface-2); }

caption { caption-side: top; text-align: left; padding: var(--sp-3) var(--sp-4); color: var(--text-muted); }

/* Числа выравниваются по правому краю моноширинными цифрами */
.col-num, td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Колонка действий: кнопки в один ряд.
   Форма удаления по умолчанию блочная и разваливала колонку. */
.row-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.row-actions form { display: inline-flex; margin: 0; max-width: none; }
/* Кнопки в строке таблицы уменьшены, но не ниже того, во что уверенно
   попадают: «Удалить» стоит вплотную к «Редактировать», и промах здесь
   стоит дорого. */
.row-actions .btn, .row-actions button {
    padding: 6px 12px;
    font-size: var(--fs-sm);
    min-height: 36px;
}

.cell-clamp {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 40ch;
}

.empty-state { text-align: center; padding: var(--sp-7) var(--sp-4); color: var(--text-muted); }
.empty-state h3 { color: var(--text); }
.empty-state p { margin-bottom: var(--sp-4); }

/* Таблица расписания */
.schedule-cell {
    min-width: 90px;
    text-align: center;
    /* Точка отсчёта для подсказки. Ширину клетки не ограничиваем
       overflow: hidden — иначе подсказка обрезалась бы по её краю. */
    position: relative;
}
.schedule-cell.occupied { background: var(--accent-soft); }

/* Подсказка о занятой клетке.

   Своя, а не Bootstrap: тот подгружается с внешнего CDN, и стоит ему
   оказаться недоступным — подсказки молча исчезают, а понять по
   галочке, кто и во сколько занимается, нельзя.

   Показывается по наведению и по фокусу: с клавиатуры дойти до ссылки
   внутри клетки можно, и содержимое подсказки нужно и там. */
.schedule-tip {
    position: absolute;
    left: 50%;
    top: 100%;
    z-index: 20;
    display: none;
    min-width: 260px;
    margin-top: var(--sp-1);
    padding: var(--sp-2) var(--sp-3);
    transform: translateX(-50%);
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    font-size: var(--fs-sm);
    font-weight: 400;
    line-height: var(--lh-base);
    text-align: left;
    white-space: normal;
}
.schedule-cell:hover .schedule-tip,
.schedule-cell:focus-within .schedule-tip { display: block; }

.schedule-tip strong { display: block; margin-bottom: var(--sp-1); }
/* Имя не переносится: «Мария\u00a0Кузнецова» в две строки читается
   хуже, чем чуть более широкая подсказка. */
.schedule-tip__row { display: block; white-space: nowrap; }

/* У крайних столбцов подсказка уезжала бы за край таблицы. */
.schedule-cell:first-of-type .schedule-tip { left: 0; transform: none; }
.schedule-cell:last-child .schedule-tip { left: auto; right: 0; transform: none; }
.schedule-entry { font-size: var(--fs-sm); }


/* ==========================================================================
   9. КАРТОЧКИ, СПИСКИ, ПЛИТКИ
   ========================================================================== */
.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4);
    margin-bottom: var(--sp-4);
    color: var(--text);
}
.card-body { padding: 0; }
.card-title { font-family: var(--font-head); margin-bottom: var(--sp-2); }

/* Тарифы (my_app/templates/billing.html): те же карточки в сетке. */
.cards-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--sp-4);
    margin: var(--sp-4) 0;
}
.cards-grid .price { font-size: var(--fs-lg); font-weight: 700; margin: var(--sp-2) 0; }
.plan-features { padding-left: var(--sp-4); margin: 0; font-size: var(--fs-sm); color: var(--text-muted); }
.plan-features li { margin-bottom: var(--sp-1); }
.plan-card:has(input[type="radio"]:checked) { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent) inset; }

.list-group { display: flex; flex-direction: column; gap: var(--sp-2); }
.list-group-item {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--sp-3) var(--sp-4);
    color: var(--text);
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}
.list-group-item:hover { background: var(--surface-2); }
.list-group-item form { margin: 0; max-width: none; }

/* Список карточек: расстояние задаёт контейнер, а не отступ у каждой —
   так последняя карточка не оставляет пустоты под собой. */
.card-list {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
}

.tutor-card, .student-card, .notification-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    padding: var(--sp-4);
    box-shadow: var(--shadow-sm);
}
.card-list > * { margin-bottom: 0; }

/* Уведомления. Непрочитанное отмечается полосой слева и насыщенным
   текстом: раньше вся карточка становилась жирной целиком, включая
   дату, и читать её было тяжело. */
.notification-card__text { margin-bottom: var(--sp-2); }
.notification-card__date { font-size: var(--fs-sm); color: var(--text-muted); margin: 0; }
.notification-card.is-unread {
    border-left: 3px solid var(--accent);
    background: var(--surface);
}
.notification-card.is-unread .notification-card__text { font-weight: 600; }

/* Метка «новое». Одной жирности мало: она заметна, только когда рядом
   есть с чем сравнить, а на странице из трёх новых уведомлений подряд
   отличать нечего. */
.badge-new {
    display: inline-block;
    margin-bottom: var(--sp-2);
    padding: 1px 8px;
    border-radius: var(--radius-pill);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: lowercase;
}

/* Фото репетитора. Раньше правило было продублировано в семи шаблонах в
   двух разошедшихся вариантах, из-за чего фото выглядели по-разному на
   разных страницах. Теперь размер задаётся здесь одним местом. */
.tutor-photo {
    width: 150px;
    height: 225px;
    object-fit: cover;
    border-radius: var(--radius);
    display: block;
}
.no-photo-placeholder {
    width: 150px;
    height: 225px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--surface-2);
    color: var(--text-muted);
    border-radius: var(--radius);
    font-size: var(--fs-sm);
    text-align: center;
}

.avatar {
    font-weight: 700;
    flex-shrink: 0;
}
.social-icons { display: flex; gap: var(--sp-2); }
.social-icon { width: 32px; height: 32px; }

/* Метки состояния: форма, а не только цвет */
.status-badge, .badge {
    display: inline-block;
    padding: 2px 10px;
    border-radius: var(--radius-pill);
    font-size: var(--fs-xs);
    font-weight: 600;
    white-space: nowrap;
    background: var(--surface-2);
    color: var(--text-muted);
}
.status-badge--ok, .badge.bg-success { background: var(--success-soft); color: var(--success); }
.status-badge--warn, .badge.bg-warning { background: var(--warning-soft); color: var(--warning); }
.status-badge--danger, .badge.bg-danger { background: var(--danger-soft); color: var(--danger); }
.status-badge--info, .badge.bg-info, .badge.bg-primary { background: var(--info-soft); color: var(--info); }
.status-badge--neutral, .badge.bg-secondary { background: var(--surface-2); color: var(--text-muted); }

.alert {
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--radius);
    border: 1px solid var(--line);
    background: var(--surface-2);
    color: var(--text);
    margin-bottom: var(--sp-4);
}
.alert-info { background: var(--info-soft); border-color: var(--info); color: var(--info); }
.alert-success { background: var(--success-soft); border-color: var(--success); color: var(--success); }
.alert-warning { background: var(--warning-soft); border-color: var(--warning); color: var(--warning); }
.alert-danger { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }


/* ==========================================================================
   10. КОМПОНЕНТЫ
   ========================================================================== */

/* --- Всплывающие сообщения --------------------------------------------- */
.flash-container {
    position: fixed;
    top: calc(var(--header-h) + var(--sp-3));
    left: 50%;
    transform: translateX(-50%);
    z-index: 1200;
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    max-width: min(90vw, 32rem);
}
.flash-message {
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--line);
    border-left: 4px solid var(--text-muted);
    box-shadow: var(--shadow);
    border-radius: var(--radius);
    padding: var(--sp-3) var(--sp-4);
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    cursor: pointer;
}
.flash-success { border-left-color: var(--success); }
.flash-danger  { border-left-color: var(--danger); }
.flash-warning { border-left-color: var(--warning); }
.flash-info    { border-left-color: var(--info); }
.flash-text { flex: 1; font-size: var(--fs-sm); }

/* --- Модальные окна ----------------------------------------------------- */
.modal-content {
    background: var(--surface);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
}
.modal-header, .modal-footer { border-color: var(--line); }
.modal-title { font-family: var(--font-head); color: var(--text); }
.modal-body { color: var(--text); }
.btn-close { filter: var(--close-filter, none); }
:root[data-theme="dark"] .btn-close { filter: invert(1) grayscale(100%); }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .btn-close { filter: invert(1) grayscale(100%); }
}

/* --- Баннер cookie ------------------------------------------------------ */
.cookie-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1150;
    background: var(--surface);
    border-top: 1px solid var(--line-strong);
    box-shadow: 0 -4px 16px var(--shadow-color);
    padding: var(--sp-4);
}
.cookie-banner.is-hidden { display: none; }
.cookie-banner__inner {
    max-width: var(--content-max);
    margin: 0 auto;
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    align-items: center;
    justify-content: space-between;
}
.cookie-banner__text { flex: 1 1 30ch; }
.cookie-banner__title { font-size: var(--fs-lg); margin-bottom: var(--sp-1); }
.cookie-banner__text p { color: var(--text-muted); font-size: var(--fs-sm); margin: 0; }
.cookie-banner__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cookie-banner__form { display: inline-flex; margin: 0; max-width: none; }

.cookie-table { font-size: var(--fs-sm); }

/* --- Пагинация ---------------------------------------------------------- */
.pagination {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    justify-content: center;
    margin-top: var(--sp-5);
    padding: 0;
}
.pagination .btn, .pagination a, .pagination span {
    padding: 6px 12px;
    font-size: var(--fs-sm);
}
.pagination .is-current, .pagination .active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
}
/* Разрыв в ряду номеров. При нынешнем окне из трёх страниц он не
   выводится, но правило оставлено: окно задаётся одним параметром в
   макросе, и при его расширении многоточие понадобится снова. */
.pagination__gap {
    align-self: center;
    padding: 0 var(--sp-1);
    color: var(--text-muted);
    user-select: none;
}
/* Стрелки: квадратные, одного размера с номерами, чтобы ряд читался
   как единая полоса, а не как кнопки разной ширины. */
.pagination__step {
    min-width: 34px;
    padding-left: 0;
    padding-right: 0;
    font-size: var(--fs-lg);
    line-height: 1;
}
.pagination .btn { min-width: 34px; }


/* ==========================================================================
   11. ПОДВАЛ
   ========================================================================== */
footer, .footer {
    background: var(--header-bg);
    color: var(--text-on-dark);
    padding: var(--sp-6) var(--sp-4);
    margin-top: auto;
}
/* Колонки одинаковой ширины и с общей базовой линией: раньше они просто
   растягивались по содержимому, и заголовки «Контакты» и «Мы в соцсетях»
   стояли на разной высоте, а на широком экране всё жалось влево. */
.footer-container {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--sp-6) var(--sp-5);
    max-width: var(--content-max);
    margin: 0 auto;
    align-items: start;
}
.footer-column { display: flex; flex-direction: column; gap: var(--sp-2); }
.footer-column h3 {
    color: var(--text-on-dark);
    font-size: var(--fs-lg);
    margin-bottom: var(--sp-1);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid rgba(255, 255, 255, .18);
}
.footer-column a, footer a { color: var(--text-on-dark); }
.footer-column p { color: var(--text-on-dark); margin: 0; }
.footer-column a:hover { color: var(--text-on-dark); text-decoration: underline; }

.social-icons { display: flex; gap: var(--sp-3); align-items: center; }
.social-icon {
    width: 32px;
    height: 32px;
    object-fit: contain;
    transition: transform var(--transition), opacity var(--transition);
    opacity: .9;
}
@media (hover: hover) and (pointer: fine) {
    .social-icon:hover { transform: translateY(-2px); opacity: 1; }
}

.footer-bottom {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    max-width: var(--content-max);
    margin: var(--sp-6) auto 0;
    padding-top: var(--sp-4);
    border-top: 1px solid rgba(255, 255, 255, .15);
    font-size: var(--fs-sm);
    color: var(--text-on-dark);
}
.footer-bottom p { margin: 0; }
.footer-links {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
}
.footer-links a, .footer-links .link-button {
    color: var(--text-on-dark);
    font-size: var(--fs-sm);
}
.footer-links .link-button { text-decoration: underline; }


/* ==========================================================================
   12. УТИЛИТЫ И СОВМЕСТИМОСТЬ
   ========================================================================== */

/* Текст только для программ чтения с экрана */
.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.text-muted { color: var(--text-muted); }
.text-center { text-align: center; }
.reading-width { max-width: var(--reading-max); }

/* Классы отступов из Bootstrap 4. В проекте их 117 штук, но сам проект
   собран на Bootstrap 5, где эти классы переименованы (me- и ms- вместо
   mr- и ml-) — то есть все эти отступы просто не применялись. Объявляем
   их здесь, чтобы вёрстка не разъезжалась и не пришлось править сотню
   мест в шаблонах. */
.mr-1 { margin-right: var(--sp-1); }
.mr-2 { margin-right: var(--sp-2); }
.mr-3 { margin-right: var(--sp-3); }
.ml-1 { margin-left: var(--sp-1); }
.ml-2 { margin-left: var(--sp-2); }
.ml-3 { margin-left: var(--sp-3); }

/* Печать: убираем интерфейс, оставляем содержимое */
@media print {
    header, footer, .navbar, .hamburger, .cookie-banner,
    .flash-container, .header-theme, .navbar-theme, .row-actions, .pagination { display: none !important; }
    body { background: #fff; color: #000; }
    .content-container, .section, .card, .table-wrap {
        border: none;
        box-shadow: none;
        padding: 0;
    }
}


/* ==========================================================================
   13. КОМПОНЕНТЫ ОТДЕЛЬНЫХ СТРАНИЦ

   Собраны сюда из инлайновых <style> в шаблонах: одни и те же правила
   были продублированы в нескольких файлах и успели разойтись между собой.
   ========================================================================== */

/* --- Появление секций при прокрутке (главная, «о нас», «как это работает»)
   Скрытое состояние навешивается только при работающем скрипте: класс js
   ставит тот же inline-скрипт в <head>, что и тему. Иначе при выключенном
   JavaScript показывать содержимое стало бы некому — секции остались бы
   прозрачными навсегда, и страница выглядела бы пустой. */
.js .section.fade-in {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity .6s ease-out, transform .6s ease-out;
}
.js .section.fade-in.visible { opacity: 1; transform: translateY(0); }
.reverse .section-content { flex-direction: row-reverse; }

@media (prefers-reduced-motion: reduce) {
    .js .section.fade-in { opacity: 1; transform: none; }
}

/* --- Флажок согласия на обработку данных (регистрация, обратная связь) --- */
.consent-container { display: flex; align-items: flex-start; gap: var(--sp-2); }
.consent-checkbox { margin-top: 4px; flex-shrink: 0; }
.consent-label { font-weight: 400; font-size: var(--fs-sm); color: var(--text); }

/* Капча: место под виджет резервируется заранее.

   Виджет приходит со стороннего сервера и появляется через мгновение
   после отрисовки формы. Без заданной высоты кнопка «Отправить» в этот
   момент подскакивает вниз — а палец уже летит к прежнему месту, и
   нажатие приходится мимо. Высота взята из рекомендации Яндекса. */
.smart-captcha {
    min-height: 100px;
    margin-bottom: var(--sp-3);
}
/* Флажок с длинной подписью рядом.

   min-width: 0 обязателен. Элемент внутри flex по умолчанию не сжимается
   ниже своего содержимого, и длинная подпись растягивала блок за край
   экрана — на узком телефоне текст уезжал вправо вместе с рамкой формы.
   Отступ справа от флажка задаётся здесь через gap, поэтому у самого
   поля он обнуляется: иначе к промежутку прибавляется ещё один. */
.custom-checkbox { display: flex; align-items: flex-start; gap: var(--sp-2); }
.custom-checkbox > label { min-width: 0; }
.custom-checkbox > input[type="checkbox"] { margin-right: 0; }

/* --- Таблица с фиксированной раскладкой (расписание, успеваемость) ------- */
.table-fixed { table-layout: fixed; }
.table-fixed th, .table-fixed td {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* --- Строка расписания --------------------------------------------------- */
.schedule-entry {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: flex-end;
    padding: var(--sp-3);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    margin-bottom: var(--sp-3);
    background: var(--surface-2);
}
.schedule-entry .form-control { margin-bottom: 0; }
.disciplines { display: flex; flex-direction: column; gap: var(--sp-3); }

/* --- Область загрузки файла --------------------------------------------- */
.drop-zone {
    border: 2px dashed var(--line-strong);
    border-radius: var(--radius);
    padding: var(--sp-6) var(--sp-4);
    text-align: center;
    color: var(--text-muted);
    background: var(--surface-2);
    cursor: pointer;
    transition: border-color var(--transition), background-color var(--transition);
}
.drop-zone:hover, .drop-zone.dragover {
    border-color: var(--accent);
    background: var(--accent-soft);
}

/* --- Таймер прохождения теста -------------------------------------------- */
#fixed-timer {
    position: fixed;
    top: calc(var(--header-h) + var(--sp-3));
    right: var(--sp-4);
    z-index: 1050;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: var(--sp-2) var(--sp-4);
    box-shadow: var(--shadow);
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--text);
}

/* --- Вопрос теста -------------------------------------------------------- */
.question {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--sp-4);
    margin-bottom: var(--sp-4);
}
/* Строка варианта ответа в конструкторе.

   Поля стояли в один ряд без переноса: текст варианта, соответствие,
   номер пропуска, отметка «верный» и кнопка удаления делили ширину
   поровну. На телефоне от поля с текстом оставалась щель в пару
   сантиметров — набранного не разглядеть целиком.

   Теперь ряд переносится, поле с текстом получает приоритет по ширине,
   а на узком экране всё складывается в столбик: текст во всю ширину,
   кнопка удаления под ним. */
.option {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-3);
    padding-bottom: var(--sp-3);
    border-bottom: 1px solid var(--line);
}
.option:last-child { border-bottom: 0; }
.option > .form-group {
    flex: 1 1 16rem;
    /* Без этого поле не сжимается ниже своего содержимого и выталкивает
       соседей за край. */
    min-width: 0;
    margin-bottom: 0;
}
.option > .form-group.only-gaps { flex: 0 1 9rem; }

@media (max-width: 768px) {
    .option { flex-direction: column; align-items: stretch; }
    /* В колонке flex-basis задаёт высоту, а не ширину: те же 16rem
       превращались в 256 пикселей пустоты под каждым полем. Здесь
       размер должен определяться содержимым. */
    .option > .form-group { flex: 0 0 auto; }
    .option .remove-option { align-self: flex-start; }
}

/* --- Чат -----------------------------------------------------------------

   Высота окна переписки задана жёстко, и прокручивается только середина.
   Раньше высота была у одного .chat-messages, а само окно тянулось по
   содержимому: шапка с именем собеседника и поле ввода уезжали вместе с
   лентой, и до поля ввода в длинном разговоре приходилось долистывать.
   Теперь панель — колонка фиксированной высоты: шапка сверху, поле ввода
   снизу, между ними лента со своей прокруткой. */
.right-panel {
    height: calc(100dvh - var(--header-h) - var(--sp-8));
    max-height: calc(100dvh - var(--header-h) - var(--sp-8));
    overflow: hidden;
}
.chat-header {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: var(--surface-2);
    border-bottom: 1px solid var(--line);
    flex-shrink: 0;
}
.chat-messages {
    /* flex-basis: 0 обязателен: с auto лента растягивается по содержимому
       и выталкивает поле ввода за нижний край панели. */
    flex: 1 1 0;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--sp-3);
    background: var(--surface);
}
/* Строка отправки: поле и кнопка одной высоты, в один ряд.

   Кнопка была обёрнута в span и выпадала из выравнивания — висела выше
   поля и разной с ним высоты. Поле растёт по ширине, кнопка занимает
   столько, сколько нужно надписи. */
.right-panel .input-group,
.chat-input {
    display: flex;
    align-items: stretch;
    gap: var(--sp-2);
    flex-shrink: 0;
    margin-top: var(--sp-3);
}
.chat-input textarea {
    flex: 1 1 auto;
    min-width: 0;
    /* Две строки в покое; дальше прокрутка внутри поля, чтобы длинное
       сообщение не выдавливало ленту переписки. */
    min-height: 3em;
    max-height: 6em;
    margin: 0;
    resize: none;
}
.chat-input .btn {
    flex: 0 0 auto;
    align-self: stretch;
    white-space: nowrap;
}

/* Кнопка «Показать ещё» над самым старым загруженным сообщением. */
.load-more-row {
    display: flex;
    justify-content: center;
    margin-bottom: var(--sp-3);
}

/* Кнопка возврата к списку контактов: вид тот же, что у кнопки «Назад» на
   остальных страницах, но стоит она в потоке шапки, а не приклеена к углу
   экрана — в углу она перекрыла бы поле ввода. */
.btn-back--inline {
    position: static;
    left: auto;
    bottom: auto;
    padding: 6px 14px;
    font-size: var(--fs-sm);
    box-shadow: none;
    flex-shrink: 0;
}
.message {
    border-radius: var(--radius);
    margin-bottom: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    max-width: 70%;
    overflow-wrap: break-word;
}
.message.mine { background: var(--accent-soft); margin-left: auto; }
.message.other { background: var(--surface-2); }
.message-time { font-size: var(--fs-xs); color: var(--text-muted); margin-left: var(--sp-2); }
.day-header { text-align: center; color: var(--text-muted); font-size: var(--fs-sm); margin: var(--sp-3) 0; }
.left-panel, .right-panel { min-width: 0; }

/* --- Только для чтения ---------------------------------------------------- */
.form-control-plaintext {
    padding: 10px 0;
    color: var(--text);
    background: none;
    border: none;
    margin: 0;
}

@media (max-width: 768px) {
    .message { max-width: 85%; }
    .reverse .section-content { flex-direction: column; }

    /* На телефоне переписка занимает весь экран под шапкой: списка
       контактов рядом нет, делить высоту не с кем. */
    .right-panel {
        height: calc(100dvh - var(--header-h) - var(--sp-4));
        max-height: calc(100dvh - var(--header-h) - var(--sp-4));
    }

    /* Переключение «список контактов ↔ переписка» на узком экране.
       На телефоне помещается только одна из двух панелей, поэтому окно
       переписки скрыто классом d-none и показывается лишь тогда, когда
       выбран собеседник: loadChat вешает на body класс show-chat, кнопка
       «назад» его снимает.

       Эти четыре правила потерялись при переносе стилей из шаблона в общий
       файл, и переписка загружалась в скрытый блок: нажатие на контакт с
       телефона внешне не делало ничего. На компьютере видны обе панели
       сразу, поэтому там всё работало — и поломка не бросалась в глаза. */
    .right-panel { display: none !important; }
    .left-panel { display: block; }
    .show-chat .left-panel { display: none !important; }

    /* Открытая переписка занимает весь экран под шапкой.

       Раньше панель была обычным блоком в потоке страницы, и страница
       под ней прокручивалась: при касании поля ввода браузер подтягивал
       его к середине экрана, а лента уезжала. Теперь прокручивается
       только лента внутри панели, а поле ввода всегда внизу экрана —
       и до касания, и после. */
    .show-chat .right-panel {
        display: flex !important;
        flex-direction: column;
        position: fixed;
        top: calc(var(--header-h) + 56px);
        left: 0;
        right: 0;
        bottom: 0;
        height: auto;
        max-height: none;
        margin: 0;
        border-radius: 0;
        z-index: 900;
    }

    /* Кнопка возврата поверх панели: панель закреплена, и обычный поток
       страницы её больше не сдвигает. */
    .show-chat .chat-back {
        position: fixed;
        top: calc(var(--header-h) + var(--sp-2));
        left: var(--sp-3);
        z-index: 901;
        margin: 0;
    }

    /* Страница под закреплённой панелью прокручиваться не должна:
       иначе телефон возит её туда-сюда вместе с клавиатурой. */
    body.show-chat { overflow: hidden; }
}


/* --- Переключатель роли при регистрации (ученик / репетитор) ------------- */
.role-toggle {
    position: relative;
    display: inline-block;
    width: 170px;
    height: 44px;
    margin-top: var(--sp-2);
}
.role-toggle input { opacity: 0; width: 0; height: 0; position: absolute; }
.role-toggle-label {
    position: absolute;
    inset: 0;
    background-color: var(--surface-3);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-pill);
    cursor: pointer;
    transition: background-color var(--transition);
    display: flex;
    align-items: center;
}
.role-toggle-label .slider {
    position: absolute;
    left: 3px;
    width: 138px;
    height: 36px;
    background-color: var(--surface);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-sm);
    transition: transform .3s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Выбранное положение видно по заливке, а не только по сдвигу.

   Раньше состояния отличались подложкой в полтона и смещением бегунка на
   24 пикселя — при беглом взгляде разницы не видно, и человек не мог
   сказать, кем он сейчас регистрируется. Теперь выбор «Преподаватель»
   заливает бегунок акцентом целиком: цвет, фон и вес подписи меняются
   разом. */
.role-toggle input:checked + .role-toggle-label {
    background-color: var(--accent-soft);
    border-color: var(--accent);
}
.role-toggle input:checked + .role-toggle-label .slider {
    transform: translateX(24px);
    background-color: var(--accent);
    box-shadow: var(--shadow);
}
.role-toggle input:checked + .role-toggle-label .slider-text { color: var(--text-on-accent); }
/* Обводка при переходе табом — сам чекбокс скрыт, поэтому показываем её на подложке */
.role-toggle input:focus-visible + .role-toggle-label {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.slider-text {
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--text);
    user-select: none;
    white-space: nowrap;
}

/* --- База знаний: раскрывающиеся разделы --------------------------------- */
/* Заголовок-раскрывашка. Это <button>, поэтому размеры и цвета
   переопределяют общее правило кнопок: выглядеть он должен заголовком. */
.toggle-header {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    width: 100%;
    padding: var(--sp-2) 0;
    background: none;
    border: none;
    border-radius: 0;
    font-family: var(--font-head);
    font-size: var(--fs-h2);
    font-weight: 700;
    line-height: var(--lh-tight);
    color: var(--accent);
    text-align: left;
    cursor: pointer;
    user-select: none;
}
.toggle-header:hover { background: none; color: var(--accent-hover); }
.toggle-header:active { transform: none; }
/* Стрелка поворачивается, а не подменяется другим символом:
   у подмены нет промежуточных кадров, и анимация читалась как рывок. */
.toggle-header::after {
    content: "▾";
    margin-left: auto;
    font-size: var(--fs-base);
    color: var(--text-muted);
    transition: transform .3s ease;
}
.toggle-header.active::after { transform: rotate(180deg); }
.knowledge-list {
    max-height: 0;
    overflow: hidden;
    opacity: 0;
    transition: max-height .3s ease-out, opacity .3s ease-out;
}
.knowledge-list.active { max-height: 400px; opacity: 1; }
.knowledge-list ul { margin-top: var(--sp-3); }
.knowledge-list li { margin-bottom: var(--sp-2); }
.knowledge-link {
    display: inline-block;
    padding: var(--sp-2) var(--sp-4);
    background-color: var(--accent);
    color: var(--text-on-accent);
    border-radius: var(--radius);
    font-weight: 600;
}
.knowledge-link:hover { background-color: var(--accent-hover); color: var(--text-on-accent); text-decoration: none; }
.knowledge-empty { color: var(--text-muted); font-style: italic; }

/* --- Новости -------------------------------------------------------------- */
.news-list { display: flex; flex-direction: column; gap: var(--sp-4); }
.news-title {
    display: block;
    font-family: var(--font-head);
    font-size: var(--fs-h3);
    color: var(--accent);
    margin-bottom: var(--sp-2);
}
.news-title:hover { color: var(--accent-hover); }
/* Текста новости в перечне нет: там только заголовок и дата, а
   содержимое открывается по ссылке. Обрезка в три строки, которая тут
   стояла, показывала начало и обрывала его на полуслове — заголовок
   говорит о новости больше, чем её первые три строки. */

/* --- Динамические списки в формах (дисциплины, вопросы, варианты) --------- */
.discipline-entry, .lessons-list, .options {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.discipline-entry {
    padding: var(--sp-3);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
    margin-bottom: var(--sp-3);
}
.add-discipline, .add-option, .add-question {
    background-color: transparent;
    border-color: var(--accent);
    color: var(--accent);
}
.remove-discipline, .remove-option, .remove-question {
    background-color: transparent;
    border-color: var(--danger);
    color: var(--danger);
    padding: 6px 12px;
    font-size: var(--fs-sm);
}
.remove-discipline:hover, .remove-option:hover, .remove-question:hover {
    background-color: var(--danger-soft);
}

/* Списки с прокруткой (выбор материалов, уроков) */
.lessons-list, .materials-list {
    max-height: 240px;
    overflow-y: auto;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    padding: var(--sp-3);
    background: var(--surface);
}

/* --- Прогресс курса ------------------------------------------------------- */
.progress {
    height: 10px;
    background: var(--surface-3);
    border-radius: var(--radius-pill);
    overflow: hidden;
}
.progress-bar { height: 100%; background: var(--accent); }

/* --- Прочее --------------------------------------------------------------- */
.category { color: var(--text-muted); font-size: var(--fs-sm); }
.password-recommendations { color: var(--text-muted); font-size: var(--fs-sm); margin-bottom: var(--sp-3); }
.correct-answer-text { margin-top: var(--sp-2); }
.img-thumbnail {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--sp-1);
    background: var(--surface);
}
.lead { font-size: var(--fs-lg); color: var(--text-muted); }
.card-text { color: var(--text); }
.list-group-item-action { cursor: pointer; }
.table-bordered th, .table-bordered td { border: 1px solid var(--line); }

@media (max-width: 768px) {
    .role-toggle { width: 150px; height: 40px; }
    .role-toggle-label .slider { width: 120px; height: 32px; }
    .role-toggle input:checked + .role-toggle-label .slider { transform: translateX(24px); }
}


/* ==========================================================================
   АДАПТИВ
   ========================================================================== */
@media (max-width: 768px) {
    :root {
        --header-h: 72px;
        --fs-h1: 26px;
        --fs-h2: 22px;
        --fs-h3: 18px;
    }

    main { padding: var(--sp-4) var(--sp-3); }

    .content-container, .container, .section {
        padding: var(--sp-4);
        border-radius: var(--radius);
    }

    /* Шапка на телефоне: лого слева, кнопка темы и бургер справа — в один
       ряд, без переноса. */
    header { padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); }
    .hamburger { display: inline-flex; align-items: center; justify-content: center; }
    .desktop-menu { display: none; }
    .mobile-logo { display: flex; align-items: center; }
    nav { flex: 0 0 auto; }
    .logo-img { height: 40px; }
    .logo-text { font-size: var(--fs-base); }

    /* Тумблер в шапке скрывается ниже, рядом с описанием компонента:
       правило, поставленное здесь, перекрывалось бы более поздним
       объявлением .header-theme. На этом уже обжигались. */


    .page-header { flex-direction: column; align-items: stretch; }
    .page-header__actions .btn { flex: 1 1 auto; }

    .toolbar, .search-row { flex-direction: column; align-items: stretch; }
    .search-item { flex: 1 1 auto; }

    /* Кнопки поиска делят строку поровну и заметно отступают от полей —
       иначе на узком экране они прижимались друг к другу вплотную. */
    .search-actions {
        width: 100%;
        margin-top: var(--sp-2);
        gap: var(--sp-3);
    }
    .search-actions > .btn { flex: 1 1 0; width: auto; min-width: 0; }
    .toolbar > .btn, .toolbar > a.btn { flex: 1 1 0; min-width: 0; }

    input, textarea, select, .form-control { max-width: none; }

    .cookie-banner__actions { width: 100%; }
    .cookie-banner__actions .btn { flex: 1 1 auto; }

    .footer-container { flex-direction: column; gap: var(--sp-4); }

    .tutor-photo, .no-photo-placeholder { width: 120px; height: 180px; }
}

@media (max-width: 480px) {
    .row-actions { width: 100%; }
    .row-actions .btn, .row-actions button, .row-actions form { flex: 1 1 auto; }
    .btn { width: 100%; }
    .row-actions .btn, .pagination .btn, .btn-sm { width: auto; }
    /* Кнопка отправки стоит в одном ряду с полем ввода: во всю ширину
       она вытесняет поле в щель шириной в букву. */
    .chat-input .btn { width: auto; }

    /* Палец попадает точнее пикселя: на телефоне все кнопки не ниже
       44 пикселей — размер, при котором промахи прекращаются. */
    .btn, .btn-sm, button, input[type="submit"], input[type="button"] {
        min-height: 44px;
    }

    /* Длинная подпись переносится, а не выталкивает кнопку за экран:
       «Удалить вариант ответа» не помещался в 390 пикселей и создавал
       горизонтальную прокрутку. */
    .btn-sm {
        white-space: normal;
        max-width: 100%;
    }
}


/* --- Главная: продающие блоки -------------------------------------------
   Первый экран отвечает на три вопроса сразу — что это, кому и что
   делать дальше. Две аудитории (ученик с родителем и преподаватель)
   разведены по своим блокам: раньше на них приходился один общий
   список и одна кнопка «Зарегистрироваться». */
.cta-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
    margin-top: var(--sp-5);
}
.cta-note { margin-top: var(--sp-3); color: var(--text-muted); font-size: var(--fs-sm); }

.value-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sp-4);
    margin-top: var(--sp-4);
}
.value-card {
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius);
    padding: var(--sp-4);
}
.value-card h3 {
    font-size: var(--fs-lg);
    color: var(--accent);
    margin-bottom: var(--sp-2);
}
.value-card p { color: var(--text); margin: 0; }

/* Путь до первого занятия: нумерация своя, потому что список
   переопределён под служебные нужды, а номер здесь несёт смысл. */
.steps {
    list-style: none;
    counter-reset: step;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--sp-5);
    margin-top: var(--sp-4);
    padding-left: 0;
}
/* Номер стоит слева от подзаголовка, а не отдельной строкой над ним.
   Раньше кружок с цифрой висел сверху, а заголовок начинался под ним —
   между ними получался пустой провал, и связь номера с пунктом читалась
   не сразу. Сетка из двух колонок ставит цифру и заголовок в одну
   строку, а описание — под заголовком. */
.steps > li {
    counter-increment: step;
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: var(--sp-3);
    row-gap: var(--sp-2);
    align-items: start;
}
.steps > li > * { grid-column: 2; }
.steps > li::before {
    content: counter(step);
    grid-column: 1;
    grid-row: 1;
    align-self: center;
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--accent);
    color: var(--text-on-accent);
    font-family: var(--font-head);
    font-weight: 700;
}
.steps h3 { font-size: var(--fs-lg); margin-bottom: 0; grid-row: 1; align-self: center; }
.steps p { margin: 0; color: var(--text-muted); }

.facts {
    list-style: none;
    padding-left: 0;
    margin-top: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}
.facts > li {
    padding-left: var(--sp-5);
    position: relative;
    margin-bottom: 0;
}
.facts > li::before {
    content: "\2713";
    position: absolute;
    left: 0;
    color: var(--success);
    font-weight: 700;
}

@media (max-width: 768px) {
    .cta-row { flex-direction: column; }
    .cta-row > .btn { width: 100%; }
}

/* --- Карусель преподавателей ---------------------------------------------
   Раскладка ряда: карточки идут в строку, лишнее прячется, а прокруткой
   управляет скрипт страницы (автопрокрутка, перетаскивание, колесо).
   Эти правила жили в инлайновом <style> страницы и при переносе стилей
   в общий файл потерялись — без white-space: nowrap карточки вставали
   столбиком, и карусель переставала быть каруселью.

   Класс намеренно не .carousel: так называется компонент Bootstrap,
   который подключён с CDN, и две реализации спорили бы за одни имена. */
.tutors-carousel {
    overflow-x: hidden;
    white-space: nowrap;
    padding: var(--sp-4) 0;
    position: relative;
    cursor: grab;
    user-select: none;
}
.tutors-carousel::-webkit-scrollbar { display: none; }
.tutors-carousel:active { cursor: grabbing; }

.tutors-carousel .tutor-card {
    display: inline-block;
    vertical-align: top;
    white-space: normal;
    width: 300px;
    min-height: 350px;
    margin: 0 var(--sp-3);
    padding: var(--sp-4);
    text-align: center;
    overflow: hidden;
    transition: transform var(--transition), box-shadow var(--transition);
}
@media (hover: hover) and (pointer: fine) {
    .tutors-carousel .tutor-card:hover {
        transform: scale(1.04);
        box-shadow: var(--shadow);
    }
}
.tutors-carousel .tutor-card h3 {
    font-size: var(--fs-lg);
    margin: var(--sp-3) 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tutors-carousel .tutor-photo,
.tutors-carousel .no-photo-placeholder { margin: 0 auto var(--sp-3); }
/* Высоту списка предметов задаёт только .disciplines--scroll.
   Здесь стояла своя max-height в 50 пикселей, и она перебивала ту —
   селектор весомее. Пятьдесят пикселей при полуторном межстрочном
   расстоянии это две строки с третью: третий предмет обрезался
   посередине букв и выглядел браком, а не подсказкой «прокрути». */
.tutors-carousel .disciplines {
    display: block;
    margin: var(--sp-1) 0;
}

/* --- Свёрнутые подробности (результаты тестов) ---------------------------
   Тоже потерялось при переносе. Без max-height: 0 блок ответов был
   раскрыт всегда, и кнопка «Подробнее» ничего не сворачивала. */
.details {
    max-height: 0;
    overflow: hidden;
    padding: 0;
    margin: 0;
    transition: max-height .5s ease-in-out;
}
.details.expanded { max-height: none; }


/* --- Бургер: три палочки складываются в крестик ---------------------------
   Символ ☰ анимировать нечем: пришлось бы вращать кнопку целиком.
   Из трёх полосок крестик собирается честно — средняя гаснет, крайние
   съезжаются в центр и разворачиваются навстречу друг другу. */
.hamburger-box {
    position: relative;
    display: block;
    width: 24px;
    height: 18px;
}
.hamburger-bar {
    position: absolute;
    left: 0;
    width: 100%;
    height: 2px;
    border-radius: 2px;
    background: currentColor;
    transition: transform .3s ease, opacity .2s ease, top .3s ease;
}
.hamburger-bar:nth-child(1) { top: 0; }
.hamburger-bar:nth-child(2) { top: 8px; }
.hamburger-bar:nth-child(3) { top: 16px; }

.hamburger.is-open .hamburger-bar:nth-child(1) { top: 8px; transform: rotate(45deg); }
.hamburger.is-open .hamburger-bar:nth-child(2) { opacity: 0; }
.hamburger.is-open .hamburger-bar:nth-child(3) { top: 8px; transform: rotate(-45deg); }


/* --- Тумблер темы внизу бургер-меню --------------------------------------
   На телефоне кнопка темы в шапке мелкая и без подписи. Здесь она
   последним пунктом меню, подписана и выглядит как обычный тумблер. */
/* Показ тумблера задан здесь, рядом с самим компонентом, а не в общем
   блоке адаптива: тот идёт по файлу раньше, и правило display:none,
   стоявшее ниже, перекрывало его при равной специфичности — тумблер
   пропадал и с телефона тоже, а кнопка в шапке там уже скрыта. */
.navbar-theme { display: none; }
@media (max-width: 768px) {
    .navbar-theme { display: flex; }
}

/* Тот же тумблер в шапке — для широких экранов, где бургер-меню нет.
   Разметка общая с мобильным, отличается только рамкой и отступами:
   в шапке он стоит в строке, а не отдельным блоком внизу списка. */
.header-theme {
    display: flex;
    padding: 0;
    margin: 0;
    border-top: 0;
    gap: var(--sp-2);
}
.header-theme .navbar-theme__label { font-size: var(--fs-sm); }

/* На телефоне тумблер живёт внизу бургер-меню: в шапке рядом с бургером
   он мелкий, без подписи и путается с ним. Правило стоит после
   объявления компонента — иначе оно проиграет по порядку, и на узком
   экране остались бы оба переключателя. */
@media (max-width: 768px) {
    .header-theme { display: none; }
}
.navbar-theme {
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-4);
    margin-top: var(--sp-2);
    /* Своей черты у тумблера нет: она висела в пустоте между последним
       пунктом меню и подписью, не совпадая по ширине ни с одной другой
       линией. Границы самих пунктов при этом остаются на месте — они
       разделяют список и к тумблеру отношения не имеют. Правило не
       удалено, а обнулено, чтобы было видно: линия здесь была и её
       сняли намеренно. */
    border-top: 0;
}
.navbar-theme__label {
    color: var(--text-on-dark);
    font-weight: 600;
}
.theme-switch {
    background: none;
    border: none;
    padding: var(--sp-1);
    min-height: 44px;
}
.theme-switch:hover { background: none; }
.theme-switch:active { transform: none; }
.theme-switch__track {
    display: block;
    width: 52px;
    height: 30px;
    border-radius: var(--radius-pill);
    background: rgba(255, 255, 255, .22);
    border: 1px solid rgba(255, 255, 255, .35);
    transition: background-color var(--transition);
    position: relative;
}
.theme-switch__thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--text-on-dark);
    transition: transform .25s ease;
}
.theme-switch.is-on .theme-switch__track { background: var(--accent); }
.theme-switch.is-on .theme-switch__thumb { transform: translateX(22px); }


/* --- Закреплённая кнопка «Назад» -----------------------------------------
   Раньше она стояла в конце страницы: чтобы вернуться, приходилось
   пролистывать весь список до низа (или до верха, смотря где стояла).
   Теперь она висит в левом нижнем углу и доступна с любого места.
   Отступ снизу учитывает баннер cookie, пока тот на экране. */
/* Кнопка возврата.
   В разметке она стоит последней — так проще: не нужно править её место
   в тридцати шаблонах. Наверх её поднимает order у гибкой раскладки
   main, а sticky оставляет на виду при прокрутке, прижимая под шапку.
   Раньше кнопка висела в нижнем левом углу экрана. */
.btn-back {
    order: -1;
    justify-self: start;
    position: sticky;
    top: calc(var(--header-h) + var(--sp-2));
    margin-bottom: var(--sp-4);
    z-index: 900;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 10px 18px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font-weight: 600;
    box-shadow: var(--shadow);
    -webkit-touch-callout: none;
    touch-action: manipulation;
}
.btn-back::before { content: "\2039"; font-size: var(--fs-lg); line-height: 1; }
@media (hover: hover) and (pointer: fine) {
    .btn-back:hover {
        background: var(--surface-2);
        color: var(--text);
        border-color: var(--accent);
        text-decoration: none;
    }
}
.btn-back:active { transform: scale(.97); }

/* ==========================================================================
   14. СЕНСОРНЫЙ ВВОД

   На телефоне указателя нет, и :hover срабатывает от касания — подсветка
   остаётся висеть на кнопке или ссылке, пока не тронешь что-то другое.
   Здесь ховер-состояния возвращаются к обычному виду: реакцией на палец
   служит :active, объявленный рядом с самими компонентами.
   ========================================================================== */
@media (hover: none) {
    button:hover, input[type="submit"]:hover, input[type="button"]:hover,
    .btn:hover { background-color: var(--surface-2); }

    .btn-primary:hover, .btn-save:hover, .btn-success:hover, .btn-add:hover,
    input[type="submit"]:hover {
        background-color: var(--accent);
        border-color: var(--accent);
    }
    .btn-delete:hover, .btn-danger:hover,
    .delete-account-btn:hover, form.delete-form button:hover {
        background-color: var(--danger);
        border-color: var(--danger);
    }
    .btn-outline:hover, .btn-secondary:hover, .btn-outline-secondary:hover,
    .btn-outline-primary:hover, .btn-edit:hover, .change-password-btn:hover,
    .btn-warning:hover, .btn-info:hover {
        background-color: transparent;
        border-color: var(--accent);
        color: var(--accent);
    }
    .btn-quiet:hover, .btn-light:hover { background-color: var(--surface-2); }

    a:hover { text-decoration: none; }
    .knowledge-link:hover { background-color: var(--accent); }
    .news-title:hover { color: var(--accent); }
    .desktop-menu li a:hover, .desktop-menu .dropdown-toggle:hover,
    .navbar ul li a:hover, .navbar-dropdown-toggle:hover,
    .hamburger:hover, .theme-toggle:hover { background: none; }
    .section-image-bot:hover { transform: none; }
}

/* ==========================================================================
   15. ПЕРЕКРЫТИЕ СИНЕГО ИЗ BOOTSTRAP

   Bootstrap подключён с CDN ради сетки и модальных окон, но тащит с
   собой свою фирменную синеву #0d6efd. Она вылезала там, где у нас нет
   своего правила: флажки и переключатели при отметке, кольцо фокуса у
   кнопок и полей, выбранный пункт списка. На кирпично-кремовой гамме
   это выглядело чужеродно — синяя вспышка при нажатии.
   ========================================================================== */
/* --- Кнопки: перекрываем переменные, а не отдельные правила -------------
   Bootstrap 5.3 собирает вид кнопки из переменных (--bs-btn-bg и её
   родни), а состояния объявляет селекторами вида .btn:first-child:active
   — это специфичность (0,2,0), выше нашего .btn-primary (0,1,0). Поэтому
   наш цвет держался в покое, но на нажатии выигрывал бутстраповский:
   «Зарегистрироваться» на главной вспыхивала синим (btn-primary), а на
   самой регистрации — зелёным (btn-success).

   Спорить специфичностью здесь бессмысленно: у Bootstrap состояний
   больше, чем можно перечислить. Вместо этого подменяются сами
   переменные — тогда его собственные правила рисуют наш цвет. */
.btn {
    --bs-btn-focus-shadow-rgb: 122, 46, 30;
    --bs-btn-active-shadow: none;
    --bs-btn-box-shadow: none;
}

.btn-primary, .btn-save, .btn-success, .btn-add {
    --bs-btn-color: var(--text-on-accent);
    --bs-btn-bg: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--text-on-accent);
    --bs-btn-hover-bg: var(--accent-hover);
    --bs-btn-hover-border-color: var(--accent-hover);
    --bs-btn-active-color: var(--text-on-accent);
    --bs-btn-active-bg: var(--accent-hover);
    --bs-btn-active-border-color: var(--accent-hover);
    --bs-btn-disabled-color: var(--text-on-accent);
    --bs-btn-disabled-bg: var(--accent);
    --bs-btn-disabled-border-color: var(--accent);
}

.btn-delete, .btn-danger, .delete-account-btn {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--danger);
    --bs-btn-border-color: var(--danger);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--danger-hover);
    --bs-btn-hover-border-color: var(--danger-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--danger-hover);
    --bs-btn-active-border-color: var(--danger-hover);
}

.btn-outline, .btn-secondary, .btn-outline-secondary,
.btn-outline-primary, .btn-edit, .change-password-btn,
.btn-warning, .btn-info {
    --bs-btn-color: var(--accent);
    --bs-btn-bg: transparent;
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--accent);
    --bs-btn-hover-bg: var(--accent-soft);
    --bs-btn-hover-border-color: var(--accent);
    --bs-btn-active-color: var(--accent);
    --bs-btn-active-bg: var(--accent-soft);
    --bs-btn-active-border-color: var(--accent);
}

.btn-quiet, .btn-light {
    --bs-btn-color: var(--text);
    --bs-btn-bg: var(--surface-2);
    --bs-btn-border-color: var(--line);
    --bs-btn-hover-color: var(--text);
    --bs-btn-hover-bg: var(--surface-3);
    --bs-btn-hover-border-color: var(--line-strong);
    --bs-btn-active-color: var(--text);
    --bs-btn-active-bg: var(--surface-3);
    --bs-btn-active-border-color: var(--line-strong);
}

/* Тот же приём для состояния нажатия «в лоб» — на случай, если разметка
   попадёт под селектор, которого нет в списке переменных выше. */
.btn-primary:active, .btn-save:active, .btn-success:active, .btn-add:active,
.btn-primary.active, .btn-success.active {
    background-color: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
}
.btn-delete:active, .btn-danger:active, .delete-account-btn:active {
    background-color: var(--danger-hover);
    border-color: var(--danger-hover);
    color: #fff;
}
.btn-outline:active, .btn-secondary:active, .btn-outline-secondary:active,
.btn-outline-primary:active, .btn-edit:active, .change-password-btn:active,
.btn-warning:active, .btn-info:active {
    background-color: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
}
.btn-quiet:active, .btn-light:active {
    background-color: var(--surface-3);
    border-color: var(--line-strong);
    color: var(--text);
}

/* --- Таблицы: цвет текста из нашей палитры -------------------------------
   Класс .table собирает вид из переменных --bs-table-*, а те привязаны к
   собственной светлой теме Bootstrap: --bs-table-color там равен
   тёмно-серому #212529 и подставляется независимо от нашей темы. В
   тёмной теме строки «Моих результатов» и других таблиц оказывались
   чёрными на тёмном фоне — текст было не прочитать.

   Наши правила задавали таблице ширину и рамки, но не цвет, поэтому
   побеждал Bootstrap. Подменяем сами переменные, как и у кнопок. */
.table {
    --bs-table-color: var(--text);
    --bs-table-bg: transparent;
    --bs-table-border-color: var(--line);
    --bs-table-striped-color: var(--text);
    --bs-table-striped-bg: var(--surface-2);
    --bs-table-active-color: var(--text);
    --bs-table-active-bg: var(--surface-2);
    --bs-table-hover-color: var(--text);
    --bs-table-hover-bg: var(--surface-2);
    color: var(--text);
}
.table > :not(caption) > * > * {
    color: var(--text);
    background-color: transparent;
    box-shadow: none;
}
.table thead th, .table th { color: var(--text); }

.form-check-input:checked {
    background-color: var(--accent);
    border-color: var(--accent);
}
.form-check-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.form-check-input:focus:not(:checked) { border-color: var(--line-strong); }
.form-check-input { border-color: var(--line-strong); }
.form-check-label { color: var(--text); }

.btn:focus, .btn:focus-visible,
.btn-check:focus + .btn, .btn-check:checked + .btn {
    box-shadow: none;
}
.btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.form-control:focus, .form-select:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.list-group-item.active,
.list-group-item-action.active {
    background-color: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
}
.list-group-item-action:focus,
.list-group-item-action:active {
    background-color: var(--surface-2);
    color: var(--text);
}

.page-link, .page-link:hover, .page-link:focus {
    color: var(--accent);
    background-color: var(--surface);
    border-color: var(--line);
    box-shadow: none;
}
.page-item.active .page-link {
    background-color: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
}

.nav-link, .nav-link:hover, .nav-link:focus { color: var(--accent); }
.nav-tabs .nav-link.active {
    color: var(--text);
    background-color: var(--surface);
    border-color: var(--line) var(--line) var(--surface);
}

/* Модальные окна Bootstrap — на наших поверхностях, а не на белом */
.modal-content {
    background-color: var(--surface);
    color: var(--text);
    border: 1px solid var(--line);
}
.modal-header, .modal-footer { border-color: var(--line); }
.btn-close { filter: none; }
:root[data-theme="dark"] .btn-close { filter: invert(1) grayscale(100%) brightness(200%); }

/* Выделение текста и подсветка совпадений */
mark, .mark { background-color: var(--warning-soft); color: var(--text); }


/* Долгое нажатие по ссылке-кнопке вызывало системное меню и позволяло
   перетащить её в поисковую строку. Для интерфейсных ссылок это лишнее;
   обычный текст остаётся выделяемым и копируемым. */
.btn, .highlight-link, .knowledge-link, .hamburger, .theme-toggle,
.desktop-menu a, .navbar a, .navbar-dropdown-toggle, .logo-link,
.pagination a, .row-actions a {
    -webkit-touch-callout: none;
    -webkit-user-drag: none;
    touch-action: manipulation;
}


/* ==========================================================================
   16. КОНСТРУКТОР ТЕСТА

   Отметка «правильный ответ» выглядит по типу вопроса: круг для одного
   ответа, квадрат для нескольких. В разметке это всегда чекбокс — форма
   ждёт отдельное поле на каждый вариант, а настоящие radio с общим
   именем отправили бы одно значение на весь вопрос. Круглый вид даёт
   это правило, единственность выбора — test_builder.js.
   ========================================================================== */
.options--single .form-check-input[type="checkbox"] {
    border-radius: 50%;
}
.options--single .form-check-input[type="checkbox"]:checked {
    /* Точка внутри круга вместо галочки — как у обычного переключателя */
    background-image: radial-gradient(circle, var(--text-on-accent) 34%, transparent 38%);
}
.options .form-check { margin-top: var(--sp-2); }
.options .form-check-label { font-weight: 400; }


/* ==========================================================================
   17. БЛОК ВОПРОСОВ И ОТВЕТОВ

   Сделан на <details>: раскрывается без единой строки скрипта, работает
   при выключенном JavaScript, и поисковый робот видит текст ответа даже
   в свёрнутом виде — а это условие, при котором ответ попадает прямо в
   выдачу.
   ========================================================================== */
.faq { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-4); }

.faq-item {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-2);
    padding: var(--sp-3) var(--sp-4);
}
.faq-item > summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--text);
    list-style: none;
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    /* Долгое нажатие не должно вызывать системное меню */
    -webkit-touch-callout: none;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after {
    content: "+";
    margin-left: auto;
    color: var(--accent);
    font-size: var(--fs-lg);
    line-height: 1;
    flex-shrink: 0;
    transition: transform var(--transition);
}
.faq-item[open] > summary::after { content: "−"; }
.faq-item > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.faq-item > p {
    margin: var(--sp-3) 0 0;
    color: var(--text-muted);
}


/* ==========================================================================
   18. СПИСКИ ЛЮДЕЙ, ССЫЛКИ И МЕЛКИЕ ПРАВКИ РАСКЛАДКИ
   ========================================================================== */

/* --- Строка с человеком и действиями (панель доступа, список репетиторов)
   Имя и почта сверху, кнопки отдельной строкой под ними. Раньше всё
   стояло в один ряд через justify-content-between, и длинная почта
   выталкивала кнопки за пределы контейнера. Теперь их положение от
   длины адреса не зависит вовсе. */
.person-row {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-3);
}
.person-row__info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.person-row__name { font-weight: 600; color: var(--text); }
.person-row__email {
    font-size: var(--fs-sm);
    color: var(--text-muted);
    /* Длинная почта переносится, а не растягивает строку */
    overflow-wrap: anywhere;
}
.person-row__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
}
.person-row__actions .btn { flex: 0 0 auto; }
.person-row__delete {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin: 0;
    max-width: none;
}
.person-row__delete input { width: 200px; max-width: 100%; margin: 0; }

@media (max-width: 768px) {
    .person-row__actions, .person-row__delete { width: 100%; }
    .person-row__actions > .btn, .person-row__delete input,
    .person-row__delete .btn { flex: 1 1 100%; width: 100%; }
}

/* --- Длинная надпись на кнопке.
   У кнопок стоит white-space: nowrap, чтобы короткие подписи не рвались
   на две строки. Но «Зарегистрироваться преподавателем» на узком экране
   в строку не помещается и вылезала за край. Крупным кнопкам и кнопкам
   в ряду призывов разрешаем перенос. */
.btn-lg, .cta-row .btn, .person-row__actions .btn {
    white-space: normal;
    text-align: center;
    overflow-wrap: anywhere;
}

/* --- Ссылки в тексте выделяются подчёркиванием.
   Раньше ссылка отличалась только цветом: рядом с галочкой согласия её
   принимали за обычный текст и не открывали — а это документы, которые
   человек принимает. Подчёркивание снимаем только там, где ссылка и так
   очевидна: в меню, кнопках, карточках. */
.consent-label a,
.faq-item p a,
.facts a,
main p a,
main li a:not(.btn):not(.highlight-link):not(.knowledge-link) {
    text-decoration: underline;
    text-underline-offset: 2px;
    font-weight: 600;
}
.consent-label a:hover, .faq-item p a:hover, .facts a:hover, main p a:hover {
    text-decoration-thickness: 2px;
}
/* Ссылка-возврат и элементы навигации подчёркивания не получают */
.btn, .btn-back, .highlight-link, .knowledge-link,
nav a, header a, footer a, .pagination a, .card-list a,
.tutors-carousel a { text-decoration: none; }
footer a { text-decoration: underline; text-underline-offset: 2px; }

/* --- Панель контактов: рамка вокруг поиска и списка была лишней,
   она дублировала рамку самой страницы. */
.left-panel, .right-panel {
    background: none !important;
    box-shadow: none !important;
}
.left-panel { border-right: 1px solid var(--line); }
.right-panel { background: var(--surface) !important; border-radius: var(--radius); }
@media (max-width: 768px) {
    .left-panel { border-right: none; border-bottom: 1px solid var(--line); }
}

/* --- Общее расписание на телефоне.
   Колонка времени и названия дней обрезались: таблица ужималась под
   ширину экрана. Даём ей прокрутку и не позволяем сжимать первую
   колонку и заголовки. */
.schedule-table { min-width: 640px; }
.schedule-table th, .schedule-table td { white-space: nowrap; }
.schedule-table th:first-child, .schedule-table td:first-child {
    position: sticky;
    left: 0;
    background: var(--surface);
    z-index: 1;
    min-width: 72px;
}
@media (max-width: 768px) {
    .schedule-table { font-size: var(--fs-sm); }
    .schedule-table th, .schedule-table td { padding: var(--sp-2); }
}


/* ==========================================================================
   19. ЕДИНЫЕ ОТСТУПЫ МЕЖДУ ДЕЙСТВИЯМИ

   Кнопки, идущие друг за другом, стояли с разным зазором: где-то его
   давал Bootstrap своим mb-3, где-то не было вовсе, и на настройке
   профиля они слипались. Расстояние задаёт одна переменная — поменять
   его везде разом можно в одну строку.
   ========================================================================== */
:root {
    --gap-actions: var(--sp-3);   /* между кнопками в ряду */
    --gap-stack: var(--sp-4);     /* между блоками действий по вертикали */
}

/* Любые соседние кнопки внутри контейнера получают одинаковый зазор,
   даже если разметка их ничем не оборачивает. */
.button-group,
.row-actions,
.cta-row,
.person-row__actions,
.search-actions,
.form-actions,
.actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--gap-actions);
    align-items: center;
}

/* Кнопки, стоящие подряд без обёртки: страница настройки профиля, меню
   разделов. Отступ ставится сверху, чтобы первая кнопка не отрывалась
   от того, что над ней. */
.content-container > .btn + .btn,
.content-container > form + form,
.content-container > .btn + form,
.content-container > form + .btn,
.section > .btn + .btn,
.section > form + form,
.section > .btn + form,
.section > form + .btn {
    margin-top: var(--gap-stack);
}

/* Отдельно стоящая кнопка или форма-кнопка отделяется от текста сверху */
.content-container > form,
.section > form { margin-top: var(--gap-stack); }
.content-container > form:first-child,
.section > form:first-child { margin-top: 0; }

@media (max-width: 480px) {
    /* На узком экране кнопки идут столбиком — зазор чуть больше, иначе
       читаются как одна полоса. */
    :root { --gap-actions: var(--sp-3); }
}

/* --- Предметы в карточке репетитора.
   У кого-то два предмета, у кого-то восемь: без ограничения высоты
   карточки разъезжались, а кнопка «Подробнее» оказывалась на разной
   высоте. Список прокручивается, если не помещается. */
.disciplines--scroll {
    display: block;
    /* Ровно три строки: у большинства преподавателей предметов не
       больше трёх, и тогда список виден целиком без прокрутки. Высота
       кратна строке — иначе снизу остаётся полоска обрезанных букв. */
    min-height: 4.5em;
    max-height: 4.5em;
    overflow-y: auto;
    text-align: left;
    padding-right: var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text-muted);
    line-height: 1.5;
    /* Тонкая полоса прокрутки, чтобы не съедала место в карточке */
    scrollbar-width: thin;
}
.disciplines--scroll::-webkit-scrollbar { width: 4px; }
.disciplines--scroll::-webkit-scrollbar-thumb {
    background: var(--line-strong);
    border-radius: 2px;
}

/* --- Пояснение под видеозвонком.
   Класс text-muted от Bootstrap привязан к его собственной светлой теме,
   поэтому в тёмной текст оставался почти чёрным. */
.text-muted, .text-secondary { color: var(--text-muted) !important; }
.text-body { color: var(--text) !important; }


/* ==========================================================================
   20. ТЕСТЫ: НАСТРОЙКИ РАЗБОРА, ПОПЫТКИ, РЕЗУЛЬТАТЫ
   ========================================================================== */

/* Пояснение под полем. Настройки разбора без объяснения выглядят набором
   переключателей, и выбирать между ними приходится наугад. */
.form-hint {
    margin: calc(var(--sp-1) * -1) 0 var(--sp-3);
    font-size: var(--fs-sm);
    color: var(--text-muted);
    max-width: 60ch;
}
.review-settings { margin-bottom: var(--sp-5); }
.review-settings legend { font-size: var(--fs-lg); }

/* Замечание в результатах: почему разбора нет или почему нет ответов. */
.review-note {
    padding: var(--sp-3);
    margin-bottom: var(--sp-3);
    border-left: 3px solid var(--accent);
    background: var(--accent-soft);
    color: var(--text);
    border-radius: var(--radius);
}

/* Итог по вопросу отмечается не только цветом: при дальтонизме и на
   чёрно-белой печати цвет не читается. */
.answer-right { color: var(--success); font-weight: 600; }
.answer-right::before { content: "✓ "; }
.answer-wrong { color: var(--danger); font-weight: 600; }
.answer-wrong::before { content: "✗ "; }

/* Сколько попыток осталось — на самой странице теста, до начала работы. */
.attempts-note {
    display: inline-block;
    padding: var(--sp-2) var(--sp-3);
    margin-bottom: var(--sp-4);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--surface-2);
    font-weight: 600;
}

/* Строка ученика в выдаче доступа: имя слева, лимит попыток справа.
   На узком экране части встают друг под друга — поле ввода рядом с
   длинным ФИО иначе выдавливается за край. */
.access-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--line);
}
.access-row__person {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex: 1 1 220px;
    margin: 0;
    font-weight: 400;
    cursor: pointer;
}
.access-row__limit {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    flex: 0 1 auto;
}
.access-row__limit label { margin: 0; white-space: nowrap; }
.access-row__limit .form-control { width: 8rem; margin: 0; }
.access-row__used { font-size: var(--fs-sm); color: var(--text-muted); white-space: nowrap; }

@media (max-width: 768px) {
    .access-row__limit { flex: 1 1 100%; }
    .access-row__limit .form-control { width: auto; flex: 1 1 0; min-width: 0; }
}


/* --- Окно выбора учеников для приглашения на звонок ---------------------- */
.invite-overlay {
    position: fixed;
    inset: 0;
    z-index: 1100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
    background: rgba(0, 0, 0, .5);
}
.invite-overlay[hidden] { display: none; }

.invite-dialog {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
    width: 100%;
    max-width: 520px;
    /* Высота ограничена, прокручивается только список: иначе на телефоне
       кнопка «Отправить» уезжает под нижний край экрана. */
    max-height: min(80vh, 640px);
    padding: var(--sp-4);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}
.invite-dialog__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
}
.invite-dialog__head h2 { font-size: var(--fs-lg); margin: 0; }
.invite-dialog .form-control { margin-bottom: 0; max-width: none; }
.invite-dialog__foot {
    display: flex;
    justify-content: flex-end;
    gap: var(--gap-actions);
}

/* Выбранные поднимаются наверх окна: в длинном списке иначе не видно,
   кого уже отметил. */
.invite-chosen {
    padding: var(--sp-3);
    background: var(--surface-2);
    border-radius: var(--radius);
}
.invite-chosen__label {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--text-muted);
}
.invite-chosen__list { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.invite-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-1);
    padding: 4px 12px;
    background: var(--accent);
    color: var(--text-on-accent);
    border: none;
    border-radius: var(--radius-pill);
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
}
/* Крестик рисуется здесь, а не подставляется в текст: имя ученика ставится
   через textContent, и лишний символ попал бы в само имя. */
.invite-chip::after { content: "✕"; font-size: var(--fs-xs); opacity: .8; }
.invite-chip:hover, .invite-chip:active { background: var(--accent-hover); }

.invite-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    margin: 0;
    padding: 0;
    list-style: none;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.invite-list__item {
    display: block;
    width: 100%;
    padding: 10px var(--sp-3);
    background: none;
    border: none;
    border-radius: 0;
    color: var(--text);
    font-weight: 400;
    text-align: left;
    cursor: pointer;
}
.invite-list__item:hover, .invite-list__item:active { background: var(--surface-2); }
.invite-list__empty {
    padding: var(--sp-4);
    color: var(--text-muted);
    text-align: center;
}

/* Ссылка внутри сообщения. Подписью служит сам адрес, поэтому длинный
   адрес обязан переноситься — иначе он растягивает пузырёк сообщения за
   край экрана. */
.message-link {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
    overflow-wrap: anywhere;
}
.message-link:hover { color: var(--accent-hover); }
/* Метка «уводит на другой сайт»: в переписке со школьниками полезно
   видеть, что ссылка ведёт не сюда. */
.message-link--external::after { content: " ↗"; font-size: var(--fs-xs); }

/* Просьба к репетитору подтвердить согласие на публикацию анкеты.
   Заметная, но не тревожная: это обычное действие, а не ошибка. */
.consent-notice {
    margin-bottom: var(--sp-5);
    padding: var(--sp-4);
    border: 1px solid var(--accent);
    border-left-width: 4px;
    border-radius: var(--radius);
    background: var(--accent-soft);
}
.consent-notice h2 { font-size: var(--fs-lg); margin-bottom: var(--sp-2); }
.consent-notice p { margin-bottom: var(--sp-3); max-width: 70ch; }

/* Полноэкранный режим на телефоне не нужен: браузер там и так занимает
   весь экран, а сама конференция открывается во всю ширину. Кнопка лишь
   отнимала место у ряда действий и переносила его на две строки. */
@media (max-width: 768px) {
    #fullscreen-btn { display: none; }
}

/* --- Составление теста с ИИ ---------------------------------------------- */

/* Свёрнутый блок над конструктором. Свёрнутый намеренно: основной способ
   создать тест — набрать его руками, и разворачивать окно должен тот,
   кому оно нужно, а не наоборот. */
.ai-box {
    background: var(--surface-2);
    /* Рамка цветом действия, а не разделителя: свёрнутый блок терялся
       среди полей формы, и найти составление с ИИ можно было только
       зная, что оно есть. */
    border: 2px solid var(--accent);
    border-radius: var(--radius-md);
    padding: var(--sp-3) var(--sp-4);
    margin-bottom: var(--sp-5);
}
.ai-box > summary {
    cursor: pointer;
    font-family: var(--font-head);
    font-weight: 700;
    font-size: var(--fs-lg);
    color: var(--accent);
    padding: var(--sp-2) 0;
    display: flex;
    align-items: center;
    gap: var(--sp-2);
}
.ai-box > summary::marker { content: ''; }
.ai-box > summary::before {
    content: '✦';
    font-size: 1.1em;
}
.ai-box > summary::after {
    content: 'развернуть';
    margin-left: auto;
    font-family: var(--font-body);
    font-size: var(--fs-sm);
    font-weight: 400;
    color: var(--text-muted);
}
.ai-box[open] > summary::after { content: 'свернуть'; }
.ai-box[open] > summary { margin-bottom: var(--sp-3); }
.ai-box fieldset { margin-bottom: var(--sp-3); }

/* Два поля рядом, пока хватает ширины. minmax(0, …) обязателен: без него
   колонка не сжимается ниже содержимого и раскладка уезжает за край. */
.ai-params {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
    gap: var(--sp-3);
}

/* Список своих материалов. У преподавателя их может быть много, поэтому
   прокручивается сам список — пояснение над ним остаётся на месте. */
.ai-source-list {
    max-height: 14rem;
    overflow-y: auto;
}
.ai-source-list .form-check {
    margin-bottom: var(--sp-1);
}
/* Поиск прячет строки атрибутом hidden, но у .form-check задан
   display: flex, а у .form-hint — display: block. Оба сильнее атрибута,
   и отфильтрованное осталось бы на виду вместе с надписью «ничего не
   нашлось». */
.ai-source-list .form-check[hidden],
.ai-source-list .form-hint[hidden] { display: none; }

/* --- Вопросы с пропусками и на соответствие ------------------------------ */

/* Пропуски идут внутри предложения, поэтому строка должна переноситься
   как обычный текст, а списки выбора — стоять по базовой линии слов. */
.gaps-question {
    line-height: 2.2;
    font-size: var(--fs-base);
}
.gap-select {
    display: inline-block;
    width: auto;
    min-width: 8rem;
    max-width: 100%;
    margin: 0 var(--sp-1);
    vertical-align: baseline;
}

/* Соответствие: слева часть задания, справа выбор. На узком экране
   колонки складываются одна под другую — иначе выбор сжимается до
   нечитаемого. */
.match-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sp-3);
    align-items: center;
    padding: var(--sp-2) 0;
    border-bottom: 1px solid var(--line);
}
.match-row:last-child { border-bottom: 0; }
.match-left { font-weight: 600; }

@media (max-width: 600px) {
    .match-row { grid-template-columns: minmax(0, 1fr); gap: var(--sp-1); }
}

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

   Ответ в тесте бывает длинным: формулировка теоремы, предложение
   целиком. В однострочном поле видно только его хвост, и проверить
   набранное невозможно, не прокручивая текст курсором. */
.option textarea.form-control {
    min-height: 3.4em;
    /* Тянуть вручную некуда: высота растёт до предела, дальше прокрутка.
       Свободное растягивание ломало бы ряд из нескольких вариантов. */
    resize: vertical;
    max-height: 8em;
}

/* Кнопка сохранения формы: отделена от кнопок, которые лишь дополняют
   форму («добавить вопрос», «добавить вариант»). Рядом их легко
   перепутать, а последствия у них разные — одна достраивает форму,
   другая заканчивает работу. */
.form-submit {
    margin-top: var(--sp-5);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--line);
}

/* Заголовок над группой вариантов одного пропуска. Он не просто
   нумерует: подпись говорит, что получится у ученика — список выбора
   или поле для ввода. Вид пропуска задаётся отметками, и видеть его
   последствия преподаватель должен здесь, а не открывая тест. */
.gap-head {
    margin: var(--sp-4) 0 var(--sp-2);
    font-weight: 600;
    color: var(--text-muted);
    font-size: var(--fs-sm);
}
.gap-head:first-child { margin-top: 0; }

/* Поле, куда ученик вписывает пропущенное слово. */
.gap-input {
    display: inline-block;
    width: auto;
    min-width: 8rem;
    max-width: 100%;
    margin: 0 var(--sp-1);
    vertical-align: baseline;
}

/* --- Соответствие: расстановка правых частей ----------------------------- */

/* Без скриптов расставлять нечем — там показываются обычные списки
   выбора, а этот блок скрыт. Метку .js ставит base.html до отрисовки. */
.js-only { display: none; }
html.js .js-only { display: block; }

.match-pick {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: grab;
    /* Текст внутри не выделяется мышью: при перетаскивании выделение
       мешает и выглядит как сбой. */
    user-select: none;
}
.match-pick:active { cursor: grabbing; }
.match-pick:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.match-pick.is-dragging { opacity: .5; }
.match-row.is-over .match-pick { border-color: var(--accent); }

.match-grip { color: var(--text-muted); }
.match-text { flex: 1 1 auto; min-width: 0; }

.match-moves { display: flex; gap: var(--sp-1); flex: 0 0 auto; }
.match-moves button {
    /* Размер сенсорной цели: пальцем по стрелке в 20 пикселей не
       попасть, а это основной способ расставить строки на телефоне. */
    min-width: 40px;
    min-height: 40px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text);
    font-size: var(--fs-lg);
    line-height: 1;
}
.match-moves button:active { background: var(--surface-3); }

/* Предпросмотр формулы под полем конструктора: преподаватель видит то,
   что увидит ученик, ещё до сохранения теста. */
.math-preview {
    margin-top: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    background: var(--surface-2);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-sm);
    font-size: var(--fs-base);
    /* Длинная формула не должна растягивать форму за край экрана. */
    overflow-x: auto;
}
.math-preview[hidden] { display: none; }

/* Формула в строке вопроса не должна ломать высоту строки на телефоне. */
.katex { font-size: 1.05em; }
.katex-display { overflow-x: auto; overflow-y: hidden; padding: var(--sp-1) 0; }

/* Предпросмотр теста: как его увидит ученик. Рамкой и отступом отделён
   от формы, чтобы не спутать его с самим конструктором. */
.test-preview {
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius);
    padding: var(--sp-4);
    margin-bottom: var(--sp-4);
    background: var(--surface);
}
.test-preview > .form-hint { margin-bottom: var(--sp-4); }

/* Действие в строке списка — «Открыть» у материала, «Выбрать» у
   преподавателя.

   Здесь стояла метка Bootstrap (.badge.bg-primary): ярко-синяя, чужая
   для сайта и похожая скорее на счётчик, чем на кнопку. Цвет берётся из
   темы, размер — сенсорный: по строке списка чаще попадают пальцем. */
.list-action {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    min-height: 32px;
    padding: var(--sp-1) var(--sp-3);
    border: 1px solid var(--accent);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--accent);
    font-size: var(--fs-sm);
    font-weight: 600;
    white-space: nowrap;
}
.list-group-item:hover .list-action,
.list-group-item-action:hover .list-action {
    background: var(--accent);
    color: var(--text-on-dark);
}

/* --- Учебные материалы ----------------------------------------------------

   Имя файла подчёркнуто: по нему нажимают, чтобы открыть материал, и
   без подчёркивания оно ничем не отличалось от обычного текста строки.  */
.material-link {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* Просмотр PDF прямо на странице. Высота задана в единицах экрана: при
   фиксированной в пикселях на телефоне остаётся полоска в два абзаца,
   на большом мониторе — пустое поле снизу. */
.material-view {
    display: block;
    width: 100%;
    height: 75vh;
    min-height: 320px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
}

/* Заголовки-переключатели сортировки. Ссылка внутри заголовка таблицы
   не должна выглядеть ссылкой в тексте: подчёркивание в шапке читается
   как опечатка, а цвет — как выделенный столбец. */
.sort-link {
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}
.sort-link:hover { text-decoration: underline; }
.sort-active { color: var(--accent); }

/* Закрытая переписка: вместо поля ввода — объяснение, почему его нет. */
.chat-closed {
    margin: 0;
    padding: var(--sp-3) var(--sp-4);
    border-top: 1px solid var(--line);
    color: var(--text-muted);
    font-size: var(--fs-sm);
}

/* Подбор преподавателя: выделен рамкой, как и блок составления теста с
   ИИ, — чтобы читалось как отдельное предложение, а не как часть списка.

   Селектор с .content-container намеренно: там секции лишены рамки и
   фона общим правилом, и одного класса не хватает по точности — рамка
   просто не появлялась. */
.content-container .section.match-form,
.content-container .section.match-result {
    border: 1px solid var(--accent);
    border-radius: var(--radius);
    padding: var(--sp-4);
}
.match-result .list-group-item p { color: var(--text-muted); }

/* Уточнения подбора: предмет, возраст, пол, цена. В ряд, пока хватает
   ширины, и в столбец на телефоне. Поля здесь короткие, и каждое во всю
   ширину растянуло бы форму на два экрана. */
.match-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 0 var(--sp-4);
}
.match-fields .form-control,
.match-fields .form-select { max-width: none; }

/* «От» и «до» — одно поле по смыслу, поэтому стоят рядом. */
.price-range { display: flex; gap: var(--sp-2); }
.price-range .form-control { margin-bottom: var(--sp-3); }

/* --- Отзывы ---------------------------------------------------------------

   Отзыв — блок, а не строка списка: у него автор, дата, текст и иногда
   ответ преподавателя, и всё это должно читаться как один кусок.  */
.review {
    padding: var(--sp-4) 0;
    border-top: 1px solid var(--line);
}
.review-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-2);
    margin-bottom: var(--sp-2);
}
/* Звёзды набраны символами, а не картинкой: одна строка вместо пяти
   запросов, и они масштабируются вместе с текстом. Рядом стоит скрытая
   подпись «Оценка N из 5» — для чтения с экрана символы бесполезны. */
.review-stars {
    color: var(--accent);
    letter-spacing: 2px;
    white-space: nowrap;
}
/* Ответ преподавателя отступом и полосой: видно, что это другой голос,
   а не продолжение отзыва. */
.review-reply {
    margin: var(--sp-3) 0 0 var(--sp-4);
    padding-left: var(--sp-3);
    border-left: 3px solid var(--line);
}
.rating-line { font-size: var(--fs-lg); }
.rating-value { font-size: 1.6em; color: var(--accent); }

/* --- Список новостей ------------------------------------------------------

   Новости выводятся блоками .section, а вложенная секция намеренно
   прозрачна — иначе получалась бы коробка в коробке. Из-за этого
   новости не отделялись ни от заголовка страницы, ни друг от друга:
   шла сплошная лента текста. Здесь рамка возвращается именно им. */
.content-container .section.news-item {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4);
    margin-bottom: var(--sp-4);
}
.news-list { margin-top: var(--sp-5); }

/* Заголовок новости — ссылка, и подчёркивание ей ни к чему: оно спорит
   с рамкой карточки и делает список пёстрым. */
.news-heading { margin-bottom: var(--sp-2); }
.news-title { color: var(--accent); font-weight: 700; text-decoration: none; }
.news-title:hover { text-decoration: underline; }
.news-item small { color: var(--text-muted); }

/* Возврат к списку контактов из открытой переписки.

   Кнопка стояла внутри строки с именем собеседника и выглядела там
   случайной. Теперь она над перепиской и в том же виде, что на всех
   страницах: сверху слева, поверх содержимого. Липкой её делает общее
   правило .btn-back — внутри панели переписки это работает так же, как
   на странице. */
.chat-back {
    margin: 0 0 var(--sp-3);
    align-self: flex-start;
}

/* Кнопка возврата к списку контактов стоит вне панели переписки: у той
   своя высота и overflow: hidden, а липкое положение в таком контейнере
   привязано к нему, а не к экрану — кнопка замирала посреди страницы.
   Здесь она ведёт себя как на любой другой странице. */
.chat-back { margin: 0 0 var(--sp-3); }
.chat-back[hidden] { display: none; }


/* --- Полоска в таблице доходов -------------------------------------------
   Столбик вместо графика: библиотека ради одной картинки не нужна, а
   полоска читается сразу и живёт в обеих темах, потому что берёт цвет
   из переменной. */
.bar-cell { width: 30%; min-width: 80px; }
.bar {
    display: block;
    height: 10px;
    min-width: 2px;
    border-radius: var(--radius-pill);
    background: var(--accent);
}


/* --- Домашние задания ------------------------------------------------------
   Задание — это переписка двоих: что задано, что прислали, что сказали в
   ответ. Части разделены отступом и левой чертой, чтобы было видно, где
   чей ход, а не сплошной текст. */
.homework-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
}
.homework-head h3 { margin: 0; }

.homework-part {
    margin: var(--sp-4) 0;
    padding-left: var(--sp-4);
    border-left: 3px solid var(--line);
}
.homework-part h4 { margin-bottom: var(--sp-1); }
/* Замечания преподавателя выделены цветом: их читают первыми. */
.homework-part--review { border-left-color: var(--warning); }

/* Проверка работы: сначала возврат на исправление, под ним принятие.
   Рядом их ставить нельзя — вторая колонка выравнивается по верху, и
   кнопка «Принять» оказывается на уровне поля «Что исправить», читаясь
   как часть возврата. */
.homework-actions {
    margin: var(--sp-4) 0;
    padding-top: var(--sp-4);
    border-top: 1px solid var(--line);
}

/* Принятие — одной строкой: слово «или», оценка и кнопка. Занимает
   немного места, и обе возможности видны разом. */
.homework-accept {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding-top: var(--sp-4);
    border-top: 1px dashed var(--line);
}
.homework-accept label { margin: 0; }
.homework-accept .form-select {
    width: auto;
    min-width: 9rem;
    margin: 0;
}
.homework-accept__or {
    color: var(--text-muted);
    font-size: var(--fs-sm);
    margin-right: var(--sp-2);
}


/* Галочка в расписании — ссылка на человека. Цвет обычного текста, а не
   ссылочный: синяя галочка в сетке читалась бы как ошибка вёрстки. */
.schedule-link {
    display: block;
    font-weight: bold;
    font-size: 1.2em;
    color: inherit;
    text-decoration: none;
}
.schedule-link:hover { color: var(--accent); text-decoration: none; }
/* Группа — не накладка, и клетка не должна читаться как ошибка. */
.schedule-link--group { color: var(--success); }


/* Полоска прямо в строке таблицы, рядом с числом. */
.bar-inline {
    display: inline-block;
    width: 60px;
    height: 8px;
    margin-left: var(--sp-2);
    border-radius: var(--radius-pill);
    background: var(--surface-2);
    overflow: hidden;
    vertical-align: middle;
}
.bar-inline .bar { height: 100%; }


/* --- Сворачиваемый блок ----------------------------------------------------
   Форма фильтра или создания занимает пол-экрана на телефоне, а нужна
   изредка. Прячем её за заголовком.

   Раскрытие через grid-template-rows: высота содержимого заранее
   неизвестна, а max-height пришлось бы задавать с запасом — и тогда
   короткая форма закрывается с задержкой, будто зависла. */
.folding > summary {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    cursor: pointer;
    list-style: none;
    font-family: var(--font-head);
    font-weight: 700;
    color: var(--accent);
    padding: var(--sp-2) 0;
    user-select: none;
}
.folding > summary::-webkit-details-marker { display: none; }
.folding > summary::after {
    content: '▾';
    font-size: .8em;
    transition: transform var(--transition);
}
.folding[open] > summary::after { transform: rotate(180deg); }

.folding-body {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .25s ease;
}
.folding[open] .folding-body { grid-template-rows: 1fr; }
/* Закрытие: атрибут open ещё стоит, но строка уже схлопывается — так
   переход есть на чём проиграть. Снимает атрибут скрипт, по его концу. */
.folding[open][data-closing] .folding-body { grid-template-rows: 0fr; }
.folding-body > div { overflow: hidden; }

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


/* Строки «день и время» при сборке группы: между ними нужна граница —
   иначе на телефоне поля сливаются в один длинный столбец. */
.group-slot + .group-slot {
    padding-top: var(--sp-3);
    border-top: 1px solid var(--line);
}


/* --- Снимок страницы в продающем тексте ------------------------------------
   Показывает то, о чём абзац выше только рассказал. Рамка и тень нужны:
   без них светлый снимок сливается с фоном страницы, и непонятно, где
   он кончается. */
.shot {
    margin: var(--sp-5) 0 0;
}
.shot img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
.shot figcaption {
    margin-top: var(--sp-2);
    color: var(--text-muted);
    font-size: var(--fs-sm);
    text-align: center;
}


/* --- Значок с числом непрочитанного ----------------------------------------
   Раньше показывался восклицательный знак: было видно, что что-то есть,
   но не сколько. Одно уведомление и двадцать выглядели одинаково, и
   заходить приходилось наугад.

   Ширина не фиксирована: «7» и «99+» должны помещаться одинаково
   аккуратно, поэтому круг задан минимальной шириной и скруглением. */
.badge-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.35em;
    height: 1.35em;
    padding: 0 .35em;
    margin-left: var(--sp-1);
    border-radius: var(--radius-pill);
    background: var(--danger);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    line-height: 1;
    vertical-align: middle;
}

/* На бургере значок висит поверх кнопки, а не в строке текста. */
.hamburger .badge-count {
    position: absolute;
    top: 2px;
    right: 2px;
    margin: 0;
}


/* --- Граница непрочитанного в переписке ------------------------------------
   Открыв чат после перерыва, человек видел сплошную ленту и отматывал
   наугад, пытаясь вспомнить, на чём остановился. Черта показывает, где
   начинается новое.

   Ставится один раз — перед первым непрочитанным — и живёт до
   перезагрузки: при следующем открытии сообщения уже прочитаны, и
   отделять нечего. */
.new-messages-mark {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin: var(--sp-4) 0 var(--sp-2);
    color: var(--accent);
    font-size: var(--fs-sm);
    font-weight: 700;
    white-space: nowrap;
}
.new-messages-mark::before,
.new-messages-mark::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--accent);
}

/* Сами новые сообщения — с меткой сбоку. Одной черты мало: пролистав
   вниз, человек теряет её из виду и снова не понимает, где новое. */
.message.is-new { position: relative; }
.message.is-new.other::before {
    content: '';
    position: absolute;
    left: calc(-1 * var(--sp-2));
    top: 4px;
    bottom: 4px;
    width: 3px;
    border-radius: var(--radius-pill);
    background: var(--accent);
}
