/*
 * Общие компоненты кабинетов (ТЗ 5.1.1).
 *
 * Всё, что переиспользуется больше чем одним экраном, живёт здесь, а не
 * в css конкретного экрана — иначе кабинеты визуально разъедутся.
 */

*, *::before, *::after { box-sizing: border-box; }
/* Полоса самой страницы спрятана тем же приёмом, что и у внутренних
   прокручиваемых блоков ниже по файлу (владелец: «уберите вообще везде,
   они не красивые» — сказано отдельно и в общей форме, страница не
   исключение). `scrollbar-gutter: stable» стоял здесь как раз из-за
   видимой полосы — он резервировал под неё место, чтобы виды расписания
   разной высоты (с полосой и без) не дёргали содержимое вбок при
   переключении. Раз полосы больше нет ни у одного вида, резервировать
   стало нечего — оба правила спорили бы за одну и ту же причину, поэтому
   `scrollbar-gutter» убран, а не оставлен рядом. Прокрутка колесом,
   свайпом и с клавиатуры (Home/End/PageDown) не пострадала — прячется
   только сама полоса, `overflow` не менялся. */
html {
  -webkit-text-size-adjust: 100%;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
html::-webkit-scrollbar { display: none; }

body {
  margin: 0;
  /* Признак «страница продолжается ниже» вместо полосы — та же тень
     у края, что и у карточек-таблиц (`.scroll-shade-y» ниже), но только
     нижняя половина: верхний край страницы всегда шапка, там неоткуда
     взяться вопросу «а что было выше». Устроено тем же приёмом с двумя
     фонами: «заглушка» (`local», едет вместе с содержимым, заливка тем
     же цветом фона) сидит у нижнего края ВСЕЙ страницы и лежит поверх
     «тени»; «тень» закреплена не за элементом (`scroll» здесь смысла не
     имеет — body сам не прокручивается, прокручивается вся страница),
     а за окном просмотра (`fixed»), поэтому и остаётся у нижнего края
     экрана, пока заглушка, приезжающая вместе с концом страницы, её не
     накроет. `min-height: 100vh» обязателен: без него на короткой
     странице заглушка сидела бы у конца короткого содержимого высоко
     над низом экрана, а тень (закреплённая за окном) осталась бы
     непокрытой и показывала бы «есть продолжение» там, где прокрутки
     нет вовсе. */
  min-height: 100vh;
  background:
    linear-gradient(rgba(0, 0, 0, 0), var(--bg) 60%) 0 100%,
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .18), rgba(0, 0, 0, 0)) 0 100%;
  background-repeat: no-repeat;
  background-color: var(--bg);
  background-size: 100% 24px, 100% 10px;
  background-attachment: local, fixed;
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* Смена темы — одно из мест, где движение объясняет изменение (ТЗ 5.1.1):
   поверхности перетекают в новую палитру, а не переключаются рывком. */
body, .app-header, .card, .lesson-card, .week-block, .date-chip, .day-empty,
.btn-primary, .btn-secondary, .icon-btn, .btn-today, .search-field,
.search-toggle, .search-toggle__btn, .status-badge, .view-tabs, .view-tabs a,
.theme-switch, .theme-switch__btn, .nav-drawer__logout {
  transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease;
}
/* Колонка отдельно от списка выше: у неё есть ещё и своё сворачивание
   (transform, см. .app-sidebar ниже), а список выше её не касается —
   писать одно и то же свойство `transition` на один элемент дважды
   в двух правилах нельзя, второе просто стёрло бы первое. */
.app-sidebar {
  transition: background-color .15s ease, border-color .15s ease, color .15s ease, box-shadow .15s ease, transform .2s ease;
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

a { color: var(--accent); }

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

/* ---------- скрытая полоса прокрутки с тенью у края ----------
   Владелец: «скроллбары не красивые, убери их везде» — правило сквозное,
   поэтому здесь общим классом, а не в стилях каждого экрана по отдельности.
   Прячется только сама полоса (scrollbar-width/-webkit-scrollbar) — колесо,
   свайп и клавиатура прокручивают контейнер как прежде, страницу это не
   трогает вовсе: html/body сюда не входят, обычная прокрутка страницы
   не сломана.

   Полоса была единственной подсказкой «дальше есть ещё» — без неё
   стол/список на телефоне выглядел бы законченным, хотя справа или
   снизу есть продолжение. Вместо неё — тень у края, которая появляется
   и пропадает сама по мере прокрутки, чистым CSS, без пересчёта в скрипте
   на каждое движение колеса. Приём известный: два фона, закреплённые
   к контенту (`local`) и залитые цветом поверхности, наезжают на два
   фона-тени, закреплённые к контейнеру (`scroll`), — у края, откуда
   прокрутки больше нет, заливка перекрывает тень, а как только край
   уходит, тень открывается.

   Цвет заливки — `--surface`: все нынешние прокручиваемые блоки (таблицы
   в карточках, история баланса, список бокового меню, тело окна занятия)
   лежат на поверхности этого цвета. Понадобится тот же приём на фоне
   страницы (`--bg`) — заводить второй вариант тогда же, не раньше. */
/* Классы применены прямо здесь, к существующим селекторам таблиц
   и прокручиваемых блоков (без правки разметки экранов — им незачем
   знать, что у них теперь спрятана полоса, это забота общих стилей):
   .manage-table-wrap — таблицы справочников (ученики, преподаватели,
   группы, расходы, регулярные расходы, категории), .balance-block
   и .table-scroll — история баланса и история занятий преподавателя,
   .nav-drawer__list и .app-sidebar__list — список телефонной панели
   и постоянной колонки, .lesson-modal__inner — тело окна занятия,
   .card-drawer__body — тело выезжающей справа карточки (ученика
   или группы, задание отдельного шага). */
.scroll-shade-x, .scroll-shade-y,
.manage-table-wrap, .balance-block, .table-scroll,
.nav-drawer__list, .app-sidebar__list, .lesson-modal__inner,
.card-drawer__body {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.scroll-shade-x::-webkit-scrollbar, .scroll-shade-y::-webkit-scrollbar,
.manage-table-wrap::-webkit-scrollbar, .balance-block::-webkit-scrollbar,
.table-scroll::-webkit-scrollbar, .nav-drawer__list::-webkit-scrollbar,
.lesson-modal__inner::-webkit-scrollbar,
.card-drawer__body::-webkit-scrollbar { display: none; }
/* Каждая ось — две пары фонов. «Заглушка» едет вместе с содержимым
   (`local`) и у своего края закрашена цветом поверхности, поэтому пока
   прокрутки в эту сторону нет, она целиком лежит поверх «тени» и прячет
   её. «Тень» закреплена за контейнером (`scroll`), то есть стоит на месте
   всегда — и обнажается, как только заглушка уезжает вместе с контентом. */
/* `touch-action: auto` (по умолчанию) в теории и так разрешает горизонтальную
   прокрутку пальцем, но на практике оставляет браузеру решать по эвристике,
   кому отдать неоднозначное касание: вложенному блоку или странице под ним.
   Мобильный Safari в этом споре проигрывал блоку с растянутой ссылкой строки.
   Поэтому ось называется явно — но ОБЕ оси, а не одна.

   `pan-x` в одиночку здесь был дефектом, и владелец нашёл его сразу: на
   телефоне таблица становится списком карточек (см. manage.css), горизонтали
   в ней нет вовсе, а `pan-x` запрещает вертикальный жест — палец над списком
   переставал прокручивать страницу. Правило, написанное ради прокрутки,
   прокрутку и сломало, потому что называло ось, глядя на широкий экран.
   `pan-x pan-y` снимает неоднозначность, ради которой всё и затевалось,
   и не отнимает ни одного направления: браузер выбирает ось по самому жесту.

   `-webkit-overflow-scrolling: touch` нужен старым WebKit (встроенные
   браузеры мессенджеров на iOS тоже на нём) — без него вложенный (не на
   весь экран) прокручиваемый блок там либо не ловит палец, либо ловит
   без инерции. */
/* `.card.manage-table-wrap` продублирован нарочно: у `.card` ниже по файлу
   та же специфичность, что у голого `.manage-table-wrap`, и его шорткенд
   `background` стирал бы тени у всех таблиц справочников. */
.scroll-shade-x, .manage-table-wrap, .card.manage-table-wrap, .balance-block, .table-scroll {
  touch-action: pan-x pan-y;
  -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, var(--surface) 40%, rgba(0, 0, 0, 0)),
    linear-gradient(to left, var(--surface) 40%, rgba(0, 0, 0, 0)) 100% 0,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .18), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .18), rgba(0, 0, 0, 0)) 100% 0;
  background-repeat: no-repeat;
  background-color: var(--surface);
  background-size: 24px 100%, 24px 100%, 10px 100%, 10px 100%;
  background-attachment: local, local, scroll, scroll;
}
.scroll-shade-y, .nav-drawer__list, .app-sidebar__list, .lesson-modal__inner,
.card-drawer__body {
  background:
    linear-gradient(var(--surface) 40%, rgba(0, 0, 0, 0)),
    linear-gradient(rgba(0, 0, 0, 0), var(--surface) 60%) 0 100%,
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, .18), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, .18), rgba(0, 0, 0, 0)) 0 100%;
  background-repeat: no-repeat;
  background-color: var(--surface);
  background-size: 100% 24px, 100% 24px, 100% 10px, 100% 10px;
  background-attachment: local, local, scroll, scroll;
}

/* ---------- шапка кабинета ---------- */
.app-header {
  position: sticky;
  top: 0;
  z-index: 30;
  /* Высота задана явно: от неё считается смещение прикреплённых шапок
     таблиц (см. --app-header-h). Без этого высота зависела бы от метрик
     шрифта и уезжала бы на доли пикселя. */
  min-height: var(--app-header-h);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Перенос остался на случай будущего пункта в шапке — сейчас в ней
     только кнопка меню и переключатель темы, оба узкие, и на телефоне
     не переносятся, но запрет переноса без причины опаснее его. */
  flex-wrap: wrap;
  gap: 12px;
  padding: 10px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
/* Прилипает на любой ширине (владелец: на длинных экранах — список
   учеников, отчёт за день, недельное расписание — до шапки приходится
   прокручивать всё обратно, особенно заметно с телефона). Раньше ниже
   720px стояла `position: static` — версия шапки с именем и кнопкой
   «Выйти» на телефоне переносилась второй строкой, и её плавающую высоту
   не хотели совмещать со sticky. Та строка переехала в выезжающую панель
   (`.nav-drawer__bottom`) ещё в прошлой правке, в шапке остался один ряд
   из кнопки меню и переключателя темы, который никогда не переносится
   — переносить там больше нечему, и особый случай для узкой ширины
   отпал сам. Прикреплённые шапки таблиц расписания (`--app-header-h`,
   schedule.css/manage.css) читают эту высоту только от 1024px и вовсе
   не видят это правило. */

/* Якорь на разделе внутри страницы (например, «Тревожных сигналов: N»
   в отчёте за день) без этого отступа утыкался бы верхним краем ровно
   в прилипшую шапку и прятался бы под ней после перехода по ссылке —
   раньше шапка на телефоне не была прилипшей, и вопрос не возникал.
   Общее правило, а не точечное под один id: тот же случай встретится
   у любого будущего якоря в содержимом. */
.app-main [id] { scroll-margin-top: calc(var(--app-header-h) + 12px); }
/* ---------- левая колонка разделов (ТЗ этого шага) ----------
   На компьютере — постоянная колонка слева, часть раскладки страницы, а не
   всплывающая панель (владелец: по такой навигации ходят десятки раз за
   сеанс, открывать-закрывать её каждый раз было бы лишним действием). На
   телефоне месту для постоянной колонки взяться неоткуда — там та же самая
   разметка становится выезжающей поверх страницы панелью с затемнением,
   раскрытой нативно через `<details>`/`<summary>`: клик по гамбургеру,
   Enter/Space с клавиатуры, чтение экранным диктором — уже умеет браузер
   сам. Это и есть безопасный откат без скрипта: на телефоне панель без
   `nav-drawer.js` разворачивается обычным блоком под кнопкой, на
   компьютере она и без скрипта уже на месте, потому что ниже 1024px
   правило не читает атрибут `open` вовсе — там колонка всегда показана.
   Скрипт поверх добавляет то, что нативному раскрытию не под силу: Esc,
   клик мимо, фокус, ловушку Tab, кнопку закрытия — и отдельно, только для
   компьютера, — сворачивание в полосу значков с запоминанием выбора между
   страницами (static/js/nav-drawer.js, static/js/sidebar-init.js). См.
   подробности в templates/_nav_drawer.html. */
.nav-drawer { position: relative; }
.nav-drawer > summary { list-style: none; }
.nav-drawer > summary::-webkit-details-marker { display: none; }

.nav-drawer__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-control);
  color: var(--text);
  cursor: pointer;
}
.nav-drawer__toggle:hover { background: var(--bg); }
.nav-drawer__toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.nav-drawer__backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(17, 24, 39, .45);
  /* Выше шапки (z-index: 30) и переключателя темы (40) — панель разделов
     общая для любого экрана и обязана перекрывать любую шапку под собой;
     ниже тоста (60), чтобы подтверждение действия было видно и поверх
     открытой панели. */
  z-index: 50;
}
.nav-drawer[open] .nav-drawer__backdrop { display: block; }

/* Панель — отдельный парящий слой на телефоне, а не глухая плита от края
   до края (владелец: «более удобное и современное... как бы парило»).
   Отступ со всех четырёх сторон, скругление той же карточечной меры, что
   и everywhere в кабинете (`--radius-card`), и тень `--shadow-float» —
   она в проекте зарезервирована именно за тем, что действительно
   приподнято над плоскостью (окна, выпадающие списки, тосты). На
   компьютере (медиазапрос ниже) панель, наоборот, становится частью
   обычной раскладки — плоской колонкой без тени и без парения: она там
   не поверх страницы, а рядом с ней. */
.nav-drawer__panel {
  position: fixed;
  top: 12px;
  bottom: 12px;
  left: 12px;
  z-index: 51;
  /* На широком экране — фиксированные 320px с большим полем затемнения
     справа; на узком — почти вся ширина, но 12px слева и 12px справа
     остаются всегда, иначе теряется сам смысл «парит», а не переезжает
     в глухую плиту вплоть до противоположного края. */
  width: min(320px, calc(100vw - 24px));
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-float);
  display: flex;
  flex-direction: column;
  /* Без скрипта панель уже здесь, в CSS, выезжает по нажатию — переход
     объясняет появление и исчезает по правилу уменьшенного движения ниже.
     `left: 12px` — часть неподвижного положения панели, а не анимации:
     translateX(-100%) сдвигает её ровно на собственную ширину и прячет
     точно за левый край экрана независимо от отступа. */
  transform: translateX(-100%);
  transition: transform .2s ease;
}
.nav-drawer[open] .nav-drawer__panel { transform: translateX(0); }
/* Отдельного правила для уменьшенного движения здесь не нужно: общее
   в начале файла (`*, *::before, *::after { transition-duration: .001ms
   !important }`) уже гасит этот переход, как и любой другой. */
/* На настоящем телефоне 320px превращаются в узкую полоску текста рядом
   с широким полем затемнения — владелец прямо просил «почти всю ширину».
   Фиксированный предел уступает место отступу: снова 12px с обеих сторон,
   но теперь считанным от самой ширины экрана, а не от карточечных 320px. */
@media (max-width: 479.98px) {
  .nav-drawer__panel { width: calc(100vw - 24px); }
}
/* Ниже 1024px панель по-прежнему выезжающая — блокировать прокрутку
   страницы, пока она открыта, нужно только там; на компьютере колонка
   не перекрывает страницу, и запирать прокрутку не для чего. */
@media (max-width: 1023.98px) {
  :root:has(.nav-drawer[open]) { overflow: hidden; }
}

/* Ниже 1024px показана телефонная панель (.nav-drawer), выше — постоянная
   колонка (.app-sidebar, следующий блок стилей); одновременно виден ровно
   один из двух, показывать оба или добиваться того же в одной разметке
   не вышло — раскрытие `<details>` без атрибута `open` браузер не рисует
   ни при каком CSS (см. комментарий в _nav_drawer.html), поэтому дальше
   не единая гибридная панель, а две простые оболочки. */
@media (min-width: 1024px) {
  .nav-drawer { display: none; }
}

/* ---------- компьютер: постоянная колонка (.app-sidebar) ----------
   Простое дерево тегов, не `<details>`: открывать-закрывать её не нужно
   вовсе, только сворачивать в полосу значков — а раскрытие `<details>`
   от атрибута `open` для этого не годится (см. выше). Скрыта на телефоне
   тем же медиазапросом наоборот — там место занимает .nav-drawer. */
.app-sidebar {
  display: none;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  /* Выше шапки (30): раньше это было неважно — развёрнутая колонка
     кончается ровно там, где начинается отступ шапки, они не пересекались
     ни разу. Раскрытие по наведению (ниже) впервые сталкивает их —
     колонка на время наведения ложится поверх первых 208px шапки, а
     не только содержимого под ней, и обязана рисоваться над её фоном,
     а не под ним. Ниже переключателя темы (40) и телефонной панели
     (50/51) — с ними колонка никогда не пересекается: тема плавает
     только на экране входа, где колонки нет вовсе, а телефонная панель
     живёт ниже 1024px, где эта колонка скрыта. */
  z-index: 35;
  width: 272px;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
  /* Ширина всегда 272px, свёрнуто или нет — «сворачивание» её не трогает
     вовсе (см. ниже), только translateX. */
  transform: translateX(0);
}
@media (min-width: 1024px) {
  .app-sidebar { display: flex; }
  /* Колонка занимает свою полосу постоянно — шапке и содержимому нужен
     тот же отступ, иначе они рисуются под ней. Отступ переключается
     мгновенно классом (has-sidebar/nav-collapsed выше), без перехода:
     transition на width, margin, padding и left этой колонки нет и не
     будет — на недельной сетке расписания (сотни абсолютно
     спозиционированных блоков за отступом .app-main) такой переход
     пересчитывал бы раскладку всей страницы на каждый кадр, а не
     проигрывал анимацию, отсюда и была прошлая правка «анимацию убрать
     вовсе». Владелец анимацию вернул, но переход теперь идёт по transform,
     который раскладку не трогает: колонка физически остаётся 272px и
     уезжает влево на 272 − 64 = 208px, поэтому визуально свёрнутой
     остаётся её собственная правая четверть (64px, border-right колонки
     как раз на этой границе) — то же самое место, где обрывается отступ
     содержимого. Довесок ниже разворачивает содержимое каждой строки
     к правому краю этой же 272-пиксельной строки, чтобы значки попадали
     в видимую четверть, а не пропадали вместе с подписями за левым краем. */
  html.nav-collapsed .app-sidebar { transform: translateX(-208px); }

  /* Через `html.has-sidebar`, а не голым классом: `.app-main» ниже в файле
     уже несёт свой `margin: 0 auto` (центрирование широкого контента),
     и тот же `margin-left» без запаса в специфичности проиграл бы порядку
     объявлений и снова стал бы «auto». Класс `has-sidebar» — тот же
     признак, что и в base.html (`user.is_authenticated»): отступ обязан
     зависеть от того, есть ли колонка на этой странице, а не только
     от ширины экрана — на экране входа колонки нет ни при какой ширине,
     а этот медиазапрос читается от 1024px независимо от нашего условия. */
  html.has-sidebar .app-header, html.has-sidebar .app-main { margin-left: 272px; }
  html.has-sidebar.nav-collapsed .app-header,
  html.has-sidebar.nav-collapsed .app-main { margin-left: 64px; }

  /* Через `html`: ниже в файле у `.nav-drawer__collapse` есть базовое
     `display: none» (кнопка нужна только на компьютере), и голому классу
     здесь не хватило бы места в порядке объявлений, чтобы его перебить. */
  html .nav-drawer__collapse { display: inline-flex; }
  html.nav-collapsed .nav-drawer__collapse svg { transform: rotate(180deg); }

  /* Подпись прячет CSS, а не разметка: подпункты внутри «Финансов»
     остаются в DOM и достижимы (проверка достижимости обходит саму
     разметку, не её видимый вид), колонка просто не становится шире.
     Знак «И» (`.nav-drawer__brand`) в свёрнутой шапке тоже прячется —
     иначе он перекрывался бы с кнопкой разворота: на 64 видимых пикселях
     места на оба нет, а кнопка не может пропасть — без неё колонку не
     развернуть обратно. До «Расписания», куда ведёт знак, и так ведёт
     первый пункт списка ниже. */
  html.nav-collapsed .nav-drawer__label,
  html.nav-collapsed .nav-drawer__brand-text,
  html.nav-collapsed .nav-drawer__user-name,
  html.nav-collapsed .app-sidebar .nav-drawer__brand { display: none; }
  /* Значки, подсветка и кнопка разворота обязаны стоять на одной оси —
     середине видимой четверти (64px). `justify-content: flex-end» этого
     не даёт: строка-заголовок (`.app-sidebar__head`) прижата к правому
     краю одним слоем паддинга (8px), а строка пункта меню —
     двумя вложенными (8px у списка `.app-sidebar__list» + 8px у самой
     `.nav-drawer__link»), и значок оказывается на 8px левее кнопки —
     ровно перекос со снимка владельца. Заплатка «сдвинуть паддинг на
     глаз» пережила бы только этот один разбор; правильно — перестать
     мерить отступами от правого края и вместо этого явно объявить сам
     позиционирующий блок (`.app-sidebar__head`, `.app-sidebar__list`)
     шириной ровно в видимую четверть и прижать его к правому краю
     272-пиксельной колонки (`align-self: flex-end»). Тогда `justify-
     content: center» центрирует значок и кнопку в одной и той же
     64-пиксельной рамке, и вложенные паддинги (списка и самой ссылки)
     ничего не портят, пока остаются симметричными — они лишь сужают
     рамку с двух сторон поровну, не сдвигая её середину. */
  /* `.nav-drawer__bottom» (пароль, выход) — тот же перекос третьим
     местом: без своей ширины его ссылки центрировались бы в исходной
     272-пиксельной строке, то есть на 208px левее видимой четверти,
     значки пропадали бы за левым краем колонки вовсе. */
  html.nav-collapsed .app-sidebar__head,
  html.nav-collapsed .app-sidebar__list,
  html.nav-collapsed .nav-drawer__bottom { width: 64px; align-self: flex-end; }
  html.nav-collapsed .app-sidebar__head { justify-content: center; padding: 16px 8px; }
  html.nav-collapsed .nav-drawer__link,
  html.nav-collapsed .nav-drawer__section-title { justify-content: center; padding: 8px; }
  html.nav-collapsed .nav-drawer__section-title::after { display: none; }
  /* Отступ вложенности подпунктов «Финансов» (`padding-left: 14px» —
     нужен только затем, чтобы в развёрнутом виде показать иерархию
     подписью правее родителя) в свёрнутой полосе не отменяется ничем —
     он односторонний (только слева), и сдвигает середину строки вправо
     на его половину: значки «Сводка»/«Расходы»/«Вознаграждения» сидели
     на 7px правее оси, на которой стоят остальные значки и кнопка. */
  html.nav-collapsed .nav-drawer__section-body { padding-left: 0; }
}
/* Раскрытие по наведению (владелец: «пока мышь в этом блоке — должно
   разворачиваться»). Только для мыши — `hover: hover` и `pointer: fine`
   исключают телефон и планшет, где нет «ухода» указателя и колонка
   осталась бы залипшей развёрнутой после первого касания. Тот же приём,
   что и у сворачивания, — translateX(0) возвращает колонку на обычное
   место, и это визуально «поверх» содержимого, а не раздвигает его:
   отступ (.app-main/.app-header) остаётся мгновенно выставленным на
   64px, колонка на время наведения просто перекрывает первые 208px
   страницы, z-index у нее выше. Состояние временное и чисто CSS —
   localStorage и класс `nav-collapsed` меняет только кнопка
   (nav-drawer.js), наведение их не трогает: убрать мышь — и колонка
   вернётся в сохранённый свёрнутый вид сама, без участия скрипта. */
@media (min-width: 1024px) and (hover: hover) and (pointer: fine) {
  html.nav-collapsed .app-sidebar:hover {
    transform: translateX(0);
    /* Тень ровно здесь и нигде больше (владелец: без неё панель «читается
       не как всплывающая, а как обрезанное содержимое» — её правый край
       проходил ровно по тексту страницы, ничем не отделённый). В обычном
       развёрнутом виде и в свёрнутой полосе колонка никогда не лежит
       поверх содержимого — её край совпадает с началом отступа
       (`margin-left`), там достаточно волоска границы (`border-right`,
       правило дизайн-системы «тень только у по-настоящему приподнятого»).
       Здесь же, единственный раз, колонка на время наведения буквально
       лежит поверх первых 208px страницы — то самое приподнятое. Тень
       направленная (сдвиг вправо), а не общая по контуру, как
       `--shadow-float` у карточек и телефонной панели: у этой колонки
       открыт только правый край, три остальных упираются в края экрана,
       и тень там попросту не видна. Готового токена под направленную тень
       в проекте нет — заводить его в tokens.css ради одного места нельзя,
       правка объявлена в границах этого файла, поэтому цвет и светлой,
       и тёмной версий взят по образцу `--shadow-float` (тот же тёмный
       нейтральный у него в светлой теме, та же непрозрачная чернота
       в тёмной — см. tokens.css), просто пересобран в двухслойную тень
       со сдвигом по X, а не во все стороны. */
    box-shadow: 6px 0 24px rgba(17, 24, 39, .16), 2px 0 6px rgba(17, 24, 39, .08);
  }
  /* Ширину и прижатие к правому краю (выше, `width: 64px; align-self:
     flex-end») раскрытие по наведению обязано снять — иначе значки
     остались бы центрированы в узкой 64-пиксельной рамке, а подписи
     рядом с ними растянулись бы куда шире неё, вместо разворота на всю
     колонку. */
  html.nav-collapsed .app-sidebar:hover .app-sidebar__head,
  html.nav-collapsed .app-sidebar:hover .app-sidebar__list,
  html.nav-collapsed .app-sidebar:hover .nav-drawer__bottom { width: auto; align-self: stretch; }
  html.nav-collapsed .app-sidebar:hover .app-sidebar__head { justify-content: flex-start; padding: 16px; }
  html.nav-collapsed .app-sidebar:hover .nav-drawer__link,
  html.nav-collapsed .app-sidebar:hover .nav-drawer__section-title { justify-content: flex-start; padding: 8px 12px; }
  html.nav-collapsed .app-sidebar:hover .nav-drawer__section-title::after { display: block; }
  html.nav-collapsed .app-sidebar:hover .nav-drawer__section-body { padding-left: 14px; }
  html.nav-collapsed .app-sidebar:hover .nav-drawer__label,
  html.nav-collapsed .app-sidebar:hover .nav-drawer__user-name { display: block; }
  html.nav-collapsed .app-sidebar:hover .nav-drawer__brand { display: flex; }
  html.nav-collapsed .app-sidebar:hover .nav-drawer__brand-text { display: inline; }

  /* Тёмная тема: чёрная тень на почти чёрном фоне не видна, если оставить
     ту же прозрачность, что в светлой, — тот же случай, что уже решён
     у `--shadow-float» в tokens.css (там непрозрачность у тёмного варианта
     в разы выше, а не светлее сам цвет). Системная тёмная тема и явный
     выбор человеком идут раздельно, тем же порядком, что и в tokens.css:
     системная — внутри своего media, явная — снаружи, перекрывает и то,
     и другое по специфичности атрибута. */
  @media (prefers-color-scheme: dark) {
    html.nav-collapsed:not([data-theme="light"]) .app-sidebar:hover {
      box-shadow: 6px 0 28px rgba(0, 0, 0, .6), 2px 0 8px rgba(0, 0, 0, .4);
    }
  }
  html[data-theme="dark"].nav-collapsed .app-sidebar:hover {
    box-shadow: 6px 0 28px rgba(0, 0, 0, .6), 2px 0 8px rgba(0, 0, 0, .4);
  }
}
.app-sidebar__head {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: 16px;
  border-bottom: 1px solid var(--border);
}
.app-sidebar__list {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 8px;
  overflow-y: auto;
}

.nav-drawer__head {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding: 16px;
  border-bottom: 1px solid var(--border);
}
.nav-drawer__brand {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-right: auto;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  min-width: 0;
}
.nav-drawer__brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-control);
  background: var(--accent-tint);
  color: var(--accent);
  font-size: 14px;
}
.nav-drawer__collapse {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  border: none;
  border-radius: var(--radius-control);
  background: none;
  color: var(--text-2);
  cursor: pointer;
}
.nav-drawer__collapse:hover { background: var(--bg); color: var(--text); }
.nav-drawer__collapse:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nav-drawer__collapse svg { transition: transform .15s ease; }

.nav-drawer__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border: none;
  border-radius: var(--radius-control);
  background: none;
  color: var(--text-2);
  cursor: pointer;
}
.nav-drawer__close:hover { background: var(--bg); color: var(--text); }
.nav-drawer__close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Список длиннее экрана телефона у директора (все разделы) — прокручивается
   сам список, а не вся панель, иначе заголовок и низ уезжали бы вместе
   с ним. Полоса прокрутки спрятана общим правилом (см. начало файла);
   тень у края появляется из тех же общих свойств `.nav-drawer__list`. */
.nav-drawer__list {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: 8px;
  overflow-y: auto;
}
.nav-drawer__link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 8px 12px;
  border-radius: var(--radius-control);
  color: var(--text);
  text-decoration: none;
  font-size: 14px;
  font-family: var(--font);
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
}
/* Владелец: наведение было «слишком бледное». `--bg` почти не отличался
   от белой поверхности колонки (`--surface`) — сменили на `--border»,
   нейтральный и заметно темнее, но не синий: текущий раздел держит
   акцентную подложку (`--accent-tint» ниже), и два состояния не должны
   спориться одним и тем же оттенком. Наведённый текущий пункт остаётся
   акцентным — `.is-current» объявлен ниже по файлу и той же специфичности,
   и при равенстве в CSS побеждает более позднее правило. */
.nav-drawer__link:hover { background: var(--border); }
.nav-drawer__link:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.nav-drawer__link.is-current { background: var(--accent-tint); color: var(--accent); font-weight: 600; }
.nav-drawer__icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  color: inherit;
}
/* Цифра именинников — квадратик прямо на значке (владелец: «рисуй прямо
   на пункте меню»), а не рядом с подписью: подпись пропадает в свёрнутой
   колонке (см. правило `html.nav-collapsed .nav-drawer__label` выше),
   значок — нет, и счётчик обязан остаться виден в обоих видах меню без
   второго правила под свёрнутое состояние. */
.nav-drawer__count {
  position: absolute;
  top: -5px;
  right: -7px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  border-radius: 4px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
  line-height: 14px;
  text-align: center;
}
.nav-drawer__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-drawer__section > summary.nav-drawer__section-title { list-style: none; }
.nav-drawer__section > summary.nav-drawer__section-title::-webkit-details-marker { display: none; }
.nav-drawer__section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  margin: 4px 0 0;
  padding: 8px 12px;
  border-radius: var(--radius-control);
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
}
.nav-drawer__section-title:hover { background: var(--border); }
.nav-drawer__section-title:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Тем же видом, что у пунктов верхнего уровня (`.nav-drawer__link.is-current`,
   выше) — раньше «Финансы» подсвечивались только цветом текста, без
   подложки, и владелец не считал это подсветкой вовсе. */
.nav-drawer__section-title.is-current { background: var(--accent-tint); color: var(--accent); font-weight: 700; }
.nav-drawer__section-title .nav-drawer__label { flex: 1 1 auto; }
/* Стрелка раскрытия — свой уголок без картинки, той же техникой, что
   и рамка-«галочка» на чекбоксах в manage.css: две границы под 45°.
   Повёрнута вправо (свёрнуто) или вниз (раскрыто) чистым CSS от
   атрибута `[open]`, без второго набора вёрстки под открытое состояние. */
.nav-drawer__section-title::after {
  content: "";
  width: 7px;
  height: 7px;
  flex: 0 0 auto;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
}
.nav-drawer__section[open] > .nav-drawer__section-title::after { transform: rotate(45deg); }
.nav-drawer__section-body {
  display: flex;
  flex-direction: column;
  padding-left: 14px;
}

.nav-drawer__bottom {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  padding: 8px;
  border-top: 1px solid var(--border);
}
.nav-drawer__user-name {
  padding: 6px 12px 2px;
  font-size: 12px;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nav-drawer__logout-form { display: contents; }

/* ---------- переключатель темы (системная / светлая / тёмная) ---------- */
.theme-switch {
  display: inline-flex;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  padding: 2px;
  gap: 2px;
  flex: 0 0 auto;
}
.theme-switch__btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  border-radius: 6px;
  color: var(--text-2);
  cursor: pointer;
  padding: 0;
}
.theme-switch__btn:hover { background: var(--bg); }
.theme-switch__btn.is-active { background: var(--accent-tint); color: var(--accent); }
/* Состояние не держится только на цвете — у активного варианта есть ещё
   заливка-подложка и контур фокуса, различимые и без цветовосприятия. */
.theme-switch--floating {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 40;
  background: var(--surface);
}

.app-main { padding: 16px; max-width: 1400px; margin: 0 auto; }
@media (min-width: 1024px) {
  .app-main { padding: 20px 24px; }
}

/* Путь «Кабинет → раздел → страница» (см. templates/_breadcrumbs.html) —
   каждый шаг свой элемент, поэтому подпись собирается флексом с зазором,
   а не пробелами внутри одной строки текста. flex-wrap — на телефоне
   длинный путь переносится по шагам, а не растягивает шапку экрана вбок
   и не обрезается. */
.crumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--text-2);
  margin: 0 0 2px;
}
.crumb-sep { color: var(--text-2); }
.crumb-text { color: inherit; }
.page-title { font-size: 24px; font-weight: 700; margin: 0 0 12px; letter-spacing: -.01em; }
@media (min-width: 1024px) {
  .page-title { font-size: 30px; }
}

/* ---------- кнопки ---------- */
.btn-primary, .btn-secondary {
  /* Часть кнопок — ссылки (<a class="btn-secondary">Отмена</a>), а браузер
     подчёркивает ссылки по умолчанию: рядом с настоящей кнопкой это
     читается как дефект вёрстки. */
  text-decoration: none;
  font-family: var(--font);
  font-size: 14px;
  cursor: pointer;
  border-radius: var(--radius-control);
  /* Подпись не переносится, пока помещается, и переносится, когда нет.
     `nowrap` без оговорки хорош для коротких кнопок («Сохранить»), но
     подпись у кнопки бывает и посчитанной: «Пересчитать вознаграждение
     за 6 занятий: было 4500 ₽, станет 4800 ₽» — это 532 пикселя, и на
     телефоне такая кнопка уезжает за край экрана вместе с ценой решения.
     Длину такой подписи не угадать заранее, поэтому запрет переноса снят
     здесь, а не подобран по месту: цена — изредка двухстрочная кнопка,
     плата за обратное — обрезанное число на экране, меняющем деньги. */
  overflow-wrap: break-word;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.btn-primary {
  font-weight: 600;
  padding: 0 16px;
  min-height: 44px;
  border: none;
  background: var(--accent);
  color: var(--on-accent);
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary {
  font-weight: 500;
  padding: 0 14px;
  min-height: 40px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.btn-secondary:hover { background: var(--bg); }
/* Неактивная, но видимая кнопка (ТЗ 5.1.1: не рисовать рабочую кнопку,
   которая ничего не делает, но и не прятать её) — приглушена и без
   указателя-руки, чтобы отличаться от рабочей на вид, а не только курсором. */
.btn-primary:disabled, .btn-secondary:disabled {
  opacity: .5;
  cursor: not-allowed;
}
.btn-primary:disabled:hover { background: var(--accent); }
.btn-secondary:disabled:hover { background: var(--surface); }

.icon-btn {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-control);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}
.icon-btn:hover { background: var(--bg); }

/* ---------- карточка ---------- */
/* Волосок границы вместо тени — карточка не висит над плоскостью, она
   лежит в общем потоке страницы рядом с другими. Тень (--shadow-float)
   зарезервирована за тем, что от плоскости действительно приподнято:
   .card--floating (экран входа), выпадающие списки, тосты, окна. */
/* Отступ внутри карточки — здесь, а не на совести каждого экрана: голая
   `.card` без своего класса padding'а не имела вовсе, и заголовок с текстом
   прилипал к краю (снимок «Поправить вознаграждение», где план был
   переделать эту дыру в четвёртый раз). Экрану, которому нужен другой
   отступ или его отсутствие — таблица списка, ряд карточек сводки, —
   даёт это своим классом (`.manage-table-wrap`, `.finance-card` и т. п.),
   и он выигрывает по специфичности у одной этой строки. */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 16px;
}
.card--floating { box-shadow: var(--shadow-float); }

/* ---------- статусная метка ---------- */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.status-badge svg { flex: 0 0 auto; }
.status-badge.b-planned { background: var(--neutral-tint); color: var(--neutral-text); }
.status-badge.b-held { background: var(--success-tint); color: var(--success-text); }
.status-badge.b-cancelled { background: var(--warning-tint); color: var(--warning-text); }
.status-badge.b-moved { background: var(--warning-tint); color: var(--warning-text); }
.status-badge.b-overdue { background: var(--error-tint); color: var(--error-text); }
.status-badge.b-no_show { background: var(--error-tint); color: var(--error-text); }

/* табличные цифры — везде, где суммы и время */
.tabular-nums { font-variant-numeric: tabular-nums; }

/* ---------- пустое состояние ---------- */
.empty-state {
  padding: 48px 24px;
  text-align: center;
  color: var(--text-2);
}
.empty-state .big-icon { color: var(--text-2); opacity: .5; margin-bottom: 12px; }
.empty-state h3 { font-size: 20px; color: var(--text); margin: 0 0 6px; }
.empty-state p { font-size: 14px; margin: 0 0 16px; }

[hidden] { display: none !important; }

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

/* ---------- подтверждение действия ---------- */
/* Показывается после сохранения: без видимого подтверждения непонятно,
   прошло действие или нет. Не всплывашка — сообщение должно пережить
   переадресацию и остаться на экране, пока человек не уйдёт со страницы. */
.app-flash { margin: 0 0 16px; display: flex; flex-direction: column; gap: 8px; }
.app-flash__item {
  margin: 0;
  padding: 10px 14px;
  border-radius: var(--radius-control);
  font-size: 14px;
  background: var(--success-tint);
  color: var(--success-text);
}
.app-flash__item--error { background: var(--error-tint); color: var(--error-text); }
.app-flash__item--warning { background: var(--warning-tint); color: var(--warning-text); }
.app-flash__item--info { background: var(--accent-tint); color: var(--accent); }

/* ---------- поле поиска ---------- */
/* Общее для расписания и списка учеников: одно поле с лупой, свой
   крестик очистки и подпись «не нашли». Живёт здесь, а не в стилях
   конкретного экрана, потому что экранов с ним уже два, а вторая
   копия тех же правил однажды разойдётся с первой — и поле начнёт
   выглядеть по-разному в двух местах кабинета. Сегментированный
   переключатель «Ученики / Преподаватели» при этом остался
   в schedule.css: он есть только там. */
.search-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding-bottom: 12px;
}

.search-field {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--text-2);
  flex: 1 1 220px;
  max-width: 320px;
}
.search-field svg { flex: 0 0 auto; }
.search-field input {
  flex: 1 1 auto;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: var(--font);
  font-size: 14px;
  color: var(--text);
}
.search-field input::placeholder { color: var(--text-2); }
/* Крестик очистки. Область нажатия 24px при значке 12px — по значку такого
   размера пальцем не попасть, а расти полю при этом нельзя. */
.search-clear {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-right: -4px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.search-clear:hover { background: var(--accent-tint); color: var(--accent); }
.search-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.search-field:focus-within { border-color: var(--accent); }

.search-hint {
  margin: 0;
  font-size: 13px;
  color: var(--error);
  font-weight: 500;
}
